Engineering research / agents

Making an Existing CLI Work for Coding Agents: Detection, Compact Output, Explicit Truncation, JSON Errors and Cost Warnings

What changes when coding agents become the main users of a command-line tool, using the DuckDB 2.0 agent mode as a worked case: how the tool detects an agent, why a truncated result must say so, errors an agent can parse, warnings before expensive work, and how to measure whether any of it helps.

By Sandeep Belgavi · Published 2026-10-25 · Prepared 2026-10-22

Agent and AI engineering · Practical technical guide · 22 October 2026

What changes when coding agents become the main users of a command-line tool, using the DuckDB 2.0 agent mode as a worked case: how the tool detects an agent, why a truncated result must say so, errors an agent can parse, warnings before expensive work, and how to measure whether any of it helps.

The new reader of your command-line tool

Command-line tools were written for a person at a terminal. Coding agents now run many of the same commands: they call a tool once per shell invocation, capture standard output through a pipe and hand the text to a model. The DuckDB team describes this reader precisely in its post on the agent mode that ships with DuckDB 2.0: it has no terminal and no pager, it cannot ask for more, it pays for every token it reads including padding, and it handles errors by matching text.

Nothing about the tool’s logic has to change for this reader. What changes is the presentation contract: how results, truncation, errors and progress are printed. That is the part of an existing tool that was designed for eyes and now has to be designed for a parser that also reasons. This guide uses DuckDB as the worked case because its design and its own measurements are published, and because the behaviour can be checked by running the binary.

Detecting the agent, and letting people override it

There is no standard signal that a process was started by an agent. DuckDB checks a list of environment variables that agents set, among them AI_AGENT, AGENT, CODEX_CI, CODEX_SANDBOX, CURSOR_AGENT, GEMINI_CLI and COPILOT_AGENT, and it switches modes only when three things hold: such a variable is set, standard output is not a terminal, and the user did not ask for a specific format with a flag such as -csv. Two flags force the decision either way, and an environment variable does the same where the command line cannot be changed.

Three properties of that design are worth copying. Detection never overrides an explicit request. When detection switches the mode on, the tool says so once, on standard error, with the reason and the way out. In the shell used for this guide, started by Claude Code 2.1.307, the CLI printed duckdb agent mode on: AI_AGENT is set and stdout is not a terminal; -no-agent turns it off before the first result. And a tool that cannot detect an agent can still help: DuckDB prints a one-line hint on standard error the first time a piped run fails.

Truncation must be explicit and resumable

The most dangerous property of a human-oriented renderer is that a truncated result looks like a complete one. DuckDB’s box renderer shows the first and last rows of a large result with a row of dots and a footer such as 200 rows (40 shown). A person notices. A model may skim past the footer and reason about rows it never saw. Agent mode prints results of up to 1,000 rows and 10,000 bytes in full; beyond that it prints the first and last 20 rows around a marker row that states how many rows were omitted, and a footer that names the setting that prints everything and a hash of the whole result. A result that keeps growing is stopped after a cap and reported as a lower bound.

The measurements below were taken on 2026-10-22 with the DuckDB 2.0.0 CLI on Windows, for a four-column query over range(n). They are bytes on standard output, not tokens; the box renderer’s border characters take three bytes each in UTF-8, which overstates its cost in bytes more than in tokens.

Rows in the resultBox renderer, pipedAgent modeWhat the agent mode printed
3710 bytes180 bytesAll rows
402,954 bytes1,451 bytesAll rows and a footer with the count and a hash
2003,198 bytes (40 rows shown)7,219 bytesAll 200 rows, because the result is under 1,000 rows and 10,000 bytes
5,0003,253 bytes (40 rows shown)1,650 bytesFirst and last 20 rows, an omission marker, the count and a hash

The 200-row line is the instructive one. Agent mode is not always smaller: it spends more bytes to avoid presenting part of a result as the whole. That is the right trade for a reader who cannot scroll, and it is why the useful metric is not output size alone.

Errors an agent can act on

An agent that receives Binder Error: Referenced column foo not found has to pattern-match prose. In agent mode DuckDB writes errors to standard error as one JSON object with a type, a subtype, the position and the offending name. The run for this guide printed {"exception_type":"Binder", ... "error_subtype":"COLUMN_NOT_FOUND","name":"foo","location":"[7,3]"}. A stable subtype is what lets an agent, or the runtime around it, distinguish a typo it can fix from a permission error it cannot. DuckDB also dropped a field that repeated information already in the message, because one verbose error weighed 2.7 kB.

Warn before expensive work

A person watches a progress bar and presses Ctrl-C. An agent sees nothing until the command returns. DuckDB’s agent mode prints the planner’s estimate on standard error before any statement that it expects to read a million rows or more, and replaces the progress bar with a plain progress line every five seconds. The agent can interrupt and rewrite the query instead of waiting. The general rule for any tool: if the tool can estimate cost before starting, say it on standard error, in one line, only when it is large.

A presentation layer you can add to your own tool

The pattern fits any tool that prints tables. Keep the data path unchanged and choose the renderer at the edge:

Illustrative python, checked for syntax; adapt names and storage to your system.

import json, os, sys

AGENT_VARS = ("AI_AGENT", "AGENT", "CODEX_CI", "CURSOR_AGENT", "GEMINI_CLI")


def agent_mode(explicit_format: str | None, force: bool | None) -> bool:
    if force is not None:                 # -agent / -no-agent always win
        return force
    if explicit_format:                   # a format flag is a deliberate choice
        return False
    return any(os.environ.get(v) for v in AGENT_VARS) and not sys.stdout.isatty()


def render_rows(cols, rows, max_rows=1000, keep=20):
    yield "| " + " | ".join(cols) + " |"
    yield "|" + "---|" * len(cols)
    shown = rows if len(rows) <= max_rows else rows[:keep] + [None] + rows[-keep:]
    for r in shown:
        yield (f"| ... {len(rows) - 2 * keep} rows omitted ... |" if r is None
               else "| " + " | ".join(map(str, r)) + " |")
    yield f"{len(rows)} rows" + ("" if len(rows) <= max_rows else ", first and last 20 shown")


def render_error(kind: str, code: str, message: str) -> None:
    print(json.dumps({"type": kind, "code": code, "message": message}), file=sys.stderr)

Keep the agent behaviour a set of defaults that explicit settings override, document it behind one help topic, and print the one-line notice on standard error so standard output stays the result.

Measure the right thing

DuckDB measured its own change, and its numbers are worth reading as the vendor’s results, not ours. Claude Code answered the 22 TPC-H questions three times with and three times without agent mode; all 132 runs were correct, and the DuckDB output the model read fell from 123.6k to 50.8k tokens, 59 percent less. The same report says the savings were about 0.5 percent of total input, because each turn re-reads a system prompt much larger than the query output, and that agent mode runs took slightly more turns, 237 against 224. The lesson for your own tool is to measure correctness, turns and total cost per task, not only bytes per call: compact output matters most where outputs are large or where truncation would otherwise mislead.

Failure modes

  • Truncation without a marker, so an agent treats a sample as the full result.
  • Detection that overrides an explicit format flag, breaking scripts that parse CSV.
  • A notice printed on standard output, which corrupts the result an agent parses.
  • Error text reworded in a release, breaking agents that matched the old wording instead of a stable code.
  • A verbose explanation printed on every run; DuckDB’s first version printed 634 bytes each time, more than most results.

Verification

  • Run your tool through a pipe with and without each detection variable, and with explicit format flags, and compare the output.
  • Feed a recorded agent session the old and new output and check that it reaches the same answer in no more turns.
  • Test that every truncated output contains the omission marker and the total count.
  • Assert that error codes, not messages, are stable across releases.

Sources and scope

Checked against these sources on 22 October 2026. Examples and figures are illustrative, not measurements.

Detect agents conservatively and never override explicit formats, mark every truncation with counts and a way to get the rest, emit errors with stable codes, warn before expensive work, and measure correctness and turns, not only bytes.