runward

RW™ · V0.22.0

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:

  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:

{
  "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 --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), 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 <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.
← Docs