A drop-in governance protocol for AI-assisted iteration. harness init lays a harness CLI plus the .harness/ state machine into any repo so AI sessions (Claude Code, Codex, Cursor) follow a coordinator → implementer two-session split with checkpointed commits.
Language-agnostic. Three AI-platform integrations + GitHub. Designed so target repos can pull upstream updates without losing their own state.
- Two-session protocol — coordinator session writes a task brief at
.harness/active/<id>.md; implementer session executes one phase at a time, stops at every commit point and session boundary. - Three triage paths — coordinator routes each request to scaffold (greenfield bootstrap), light (small change), or full (non-trivial change). Greenfield is auto-detected at
harness init(no language manifest + no source dirs + no architecture doc) or forced via--greenfield. - TODO gate — if
CONTRIBUTING.mdstill has unfilled🛠 TODO (project maintainers)blocks, the coordinator first runs afill-contributingprep task that scans manifest/CI/formatter configs, proposes candidate answers, and writes only after you confirm. No surprise auto-fills. - Spec gate — for tasks that span cross-cutting dirs, introduce new external dependencies, change external interfaces, or affect the data model, the coordinator decides whether to produce a standalone design spec at
docs/specs/<task-id>.md(in addition to the task brief). Phase 0 of the brief writes the spec; subsequent phases use it as the design authority. - State on disk —
.harness/CURRENT.mdis the single source of truth; sessions can crash and resume with no information loss..harness/SCAFFOLD-PENDINGis the marker that drives the scaffold gate. - Cross-task memory —
.harness/MEMORY.mdaccumulates conventions, pitfalls, and decisions over time, surfaced to every new session. - Three AI-platform hooks — Claude Code (slash command +
SessionStart/PreToolUsehooks), Cursor (alwaysApplyrule), Codex (MCP skill). - GitHub PR template with a required protocol-status field.
Every code block below has a label telling you where to run it:
bashblocks → run in your terminal (zsh / bash). Copy the whole block; comments (# ...) are fine to keep.textblocks → paste into your AI tool (Claude Code / Cursor chat / Codex CLI prompt) as a chat message.- File-edit instructions are spelled out in prose ("open
harness.config.tomlin your editor and …") — no code block.
If a block has no explicit "Paste into your AI tool" callout, treat it as a terminal command.
CommonHarness is distributed as release tags. Every install pins to a specific version and lands in its own directory; a current symlink picks which version is active. Multiple versions can co-exist for safe upgrades and rollback.
In your terminal:
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.2.0/install.sh | bashWhen this finishes, the harness CLI is at ~/.local/bin/harness (symlinked through ~/.commonharness/current/).
Check whether it's already there:
echo $PATH | tr ':' '\n' | grep -F "$HOME/.local/bin"-
If it prints a path (e.g.
/Users/you/.local/bin) → already set, skip to Step 3. -
If it prints nothing → add it to your shell rc and reload it:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc source ~/.zshrc
macOS default shell since Catalina is zsh (
~/.zshrc). If you're on bash, use~/.bashrcinstead. Ifsourcedoesn't seem to take effect, open a fresh terminal window.
harness --versionExpected: harness 0.2.0. If you see command not found: harness, redo Step 2 (most likely the PATH change didn't propagate to your current shell — open a new terminal window).
Note: env vars must come before bash, not before curl — otherwise the variable only reaches curl and is dropped before install.sh runs.
# Example: pin to an older release for rollback.
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.1.0/install.sh \
| HARNESS_VERSION=v0.1.0 bashcurl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/main/install.sh \
| HARNESS_VERSION=main bashgit clone --depth 1 --branch v0.2.0 \
https://github.com/Libr-AI/CommonHarness.git \
~/.commonharness/v0.2.0
~/.commonharness/v0.2.0/install.shTo use SSH instead, set HARNESS_REPO_URL (note: env var goes before bash, not before curl):
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.2.0/install.sh \
| HARNESS_REPO_URL=git@github.com:Libr-AI/CommonHarness.git bashRequires: bash, git, python ≥ 3.9.
~/.commonharness/
├── v0.1.0/ ← pinned snapshot (shallow tag clone, can't switch branches)
├── v0.2.0/ ← later, after upgrade — old versions kept for rollback
└── current → v0.2.0 ← which version is active
~/.local/bin/harness → ~/.commonharness/current/bin/harness
harness --version reads from the active install's VERSION file. Each project records the version it was init'd against in its harness.config.toml (harness_version = "..."), so team consistency is auditable.
In your terminal, from the project's repo root:
cd /path/to/your/project
harness init --preset python-uvThat writes AGENTS.md, CONTRIBUTING.md (with TODO sections), harness.config.toml, the .harness/ state directory, and the AI integrations. The CLI prints "Next steps" telling you what to do next.
Greenfield projects. If you're initializing into an empty repo (no language manifest, no source dirs, no architecture doc), harness init auto-detects this and offers to bootstrap with the scaffold path. You can also force the decision:
harness init --preset python-uv --greenfield # force greenfield bootstrap
harness init --preset python-uv --no-greenfield # force brownfield (skip detection)
harness init --preset python-uv --non-interactive # auto-confirm detectionWhen greenfield is on, a marker file .harness/SCAFFOLD-PENDING is written; your first harness start will route to a 4-phase scaffold task (architecture decisions → directory skeleton → first runnable module → CONTRIBUTING fill + CI). The marker clears automatically when the scaffold task archives.
The first harness start will offer to fill CONTRIBUTING.md's 🛠 TODO (project maintainers) blocks for you (scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm). You can still hand-edit if you prefer — but the coordinator's TODO gate will detect unfilled blocks and offer the prep task either way.
For greenfield projects: same — but the TODO fill happens as Phase 4 of the scaffold task, not a separate prep task.
In your terminal:
harness startThis prints a long block of text — the coordinator session opening prompt. The next step happens in your AI tool.
Paste the printed text into your AI tool (Claude Code / Cursor / Codex chat) as your first chat message:
You are the **coordinator** for an iteration on this repository. Follow the harness governance protocol strictly.
…
(everything `harness start` printed)
The AI then walks you through the protocol: asks what you want to change, runs triage, writes a task brief at .harness/active/<task-id>.md, and tells you how to start the implementer session.
In your terminal:
harness implement <task-id> # resume / enter the implementer session (also prints a prompt to paste into AI tool)
harness status # show CURRENT.md + the active brief
harness end # archive a finished task → CURRENT.md back to idle
harness remember "<text>" # append a convention/pitfall/decision to MEMORY.md
harness curate-memory # quarterly MEMORY.md cleanup (opens it in $EDITOR)The full protocol (triage paths, commit-point markers, anti-garbage rules) is rendered into your repo as AGENTS.md and .harness/workflow.md. Read those.
The coordinator routes each request through three sequential gates (.harness/triage.md in target repos):
| Gate | When | What happens |
|---|---|---|
| Step 0 — Scaffold gate | .harness/SCAFFOLD-PENDING marker exists |
Force scaffold path (4 pre-named phases). Skip the three-question rubric. Marker is removed when the scaffold task archives. |
| Steps 1–3 — Light/Full rubric | Marker absent | Three checks (schema / cross-cutting / tests). All NO → light (single phase). Any YES → full (multi-phase, fresh session per phase). |
| Step 4 — Spec gate | After path decision | Decide if the task needs a standalone design spec at docs/specs/<task-id>.md. Triggers: cross 2+ cross-cutting dirs, new external dep, external-interface change, data-model change, or explicit ask. Adds a Phase 0 to the brief. |
There's also a TODO gate in the coordinator opening (before the rubric): if CONTRIBUTING.md still has unfilled 🛠 TODO (project maintainers) blocks and you're not in scaffold mode, the coordinator runs a fill-contributing prep task first — scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm.
| Preset | Format / test commands | Default forbidden_without_brief |
Default spec_dir |
|---|---|---|---|
python-uv |
uv run ruff format . / uv run pytest |
["src", "tests"] |
docs/specs/ |
Adding a new preset is one TOML file under presets/ — copy presets/python-uv.toml and edit. After running init, harness.config.toml is yours to tune; the preset only seeds the defaults.
Key config keys you'll want to know about:
paths.architecture_doc— where the project's directory-layout doc lives (defaults todocs/ARCHITECTURE.md); coordinator/implementer always read this.paths.spec_dir— where standalone design specs land when the spec gate fires (defaults todocs/specs/).paths.cross_cutting— directories whose 2+-span triggers full path and spec gate.
CommonHarness sorts files in the target repo into three layers, and each behaves differently on re-runs / upgrades:
| Layer | Files | Behavior on harness init --force / future harness upgrade |
|---|---|---|
| Managed | AGENTS.md, .harness/{workflow,triage,README,templates/*}, .claude/*, .cursor/rules/harness.mdc, mcp/skills/harness/SKILL.md |
Overwritten cleanly from upstream templates |
| Fenced fragment | CONTRIBUTING.md, CLAUDE.md, .github/PULL_REQUEST_TEMPLATE.md |
Marker-aware merge: only the <!-- harness:begin --> … <!-- harness:end --> block is replaced; everything outside (your business content) is preserved |
| Owned | .harness/CURRENT.md, .harness/MEMORY.md, .harness/active/*, .harness/archive/*, .harness/SCAFFOLD-PENDING, docs/specs/* |
Never touched. The scaffold marker is created by --greenfield init and removed by harness end of a path: scaffold brief. Spec files are created by the implementer in Phase 0. |
This is what makes the protocol upgradable without clobbering project-specific work.
# Install a new version alongside the old one + flip 'current'.
# (Replace v0.3.0 with whichever release you're upgrading to.)
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.3.0/install.sh \
| HARNESS_VERSION=v0.3.0 bash
harness --version # confirms the new version is now active
# Roll back any time by flipping the symlink (older versions stay on disk):
ln -sfn ~/.commonharness/v0.2.0 ~/.commonharness/currentOld versions stay on disk; switching is a single symlink. Because each version dir is a shallow tag clone, you can't accidentally git checkout a different ref and produce inconsistent behavior across the team.
Find the preset your project was originally initialized with — it's recorded at the bottom of harness.config.toml:
grep '^preset' harness.config.toml
# e.g. preset = "python-uv" → use python-uv belowThen back up the config (it's the one file that gets overwritten in full — see the caveat below) and re-run init with --force and --no-greenfield:
cd /path/to/target/project
cp harness.config.toml harness.config.toml.bak # safety backup
harness init --preset <same-preset> --force --no-greenfield
diff harness.config.toml.bak harness.config.toml # spot any tuning to merge back
# (manually re-apply any [verify] / [paths] / [branch] tuning you'd customized)
rm harness.config.toml.bak # once you're satisfiedWhy --no-greenfield? Without that flag, init's greenfield detection re-runs on every --force re-init. The heuristic CAN false-positive on existing projects whose source dir isn't named src/ / tests/ etc. — and an accidental Y at the prompt would write a stray scaffold marker into your already-mature project. --no-greenfield skips the prompt and the detection entirely; for any project being upgraded (not freshly bootstrapped), that's always the right answer.
What --force touches:
- Managed files (
AGENTS.md,.harness/{workflow,triage,README,templates/*},.claude/*,.cursor/rules/*,mcp/skills/*) → re-rendered from upstream templates (your edits to these files are overwritten — they're not meant to be edited locally). - Fenced fragments (
CONTRIBUTING.md,CLAUDE.md,.github/PULL_REQUEST_TEMPLATE.md) → marker-aware merged. Only the<!-- harness:begin --> … <!-- harness:end -->block is replaced; your business content outside the fence is preserved. - Owned files (
.harness/CURRENT.md,.harness/MEMORY.md,.harness/active/*,.harness/archive/*,.harness/SCAFFOLD-PENDING,docs/specs/*) → never touched. In-progress tasks, accumulated memory, and existing specs all survive. harness.config.toml→ ⚠ rewritten in full from the preset defaults, including resetting[verify]/[paths]/[branch]to preset values. This is why Step 2 starts withcp ... .bak— diff your backup against the new file and re-apply any tuning. (A futureharness upgradecommand, planned, will do field-level merge to avoid this manual step.)
The renderer prints + wrote / ~ merged / · skipped for each file so the diff is auditable.
Whoever runs Step 2 then commits + pushes, and the rest of the team gets the new protocol on git pull of their project — they don't all need to run init --force themselves.
harness upgrade [--to <ver>] [--dry-run]— combines Steps 1 and 2; shows diff before writing.harness doctor— reports drift, broken hooks, version mismatch (harness --version≠ project'sharness_version), and unfilled🛠 TODO (project maintainers)markers (note: the v0.2.0 TODO gate already detects these atharness starttime, but doctor surfaces them outside any coordinator session).
bin/
harness # bash CLI (init, start, implement, status, end, remember, curate-memory)
_render.py # template engine + marker-aware merge
_toml.py # zero-dep TOML loader (Python 3.9+ fallback)
_cfg_get.py # CLI: read one key from harness.config.toml
templates/ # .tmpl files; directory layout mirrors the target repo
presets/ # one .toml per preset
install.sh # curl-pipe-bash bootstrap
.claude-plugin/ # Claude Code plugin manifest
tests/ # (placeholder for bats-core suite)
MIT.