The machine contract¶
Agents and scripts do not read prose — they parse a contract. This reference defines that contract: the
clean, stable output that --json, --quiet, and the MCP server emit, the structured
error envelope agents branch on, and the hard rule that none of it ever carries emoji, celebration,
or a warmth line.
The hard rule: no delight in the machine contract¶
TOVIO's human-facing output is deliberately warm — it celebrates a first landing and can name how to reverse a supported mutation retained in the local op-log. None of that may appear in machine output. This is a non-negotiable boundary, stated normatively in the Experience & Voice spec:
REQ-UX-011Emoji, celebration, warmth lines, and decorative glyphs MUST appear only in interactive human (TTY) output.--json,--quiet, and the MCP/agent API output MUST contain none of it — no emoji, no celebration, no "warmth" line. This preserves the machine contract and is non-negotiable: a delight feature that leaks into--jsonis a compatibility bug.
The discipline is simple to state and absolute in effect:
| Surface | Audience | May contain |
|---|---|---|
| Interactive TTY | A human at a terminal | Glyphs (✓ ✗ ! ⚠ 🔒 ⚡), color, one warmth line, a once-only milestone |
--json |
A script or orchestrator | Only structured JSON. No emoji, no warmth, no color. |
--quiet |
A pipeline | Only essential machine-relevant lines. No decoration. |
| MCP / agent API | An agent | Only parsed JSON objects and the error envelope. No prose to scrape. |
Why so strict? Joy is for the human; the machine contract is sacred. An emoji that slips into --json
can break a parser, shift a byte offset, or poison a downstream diff. So the rule is not "prefer plain
output" — it is "the machine surfaces carry zero decoration, ever". Meaning is always carried by the
words and the exit code, never by a glyph or a color.
A leak is a bug, not a nicety
If you ever see an emoji, a 🎉, or a "your first landing!" line in --json or an MCP response,
that is a compatibility defect to report — not a feature. The same goes for any decoration in
--quiet or non-TTY output.
The success contract¶
Machine output of a successful operation is a JSON object whose fields are stable within a protocol
generation. The CLI's --json and the MCP tool results are two documented shapes that carry the same
facts and follow the same add-only rules, so a parser written against either never meets decoration. A
commit reports its stable change id and commit address on both. tovio commit --json:
The MCP commit tool result:
{
"commit": "blake3:91be07…",
"change_id": "chg:a3f7b2",
"branch": "agent/cgm-sync",
"verified": true
}
Note what is absent: there is no human-only guidance such as undo with \tovio undo``, and no
glyph. The human terminal names that local reversal for a reversible mutation; the contract shows only
the facts.
Success with follow-up¶
Some "successes" carry a follow-up the agent must act on — most notably a sync that reconciles a divergent
lane. These are reported as success, never as an error. When a sync brings a divergent incoming tip on
an unprotected lane, TOVIO joins the two tips into a two-parent commit on the lane — a clean merge, or one
carrying first-class conflict objects — and reports it in a reconciled object, never a "diverged history"
failure.
// tovio sync --json (the isolated-path and pin counts are omitted here)
{
"remote": "origin",
"received": 9,
"sent": 3,
"refs_merged": 2,
"reconciled": {
"lane": "main",
"tip": "blake3:7c2e91…",
"conflicts": 1,
"conflict_paths": ["src/integrations/cgm/dexcom.ts"]
}
}
An agent branches on reconciled (and reconciled.conflicts), not on a human-readable "3 conflicts stored —
your work keeps moving" line. A clean reconciliation reports "conflicts": 0; when the pull found no
divergence, reconciled is null. (That warm line is exactly what the human sees; the agent sees the
object.)
The error envelope¶
Every failing call returns the same structured envelope — the Error Catalog §2.2 shape — so an
agent branches on a stable code without parsing human text. On the CLI, --json wraps it in an
error object:
{
"error": {
"code": "TVO-PERM-001",
"area": "perm",
"category": "authorization",
"exit_class": "permission",
"retryability": "after-user-action",
"title": "Cannot read config/production/api-keys.env",
"cause": "Read requires role=senior AND clearance=secrets; this agent has entity=agent, team=backend; missing clearance=secrets.",
"remediation": [
{ "text": "A human must perform or re-authorize this read", "command": "tovio agent show cap_7r4qy9m2x8k3v6bd0n1p5h" }
],
"context": {
"path": "config/production/api-keys.env",
"policy_id": "prod-secrets",
"policy_requires": "role=senior & clearance=secrets",
"your_attributes": ["entity=agent", "team=backend"],
"missing": ["clearance=secrets"],
"token_id": "cap_7r4qy9m2x8k3v6bd0n1p5h"
},
"exit_code": 13
}
}
Over MCP the same engine envelope is passed through verbatim — code, title, cause, context,
and exit_code, with no error wrapper — as the tool result's text, flagged isError. The
Node SDK carries that identical envelope in a return value instead:
every operation yields { ok: true, ... } or { ok: false, error }, and unwrap() throws the envelope
as a typed error. The remediation list is defined by the catalog and rendered by the CLI; the engine
marshal does not emit it, so an agent branches on code and treats remediation as optional.
| Field | Meaning |
|---|---|
code |
The stable TVO-<AREA>-<NNN> identifier. Branch on this. |
area, category, exit_class, retryability |
The catalog's closed classifications (CLI --json). |
title |
A one-line human-readable summary (still plain — no glyphs). |
cause |
What actually went wrong, in attribute terms the agent can reason about. |
remediation |
An ordered list of { text, command } next steps (CLI --json; optional over MCP). |
context |
Stage-appropriate structured fields (see leak-free ordering below). |
exit_code |
The process exit code the CLI would return. |
The codes an agent will see¶
These are the errors specific to the agent surface. The full catalog covers the rest.
| Code | Meaning | Typical agent reaction |
|---|---|---|
TVO-TOKEN-001 |
Path (or lane) outside scope | Treat the path as not this agent's concern. |
TVO-TOKEN-002 |
Token expired | Request a fresh token and reconnect. |
TVO-TOKEN-003 |
Token revoked | Stop; the session is closed. |
TVO-TOKEN-004 |
Operation not permitted / not exposed | The op is outside the token (or never offered to agents). |
TVO-TOKEN-005 |
Sub-token scope exceeds parent | Delegate within the parent's scope. |
TVO-CRYPTO-002 |
Token signature or delegation chain does not verify | Stop; obtain a validly issued token. |
TVO-PERM-001 |
Read denied by policy / clearance | A human must read or re-authorize. |
TVO-PERM-002 |
Write policy unsatisfied | The push needs a satisfying proof. |
TVO-SYNC-001 |
Relay unreachable (local work safe) | Retry later; nothing was lost. |
TVO-SYNC-002 |
Protocol version mismatch | Negotiate a supported protocol. |
TVO-CONFLICT-001 |
Conflict stored (success-with-follow-up) | Not an error — resolve when ready. |
Leak-free ordering is part of the contract¶
The error context reflects exactly what the enforcement stage was permitted to learn — and no
more. This is a deliberate security property, not an inconsistency:
- A path-scope rejection (
TVO-TOKEN-001) carries the offendingpath(orbranch) but neverpolicy_requires. Policy was never read, so the response cannot disclose it. - An operation-allow-deny rejection (
TVO-TOKEN-004) names the operation inopbut carries no policy fields — it is a pre-policy stage. - A policy denial (
TVO-PERM-001) — reached only when the path was in scope and the op allowed — identifies the policy that was evaluated (policy_id, plussecret_clearanceon the clearance gate), because the policy was read.
The context objects above are what the engine emits over MCP and the SDK. The CLI's --json today sends
an empty context for both TVO-TOKEN-* stages, so read the offending target from the envelope you
get over MCP rather than expecting the CLI to repeat it.
// TVO-TOKEN-001 over MCP — out of scope: policy was never consulted, so no policy_requires
{
"code": "TVO-TOKEN-001",
"title": "Agent token is not scoped to this path",
"cause": "The read targeted `config/production/api-keys.env`, outside the token's path_scope (minus denied_path_scope); it is rejected before any policy or envelope is consulted (§7 step 2).",
"context": { "path": "config/production/api-keys.env" },
"exit_code": 13
}
The asymmetry is the point: an out-of-scope request is told only that it is out of scope. An earlier
stage must never leak what a later stage gates. An agent can rely on this — the presence or absence of
policy_requires tells it which stage denied the call.
Stability guarantees¶
The contract is versioned with the protocol and is a stable compatibility surface:
- Within a protocol generation, fields and codes are add-only — the server may add tools, optional inputs, and output fields, but never remove or repurpose one without a major-version bump.
- Backward compatibility holds for one minor generation back (N-1): a newer server keeps every prior name and field working with unchanged meaning.
- An error
codenever changes meaning without a major-version bump and the deprecation process.
These guarantees mirror across the CLI --json, --quiet, and MCP so the three surfaces stay in
lockstep — see the MCP server.
Where this connects¶
- The server that emits this contract: the MCP server.
- The token whose denials populate these codes: capability tokens.
- Wiring a client to branch on these codes: write an integration.
- The human counterpart (where delight does live): TOVIO's interactive CLI output.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure