Why an agent operation was refused

Reading a refusal by the stage it fired at.

Written By Dustin

Last updated About 3 hours ago

Agent refusals are easier to act on once you know they are staged. The engine checks in a fixed order, stops at the first failure, and the code it returns tells you which stage you are at — and therefore which knob to turn. Trying the next flag at random usually widens something you did not mean to widen.

The stages

  1. Token validity. Right version, signature verifies against the issuer, the issuer is a human identity (or the chain terminates at one), unexpired, unrevoked, and — if delegated — the whole chain still valid.
  2. Scope. Every touched path is inside path_scope minus the excluded paths, and any target lane is inside the lane scope.
  3. Operation. The operation is in the token's allowed set, not in its denied set, and the relevant capability flags are set.
  4. Policy, clearance and keys. Only now is policy evaluated, a content key sought, or a write-policy proof required.

Stages 1 to 3 are entirely offline and local. Stage 4 is the only one that may need to consult a Key Authority.

What each code tells you

CodeWhat to change
TVO-TOKEN-002 / -003Expired or revoked. Issue or renew. Nothing about scope will help, and it is easy to mistake this for a permission problem.
TVO-TOKEN-001A path is outside scope — usually because it is policy-protected, and every agent token is structurally excluded from those. Either the agent should not be touching it, or the path should not be protected.
TVO-TOKEN-004The verb was never granted. Some verbs — obliterate, modify policy, force a tag — are never granted to agents at all, so the answer is that a human does it.
TVO-TOKEN-005A delegation attempt that was not provably narrower than its parent.
TVO-TOKEN-010The token reached a clearance-gated path with no clearance, so the object would seal for nobody.
TVO-PERM-001The token was in scope, but the agent's attributes do not satisfy the policy.

A lane-scope refusal is worth a second look before you widen anything: an agent trying to write outside agent/<name>/** is usually an agent that should be promoting instead.

Why scope failures say so little

A scope rejection deliberately does not tell you what the policy on that path requires. It fires before policy is evaluated, and leaking the requirement there would turn a refusal into a way of mapping a repository you cannot read. That is why the ordering is normative rather than incidental.

If you need to know what a path requires, ask as yourself:

tovio policy show <path>
tovio access check <path> --identity <path-to-identity.pub>

Every refusal is recorded

A denied agent write is appended to the per-actor signed audit chain before the error is returned, and a non-gating lifecycle event fires alongside it. So you can ask what an agent attempted, not only what it managed:

tovio audit show --token <token-id>

Do not widen to make it go away

The reflex when an agent is blocked is to grant it more. Check the stage first — half of these are not permission problems at all. Clearance in particular is worth withholding through one or two rounds of "but it says it needs it".