Steals the token
Nothing. Every call needs a fresh proof signed by the agent key named in cnf.jkt. Replays are single-use-jti dead.
TG_PROOF_INVALID
rec 001 capability control plane for ai agents
Toolgate sits between agents and their tools. Agents hold one Ed25519 key — nothing else. Every call is policy-checked, budget-metered, executed with credentials injected server-side, and sealed into a signed audit chain.
every call, in order
rec 002 interactive · runs entirely in your browser
This is the real decision logic and a real SHA-256 hash chain, computed live with Web Crypto. Pick what the agent tries to do — then try to tamper with the record of it.
agent request
delegation budget
8 / 8 units
active policy · first match wins
gate pipeline
Parked. The human decides on exactly these arguments — the agent can't swap them afterwards.
audit chain · sha-256, each record seals the one before it
— no records yet. send a call through the gate. —
rec 003 integrate
# POST /v1/control/grants
{
"userId": "usr_sam", "agentId": "agt_assistant",
"authorization": [{"upstream": "crm", "tools": ["*"]}],
"budgetMaxUnits": 100, "policyId": "pol_default"
}
# pip install toolgate
client = ToolgateClient(
agent_private_jwk=key, # its only secret
grant_id="grt_...", base_url=GATE)
r = client.call("crm", "read_contact",
{"contactId": "c-014"})
parked = client.call("email", "send_email",
{"to": "cfo@globex.com", ...})
# 202 pending_approval — Sam sees the exact args
done = client.wait_for_approval(parked.approval_id)
rec 004 token anatomy
RFC 8693 semantics: sub stays the human, act.sub is the agent. Hover a claim.
{
"sub": "usr_sam",
"act": { "sub": "agt_assistant" },
"aud": "toolgate:gate",
"authorization_details": [
{ "upstream": "crm", "tools": ["*"] }
],
"cnf": { "jkt": "kUx0...Q7w" },
"txn": "txn_9f21",
"exp": 1788427121 // ~120s, jittered
}
sub — the human whose authority is exercised. Never the agent.
act.sub — who is actually acting. Audit records carry both, forever distinguishable.
aud — audience-bound; rejected anywhere but the gate. No token passthrough.
authorization_details — the reachable surface (RFC 9396). Outside it, policy never even runs: denied.
cnf.jkt — thumbprint of the agent key that must sign a one-time proof per call. A stolen token is inert.
txn — per-task id; the join key across every audit record it produced.
exp — minutes, not months. Jittered so harvested batches never expire together.
new in 0.5
rec 005 guarantees
Nothing. Every call needs a fresh proof signed by the agent key named in cnf.jkt. Replays are single-use-jti dead.
TG_PROOF_INVALID
A bounded box: authorized tools ∩ policy ∩ remaining budget ∩ ≤300s. And revocation kills live tokens on the next call.
TG_REVOKED
Every decision — denials included — is hash-chained and Ed25519-signed. Edit, remove, or reorder one record and verification names the exact breakpoint.
valid: false, broken_at_seq: n