# Wire the gate runward's gate is a small deterministic, zero-LLM check that reads your runward/ mission and answers one question with an exit code: is every expected deliverable filled (and, under --strict, every mapped rule accounted for)? By default that check runs only when a human types runward check. "Wiring the gate" means installing a thin sample so the same check also runs at the moment it matters: a commit, a CI run, or the moment an AI agent finishes a turn. The load-bearing rule is that runward hands over these samples but never installs them. It writes nothing into .git/, edits no harness config, registers no CI. You (or an agent acting on your explicit say-so) do the wiring. This page covers every channel, the exact exit-code contract they read, and why the boundary is drawn where it is. ## The port contract: what every channel reads Each channel does the same thing — it runs `runward check` and reacts to the exit code. That exit code is the entire contract: | Exit | Meaning | |------|---------| | `0` | current gate clean: every expected deliverable is filled (and under `--strict`, every CRITICAL/HIGH rule mapped to a [build phase](https://runward.dev/docs/en/concepts/six-phases/) is accounted for) | | `1` | gaps: a deliverable is unfilled, a `--strict` rule-conformance gap remains, or an enabled `--hooks` hook failed | | `2` | no `runward/` mission found here or above | ### Where the codes come from These are set in the check command itself: - exit `2` when no mission is found - exit `1` on any gap - exit `0` when clean The same code appears in the machine-readable `--json` payload as `exitCode`, but the process exit code stays the primary signal. The gate that a CI job reads is the same one a human reads by typing `runward check` — no channel adds gate logic or influences the verdict. ### It is runward's own gate, not arbitrary shell The command inside every adapter is runward's own gate, not arbitrary shell: - `runward check` runs the deliverable audit. - `runward check --strict` additionally enforces the floor rule-conformance manifests. ## The channels you can wire After `runward init`, the inert samples live in `runward/adapters/` (copied there from the templates by init). Each file is text until you install it. The universal channels, valid whatever AI harness produced the code, are declared in code as `pre-commit` and `ci-required-check`. ### Local: git pre-commit hook `runward/adapters/pre-commit` is a POSIX `sh` script that runs `runward check --strict` (preferring an installed `runward`, falling back to `npx --yes runward`) and lets a non-zero exit abort the commit. You install it yourself, two supported ways. Copy it into `.git/hooks/`: ```sh cp runward/adapters/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit ``` Or keep hooks in-tree: ```sh mkdir -p .githooks && cp runward/adapters/pre-commit .githooks/pre-commit chmod +x .githooks/pre-commit git config core.hooksPath .githooks ``` Bypass a single commit with `git commit --no-verify` when you have a reason. ### CI: required status check Two forms ship: - **`runward/adapters/github-actions.yml`** — a job that checks out, sets up Node 20, and runs `npx --yes runward check --strict` on `pull_request` and on `push` to `main`. Copy it into `.github/workflows/`, then make it a required status check on your protected branch so no gap merges. - **`runward/adapters/gitlab-ci.yml`** — the same one line (`npx --yes runward check --strict`) on merge-request and default-branch pipelines. Merge it into `.gitlab-ci.yml` and require the pipeline on your protected branch. #### The published composite action There is also a published composite GitHub Action at the repo root, `action.yml` (ADR-0028), for teams who prefer a versioned marketplace action over a copied job: ```yaml - uses: stranxik/runward@ # pin by commit SHA with: path: . # dir containing the runward/ mission strict: 'true' # verify the rule-conformance manifests version: latest # an npm version or dist-tag ``` Its three inputs are `path`, `strict`, and `version`. Their handling is hardened: - Inputs are passed through the environment and never interpolated into the run script, so a malicious `version`/`path` cannot break out and inject shell (CWE-78). - `version` is additionally validated against an allowlist (`latest`, `next`, or a three-part semver) before it reaches `npx`, exiting `2` on a bad value. The action needs nothing but the repo and Node ≥ 20 — deterministic, zero-LLM, no secrets, no model key. CI is the audit-evidence seam: a dated, versioned record that the gate passed on every merge, feeding a regulated pipeline as audit-ready supporting evidence. ### Agent turn-end hooks (per-harness, non-privileged) Where a harness can run a command when the agent finishes a turn, the same one line — `runward check --strict` — closes the loop so an agent cannot finish with the gate never run. These turn-end hooks surface the verdict in the agent's loop; the hard-blocking enforcement points remain pre-commit and CI. runward ships example samples for harnesses that expose a clean seam; none is privileged over another: - **`runward/adapters/claude-code-settings.json`** — a `Stop`-hook block you merge into `.claude/settings.json` (or `.claude/settings.local.json`); it runs the gate at turn-end and surfaces the verdict. The sample command is `runward check --strict 2>&1 || true`, so it reports the gap into the loop but does not itself fail the turn — advisory, not blocking. - **`runward/adapters/kiro-hooks.json`** — a `Stop`-trigger hook you copy into `.kiro/hooks/`, same one line. - **`runward/adapters/bmad-review-layer.toml`** — adds the gate as one review layer BMAD calls beside its own reviewers; a complement, not a competitor. These are explicitly examples, not the privileged path: any harness that can run a command at turn-end wires the same line, and where a harness offers no such seam the pre-commit and CI channels already gate the code whatever agent produced it. ## `runward wire`: the read-only advisor `runward wire` helps an operator (or an agent) pick the right channel. It detects which AI harness is running the command and prints the matching auto-trigger channel plus the always-available universal channels. It is strictly read-only: it detects and prints, it never wires anything, and its exit code is always `0`. It never prompts, so an agent-driven run never hangs. ### Two signal strengths Detection has two signal strengths: 1. **Runtime signal** — a verified environment marker the harness injects into the child process (e.g. `CLAUDECODE=1`, `GEMINI_CLI=1`, `CURSOR_AGENT=1`), reported as `status: "detected"`. Strong: it attests *this* run. 2. **Config file** — a tool-profile marker already in the repo (e.g. `.cursor/rules/runward.mdc`, `GEMINI.md`), reported as `status: "config-detected"`. Weaker: a folder's presence does not prove that tool is running this command. When neither resolves, the status is `undetermined` — a normal outcome, not an error; the expected case for harnesses with no marker (Copilot CLI, Windsurf, Kiro, Continue, Aider, Trae). The agent then simply asks the operator which tool it is. ### Flags - `-p, --path ` — the project directory containing the `runward/` mission. - `--json` — a stable machine payload: `{ runward, mission, schemaVersion, status, harness, label, family, detectedVia, signal, recommendedChannel, candidateChannels, wires, operatorAction }`. In every JSON payload, `wires` is the literal `false` — the ADR-0012 invariant made machine-checkable, so a caller can assert runward wrote nothing. The `operatorAction` field tells the driving agent what to do next: - `offer-to-wire-sample` when a channel is recommended. - `ask-operator-which-harness` when undetermined. ### What `wire` does and does not do What `wire` outputs: - the detected harness (or "undetermined") - the recommended channel with its sample path (or a note when the channel lives in a distribution packaging rather than a mission adapter — e.g. Gemini's extension, Cursor's advisory hook) - the two universal channels - a "Next" line telling the agent to offer to wire the sample **on the operator's approval** What it never does: write a file, edit `.git/`, install a hook, or prompt. Its own closing line states it: "runward wires nothing — you are the operator's hands (ADR-0012)". ## The ADR-0012 invariant: why runward never auto-wires The design problem was real: a gate that is not called is not a gate. The opening incident was exactly this — an agent finished a turn, the gate was never invoked, nothing surfaced the gap. ### What was rejected The tempting cure was rejected outright: - a runward daemon - a file-watcher - a managed hook runtime - silent installation into `.git/hooks/` Each would turn runward from a frame into a runtime, the one thing the doctrine forbids it to become. Auto-installing the hook or editing `.claude/settings.json` on `init` was rejected on security grounds: surprise execution wired into a repo without the operator's act. ### The decision: gate as port, channels as inert samples So the decision: treat the gate as a **port** and ship the channels as **inert, copy-in samples**. runward emits them; the operator wires them. Nothing executes on `init`, on clone, or on `check`; runward writes no file into `.git/`, edits no harness config, registers no CI. This is the same opt-in posture as the `--hooks` seam (ADR-0008), one step stronger: runward does not even *run* these — it hands them over. ### The 2026-07-12 agentic amendment A 2026-07-12 amendment operationalizes this for agentic workflows: `AGENTS.md` directs the operating agent to proactively **offer** to wire a channel, acting **only on the operator's explicit approval** and never silently. This does not weaken the invariant — runward still installs nothing and writes nothing into `.git/`. The difference from the rejected auto-install is consent: the agent proposes and the operator decides, versus runward acting on its own. "Agent-operable" means an agent can drive runward end-to-end, including performing the wiring on your say-so; it does not mean runward modifies your repo behind your back. ### Adapters are runward-owned templates The adapters are runward-owned templates, treated like `workflows/` and `rules/`: - emitted by `init` - refreshed by `runward update` (which may overwrite them) - verified by `runward doctor` They are never mission state. ## The gate wired on runward itself (a worked example) runward's own repository carries a `runward/` mission and makes the strict gate a required CI check on itself. Its CI runs `node dist/cli.js check --strict` as a "Self-gate — runward gates runward" step, and again inside a network-isolated namespace to prove the gate needs nothing but the repo — any network call fails the job. That is the CI channel in production: dogfooding as a required check, not a claim. ## Quick reference for an operating agent - Detect the channel: `runward wire --json` (read-only, exit 0; read `recommendedChannel`, `candidateChannels`, `operatorAction`). - The gate itself: `runward check` (deliverable audit) or `runward check --strict` (also enforce rule-conformance); exit `0`/`1`/`2`. - Samples to hand the operator (never install yourself): `runward/adapters/pre-commit`, `runward/adapters/github-actions.yml`, `runward/adapters/gitlab-ci.yml`, `runward/adapters/claude-code-settings.json`, `runward/adapters/kiro-hooks.json`, `runward/adapters/bmad-review-layer.toml`. - Refresh/verify samples: `runward update`, `runward doctor`. - Always act only on the operator's explicit approval before copying a sample into their harness. ## Why this choice ADR-0012 deliberately makes runward emit inert channel samples that the operator (or an agent on the operator's explicit approval) installs, rather than having runward auto-wire them. The rejected alternative — a daemon/file-watcher, a runward-managed hook runtime, or silent install into .git/hooks or .claude/settings.json on init — would turn runward from a frame into a runtime and wire surprise execution into a repo without the operator's act, the exact security foot-gun the doctrine forbids. Consent is the whole distinction: the agent proposes, the operator decides. The invariant is even made machine-checkable via the constant wires:false in every wire --json payload. ## What comes next ADR-0012 sets a dated reevaluation trigger: reopen if a harness the community actually uses cannot be wired by a static sample (i.e. wiring it would require runward to run, watch, or install something itself). At that point the team would re-examine whether a thin adapter library — still invoked by the harness, never a daemon — is warranted, rather than expanding runward's runtime surface. ## See also - [The boundary: runward never wires itself](https://runward.dev/docs/en/operating/from-an-agent/the-wiring-boundary/) - [Concepts: the deterministic gate](https://runward.dev/docs/en/concepts/the-gate/) - [From an AI agent](https://runward.dev/docs/en/operating/from-an-agent/) - [Maintain & diagnose](https://runward.dev/docs/en/operating/maintain/)