Facts as JSON
Every command has one machine shape: one JSON value on stdout, diagnostics on stderr, stable error codes. The same command answers a person and an agent; only the reader changes.
The rules
- With plain
--json, stdout holds exactly one JSON value. Diagnostics go to stderr and never mix into it. - Streaming commands use NDJSON: one value per line.
- Exit codes are 0 for success, 1 for a run failure, 2 for an argument or option error, and 130 when interrupted by Ctrl-C.
- An error is
{"error":{"code":"…","message":"…","hint":"…"}}. The hint points at evidence that really exists. - No wire field is accepted without being read. A published field is a promise every client may rely on, so the protocol build fails on a field no handler acts on.
The stable codes
| Group | Codes | What they mean |
|---|---|---|
| Input | invalid_params, not_found, invalid_config, ambiguous | The request or the declaration is wrong; the message names the object. |
| Permission | permission_denied | An operation was refused; the hint says what to change. |
| Time | timeout, daemon_busy | The engine gave up, or another action holds the daemon; read the hint, then retry. |
| Observation | observation_empty, observation_unrecognized, observation_incomplete, observation_blind, observation_bind_denied | None means “nothing is listening”; check observation.state and doctor --only listener_observation. |
| Internal | internal | An unexpected failure; report the command and the version, not just the message. |
A worked read
A partial success is still one value. This up --json started one service and failed the other; the exit code is 1, and both outcomes are in the same document.
{"services":[
{"service":"api","state":"started","ready":false,"pid":1234,"port":3000},
{"service":"worker","state":"failed","reason":"start_failed",
"error":"exec: \"nosuchcmd\": executable file not found in $PATH",
"hint":"check the command and its working directory"}
],"started":["api"],"skipped":[],"errors":["worker"]}
statestarted,skippedorfailed: the result of the start action, not a readiness claim.reason- A stable, branchable failure reason such as
start_failed,port_occupiedorready_timeout. hint- A deterministic next step that points at evidence that exists: the service's logs only if the process really came up, the dependency's logs when a dependency was not ready, nothing when there is no executable step.
port_holder- Present on
port_occupied: the port, pid, command and kind of the holder, plus the managed group, service name and run id when it is one of ours. ready- The CLI's annotation, not a daemon fact:
trueafter--waitconfirms readiness,falsewhen no wait was requested.falseis not a failure.