# 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 ```json { "runward": "", "mission": null, "verdict": "no-mission", "exitCode": 2 } ``` ### Normal run Add `--strict` to also get the `conformance` array: ```json { "runward": "", "mission": "", "currentGate": "", "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: 1. **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 becomes `detected`. 2. **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 as `config-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: ```json { "runward": "", "mission": "", "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 `--yes` flag sets it) - `CI` is 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](https://runward.dev/docs/en/concepts/evidence/)), `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 the `conformance` array; the deterministic authority. - `runward wire --json` — detection + recommended channel; `wires:false` always; exit 0 always. - On `wire` status `detected`/`config-detected`: offer the recommended sample; on `undetermined`: ask the operator which tool, then wire the matching sample. Act only on explicit approval. - `runward explain --json`, `runward rules --json` — machine-readable rule text. - Compliance vocabulary: `runward compliance ` assembles **audit-ready supporting evidence** (OSCAL) that feeds a programme; acceptance is the auditor's call — never a claim of certification. ## See also - [From an AI agent](https://runward.dev/docs/en/operating/from-an-agent/) - [The boundary: runward never wires itself](https://runward.dev/docs/en/operating/from-an-agent/the-wiring-boundary/) - [Wire the gate](https://runward.dev/docs/en/operating/wire-the-gate/) - [CLI commands](https://runward.dev/docs/en/reference/cli/)