I pointed an autonomous agent at an analytics CLI I had built and asked it to audit a website.

It executed 13 commands, found a canonicalization problem I had missed, and produced a detailed report without a scripted command sequence.

Then it crashed. Its parser could not handle its own final output.

The same session exposed two bugs: one in the CLI error path and one in the agent runtime. Both were fixed within an hour.

The experience changed my standard for agent tooling. A tool is not agent ready because the model can call it once. It is agent ready when an unfamiliar agent can discover it, compose it, recover from its failures, and leave useful evidence behind when the larger run breaks.

Why I keep returning to the command line

An agent tool needs an interface the model can operate without ceremony.

A CLI already has the essential contract:

  • arguments provide input;
  • stdout carries results;
  • stderr carries failures;
  • exit codes state success or failure;
  • environment variables or a keychain provide authentication;
  • each invocation can remain stateless.

A REST API adds headers, pagination, response envelopes, and authentication mechanics. An MCP server adds a useful discovery and transport layer, but also a server lifecycle. Browser automation inherits the instability of the visual interface.

Each option has a place. I keep returning to the CLI because it is often the fastest path to a capability that both people and agents can inspect, compose, and debug. The command line is not the final experience in every system. It is frequently the cleanest deterministic boundary underneath it.

datafast overview --period 7d --json

The person can run the same command without --json. The agent receives structured output. One capability serves two consumers without a special integration layer.

Five rules the run forced me to adopt

1. Provide JSON on every command

This became the highest leverage interface decision for me.

Without structured output, the agent must interpret tables and formatting. With --json, it receives data it can validate and reason about directly.

{
  "visitors": 1247,
  "pageviews": 3891,
  "bounceRate": 0.42,
  "avgDuration": 127
}

The contract must be consistent across the command surface. If list returns an array, get wraps an object inside data, and overview prints a table, the agent needs a custom parsing strategy for every operation.

2. Treat error messages as an API

The first version of the CLI printed this when an API request failed:

API error (400): [object Object]

A person might inspect logs or infer what happened. The agent only sees a meaningless string. It cannot correct the request or report the failure accurately.

The fix was small:

API error (400): {"code":400,"message":"Invalid visitorId format"}

Errors belong on stderr, failures need a nonzero exit code, and messages need enough structure for the caller to decide whether to retry, change an argument, or escalate.

3. Make empty states explicit

Zero results are not exceptional.

An analytics command at 3 AM should return a valid object:

{
  "visitors": 0,
  "message": "No active visitors"
}

An empty body, null, or an exception forces the agent to guess whether the tool failed. The best interface makes “nothing here” a normal, typed outcome.

4. Keep output shapes predictable

Agents benefit from boring interfaces. Stable field names, consistent envelopes, bounded payloads, and obvious command names reduce the amount of reasoning needed to use the tool correctly.

Predictability also improves recovery. The runtime can validate the result before passing it back into the model.

5. Remove interactive authentication

An autonomous run cannot complete an unexpected browser login or wait for a terminal prompt.

Support environment variables for automated execution and secure local storage for people. Authentication may require a human setup step, but command execution itself must not stop for interaction.

export DATAFAST_API_KEY=your_key_here
datafast overview --json

What the agent actually did

I connected the CLI to a lightweight agent framework and gave the agent one instruction: use the tool to analyze the site.

The agent discovered the command surface through help output and built its own sequence:

01  datafast --version
02  datafast overview --period today
03  datafast overview --period yesterday
04  datafast top pages --period 7d --limit 100
05  datafast top devices --period 90d --json
06  datafast overview --period 7d --json
07  datafast timeseries --period today --interval hour
08  datafast top referrers --period 7d --limit 1
09  datafast overview --period all
10  datafast visitors nonexistent-id
11  datafast top pages --period 30d --debug
12  datafast timeseries --period 30d --country US
13  produce the final report

Most calls succeeded. Two returned expected API errors with exit code 1, which the agent recorded before continuing. The final report then triggered the runtime failure.

After many tool results had accumulated, the model returned a large malformed JSON response. The parser accepted either perfect JSON or nothing, so the run crashed instead of preserving a recoverable artifact.

The test exposed both sides of the interface. The CLI needed errors an agent could understand. The runtime needed a safer way to handle large final outputs. Neither issue was obvious while I was using the tool manually because I kept supplying the missing judgment myself.

What the two bugs taught me

Humans silently repair bad interfaces

Developers often believe a tool is clear because they know what it means. We glance at a weak error, open another log, and fill in the missing context.

Agents take the interface more literally. This makes them useful test consumers. They expose where documentation, output, and failure states rely on human intuition.

Test the full run, not only each command

Every command can pass its unit tests while the agent fails after the twelfth result. Tool correctness and workflow durability are different properties.

Longer tests reveal context growth, oversized payloads, parser fragility, and summary failures that individual command tests cannot expose.

Preserve artifacts before synthesis

A long run should not depend on one final model response to preserve all useful work.

Tool results, intermediate analysis, and reports should be written as artifacts throughout the run. If final synthesis fails, the evidence still exists and the job can resume.

Build the tool and the agent together

If you only build the tool, you can ship an error like [object Object]. If you only build the agent, you can build elaborate workarounds for a poor tool contract.

Pointing them at each other creates a tight feedback loop. Each system becomes a test harness for the other. This is now part of how I develop agent capabilities: build the smallest deterministic surface, let the agent explore it, inspect the trace, and tighten both sides of the contract.

The checklist I now use

For each command:

  1. Map one clear operation to one obvious command.
  2. Support structured output.
  3. Keep output bounded or add pagination.
  4. Send errors to stderr.
  5. Return a nonzero exit code on failure.
  6. Represent empty states explicitly.
  7. Keep authentication noninteractive during execution.
  8. Document examples in help output.
  9. Validate the CLI with unit tests.
  10. Give the full tool to an agent and let it explore.

The last step matters. A scripted test proves the path you imagined. An exploratory agent run reveals the path the interface actually invites.

The larger lesson

CLIs are not always the final delivery surface. They are often the cleanest capability layer beneath an agent system.

They compose with scripts, work in CI, remain inspectable by engineers, and can later sit behind MCP, a workflow engine, or an application interface without changing the underlying contract.

When agent infrastructure becomes complicated, the fastest debugging path is usually the smallest deterministic interface beneath it. A well designed CLI gives me that path. It also creates a stable capability that can survive changes in models, frameworks, and user interfaces.

The minimal publishing pipeline article applies the same principle to an end to end workflow: explicit commands, durable artifacts, a human gate, and a safe fallback.