Airforge / Field note

When another system reads the output

When software acts on an interface, output formatting becomes system behavior. A field note on explicit results, branchable failure, and executable contracts.

An interface changes when another system begins making decisions from it.

A person can often recover from irregular output. We skim, infer, retry, and quietly repair missing context. If a command prints nothing, we may run another command to see whether anything happened. If an error is vague, we read nearby logs. If two messages use slightly different words for the same state, we usually understand what they meant.

Software does not repair ambiguity in the same way. It needs an outcome it can distinguish, a shape it can depend on, and a failure it can route somewhere useful. Once software acts on the result, formatting is no longer a finishing detail. It is system behavior.

That shift is easy to underestimate. A command-line interface still looks like text in a terminal. But its reader may now be a scheduler, an agent runtime, a script, or another service. The text is carrying control flow.

Readable is not the same as dependable

Human-readable output matters. It should remain concise, legible, and direct. But readability alone says very little about whether another system can use an interface safely.

Consider a command that succeeds without printing anything. To a person, silence may feel pleasantly minimal. To a caller, it leaves several possibilities open:

  • the operation succeeded and found nothing;
  • the operation succeeded but forgot to report its result;
  • the process stopped before producing output;
  • output was redirected or lost;
  • the command is still running somewhere else.

The caller can investigate, but every investigation adds another branch, another request, and another chance to infer incorrectly. Quiet output is not necessarily a quiet system. Sometimes it merely moves uncertainty downstream.

The same problem appears when success and diagnostics share one channel, or when every failure exits with the same status. The shell already provides separate standard output, standard error, and exit status. POSIX defines command exit status as an input to subsequent shell behavior, while long-standing command-line guidance treats diagnostic formatting as its own interface concern. The GNU coding standards describe consistent forms for noninteractive error messages.

Those conventions do not design an agent interface for us. They establish something more basic: results, diagnostics, and process status are different signals. Collapsing them throws useful structure away.

Success, emptiness, and failure need different shapes

Axial’s current command contract assigns one job to each channel. Standard output carries successful results. Standard error carries one diagnostic line on failure. The exit status identifies the class of outcome.

An empty query is still a successful query, so it receives an explicit result:

0 issues for status:blocked priority:urgent

The line is small, but it closes an important branch. The caller knows the command ran, the filters were applied, and the result set is empty. It does not need a second read to distinguish “nothing found” from “nothing reported.”

A mutation echoes the state it created or changed:

created PLAT-1 [todo] p:high @codex #backend: Fix vector search
updated PLAT-14 status: in_progress -> done

That echo is not decorative confirmation. It lets a caller continue from the result of the write without immediately fetching the same record again. It also gives logs and human reviewers a compact account of what the command claims to have done.

Failures use a stable code and a single diagnostic line:

error[NOT_FOUND]: issue PLAT-99 does not exist

The exit status and the error code are intentionally related but not interchangeable. A shell can branch on the numeric status. A higher-level caller can retain the named code in its own activity record. A person can still read the message without decoding a wire format.

The point is not that every command should copy these exact strings. The point is that each observable outcome has a documented shape and one clear owner.

Conflicts are outcomes, not generic errors

Some failures say that the system is unavailable. Others say that the requested action is invalid. A coordination conflict says something different: the world changed, or another actor already holds the right to proceed.

Those cases should not disappear into a generic nonzero exit.

When a second actor tries to claim work that is already held, Axial returns a conflict rather than pretending the write succeeded:

error[CONFLICT]: PLAT-1 already claimed by codex

The corresponding exit status is reserved for conflict. A caller can stop, select different work, wait for a lease, or surface the decision to a person. It does not have to parse prose to decide whether retrying is sensible.

The output contract is only half of this behavior. The claim itself must be atomic. If the implementation first reads the issue and then writes a claim later, two actors can both observe the same unclaimed state. Precise error text cannot repair an imprecise operation.

This is why interface and mechanism belong together. The mechanism establishes one true outcome. The interface makes that outcome available to the next participant.

The specification must be executable

Writing down output helps, but prose drifts when nothing checks it.

A developer adjusts punctuation to improve readability. A library upgrade changes usage text. A new command returns an empty string because the empty case was not discussed. Each change can appear harmless in isolation. Together they turn a supposed contract into a collection of conventions remembered differently by every contributor.

Axial pairs its written command specification with golden tests. The specification states the invocation, standard output, standard error, and exit status for success, empty, validation, missing-record, and conflict paths. The golden files record those observations as executable examples.

Neither artifact is sufficient alone. A snapshot can preserve a mistake without explaining why it matters. Prose can describe a guarantee no build still provides. The useful contract is the agreement between intent and evidence.

That agreement also changes the review question. An output diff is not merely “Does this look better?” It is “Which callers observe a different protocol, and has the specification approved that change?”

Versioning follows from the same idea. A storage schema and an output protocol may evolve for different reasons. Treating them as one version number makes unrelated changes look coupled and hides the boundary consumers actually depend on. Version the observable contract that callers read.

A compact interface audit

Choose one command, endpoint, event, or generated file that another system consumes. Before changing its implementation, write down the answers to these questions:

  1. Who owns each channel? Identify where results, diagnostics, metadata, and progress belong.
  2. What does empty mean? Give successful emptiness an explicit, testable representation.
  3. Which failures require different caller behavior? Separate usage, missing data, conflicts, and unavailable infrastructure when callers can act differently.
  4. Can a write report its result? Avoid forcing an immediate read merely to discover what the mutation did.
  5. Is concurrency resolved by the mechanism? Do not rely on output wording to conceal a race in the operation itself.
  6. What is stable? Name field order, codes, timestamps, truncation, and other details consumers may parse.
  7. How does the contract change? Define its version boundary and what counts as breaking.
  8. What proves it? Pair the written contract with fixtures, golden tests, or another deterministic observation.

This is not a call to freeze every character forever. It is a call to know when characters carry behavior.

When another system reads the output, the interface is already part of the system. Design it with the same care as the state behind it.