Machine interface¶
Snodo's first committed machine interface (ADR 022). This is a contract, not a convenience: field names and exit codes are stable, and a breaking change bumps the schema version so a consumer can detect it before parsing.
Conventions¶
--jsonis additive. Human output is unchanged;--jsononly changes what is written to stdout.- Every
--jsoncommand writes a single JSON object to stdout. Errors are written to stderr, never stdout, so stdout is always one parseable document. - Every payload carries a
schemafield of the formsnodo.<command>.v<N>. A consumer must check this field first; a mismatch means the payload shape changed and the consumer should refuse to parse rather than misread it. - Field names are asserted by the test suite. A rename fails the suite rather than a downstream consumer.
Commands¶
snodo status --json¶
Schema: snodo.status.v1
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.status.v1 |
ok |
bool | always true on success |
project_root |
string | absolute project root |
protocol |
object | {id, name} from protocol.yml |
mode |
string | null | active mode id, or null |
active_session |
string | null | active session id, or null |
last_run |
object | null | {session_id, mode, updated_at, outcome} |
snodo mode show --json¶
Schema: snodo.mode.v1
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.mode.v1 |
ok |
bool | always true |
mode |
string | null | active mode id, or null |
name |
string | null | display name, or null |
active_session |
string | null | active session id for this mode |
snodo session show <id> --json¶
Schema: snodo.session.v1
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.session.v1 |
ok |
bool | true on success |
session_id |
string | session id |
mode |
string | session mode |
project_root |
string | project root |
project_id |
string | project identity |
created_at |
string | ISO timestamp |
updated_at |
string | ISO timestamp |
checkpoint |
object | {current_task, decisions, memory_summary} |
snodo task show <id> --json¶
Schema: snodo.task.v1
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.task.v1 |
ok |
bool | true on success |
task_id |
string | task id |
session_id |
string | session holding the record |
mode |
string | session mode |
halt |
object | null | the halt payload, or null |
failure |
object | null | the failure context, or null |
spec |
string | null | the task spec |
For a task that is still running (no halt/failure record yet, a live run
record on disk) the command answers ok: true with status: "running", null
halt/failure, and a watch field pointing at the live surface rather than
a record. This distinguishes a running task from an unknown one, which keeps
returning the No record for task <id> error.
The halt payload carries an attempts summary alongside the final
validator_results: total, coder_dispatches, and a bounded history of
{attempt, outcome} entries (ADR 045). A consumer can therefore tell a
first-time pass from a hard-won one, and count coder dispatches, without
parsing prose. The summary is additive to the payload.
snodo worktree list --json¶
Schema: snodo.worktree.v1
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.worktree.v1 |
ok |
bool | always true |
project_root |
string | project root |
worktrees |
array | [{task_id, path, age_days}] |
snodo validate <task_spec> [--phase pre_execute|post_execute] [--mode <m>]¶
Schema: snodo.validate.v1
Runs the phase's validators through the shared engine runner and returns the
validation-outcome result without running a coder. The shape mirrors the
engine's halt payload. The engine's canonical vocabulary is five (ADR 015);
environment_error is an execution halt, and because this command never
invokes a coder, it is not one of the outcomes returned here.
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.validate.v1 |
ok |
bool | true on success |
status |
string | pass | escalate | blocker | validator_error |
task_id |
string | derived task id |
phase |
string | the phase validated |
mode |
string | the mode validated |
results |
array | [{validator_id, severity, justification}] |
policy_decision |
object | null | the policy decision |
instruction |
string | follow-up instruction |
snodo survey --json¶
Schema: snodo.survey.v1
Reports one repository in one of two shapes, depending on whether a protocol
already exists. The analysis object is the same in both; analysis.drift is
present only for a repository that already has a protocol.
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.survey.v1 |
ok |
bool | true on success |
project_root |
string | absolute project root |
project_id |
string | project identity |
scope |
string | remote | local | override |
display_name |
string | repository directory name |
analysis |
object | discovered modules, languages, tooling, decision records, test command, findings, agent judgements and the judgements not made |
analysis.drift |
object | governed repositories only — what the protocol claims against what the code shows |
analysis.drift:
| Field | Type | Meaning |
|---|---|---|
protocol_id |
string | the protocol surveyed |
uses_modules |
bool | whether the protocol declares modules: at all |
shape |
string | which shape the report is reading, in prose |
summary |
string | one-line account of what was weighed |
checks_made |
int | comparisons that were actually carried out (agreements + divergences) |
has_divergences |
bool | whether any comparison diverged |
agreements |
array | [{check, statement, subject, evidence}] — comparisons that agreed |
divergences |
array | [{check, subject, claim, observation, evidence}] — where the two differ; claim restates the protocol, observation restates the code, and neither names a fault |
not_compared |
array | [{check, reason}] — comparisons this protocol's shape does not support, and why |
check is one of protocol-module-paths, module-coverage,
decision-records, test-commands, validator-tooling. A protocol that
declares no modules (the common shape) gets no module-level comparisons at all:
the three module checks appear in not_compared with their reason, and no
discovered boundary is reported as ungoverned.
snodo survey exits 0 whenever it produced a report, including a report
that found drift: a comparison that diverged is a true observation, not a
judgement that either side is at fault, and survey adjudicates nothing. A
caller distinguishes the outcomes from the payload, not the exit code:
| Situation | Exit | Payload |
|---|---|---|
| ungoverned repository | 0 | analysis without a drift field |
| governed, no divergence | 0 | analysis.drift.has_divergences: false |
| governed, divergence found | 0 | analysis.drift.has_divergences: true |
| no git repository, or a protocol that will not load | 4 | ok: false, error |
Code 4 therefore means "no report was produced" and nothing else. Surveying a
governed repository is not an error state, and 1/2/3 are validation
outcomes that survey does not emit: survey proposes and reports, it does not
judge work.
snodo intake --json¶
Schema: snodo.intake.v1
Reports the validator criteria snodo intake would offer, drawn from the
repository's decision records. It writes nothing and prompts for nothing —
a machine can see the proposals, but acceptance stays a human act; the write
path is entered only by the interactive command or an explicit --accept-all.
| Field | Type | Meaning |
|---|---|---|
schema |
string | snodo.intake.v1 |
ok |
bool | true on success |
project_root |
string | absolute project root |
protocol_id |
string | the protocol the criteria would be added to |
proposals |
array | [{criterion, record_path, record_title, record_section}] — each rule and the record it came from |
written |
bool | always false |
record_path is a repository-relative citation resolved against the
repository before the proposal is built; a criterion whose record cannot be
found is never proposed. A non-zero exit from intake --json means no
proposals could be produced (no git repository, no protocol, or an unloadable
one), and it writes nothing.
Exit codes¶
snodo validate (and any command that returns a validation outcome) uses exit
codes that distinguish the validation outcomes, so a caller can branch without
parsing prose:
| Exit code | Outcome |
|---|---|
| 0 | pass |
| 1 | blocker |
| 2 | escalate |
| 3 | validator_error |
| 4 | internal_error |
No command adds a sixth code. environment_error is an engine execution halt
(ADR 015), not a validation outcome, so it has no code here. snodo survey deliberately uses none of the
judgement codes (1–3): it adjudicates nothing, so a governed repository whose
protocol has diverged from its code exits 0 like any other successful run
and reports the divergence in its payload
(analysis.drift.has_divergences). A non-zero exit from survey means no report
was produced at all — see snodo survey --json above.
Error shape¶
When a --json command cannot produce its normal payload (not inside a
project, missing argument, unknown id), it emits a uniform error object and a
non-zero exit code:
{
"schema": "snodo.<command>.v1",
"ok": false,
"error": "human-readable reason"
}