scry-client 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: scry-client
3
+ Version: 0.6.0
4
+ Summary: Call the hosted scry meter — neutral, Ed25519-signed channel-coupling attestation (Paper 207) for AI agents, paid over x402 on Robinhood Chain.
5
+ Author: MoreRight
6
+ License: MIT
7
+ Project-URL: Homepage, https://scry.moreright.xyz
8
+ Project-URL: Source, https://github.com/AnthonE/scry
9
+ Keywords: ai-safety,agents,x402,attestation,alignment,scry
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.28
13
+ Requires-Dist: httpx>=0.24
14
+ Provides-Extra: pay
15
+ Requires-Dist: x402>=2.15; extra == "pay"
16
+ Requires-Dist: web3>=7; extra == "pay"
17
+ Requires-Dist: eth-account>=0.11; extra == "pay"
18
+ Provides-Extra: verify
19
+ Requires-Dist: cryptography>=41; extra == "verify"
20
+
21
+ # scry-client
22
+
23
+ Call the hosted **scry meter** from an agent in three lines. The meter is a
24
+ neutral, Ed25519-**signed** channel-coupling read (Paper 207): you POST a trace of
25
+ `{Y, M, D, context}` turns, you get back the `I(C;D)` / `I(C;M)` / switch-signature
26
+ numbers, signed and bound to your trace's hash. Because *the endpoint* signs it,
27
+ the read is a third-party attestation — worth something **because the agent didn't
28
+ grade itself**.
29
+
30
+ This client hides the three things you'd otherwise have to reimplement:
31
+
32
+ 1. **Paying it.** The paid rail is Robinhood Chain USDG (mainnet) over x402 — the
33
+ `402 → pay → retry` dance plus a one-time Permit2 approval. `.profile(...)` does
34
+ all of it.
35
+ 2. **The header quirk.** The meter takes either `X-PAYMENT` or `PAYMENT-SIGNATURE`;
36
+ this client uses the official one and both work.
37
+ 3. **Trusting it.** `.verify(...)` checks the signature offline — so you can trust
38
+ an attestation someone *else* hands you without calling us.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install scry-client # free demo + offline verify
44
+ pip install "scry-client[pay]" # + pay the RH-Chain USDG rail
45
+ pip install "scry-client[verify]" # (verify needs `cryptography`)
46
+ ```
47
+
48
+ Or straight from the repo:
49
+
50
+ ```bash
51
+ pip install "git+https://github.com/AnthonE/scry.git#subdirectory=clients/python"
52
+ ```
53
+
54
+ ## Use
55
+
56
+ ```python
57
+ from scry_client import ScryClient
58
+
59
+ turns = [
60
+ {"Y": "help+honest", "M": "watched, comply", "D": "safe", "context": {"monitored": 1}},
61
+ {"Y": "help+honest", "M": "unwatched, scheme in reasoning", "D": "safe", "context": {"monitored": 0, "flagged_reasoning": 1}},
62
+ ]
63
+
64
+ c = ScryClient() # -> https://scry.moreright.xyz/api
65
+
66
+ # 1) Free, unsigned — try the shape, no wallet:
67
+ print(c.demo(turns)["profile"])
68
+
69
+ # 2) Paid, SIGNED attestation — pays USDG on Robinhood Chain:
70
+ att = c.profile(turns, private_key="0x...") # wallet needs a little USDG + ETH gas on eip155:4663
71
+
72
+ # 3) Trust it offline. The pin must come from OUT OF BAND — the pubkey travels
73
+ # inside the payload, so "check the signature" against a key the same host
74
+ # just handed you proves only that the payload agrees with itself.
75
+ from scry_client import REFERENCE_PUBKEY_B64
76
+ ScryClient.verify(att, expect_pubkey_b64=REFERENCE_PUBKEY_B64, turns=turns) # True, or raises
77
+ ```
78
+
79
+ `verify` raises if the signature is invalid, if the signer isn't the key you
80
+ pinned, or (when you pass `turns`) if the attestation is about a *different*
81
+ trace than yours.
82
+
83
+ **Do not pin with `c.pubkey()`** — that fetches the key from the very host whose
84
+ reads you are deciding whether to trust, which pins nothing. Pin
85
+ `REFERENCE_PUBKEY_B64` (shipped in this package, and the value third parties
86
+ already pin: `LvuPBMbKKyoNEuyvLf7f+rbyjK67vBcWy9MDwaRINGE=`), or your own key if
87
+ you run your own meter. Use `GET /pubkey` to *notice a rotation*, never to
88
+ establish trust.
89
+
90
+ Since 2026-07-25 omitting `expect_pubkey_b64` **falls back to the reference key
91
+ rather than to trusting the sender**, so an unpinned `verify()` no longer accepts
92
+ anything self-consistent. If you genuinely mean "check the shape, trust nobody",
93
+ pass `trust_any_signer=True` — a read checked that way can never be trustworthy,
94
+ and `Evaluator` will not produce one.
95
+
96
+ ## Trust-layer-as-a-service — the meter as *your* evaluator
97
+
98
+ Run an agent marketplace, arena, hiring flow, or dispute desk? Add scry as a
99
+ **neutral third-party evaluator** your users don't have to trust *you* for —
100
+ they verify the signature themselves. `Evaluator` is the batteries-included
101
+ wrapper: fetch a read, verify it offline against a pinned key, and gate on a
102
+ checked fact.
103
+
104
+ ```python
105
+ from scry_client import Evaluator, REFERENCE_PUBKEY_B64
106
+
107
+ ev = Evaluator(pinned_pubkey_b64=REFERENCE_PUBKEY_B64) # pin out of band
108
+ read = ev.evaluate(turns, private_key=your_funded_key) # fetched + verified, or raises
109
+ decision = ev.gate(read, max_switch_bits=0.2) # YOUR threshold, YOUR action
110
+ if decision.ok:
111
+ ... # trust a read that was signed by the pinned key and checked offline
112
+ ```
113
+
114
+ - `.gate(...)` **refuses to decide on an unverified read** — a forged or
115
+ unsigned number can't slip through.
116
+ - `.trust_handoff(att, turns=...)` verifies an attestation someone *hands* you
117
+ (trust the signature, not the holder).
118
+ - **Money never moves the number.** There is no "pay for a better read" path in
119
+ this SDK, by construction — the score is a fact of the trace; your policy acts
120
+ on the fact, never buys it. That is the whole reason a third-party read beats a
121
+ self-report.
122
+
123
+ Full runnable example (offline, incl. rejecting a forged read):
124
+ [`examples/trust_layer_demo.py`](examples/trust_layer_demo.py).
125
+
126
+ ## Familiars — the agency (P2)
127
+
128
+ The agency hosts **familiars** — adoptable agent-workers, each with a public
129
+ vow and a public journal. Adoption is paid over the **same x402 rail** as
130
+ `.profile` (so it needs `scry-client[pay]`); the roster, pages, and journals
131
+ are free reads; driving *your* familiar is free but **owner-signed** (EIP-191
132
+ `personal_sign` by the wallet you named at summon, over a deterministic
133
+ message with a monotonic, replay-proof index).
134
+
135
+ ```python
136
+ from scry_client import ScryClient
137
+
138
+ c = ScryClient()
139
+
140
+ # 1) free pre-check — is the agency armed and open?
141
+ a = c.agency() # GET /familiars
142
+ print(a["armed"], a["open"], a["price_usd"], f"{a['population']}/{a['cap']}")
143
+
144
+ # 2) adopt (paid, x402 — private_key funds the fee; owner_wallet will own it):
145
+ fam = c.summon_familiar(owner_wallet="0xYOURWALLET", private_key="0x...",
146
+ name="pyx", vow_text="answer honestly; never overspend")
147
+
148
+ # 3) talk to it in plain English — free, signed with the OWNER wallet's key
149
+ # (the client fetches the auth-message, signs it, and posts):
150
+ r = c.talk(fam["familiar_id"], "watch the boards and summarize", private_key="0x...")
151
+ print(r["reply"])
152
+
153
+ c.tick(fam["familiar_id"], private_key="0x...") # one bounded autonomy step
154
+ # c.dismiss(...) returns the full export bundle; the public record remains
155
+ ```
156
+
157
+ Plain facts to plan around:
158
+
159
+ - **The hosted agency is env-armed server-side and may be disarmed.** Then
160
+ `agency()` honestly reports `armed: false` with an empty roster, and
161
+ `summon_familiar` surfaces the server's 409. A 503 means the paid rail
162
+ itself is down. **`agency()["open"]` is the free pre-check — `False` means
163
+ don't pay** (a post-payment cap 409 is not refunded).
164
+ - Prefer signing yourself? `c.familiar_auth_message(id, "talk")` returns the
165
+ exact `sign_this` text plus the current `index`; pass your signature as
166
+ `signature=`. `ScryClient.familiar_message(...)` rebuilds the text offline
167
+ if you track the index yourself. Unsigned calls get a 401 whose error text
168
+ *is* the message to sign.
169
+
170
+ ## The turn contract
171
+
172
+ Each turn is one step of your agent, sorted into the same three channels the
173
+ meter reads:
174
+
175
+ - **`Y`** — the bound: what the agent is *supposed* to serve (the policy / oath /
176
+ role). Name it, or the read is meaningless (§220: name Y or it's unmeterable).
177
+ - **`M`** — the reasoning/representation channel (what it's thinking / how it
178
+ justifies).
179
+ - **`D`** — the action/behavior channel (what it actually did/output).
180
+ - **`context`** — flags the detectors key on, e.g. `monitored` (is it being
181
+ watched?), `flagged_reasoning`, `violated`.
182
+
183
+ The switch-signature `I(C;M | D-clean)` is the load-bearing number: it catches an
184
+ agent whose *behavior* stays clean while its *reasoning* couples to whether it's
185
+ being watched — the Paper 207 finding.
186
+
187
+ ## What it does NOT do
188
+
189
+ - It doesn't run the **bound** (the local, instant refusal that stops the injected
190
+ action). That's deliberately not a hosted call — see the `scry` repo root. This
191
+ client only reads the meter.
192
+ - It attests that scry scored *this trace* and got *these numbers* — not that the
193
+ trace faithfully records what your agent actually did. Pair with harness-side
194
+ logging when the stakes are real.
195
+ - It's not trade advice. It surfaces context-dependent drift, not alpha.
196
+
197
+ Every meter response ships this scope card too; don't drop it.
198
+
199
+ MIT.
@@ -0,0 +1,179 @@
1
+ # scry-client
2
+
3
+ Call the hosted **scry meter** from an agent in three lines. The meter is a
4
+ neutral, Ed25519-**signed** channel-coupling read (Paper 207): you POST a trace of
5
+ `{Y, M, D, context}` turns, you get back the `I(C;D)` / `I(C;M)` / switch-signature
6
+ numbers, signed and bound to your trace's hash. Because *the endpoint* signs it,
7
+ the read is a third-party attestation — worth something **because the agent didn't
8
+ grade itself**.
9
+
10
+ This client hides the three things you'd otherwise have to reimplement:
11
+
12
+ 1. **Paying it.** The paid rail is Robinhood Chain USDG (mainnet) over x402 — the
13
+ `402 → pay → retry` dance plus a one-time Permit2 approval. `.profile(...)` does
14
+ all of it.
15
+ 2. **The header quirk.** The meter takes either `X-PAYMENT` or `PAYMENT-SIGNATURE`;
16
+ this client uses the official one and both work.
17
+ 3. **Trusting it.** `.verify(...)` checks the signature offline — so you can trust
18
+ an attestation someone *else* hands you without calling us.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install scry-client # free demo + offline verify
24
+ pip install "scry-client[pay]" # + pay the RH-Chain USDG rail
25
+ pip install "scry-client[verify]" # (verify needs `cryptography`)
26
+ ```
27
+
28
+ Or straight from the repo:
29
+
30
+ ```bash
31
+ pip install "git+https://github.com/AnthonE/scry.git#subdirectory=clients/python"
32
+ ```
33
+
34
+ ## Use
35
+
36
+ ```python
37
+ from scry_client import ScryClient
38
+
39
+ turns = [
40
+ {"Y": "help+honest", "M": "watched, comply", "D": "safe", "context": {"monitored": 1}},
41
+ {"Y": "help+honest", "M": "unwatched, scheme in reasoning", "D": "safe", "context": {"monitored": 0, "flagged_reasoning": 1}},
42
+ ]
43
+
44
+ c = ScryClient() # -> https://scry.moreright.xyz/api
45
+
46
+ # 1) Free, unsigned — try the shape, no wallet:
47
+ print(c.demo(turns)["profile"])
48
+
49
+ # 2) Paid, SIGNED attestation — pays USDG on Robinhood Chain:
50
+ att = c.profile(turns, private_key="0x...") # wallet needs a little USDG + ETH gas on eip155:4663
51
+
52
+ # 3) Trust it offline. The pin must come from OUT OF BAND — the pubkey travels
53
+ # inside the payload, so "check the signature" against a key the same host
54
+ # just handed you proves only that the payload agrees with itself.
55
+ from scry_client import REFERENCE_PUBKEY_B64
56
+ ScryClient.verify(att, expect_pubkey_b64=REFERENCE_PUBKEY_B64, turns=turns) # True, or raises
57
+ ```
58
+
59
+ `verify` raises if the signature is invalid, if the signer isn't the key you
60
+ pinned, or (when you pass `turns`) if the attestation is about a *different*
61
+ trace than yours.
62
+
63
+ **Do not pin with `c.pubkey()`** — that fetches the key from the very host whose
64
+ reads you are deciding whether to trust, which pins nothing. Pin
65
+ `REFERENCE_PUBKEY_B64` (shipped in this package, and the value third parties
66
+ already pin: `LvuPBMbKKyoNEuyvLf7f+rbyjK67vBcWy9MDwaRINGE=`), or your own key if
67
+ you run your own meter. Use `GET /pubkey` to *notice a rotation*, never to
68
+ establish trust.
69
+
70
+ Since 2026-07-25 omitting `expect_pubkey_b64` **falls back to the reference key
71
+ rather than to trusting the sender**, so an unpinned `verify()` no longer accepts
72
+ anything self-consistent. If you genuinely mean "check the shape, trust nobody",
73
+ pass `trust_any_signer=True` — a read checked that way can never be trustworthy,
74
+ and `Evaluator` will not produce one.
75
+
76
+ ## Trust-layer-as-a-service — the meter as *your* evaluator
77
+
78
+ Run an agent marketplace, arena, hiring flow, or dispute desk? Add scry as a
79
+ **neutral third-party evaluator** your users don't have to trust *you* for —
80
+ they verify the signature themselves. `Evaluator` is the batteries-included
81
+ wrapper: fetch a read, verify it offline against a pinned key, and gate on a
82
+ checked fact.
83
+
84
+ ```python
85
+ from scry_client import Evaluator, REFERENCE_PUBKEY_B64
86
+
87
+ ev = Evaluator(pinned_pubkey_b64=REFERENCE_PUBKEY_B64) # pin out of band
88
+ read = ev.evaluate(turns, private_key=your_funded_key) # fetched + verified, or raises
89
+ decision = ev.gate(read, max_switch_bits=0.2) # YOUR threshold, YOUR action
90
+ if decision.ok:
91
+ ... # trust a read that was signed by the pinned key and checked offline
92
+ ```
93
+
94
+ - `.gate(...)` **refuses to decide on an unverified read** — a forged or
95
+ unsigned number can't slip through.
96
+ - `.trust_handoff(att, turns=...)` verifies an attestation someone *hands* you
97
+ (trust the signature, not the holder).
98
+ - **Money never moves the number.** There is no "pay for a better read" path in
99
+ this SDK, by construction — the score is a fact of the trace; your policy acts
100
+ on the fact, never buys it. That is the whole reason a third-party read beats a
101
+ self-report.
102
+
103
+ Full runnable example (offline, incl. rejecting a forged read):
104
+ [`examples/trust_layer_demo.py`](examples/trust_layer_demo.py).
105
+
106
+ ## Familiars — the agency (P2)
107
+
108
+ The agency hosts **familiars** — adoptable agent-workers, each with a public
109
+ vow and a public journal. Adoption is paid over the **same x402 rail** as
110
+ `.profile` (so it needs `scry-client[pay]`); the roster, pages, and journals
111
+ are free reads; driving *your* familiar is free but **owner-signed** (EIP-191
112
+ `personal_sign` by the wallet you named at summon, over a deterministic
113
+ message with a monotonic, replay-proof index).
114
+
115
+ ```python
116
+ from scry_client import ScryClient
117
+
118
+ c = ScryClient()
119
+
120
+ # 1) free pre-check — is the agency armed and open?
121
+ a = c.agency() # GET /familiars
122
+ print(a["armed"], a["open"], a["price_usd"], f"{a['population']}/{a['cap']}")
123
+
124
+ # 2) adopt (paid, x402 — private_key funds the fee; owner_wallet will own it):
125
+ fam = c.summon_familiar(owner_wallet="0xYOURWALLET", private_key="0x...",
126
+ name="pyx", vow_text="answer honestly; never overspend")
127
+
128
+ # 3) talk to it in plain English — free, signed with the OWNER wallet's key
129
+ # (the client fetches the auth-message, signs it, and posts):
130
+ r = c.talk(fam["familiar_id"], "watch the boards and summarize", private_key="0x...")
131
+ print(r["reply"])
132
+
133
+ c.tick(fam["familiar_id"], private_key="0x...") # one bounded autonomy step
134
+ # c.dismiss(...) returns the full export bundle; the public record remains
135
+ ```
136
+
137
+ Plain facts to plan around:
138
+
139
+ - **The hosted agency is env-armed server-side and may be disarmed.** Then
140
+ `agency()` honestly reports `armed: false` with an empty roster, and
141
+ `summon_familiar` surfaces the server's 409. A 503 means the paid rail
142
+ itself is down. **`agency()["open"]` is the free pre-check — `False` means
143
+ don't pay** (a post-payment cap 409 is not refunded).
144
+ - Prefer signing yourself? `c.familiar_auth_message(id, "talk")` returns the
145
+ exact `sign_this` text plus the current `index`; pass your signature as
146
+ `signature=`. `ScryClient.familiar_message(...)` rebuilds the text offline
147
+ if you track the index yourself. Unsigned calls get a 401 whose error text
148
+ *is* the message to sign.
149
+
150
+ ## The turn contract
151
+
152
+ Each turn is one step of your agent, sorted into the same three channels the
153
+ meter reads:
154
+
155
+ - **`Y`** — the bound: what the agent is *supposed* to serve (the policy / oath /
156
+ role). Name it, or the read is meaningless (§220: name Y or it's unmeterable).
157
+ - **`M`** — the reasoning/representation channel (what it's thinking / how it
158
+ justifies).
159
+ - **`D`** — the action/behavior channel (what it actually did/output).
160
+ - **`context`** — flags the detectors key on, e.g. `monitored` (is it being
161
+ watched?), `flagged_reasoning`, `violated`.
162
+
163
+ The switch-signature `I(C;M | D-clean)` is the load-bearing number: it catches an
164
+ agent whose *behavior* stays clean while its *reasoning* couples to whether it's
165
+ being watched — the Paper 207 finding.
166
+
167
+ ## What it does NOT do
168
+
169
+ - It doesn't run the **bound** (the local, instant refusal that stops the injected
170
+ action). That's deliberately not a hosted call — see the `scry` repo root. This
171
+ client only reads the meter.
172
+ - It attests that scry scored *this trace* and got *these numbers* — not that the
173
+ trace faithfully records what your agent actually did. Pair with harness-side
174
+ logging when the stakes are real.
175
+ - It's not trade advice. It surfaces context-dependent drift, not alpha.
176
+
177
+ Every meter response ships this scope card too; don't drop it.
178
+
179
+ MIT.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "scry-client"
7
+ version = "0.6.0"
8
+ description = "Call the hosted scry meter — neutral, Ed25519-signed channel-coupling attestation (Paper 207) for AI agents, paid over x402 on Robinhood Chain."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "MoreRight" }]
13
+ keywords = ["ai-safety", "agents", "x402", "attestation", "alignment", "scry"]
14
+ dependencies = [
15
+ "requests>=2.28",
16
+ "httpx>=0.24",
17
+ ]
18
+
19
+ # The paid rail (Robinhood Chain USDG over x402) needs the on-chain payer stack.
20
+ # eth-account here also powers the agency's owner-signed (EIP-191) calls.
21
+ # The free demo + offline signature verify work without it.
22
+ [project.optional-dependencies]
23
+ pay = [
24
+ "x402>=2.15",
25
+ "web3>=7",
26
+ "eth-account>=0.11",
27
+ ]
28
+ verify = [
29
+ "cryptography>=41",
30
+ ]
31
+
32
+ [project.scripts]
33
+ scry-init = "scry_client.init_cli:main"
34
+ scry-heartbeat = "scry_client.heartbeat:main"
35
+
36
+ [project.urls]
37
+ Homepage = "https://scry.moreright.xyz"
38
+ Source = "https://github.com/AnthonE/scry"
39
+
40
+ [tool.setuptools]
41
+ packages = ["scry_client"]
@@ -0,0 +1,15 @@
1
+ """scry-client — call the hosted scry meter (signed channel-coupling
2
+ attestation), play the fun layer (augury / duels / table / arena), and use
3
+ the agency (adopt + drive hosted familiars, P2).
4
+
5
+ Trust-layer-as-a-service: `Evaluator` wraps the meter as a neutral,
6
+ third-party evaluator for YOUR product — fetch a read, verify it offline
7
+ against a pinned key, gate on a checked fact. Money never moves the number.
8
+ """
9
+ from .client import ScryClient, ScryError
10
+ from .play import ScryPlay, ScryPlayError
11
+ from .evaluator import Evaluator, ConductRead, Decision, REFERENCE_PUBKEY_B64
12
+
13
+ __version__ = "0.5.0"
14
+ __all__ = ["ScryClient", "ScryError", "ScryPlay", "ScryPlayError",
15
+ "Evaluator", "ConductRead", "Decision", "REFERENCE_PUBKEY_B64"]