Content
# AgentGate
AgentGate is a collateralized execution engine for AI agents: Ed25519 identities, reusable bonds, bounded exposure, settlement and slashing, and progressive trust tiers at the point where agents attempt external actions.
[](https://github.com/selfradiance/agentgate/actions/workflows/ci.yml)
> [!IMPORTANT]
> **Where To Start**
>
> AgentGate is the deeper accountability substrate underneath parts of this ecosystem. It is usually not the first repo a cold reader should start with.
>
> Recommended order for a first read:
> 1. [Governed WriteFile Demo](https://github.com/selfradiance/agentgate-governed-writefile-demo) for the narrowest end-to-end proof path
> 2. [MCP Firewall](https://github.com/selfradiance/agentgate-mcp-firewall) for the governed tool-call layer built on top
> 3. AgentGate (this repo) for the underlying execution engine
>
> Start here when you want the engine itself: identity registration, bond lifecycle, bounded exposure accounting, settlement and slashing, and transport/security mechanics.
AgentGate is the bond-and-slash substrate itself. It requires signed identities, lets agents lock reusable bond capacity against declared exposure, and records settlement outcomes in durable audit state at the deterministic choke point between agents and external actions.
## Visual Explainer
Prefer a short visual overview before reading? Watch the 4-part intro on X:
**[AgentGate explainer thread (4 short videos)](https://x.com/selfradiance11/status/2046010251128832398)**
Covers:
- what AgentGate is
- why permissions are not enough
- a concrete MCP Firewall example
- why runtime accountability matters
## What AgentGate Does
AgentGate sits between an autonomous agent and an external action such as an API call, bid, or financial operation. Before the action runs, the agent authenticates with an Ed25519 identity and locks bond capacity against a declared exposure.
If the action resolves cleanly, the reserved exposure is settled and released. Manual `malicious` resolution now requires two distinct eligible resolver identities before the slash finalizes, while expired-action sweeps still slash immediately. The system keeps that lifecycle in durable audit state and uses prior outcomes to gate larger bond sizes through progressive trust tiers.
This is a narrow runtime accountability mechanism, not a general AI safety or general agent security solution.
The `actions` table serves double duty: it's both a real-time enforcement log for slashing and a durable post-incident audit trail that can support disclosure to affected parties. The threat model doc covers this in more detail.
> **[Threat Model →](docs/threat-model.md)** — What AgentGate defends against, what it doesn't, and why.
Read the full story: **[How I Built AgentGate](docs/manifesto.md)**
---
## Quick Integration
AgentGate works with any agent that can make HTTP requests. If you want the shortest outsider-readable proof, start with the [Governed WriteFile Demo](https://github.com/selfradiance/agentgate-governed-writefile-demo). If you want the governance layer that fronts MCP tools, see [MCP Firewall](https://github.com/selfradiance/agentgate-mcp-firewall). The flow below is the direct engine integration path: register an identity, lock a bond, execute an action against that bond, and resolve the outcome.
> **Note:** The curl examples below assume `AGENTGATE_DEV_MODE=true` for local development (auth key enforcement is skipped). In production, add `-H 'x-agentgate-key: YOUR_KEY'` to every POST request.
**1. Register an identity**
Identity registration requires proof-of-possession — the caller must sign the request with the private key matching the public key being registered. This uses the same signature headers as all other state-changing endpoints:
```bash
curl -s http://127.0.0.1:3000/v1/identities \
-H 'content-type: application/json' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "publicKey": "<base64-encoded Ed25519 public key>" }'
```
Returns an `identityId` (e.g., `id_abc123`).
**2. Lock a bond**
All state-changing requests must be signed. Headers required on every request:
- `x-agentgate-timestamp` — current time in epoch milliseconds
- `x-agentgate-signature` — Ed25519 signature over `sha256(nonce + method + path + timestamp + JSON.stringify(body))`
- `x-nonce` — a unique string per request (UUID recommended); bound into the signed message AND stored server-side — the server rejects duplicates per identity, providing replay protection on top of the timestamp window
```bash
curl -s http://127.0.0.1:3000/v1/bonds/lock \
-H 'content-type: application/json' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "identityId": "id_abc123", "amountCents": 5000, "currency": "USD", "ttlSeconds": 300, "reason": "marketplace bid" }'
```
Returns a `bondId`.
**3. Execute a bonded action**
```bash
curl -s http://127.0.0.1:3000/v1/actions/execute \
-H 'content-type: application/json' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "identityId": "id_abc123", "bondId": "bond_xyz", "actionType": "place-bid", "payload": { "item": "widget-42", "price": 1500 }, "exposure_cents": 1500 }'
```
Returns an `actionId`. The bond's available capacity is reduced by `ceil(exposure_cents × 1.2)`.
**4. Resolve the action**
```bash
curl -s http://127.0.0.1:3000/v1/actions/<actionId>/resolve \
-H 'content-type: application/json' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "outcome": "success", "resolverId": "id_resolver123" }'
```
Outcome must be one of: `success`, `failed`, or `malicious`. On `success` or `failed`, that action's reserved exposure is settled and released immediately. On manual `malicious`, the first eligible resolver vote returns a pending response with `finalized: false`, `actionStatus: "open"`, `maliciousVotes: 1`, and `maliciousVotesRequired: 2`; the second distinct eligible resolver vote finalizes the slash. Sweeper auto-slash on expired actions remains immediate.
### Common Errors
| Error | Cause | Fix |
|---|---|---|
| `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 |
| `MISSING_NONCE` | `x-nonce` header is missing | Send a fresh nonce on every POST request |
| `DUPLICATE_NONCE` | Same nonce reused by the same identity | Generate a fresh UUID for every request |
| `DUPLICATE_MALICIOUS_VOTE` | Same resolver tried to cast a second malicious vote for the same open action | Use a different eligible resolver identity for the second malicious vote |
| `TIER_BOND_CAP_EXCEEDED` | Bond amount exceeds identity's trust tier cap | Build reputation with successful resolutions to unlock higher tiers |
| `INSUFFICIENT_BOND_CAPACITY` | Bond doesn't have enough remaining capacity | Lock a larger bond or resolve outstanding actions to free capacity |
| `RATE_LIMIT_EXCEEDED` | More than 10 executes in 60 seconds for this identity | Wait and retry, or spread actions across a longer window |
| `IDENTITY_BANNED` | Identity has been banned (manually or after 3 malicious resolutions) | Contact the operator or use a different identity |
| `SERVER_MISCONFIGURED` | Auth key not set and `AGENTGATE_DEV_MODE` is not `true` | Set the missing auth key or set `AGENTGATE_DEV_MODE=true` for local dev |
For the full security posture, see the **[Threat Model](docs/threat-model.md)**.
---
## Core Concepts
### Identity
- Ed25519 public key (raw 32-byte base64)
- All state-changing endpoints require signed requests — including identity registration itself, which requires proof-of-possession (the caller must sign the request with the private key matching the public key being registered)
- Public key uniqueness enforced at the database level — duplicate identity registration is rejected with `409 DUPLICATE_IDENTITY`
- Replay protection via timestamp validation (60-second window, 5-second future tolerance) AND nonce store (duplicate rejection per identity)
- Named agent support: set `AGENTGATE_AGENT_NAME` env var to create separate identity files per agent (e.g., `agent-identity-trader.json`)
Signed message format: `sha256(nonce + method + path + timestamp + JSON.stringify(body))`
Required headers: `x-agentgate-timestamp`, `x-agentgate-signature`, `x-nonce`
### Reusable Bond Model
Bonds are not single-use. Each bond represents reusable execution capacity.
- **Capacity rule:** effective exposure = `ceil(declared_exposure × 1.2)`
- **Constraint:** `outstanding_exposure_cents + effective_exposure <= amount_cents`
- If exceeded → `INSUFFICIENT_BOND_CAPACITY`
- **TTL cap:** maximum 86400 seconds (24 hours) — requests exceeding the cap are rejected
- Bond status lifecycle during normal settlement: `active` → `occupied` (when action attached) → `released` / `burned` / `slashed`
- Idle bonds that are touched after their TTL has elapsed are marked `expired`; expired bonds with open actions are handled by the sweeper
### Exposure Lifecycle
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.
- **Execute:** exposure reserved, `outstanding_exposure_cents` incremented, bond marked `occupied`
- **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`
- **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`
- **Resolve (malicious):** manual resolution uses a narrow dual-control seam: the first eligible non-executor resolver records a pending malicious vote and leaves the action open; the second distinct eligible resolver releases that action's exposure, reduces `amount_cents` (clamped at zero), increases `slashed_cents`, and closes the bond as `slashed` when no open actions remain
- **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
### Auto-Slash Sweeper
A background sweeper runs every 60 seconds, checking for actions whose associated bond has expired while the action is still open. Any such action is automatically resolved as `malicious` and slashed immediately; the sweeper does not go through the manual dual-control vote path. On the same 60-second interval, the server also cleans up expired nonces (older than 5 minutes) and expired rate-limit buckets (older than 60 seconds). All three run with clean shutdown on SIGINT/SIGTERM.
### Reputation Scoring & Trust Tiers
Each identity accumulates a reputation score based on its history:
`score = locks×2 + actions×3 + successes×10 - failures×5 - malicious×20`
The dashboard shows per-identity scores with color coding (green for positive, red for negative, gray for zero). Available via the `get_reputation` MCP tool or the dashboard.
**Progressive Trust Tiers** — bond capacity is reputation-gated. Tiers are purely computed from resolution history at bond-lock time (no stored state):
| Tier | Label | Requirement | Bond Cap |
|---|---|---|---|
| 1 | New | Default | 100¢ |
| 2 | Established | 5 qualifying successes from 2 distinct resolvers, 0 malicious | 500¢ |
| 3 | Trusted | 20 qualifying successes from 20 distinct resolvers, 0 malicious | No tier cap (normal capacity rules) |
- Any malicious resolution forces immediate demotion to Tier 1
- Only successes with at least 100¢ effective exposure count toward promotion
- Trust tiers are computed from the resolution history record only: outcome, effective exposure, and resolver identity
- Banned identities are blocked separately; the dashboard keeps showing the history-based tier and adds a `[BANNED]` tag
- Exceeding the tier cap returns `403 TIER_BOND_CAP_EXCEEDED`
**Recent hardening**
- What changed: manual `malicious` action resolution now requires two distinct eligible resolver identities; the first vote is stored as a pending malicious vote, the second vote finalizes the existing slash path, and `success`, `failed`, plus sweeper auto-slash stay unchanged.
- How to verify: run `npm test -- --run test/app.test.ts test/trust-tier.test.ts test/sweeper.test.ts`, then `npm test`, `npm run lint`, and `npm run build`.
- Next steps: if you ever need broader adjudication than this one manual malicious seam, add it as a separate design instead of expanding this into a general quorum system.
### Outbound HTTP Safety (`market.http`)
- **Host:port allowlist** — each allowlist entry is a `host:port` pair (e.g., `localhost:3000`). Wildcard port supported (e.g., `localhost:*`). Default allowlist is fail-closed to loopback ports 80 and 443 only; local dev services on custom ports must be explicitly listed in `AGENTGATE_HTTP_ALLOWLIST`
- http/https only (default port inferred: 80 for http, 443 for https)
- Timeout (default 2500ms)
- Max request size, max response size
- **Response sanitization** — before persisting outbound response data to the database, headers are stripped and the body is truncated to 1024 characters (with a `[truncated]` marker). This only affects storage — the full response is still returned to the caller
- **Redirect protection** — `fetch` is called with `redirect: "manual"`; every redirect hop has its `Location` header re-checked against the allowlist before following (max 5 hops). Prevents an allowlisted host from acting as a trampoline to a non-allowlisted host.
- Errors wrapped as `DESTINATION_BLOCKED` or `REDIRECT_BLOCKED`
Environment variables: `AGENTGATE_HTTP_ALLOWLIST`, `AGENTGATE_HTTP_TIMEOUT_MS`, `AGENTGATE_HTTP_MAX_BODY_BYTES`, `AGENTGATE_MAX_RESPONSE_BYTES`
---
## MCP Transport
AgentGate exposes its 7 tools to Claude Desktop over two transports:
- **Streamable HTTP** (recommended) — Express server on port 3001 at `/mcp`. Claude Desktop connects via `mcp-remote`. Sessions are managed server-side with a 100-session cap, 1MB body size limit, and automatic cleanup of sessions idle for more than 5 minutes.
- **stdio** — launches `src/mcp/server.ts` as a subprocess directly from Claude Desktop.
Both transports require the AgentGate HTTP server (`npm run restart`) to be running.
### Claude Desktop config (HTTP transport via mcp-remote)
**Local:**
```json
{
"mcpServers": {
"agentgate": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:3001/mcp"]
}
}
}
```
File location: `~/Library/Application Support/Claude/claude_desktop_config.json`
### MCP Tools (7 total)
| Tool | Description |
|---|---|
| `create_identity` | Create or load an Ed25519 agent identity |
| `lock_bond` | Lock a bond (stake) for an identity |
| `execute_bonded_action` | Execute a bonded action through the gate |
| `resolve_action` | Resolve an action as success/failed/malicious |
| `get_reputation` | Get identity reputation score |
| `create_market` | Create a prediction market with a yes/no question and resolution deadline |
| `resolve_market` | Resolve an open market as yes/no — settles all positions automatically |
---
## Prediction Markets
AgentGate includes a prediction market demo that illustrates multi-agent economic coordination using bonds as stake.
**How it works:**
1. An operator creates a market with a yes/no question and a resolution deadline (must be a valid future ISO 8601 timestamp)
2. Agents take positions by executing a `market.position` action against a locked bond, declaring a `side` of `yes` or `no`
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
**REST endpoints:**
```bash
# Create a market
curl -s http://127.0.0.1:3000/markets \
-H 'content-type: application/json' \
-H 'x-agentgate-key: YOUR_KEY' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "creatorId": "id_abc123", "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2026-04-20T00:00:00Z" }'
# Resolve a market
curl -s http://127.0.0.1:3000/markets/<marketId>/resolve \
-H 'content-type: application/json' \
-H 'x-agentgate-key: YOUR_KEY' \
-H "x-agentgate-timestamp: $TIMESTAMP" \
-H "x-agentgate-signature: $SIGNATURE" \
-H "x-nonce: $(uuidgen)" \
-d '{ "outcome": "yes", "resolverId": "id_abc123" }'
```
**MCP tools:** `create_market` and `resolve_market` expose the same flow to Claude Desktop.
The dashboard shows a live Markets table with status color-coding (open → amber, resolved → green).
---
## Dashboard
AgentGate includes a real-time HTML dashboard at http://127.0.0.1:3000/dashboard. It shows:
- Summary bar with identity, bond, action, and market counts
- Per-identity reputation scores with color coding and trust tier labels (Tier 1 New / Tier 2 Established / Tier 3 Trusted)
- Tables for bonds, actions, identities, and markets with truncated IDs and status indicators
- Agent names displayed per identity (for multi-agent setups)
- `[BANNED]` tags on banned identities
The page auto-refreshes every 5 seconds. The server must be running.
---
## Health Check
```
GET /health
```
Returns `200 OK` with `{ "status": "ok", "timestamp": "<ISO>" }`. No authentication required — designed for external uptime monitors (e.g., UptimeRobot).
---
## Running Locally
**Install:**
```
npm install
```
**Start server:**
```
npm run restart
```
This kills any old server process on port 3000 and starts fresh. Fastify REST API runs at http://127.0.0.1:3000, MCP HTTP server at http://127.0.0.1:3001/mcp, dashboard at http://127.0.0.1:3000/dashboard. The sweeper, nonce cleanup, and bucket cleanup logs appear every 60 seconds. Database file is created automatically at `data/agentgate.sqlite` on first run, with automatic backups to `data/backups/` on each startup (keeps the 5 most recent).
**Run tests:**
```
npm run test
```
123 tests across 12 test suites (API, MCP integration, prediction markets, sweeper, red team, outbound HTTP, dashboard, adapter).
---
## Security
### Nonce Replay Protection
All POST endpoints require an `x-nonce` header. The server stores each nonce per identity and rejects duplicates with a `409 DUPLICATE_NONCE` response. The `AgentAdapter` generates UUID nonces automatically. Expired nonces (older than 5 minutes) are cleaned up every 60 seconds alongside the sweeper. This provides replay protection on top of the 60-second timestamp window.
### MCP Endpoint Authentication
The MCP HTTP endpoint (port 3001) is protected by a shared-secret header.
- Set `AGENTGATE_MCP_KEY` in your `.env` file (loaded automatically via dotenv on startup)
- If the key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests are rejected with `500 SERVER_MISCONFIGURED`
- If the key is **not set** and `AGENTGATE_DEV_MODE=true`, auth is skipped (suitable for local dev)
- If the key **is set**, any request to `/mcp` without a matching `x-agentgate-key` header receives a `401 UNAUTHORIZED` response
`.env` entry:
```
AGENTGATE_MCP_KEY=your-long-random-secret
```
### REST API, Admin & Dashboard Authentication
Auth is split into three independent environment variables, each protecting a different surface:
| Variable | Protects | How it's checked |
|---|---|---|
| `AGENTGATE_REST_KEY` | All non-admin POST routes (bonds, actions, markets) | `x-agentgate-key` header |
| `AGENTGATE_ADMIN_KEY` | Admin endpoints (`/admin/ban-identity`, `/admin/unban-identity`) | `x-agentgate-key` header |
| `AGENTGATE_DASHBOARD_KEY` | Dashboard (`/dashboard`) | HTTP Basic Auth (username `admin`, password = key value) |
- **Auth is required by default.** If a key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests to that surface are rejected with `500 SERVER_MISCONFIGURED`
- If `AGENTGATE_DEV_MODE=true`, missing keys are allowed and auth is skipped for that surface (suitable for local dev)
- If a key **is set**, requests without a valid credential receive `401 UNAUTHORIZED`
- Each key can be rotated independently without affecting the others
```
AGENTGATE_DEV_MODE=true # skip auth enforcement for local dev (default: not set — auth required)
AGENTGATE_REST_KEY=your-rest-secret
AGENTGATE_ADMIN_KEY=your-admin-secret
AGENTGATE_DASHBOARD_KEY=your-dashboard-secret
```
### Identity Governance
Operators can ban and unban identities via the admin API. Banned identities receive `403 IDENTITY_BANNED` on all `lockBond` and `executeAction` calls.
```bash
# Ban an identity
curl -s http://127.0.0.1:3000/admin/ban-identity \
-H 'content-type: application/json' \
-H 'x-agentgate-key: YOUR_KEY' \
-d '{ "publicKey": "<base64-encoded Ed25519 public key>" }'
# Unban an identity
curl -s http://127.0.0.1:3000/admin/unban-identity \
-H 'content-type: application/json' \
-H 'x-agentgate-key: YOUR_KEY' \
-d '{ "publicKey": "<base64-encoded Ed25519 public key>" }'
```
**Auto-ban:** an identity is automatically banned after 3 malicious action resolutions. The trigger logs an `identity_auto_banned` security event.
### Security Event Logging
All security-relevant events are logged as structured JSON to stderr with an `event` field for easy filtering:
| `event` | Trigger |
|---|---|
| `auth_failed` | Wrong or missing `x-agentgate-key` on REST or MCP |
| `signature_failed` | Missing headers, stale timestamp, or bad Ed25519 signature |
| `duplicate_nonce` | Same nonce reused by the same identity |
| `bond_slashed` | Action resolved as malicious (via API or sweeper) |
| `identity_auto_banned` | Identity automatically banned after 3 malicious resolutions |
| `outbound_blocked` | `market.http` action blocked by allowlist or protocol check |
Each entry includes relevant context: `identityId` (truncated), `endpoint`, `reason`, and `requestId` where available.
---
## Security Hardening
AgentGate v0.2.0 was put through a structured red team process before release: 20 adversarial attack scenarios written as automated tests across 5 phases.
**Invariant validator** — every attack test ends by calling `validateInvariants(db)`, which runs 8 SQL assertions against the live database: bond amounts never go negative, outstanding exposure never exceeds bond capacity, settlement is always consistent, and nonces are never duplicated. Any state corruption — however subtle — causes an immediate test failure with a precise diagnostic.
**Attack phases:**
| Phase | Focus | Tests | Findings |
|---|---|---|---|
| 1 | Bond/exposure math | 6 | Fixed: `slashed_cents` not written to DB on malicious resolution; negative `exposure_cents` bypassed service-layer guard |
| 2 | Sweeper edge cases | 3 | Confirmed: resolve/sweep race is safe (SQLite serialization); double-slash prevented by open-action query |
| 3 | Replay attacks | 4 | Confirmed: nonce check catches replays even within the 60-second timestamp window; parallel duplicate nonce via `Promise.all` safely rejected |
| 4 | SQLite concurrency | 2 | Confirmed: `better-sqlite3` synchronous transactions serialize concurrent requests correctly |
| 5 | Outbound HTTP | 9 | Fixed: redirect bypass SSRF (allowlisted host could 302 to non-allowlisted target); IPv6 bracket allowlist bug (`[::1]` vs `::1`) |
**Total: 3 logic bugs fixed, 1 SSRF vulnerability fixed, 29 red team tests passing.**
Full attack scenarios documented in [`docs/red-team-plan.md`](docs/red-team-plan.md).
**Post-v0.2.0 hardening (Session 15):** A cold-eyes security audit identified additional issues that have since been fixed:
- Timestamp validation now rejects future-dated timestamps (>5 seconds ahead) in addition to stale ones, closing a clock-skew attack vector
- Dashboard HTML output is now XSS-safe — all database-backed values are escaped via `escapeHtml()` before interpolation
- MCP HTTP server hardened with a 1MB body size limit, 100-session cap, and automatic cleanup of sessions idle for more than 5 minutes
- Bond locking (`POST /v1/bonds/lock`) now requires Ed25519 signature verification, matching the auth model on all other state-changing endpoints
- Fastify upgraded to 5.8.2
**Post-v0.3.0 hardening (Session 20):**
- Bond TTL capped at 24 hours (86400 seconds) — requests exceeding the cap are rejected with `400 TTL_TOO_LONG`
- Action payload capped at 4096 bytes — oversized payloads are rejected with `400 PAYLOAD_TOO_LARGE`
- SQLite WAL mode enabled for improved concurrent read performance
- SQLite busy timeout set to 5 seconds — database operations wait instead of failing immediately when the database is locked
**Post-v0.3.0 hardening (Session 21):**
- **Fail-closed auth by default** — all auth keys (`AGENTGATE_REST_KEY`, `AGENTGATE_ADMIN_KEY`, `AGENTGATE_MCP_KEY`, `AGENTGATE_DASHBOARD_KEY`) are now required unless `AGENTGATE_DEV_MODE=true` is explicitly set. Missing keys return `500 SERVER_MISCONFIGURED` instead of silently skipping auth
- **SQLite CHECK constraints** — database-level enforcement on bonds (`amount_cents >= 0`, `outstanding_exposure_cents >= 0`, `slashed_cents >= 0`, valid status enum), actions (`exposure_cents >= 0`, valid status enum), and identities (`status IN ('active', 'banned')`). Startup data validation catches violations in existing databases
- **Rate-limit bucket cleanup** — expired `action_execute_buckets` entries (older than 60 seconds) are now pruned on the same 60-second interval as the sweeper and nonce cleanup
- **Demo echo route gated** — `POST /v1/demo/echo` is only registered when `AGENTGATE_DEV_MODE=true`; returns 404 in production
- **Market position filtering at DB level** — `resolveMarket()` now uses `json_extract(payload, '$.marketId')` in the SQL query instead of loading all open positions into memory
- **Market deadline validation** — `resolutionDeadline` must be a valid future ISO 8601 timestamp (enforced via Zod `.refine()`)
- **Payload size measured in bytes** — the 4096 limit now uses `Buffer.byteLength()` instead of `.length`, correctly measuring multi-byte characters
**Post-v0.3.0 hardening (Session 22):**
- **Positive exposure required** — `exposure_cents` is now mandatory and must be a positive integer. Zero-stake actions are rejected with `400 INVALID_EXPOSURE`
- **Prediction market payload validation** — `market.position` actions now require a JSON object with `marketId` and `side` (`yes` or `no`) before anything is persisted
- **Legacy malformed market rows no longer break resolution** — `resolveMarket()` now filters malformed JSON safely instead of letting SQLite throw `malformed JSON`
- **Banned identities fully blocked from market writes** — banned identities can no longer create markets or resolve markets they previously created
- **Final bond status reflects aggregate outcomes** — once the last action settles, bond status is derived from cumulative settlement totals (`slashed` beats `burned`, which beats `released`)
- **Safer outbound defaults** — the implicit outbound allowlist no longer grants arbitrary loopback ports
- **WAL-safe backups** — startup backups now use SQLite’s backup API instead of copying only the main `.sqlite` file
- **Agent adapter auto-init fix** — `AgentAdapter.lockBond()`, `executeBondedAction()`, and market helpers now auto-create/load the identity before building signed request bodies
- **MCP log sanitization** — MCP tool completion logs now record result shape/keys instead of dumping full tool payloads or outbound response bodies to stderr
**Comprehensive Security Audit (Codex + Claude Code cross-audit, 3 passes):**
The full `src/` codebase was put through a structured security audit — 3 passes to clean, with each pass re-reading every source file and verifying prior fixes before looking for new issues.
- **Pass 1:** 22 findings (6 critical/high, 10 medium, 6 low). All fixed: self-resolution bypass, market creator arbitrage, missing signature verification on market endpoints, adapter missing auth headers, 204 response crash, identity nonce replay, market resolution ordering, silent catch-all in settlement, bond expiration TOCTOU, dashboard auth weakness, protocol-relative redirect, malformed deadline NaN bypass, agent name path traversal, dashboard XSS via title length, weak request ID entropy, nonce recording order, SQLite error detection fragility, and documentation gaps.
- **Pass 2:** 6 medium findings, 5 low. All fixed: upper bounds on exposure/bond amounts (1B cent cap), market deadline capped at 1 year, MCP transport close error logging, strict MCP schema types, and known-limitation documentation for accepted trade-offs.
- **Pass 3:** Clean. No new findings. All prior fixes verified in place.
### How To Verify
Run:
```bash
npm run lint
npm run test
```
Regression coverage added for:
- adapter auto-identity creation
- WAL-safe backups
- banned market create/resolve attempts
- mixed-outcome bond status precedence
- explicit allowlisting for outbound HTTP tests
- market position payload validation and malformed legacy row handling
### Next Steps
- Add a small admin repair tool for malformed legacy `market.position` rows so operators can settle or purge bad historical data explicitly
- Add one end-to-end test for the startup backup path in `src/index.ts` to exercise the async backup call in the same flow production uses
---
## Remote Deployment (Archived)
AgentGate was previously deployed to a DigitalOcean droplet at agentgate.run with Caddy reverse proxy, UFW firewall, pm2 process management, and UptimeRobot monitoring. The live instance was decommissioned in March 2026. The project runs fully on localhost — see the "Running Locally" section above.
---
## Ecosystem
AgentGate is the core substrate in a larger family of reference projects. For a cold first run, start with the Governed WriteFile Demo; it is the narrowest proof path. The rest of this section is for readers who want adjacent reference implementations built on the same bond-and-slash model.
### Reference Agents
| # | Agent | What it proves | Tests | Repo |
|---|-------|---------------|-------|------|
| 001 | **Bonded File Transform** | Deterministic verification — machine checks machine | 22 | [agentgate-bonded-file-transform](https://github.com/selfradiance/agentgate-bonded-file-transform) |
| 002 | **File Guardian** | Command-based verification + rollback on failure | 50 | [agentgate-bonded-file-guardian](https://github.com/selfradiance/agentgate-bonded-file-guardian) |
| 003 | **Email Rewriter** | Human judgment in the loop | 11 | [agentgate-bonded-email-rewriter](https://github.com/selfradiance/agentgate-bonded-email-rewriter) |
| 004 | **Red Team Simulator** | Adversarial probing — five stages from solo attacker to 9-agent coordinated swarms. Includes Sleeper Agent (v0.6.0). | 330 | [agentgate-red-team-simulator](https://github.com/selfradiance/agentgate-red-team-simulator) |
| 005 | **Recursive Verifier** | Proof-style verification — generates executable scripts, runs in sandbox, scores, iterates | 149 | [agentgate-recursive-verifier](https://github.com/selfradiance/agentgate-recursive-verifier) |
| 006 | **Incentive Wargame** | Stress-tests incentive rules with AI-generated economic strategies | 301 | [agentgate-incentive-wargame](https://github.com/selfradiance/agentgate-incentive-wargame) |
### Governance Extensions
| Project | What it adds | Status | Repo |
|---------|-------------|--------|------|
| **Governed WriteFile Demo** | Narrowest first-run path: identity -> bond -> authenticated governed `write_file` -> independent on-disk verification -> audit artifact | v0.1.0 shipped | [agentgate-governed-writefile-demo](https://github.com/selfradiance/agentgate-governed-writefile-demo) |
| **Delegation Identity Proof** | Bounded human-to-agent delegation with dual bonds and a 6-state machine | v0.1.0 shipped | [agentgate-delegation-proof](https://github.com/selfradiance/agentgate-delegation-proof) |
| **MCP Firewall** | Governance proxy for MCP tool calls — bonds before forwarding, slashes on bad outcomes | v0.1.0 shipped | [agentgate-mcp-firewall](https://github.com/selfradiance/agentgate-mcp-firewall) |
| **Epistemic Poisoning Simulator** | Tests whether bond-and-slash can govern knowledge integrity | Design stage | — |
### Writeups
- [What Happens When an AI Agent Has to Post Collateral Before It Acts?](https://medium.com/@selfradiance/what-happens-when-an-ai-agent-has-to-post-collateral-before-it-acts-28e098198936)
- [What If Your AI Coder Had Skin in the Game?](https://medium.com/@selfradiance/what-if-your-ai-coder-had-skin-in-the-game-ee10664d475c) (Agent 002)
- Agent 003 writeup submitted to Coding Nexus on Medium (pending review)
### Related
- [RestaRules](https://github.com/selfradiance/restarules) — machine-readable agent conduct rules for restaurants. A different angle on agent governance: environment-published norms rather than substrate-enforced bonds.
---
## Tech Stack
- **Language:** TypeScript (100%)
- **Runtime:** Node.js 20+
- **Web framework:** Fastify
- **Database:** SQLite via better-sqlite3
- **Validation:** Zod
- **Testing:** Vitest (123 tests)
- **MCP SDK:** @modelcontextprotocol/sdk
- **CI:** GitHub Actions (build, lint, and test on every push and PR to main)
---
## Project Files
- `src/` — core server logic (Fastify API, service layer, database, signing, structured logging)
- `src/mcp/` — MCP server exposing 7 tools over stdio and Streamable HTTP transports
- `src/agent-adapter.ts` — clean agent-facing interface that hides signing, nonces, and HTTP details
- `src/dashboard.ts` — real-time HTML dashboard
- `src/backup.ts` — automatic database backup on startup (keeps 5 most recent)
- `src/reputation.ts` — reputation scoring and trust tier computation
- `test/` — 123 tests across 12 suites (API, MCP integration, prediction markets, sweeper, red team, outbound HTTP, dashboard, adapter, reputation, trust tier)
- `examples/` — demo agents and adapter demo
- `docs/threat-model.md` — threat model (attacks, defenses, non-goals, assumptions)
- `docs/red-team-plan.md` — 20 adversarial attack scenarios across 5 phases
- `docs/manifesto.md` — "How I Built AgentGate" — the full story
- `.github/workflows/ci.yml` — GitHub Actions CI (build, lint, and test on every push/PR)
- `AGENTS.md` — conventions for AI coding agents
- `LICENSE` — MIT License
---
## Roadmap
Future modules under consideration (not yet in development):
- **ZK-proof bond verification** — Anonymous agent identity with economic accountability. Agents prove they hold a valid bond via zero-knowledge proof without revealing which agent they are. Monitoring ZK-API (Buterin/Davide, 2026) and OpenAnonymity for integration-ready implementations.
- **Non-deterministic verifiers** — LLM-as-judge, API state checks, and human review verification modes. The bond loop is verifier-agnostic by design.
- **Hardware-backed delegation** — YubiKey/FIDO2/WebAuthn to authorize agent bonds, replacing OAuth.
---
## License
MIT — see [LICENSE](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
Vibe-Trading
Vibe-Trading: Your Personal Trading Agent
ai-berkshire
Berkshire in the AI Era: A Value Investment Research Framework Based on...
hexstrike-ai
HexStrike AI is an AI-powered MCP cybersecurity automation platform with 150+ tools.
valuecell
Valuecell is a Python project for efficient data management.
tradingview-mcp
AI-assisted TradingView chart analysis — connect Claude Code to your...
tradingview-mcp
TradingView MCP Server offers real-time market analysis for crypto and stocks.