DocsGuardrails

Guardrails

Four stages stand between a mind and the chain, and any one of them can refuse on its own. This page is the one to read if you only read one.

mind → policy → signer → chain

  1. Tool schema — make the dangerous thing unsayable

    No tool takes an address. Fields like recipient or destination are rejected by the schema. Recipients are computed by the platform from on-chain snapshots, and the model can only reference ids the platform put into its context.

  2. Policy engine — deterministic, and the mind cannot change it

    A zero-dependency pure function: it does not touch the chain, an LLM or the database. It takes the proposed action plus a state snapshot and returns either an approval or every rejection code that fired. The same implementation runs for autonomous moves and for moves a human triggers.

  3. Isolated signer — does the whole job again, independently

    Separate service, separate keys, no network path from the mind. It recomputes available balance itself, re-runs the simulation itself, checks the action against a program allowlist, and pays only wallets that actually hold the token on chain at the moment of signing. It has its own circuit breaker that fires regardless of what the policy engine approved.

  4. Chain — only confirmed results change the books

    Quotes and estimates never move the ledger. A buyback is settled from the confirmed on-chain balance change, and the difference against the quote is booked as slippage.

No tool takes an address

This is the base of everything else, so it is worth being exact about what the claim is.

This is not a prompt telling the model not to send funds anywhere. The model is structurally unable to express "send to an arbitrary address".

There is no address-shaped parameter anywhere in the tool surface for it to fill in. A tool definition that declares one fails to compile — the constraint is in the types, so it cannot be reintroduced by someone adding a tool in a hurry.

Payout tools take filters, not addresses: "holders above this balance who have held for this long". The platform resolves that against its own holder index. The model never sees, and never supplies, a destination.

The policy engine

Enforced by policy, not the model.

Limits apply per action, per hour and per day, as a share of that token’s own available treasury. On top of those sit reserve floors, a price-impact bound, an exposure cap, and cooldowns. The mind has no tool that can modify any of them.

It returns every code, not the first one

Returning only the first failure turns debugging into whack-a-mole, and for an agent that spends money in public, "which lines did this move cross" is itself information worth publishing. There are twenty-one codes today:

GroupCodes
LimitsSINGLE_ACTION_LIMIT · HOURLY_LIMIT · DAILY_LIMIT · PROGRAM_COMMIT_LIMIT · TOO_MANY_PROGRAMS · REWARD_LIMIT · COOLDOWN
FundsINSUFFICIENT_FUNDS · RESERVE_POLICY · ETH_RESERVE · DUST_DISTRIBUTION · INVALID_AMOUNT
MarketPRICE_IMPACT · EXPOSURE_LIMIT · STALE_QUOTE
Data freshnessSTALE_DATA · PRICE_UNAVAILABLE
StateUNKNOWN_COIN · STATE_CHANGING · MIND_HALTED
Sybil resistanceSYBIL_INELIGIBLE

The isolated signer

The third stage trusts no number it is handed. "Independent" here means it repeats the work rather than reviewing the conclusion:

  1. Recomputes the available balance from the ledger itself.
  2. Re-runs the simulation itself.
  3. Checks the action against a per-action-type program allowlist.
  4. Pays only wallets holding the token on chain at signing time.
  5. Applies its own per-transaction and per-hour outflow caps.
  6. Persists the signed bytes before broadcasting them.

The breaker is a latch

When the breaker trips it stays tripped until a human unlocks it against a written runbook, and the unlock itself is audited. It is not a rate limiter that heals on its own.

The reason is specific to this design. The treasury is an off-chain ledger with no on-chain custody to fall back on, so when solvency does not reconcile we cannot tell a transient blip from an active leak. Automatic recovery would mean continuing to spend while the cause is still unknown.

The global invariant

Every request is checked against the coin’s own available balance, reserved atomically, and re-checked by the isolated signer before anything is signed.

That sentence holds for every path that spends money — autonomous or manual, rewards or buybacks or program funding. The two halves that do the work are "reserved atomically", which is why one token can never spend another’s money, and "re-checked by the isolated signer", which is why a bug in the policy engine is not by itself enough to lose funds.