DEV Community

Cover image for The Niners: How 9 CLIs Turned My AI Agent Into a Contender
Abhijit Karode
Abhijit Karode

Posted on • Originally published at dev.to

The Niners: How 9 CLIs Turned My AI Agent Into a Contender

From the Bay, with love for the red and gold.

I live in the Bay Area, so I'm contractually obligated to believe in two things: fog as a lifestyle, and the Niners. This season, my football team is nine command-line tools — and they've changed what my AI agent can do more than any model upgrade this year.

This is the story of how a roster of nine typed CLIs turned Neo — the AI teammate for solutions architects I wrote about in One Year of Building on Kiro — from a promising rookie into a playoff contender.

In that retrospective, lesson #5 was one line: "prefer typed CLIs for compound work; keep tool calls for conversation." This post is that one line, taken to the playoffs.

The problem: my agent was running a slow offense

Neo started life the way most agents do — wired to a stack of MCP servers. It worked, but every play was a huddle: load a large tool surface into context, call a fixed-shape tool, think, and call again. In the final benchmark, the three MCP servers attached to the router contributed 86,221 tokens of schemas before a turn did any work. Answering "what's going on with this customer?" took five MCP calls; the equivalent compound CLI used one.

The industry has spent the last year converging on the fix from a few directions — agents that write code to orchestrate tools instead of calling them one at a time, keeping intermediate results out of the context window entirely. Anthropic wrote about it. Cloudflare built a mode for it. The "CLI vs MCP" debate filled my feed all spring.

Here's the thing: the terminal already had this figured out in 1978. Small programs. Text streams. Exit codes. Pipes.

So I drafted a team.

Credit where it belongs: the nine-tool roster is my implementation, but its shared CLI contract was inspired by the Printing Press CLIs. Their machine-first conventions — self-describing command catalogs, a consistent agent mode, runtime intent resolution, typed exit behavior, and out-of-band delivery — became the pattern I adapted across Neo's toolkit.

What the benchmark actually measured

I ran both paths against the same live production backends: real MCP stdio JSON-RPC on one side, direct CLI subprocesses on the other, five repetitions per arm, with tokens counted using cl100k_base rather than a bytes-to-tokens estimate.

The result was more useful than "CLI good, MCP bad":

  • Resident context was the decisive difference. The router's three MCP servers exposed 148 tools and 86,221 tokens of schemas — 43.1% of a 200k context window — on every turn. CLI catalogs were loaded only when needed.
  • Ordinary call latency was a tie. Identity, inbox, and email search differed by roughly 10-70ms. Network time dominated; the CLI repaid a ~60ms process-spawn cost on every call.
  • Raw CLI payloads could be worse. Unprojected email results were 2.4-7.2x larger because the CLI returned the upstream envelope. But --select cut a CRM response by 64.5%, and the compound account fingerprint used 689 tokens versus 5,367 across five MCP calls.
  • Conversation length changed the economics. In a modeled ten-turn session with three calls total, the CLI path used 37.7x fewer tokens. That is an uncached upper bound: prompt caching should narrow the gap, and I could not measure the cache from this environment.

So the claim is not that every CLI call is faster or smaller. The win comes from on-demand discovery, source projection, compound commands, and keeping intermediate data out of the model's context.

The roster

Reimagined nine-CLI depth chart: field-ops as offensive coordinator, memory at quarterback, tasks at center, and each tool mapped to its operational role

Nine CLIs, each covering one domain my agent works in daily. Position by position:

  • QB1 — the memory CLI. Local SQLite brain. Save, search (keyword/semantic/hybrid), edit in place, and — the new trick — a work graph: typed edges between memories, so "show me everything connected to this customer" is one graph traversal, not twelve searches. Also runs the scheduler: any recurring prompt becomes a cron job with one command.
  • RB — the CRM CLI. Compound reads that replace five API round trips with one command (fingerprint = account + team + open deals + spend, joined server-side).
  • WR1 — the Slack CLI. 189 commands. Search, history, posting, reactions, file uploads.
  • WR2 — the email + calendar CLI. Inbox, search with a full query DSL, full message bodies by ID, calendar views, meeting create/respond — and a guardrail that refuses to send external email without an explicit human yes.
  • TE — the docs CLI. Full-text search across the document library (SharePoint), drive walks, downloads.
  • OL — the tasks CLI. Asana, 57 commands, including in-place updates (no more delete-and-recreate as an "edit").
  • DB — the sales-content CLI. Read-only search + LLM instant answers over the approved content library.
  • LB — the expert CLI. Deep, citation-backed answers from a curated knowledge vault. Slow (minutes, not seconds) but authoritative — the run-stopper you send in when the answer has to be right.
  • Special teams — the field-ops orchestrator. Compound plays that call the other CLIs: one command for the morning briefing, one for meeting prep, one for public-signal research on a company (SEC filings, GitHub footprint, Wikipedia, Hacker News).

397 commands total. Zero of them loaded into the model's context until needed.

What makes a CLI agent-ready

Handing an agent bash and hoping is not a strategy. Every CLI on this roster follows the same contract, and this is the actual secret sauce:

1. One flag for agent mode. --agent = JSON output, no interactive prompts, no colors, and auto-confirm. Compact output is part of the intended contract, but the benchmark caught a real defect: --compact was a no-op on several email and CRM paths. Today, --select and compound commands are the reliable shaping primitives.

2. Self-description. Every CLI ships agent-context --json — a machine-readable catalog of every command, flag, and whether it's read-only. The agent never guesses a flag shape; it asks the binary.

3. Runtime tool search. Most ship which "<natural language intent>" — a built-in resolver that maps "find stale tasks" to the right command, with a typed exit code (2 = no confident match, a machine-readable "I don't know"). This is the "tool search" pattern everyone's excited about, implemented per binary.

4. Out-of-band results. --deliver file:/tmp/x.json routes output to disk atomically. The agent chains CLI → jq → CLI without any of the intermediate data transiting the model. --select id,name,status projects fields at the source. This is the token-efficiency play: the model sees the compiled answer, not the raw feed.

5. Retry-safe semantics. --dry-run, --idempotent, --ignore-missing. Agent loops retry things; the tools shrug instead of double-creating.

6. Offline mode. sync mirrors everything to local SQLite; --data-source local runs against the mirror. Demos don't die when SSO expires at 2 PM.

The playbook: composite prompts

Play diagram: one prompt fans out through nine CLI routes and converges into one compiled dossier entering the context window

Where it gets fun is when one prompt fans out across the whole roster. My favorites from the actual playbook:

The West Coast Offense (all 9, one prompt):

"Prep me for the customer QBR — pull the CRM fingerprint, my email threads and calendar history with them, what the team's saying in Slack, the docs library, open tasks, approved messaging for their use case, ask the expert agent about their architecture, add public-signal research, then compile one dossier and link it into my work graph."

Nine tools, parallel fan-out, intermediate JSON on disk, one compiled brief in context. The fast legs finish in seconds; the expert leg can take several minutes, so the dossier fills in incrementally instead of pretending every route has the same clock.

The two-minute drill:

"Clear my plate for travel week — find calendar conflicts with my trip, push out task due dates that land while I'm out, draft the meeting declines for my review, save the decisions to memory."

The film room:

"Why did you claim X in this morning's briefing?" — the memory CLI's bisect traces any compiled claim back to its source memories, and the work graph shows what's connected. Provenance as a first-class query.

The season-long stat sheet:

"Draft my quarterly review from what I actually did" — completed tasks, closed deals, posts, saved outcomes, all pulled from the namespaces they auto-landed in during the season.

The box score

Box score: tool calls per turn from 15 down to 1-3, context tokens down 10x, morning briefing from 45 minutes to 30 seconds, works offline

The measured box score:

  • The router's resident MCP schemas: 86,221 tokens per turn; the CLI path loads catalogs on demand
  • Basic identity, inbox, and search latency: effectively a tie
  • Account fingerprint: 1 call, 689 tokens, ~0.78s via CLI versus 5 calls, 5,367 tokens, 1.667s via fixed-shape MCP tools
  • Field projection: 282 → 100 tokens with --select, a 64.5% reduction
  • Ten-turn session model: 37.7x lower token spend for CLI as an uncached upper bound
  • The agent still works on a plane, recurring workflows still become cron jobs, and failures are still shell commands I can reproduce myself

That last one matters more than the benchmarks. CLIs are inspectable. The agent and I debug the same way.

Build your own niners

You don't need nine. You need the contract:

  1. Pick the 3-4 systems your agent touches daily
  2. Wrap each in a CLI that speaks JSON, never prompts, and describes itself
  3. Add an intent resolver so the agent can search commands at runtime
  4. Route intermediate results through files, not the context window
  5. Ship compound commands for your most common multi-call sequences

The models keep getting better. But the biggest capability jump my agent ever got wasn't a model release — it was a good roster, a shared contract, and a playbook.

Go Niners. 🏈


Neo is a router agent with domain sub-agents, a local-first memory, and now, a nine-CLI toolkit. For the season-one recap — how it grew from one Kiro CLI agent to a fleet — read One Year of Building on Kiro. Questions and roster suggestions welcome in the comments.

Top comments (0)