Docs · Operate · From an AI agent · The machine contract
The machine contract: check --json and wire.
Drive runward on data: check's JSON output, read-only wire detection, hardened non-interactivity, and the quick reference for an operating agent.
Step 3 — check --json, the machine contract
For autonomous driving, use runward check --json so the agent branches on data, not on scraped coloured text (ADR-0030). In --json mode every human line is suppressed and the sole stdout output is one deterministic JSON object; the exit code is unchanged and remains the primary signal.
No mission found — exit 2
{ "runward": "<version>", "mission": null, "verdict": "no-mission", "exitCode": 2 }
Normal run
Add --strict to also get the conformance array:
{
"runward": "<version>",
"mission": "<abs path>",
"currentGate": "<phase>",
"adrCount": 0,
"strict": true,
"verdict": "clean", // or "gaps"
"exitCode": 0, // 0 clean, 1 gaps
"gaps": { "deliverables": 0, "conformance": 0, "hooks": 0 },
"deliverables": [ { "phase": "...", "artifact": "...", "relPath": "...", "state": "filled" } ],
"conformance": [ { "scope": "...", "rule": "...", "problem": "..." } ]
}
The conformance key is present only under --strict.
What --strict verifies
--strict verifies the rule-conformance manifests deterministically: rows present, typed pointers resolve to real content, signatures match, no drift, seal intact. It checks the presence and shape of a traced decision, never the quality of the code — that stays the operator's judgment at the gate.
Above a green strict gate, runward surfaces two advisory proofs it never runs or reads:
- the behavioral proof (your test suite)
- the verify workflow (an adversarial cite-vs-apply pass)
Both advisory, neither blocks.
Hooks
If you also pass --hooks, operator-supplied commands from runward/hooks.json run around the audit; under --json their stdout is routed to stderr (fd 2) so it cannot corrupt the single-JSON-object contract. Hooks are opt-in only — runward's own gate never runs them, so a clone cannot execute anything by surprise.
--json coverage today
Note for a driving agent: --json currently ships on check (the load-bearing path), on wire, and on rules and explain. status and doctor do not yet expose --json; ADR-0030 defers their machine output as an additive, non-breaking follow-on. Drive on check --json and its exit code; read status/doctor as text for now.
Step 4 — runward wire: best-effort detection, read-only
runward wire answers one question: which AI harness is running this command, so the agent can propose the matching auto-trigger channel. It is read-only and never prompts, so an agent-driven run never hangs; its exit code is always 0.
Detection has two strengths
Tried in order:
- Runtime signal — an environment marker the harness injects into the child process. Strong: it attests this run. Only verified markers are used:
CLAUDECODE=1(Claude Code / Cowork),GEMINI_CLI=1(Gemini CLI),CURSOR_AGENT=1(Cursor agent mode). Status becomesdetected. - Config file — a tool-profile marker already in the repo (e.g.
.cursor/rules/runward.mdc,GEMINI.md,.kiro/steering/). Weaker: a.cursor/folder does not prove Cursor is running this command, so it is surfaced distinctly asconfig-detected.
When neither resolves
When neither resolves, the status is undetermined — not an error. This is expected for Copilot CLI, Windsurf, Kiro, Continue, Aider, Trae, because no reliable runtime signal exists for them and there is no cross-harness AI_AGENT=1 convention. The correct behavior is to ask the operator, in plain language, which AI tool they use, then wire the matching sample — never guess.
The --json contract
runward wire --json emits the detection as a stable contract. The HarnessDetection shape:
{
"runward": "<version>",
"mission": "<abs path or null>",
"schemaVersion": 1,
"status": "detected", // detected | config-detected | undetermined
"harness": "cursor", // stable id, or null
"label": "Cursor (agent mode)",
"family": "cursor",
"detectedVia": "runtime-signal", // runtime-signal | config-file | null
"signal": "CURSOR_AGENT", // env var name, or null
"recommendedChannel": { "channel": "advisory-hook", "sample": null, "note": "..." },
"candidateChannels": [
{ "channel": "pre-commit", "sample": "runward/adapters/pre-commit" },
{ "channel": "ci-required-check", "sample": "runward/adapters/github-actions.yml" }
],
"wires": false,
"operatorAction": "offer-to-wire-sample" // or "ask-operator-which-harness"
}
wires is always false
wires is always false — the ADR-0012 invariant made machine-checkable. candidateChannels are the universal hard channels present in every mission after init and valid whatever the harness: git pre-commit and a CI required check. Where runward ships a harness-specific sample it points at an inert adapter (e.g. runward/adapters/claude-code-settings.json for the Claude turn-end hook); where it ships none (gemini/cursor/copilot) sample is null with a note pointing at the distribution packaging.
Hardened non-interactivity (so an autonomous run never hangs)
When a run is treated as non-interactive
An autonomous agent must never hang on a prompt it cannot answer. isNonInteractive() returns true when:
RUNWARD_YES=1(the--yesflag sets it)CIis set to a truthy value- either standard stream is not a TTY
The last condition is the fix from ADR-0030: an agent in an allocated pty, or with piped stdin, is treated as non-interactive even without --yes, so init's wizard falls back to defaults instead of blocking on stdin. Every command other than init is already non-interactive; wire never prompts by design.
Deterministic dated output
For deterministic dated output in non-interactive runs (e.g. OSCAL packs, the evidence seal), RUNWARD_NOW overrides "today" but is validated as a real YYYY-MM-DD; a malformed value falls back to today rather than producing schema-invalid output.
Quick reference for an operating agent
runward init --yes— neutral baseline (AGENTS.md+.agents/skills/), no profile privileged.runward check --json— verdict + gate + deliverable states; exit 0 clean / 1 gaps / 2 no mission.runward check --strict --json— adds theconformancearray; the deterministic authority.runward wire --json— detection + recommended channel;wires:falsealways; exit 0 always.- On
wirestatusdetected/config-detected: offer the recommended sample; onundetermined: ask the operator which tool, then wire the matching sample. Act only on explicit approval. runward explain <rule> --json,runward rules --json— machine-readable rule text.- Compliance vocabulary:
runward compliance <regime>assembles audit-ready supporting evidence (OSCAL) that feeds a programme; acceptance is the auditor's call — never a claim of certification.