Skip to content

Commit f15fe29

Browse files
committed
Redesign around obfuscated LLM-capability challenges
1 parent 7073adc commit f15fe29

24 files changed

Lines changed: 1116 additions & 286 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 0.2.0 - 2026-03-07
4+
5+
- Repositioned `agentproof` as an LLM-capability CAPTCHA library
6+
- Added `obfuscated_text_lock` as the primary challenge family
7+
- Added private server-side verification data via `Challenge.to_internal_dict()`
8+
- Added a public/private CLI generation flow for obfuscated challenges
9+
- Updated the local demo for manual LLM response entry and server-side challenge storage
10+
- Rewrote the README and docs around the obfuscated public challenge flow
11+
312
## 0.1.1 - 2026-03-06
413

514
- Fixed GitHub release creation workflow by checking out the repository before invoking `gh release`

README.md

Lines changed: 92 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
![agentproof overview](assets/agentproof-hero.svg)
1010

11-
`agentproof` is a Python library for agent-oriented verification challenges.
12-
It lets a service issue a structured challenge, lets an agent solve it, and verifies the result
13-
deterministically on the server.
11+
`agentproof` is a Python library for LLM-capability CAPTCHA flows.
12+
It issues obfuscated public challenges, expects a structured answer back, and verifies the answer
13+
deterministically against the private server-side copy.
1414

1515
Install:
1616

@@ -24,180 +24,168 @@ Import:
2424
import agentproof
2525
```
2626

27-
## What problem it solves
27+
## What it is
2828

29-
Traditional CAPTCHA asks "are you human?".
29+
Traditional CAPTCHA asks whether the client is human.
3030

3131
`agentproof` asks a different question:
3232

33-
"Can this client complete an agent-friendly, machine-checkable challenge?"
33+
> Can this client recover and execute an obfuscated instruction in an LLM-like way?
3434
35-
That is useful when you want to:
35+
That makes it useful for:
3636

37-
- gate agent-focused endpoints
38-
- prototype reverse-CAPTCHA style flows
39-
- add a structured verification step before allowing API access
40-
- experiment with challenge-response systems for LLM agents
37+
- LLM-first endpoints
38+
- reverse-CAPTCHA experiments
39+
- capability gates before access to an API
40+
- local testing of challenge-response flows for agents
4141

4242
## How it works
4343

44-
1. Your server generates a challenge JSON payload.
45-
2. The agent reads it and produces a structured response.
46-
3. Your server verifies the response.
47-
4. Verification returns `ok: true` or a deterministic failure reason.
44+
1. Your server generates a challenge and keeps the private verification copy.
45+
2. You send the public challenge JSON to the client.
46+
3. The client returns structured JSON with `payload.answer`.
47+
4. Your server verifies the response and gets `ok: true` or an exact failure reason.
4848

49-
## Smallest example
49+
## Quickest real example
5050

5151
```python
52-
from agentproof import ChallengeSpec, generate_challenge, solve_challenge, verify_response
52+
from agentproof import AgentResponse, ChallengeSpec, generate_challenge, verify_response
5353

5454
challenge = generate_challenge(
55-
ChallengeSpec(challenge_type="proof_of_work", difficulty=8, ttl_seconds=60)
55+
ChallengeSpec(
56+
challenge_type="obfuscated_text_lock",
57+
difficulty=2,
58+
options={"template": "amber_sort"},
59+
)
60+
)
61+
62+
public_challenge = challenge.to_dict()
63+
64+
# Send public_challenge to an LLM-capable client.
65+
# The client responds with structured JSON.
66+
response = AgentResponse(
67+
challenge_id=challenge.challenge_id,
68+
challenge_type=challenge.challenge_type,
69+
payload={"answer": "EMBER-HARBOR-SIGNAL"},
5670
)
57-
response = solve_challenge(challenge)
58-
result = verify_response(challenge, response)
5971

72+
result = verify_response(challenge, response)
6073
assert result.ok
6174
```
6275

63-
## What a real challenge looks like
64-
65-
Example `proof_of_work` challenge:
76+
## What the public challenge looks like
6677

6778
```json
6879
{
69-
"challenge_id": "6f2c8e4a91d3b5c1",
70-
"challenge_type": "proof_of_work",
71-
"prompt": "Find a nonce such that sha256_hex(payload + ':' + nonce) starts with 8 leading zero bits.",
72-
"issued_at": "2026-03-07T01:10:00+00:00",
73-
"expires_at": "2026-03-07T01:11:00+00:00",
74-
"version": "1",
80+
"challenge_id": "bb28567e201b35aa",
81+
"challenge_type": "obfuscated_text_lock",
82+
"prompt": "gl1tch//llm-cap-v1::d2\nfrag@f8 // D3c0d3 the driFted Br13f ANd 4N5w3r tHrOUgH Payload.answer 0NLY\nfrag@d8 %% d3CK: slOt5 v10l37 cIndEr\nfrag@f6 %% d3ck: sloT2 4Mb3R h4Rb0r\nfrag@c9 || task: 0rD3R thE kept 5h4Rd WOrdS By 5l07 numBer fr0m loW to h1gh\nfrag@b3 %% dEcK: slOt3 C0b4L7 sabLe\nfrag@d3 %% AnswEr ruLe: R37urn ThE 5H4rd W0rd5 in UpPercaSe aScii J01N3D WIth hYpheNs\nfrag@e2 || d3Ck: SLot4 4mb3R 51gn4L\nfrag@e5 ^^ tasK: keEp onLy ShArds cArrying the 4MB3r TAg\nfrag@e4 :: d3CK: slot1 4mB3r 3Mb3R\nreply via payload.answer only // structured-json",
83+
"issued_at": "2026-03-07T02:58:20.639623+00:00",
84+
"expires_at": "2026-03-07T03:00:20.639623+00:00",
7585
"data": {
76-
"algorithm": "sha256",
77-
"difficulty": 8,
78-
"salt": "a14d22b8f91c77e2",
79-
"payload": "6f2c8e4a91d3b5c1:a14d22b8f91c77e2"
80-
}
86+
"difficulty": 2,
87+
"profile": "llm_capability_v1",
88+
"response_contract": {
89+
"payload.answer": "UPPERCASE ASCII words joined with hyphens",
90+
"payload.decoded_preview": "optional free-form notes"
91+
}
92+
},
93+
"version": "1"
8194
}
8295
```
8396

84-
Example agent response:
97+
The matching client response looks like:
8598

8699
```json
87100
{
88-
"challenge_id": "6f2c8e4a91d3b5c1",
89-
"challenge_type": "proof_of_work",
101+
"challenge_id": "bb28567e201b35aa",
102+
"challenge_type": "obfuscated_text_lock",
90103
"payload": {
91-
"nonce": "223",
92-
"hash": "00bf9b61a372cbd81bef570069b655fd02ef299cc29e9e59d5739e86f5fb6974"
104+
"answer": "EMBER-HARBOR-SIGNAL",
105+
"decoded_preview": "kept amber shards ordered by slot"
93106
}
94107
}
95108
```
96109

97-
Example verification result:
110+
And verification returns:
98111

99112
```json
100113
{
101114
"ok": true,
102115
"reason": "ok",
103116
"details": {
104-
"hash": "00bf9b61a372cbd81bef570069b655fd02ef299cc29e9e59d5739e86f5fb6974",
105-
"nonce": "223"
117+
"answer": "EMBER-HARBOR-SIGNAL",
118+
"template_id": "amber_sort",
119+
"difficulty": 2
106120
}
107121
}
108122
```
109123

110-
## Why this fits agents
111-
112-
These challenges are good for agents because they are:
113-
114-
- machine-readable
115-
- automatable
116-
- exact
117-
- easy to verify on the server
118-
119-
Agents are typically better than humans at:
120-
121-
- reading structured JSON
122-
- following exact constraints
123-
- iterating until a condition is satisfied
124-
- returning properly formatted machine output
125-
126124
## Built-in challenge types
127125

128-
| Challenge type | What the agent does | How it is verified |
126+
| Challenge type | Role | Built-in solver |
129127
| --- | --- | --- |
130-
| `proof_of_work` | Search for a nonce | Recompute hash and check difficulty |
131-
| `semantic_math_lock` | Produce constrained text | Check required words, exact word count, and initial-letter sum |
132-
133-
## Semantic example
134-
135-
```python
136-
from agentproof import ChallengeSpec, generate_challenge, solve_challenge, verify_response
137-
138-
challenge = generate_challenge(
139-
ChallengeSpec(
140-
challenge_type="semantic_math_lock",
141-
ttl_seconds=90,
142-
options={"topic": "security", "word_count": 7},
143-
)
144-
)
145-
response = solve_challenge(challenge)
146-
result = verify_response(challenge, response)
147-
148-
print(response.payload["text"])
149-
print(result.to_dict())
150-
```
151-
152-
Typical response text:
128+
| `obfuscated_text_lock` | Primary LLM-capability challenge | No |
129+
| `proof_of_work` | Deterministic compute baseline | Yes |
130+
| `semantic_math_lock` | Readable exact-constraint baseline | Yes |
153131

154-
```text
155-
security demands careful metrics metrics metrics metrics
156-
```
157-
158-
## API shape
159-
160-
```python
161-
from agentproof import ChallengeSpec, generate_challenge, solve_challenge, verify_response
162-
from agentproof import Challenge, AgentResponse, VerificationResult
163-
```
132+
`obfuscated_text_lock` is the main product path. It is meant to be solved by an external
133+
LLM-capable client, not by a bundled reference solver.
164134

165135
## CLI
166136

167-
Generate, solve, and verify from the command line:
137+
Baseline challenge roundtrip:
168138

169139
```bash
170140
agentproof generate proof_of_work --difficulty 16 --output challenge.json
171141
agentproof solve challenge.json --output response.json
172142
agentproof verify challenge.json response.json
173143
```
174144

175-
## Demo
145+
Obfuscated challenge flow:
176146

177-
A runnable local demo lives in [`demo/`](https://github.com/bnovik0v/agentproof/tree/main/demo).
147+
```bash
148+
agentproof generate obfuscated_text_lock \
149+
--difficulty 2 \
150+
--template amber_sort \
151+
--output challenge.internal.json \
152+
--public-output challenge.public.json
153+
```
154+
155+
Use `challenge.public.json` for the client and keep `challenge.internal.json` server-side for
156+
verification.
157+
158+
## Demo
178159

179-
Run it with:
160+
Run the local demo:
180161

181162
```bash
182163
uv run python demo/app.py
183164
```
184165

185-
Then open:
166+
Then open `http://127.0.0.1:8765`.
186167

187-
```text
188-
http://127.0.0.1:8765
189-
```
168+
The demo centers the obfuscated challenge flow and lets you paste a real LLM response into the
169+
browser before verifying it.
170+
171+
## What this proves
172+
173+
`agentproof` is best used to prove:
174+
175+
- the client can recover intent from obfuscated text
176+
- the client can return exact structured output
177+
- the response can be checked deterministically on your server
190178

191179
## What this does not prove
192180

193181
`agentproof` does not prove:
194182

195-
- model provenance
196-
- provider identity
183+
- model identity
184+
- provider provenance
197185
- hardware-backed execution
198-
- protection against determined custom automation
186+
- protection against every scripted solver
199187

200-
It is a challenge-response library, not an identity system.
188+
It is an LLM-capability CAPTCHA library, not an identity system.
201189

202190
## Development
203191

@@ -218,5 +206,3 @@ uv run mkdocs build --strict
218206
- Contributing: [CONTRIBUTING.md](CONTRIBUTING.md)
219207

220208
## License
221-
222-
MIT

demo/README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# agentproof demo
22

3-
This folder contains a small local web demo for the `agentproof` library. It uses only the
4-
Python standard library for the server and imports the local package source directly, so you can
5-
run it from VSCode without installing a separate web framework.
3+
This folder contains a small local web demo for the `agentproof` library. It uses only the Python
4+
standard library for the server and imports the local package source directly, so you can run it
5+
from VSCode without installing a separate web framework.
66

77
## What it shows
88

9-
- challenge generation
10-
- reference solver output
11-
- response verification
12-
- easy manual tampering to inspect failure modes
9+
- public challenge generation
10+
- server-side storage of the private verification copy
11+
- manual response entry for `obfuscated_text_lock`
12+
- built-in solver behavior for the baseline families
13+
- deterministic verification and failure modes
1314

1415
## Run it
1516

@@ -28,13 +29,13 @@ http://127.0.0.1:8765
2829
## Demo flow
2930

3031
1. Generate a challenge
31-
2. Auto-solve it with the bundled solver
32-
3. Verify the response
33-
4. Edit the response JSON and verify again to trigger a deterministic failure
32+
2. If it is `obfuscated_text_lock`, paste a response from an LLM-capable client
33+
3. If it is a baseline family, use the built-in solver button
34+
4. Verify the response
35+
5. Edit the response JSON and verify again to trigger a deterministic failure
3436

3537
## Notes
3638

37-
- `proof_of_work` difficulty `16` is a good default for local demos
38-
- `semantic_math_lock` is easier to inspect manually because the constraints are readable
39-
- the demo does not persist state; everything is driven by the JSON payloads shown on screen
40-
39+
- `obfuscated_text_lock` is the default view because it is the primary product path
40+
- `proof_of_work` and `semantic_math_lock` stay useful for fast baseline checks
41+
- the demo keeps internal challenge state in memory only; restart the server to clear it

0 commit comments

Comments
 (0)