Autonomous spec-driven development for Claude Code, layered on OpenSpec.
One human checkpoint. Spec a change, read it, then ship it start-to-commit with parallel agents.
/plugin marketplace add renzrollon/interlock
/plugin install interlock@interlockThen, in a repo, onboard once:
/interlock:bootstrap # code → understanding → graphEvery change after that:
/interlock:spec "<idea>" # explore → artifacts → review, then stops
/interlock:ship # waves → verify → commit
/interlock:ship --strict # previous default: + review, handoff, conformance| Requirement | Why |
|---|---|
| Claude Code v2.1.154+ | /interlock:ship launches a dynamic workflow. Known-good on 2.1.229 |
| Dynamic workflows enabled | Off via disableWorkflows, org policy, or CLAUDE_CODE_DISABLE_WORKFLOWS means no ship. On Pro, enable it in /config |
CLAUDE_CODE_SUBAGENT_MODEL unset |
If it is set it overrides every per-tier model the planner assigns, so ship runs entirely on that model. It banners this rather than hiding it — see when it stops |
openspec CLI |
Interlock drives it; it does not replace it |
| Node.js ≥ 18 | For the three bundled CLIs |
Interlock drives the openspec CLI, so install it and initialize it in the repo first (OpenSpec quick start):
npm install -g @fission-ai/openspec@latestcd your-project && openspec initOpenSpec itself requires Node.js 20.19.0+ (higher than Interlock's own ≥ 18) and also installs via pnpm, yarn, bun or nix. openspec init creates openspec/ and installs its stock skills — Interlock composes with those rather than replacing them.
Interlock is a Claude Code plugin. It relies on Claude Code's skill frontmatter, plugin bin/ PATH injection, subagent fan-out, and the workflow runtime — Cursor and Copilot are not supported in 0.x. Claude Code is the default and supported host, and /interlock:ship launches the workflow there or halts; it never falls back to anything else. There is one experimental second host — an ACP driver you invoke yourself, described under Experimental — and no slash command starts it for you.
Before a long ship run, allowlist the commands its agents use (interlock, interlock-graph, openspec, git, and your test runner). Workflow agents inherit your permission settings, so a command that is not allowlisted stops the run on an approval prompt — which is exactly what a zero-touch run should never do.
interlock doctorThat is the preflight: it checks the allowlist against the commands the flow actually shells out to (including the one your own .claude/testing/profile.json names), plus the Node version, the installed plugin's workflow and agent types, the OpenSpec CLI, git, and whether the run-state directories can be written at all. It exits 1 when a check would stop an unattended run, prints the settings snippet that fixes it, and changes nothing itself. Every condition it names is one you would otherwise meet three waves in. The plugin also runs this preflight automatically at session start (a SessionStart hook) so a missing allowlist entry surfaces before a run rather than three waves in.
The plugin ships three PreToolUse guards as well — deterministic deny rules that stop an in-run agent from editing a test during remediation, hand-ticking tasks.md, or committing outside the commit stage. They bind only agents running inside an Interlock ship run and are inert outside one: they fail open whenever no active run marker is present, so installing the plugin does not change how your own editing or committing behaves. See 13 — The guards.
New here? Start with the first hour. If you have only ever prompted a coding agent — no skills, no specs, no gates — read 09 — From prompt to workflow first: every term defined once, ending at why ship is a script and not a prompt. Then 10 for this repo's loop reviewed in depth.
| Doc | |
|---|---|
| 01 — The first hour | Install to first shipped change |
| 02 — The checkpoint | How to read a spec in ten minutes |
| 03 — OpenSpec vs Interlock | What composes with what |
| 04 — When it stops | Every halt and banner, and what to do |
| 05 — Continuity | When --continue may skip the human read |
| 06 — Why it works | The mechanisms, low-level, with the costs stated |
| 08 — The harness landscape | OpenClaw, Hermes Agent and DeepSeek Harness, and which layer each sits at |
| 09 — From prompt to workflow | New to agentic workflows? Every term defined, then why ship is a script |
| 10 — Ship and spec for prompt-only engineers | Agentic-workflow primer, review of spec+ship, token and quality tactics |
| 11 — The indicators | What interlock report measures, each denominator, and why it gates nothing |
| 12 — Repository review policy | The optional REVIEW.md: what it can change (scope, advice) and what it cannot (the band, nit cap) |
| 13 — The guards | The four hooks, the stage marker's lifecycle, and the fail-open rule. The guards bind only agents inside a ship run and are inert outside one |
One human stop. Everything else is automatic.
The gap between spec and ship is the product. A spec is the cheapest place to catch a wrong idea, so that is the one place a person is required to look.
/interlock:ship is the one that truly asks nothing, and it is structurally incapable of it: ship is a dynamic workflow, and the workflow runtime takes no mid-run user input at all. The zero-touch contract is a property of the runtime rather than a promise in a prompt. Default ship is waves → unit verify → commit. Pass --strict when you want the adversarial review and handoff tail. commit and mr set disable-model-invocation: true for the same reason — Claude cannot decide on its own that now is a good time to commit.
The steps before it are conversational where they have to be: spec asks about intent, bug-fix evidence, and dependency versions it refuses to guess — questions with no correct answer available in the repo. That is also why every decision that could need a human has to be settled before ship starts. Once the workflow is running, there is nobody to ask.
Decisions that have a correct answer are moved out of prose and into code, one at a time. The plugin ships two CLIs on your PATH:
interlock — the deterministic spine. Each subcommand replaces a judgement the model used to re-derive in prose on every run, usually inconsistently:
| Command | Decides |
|---|---|
interlock waves |
Wave order, per-task model, a hard cap on parallel agents, and whether two tasks in one wave would edit the same file |
interlock surface |
Whether a diff touches UI, and therefore needs a manual test plan |
interlock gate |
Whether a review blocks, which findings are too weak to report, and how the rest partition for parallel fixers |
interlock review |
Which findings survive two skeptics, and how many were dismissed versus dropped as too weak |
interlock remediate |
What gets fixed, what gets deferred, and when the round budget is spent |
interlock verify |
What to run, what a red result means, and which failures share a root cause |
interlock wave-state |
What happens next in the wave loop, and when to stop |
interlock risk |
How dangerous a change is, from its paths and artifacts |
interlock drift |
Which completed changes were never archived, which specs cite files that are gone, and which changed files no spec describes |
interlock conformance |
Which spec scenarios a change must be checked against — the questions, never the verdicts |
interlock ready |
Whether a change may skip the human checkpoint — fail-closed |
interlock ledger |
Whether the decision ledger still holds an unanswered product question |
interlock validate |
Whether a change is actually implementable |
interlock tasks |
Whether the wave plan covers every unchecked box, and which ids may be ticked |
interlock run-log |
Whether a finished run's trajectory can actually be replayed |
interlock limits |
Every cap the loop obeys, so nothing restates one |
Every one of them runs without a model and without the network, so you can check any decision the loop made yourself.
interlock-graph — a local, deterministic code knowledge graph. No vector store, no network. Agents navigate with token-budgeted subgraphs instead of re-grepping:
interlock-graph build .
interlock-graph consumers normalizeEmail
interlock-graph path lib/auth app/apiEverything genuinely requiring judgement — classification, implementation, review, synthesis — stays with the model. The split is the point: the script holds the loop, the CLI holds the rules, the agents do the work.
The wave loop, the halt conditions and the verification order are workflows/ship.js — a script, not numbered headings a model is asked to follow. Control flow written as prose is control flow the model can talk itself out of. Default ship is that loop through to a green unit suite and a commit. Adversarial review and handoff artifacts are --strict (or --review / --handoff on their own), not the execute loop itself.
That leaves one thing worth calling out because it took the longest to close: tasks in a wave run in parallel in one working tree, and their independence used to be asserted by the classifier and checked by nothing. The planner now takes each task's predicted file list and moves any task that would collide with a sibling into a later batch of the same wave — ordering inside a wave is free, while a new wave is a checkpoint. Collision is compared on the canonical path, so src/a.ts and ./src/a.ts are one file rather than two keys; a path that is absolute or escapes the repo root is reported as unusable rather than rewritten into scope. The prediction is still a model's — but with --isolate-waves, each lane in a batch runs in its own git worktree, so the race is closed within a batch rather than merely narrowed: a mis-predicted shared write can no longer overwrite a sibling lane. Their worktrees fold back into the shared tree afterward (interlock merge-lanes); a prediction miss — two lanes that actually wrote the same file — surfaces as a named halt at merge time, never as a silently discarded write. Without the flag, the race is narrowed exactly as before.
/interlock:review-code fans out up to six dimensions in parallel — language, architecture, QA and technical-lead always; devops and security when the diff earns them — then puts two skeptics on every blocker and warning and tries to refute it. Findings that don't survive are never shown to you.
An unverified review reports everything it notices, so you learn to skim it. A review where every finding survived two adversaries is one you read line by line. The report always tells you how many findings were dismissed — that number is the evidence it's worth trusting.
A skeptic must cite what it read to dismiss a finding. A verdict of "not real" has to carry a file:line (or file:start-end) span naming a path that is actually in the reviewed diff — a shape a machine can check, not a judgement call handed to another model. A dismissal that fails either half does not dismiss anything: it is recorded, its quality score still counts, and the finding survives to you. The report says how many refutations were refused, so you can see the rule fire. Voting a finding real needs no citation, because that direction already ends with a human reading it — the cheap error. Only the dismissing direction is gated, because a wrongly dismissed finding is invisible, and nobody can catch a mistake they never see. Research on adversarial review documents where uncited refutation ends: eighty-plus agents, dedicated skeptics among them, unanimously endorsing an OpenSSL vulnerability that did not exist. Confident prose is the one thing an LLM produces reliably, so it is the one thing a dismissal must not rest on.
Surviving is not sufficient. interlock gate also applies a quality band: a finding the skeptics scored too low for how well-grounded and actionable it is gets dropped before the gate counts blockers, so a vague blocker cannot hold up a change. That threshold lives in the CLI rather than in the review prose, which is what stops it from being quietly re-argued on each run.
A repo can own its review policy in an optional root REVIEW.md, read on every review. It can change scope and advice: declare ## Do Not Report paths the CLI drops findings on, and prose — the local definition of "Important", who owns the bar — injected into the reviewer as clearly-delimited advice. It cannot change the band or the nit cap: those stay in the CLI, and a threshold-shaped key in the file is reported and ignored, never adopted — a path exclusion a model could talk past is not an exclusion, and a band a file could edit is not a gate. No REVIEW.md changes nothing; a malformed one is reported and the run proceeds under default policy. See repository review policy.
bootstrap once per repo. Then spec and ship on every change. mr when you want the merge request.
bootstrap |
Onboard a repo — once | skill |
spec |
Idea → reviewed, implementation-ready change | skill |
ship |
Reviewed change → commit (waves → verify → commit). --strict adds review and handoff |
workflow (skill trampoline) |
mr |
Change → merge request | skill |
ship is the odd one out on purpose: a skill is instructions Claude follows, a workflow is a script a runtime executes. /interlock:ship is a thin skill that only launches workflows/ship.js, so the Skill tool can find it in any repo where the plugin is installed. The loop stays in the script.
Advanced surface — mostly called by the four above; reach for them directly only when you know why
explore |
Parallel read-only reconnaissance, durable brief |
review-code · review-artifacts |
The adversarial gates, run standalone. Default ship does not run review-code; pass --review or --strict. |
graph · docs-digest |
Build and query the local code graph and docs digest |
fix-tests |
Discover the test setup, then repair failures by root cause |
manual-test-plan · explain-code · commit |
Individual ship stages, run on their own |
dispatch |
One batched pre-flight, then routes you to the right skill |
None of these are part of a first loop — see the first hour.
Live-session retro (session-retro) now ships from shippable-skills so it can run on Cursor, Copilot, Codex, and Claude Code. Install with npx skills add renzrollon/shippable-skills.
openspec init installs its own openspec-propose, openspec-explore and openspec-apply-change skills. Interlock does not fork them. /interlock:spec drives the openspec CLI directly — openspec new change, openspec status --json, openspec instructions — because the CLI is the stable contract and a forked skill drifts on every OpenSpec release.
What Interlock adds around it: parallel exploration with a durable brief, an evidence gate for bug fixes, invariant sweeps, wave execution with mechanical caps, optional adversarial review (--strict or /interlock:review-code), and the deterministic spine above.
Both sets of skills coexist. Plugin skills are namespaced, so /openspec-propose and /interlock:spec both stay available. Use /interlock:spec when you want the gates; use the stock skills when you want the plain artifact loop.
Spec drift is the standing criticism of every tool in this category, and the usual answers are to delete the spec after shipping or to leave it to discipline. OpenSpec's openspec archive merges a completed change's deltas back into the living specs — Interlock never archives for you, it just stops the step being forgotten:
interlock drift --changed <files>Four findings, deliberately kept at different confidence rather than averaged into one number: changes that finished but were never archived (certain — read off the filesystem); specs citing files that no longer exist (evidence — the file was there when the graph was built); changed source files no spec describes, always reported with a repo-wide coverage figure so the count means something; and specs older than code they cite (an inference from dates, printed last and labelled as such).
interlock conformance is the other half: it lists the scenarios a change's delta specs promised, so each can be checked against what was actually built. It emits questions, never verdicts.
Neither blocks. Every other subcommand exits non-zero when it blocks; these two never do. A gate built on regex-inferred spec→file links would be wrong often enough to get switched off, and a gate everyone disables protects nothing.
Structural graph indexing — import and symbol edges — covers JavaScript/TypeScript, Python, and shell. Other languages (Go, Rust, Java, Ruby) get everything else: docs and OpenSpec indexing, spec→file links, prose retrieval, and the full workflow. When interlock-graph build finds nothing to index it says so and explains why, rather than reporting an empty graph as success.
Everything else in the plugin is stack-agnostic. bootstrap reads your dependency manifest and phrases its explorer agents in your stack's vocabulary.
Most of the category competes on how much structure you write before coding — Spec Kit adds phases, BMAD adds roles, Kiro adds an IDE. Interlock competes on a different axis: how many decisions the model is not allowed to make.
- Caps and gates are code. Remediation rounds, the task-failure budget, parallelism, the review quality floor — all in a tested CLI, not in markdown a model can talk itself past.
- The zero-touch contract is the runtime's, not a prompt's.
shipis a workflow, so there is nobody to ask. Everyone else promises autonomy in prose. Default ship is waves → verify → commit;--strictis the review/handoff tail. - A dismissal must cite evidence; a report needn't. On
--review/--strictor/interlock:review-code, findings are attacked before you see them, dismissal counts are printed, and a refutation that cites nothing refutes nothing. - The invariant sweep is the licensed exception to the diff leash — a value canonicalized in one place and still read raw in three others is the one bug class every diff-scoped review is structurally blind to.
- Spec drift is measured, not hand-waved — and reported at three separate confidence levels rather than one misleading number.
The trade is portability. Spec Kit runs on thirty agents; Interlock runs on one, because the guarantees above come from Claude Code's workflow runtime and its plugin surface. A portable version of this would be a folder of prompts, which is the thing it exists not to be.
That is a bet, not a wall. The part of Interlock that is host-specific turns out to be small — spawn an agent, run the CLI, branch on its exit code — so it is now a stated contract with a second implementation behind it (below). Everything the loop actually decides lives in a CLI any host shells out to. Portability here means a second host adapter, not thirty prompt templates.
Earned autonomy is an internal ledger. interlock autonomy records per-path run outcomes (review-code, artifact review, and ship --strict) and interlock outcomes accumulates one line per ship run, but nothing reads either to change what the workflow does. No autonomy level and no accumulated outcome ever relaxes a gate. The only path that skips the checkpoint is the explicit --continue flag on spec, which is fail-closed and accounted for in continuity — never something the ladder earns on your behalf.
They exist to answer, later and from evidence, whether any gate can safely be relaxed. That question stays open until there is a corpus to answer it with, and wiring a branch before then would be deciding without the data these were built to gather.
interlock report reads them, and still changes nothing. It computes indicators over all three recorded corpora — outcome records, run trajectories, review metrics — with every value carrying its denominator, and it gates nothing: no threshold, no verdict, always exit 0, and no step of any run consults it. Reading a corpus and branching on it are different acts, and only the first has been built. See the indicators.
A second host, over ACP. lib/host.mjs states the whole host contract — spawn one labeled agent, spawn a batch, run interlock and branch on its exit code — and forbids a host from reimplementing wave ordering, verify judgement, limits or the gate. bin/interlock-ship-acp is the second implementation of it, over the Agent Client Protocol: it spawns your ACP agent as a subprocess per task and shells out to the same CLI for every decision.
INTERLOCK_ACP_COMMAND="<your-acp-agent>" interlock-ship-acp <change-name>What it is for: proving the boundary is real. A host contract with one implementation is a comment. What it is honestly not: the supported path.
- Lean only. waves → verify → commit.
--strict,--review,--handoffand--conformanceexit2rather than quietly shipping something smaller than you asked for. - No per-tier model routing. ACP v1 has no per-prompt model selector, so the planner's tier ladder is not in effect and the run banners
MODEL ROUTING UNAVAILABLE (ACP host). The cost story is a Claude Code property. - The zero-touch contract is weaker. On Claude Code nobody can interrupt a run because the runtime has no channel for it. Here the driver just declines to ask — a policy in a file, not a property of a runtime.
/interlock:shipis untouched. No flag, no auto-detect, no fallback: when the Workflow tool is missing the trampoline still halts. See when it stops.
Evals over the model-facing surface. The deterministic spine is densely unit-tested; the prompts, skills and shared contracts that steer a model are not, and the repo's own archived proposals record failures where the prompt bytes were correct and a model did the wrong thing anyway. An evals/ case suite regression-tests that surface against a real model — tier read-scope, cited-cap resolution, lane partial-failure reporting, handoff enum conformance, control-plane action invention, trampoline halt, skill routing, and evidence-locator fabrication — each case citing the reproduced failure it encodes.
export CLAUDE_CODE_WALNUT_SPIRE=1 # early-access enablement — env only, never committed
claude plugin eval . --tag smoke --no-publish --json evals-results.json
interlock evals triage --results evals-results.json # regression / variance / no signal — exit code is the verdict- Enablement is a local prerequisite.
claude plugin evalis early-access and does nothing untilCLAUDE_CODE_WALNUT_SPIRE=1is set in the environment. Do not commit it to.claude/settings.json— a committed value produces a suite that looks configured and does not run. - The verdict is model-free.
interlock evals triageclassifies a results file without a model or the network; its exit code is the verdict, so the one gate a model could otherwise re-argue is on the deterministic spine like every other decision. - Advisory, pending a baseline. The CI eval job reports and does not block. No baseline scores exist yet, so any blocking threshold would be a guess; promotion needs observed variance across more than one run. The offline structural gate
test/evals.test.mjsruns innpm testand does gate every pull request. - CI skips until access is provisioned. The eval job (.github/workflows/evals.yml) skips fork pull requests and skips cleanly when no model credential is present, so the suite lands and is maintained before paid access exists. Provisioning a credential is a later configuration action, not a prerequisite for the change.
Code Mode is out of scope. Running the loop as generated code against a tool API is interesting and it is not this: it would need Interlock to own a runtime to execute that code in, which it does not. Future work, contingent on that, not a supported ship host today.
git clone https://github.com/renzrollon/interlock && cd interlock
npm test # 760 tests, no dependencies
claude plugin validate . --strict
claude --plugin-dir . # load it without installing/reload-plugins picks up edits without restarting.
Built on OpenSpec by Fission AI, and on the wave-execution pattern for parallel task application.
