Capability tokens¶
A capability token is how you hand an agent bounded authority: a signed object that says who the agent is, what paths it may touch, which operations it may perform, and when that all expires. This page is a how-to — issue, scope, renew, revoke, and delegate tokens. For the model behind them, see agent sessions; for the spec, see Policy & Tokens §6.
Built across the CLI and agent edge
The token primitive and the tovio agent new / show / renew / revoke / list lifecycle are implemented
in the Rust core. Delegation is end-to-end: the MCP issue_sub_token tool mints a narrower leaf, and
that leaf opens its own later session with the full root-to-leaf chain re-verified and recorded in
provenance. Relay-side write enforcement is implemented in the Forge. The commands below show the
human/CLI surface; the CLI issues the §10 default scope, and narrower scopes come from delegation.
Issue a token¶
One command issues a token signed by your human identity, with safe defaults. The agent's name, the model it runs, and a short task description are required; the expiry is given in hours:
$ tovio agent new cgm-sync --model anthropic:claude-sonnet --task "stabilize the CGM sync" --expires-in 8
--json for the machine-readable result an orchestrator would parse:
$ tovio agent new cgm-sync --model anthropic:claude-sonnet --task "stabilize the CGM sync" --expires-in 8 --json
{
"token_id": "cap_7r4qy9m2x8k3v6bd0n1p5h",
"object": "3f9c…",
"agent": "did:tovio:agent/cgm-sync",
"model": "anthropic:claude-sonnet",
"authorized_by": "did:tovio:dustin",
"path_scope": ["**"],
"denied_path_scope": ["config/production/**", "secrets/**"],
"branch_scope": ["agent/cgm-sync/**"],
"secret_clearance": false,
"issued_at": 1782482400,
"expires_at": 1782511200,
"revoked": false,
"signature_valid": null
}
Timestamps are Unix seconds. The operation allow/deny lists and capability flags are set by the safe
defaults below and are part of the signed token; the CLI's JSON does not echo them. An MCP session's
effective_scope tool echoes the allowed operations alongside the scope globs --- today in the engine's
internal spelling (ConflictResolve, not conflict:resolve), so normalize before you compare.
The safe defaults¶
tovio agent new is designed so the easy thing is the safe thing. Out of the box a token gets:
| Default | Value | Why |
|---|---|---|
path_scope |
["**"] |
Broad access to ordinary code. |
denied_path_scope |
every policy-protected path | Everything except secrets. Protected paths are excluded at the scope stage — refused before policy is read, with no leak. |
secret_clearance |
false |
The agent cannot obtain keys for clearance-gated objects. Cleared agents are the rare, explicit case. |
allowed_ops |
read, commit, amend, branch:create, relay:fetch, relay:push, conflict:resolve |
Everyday coding operations. |
denied_ops |
obliterate, policy:modify, tag:create, tag:force, sub-token:issue |
No self-escalation, no history destruction, no delegation. |
branch_scope |
["agent/<name>/**"] |
Keeps agent work on namespaced lanes. |
expires_at |
now + --expires-in hours (default 24) |
A token is never indefinite. |
The summary, in one line: an agent made with tovio agent new can touch ordinary code immediately,
cannot read or write any policy-gated path, and cannot escalate. Promotion to broader authority is
a separate, explicit, audited action.
Every token expires by design
There is no "forever" token: --expires-in defaults to 24 hours when you omit it. Pick a window that
matches the task — a few hours for an interactive session, a workday for a long migration. The
deadline is the cryptographic backstop that makes a leaked token self-limiting.
What the scope fields mean¶
A token bounds an agent on independent dimensions. Each is checked in a fixed order on every operation (see enforcement).
path_scope and denied_path_scope¶
path_scope is the set of globs the token may read and write within. denied_path_scope is a set of
globs excluded from it — and exclusion always wins. A path matching a denied glob is out of
scope even if it also matches an allowed one. This is how a token expresses "everything except these
paths", which a positive list alone cannot, and it is how the default carves secrets out of **.
Globs use the standard TOVIO path matching: * within a segment, ** across whole segments.
Narrowing happens through delegation today
tovio agent new always issues the §10 default scope (** minus every policy-protected path); it has
no --scope or --deny-scope flag yet. To hand an agent a narrower slice — say docs/** only — issue
the root token with --can-delegate and mint the narrower leaf with the MCP issue_sub_token tool (or
the Node SDK's issueSubToken), as described under delegation.
A read or write outside scope is rejected before any policy is consulted, so the agent is told only that the path is out of scope — never what policy guards it. That ordering is a security property, not an accident: the early stage is not allowed to learn what the later stage gates.
secret_clearance¶
Read access in TOVIO is enforced by cryptography, not a server check. A protected file is a policy
object — ciphertext — and you read it only by obtaining its content key. secret_clearance is the
switch that governs whether a token can ever obtain a key for an object whose policy references
clearance:
secret_clearance: false(the default) — the agent cannot obtain a clearance-gated key through the TOVIO read surface. Even if the path were in scope and the policy otherwise satisfied, the read fails. This is not an operating-system sandbox for unrelated tools or plaintext supplied outside TOVIO.secret_clearance: true— the rare, explicit case: the token may obtain clearance-gated keys, if the agent identity's attributes also satisfy the policy.
Allowed and denied operations¶
allowed_ops is an allow-list: an operation not listed is denied by default. denied_ops is an
explicit deny-list, and deny overrides allow. The operation set is closed; the ones agents get by
default cover everyday coding. Several are withheld by default and never offered to agents over MCP:
| Operation | Default for agents |
|---|---|
read, commit, amend, conflict:resolve, relay:fetch |
Allowed |
branch:create, relay:push |
Allowed (each gated by a capability flag) |
obliterate |
Denied — permanent history removal is a human, fully-audited act |
policy:modify |
Denied — changing a policy is privilege-defining; agents must never self-escalate |
tag:create, tag:force |
Denied — creating or repointing a tag is withheld; tag:force requires a write proof |
sub-token:issue |
Denied by default — an agent delegates only if the token was issued with --can-delegate |
Capability flags gate specific operations further: can_create_branches, can_push_to_relay, and
can_issue_sub_tokens must each be true for the corresponding operation to succeed.
Inspect a token¶
show reports the token's scope — its agent, model, authorizer, path and lane globs, clearance, and
deadline — so you (or an agent debugging a denial) can see what it permits, and verifies the signature
against the issuing identity. Add --json for the machine shape: the same fields agent new --json
prints --- signature_valid is on both, null from agent new (nothing to compare against yet) and a
boolean from show. tovio agent list --json reports every issued token with an
active / expired / revoked status, and tovio agent token <id> prints the hex-serialized token
material an MCP client can present directly.
Renew a long-running token¶
A job that outlives its token must renew. Renewal is a deliberate re-issue — never a silent extension — of a fresh token with equal-or-narrower scope from the same human authorizer, linked to the prior token through provenance.
The result is a fresh token with a new token_id and a new issued_at / expires_at window, printed
in the same shape as agent new --json; the human output names the lineage
(Renewed cap_7r4qy9m2… → cap_2b8h4k0m…), and the signed audit chain records the renewal against the
prior token id.
Renew proactively, around 80% of the token's life
An orchestrator should renew before the deadline — a common threshold is 80% of the token's
TTL — so the active operation always runs under a valid token. Track the deadline yourself from
expires_at (tovio agent show --json, or the effective_scope echoed at connect): the MCP server
does not yet attach an approaching-expiry signal to tool responses. A renewal chain is treated as
one session, not many (see agent sessions).
An operation under an already-expired token fails closed with TVO-TOKEN-002 — there is no grace
period and no implicit extension. A revoked token, or a delegated sub-token, cannot be renewed
(TVO-TOKEN-007); renew the root and re-delegate.
Revoke a token¶
Revocation is immediate and audited. Revoke a token before its deadline when a task is done early, a session looks wrong, or you simply want to pull access:
The token is re-printed in the agent new --json shape with "revoked": true, the agent's registration
is removed from the access registry, and the revocation is appended to the signed audit chain. Revoking a
token that is already revoked is a safe no-op, but it prints no JSON result --- so treat exit code 0,
not the presence of an object, as the confirmation when you retry.
A revoked token's next operation fails with TVO-TOKEN-003 and the session closes. Revocation
cascades: any chain containing the revoked ancestor is rejected, and a live delegated session observes
it on its next call — every call re-verifies the whole persisted chain.
Delegate to sub-agents¶
An orchestrator agent can hand bounded slices of its own authority to sub-agents by minting
sub-tokens — but only if its token has can_issue_sub_tokens: true and sub-token:issue in its
allowed ops. Neither is a default; both are granted together by issuing the root with
tovio agent new … --can-delegate. The MCP tool is issue_sub_token. expires_at is required (unix
seconds, at or before your own expiry); paths, branches, and ops inherit your token's value when
left empty; can_delegate does not inherit --- it is false unless you ask for it, so a leaf never
gains delegation by accident. The core enforces monotonic narrowing: a sub-token must be provably
narrower than its parent on every dimension.
did:tovio:dustin (human)
└── cap_root path_scope=["src/frontend/**"], can_issue_sub_tokens=true, expires 22:00Z
├── cap_dash path_scope=["src/frontend/Dashboard.tsx"], expires 20:00Z
└── cap_panel path_scope=["src/frontend/MetricsPanel.tsx"], expires 20:00Z
Every dimension must shrink or stay equal: path_scope ⊆ parent, allowed_ops ⊆ parent, clearance
only dropped, each flag only dropped, expires_at <= parent. Anything the core cannot prove is
contained is conservatively rejected with TVO-TOKEN-005. The chain's root always remains the
original human — a sub-token's authorized_by is the delegating agent, but following
parent_token_id upward always terminates at a human-authorized token.
This is what makes hierarchical agents safe: a sub-agent can never reach beyond what its orchestrator could reach, which can never reach beyond what the human granted.
A leaf opens its own session
issue_sub_token verifies the whole chain before returning the signed leaf as hex. Hand that hex to
the sub-agent as its own TOVIO_TOKEN: at connect the core reconstructs the ancestor chain from the
persisted parent links and re-verifies every hop (human-rooted, unexpired, unrevoked, monotonically
narrower) before any session exists, and the sub-agent's commits record the complete
delegation_chain. Re-delegation needs an explicit can_delegate grant at every hop — rights never
propagate silently.
Provenance: every action traces to a human¶
You never have to trust an agent's word about what it did. On every successful write — commit, amend, conflict resolution, or pushed commit — the MCP server records an AgentProvenance block on the produced object. The agent cannot supply, omit, or override these fields; they are derived from the bound token and the server's own state.
| Provenance field | What it records |
|---|---|
token_id |
The leaf token the action ran under. |
model / model_hash |
The model powering the agent and its pin — today the BLAKE3 digest of the model identifier; a content hash of the weights or manifest replaces it once a model registry exists. |
task_id / task_description |
The bounded task the token authorized. |
prompt_hash |
A BLAKE3 hash of the initiating prompt, computed by the server. |
session_id |
A server-generated correlation ID shared by commits from one write session. |
tool_manifest_hash |
BLAKE3 over the canonical tovio-tool-manifest-v3 manifest — the exact tool catalog and allowed ops the session negotiated — computed at session start. |
mcp_server_version |
The build of the MCP server. |
authorized_by |
The human at the root of the delegation chain. |
delegation_chain |
The ordered token IDs, root → leaf. |
This makes every successful write performed through the MCP surface traceable to the recorded model, task, token chain, and human root authorizer. Provenance attaches to produced objects and operations; it is not a permanent line-by-line authorship claim after later merges or rewrites.
How a call is checked¶
Whichever surface the agent uses, the core runs the same four checks in this exact order on every operation. The order is the contract — earlier checks must not leak what later checks gate.
- Token validity — signed by a human, unexpired, not revoked, chain valid. →
TVO-TOKEN-002/003 - Path scope — target within
path_scope, not indenied_path_scope. Checked before any policy is read. →TVO-TOKEN-001 - Operation allow/deny — op in
allowed_ops, not indenied_ops, flags satisfied. →TVO-TOKEN-004 - Policy / clearance / key availability — for protected paths: policy evaluation and key
availability (reads), or a write proof (writes). →
TVO-PERM-001/TVO-PERM-002
A denial returns the structured error contract an agent parses — see the machine contract.
Where this connects¶
- The session a token drives: agent sessions.
- The server that validates it on every call: the MCP server.
- The error shapes a denial returns: the machine contract.
- The permission model the scope rides on: concepts.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure