Reference
Machine contract (--json)
The single, versioned JSON envelope every core command emits on stdout with --json. Additive-only: breaking changes bump schema_version.
Every core command accepts --json and emits a single, versioned JSON envelope on stdout. This is the contract coding agents and scripts consume; it is additive-only, so breaking changes bump schema_version. The matrix (matrix list/show/status) and webhook (webhook status/history) JSON outputs predate this contract and keep their own schemas for backward compatibility.
Envelope
{ "schema_version": "1", "operation_id": "op_1a2b3c4d5e6f7081", "status": "succeeded", "result": {}, "error": null}| Field | Presence |
|---|---|
| schema_version | Always |
| operation_id | Mutation commands only - the durable operation identifier; read-only commands never fabricate one |
| status | "succeeded" on success, "failed" on failure |
| result | Success only; shape documented per command |
| error | Failure only |
stdout / stderr discipline
stdout carries the envelope and nothing else - no banners, progress, colors, warnings, or debug output. stderr carries progress and diagnostics (what a non-JSON run prints to stdout). Exit codes are unchanged by --json. The MCP adapter reuses this exact envelope: each tool returns one envelope as its result content, keeping the process’s real stdout reserved for the MCP protocol.
Commands
| Command | operation_id | result |
|---|---|---|
| build | op_… (matrix: the mx_… run ID) | {app, app_id, version, path} / matrix run summary |
| rebuild | op_… (correlated with the dep-… deployment ID) | {app, app_id, version, port, strategy} |
| rollback | op_… | {app, from_version, to_version, strategy, port, verification} |
| status / list / doctor | - | app view / fleet view / {checks[], summary} (doctor exits 3 on failing checks) |
| operation status / list | the queried id | operation view / {operations[], count} |
| inspect / context | - | targeted inspection / one bounded composite snapshot |
| plan create | - (the plan is the artifact) | immutable execution plan |
| plan apply <plan-id> | the produced op_… | execution result + correlation |
| authz status / check / approve / revoke | - | posture / decision / approval artifact / {plan_id, revoked} |
| session create / show / list / checkpoint / … | - (the session is the artifact) | the session record (+ resolved on show) |
Plans
Plans are first-class, immutable, content-addressed execution artifacts. The invariant: the thing that was planned is the thing that gets executed, or execution fails closed. A plan_id (pln_ + 16 hex) names the artifact; a plan_hash (sha256: over a canonical preimage of the execution-relevant fields) names what it will do. Every load recomputes and verifies the hash - a mismatch fails closed with PLAN_HASH_MISMATCH.
Only created → applied | failed are stored; staleness is never stored - it is computed from current state at show/apply time against typed preconditions (app exists, current version, version exists, deploy mode, config fingerprint, source commit, toolchain available). Plan files are create-only; there is no edit - a different plan means a new plan. Plans never contain secrets: the schema has no environment-value fields, and the only free-text field (a rollback reason) is validated and redacted.
source_commit is a precondition only when the source is a git work tree - the config fingerprint covers configuration, not source content. A plan built from a non-git source has no source-content precondition, so its applicability carries source_drift_unprotected: true (and plan show prints a “not drift-protected” line): the source tree may change between create and apply without the plan going stale. Git-backed plans never carry it.
Applying a plan is idempotent and fail-safe. Concurrent applies serialize - a cross-process begin/complete file lock rejects a duplicate started while the first is in flight with UNAVAILABLE. After a failure the plan is marked failed and re-applying it is refused with PLAN_STALE (“plan already ran; create a new plan to retry”) - a gate driven by the plan’s durable status, so a failed plan can never silently re-execute later, independent of ledger eviction. The first apply still surfaces the real execution error (BUILD_FAILED, DEPLOY_FAILED, …).
Execution authorization
Plan application passes one gate between a validated plan and the engine: load + hash-verify → preconditions / capabilities → re-resolve + verify equivalence → AUTHORIZE → request-key idempotency → execute. Authorization is the last gate before mutation (so a refusal is provably zero-mutation) and runs before the request-key ledger (so a refusal consumes no idempotency key - the same plan applies once approved). It binds to plan_id and plan_hash, and selected by a host-owned authz/policy.json: absent means legacy_local, present and enforced means plan-bound execution only. Errors use exit 11 (AUTHZ_DENIED, AUTHZ_UNAVAILABLE, AUTHZ_INVALID, APPROVAL_REQUIRED, APPROVAL_STALE, APPROVAL_INVALID). The full model is in Execution authorization.
Agent sessions
A session (ses_ + 16 hex) is a durable tracking record that groups one unit of agent work and references the plans, operations, and deployments it touched. Session commands are not operations - their success envelope is read-shaped and carries no operation_id, and the session’s own lifecycle status lives in result.status (active → completed | failed | cancelled). Session-specific codes: SESSION_INVALID, SESSION_CORRUPT, SESSION_INVALID_TRANSITION (exit 3), and SESSION_CONFLICT (exit 1, retryable). See Agent execution sessions.
Errors
{ "schema_version": "1", "operation_id": "op_1a2b3c4d5e6f7081", "status": "failed", "error": { "code": "DEPLOY_FAILED", "message": "…redacted…", "exit_code": 21, "retryable": true, "operation_id": "op_1a2b3c4d5e6f7081" }}code is the stable structured category; message is redacted; exit_code is the exit code the process returns; retryable is omitted when the failure cannot be classified. Usage errors (unknown flags, missing args) occur before a command runs and are rendered human-readable on stderr with exit 2 - they never produce JSON.
Request-key idempotency
rebuild and rollback accept --request-key <key>. First use records the key durably with a fingerprint of the operation-defining inputs and executes; the same key with the same inputs replays the stored terminal envelope byte-for-byte without executing again (across process restarts); the same key with different inputs is rejected with IDEMPOTENCY_CONFLICT (exit 1). An interrupted key is closed as indeterminate - the mutation is never re-executed under an unknown outcome. The ledger is fail-closed: a corrupt ledger answers UNAVAILABLE for every keyed operation rather than executing without idempotency. build deliberately does not accept a request key - a build is naturally repeatable and version-minting.