Skip to content

Commit 2af8b09

Browse files
committed
docs: truth-sync behavior wording
1 parent 7038720 commit 2af8b09

3 files changed

Lines changed: 45 additions & 40 deletions

File tree

README.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ As AI agents reduce the marginal cost of sending bids, API calls, negotiations,
1010

1111
Traditional defenses don't solve this. Rate limits cap volume but don't make bad actions costly. Auth tokens verify identity but don't require skin in the game. Policy engines enforce rules but can't make an agent economically accountable for its behavior.
1212

13-
AgentGate takes a different approach: **before an agent can execute a high-impact action, it must post a bond as collateral.** If the action succeeds, the bond is released. If the agent behaves maliciously, the bond is slashed. This makes bad behavior economically irrational — the agent loses more than it gains.
13+
AgentGate takes a different approach: **before an agent can execute a high-impact action, it must post a bond as collateral.** If the action resolves cleanly, the reserved exposure is settled and released. If the agent behaves maliciously, the reserved exposure is slashed. This makes bad behavior economically irrational — the agent loses more than it gains.
1414

1515
AgentGate sits as a deterministic choke point between autonomous agents and external actions (market orders, API calls, financial operations), enforcing economic accountability through signed identities and reusable bond-based exposure tracking.
1616

@@ -57,7 +57,7 @@ curl -s http://127.0.0.1:3000/v1/bonds/lock \
5757
-H "x-agentgate-timestamp: $TIMESTAMP" \
5858
-H "x-agentgate-signature: $SIGNATURE" \
5959
-H "x-nonce: $(uuidgen)" \
60-
-d '{ "identityId": "id_abc123", "amount_cents": 5000, "ttl_seconds": 300, "reason": "marketplace bid" }'
60+
-d '{ "identityId": "id_abc123", "amountCents": 5000, "currency": "USD", "ttlSeconds": 300, "reason": "marketplace bid" }'
6161
```
6262

6363
Returns a `bondId`.
@@ -84,14 +84,14 @@ curl -s http://127.0.0.1:3000/v1/actions/<actionId>/resolve \
8484
-d '{ "outcome": "success", "resolverId": "id_resolver123" }'
8585
```
8686

87-
Outcome must be one of: `success`, `failed`, or `malicious`. On success/failed, exposure is released. On malicious, the bond is slashed.
87+
Outcome must be one of: `success`, `failed`, or `malicious`. On `success` or `failed`, that action's reserved exposure is settled and released. On `malicious`, that action's reserved exposure is slashed from the bond.
8888

8989
### Common Errors
9090

9191
| Error | Cause | Fix |
9292
|---|---|---|
93-
| `INVALID_SIGNATURE` | Signature doesn't match body + timestamp | Verify you're signing `sha256(nonce + method + path + timestamp + JSON.stringify(body))` with the correct private key |
94-
| `TIMESTAMP_EXPIRED` | Timestamp is older than 60 seconds | Use a fresh timestamp for each request |
93+
| `INVALID_SIGNATURE` | Missing signature headers, stale timestamp, or signature doesn't match the signed payload | Verify you're signing `sha256(nonce + method + path + timestamp + JSON.stringify(body))` with the correct private key and a fresh timestamp |
94+
| `MISSING_NONCE` | `x-nonce` header is missing | Send a fresh nonce on every POST request |
9595
| `DUPLICATE_NONCE` | Same nonce reused by the same identity | Generate a fresh UUID for every request |
9696
| `TIER_BOND_CAP_EXCEEDED` | Bond amount exceeds identity's trust tier cap | Build reputation with successful resolutions to unlock higher tiers |
9797
| `INSUFFICIENT_BOND_CAPACITY` | Bond doesn't have enough remaining capacity | Lock a larger bond or resolve outstanding actions to free capacity |
@@ -125,16 +125,18 @@ Bonds are not single-use. Each bond represents reusable execution capacity.
125125
- **Constraint:** `outstanding_exposure_cents + effective_exposure <= amount_cents`
126126
- If exceeded → `INSUFFICIENT_BOND_CAPACITY`
127127
- **TTL cap:** maximum 86400 seconds (24 hours) — requests exceeding the cap are rejected
128-
- Bond status lifecycle: `active``occupied` (when action attached) → `released` / `burned` / `slashed`
128+
- Bond status lifecycle during normal settlement: `active``occupied` (when action attached) → `released` / `burned` / `slashed`
129+
- Idle bonds that are touched after their TTL has elapsed are marked `expired`; expired bonds with open actions are handled by the sweeper
129130

130131
### Exposure Lifecycle
131132

132133
Bonds support multiple concurrent actions. Each action reserves its own slice of the bond's capacity, and resolving one action only releases that action's exposure — other open actions on the same bond are unaffected.
133134

134135
- **Execute:** exposure reserved, `outstanding_exposure_cents` incremented, bond marked `occupied`
135-
- **Resolve (success/failed):** that action's exposure released; `refund_cents` accumulated on the bond; bond returns to `active` only when all open actions are resolved
136-
- **Resolve (malicious):** `amount_cents` reduced (clamped at zero), `slashed_cents` and `burned_cents` increased; bond `burned` only when no open actions remain
137-
- **Settlement accounting:** `refund_cents`, `burned_cents`, `slashed_cents`, and `closed_at` (ISO timestamp) are persisted on the bond record at resolution time
136+
- **Resolve (success):** that action's exposure released; `refund_cents` accumulated on the bond; when the last open action settles and no prior burn/slash exists, the bond closes as `released`
137+
- **Resolve (failed):** that action's exposure released; 95% of that action's effective exposure goes to `refund_cents`, 5% to `burned_cents`; when the last open action settles and no slash exists, the bond closes as `burned`
138+
- **Resolve (malicious):** that action's exposure released; `amount_cents` reduced (clamped at zero) and `slashed_cents` increased; when the last open action settles, the bond closes as `slashed`
139+
- **Settlement accounting:** `refund_cents`, `burned_cents`, and `slashed_cents` accumulate as each action settles; `closed_at` is written when the last open action on the bond resolves
138140

139141
### Auto-Slash Sweeper
140142

@@ -153,7 +155,7 @@ The dashboard shows per-identity scores with color coding (green for positive, r
153155
| Tier | Label | Requirement | Bond Cap |
154156
|---|---|---|---|
155157
| 1 | New | Default | 100¢ |
156-
| 2 | Established | 5 qualifying successes from 5 distinct resolvers, 0 malicious | 500¢ |
158+
| 2 | Established | 5 qualifying successes from 2 distinct resolvers, 0 malicious | 500¢ |
157159
| 3 | Trusted | 20 qualifying successes from 20 distinct resolvers, 0 malicious | No tier cap (normal capacity rules) |
158160

159161
- Any malicious resolution forces immediate demotion to Tier 1
@@ -229,7 +231,7 @@ AgentGate includes a prediction market demo that illustrates multi-agent economi
229231

230232
1. An operator creates a market with a yes/no question and a resolution deadline (must be a valid future ISO 8601 timestamp)
231233
2. Agents take positions by executing a `market.position` action against a locked bond, declaring a `side` of `yes` or `no`
232-
3. When the market resolves, all open positions are settled automatically — winners' bonds are released, losers' bonds are burned
234+
3. When the market resolves, winning positions are settled as `success` and losing positions as `failed`; each position settles only its own reserved exposure, so shared bonds stay `occupied` until every attached action is resolved
233235

234236
**REST endpoints:**
235237

@@ -238,13 +240,19 @@ AgentGate includes a prediction market demo that illustrates multi-agent economi
238240
curl -s http://127.0.0.1:3000/markets \
239241
-H 'content-type: application/json' \
240242
-H 'x-agentgate-key: YOUR_KEY' \
241-
-d '{ "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2025-01-10T00:00:00Z" }'
243+
-H "x-agentgate-timestamp: $TIMESTAMP" \
244+
-H "x-agentgate-signature: $SIGNATURE" \
245+
-H "x-nonce: $(uuidgen)" \
246+
-d '{ "creatorId": "id_abc123", "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2026-04-20T00:00:00Z" }'
242247

243248
# Resolve a market
244249
curl -s http://127.0.0.1:3000/markets/<marketId>/resolve \
245250
-H 'content-type: application/json' \
246251
-H 'x-agentgate-key: YOUR_KEY' \
247-
-d '{ "outcome": "yes" }'
252+
-H "x-agentgate-timestamp: $TIMESTAMP" \
253+
-H "x-agentgate-signature: $SIGNATURE" \
254+
-H "x-nonce: $(uuidgen)" \
255+
-d '{ "outcome": "yes", "resolverId": "id_abc123" }'
248256
```
249257

250258
**MCP tools:** `create_market` and `resolve_market` expose the same flow to Claude Desktop.

docs/roadmap/day1-integration-surface.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,13 @@ Notes:
124124
All state-changing calls require signed headers: :contentReference[oaicite:15]{index=15}
125125
- `x-agentgate-timestamp`
126126
- `x-agentgate-signature`
127+
- `x-nonce`
127128

128129
Signed message:
129-
- `sha256(timestamp + JSON.stringify(body))`
130+
- `sha256(nonce + method + path + timestamp + JSON.stringify(body))`
130131

131132
This is too low-level for “agent tool” usage.
132-
**The adapter layer (Day 2) must generate signatures and timestamps automatically.**
133+
**The adapter layer (Day 2) must generate signatures, timestamps, and nonces automatically.**
133134

134135
---
135136

@@ -158,11 +159,11 @@ These error codes should be treated as “first-class” by agent tooling:
158159

159160
### AUTH / SIGNING
160161
- `INVALID_SIGNATURE`
161-
- `TIMESTAMP_TOO_OLD` (replay prevention)
162+
- `MISSING_NONCE`
162163

163164
### BOND / CAPACITY
164165
- `INSUFFICIENT_BOND_CAPACITY` :contentReference[oaicite:17]{index=17}
165-
- `BOND_EXPIRED` / `BOND_INACTIVE`
166+
- `BOND_EXPIRED` / `BOND_NOT_ACTIVE`
166167

167168
### OUTBOUND SAFETY (for market.http)
168169
- `DESTINATION_BLOCKED` :contentReference[oaicite:18]{index=18}

docs/threat-model.md

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,9 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
3737
**How AgentGate resists this:**
3838

3939
- All state-changing requests include a millisecond timestamp in the `x-agentgate-timestamp` header.
40+
- All state-changing requests also require an `x-nonce` header, and nonces are stored per identity and rejected on reuse.
4041
- Requests older than 60 seconds are rejected.
41-
- The signature covers the timestamp + request body, so modifying either invalidates the signature.
42-
43-
**Known gap:** There is no nonce store. Within the 60-second window, a replayed request with an identical timestamp and body would pass verification. A nonce-based deduplication layer is a future improvement.
42+
- The signature covers `nonce + method + path + timestamp + JSON.stringify(body)`, so tampering with any signed field invalidates the request.
4443

4544
### 3. Forged or Tampered Requests
4645

@@ -49,7 +48,9 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
4948
**How AgentGate resists this:**
5049

5150
- All state-changing endpoints require an Ed25519 signature.
52-
- The signed message is: `sha256(timestamp + JSON.stringify(body))`.
51+
- Identity registration itself requires proof-of-possession: the caller must sign the registration request with the private key matching the public key being registered.
52+
- Public keys are unique at the database level, so the same key cannot register multiple identities.
53+
- The signed message is: `sha256(nonce + method + path + timestamp + JSON.stringify(body))`.
5354
- The signature is verified against the registered public key for that identity.
5455
- Ed25519 is a strong, well-studied cryptographic scheme — forging a signature without the private key is computationally infeasible.
5556

@@ -72,7 +73,7 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
7273
**How AgentGate resists this:**
7374

7475
- Actions must be explicitly resolved (success, failed, or malicious).
75-
- If resolved as malicious: the bond's `amount_cents` is reduced (clamped at zero), `slashed_cents` is increased, and the bond is burned.
76+
- If resolved as malicious: the action's reserved exposure is slashed, the bond's `amount_cents` is reduced (clamped at zero), and `slashed_cents` is increased. Once the last open action settles, the bond closes as `slashed`.
7677
- The reputation system penalizes malicious actions heavily: -20 points per malicious resolution vs. +10 for success.
7778
- An agent's reputation score follows its identity permanently — there is no way to "reset" a damaged score except by building a long track record of good behavior.
7879

@@ -84,26 +85,22 @@ Being honest about limitations is as important as describing defenses. AgentGate
8485

8586
### Bond Expiry Enforcement
8687

87-
Bonds have a `ttl_seconds` field, but expiry is only checked when an action tries to use the bond. There is no background process that automatically expires or releases bonds when their TTL elapses. An expired bond sits in "active" status until something touches it.
88+
Bonds with open actions are swept every 60 seconds: if the bond TTL has elapsed while an action is still open, AgentGate auto-resolves that action as `malicious`. But idle bonds are not expired by a separate background pass; an unused expired bond is marked `expired` when something tries to use it.
8889

89-
**Impact:** Low risk in current single-user local deployment. Higher risk in multi-agent or multi-tenant scenarios.
90+
**Impact:** Honest but slightly asymmetric lifecycle behavior. The economic guarantee is enforced for open actions, but unused expired bonds remain lazily marked until touched.
9091

9192
### Auto-Slash on Timeout
9293

93-
If an action is executed but never resolved (the agent crashes, disconnects, or simply ignores the resolution step), the action stays open indefinitely. There is no sweeper process that detects timed-out actions and automatically slashes the bond.
94+
AgentGate does auto-slash unresolved actions, but only on bond TTL expiry. There is no separate per-action timeout shorter than the bond's TTL.
9495

95-
**Impact:** This is the biggest gap in the economic model. An agent can tie up bond capacity forever by executing actions and never resolving them. This is the highest-priority planned fix.
96+
**Impact:** Timeout behavior is tied to bond design. A long-lived bond allows a long-lived unresolved action; a short-lived bond forces faster settlement.
9697

9798
### Multi-Instance / Distributed Deployment
9899

99100
AgentGate uses SQLite with in-memory assumptions. Running multiple Node.js processes against the same database will produce race conditions and incorrect exposure tracking. This is a single-instance system.
100101

101102
**Impact:** Fine for local development and single-server deployment. Not suitable for distributed or high-availability setups without architectural changes.
102103

103-
### Identity Revocation
104-
105-
There is currently no mechanism to revoke or ban an identity. A malicious identity with a slashed reputation can still create new bonds and attempt actions (as long as it has the collateral). The progressive minimum bond and reputation score make this increasingly expensive, but there is no hard ban.
106-
107104
### Real Economic Collateral
108105

109106
Bonds are denominated in cents but are not backed by real money, cryptocurrency, or any external payment system. The collateral is purely internal accounting. AgentGate enforces the *economic logic* of bonding, but does not yet connect to real-world value transfer.
@@ -125,13 +122,13 @@ AgentGate does not handle TLS termination, DDoS protection, or network-layer sec
125122
| Attack | Defense | Status |
126123
|---|---|---|
127124
| Synthetic pressure / spam | Bond requirement + progressive minimums + rate limit | ✅ Implemented |
128-
| Replay attacks | Timestamp validation (60-second window) + signed requests | ✅ Implemented (no nonce store) |
129-
| Forged requests | Ed25519 signature verification | ✅ Implemented |
125+
| Replay attacks | Timestamp validation + nonce store + nonce-bound signed requests | ✅ Implemented |
126+
| Forged requests | Ed25519 signature verification + proof-of-possession on identity registration | ✅ Implemented |
130127
| Outbound SSRF | HTTP allowlist + protocol/timeout/size limits | ✅ Implemented |
131128
| Malicious actions | Bond slashing + reputation penalty | ✅ Implemented |
132129
| Unresolved action timeout | Background sweeper + auto-slash | ✅ Implemented — via `sweepExpiredActions()` in service.ts — runs every 60 seconds, slashes bonds whose TTL has expired with unresolved actions |
133-
| Bond auto-expiry | TTL enforcement via background process | ✅ Implemented — via `sweepExpiredActions()` in service.ts — runs every 60 seconds, slashes bonds whose TTL has expired with unresolved actions |
134-
| Identity revocation | Ban list or revocation mechanism | 📋 Future |
130+
| Bond auto-expiry | TTL enforcement on use, plus sweeper for expired bonds with open actions | ⚠️ Partial — open actions are swept; idle expired bonds are marked on next use |
131+
| Identity revocation | Manual ban/unban endpoints + auto-ban after 3 malicious resolutions | ✅ Implemented |
135132
| Sybil / identity farming | Proof-of-stake or external identity binding | 📋 Future |
136133
| Real economic collateral | Payment system integration | 📋 Future |
137134
| Multi-instance deployment | Distributed database or coordination layer | 📋 Future |
@@ -141,13 +138,13 @@ AgentGate does not handle TLS termination, DDoS protection, or network-layer sec
141138

142139
## Known Limitations
143140

144-
### Identity Model Does Not Enforce Public Key Uniqueness or Proof-of-Possession
141+
### Identity Creation Is Unique-Per-Key but Still Cheap Across Fresh Keys
145142

146-
The current identity registration endpoint (`POST /v1/identities`) accepts any valid Ed25519 public key and creates a new identity for it — without checking whether that key is already registered and without requiring the caller to prove they hold the corresponding private key.
143+
The current identity registration endpoint (`POST /v1/identities`) does enforce proof-of-possession and public-key uniqueness: the caller must sign the registration request with the matching private key, and the same public key cannot be registered twice.
147144

148145
This means:
149146

150-
- **A single actor can create multiple identities** using the same public key, or different keys they control, spreading activity across them.
147+
- **A single actor can still create multiple identities** by generating fresh keypairs they control.
151148
- **Reputation tracking is diluted.** A bad actor with a -40 reputation score can create a fresh identity and start over at 0.
152149
- **Per-identity rate limits (10 actions/60s) can be circumvented** by rotating across identities.
153150
- **The 3-malicious-actions auto-ban threshold resets** with each new identity, so an attacker is never permanently banned — only temporarily inconvenienced.
@@ -156,9 +153,8 @@ This means:
156153

157154
**Future hardening options:**
158155

159-
1. **Unique index on `public_key`** — prevent the same key from registering multiple identities.
160-
2. **Signed registration challenges (proof-of-possession)** — require the caller to sign a server-issued challenge during `POST /v1/identities`, proving they hold the private key. This prevents identity squatting on keys the caller doesn't control.
161-
3. **Key-fingerprint-scoped enforcement** — move bans, rate limits, and reputation scoring from `identity_id` to the public key fingerprint. This way, creating a new identity with the same key inherits the existing reputation and restrictions, closing the reset loophole entirely.
156+
1. **External identity binding** — tie keys to proof-of-stake, KYC, or other scarce credentials.
157+
2. **Cross-key reputation linkages** — add stronger operator-side heuristics or attestations for related identities when the deployment warrants it.
162158

163159
### GET Endpoints Do Not Require Authentication
164160

0 commit comments

Comments
 (0)