Skip to content

The best rule in AXI is not the one it leads with

I build CLIs that agents drive.

Not CLIs that happen to be scriptable — CLIs whose primary caller is a language model running a shell command and reading whatever comes back on stdout. cyber-mux is one of mine.

That turns out to be a genuinely different design problem from building for humans, and AXI — Agent eXperience Interface — is the best attempt I have seen at writing down what the difference is.

It is ten rules. The rest of this post argues with three of them, so here they are first, as the spec states them:

# Principle Summary
1 Token-efficient output Use TOON format for ~40% token savings over JSON
2 Minimal default schemas 3–4 fields per list item, not 10+
3 Content truncation Truncate large text with size hints and a --full escape hatch
4 Pre-computed aggregates Include aggregated counts and statuses that eliminate round trips
5 Definitive empty states Explicit “0 results” rather than ambiguous empty output
6 Structured errors & exit codes Idempotent mutations, structured errors, no interactive prompts, fail loud on unknown flags
7 Ambient context Install opt-in session integrations first, then offer an on-demand skill
8 Content first Running with no arguments shows live data, not help text
9 Contextual disclosure Include next-step suggestions after each output
10 Consistent way to get help Concise per-subcommand reference when agents need it

Table from the AXI README. Each rule has a full specification behind it; the summaries are enough to follow the argument.

I build against all ten. I think they are mostly right.

I also spent a day measuring them, and I came away running my own variant of three: TOON only for collections, command structure disclosed only at the node the agent is standing on, and next-step suggestions pruned by validity rather than relevance.

A departure only makes sense if you know what it departs from, so let me start with why the document is worth building against at all.

AXI’s rule 4 contains the sentence the whole document deserves to be read through:

The most expensive token cost is often not a longer response — it’s a follow-up call.

That is the insight, and it is the right one. An agent that has to run your command twice to learn one thing has paid for a tool invocation, a result, a re-read of that result, and the reasoning to compose the second attempt. A slightly longer first response is nearly free by comparison.

Once you have that sentence, most of the spec writes itself — and AXI writes it well. Include the total count so the agent does not paginate to find out. Say “0 closed tasks found” instead of printing nothing, so it does not re-run with different flags to check whether the command worked. Reject unknown flags loudly, and print the valid ones inline, so the correction takes one turn instead of two. Truncate long bodies but say how much you truncated, so the agent knows whether it is missing anything. Show live state on a bare invocation instead of a usage manual.

Every one of those is a round-trip removed, and I have not found a better list anywhere. They are also the rules that survived contact with my own tooling unchanged, which is the strongest thing I can say about a spec.

Two more things it gets right that are easy to miss.

Rule 7 says ambient context must stay minimal — install a session integration, then offer the details as an on-demand skill rather than loading everything up front. That is the correct instinct, and it is going to matter a lot in the second departure below.

And rule 2 is stricter than it looks. Three to four fields per list item sounds like a style preference until you multiply it by row count and session length. Every field you add to a default list schema is a tax on every list call the agent makes.

So this is not a post about what AXI got wrong. It is a post about the rules that are newer than the others and still moving.

My variant: TOON for collections, plain keys for everything else

Section titled “My variant: TOON for collections, plain keys for everything else”

Rule 1 — the front door — is a format mandate: use TOON on stdout, roughly 40% cheaper than JSON. It is a good rule with a real number behind it. It is also the rule with the most headroom, because the number comes from benchmarks built for a different shape of data than a CLI emits, and nobody has checked what happens when you point it at ours.

The published TOON benchmarks are good work. Thirteen datasets, 244 generated questions, four models, confidence intervals reported, and refreshingly candid about where TOON loses.

They also do not measure anything resembling a CLI. Every benchmark I could find tests a single question against one large blob — a thousand employee records, an e-commerce order tree. A CLI emits fifteen rows. Then a single object. Then a two-line error. Then fifteen rows again, forty times in a session.

So I ran it myself, with the real encoder and the same tokenizer family the official suite uses.

The first question was whether TOON’s advantage survives small payloads, since its tabular header costs a fixed twelve tokens and there are only three rows to amortize it over.

It survives comfortably:

TOON vs N=3 N=15 N=1000
JSON compact −25.8% −35.5% −38.2%
JSON pretty −59.6% −64.1% −65.5%
YAML −40.3% −49.7% −52.4%
TOML −41.0% −51.4% −54.1%

The header is 26% of the output at three rows and 1% at a hundred, and it never once flips a comparison. So the rule is safe at CLI sizes. Good.

Two other columns were more interesting.

TOON vs N=3 N=15 N=1000
Markdown table −16.4% −8.3% −5.5%
Markdown list — - #1 Fix auth — open +39.4% +8.8% +2.4%
CSV +31.4% +21.5% +19.2%

A plain markdown bullet list is cheaper than TOON at every size I tested. Not close at three rows — 39% cheaper. And a markdown table, which keeps the field names, stays within 6–16%.

This does not undercut rule 1. It gives it a better argument.

What TOON buys over a markdown list was never really tokens. It is the tasks[15]{number,title,state,assignee}: header — an explicit row count and field list, which is the one thing the published benchmarks show TOON doing clearly better than everything else: detecting when output has been truncated or a field has gone missing.

Which is precisely the guarantee rule 4 is reaching for with count: 30 of 847 total. The two rules are after the same property, and TOON is the only format that provides it for free on every response.

That is a stronger case than compression, and it is the one I would put in the text. Compression is a nice side effect that happens to be smaller than advertised against the formats nobody thought to compare with.

Single objects: rule 3 says where rule 1 should stop

Section titled “Single objects: rule 3 says where rule 1 should stop”

The nicest thing about a spec with ten specific rules is that the rules can be checked against each other.

Rule 3 says detail views should include a truncated preview of long text, never omit it. Rule 1 says put everything on stdout in TOON. Run both and you get a measurable answer about where the format boundary belongs.

Here is the payload — a task detail view, seven fields, the last one a 188-character body of exactly the kind rule 3 exists for. In TOON, at 85 tokens:

task:
number: 42
title: Fix auth bug
state: open
assignee: alice
checks: 3/3 passed
comments: 7
body: "Users report intermittent 401s after token refresh. Repro: log in, wait 15 minutes, hit any authenticated endpoint. Suspect the refresh token is not being persisted before the retry fires."

And as plain markdown key-value, at 78:

## task
number: 42
title: Fix auth bug
state: open
assignee: alice
checks: 3/3 passed
comments: 7
body: Users report intermittent 401s after token refresh. Repro: log in, wait 15 minutes, hit any authenticated endpoint. Suspect the refresh token is not being persisted before the retry fires.

Those are the same characters apart from one pair of quotes. That pair is most of the seven-token gap.

Across six formats:

format tokens
JSON compact 76
Markdown KV 78
TOML 82
TOON 85
YAML 89
JSON pretty 102

TOON lands fourth of six, 12% above compact JSON.

And this is not TOON underperforming — it is TOON being used outside the niche its own documentation claims. The format’s README is explicit that compact JSON often wins where there is nothing tabular to factor, and a single object is exactly that case.

Look at which field got quoted. Not Fix auth bug, not 3/3 passed — only the body, because the body contains Repro: and TOON quotes any string holding a delimiter. There is no tabular structure here for TOON’s header to factor out, so the format contributes nothing but the escaping. And rule 3 guarantees every detail view in your CLI will carry a body like that one.

Errors make the same point more sharply. A real TOON encoder produces:

error: "--title is required"
help: "tasks create --title \"...\" [--body \"...\"]"

Twenty-four tokens, with backslash-escaping an agent has to see through. The plain unquoted version is twenty.

And here is my favourite thing I found all day: AXI’s own examples already do it the cheaper way. Rules 3, 5 and 6 all print unquoted key: value lines that no TOON encoder would emit.

Whoever wrote those examples had the right instinct. The text simply has not caught up with them yet, which makes this the easiest possible improvement to land.

So that is where I put the boundary. TOON’s tabular form for collections, where it earns 25–38% and hands you the row count. Plain key: value for single objects, errors, and empty states — cheaper, more readable, and already what the document shows.

Ten rules, and two of them together pin down a boundary that neither of them states alone. That is a spec doing its job.

My variant: disclosure scoped to the node, never wider

Section titled “My variant: disclosure scoped to the node, never wider”

The rules AXI has are good. The interesting question is what it has not reached yet.

Command structure is the big one.

Rule 6 mentions grouped nouns in passing — “only the subcommand layer knows which is in play” — while validating flags, which presumes a grouping the document has not specified. Rule 10 requires per-subcommand --help. The scaffolding is there; the rule that would sit on top of it is not written yet.

It is worth writing, because command depth is the one place a CLI forces the round-trip that rule 4 exists to eliminate. An agent that does not already know your tool must spend one invocation per level to find out what exists. mytool --help, then mytool tasks --help, then mytool tasks list --help. Three turns before it has run anything.

The flat answer, and where its best argument fails

Section titled “The flat answer, and where its best argument fails”

The obvious fix is to delete the levels. mytool send-keys instead of mytool send keys. One --help, the whole surface, no tree to walk. It is also what MCP does — a flat list of tools, no nesting, every definition disclosed up front — and MCP is the most widely deployed agent-facing interface there is.

That last clause is the argument everyone reaches for, and it is the one that fails.

MCP is the natural experiment in “flat, disclose everything at once,” and the published result is that it breaks. From Anthropic’s tool search documentation:

A typical multiserver setup (GitHub, Slack, Sentry, Grafana, and Splunk) can consume ~55k tokens in definitions before Claude does any work.

Claude’s ability to pick the right tool degrades once you exceed 30–50 available tools.

Their threshold for “stop disclosing up front” is ten tools. The fix that shipped was deferred loading plus search over the catalogue. Dumping your entire surface into the context on every request is not free, and MCP is where that got found out.

So “be like MCP” is advice to reproduce a failure mode MCP has already retreated from. It also contradicts rule 7, which is right that ambient context must stay minimal. A 170-command --help is ambient context by another name.

The nearest independent measurement I found is ETOM, a five-level benchmark for tool orchestration across MCP namespaces. Accuracy declines with namespace depth and with breadth, and the two compound. That is tool namespaces rather than subcommand trees, so it is an analogy — but it points the same direction, and it is the reason flat-and-huge is not a free win.

MCP’s fix was not nesting. It was flat names plus on-demand loading, with this guidance on naming:

Use consistent namespacing in tool names: prefix by service or resource (for example, github_, slack_) so one search matches the whole group.

Flat names, resource-prefixed, discovered on demand rather than walked.

Which is worth sitting with, because it says the real variable was never flat versus nested. It is how wide a scope each disclosure covers. Nesting only costs turns if there is no way to see a level without walking to it. Flatness only helps if the flat list does not itself become the context problem.

The answer that suggests itself is a manifest: mytool schema, the whole surface in one call, tree walk gone.

That answer is half right, and the half it misses is the one MCP found first.

Tool search solves two problems, not one. It stops definitions loading before the agent has read the request, and it returns roughly five matching tools rather than the catalogue. A manifest only solves the first. Print 170 commands in one call and you have moved the cost, not removed it: the agent is still choosing from 170 options, which is the number Anthropic’s own guidance says selection accuracy does not survive.

It is worth being precise about which cost that is, because the obvious one is the wrong one. Tokens an agent has already read are cached on later turns at about a tenth of the input price, so a manifest pulled on turn three is not expensive to keep re-reading. What it costs is occupancy. It holds context window that nothing reclaims, and it holds the agent’s attention across every option it contains. Selection accuracy is a function of how many things you are choosing between, not of what they cost to re-read, and no cache touches that.

Which suggests the fix is not a cheaper manifest. It is a narrower one.

Measured on a tmux-shaped surface — twelve resources, sixty-six commands, full signatures for every one:

disclosure tokens
the whole surface 1517
one resource, on its own 138
the largest resource (pane) 246
every resource, one by one 1660

A resource slice is eleven times smaller than the surface it belongs to. And the last row is the one that settles it: an agent that walked every resource in the tool, one at a time, would spend nine percent more than it would have spent on a single manifest. Break-even is eleven slices out of the twelve that exist.

So segregation has no losing case. The worst outcome ties; the ordinary outcome — an agent that touches two or three resources in a session — is an order of magnitude cheaper in the only currency that does not cache away.

There is already a place to put a slice, and rule 10 named it. mytool pane --help addresses exactly one resource, using a flag every agent that has used any CLI already knows. Make that output be the schema for that resource — not prose with a schema attached, which is the same facts twice and two things to keep in sync:

$ mytool pane --help
commands[10]{path,summary,args,flags,exit}:
pane list,List panes,,--session --format --limit,0|2
pane view,Show one pane,<id>,--full --fields,0|1|2
pane send keys,Send keys to a pane,<id> <keys>,--literal,0|1|2
...

Which brings the argument back to where it started, from the other side.

Rule 8 says a bare invocation shows live data rather than a usage manual. Apply it at every node, not just the root. mytool pane does not print help for the pane group — it lists your panes.

That is clearly right, and on its own it makes the discovery problem worse. The agent now gets data it did not ask for and still does not know which verbs exist at that level, so it spends the --help call anyway.

Unless the response carries them. Rule 9 already says outputs should suggest next steps. Point that mechanism at the node’s own command list and the extra call disappears:

$ mytool pane
panes[4]{id,title,cmd,active}:
1,editor,nvim,true
2,server,pnpm dev,false
3,tests,vitest --watch,false
4,shell,zsh,false
commands[5]{path,args}:
pane view,<id>
pane send keys,<id> <keys>
pane kill,<id>
pane resize,<id> <cols> <rows>
pane split,<id> --horizontal|--vertical

Measured on a smaller five-verb node than the table above: the command block costs 54 tokens on a 52-token response. The pane --help it saves costs 122 tokens and a round trip — call that 600, and the fold pays for itself unless the agent returns to the same bare node a dozen times in one session. Orienting takes one visit, sometimes three.

Two details decide whether it stays cheap.

Names and required arguments, not signatures. Names alone are 20 tokens but do not tell the agent how to invoke anything, so it buys the help call back. Full signatures are 123 and duplicate --help. Names with required arguments are enough to act on; optional flags stay one call away, at the node that owns them.

Navigational responses only. mytool pane carries the map because the agent is orienting. mytool pane list does not — it asked a specific question and got an answer. Attach the block to the bare node, not to every command in the tool.

Three rules, and the same sentence under all of them: nothing is disclosed at a scope wider than the node the agent is standing on.

Shallow groups. noun verbpane list, pane view. Flat is right below roughly thirty commands, where one --help prints the whole surface for a few hundred tokens and there is nothing to segregate. Past that you need segregation, and a subcommand tree is what segregation is. Two levels is the target and three is the ceiling. Prefer a flag over a level: if a third level only selects among variants of one action, it is a flag, and pane list --dead beats pane dead list. Keep verbs consistent across nouns, so an agent that learned pane list can guess session list and be right.

Live data at every node, with the node’s verbs attached. Rule 8 at each level, rule 9 carrying that level’s own commands, names and required arguments only, on bare nodes only. The walk still happens; it just stops being a walk that returns nothing.

Per-node --help is that node’s schema. Full signatures for one resource — arguments, flags with required ones marked, exit codes — generated from the parser, so it cannot drift from what the CLI dispatches. Line-oriented and name-first, because that is the form both a human and a parser can read.

And no whole-surface command. It is the one thing that would break the rule the other three are made of, and nothing needs it: the tree walk is now productive at every step, so an agent never has to see more of the tool than the part it is working in. If you want a skill file to ship alongside the CLI — rule 7 is right that you might — generate it from the parser at build time. That is an artifact, not a command, and no agent has to call it.

So the rule is not “go flat” and it is not “one call to learn the surface.” The enemy was disclosure wider than the question, whether that arrives as a mandatory tree walk or as a manifest nobody asked for.

My variant: suggestions pruned by validity, not relevance

Section titled “My variant: suggestions pruned by validity, not relevance”

The previous section put the node’s own verbs into the node’s response. This one is about what happens to that block once the response is about a specific object rather than a level.

Rule 9 says to include next-step suggestions after each output. It is a good rule, and it is the one with the least written underneath it.

The suggestion mechanism AXI describes ranks candidates by relevance: after listing tasks, offer the things one commonly does with a task. What it should do is filter them by validity: offer only the commands that will actually succeed against the object just returned.

A closed task’s response should not offer to close it. A pane that is already dead should not come back with pane send keys in its hints.

The difference matters because of what each one saves. An irrelevant-but-valid suggestion costs the agent a moment of ranking. An invalid suggestion costs a failed mutation — an invocation, an error, a re-read, and a recovery plan. That is the expensive kind of round-trip, the one rule 4 is named after, and it is the only place in the spec where a suggestion can actively create one.

Validity is also something the server already knows and the agent does not. You have the task’s state in hand when you render the response. The agent has to infer it from fields you chose to include. Handing over a pruned list is free for you and unguessable for it.

This turned out to be an argument about hypermedia rather than about CLIs, and it got its own post — the short version is that this is the HATEOAS constraint, that it failed for twenty years because no client could improvise against an unfamiliar link, and that an LLM is exactly the client it was waiting for.

Fair warning: unlike the other two, this one is reasoning rather than data.

Adopt all ten rules. If you are staging the work, rule 4 tells you how to order it — biggest round-trip saving first:

  1. Aggregates and counts, definitive empty states, loud unknown-flag errors with the valid flags inline. These are the round-trip eliminations and they are the entire point.
  2. Command structure: shallow groups, live data at every node, and the node’s own verbs attached to it. This is the other forced round-trip, and you pay it on every agent that has not met your tool before.
  3. Truncation with the size disclosed.
  4. Content-first home view, so a bare invocation shows state rather than a manual.
  5. Then the output format.

The format still matters — over forty calls in one session, TOON saved me 1,845 tokens against compact JSON and 7,145 against pretty JSON, which at a rough 600 tokens per avoided round-trip is worth about three and twelve saved turns respectively.

That is real, and larger than I expected. I had assumed the format would be a rounding error next to turn elimination, and against pretty JSON it clearly is not. Rule 1 earns its place.

It is still a 26% saving on a thing you emit, where rule 4 is about not emitting the thing at all — which is why I would reach for rule 4 first, not instead.

Tokens only. Nothing here tests whether an agent reads any of these formats more correctly, and on that question the published evidence is genuinely muddled — TOON’s own suite has it statistically tied with JSON overall, and ranking last on field retrieval, which is the operation an agent performs most on CLI output. A separate eleven-format study found the opposite ordering, with the most redundant formats winning on retrieval.

I also used a GPT tokenizer rather than a Claude one. The ordering should transfer. The absolute numbers will not.

The command-structure argument leans on MCP’s published thresholds rather than on a CLI experiment. The failure mode is documented; that the same numbers land in the same places for subcommands is inference.

The segregation numbers are a token count against a surface I wrote to look like tmux. The eleven-to-one ratio moves with how you shape the tool — fewer, fatter resources narrow it — but it would take a strange surface to make one blob the cheaper shape. And the whole cost model assumes an agent starting cold. A harness with cross-session memory pays discovery once per project rather than once per session, which changes who pays and when, though not the occupancy that a wide disclosure costs once it lands.

The case for folding a node’s verbs into its response is a token count against an assumed price for a round trip. Six hundred is a reasonable figure and it is not a measured one. The fold wins by an order of magnitude at that price, so it would take a very wrong assumption to flip it — but it is an assumption. What I have not tested at all is whether an agent given the block actually uses it instead of reaching for --help out of habit.

And the validity-pruning claim is reasoning, not data. It is testable — count failed mutation attempts with and without it — and I have not done it yet.

None of which changes the headline: AXI is the document I would hand someone building their first agent-facing CLI, and I have not found a better one.

What it gives you that a blog post cannot is something specific enough to test. A spec vague enough to agree with is a spec you cannot measure, and every finding above exists because ten rules were concrete enough to point an encoder and a tokenizer at.

Three things came out of the day that I now build differently: TOON only for collections, disclosure scoped to the node, suggestions pruned by validity.

None of them is a fork. They are what ten concrete rules let me find by measuring, and I am writing them up for upstream. Contributing to a spec that is mostly right seems like a better use of an afternoon than writing a competing one.