# 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`.
```json
{"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 string
- `message`: 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:
```text
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.json`
- `spec/fixtures/protocol_streams.json`