Docs menu

Guides

Budget attestation

Multi-agent systems delegate: an orchestrator hands a sub-task to a tool-calling agent, which may hand a further sub-task to another agent — possibly across a process or trust boundary. That handoff has never carried an enforceable budget. Budget attestation closes the gap with a chain of Ed25519-signed blocks where each hop can only narrow what it received — less budget, fewer providers, less delegation depth, an earlier expiry — never widen it.

RateGuard's own extension, not a compliance claim

This mirrors the shape the IETF Agent Identity Protocol draft (draft-prakash-aip, draft-singla-agent-identity-protocol) is standardizing around. The spec is still draft-level — RateGuard implements attenuated delegation chains as its own extension, not a claim of AIP compliance.

Mint a root, then delegate

Go
// authorityPrivateKey is the long-term key every verifier must already
// trust out-of-band — the same way a TLS client trusts a CA root certificate.
root, holderKey, err := rateguard.NewRootBudgetToken(authorityPrivateKey, rateguard.AttestOptions{
    Grant: rateguard.BudgetGrant{
        MaxTokens: 100_000,
        Providers: []string{"openai", "anthropic"},
        MaxDepth:  3,
        ExpiresAt: time.Now().Add(time.Hour),
    },
})

// parentPrivateKey must match the token's current holder key — proof the
// caller legitimately holds it, not just read a copy of it.
delegated, subAgentKey, err := rateguard.Attest(root, holderKey, rateguard.AttestOptions{
    Grant: rateguard.BudgetGrant{
        MaxTokens: 10_000,
        Providers: []string{"openai"},
        MaxDepth:  0, // may use it, may not delegate further
        ExpiresAt: time.Now().Add(10 * time.Minute),
    },
})

Narrowing is enforced, not conventional

Attest rejects a grant that widens any field relative to its parent — a wider MaxTokens, a provider not in the parent's list, more delegation depth, or a later expiry all fail with an explicit error, at delegation time.

FieldRule
MaxTokensUnlimited only if the parent is also unlimited; otherwise ≤ parent's value.
Providers / ModelsEmpty (any) only if the parent is also unrestricted; otherwise a subset of the parent's list.
MaxDepth≤ parent's MaxDepth − 1 — each delegation consumes one unit of depth.
ExpiresAtAt or before the parent's expiry. Mandatory on every grant — an unexpiring budget token is a standing liability.

A token is data, not proof of possession

Anyone who intercepts a serialized token can read its terms — that's what VerifyChain checks: the signature chain is valid, every block narrowed its parent, nothing has expired. It does not prove the presenter is the legitimate holder. For an authorization decision, the holder must sign a verifier-supplied context with its private key:

Go
// The sub-agent proves possession over a verifier-supplied nonce.
sig, err := rateguard.Sign(delegated, subAgentKey, []byte("request-nonce"))

// The verifier checks the chain AND the proof of possession together.
grant, err := rateguard.VerifyPresentation(
    delegated, authorityPublicKey, []byte("request-nonce"), sig,
)

Tip

A signature over one context never verifies against a different context — a captured token plus a captured signature can't be replayed against a fresh challenge.

Who generates the delegate keypair?

DelegatePublicKey on AttestOptions is optional. Omit it and RateGuard generates a fresh keypair, returning the private key for you to hand to the sub-agent you're spawning — the convenient path for a single hop. Supply it when the sub-agent already generated its own keypair and shared only the public half; its private key then never transits through the delegator, the recommended pattern for longer chains.

Wire format

Marshal / ParseBudgetToken round-trip a token as compact JSON text — safe to pass as an MCP tool argument, an HTTP header, or a file handoff.

MCP tools

attest_budget / verify_budget
// attest_budget — mint a root or delegate further
{
  "signing_key": "<base64 Ed25519 private key>",
  "parent_token": "<omit to mint a root token>",
  "max_tokens": 10000, "providers": ["openai"], "max_depth": 0,
  "expires_in_seconds": 600
}
// -> { "token": "...", "delegate_private_key": "...", "delegate_public_key": "..." }

// verify_budget — chain-only, or full presentation with context+signature
{
  "token": "...", "root_public_key": "<base64 Ed25519 public key>",
  "context": "request-nonce", "signature": "<base64, from Sign>"
}
// -> { "valid": true, "proof_of_possession_verified": true, "effective_grant": {...} }

See Agents & MCP for the other five pre-flight tools. Full walkthrough with output: examples/budget-attestation.

Scope

Single-hop delegation, verified end-to-end, is the primary target for v0.1. The chain design supports multiple hops because attenuation only works if it composes, but longer chains are unproven in production here and should be adopted cautiously. Go only for now — Node and Python parity is tracked but not yet built.