AFDATA protocol v1 decision record
Status: accepted.
AFDATA protocol v1 uses one discriminator field, kind, and one payload field
whose name is identical to kind.
{"kind":"result","result":{"rows":12}}
{"kind":"error","error":{"code":"file_not_found","message":"missing input","hint":"check --input"}}
{"kind":"progress","progress":{"percent":50}}
{"kind":"log","log":{"event":"startup"}}
Top-level fields are closed:
kind- the payload field matching
kind - optional
trace
trace, when present, must be a JSON object. Its internal fields are owned by
the emitting tool. Normal recursive AFDATA redaction still applies when the
event is formatted.
result, progress, and log may carry any valid JSON value. AFDATA does not
define business payload structure for these event kinds.
error must be a JSON object with:
code: non-empty stringmessage: non-empty string- optional
hint: string
Tools may add extension fields directly to the error object. There is no
required details wrapper.
Finite structured CLI event streams follow this lifecycle:
(log | progress)* -> exactly one (result | error) -> end
Canonical CLIs do not need --stream or --result-only mode switches. The
default finite execution emits exactly one terminal result or error event.
Diagnostic log or progress events are opt-in, for example through an
explicit --log ... filter or --verbose shorthand. TTY detection,
redirection, and pipe targets must not change this event policy.
Finite commands route by kind: result goes to stdout, while error,
progress, and log go to stderr. Ordered event streams keep every kind on
one selected destination so interleaving is preserved. --output-to selects
between those policies as described in docs/transport-mappings.md.
Routing and event meaning are independent from process status. result means
the command intent was fulfilled and always uses the result channel, even when
a tool-defined condition uses a non-zero exit code. error always uses the
error channel. AFDATA reserves exit 2 for closed-world CLI usage failures but
does not define a global mapping from every result/error code to process exit
status.
Cancellation is represented as an ordinary tool-defined error event when the
tool can observe the cancellation and still write a terminal event. For example,
a tool may use error.code: "cancelled", but AFDATA does not reserve that code.
If the selected event destination or transport is already closed and the
terminal event cannot be written, the outcome is a transport interruption with
unknown business result.
A CLI resolution failure names itself in code, the same way a document
failure does: cli_unknown_argument, cli_unknown_command,
cli_missing_argument_value, cli_invalid_argument_value,
cli_duplicate_argument, cli_unexpected_positional,
cli_unregistered_combination, cli_invalid_utf8. A CLI that is not compiled
from a registry — anything built with build_cli_error — reports the generic
cli_error.
There is one place to look. An earlier draft put the classification in a
separate rule field beside a generic cli_error, alongside command_path
and argument_names; that asked an agent to learn a second branching key for
one family of errors while document_* already spelled its classification into
the code, and it cost a closed enum kept in step across four files and four
mirrored schemas. message identifies the offending argument name or failure
category, hint gives the command to run next, and neither ever quotes a raw
value. Every such error is retryable: false and exits 2.
The machine-readable event schema is:
spec/protocol-v1.schema.json$id:https://agentfirstkit.org/schemas/agent-first-data/protocol-v1.schema.json
The shared fixtures proving constructor, validation, and lifecycle behavior are:
spec/fixtures/protocol.jsonspec/fixtures/protocol_streams.json