emerik documentation

Store decisions with your code. Check that they still hold.

emerik is a git-native CLI for keeping engineering knowledge current and enforceable. It records context as you work, turns important decisions into checks, and coordinates parallel work. There is no server or database to operate.

Node 22.16+ Package manager pnpm Backend git only Version 0.8.0 License MIT

What is emerik

AI agents can change software quickly, but each session begins with limited context. The reasons behind the code are easily lost: why a value exists, which constraint must hold, or what the team already tried. emerik records that knowledge as small artifacts committed beside the code.

The record lives in .emerik/ and is append-only. Your git remote is the source of truth. Knowledge travels with the software and uses the same version history.

The name

emerik is named for Emerik Blum, founder of Energoinvest. Bloomteq shares the same namesake.

The mental model

Start with four ideas. They explain how emerik stores knowledge, keeps it current, and coordinates work.

Pin

A note tied to specific source code. emerik refreshes it when the source changes and retires it when the source is deleted.

Anchor

A reviewed decision with a check. If the implementation drifts, emerik advises, warns, or blocks at the level you chose.

Lease

A time-limited, exclusive claim on part of the codebase. It keeps concurrent work apart without a lock server.

The substrate

The append-only .emerik/ store. New records replace old ones without erasing them. Deletions leave a tombstone.

An Anchor is useful only if its check fails when the decision is broken. See that behavior end to end in the honesty loop.

The spine: how work is organised

A second set of concepts describes the work itself: what is active, what remains, and how it ended. Each record belongs to an Initiative and appears on the departure board.

Finish condition

One line that states when an initiative is finished. A person writes it. Without one, the initiative appears as undefined.

Remainder item

One thing still left to do. An item owed by someone outside the team is a blocker with an externalOwner.

Derived state

emerik computes state from the live records: closed, undefined, blocked, or active.

The declared lane

A marker a person gives to the few initiatives that matter now. Set it with declare or amend.

Closure

An outcome, a cause when the work was not completed, and one closing line in the author’s own words.

The departure board

One line per initiative, ordered so the next useful work appears first. The board does not require manual status updates.

Requirements

  • Node.js 22.16 or newer. The CLI is tested on Node 22 and 24. 22.16 is where Node's built-in SQLite gained full-text search, which emerik's lexical retrieval floor needs on platforms with no hybrid search binary (see the local index); Node 20 reached end of life in April 2026.
  • git. emerik is git-native — every command operates on a git repository.
  • pnpm. The project's package manager; the lockfile is committed. Install it with corepack enable or from pnpm.io.

Installation

emerik v0.8.0 installs from source. Clone the repository, install dependencies, and build the CLI bundle.

01

Clone & build

git clone <emerik-repo-url> emerik
cd emerik
pnpm install
pnpm build          # tsc --noEmit + tsup + oclif manifest → dist/
02

Verify the binary runs

./bin/run.js --version   # → emerik/0.8.0 …
./bin/run.js --help      # show the full command tree
03

Put emerik on your PATH (optional)

So you can call emerik from any repository instead of the full path:

# option A — symlink the launcher into a directory already on PATH
ln -s "$(pwd)/bin/run.js" /usr/local/bin/emerik

# option B — npm link (from the emerik project root)
npm link
Note

The rest of this guide writes emerik <command>. If you skipped step 03, substitute the path to bin/run.js.

Quickstart

Run these commands inside any git repository to create and verify its knowledge graph.

cd your-project
emerik init                 # store, hooks + wire the agents you pick (asks once, remembers)
git commit -am "chore: emerik init"
emerik harvest              # harvest source-bound Pins from HEAD
emerik verify               # run the convention + Anchor gate (exit 0 = clean)

Inside a coding agent, start with emerik-guide for an orientation, tour, or guided workflow. For a new repository, follow Start a new project.

Contributor aside

Do not run emerik init inside emerik's own source repo — a root .emerik/ there pollutes emerik's self-hosted CI. Dogfood on a separate project.

Start a new project

emerik can take a new repository from an idea to its first verified Goal. Five planning skills turn the idea into sequenced Goal briefs. Each step commits its output, so planning and delivery stay in the same history.

What is proven, and what you do

CI tests this complete path through the real CLI and MCP server: init → planning → start a Goal → commit the work → pass verify → complete the Goal. The walkthrough below follows the same sequence with a person driving it.

01

Create the repo and initialize

Start from nothing but an empty directory and wire emerik in.

mkdir task-inbox && cd task-inbox
git init
emerik init                 # store, hooks + the planning skills, in the agents you pick

An empty substrate — a fresh repo with zero Pins — is a valid entry state, not a special mode. Greenfield is simply what it looks like when the grounding reads come back empty.

02

Brainstorm the idea

Inside your coding agent, run emerik-brainstorm. The session starts from the knowledge already in the repository. In a new repository, that knowledge is empty. The skill commits the session to .emerik-planning/brainstorm-<slug>.md, which the harvest hook captures as Pins. It records any stated constraint as a constraint-Anchor for its steward.

03

Grow the planning head

Three skills turn the idea into committed planning records:

  • emerik-brief — the idea becomes a committed brief artifact: problem, users, outcome, scope fence.
  • emerik-prd — the brief grows into a prd, and each binding requirement is offered as a candidate constraint-Anchor at the moment of statement.
  • emerik-architect — the prd becomes a durable architecture, and each decision that governs a code region lands as a governing Anchor (inverted CODEOWNERS) — the design document and the enforcement surface are the same thing.

A revision never edits in place: a changed head is a superseding artifact (--supersedes), so the old one stays replayable in lineage.

04

Break it down into Goals

emerik-breakdown accepts either the live PRD and architecture or a scoped epic brief from a project manager. The first path creates an initiative brief at .emerik-planning/initiative-<slug>.md. On the second path, the epic brief is the initiative and receives the sequence directly. Both paths create delivery-sized Goal briefs under .emerik-goals/. Each brief names its initiative, priority, and dependencies.

05

Pick up the first Goal — and build

Use emerik-goal to turn one brief into one Goal. It reads the priority from the Sequence block. To distribute the whole sequence in dependency order, use emerik-orchestrate. The engineering loop then retrieves the relevant Pins and Anchors, implements under a Lease, runs emerik verify, and completes the Goal.

One substrate, two halves

Planning and engineering live on the same append-only substrate — the brief, prd and architecture heads, the constraint- and governing-Anchors, and the Pins harvested from every committed doc all feed the same retrieval the implementation loop grounds in. See Use with an AI agent for the full skills grid and the tool surface.

Initialize a repo

emerik init is idempotent — safe to run again. It creates:

  • .emerik/ — the append-only, content-addressed store (committed with your code).
  • .emerik/.gitignore and a root .gitignore rule for the ephemeral local index.
  • a post-commit hook (auto-harvest) and a pre-push hook (freshness re-derive).

It asks which agents you use

On a terminal, emerik init shows the eight supported coding agents and pre-selects the ones already present in your repo. Use j/k to move, space to toggle, to confirm, and q to cancel. You must select at least one agent to confirm. To wire none, use emerik init --no-agents.

Your choice is saved in .emerik/config.yaml and committed with the repo. After cloning, teammates can run emerik init and get the same setup without answering again.

The eight ids — they are the flag vocabulary and the config values: claude-code, cursor, codex, opencode, gemini-cli, antigravity, windsurf, github-copilot.

It never prompts when nobody can answer. With --json, redirected input or output, or CI set, emerik init chooses from the following sources, in order:

What you didWhat gets wired
--no-agentsNothing at all. No skills, no MCP registration, no AGENTS.md — just the store, the ignore rules and the hooks.
--agents claude-code,cursorExactly those ids (comma-separated, kebab-case, any order). Skips the prompt, and is remembered in .emerik/config.yaml. An unknown id fails with the list of valid ids and writes nothing at all.
a repo with an agents: list already in .emerik/config.yaml That list — and the prompt does not re-open. A committed selection is a decision your team already made, not a question worth re-asking everyone who clones, so emerik init heals it silently and the report names the command to change it: emerik init --agents <ids>.
a terminal, and no answer yetWhatever you pick in the prompt.
no terminal, and no answer yet — but the repo carries agent directories The agents it detected: .claude/, .cursor/, .codex/, .opencode/, .gemini/, .agents/, .windsurf/, or a .github/copilot-instructions.md. emerik's own output does not count as evidence: a directory holding nothing but the skills and MCP files emerik wrote is not a detection.
no terminal, no answer, nothing detectedThe harness-agnostic baseline — the AGENTS.md block plus the .agents/skills/ tree, and nothing else. The report says so, and how to choose instead.

Detection and the baseline are read fresh every run and are never written into your config — only an answer you actually gave (the prompt or --agents) is remembered. And de-selecting an agent removes what a previous init wrote for it — see what it removes when you drop an agent below, including the one rule that matters: only an answer ever removes anything.

What it writes for the agents you picked

The shipped skills land in two directories, because no single one is read by every agent: .claude/skills/ is what Claude Code reads, and .agents/skills/ is the cross-tool standard that Codex CLI, Cursor, Gemini CLI, Antigravity, Windsurf, OpenCode and GitHub Copilot read. Same twenty-two skills, same bytes — and you only get the trees your selection actually needs (pick Codex alone and .claude/skills/ is never created):

  • .claude/skills/ — the twenty-two shipped skills, loaded by Claude Code and by Cursor (2.4+, which reads .claude/skills/ natively): emerik-brainstorm, emerik-brief, emerik-prd, emerik-architect, emerik-breakdown, emerik-discover, emerik-goal, emerik-plan, emerik-implement, emerik-review, emerik-address-review, emerik-guide, emerik-retro, emerik-orchestrate, emerik-onboard, emerik-prepare, emerik-scope, emerik-status, emerik-skillsmith, emerik-explain, emerik-consult and emerik-reconcile.
  • .agents/skills/the same twenty-two skills, byte for byte, in the cross-tool Agent Skills directory. Codex CLI, Cursor, Gemini CLI, Antigravity, Windsurf, OpenCode and GitHub Copilot read this one natively, so they find emerik's skills with nothing to configure. Both trees exist because Claude Code reads only .claude/skills/ — agents that read both (Cursor, OpenCode, Windsurf, Copilot) will therefore list each skill twice, which is expected and harmless. Everything true of the first tree is true of this one: a drifted copy is healed on the next init, the emerik verify skill patrol covers both surfaces, and neither is ever harvested as knowledge.
  • .claude/agents/only when Claude Code is selected. The four reviewer roles the emerik-review skill fans out: blind-hunter, edge-case-hunter, acceptance-auditor and substrate-auditor. Copied verbatim, tool allow-list included.
  • .codex/agents/only when Codex CLI is selected. The same four roles as Codex subagent TOML: name, description and developer_instructions carrying the role's instructions unchanged. Nothing else is written — model, sandbox_mode and the rest are your call, not emerik's.
  • .opencode/agents/, .gemini/agents/, .agents/agents/only when OpenCode, Gemini CLI or Antigravity is selected. The same four roles as markdown, one file per role.
  • .github/agents/only when GitHub Copilot is selected. The same markdown under the .agent.md extension. The extension is part of the contract: Copilot loads x.agent.md and does not load x.md.
  • .mcp.jsononly when Claude Code is selected. Registers the emerik mcp server (merged additively, other servers untouched).
  • .cursor/mcp.jsononly when Cursor is selected. The same emerik mcp server, same mcpServers shape, merged additively the same way.
  • .codex/config.tomlonly when Codex CLI is selected. The same server as an [mcp_servers.emerik] TOML table. emerik owns that one table and replaces its body; every other table, every comment and every blank line in the file is preserved byte for byte, because the edit is surgical rather than a re-serialization. If your file already registers emerik some other way — an inline key under [mcp_servers], a dotted mcp_servers.emerik, or [[mcp_servers.emerik]] — emerik warns and changes nothing, since a second table would be a duplicate key and break Codex outright.
  • opencode.jsononly when OpenCode is selected. At the repo root (not under .opencode/), under OpenCode's own mcp key and in its own shape: {"type": "local", "command": ["emerik", "mcp"], "enabled": true}. A $schema you already have is preserved; emerik never adds one.
  • .gemini/settings.jsononly when Gemini CLI is selected. The server under mcpServers — exactly the key gemini mcp add -s project writes. emerik adds a server and nothing more: it never touches context.fileName or anything else that would change what Gemini reads.
  • .agents/mcp_config.jsononly when Antigravity is selected. The server under mcpServers, merged additively.
  • a delimited <!-- emerik:begin -->…<!-- emerik:end --> block describing the substrate and the skills, in each instructions file your selection claims. emerik creates the files it owns outright and only decorates your own front doors when they already exist:
    • AGENTS.md — created if absent, when any selected agent reads it (Cursor, Codex CLI, OpenCode, Windsurf and GitHub Copilot all do).
    • .agents/rules/emerik.mdonly when Antigravity is selected. Created; emerik owns that file outright.
    • CLAUDE.mdonly when Claude Code is selected and the file already exists. Never created.
    • GEMINI.mdonly when Gemini CLI is selected and the file already exists. Never created — see the note below.
    Content outside the markers is never touched, and re-running init heals a missing piece without duplicating anything.

Two honest caveats on the ported reviewer roles. First, the roles emerik ships name their allowed tools in Claude Code's vocabulary (Read, Grep, mcp__emerik__*), which means nothing on the other harnesses — a type mismatch there risks the harness rejecting the file and the role silently not existing, and translating the names would grant access to zero real tools instead. So the ports drop the tool line and each role runs with your harness's default access, which is wider than the role asks for. Each role's discipline is carried by its own prose (“Your inputs — and only these”), which every port keeps word for word, and the Claude Code copy keeps its allow-list intact. Second, Antigravity caps a .agents/rules/ file at 12,000 characters and emerik's block is comfortably under it — but the activation mode (Manual / Always On / Model Decision / Glob) is set inside Antigravity and the key that declares it is undocumented, so emerik writes plain markdown rather than guessing one. Set the rule to Always On yourself if you want it loaded every turn.

And one deliberate refusal: Gemini CLI reads AGENTS.md only when its context.fileName setting says so. Flipping that setting would change what Gemini reads — a different thing entirely from adding a server to its MCP registry — so emerik decorates a GEMINI.md you already have and mints nothing when you don't.

Every one of those files is written the same way: created if absent, merged additively if you already have one, healed if emerik's own entry was stripped, and left byte-identical on a re-run that finds nothing to do. A file emerik cannot read — invalid JSON, something that is not TOML — is never clobbered: emerik warns and leaves it exactly as it was.

What it removes when you drop an agent

Wiring converges in both directions. Narrow your selection — emerik init --agents codex in a repo that used to wire Claude Code too — and the next init removes what emerik wrote for the agent you dropped, so the repo carries no stale wiring. There is no manifest of what was written: every removable path is derived from the same registry the writes come from, which is what makes removal idempotent and harmless on a file emerik never touched.

One rule decides whether anything is removed at all: only an answer removes. --agents, the prompt, and a committed agents: key are answers, and they remove. A detected selection and the harness-agnostic baseline are this run's reading of your repo, not decisions anyone made — they remove nothing, ever. Nor does --no-agents, nor a cancelled prompt, nor an agents: [] that selects nothing: an opt-out for one run is not an instruction to unwire the repo. This is not caution, it is correctness — emerik's own output does not count as detection evidence, so a repo wired by a detected selection detects nothing on its next run and would resolve the baseline; a removal on that inference would delete a repo's wiring in CI, silently, on a selection nobody chose.

What goes, and what stays:

  • The skills trees and the reviewer roles go whole, file by file — emerik overwrites drift in those two places on every init, so it deletes drift there too. A file you dropped inside a shipped skill directory is not one emerik ships, so it survives; so does your own subagent file sitting beside the four roles.
  • An MCP registration loses exactly its own entry. The emerik key under mcpServers (or OpenCode's mcp), the [mcp_servers.emerik] table and any [mcp_servers.emerik.*] sub-table of it. Every other server, every other table, every comment and all key order survive byte for byte. A file that held nothing but emerik's registration is deleted rather than left as an empty husk; a file that still holds anything of yours is kept, minus that one entry.
  • An instructions file loses exactly its marker block. Bytes outside <!-- emerik:begin -->…<!-- emerik:end --> are never touched, so an AGENTS.md carrying your team's own conventions keeps every one of them. An AGENTS.md or .agents/rules/emerik.md that emerik created and that now holds nothing else is deleted. A CLAUDE.md or GEMINI.md is never deleted — emerik never created it, so it is not emerik's to remove; only the block goes.
  • Emptied directories are pruned, and only if genuinely empty — the check is the operating system's, not a guess. A .cursor/ whose only content was mcp.json goes; a .claude/ still holding your settings.local.json stays.
  • A file emerik cannot read is never touched. Invalid JSON, something that is not TOML, or a registration written in a spelling emerik does not use: it warns and leaves the file exactly as it was — the same refusal it applies on the way in. Those bytes are yours.

And when the gate is closed, emerik says what it is leaving behind. A repo whose selection came from detection or the baseline never removes anything — but it does now name the wiring it can see is unclaimed, so a correct refusal no longer looks like an oversight:

! this repo carries emerik wiring for Claude Code, not in this selection — left in place,
  because only an answered selection removes anything
  name the agents to converge it: `emerik init --agents <ids>`

That line is derived from each agent's exclusive destinations — the ones no other agent claims — so it never mistakes the shared AGENTS.md or .agents/skills/ for evidence about a particular harness, and a repo that only ever ran the harness-agnostic baseline stays silent. And when a removal is driven by the committed agents: key rather than by something you passed on this run, the report says that too — because one shared key means a teammate's answer can unwire your harness, and the fix is to widen the key rather than to keep re-running the flag.

The report distinguishes the two cases, because they are not the same thing: a file that is genuinely gone reads removed .claude/agents/blind-hunter.md, while a file that kept your content and lost only emerik's entry reads removed emerik from .mcp.json. If a line says a file was removed, that file is gone — you can check it against git status and the two will agree.

Removals are reported, one line per file plus one line naming the agents whose wiring went, and those lines print even under --quiet: success chatter can be suppressed, a deletion cannot. A full add → remove → re-add cycle ends byte-identical to a fresh install, and nothing under .emerik/, your store, your hooks or the agents: key itself is ever removed — de-selection is about agent wiring and nothing else. There is deliberately no “unwire this repo” command; if you want emerik's wiring gone entirely, that is a request for a separate verb, not an overload of the opt-out.

Two consequences worth knowing before you narrow. First, AGENTS.md and .agents/skills/ are shared — five agents read the first, seven the second — so they are removed only when no agent you selected reads them, and kept automatically when one does. That also means the free coverage those two destinations buy (Amp, Jules, Zed, Warp, Roo Code, Kilo Code and goose all read AGENTS.md or the cross-tool skills tree without emerik naming them) goes with them: narrow to --agents claude-code alone and you lose it. Keeping one AGENTS.md-reading agent in the selection is the whole fix. Second, Cursor has no bench of its own and reads Claude Code's .claude/agents/, so dropping claude-code while keeping cursor removes the bench Cursor was reading — emerik says so in the same run.

What emerik cannot wire for you

This is permanent, expected behavior rather than a gap to be fixed — neither agent has a project-scoped MCP file for emerik to write, so emerik init says so instead of pretending. The note prints every time the agent is in your selection, including on an idempotent re-run and under --quiet: nothing has changed, and a re-init that went quiet about it would look complete when it is not.

  • GitHub Copilot — the cloud coding agent reads MCP configuration from a repository Settings value, not from a file in your tree. emerik prints the ready-to-paste JSON on one line; paste it into Settings → Copilot → coding agent → MCP configuration.
  • Windsurf — its MCP registry is global, not per-repo: ~/.codeium/windsurf/mcp_config.json, outside your repository. emerik names the file and the shape to add, and writes nothing there. (This note does not appear on a default init in a repo where nothing was detected — the harness-agnostic baseline is named windsurf because its whole surface is AGENTS.md plus .agents/skills/, which is not a claim that your repo runs Windsurf.)

The reviewer bench degrades the same way, and says so on the same terms. emerik-review fans out four asymmetric reviewers; where the roles are installed it uses your harness's own subagent mechanism, and where they are not it runs the four inline in one session instead — the asymmetry is the method, so the review still happens, just without separate contexts.

  • Windsurf — the one agent of the eight with no subagent surface at all. There is nowhere to install the roles, so a selection containing Windsurf is told the fan-out goes inline. (Like the MCP note above, this does not appear on a default init in a repo where nothing was detected.)
  • Cursor — Cursor has no bench of its own: it reads Claude Code's .claude/agents/. Select both and the roles are there. Select Cursor without Claude Code and emerik installs the roles nowhere, so it says so rather than letting a Cursor-only init read as complete.

One Codex caveat worth knowing: Codex honors a project .codex/config.toml in trusted projects only. That is a trust decision you make inside Codex, once, and emerik can neither detect it nor make it for you — so if the emerik tools do not show up there, trust the project in Codex and they will.

$ emerik init
Which coding agents does this repo use?
emerik wires the ones you pick and remembers them in .emerik/config.yaml
ids for --agents: claude-code, cursor, codex, opencode, gemini-cli, antigravity,
                  windsurf, github-copilot

  [x] Claude Code
▸ [x] Cursor
  [ ] Codex CLI
  [ ] OpenCode
  [ ] Gemini CLI
  [ ] Antigravity
  [ ] Windsurf
  [ ] GitHub Copilot

2 selected
[j/k] move · [space] toggle · [↵] confirm · [q]uit
 initialized .emerik/ — Pin, Anchor and Glacier store ready
  created .emerik/config.yaml
  created .emerik/pins/.gitkeep
  created .emerik/anchors/.gitkeep
  created .emerik/glacier/.gitkeep
  created .emerik/goals/.gitkeep
  created .emerik/dependencies/.gitkeep
  created .emerik/reviews/.gitkeep
  created .emerik/briefs/.gitkeep
  created .emerik/prds/.gitkeep
  created .emerik/architectures/.gitkeep
  created .emerik/initiatives/.gitkeep
  created .emerik/snapshots/.gitkeep
  created .emerik/remainders/.gitkeep
  created .emerik/explainers/.gitkeep
  created .emerik/citations/.gitkeep
  created .emerik/.gitignore
  created .gitignore
  created .git/hooks/post-commit
  created .git/hooks/pre-push
  created .claude/skills/emerik-plan/SKILL.md
  created .claude/skills/emerik-plan/template.md
  created .claude/skills/emerik-implement/SKILL.md
  created .claude/skills/emerik-discover/SKILL.md
  created .claude/skills/emerik-guide/SKILL.md
  created .claude/skills/emerik-guide/steps/orient.md
  created .claude/skills/emerik-guide/steps/step-through.md
  created .claude/skills/emerik-guide/steps/tour.md
  created .claude/skills/emerik-goal/SKILL.md
  created .claude/skills/emerik-goal/checklist.md
  created .claude/skills/emerik-goal/template.md
  created .claude/skills/emerik-review/SKILL.md
  created .claude/skills/emerik-review/checklist.md
  created .claude/skills/emerik-review/template.md
  created .claude/skills/emerik-retro/SKILL.md
  created .claude/skills/emerik-retro/checklist.md
  created .claude/skills/emerik-retro/template.md
  created .claude/skills/emerik-orchestrate/SKILL.md
  created .claude/skills/emerik-brainstorm/SKILL.md
  created .claude/skills/emerik-brainstorm/template.md
  created .claude/skills/emerik-brief/SKILL.md
  created .claude/skills/emerik-brief/checklist.md
  created .claude/skills/emerik-brief/template.md
  created .claude/skills/emerik-prd/SKILL.md
  created .claude/skills/emerik-prd/checklist.md
  created .claude/skills/emerik-prd/template.md
  created .claude/skills/emerik-architect/SKILL.md
  created .claude/skills/emerik-architect/checklist.md
  created .claude/skills/emerik-architect/template.md
  created .claude/skills/emerik-breakdown/SKILL.md
  created .claude/skills/emerik-breakdown/checklist.md
  created .claude/skills/emerik-breakdown/template.md
  created .claude/skills/emerik-onboard/SKILL.md
  created .claude/skills/emerik-onboard/checklist.md
  created .claude/skills/emerik-onboard/ecosystem-template.md
  created .claude/skills/emerik-onboard/template.md
  created .claude/skills/emerik-prepare/SKILL.md
  created .claude/skills/emerik-prepare/checklist.md
  created .claude/skills/emerik-scope/SKILL.md
  created .claude/skills/emerik-scope/checklist.md
  created .claude/skills/emerik-scope/template.md
  created .claude/skills/emerik-status/SKILL.md
  created .claude/skills/emerik-status/checklist.md
  created .claude/skills/emerik-address-review/SKILL.md
  created .claude/skills/emerik-address-review/checklist.md
  created .claude/skills/emerik-skillsmith/SKILL.md
  created .claude/skills/emerik-skillsmith/checklist.md
  created .claude/skills/emerik-skillsmith/template.md
  created .claude/skills/emerik-explain/SKILL.md
  created .claude/skills/emerik-explain/checklist.md
  created .claude/skills/emerik-explain/template.md
  created .claude/skills/emerik-consult/SKILL.md
  created .claude/skills/emerik-consult/checklist.md
  created .claude/skills/emerik-consult/template.md
  created .claude/skills/emerik-reconcile/SKILL.md
  created .agents/skills/emerik-plan/SKILL.md
  created .agents/skills/emerik-plan/template.md
  created .agents/skills/emerik-implement/SKILL.md
  created .agents/skills/emerik-discover/SKILL.md
  created .agents/skills/emerik-guide/SKILL.md
  created .agents/skills/emerik-guide/steps/orient.md
  created .agents/skills/emerik-guide/steps/step-through.md
  created .agents/skills/emerik-guide/steps/tour.md
  created .agents/skills/emerik-goal/SKILL.md
  created .agents/skills/emerik-goal/checklist.md
  created .agents/skills/emerik-goal/template.md
  created .agents/skills/emerik-review/SKILL.md
  created .agents/skills/emerik-review/checklist.md
  created .agents/skills/emerik-review/template.md
  created .agents/skills/emerik-retro/SKILL.md
  created .agents/skills/emerik-retro/checklist.md
  created .agents/skills/emerik-retro/template.md
  created .agents/skills/emerik-orchestrate/SKILL.md
  created .agents/skills/emerik-brainstorm/SKILL.md
  created .agents/skills/emerik-brainstorm/template.md
  created .agents/skills/emerik-brief/SKILL.md
  created .agents/skills/emerik-brief/checklist.md
  created .agents/skills/emerik-brief/template.md
  created .agents/skills/emerik-prd/SKILL.md
  created .agents/skills/emerik-prd/checklist.md
  created .agents/skills/emerik-prd/template.md
  created .agents/skills/emerik-architect/SKILL.md
  created .agents/skills/emerik-architect/checklist.md
  created .agents/skills/emerik-architect/template.md
  created .agents/skills/emerik-breakdown/SKILL.md
  created .agents/skills/emerik-breakdown/checklist.md
  created .agents/skills/emerik-breakdown/template.md
  created .agents/skills/emerik-onboard/SKILL.md
  created .agents/skills/emerik-onboard/checklist.md
  created .agents/skills/emerik-onboard/ecosystem-template.md
  created .agents/skills/emerik-onboard/template.md
  created .agents/skills/emerik-prepare/SKILL.md
  created .agents/skills/emerik-prepare/checklist.md
  created .agents/skills/emerik-scope/SKILL.md
  created .agents/skills/emerik-scope/checklist.md
  created .agents/skills/emerik-scope/template.md
  created .agents/skills/emerik-status/SKILL.md
  created .agents/skills/emerik-status/checklist.md
  created .agents/skills/emerik-address-review/SKILL.md
  created .agents/skills/emerik-address-review/checklist.md
  created .agents/skills/emerik-skillsmith/SKILL.md
  created .agents/skills/emerik-skillsmith/checklist.md
  created .agents/skills/emerik-skillsmith/template.md
  created .agents/skills/emerik-explain/SKILL.md
  created .agents/skills/emerik-explain/checklist.md
  created .agents/skills/emerik-explain/template.md
  created .agents/skills/emerik-consult/SKILL.md
  created .agents/skills/emerik-consult/checklist.md
  created .agents/skills/emerik-consult/template.md
  created .agents/skills/emerik-reconcile/SKILL.md
  created .claude/agents/blind-hunter.md
  created .claude/agents/edge-case-hunter.md
  created .claude/agents/acceptance-auditor.md
  created .claude/agents/substrate-auditor.md
  created .mcp.json
  created .cursor/mcp.json
  created AGENTS.md
  multi-repo product? register each code repo with `emerik workspace add <url>`
  agents wired: Claude Code, Cursor

The same command in CI, on a repo carrying no agent directories at all, asks nothing and falls back to the harness-agnostic baseline — and says so:

$ emerik init          # piped output, or --json, or CI set
 initialized .emerik/ — Pin, Anchor and Glacier store ready
  created .emerik/config.yaml
  created .emerik/pins/.gitkeep
  created .emerik/anchors/.gitkeep
  created .emerik/glacier/.gitkeep
  created .emerik/goals/.gitkeep
  created .emerik/dependencies/.gitkeep
  created .emerik/reviews/.gitkeep
  created .emerik/briefs/.gitkeep
  created .emerik/prds/.gitkeep
  created .emerik/architectures/.gitkeep
  created .emerik/initiatives/.gitkeep
  created .emerik/snapshots/.gitkeep
  created .emerik/remainders/.gitkeep
  created .emerik/explainers/.gitkeep
  created .emerik/citations/.gitkeep
  created .emerik/.gitignore
  created .gitignore
  created .git/hooks/post-commit
  created .git/hooks/pre-push
  created .agents/skills/emerik-plan/SKILL.md
  created .agents/skills/emerik-plan/template.md
  … the same twenty-two skills
  created AGENTS.md
  multi-repo product? register each code repo with `emerik workspace add <url>`
  agents wired: the harness-agnostic baseline — AGENTS.md + .agents/skills/ only
  no coding agent detected here; run `emerik init` on a terminal to pick from the list,
  or name them: `emerik init --agents claude-code,cursor`

Prefer just the substrate, no agent wiring? Run emerik init --no-agents. Want a specific set with no prompt? emerik init --agents claude-code,cursor.

Works with Cursor. One emerik init wires both harnesses — select them both and it is one command. Cursor reads the shipped skills from .agents/skills/ (the cross-tool Agent Skills directory) and, 2.4+, from .claude/skills/ too — so when Claude Code is selected as well it sees each skill twice, which costs nothing but a duplicate name (select Cursor alone and only .agents/skills/ is written, so there is nothing to duplicate); and AGENTS.md is first-class in Cursor, so the same substrate context, lexicon and skills reach a Cursor agent unchanged. Two caveats worth knowing: the emerik-review fan-out uses the host's native subagents where present, else the documented prompt-file fallback (one prompt per reviewer under .emerik-local/reviews/, run manually) is available as an escalation when you want stronger isolation than one session gives; and emerik exposes sixty-two MCP tools, comfortably under Cursor's historically undocumented per-server tool cap. Leases, hooks and harvest are plain git, so a Cursor agent and a Claude Code agent on the same repo coordinate through the same leases and the same harvest hooks.

The harvest hook resolves emerik robustly: it prefers a global emerik on your PATH, and otherwise falls back to the absolute CLI path baked in at init time — so a commit auto-harvests even when emerik is not installed globally. The hook swallows its own output and always exits 0, so it never blocks a commit or push. If emerik cannot be resolved at all — no global install and no reachable fallback (a moved clone or a deleted build) — the hook prints one stderr line (emerik: harvest skipped — emerik not found …) so a missed harvest is visible rather than silent; it still exits 0 and never blocks the commit.

Harvest Pins

emerik harvest reads a commit, extracts source-bound Pins, and re-derives any Pin whose source changed. With the post-commit hook installed it runs automatically on every commit; you can also invoke it directly.

$ emerik harvest
{"level":"info","message":"harvest: committed run","harvested":1,"rederived":0,"invalidated":0}
 harvested 1 Pin

Freshness — the honest part

Every Pin is bound to the bytes it came from. On the next harvest:

  • Source changed → the Pin is re-derived; the stale predecessor is superseded.
  • Source deleted / became binary → the Pin is invalidated with a tombstone.
  • Nothing changed → no-op (re-harvest is idempotent).

This is why a Pin never silently drifts out of sync with the code it describes.

Harvest never pins emerik's own scaffolding: the .emerik/ store, the whole .claude/ tree, and the root-level .mcp.json, AGENTS.md and CLAUDE.md that init wrote are excluded — so a git add -A right after init mints Pins only for your real source, never for the wiring outputs. A nested CLAUDE.md (your own knowledge file) is still harvested.

Capturing human review — --review

Pull-request comments often contain lessons that should outlive the review. emerik harvest --review <file> records them as Pins. emerik does not access the network. Your agent reads the review, writes a handshake file, and passes that file to emerik. Review Pins still enter only through harvest; there is no free-form Pin command.

The handshake file is plain JSON. You supply the lesson and the region it is about; emerik computes every hash itself:

{
  "formatVersion": 1,
  "entries": [
    {
      "content": "Bound the fan-out — an unbounded worker pool melts CI on big repos.",
      "source": {
        "prNumber": 42,
        "commentUrl": "https://github.com/acme/widget/pull/42#discussion_r1234567",
        "reviewer": "octocat",
        "commentedAt": "2026-07-18T09:14:02Z"
      },
      "sourceBinding": { "path": "src/pool.ts", "region": "L2-L3" }
    }
  ]
}
$ emerik harvest --review .emerik-review/pr-42.json
 minted 1 review Pin

The producer must not supply contentHash, repo, or any unknown key. emerik computes the binding from the committed tree. Each Pin records { origin: "review", synthesis: "human" } as production provenance, not a trust score. Its attribution names the pull request, reviewer, comment date, and URL. Each deposit creates its own emerik(pin): create <id> commit.

What can go wrong, and what happens

  • The file is missingHARVEST_REVIEW_FILE_MISSING, exit 1.
  • The file is malformed — bad JSON, a wrong formatVersion, or any structurally invalid entry → HARVEST_REVIEW_HANDSHAKE_INVALID, exit 1, and nothing is minted. A producer bug is fixed at the producer, never half-applied.
  • One entry's region no longer resolves — the path is gone at HEAD, the region is unparseable or past end-of-file, or the file is binary → that entry alone fails with HARVEST_REVIEW_SOURCE_UNRESOLVED; every other valid entry still mints and the run exits 0. The per-entry report says so explicitly. A lesson that is not about a surviving region of code is never force-bound to a fake one.
  • The same handshake is deposited twice → the second run reports the entries as already pinned and writes nothing. “The same” means the same comment on the same region: two different reviewers commenting on the same lines are two lessons, and both are kept.

Review Pins keep the same freshness contract as every other Pin, judged against their own region: an edit that leaves the bound lines untouched leaves the Pin alone, and an edit that moves or rewrites them invalidates it with a tombstone. Because a human wrote the words, emerik cannot re-derive a successor — so it retires the Pin rather than silently presenting stale advice, and a machine-extracted Pin never supersedes a reviewer's lesson.

Working a review with your agent — emerik-address-review

The handshake file above is the channel. The shipped emerik-address-review skill is what drives it: it reads the review threads, addresses each comment on the branch, and triages what each one taught into the substrate — per pull request, while the context is still hot. emerik itself still never touches the network; the skill reads GitHub with your agent's own gh CLI, so the network stays on your side of the boundary.

It runs three entry checks first, each with a stated stop — an initialized store (emerik index status), an authenticated gh, and a pull request that actually resolves — and then, per actionable comment, walks one fixed order:

  1. Retrieve before acting — ask the substrate what it already knows about this comment and the paths it touches (prior review Pins, governing Anchors, antibodies) before writing a line.
  2. Implement the fix on the branch. A comment that is really a Goal-sized change is named as such and handed to the ordinary engineering loop, which claims its territory under a Lease — it is never silently absorbed into “addressing feedback”.
  3. Commit the fix — the post-commit hook harvests the code Pins. This ordering is load-bearing, not stylistic: a review binding hashes committed source at HEAD, so a deposit made before the fix is committed would bind the lesson to the pre-fix bytes.
  4. Triage, then deposit — write the handshake file, run emerik harvest --review …, and read the per-entry report.

Triage has exactly four dispositions, judged per comment:

DispositionWhat lands
No-residue nitNothing. The fix is the whole outcome — most review comments are honestly one-offs.
Durable lesson bound to sourceOne handshake entry → a review-origin Pin on the settled region.
Generalizable ruleAn Anchor candidate: emerik anchor author --from-pin … --check-kind … --check-ref … without --approve, which is a no-write preview. A promotion always carries a check — that is what makes it a contract rather than a note — and the review gate is untouched: the skill never approves on its own initiative.
“Never again”An antibody candidate: emerik antibody mint …. A block antibody still needs a human bless before it binds; the skill mints and stops.

When retrieval turns up a prior review Pin teaching the same lesson from an earlier pull request, that is recurrence — and recurrence is the promotion signal, never a duplicate. The new entry is still deposited (two lessons on one region are two Pins by design), and the lesson is surfaced as a promotion candidate carrying its observed count. Do not confuse that with already pinned, which is what re-running the skill on the same pull request reports: an honest no-op.

The run ends in a per-thread resolution table — one row per thread, with its artifact ids:

ThreadDispositionWhat was doneArtifacts
#42 · pool.ts:118addressedbounded the worker pool; committed
#42 · pool.ts:118captureddurable lesson deposited on the fixed regionPin 01J…
#42 · retry.ts:64promoted-candidaterecurrence (taught in 3 reviews) — Anchor preview shown, awaiting confirmationpreview only
#42 · legacy.ts:9skippedsource rewritten past recognition; thread unresolvable

…followed, always, by what the run did not process: threads beyond the stated fetch bound, threads whose source could not be resolved, and non-actionable bot noise, each named and counted. There are no silent caps. And nothing goes back out without being asked for: a reply on a review thread or a push of the addressed branch happens only on an explicit, per-action confirmation in the session.

Anchors & verify

An Anchor is a reviewed contract with a machine-checkable assertion. You choose a subtype, an enforcement level, and a check.

FlagOptionsMeaning
--subtypecontract · constraint · steward · antibodyWhat kind of contract this is.
--enforcementblock warn advisoryWhat happens when the check fails.
--check-kindgrep · test · lint · fitness · agentHow the Anchor is checked.
--check-ref<pattern | command>The concrete target — a grep pattern, a test, a shell command.
--steward<who>Who owns this Anchor (required — an honesty label).
--governs<path glob>The code region this Anchor stewards (repeatable; inverted CODEOWNERS, resolved by emerik steward resolve).
--repo<workspace member>Scope the contract to a workspace member repoemerik verify runs its check inside that repo. Absent, it defaults to the member you are standing in, exactly like emerik seed; to land a workspace-level (unqualified) Anchor, run it from the workspace root.

Author a block-level constraint guarded by a grep pattern:

emerik anchor author \
  --content "Auth token TTL must remain 3600s to match the gateway." \
  --subtype constraint --enforcement block --steward you \
  --check-kind grep --check-ref "TOKEN_TTL_SECONDS = 3600"

List what exists, and run the gate:

emerik anchor list        # inventory of Anchors + lineage (a bounded page)
emerik verify             # check every Anchor + convention lint

anchor list returns 50 entries by default, keeping large repos from flooding a terminal or an agent's context. Filter with --subtype, --enforcement, --steward, or --governs-path. Page with --limit and the printed --cursor; use --id <ulid> for one Anchor's full text.

Truncation is always explicit. Plain text tells you how to continue, while --json returns total and nextCursor. An --id is resolved inside any other filters, so use the id alone when you simply want that record.

Enforcement levels

block fails emerik verify with exit code 1 — a hard CI gate. warn reports but does not fail. advisory is informational only.

emerik verify also reads the initiative ledger and prints one advisory line per initiative that has been idle past its TTL, pointing at emerik initiative sweep. It is strictly a read: verify never mints a flag and never changes its exit code because of it — a stale initiative belonging to someone else must not fail a stranger's CI. --quiet suppresses the line, and a repo whose store holds no initiatives prints nothing extra.

It also patrols the skills membrane: every skill installed under .claude/skills/ or .agents/skills/ is re-hashed from its own bytes and matched against an admitted record — the shipped baseline for a shipped skill, the repo's admission record for a team one. Unlike the initiative advisory this one is blocking: an unregistered skill, a tampered mechanics region or a drifted supporting file exits 1, because an out-of-band skill is a live instruction stream to every agent in the repository. The patrol runs only where a .emerik/ store resolves, and a repo with no skill directory prints nothing extra.

Incremental verify — the gate pays for what changed

emerik verify keeps a last-verified ledger of derived state in .emerik-local/verify/ — gitignored, never committed, reconstructible by simply running verify again. A check whose inputs have not moved since the last passing run resolves straight from that ledger instead of spawning a subprocess, and the convention lint reuses the verdict of every store file whose bytes are unchanged rather than re-parsing and re-validating it. The checks that do run execute in parallel with a bounded worker pool. The reported order, the enforcement badges, the exit code and the --json shape are unchanged — only redundant re-execution is skipped.

Every run says what it skipped and why. A green incremental run prints a line like ✓ 12 check(s) resolved from the last-verified ledger · 3 re-run, and the lint's scanned summary gains (3 re-parsed, 41 unchanged); when a complete pass happens instead, verify names the cause — note: complete verify pass — no ledger. --quiet suppresses that success chatter, never a violation, a failing check, or the reason a complete pass ran.

What invalidates a check — any one of these makes it re-run:

  • the Anchor was superseded (a new artifact carries a new ULID);
  • its --check-ref or --check-kind changed;
  • any content change in the repository the check runs against — committed, staged, unstaged, or a brand-new untracked file. The ledger keys each check root on its HEAD tree plus a digest of every dirty path, so the invalidation is deliberately conservative: a grep judges the working tree and a shell check can read anything in it, so both are invalidated by any of it. A workspace member sitting in that tree as a submodule folds in its own state the same way, so moving a member's checkout — or editing a file inside it — invalidates the checks above it even before the bump is committed. Gitignored paths (including .emerik-local/ itself) never count;
  • the check did not pass. Only passing checks are ever banked — a failing or erroring check re-executes on every run, so the gate can only turn green through real execution and a cached failure can never mask a fix. A partially-red run still banks its green checks, so the next run re-runs exactly the red set.
The honest limit — and the escape hatch

The ledger keys on repository content. A check whose verdict depends on something outside the repo — installed packages, a tool version, an environment variable, a network service a badly-written check reaches — is not tracked by that key, so such a check can resolve from the ledger even though the world moved underneath it. Run emerik verify --full to force the complete pass: every check executes and every artifact is re-parsed, ignoring the ledger entirely (it is still rewritten afterwards). The same complete pass happens automatically whenever the ledger is missing, unreadable, or was written by a newer emerik — the ledger is an optimization, never a correctness dependency.

Promote a Pin into an Anchor

A load-bearing Pin can be promoted through a review gate. Without --approve it is a no-write preview; with it, a reviewed Anchor is minted carrying promotedFrom lineage.

emerik anchor author --from-pin <pin-ulid> --subtype contract \
  --enforcement block --steward you \
  --check-kind grep --check-ref "requireAuth" --approve

Practice documents

An Anchor states an obligation and a check enforces it. But some knowledge does not fit in one line and cannot be checked by a grep — how we write tests here, how we shape API errors, why this service never retries. That knowledge normally lives in a senior engineer’s head and gets re-explained in every code review. An explainer is where it lives instead: a real document in your repo covering exactly one practice, plus a record in the substrate that points at it.

A practice document is normative

This inverts what you expect a document to be, so it is worth stating plainly. An explainer does not describe what the code does. It states how work is done here. So when the code and the document disagree, the code is the defect — not evidence the document has gone stale. Only a change of intent stales a practice document; code drift never does. That leaves exactly two honest moves on a disagreement: fix the code, or — if the intent itself has changed — amend the document and re-declare it. “Update the doc to match the code” is never one of them. Nothing in emerik treats a document that differs from the code as a problem: there is no staleness detector, no mtime heuristic, no code-versus-doc comparison, and emerik verify never mentions explainers at all.

One document, one practice. If it needs two titles, it is two documents. That bound is what makes an explainer worth retrieving whole rather than in fragments — and it is a rule you keep, not one emerik enforces: there is no mechanical splitter, and there should not be. The convention is docs/practices/<slug>.md, but any committed repo-relative path works, so emerik never fights an existing docs layout.

Write the document first, then declare it. Declaring reads the file, hashes it, and records the pointer — it never writes prose.

Terminal
$ emerik explainer declare --path docs/practices/testing.md \
    --title "How we write tests here" \
    --description "Test doubles, fixtures, and what we never mock."
 declared explainer 01KWH64…
  How we write tests here
  doc docs/practices/testing.md
  Test doubles, fixtures, and what we never mock.
  this document is NORMATIVE: it states how work is done here.
  code that disagrees with it is the defect — not evidence the document has gone stale.
  only a change of intent stales it; code drift never does.
exit 0

The record carries the title, the path, a short description and a hash of the document’s bytes. The file is authoritative — if the record and the document ever disagree, the document wins. Git history is the document’s history; the record’s own lineage tracks nothing but the pointer.

When the document moves on

Edit the document and the hash on the record no longer matches. That is reported, once, as a plain note — never a warning, never a failure, and always exit 0:

Terminal
$ vim docs/practices/testing.md          # the practice changes

$ emerik explainer list
explainers: 1 of 1 practice document
  How we write tests here  · doc docs/practices/testing.md  · id 01KWH64…
    note: the document has changed since it was declared — the file wins; re-declare to catch the record up
  each one is normative — read it at its path; the file is the source of truth.
exit 0

All that note means is that the local search index is behind. Re-declaring the same path supersedes the old record and catches the pointer up — that is the whole repair, and it is the only thing that ever re-embeds a document.

Two other notes can appear on the same line, and neither is a failure either: a document that is not on disk here (moved, or living in another repo), and a record whose path cannot be read from this repo at all — one that points outside the tree it claims to describe. The second one is named rather than followed: nothing reads that path and nothing indexes it, and the record is still listed so you can see it and re-declare the document from inside the repo.

Anchors give a practice teeth

Prose alone cannot be enforced, and it is not supposed to be — the division of labour is exact: the prose carries the mechanism, the Anchors carry the obligation. What makes a practice document more than a wiki page is its member Anchors — the enforceable obligations that carry the practice, named on the record when you declare it. Membership is declared on the explainer; the Anchor never records that it belongs to anything. That direction has three consequences worth knowing: an Anchor can belong to two practices, an Anchor can belong to none, and a standalone Anchor is not second-class — there is no field on it to make it one.

Write the anchors first, on the same branch as the prose, then name them at the finish. One pull request then carries the document and its obligations together, and one reviewer sees both. That is a commit order, not a thinking order: the Anchor artifacts have to exist before the declare can name them, but the practice itself is understood as prose first and its obligations are distilled out of that understanding — which is exactly why membership is declared on the document:

Terminal
$ emerik anchor author --content "Tests use real fixtures; never mock the store" \
    --enforcement block --steward platform-team \
    --check-kind grep --check-ref "openStore" --governs "src/**/*.test.ts"
 authored anchor 01KZY8A…

$ emerik anchor author --content "No test asserts on log output" \
    --enforcement warn --steward platform-team \
    --check-kind grep --check-ref "expect(logs" --governs "src/**/*.test.ts"
 authored anchor 01KZY8B…

$ emerik explainer declare --path docs/practices/testing.md \
    --title "How we write tests here" \
    --description "Test doubles, fixtures, and what we never mock." \
    --anchor 01KZY8A… --anchor 01KZY8B…
 declared explainer 01KWH64…
  How we write tests here
  doc docs/practices/testing.md
  Test doubles, fixtures, and what we never mock.
  2 member anchors — 01KZY8A…, 01KZY8B…
  the gate is `emerik verify` failing on these anchors at their own declared level —
  so the only two ways back to green are fix the code or amend the practice.
  …
exit 0

Every id must name a live Anchor at the moment you declare. A dead one is refused, and the refusal names the live successor to use instead — so the fix is a copy-paste out of the error. A rejected Anchor is dropped and re-authored; if the whole branch is rejected, its Anchors and its record die with it, because both are ordinary committed branch artifacts.

The gate costs nothing new

There is no explainer phase in emerik verify — not here, and not by design anywhere. A member Anchor is an ordinary Anchor, so it is checked at its own declared level by machinery that predates practice documents entirely. That is the whole enforcement story: the gate is verify failing on the practice’s Anchors, so the only two ways back to green are fix the code or amend the practice. The gate therefore reaches exactly as far as the Anchors do. Prose with no Anchor behind it is unenforceable by construction — that is a boundary worth knowing, not a defect to report, and emerik states it plainly rather than pretending otherwise.

Where a practice lives: the derived region

emerik explainer show reports the practice’s region — where it applies. The region is derived from the member Anchors’ own governed paths and never declared, so it cannot drift from the obligations it summarises. It reads as one of three facts:

  • a path list — the deduplicated union of the members’ governed globs.
  • repo-wide — a member narrows nothing, so the practice binds the whole repo. How we handle errors lands here, correctly: a broad region is honest, not degenerate. In a workspace the line names which repos bind, and any territory the practice’s other live members govern outside them — binding one repo wholly never hides an obligation in another.
  • none — no live member Anchor. The prose is real; nothing enforces it yet.

The region is an instrument, never a gate. Nothing routes enforcement through it — emerik verify never consults a governed region when deciding whether a check runs — so breadth costs a region its usefulness for narrowing, and never costs an Anchor its teeth.

When a member Anchor dies: known-wrong

Practices change. When one of a document’s Anchors is superseded through the normal review-gated path — or retracted — the document is known-wrong until someone updates it: the child’s death is an event the parent has to answer for. It is derived from the substrate’s own supersession record, not from anyone’s attestation, so nobody can talk a document back into looking current. It shows up on every surface that renders the explainer, and — like every other note here — it is information at exit 0, never a failure:

Terminal
$ emerik explainer show 01KWH64…
explainer 01KWH64…
  How we write tests here
  Test doubles, fixtures, and what we never mock.
  doc docs/practices/testing.md
  read it at /repo/docs/practices/testing.md
  declared 2026-08-14T09:12:03.117Z  · origin seed
  2 member anchors
    ⛔ BLOCK  01KZY8A…  · grep: openStore  · steward platform-team  · superseded → now 01KZYC2…
    ⚠ WARN  01KZY8B…  · grep: expect(logs  · steward platform-team
  region: src/**/*.test.ts
  the gate is `emerik verify` failing on these anchors at their own declared level —
  so the only two ways back to green are fix the code or amend the practice.
  known-wrong: 1 member anchor is no longer live — 01KZY8A… (superseded → now 01KZYC2…); re-declare this explainer with the live anchor ids to clear the marking
  this document is NORMATIVE: it states how work is done here.
  …
exit 0

The repair is the verb you already know. Read the document against the successor Anchor, edit the prose if the intent really moved, and re-declare with the current live ids — there is no separate “relink” command:

Terminal
$ emerik explainer declare --path docs/practices/testing.md \
    --title "How we write tests here" \
    --description "Updated after the fixtures rework." \
    --anchor 01KZYC2… --anchor 01KZY8B…
 declared explainer 01KWH99…
  …
  re-declared — supersedes 01KWH64… (the pointer caught up; the document's own history is git's)
exit 0

The marking clears because the derivation now finds every member live — nothing was flipped or mutated. Note also that known-wrong and the document has changed note are separate, independent facts, and render as separate lines when both apply: one is about a member Anchor dying, the other about the file moving ahead of its record.

When your intent contradicts an Anchor: the warn, and the override that amends

The other direction is the interesting one. A practice does not only go wrong when an Anchor dies underneath it — it goes wrong when your intent changes and nobody updates the document. That is the only kind of staleness this design admits, and it is exactly the kind that used to depend on somebody remembering. So emerik makes the remembering unnecessary: when you are about to act against an anchored obligation that a practice document explains, it names the conflict — which document, which clause, at what enforcement level — and then gets out of the way.

It fires on anchored obligations only — never on prose disagreement

The threshold here is structural, not a tuned heuristic: emerik explainer conflict takes an Anchor id. A disagreement with a document’s prose has no id, so it cannot be expressed through this instrument at all. There is no matcher, no similarity score and no prompt interception anywhere behind it — nothing that could interrupt every third thing you do and get itself ignored. Your agent is the one that notices the contradiction; emerik is the one that names it precisely. If the prose is what is wrong, do not reach for this: edit the document and re-declare it.

Terminal
$ emerik explainer conflict --anchor 01KZYWT… \
    --intent "stop requiring auth on the /health endpoint"
 conflict — 01KZYWT… is a live obligation that 1 practice document explains
  intent: stop requiring auth on the /health endpoint
  ⛔ BLOCK  01KZYWT…  · grep: requireAuth  · steward platform-team
  clause: “every entry point requires auth”
  explained by:
    How we write tests here  · doc docs/practices/testing.md  · id 01KZYWTS…
  emerik does not refuse and does not block — the choice is yours. This is a WARN: named, surfaced, and out of your way.
  if your INTENT has changed, the override IS the amendment — no separate step to remember:
    1. edit docs/practices/testing.md to say what you now intend. emerik never writes your prose: the amendment’s
       author is this conversation, and git history is its lineage.
    2. re-declare the record so the pointer catches up:
       `emerik explainer declare --path <doc> --title <…> --description <…> --anchor <ids>`
       REPEAT the membership — omitting --anchor while a member anchor is still live is refused,
       so an override can never quietly disarm the practice.
    that amends the EXPLANATION only.
  if the OBLIGATION itself is what has to change, that is the review-gated path — not this one:
    `emerik anchor author --supersedes 01KZYWT… …` — an anchor with dependents routes
    to `emerik review open` for the human gate.
    until it is superseded, this anchor STILL BINDS and `emerik verify` still enforces it at ⛔ BLOCK.
  this document is NORMATIVE: it states how work is done here.
  …
exit 0

Read the exit code. The warn is a warn: it is not an error, it does not fail, and it does not stand between you and your work. Nothing about this surface can block — it writes nothing to the store either, so calling it costs you a line of output and nothing else.

The override is the amendment

Now the part that closes the loop. If you push through the warning, that act means the practice has changed — so there is deliberately no separate “remember to update the doc” step to forget, and no emerik explainer override verb to learn. You edit the document (emerik never writes your prose; the amendment is yours) and you re-declare it with the verb you already use for everything else. The re-declare is the amendment:

Terminal
$ $EDITOR docs/practices/testing.md      # you write the amendment
$ emerik explainer declare --path docs/practices/testing.md \
    --title "How we write tests here" \
    --description "Amended: the health endpoint is exempt." \
    --anchor 01KZYWT…
 declared explainer 01KZYX2…
  re-declared — supersedes 01KZYWTS… (the pointer caught up; the document's own history is git's)
exit 0

Note that the membership is repeated. That refusal — the next section walks it in full — is this flow’s guard rail: an override cannot quietly disarm the practice on its way through, because dropping --anchor is refused while a member is still live.

An override amends the explanation — never the gate

This is the asymmetry that keeps the override honest. Re-declaring changes the document and nothing else: the Anchor still binds, and emerik verify still fails on it at its declared level. You cannot talk past a check by rewriting the prose about it. Changing an obligation is the other path, and it is review-gated: emerik anchor author --supersedes, which routes an Anchor with dependents to emerik review open for a human. Prose moves at the speed of conversation; obligations move at the speed of review.

And when the obligation really does move, the loop closes itself with no new machinery. The successor Anchor lands through review, the explainer goes known-wrong because its member died, and the conflict surface stops warning on the old id — it answers with the successor instead, because your conflict may already have been resolved by somebody else:

Terminal
$ emerik explainer conflict --anchor 01KZYWT…
· no explainer conflict — 01KZYWT… is no longer live (superseded)
  ⛔ BLOCK  01KZYWT…  · grep: requireAuth  · steward platform-team
  clause: “every entry point requires auth”
  the obligation moved: 01KZYWV3… carries it now.
    `emerik explainer conflict --anchor 01KZYWV3…` — re-check your intent against the successor.
  your conflict may already be resolved: this id is not what binds any more.
  1 practice document still lists this id as a member, so it is known-wrong until reworked:
    docs/practices/testing.md
  rework it wholesale and re-declare with the live membership — that clears the marking.
  …
exit 0

Re-declare the explainer with the successor id and the known-wrong marking clears — the same verb, a third time. That is the whole loop: warn → override → the document amended → the obligation superseded through review → known-wrong → re-declare → clear, and not one step of it is a step you have to remember.

Which one fires when

There are two enforcement moments in emerik and they never duplicate each other, because they judge different things at different times:

The prompt-time warnThe verify-time gate
JudgesYour intent, before the workYour code, as it stands
FiresWhen your agent pulls it — it noticed a contradiction and askedOn every emerik verify, pushed by the Anchor’s own check
AnswerNames the document and the clause. Always exit 0Passes or fails at the Anchor’s declared level. Exit 1 on a blocking failure
RepairAmend the practice — or proceed; the choice is yoursFix the code — or supersede the Anchor through review

The warn runs no checks and the gate reads no intent. That separation is why the warn can afford to be advisory: it is not the thing standing guard, and it never has to be.

Repeat the membership, or drop it on purpose

Re-declaring is the general update verb — you use it to fix a description, to catch a record up after an edit, and to repair membership — so forgetting --anchor is an easy slip, and a slip must never be able to quietly disarm a practice. It cannot: emerik refuses a re-declare that omits the Anchors while any member is still live, writes nothing, and names the ids to repeat — every live member, plus the live successor of any member that has since died, so one copy-paste both keeps the membership and clears a known-wrong marking:

Terminal
$ emerik explainer declare --path docs/practices/testing.md \
    --title "How we write tests here" \
    --description "Updated after the fixtures rework."
 re-declaring docs/practices/testing.md without anchors would drop membership that still binds:
  1 member anchor is live on the record it supersedes (01KWH64…). Repeat the membership you intend —
  `--anchor <id>` (repeatable), MCP `anchors` — the current live ids are 01KZYC2…, 01KZY8B…;
  or pass `--detach-anchors` (MCP `detachAnchors: true`) to remove membership deliberately,
  which leaves the prose unenforceable. Nothing was written.
exit 1

Removing membership is a legitimate act — the Anchors were retired, the obligation moved elsewhere — but it has to be said: --detach-anchors files a successor record with no members, and the record then reports region: none and drops the gate sentence, because the prose really has stopped being enforceable. Membership is never inherited silently either: one spelling cannot mean “no members” on a first declare and “carry the previous ones forward” on a re-declare, or a record’s teeth would stop being readable from the command that made it. Omitting the Anchors is allowed once none of them is live — at that point there is nothing left to lose.

Cite the clause that authorized your work

A practice document only earns its keep if people actually lean on it — and the honest way to know is to record use, not to ask. emerik explainer cite records which clause authorized what you did: the sentence you leaned on, and the act it authorized. That is a citation, not an attestation. “I read the document” is unfalsifiable and worth nothing — it is entirely possible to skim a practice and confidently do the wrong thing while attesting perfectly. A citation names something a reviewer can check.

And it is checkable literally, because the citation is committed on the branch you are working on: it rides the same pull request as the code, so the clause and the diff sit side by side and a reviewer reads them together. If the diff contradicts the citation, the diff wins — the citation is evidence, never a claim about anyone’s diligence.

Terminal
$ emerik explainer cite 01KZZ74… \
    --clause "Never mock what you own." \
    --act "Replaced the mocked repository in checkout.test.ts with the real one over a tmpdir." \
    --moment implementation
 cited explainer 01KZZ74C37MGM1Y6R8X8H4S4WH
  How we write tests here  · doc docs/practices/testing.md
  clause "Never mock what you own."
  authorized Replaced the mocked repository in checkout.test.ts with the real one over a tmpdir.
  moment implementation  · citation 01KZZ74CNA5V3Q0Q6R7GN5SQW6
  this citation is committed on this branch, so it rides the same pull request as the work —
  which is what makes it checkable against the diff rather than a claim of having read the document.
  paste this into the pull-request body:
    Cited practice: How we write tests here — "Never mock what you own." — authorized: Replaced the
    mocked repository in checkout.test.ts with the real one over a tmpdir. (emerik citation 01KZZ74C…)
exit 0

A citation lands in three places. The committed artifact is the machine record. --goal <id> appends one line to that Goal’s progress notes, naming the practice, the clause and the act. And the prBlock line above is the third: emerik performs no network I/O and cannot write a pull-request body, so it hands you a paste-ready block for the one place a human actually sees the citation while deciding whether to approve. --goal is validated before anything is written, so a bad Goal id refuses with nothing on disk rather than leaving a citation whose note never landed.

--anchor is optional and is for the case where the clause is an anchored obligation of that same document; it is validated as a live member, so a mismatched pairing is refused. Most clauses are prose, and an anchorless citation is entirely normal — that is the coverage boundary stated honestly, not a gap.

Heat for prose: which practices are load-bearing

Citations accumulate, and every read surface renders them as heat — the same thermal number Pins carry, computed with the same formula. Which practices are load-bearing stops being a matter of opinion and becomes a number you can look at:

Terminal
$ emerik explainer show 01KZZ74…
explainer 01KZZ74C37MGM1Y6R8X8H4S4WH
  How we write tests here
  Test doubles, fixtures, and what we never mock.
  doc docs/practices/testing.md
  read it at /work/repo/docs/practices/testing.md
  declared 2026-08-14T04:05:24.181Z  · origin seed
  0 member anchors
  region: none — no member anchors, so nothing enforces this practice yet (the gate reaches exactly as far as the anchors do)
  heat ▃ 0.51  · cited 1 time, last on 2026-08-14T04:05:24.760Z
  cited at: 0 authoring · 1 implementation · 0 retro
  this document is NORMATIVE: it states how work is done here.
  code that disagrees with it is the defect — not evidence the document has gone stale.
  only a change of intent stales it; code drift never does.
exit 0

Prose heat diverges from Pin heat in exactly one deliberate way, and it is the important one: the signal is the citation, never the retrieval touch. A Pin warms when retrieval surfaces it — a mechanical measure of how useful retrieval found it. Warming a practice document the same way would measure the precise thing this feature refuses to reward: that an agent skimmed it. So explainer search, show and list warm nothing. Only a committed citation does.

Nothing is stored for any of this. The record stays a .strict() pointer with no citation field on it; the count, the last-cited instant, the moments and the heat are all derived on every read from the citations/ directory and the record’s own supersession chain — the same way knownWrong and region are derived, and un-fakeable for the same reason. Heat aggregates across that whole chain, so reworking a document wholesale does not zero its history and make a freshly repaired practice look dead.

Heat is an instrument, never a gate. It is not blended into explainer search ranking — hits stay ordered by similarity, with and without citations. It never excludes a document from the retrieval pool, never freezes anything, and never touches an exit code. A quietly-cited practice is exactly as findable as a heavily-cited one.

The measurement: does this feature earn its keep?

The cited at: line is not incidental instrumentation — it is designed measurement, and it is how this feature answers for itself. Every citation records a --moment: authoring (you were writing or revising the practice), implementation (you were doing the work), or retro (you were looking back).

Read the split off byMoment after a quarter. Citations clustering at authoring and retro confirm the first argument for practice documents: they earn their keep as something the team writes down and returns to. Citations clustering at implementation mean the second argument is real too: agents reach for them mid-work, while the code is being written. Both are good answers, and they are different answers — which is why the axis is recorded rather than inferred. A team that finds neither cluster has learned something worth knowing, from its own repository, in one line.

The pre-flight read: practices reach the work before it starts

A practice document found after the work went wrong is a post-mortem exhibit. So the reading flow is routed, not hoped for: the emerik-consult skill is the pre-flight read, and the scoping, Goal, planning and implementation skills invoke it at entry — the practices that govern the work are surfaced before the first line is written, at the only moment they can still change the outcome.

The walk is deliberately small. Ground the index, establish what the work is about to touch, and narrow the practice documents to that footprint — emerik explainer list --paths for a footprint of paths, explainer_search for a “how do we do X here” question — then read the few relevant documents whole: hits are pointers at real files, never a copy of the prose, and one document covers one practice, which is what makes whole-document reading affordable (at most three by default, and the walk says so when more matched). A known-wrong practice is surfaced first. The region only ever narrows relevance, never grants it — an anchorless practice (region none) is never hidden by the filter, because the gate’s coverage boundary is not a relevance boundary. And the whole walk warns and never blocks: every surfaced fact is information the agent carries into the work — nothing stops, nothing gates, nothing asks permission to continue.

The coverage boundary, stated honestly. Nothing intercepts prompts — there is no hook, no daemon and no per-turn machinery, by design. The pre-flight read is push-shaped in effect — the routed flows invoke it at entry, so it fires without the agent choosing — and pull machinery in fact: skill prose reading shipped tools, nothing more. So it reaches exactly as far as the routed flows do, and work entered outside any emerik flow gets no pre-flight read. That is a documented boundary, not a defect — the same symmetry as the enforcement gate, which reaches exactly as far as the Anchors do.

The pre-flight read also answers for itself with the instrument above: implementation-moment citations are its route-correctness figure — the honest, after-the-fact measure of whether the practices it surfaced were actually load-bearing while the code was being written, read off byMoment exactly as the quarterly split is. The baseline at ship is zero by construction — before this skill existed, nothing routed an agent to the documents or to explainer cite at the work moment — so the reading is directional: implementation citations appearing at all proves the read is happening, and their absence after a quarter is the honest signal to shrink the reading flow, not to defend it.

A stale practice is removed, not flagged

A practice nobody has cited in a long time surfaces on show and list as a deletion candidate. The threshold is not a new setting: with the shipped heat constants, a never-cited document crosses the cold line about 60 days after it is declared, and any citation resets the clock. That line is a consequence of the existing arithmetic, not a number someone picked.

Cold means unused, not wrong. Nothing here judges the document’s contents — emerik has no staleness detector, no mtime heuristic and no code-versus-document comparison anywhere, and it never will. A quiet practice may be perfectly correct and simply cover ground the team has not touched. The candidate line names the state and the verb, and that is the whole of what it does: the document stays fully in the retrieval pool, at full rank, until a human decides otherwise.

When a practice really has stopped holding, the answer is emerik explainer remove — a real removal, not a warning label. This is deliberate: a warned document is still in the retrieval pool. It still comes back from search, it still shapes work, and the warning does nothing to stop it — so a “stale” marker would leave the document lying to agents with a note attached. Explicit closure beats silent decay.

Terminal
$ emerik explainer remove 01KZZ74… --reason "folded into the testing guide"
 removed explainer 01KZZ74C37MGM1Y6R8X8H4S4WH
  How we write tests here  · doc docs/practices/testing.md
  reason folded into the testing guide
  tombstone 01KZZ74P95ESDTYZM7Y93TVY9C — the record leaves the live set, append-only
  the next `emerik index rebuild` (or `refresh`) drops this document from the retrieval pool.
  emerik does not delete your document — delete the file yourself in this same change,
  so the record and the prose leave together and no orphan file keeps answering questions:
    git rm docs/practices/testing.md
exit 0

The removal is append-only: a tombstone, so the history survives. What does not happen is emerik touching your prose — it has never written, edited, moved or deleted a document and does not start here, which is why the render tells you to delete the file yourself in the same change. A record removed while its file stays behind leaves an orphan document that goes on answering questions. Historical citations of a removed practice stay in the store, inert: append-only means the audit trail outlives its subject.

CommandWhat it does
emerik explainer declareRecord a practice document that already exists in the repo, optionally naming its member Anchors with a repeatable --anchor; re-declaring the same path supersedes the previous record and is also how membership is updated. Repeat --anchor on a re-declare — omitting it while a member is live is refused, and --detach-anchors is the deliberate removal.
emerik explainer listList the live practice documents with their citation heat, noting any whose file has changed or gone missing, marking any that are known-wrong, and naming any that have gone cold as a deletion candidate; a bounded page (--limit / --cursor / --id, FR-39). --paths <path> (repeatable) narrows the page to the practices whose derived region covers what you are touching — a practice with region none always passes, because the region narrows relevance and never grants it.
emerik explainer show <id>Show one record, its member Anchors, its derived region and any known-wrong marking, its citation heat and the byMoment split, and where to read the document. It never prints the document itself — the file is the one copy. --json additionally carries the five most recent citations.
emerik explainer conflictName a conflict between what you intend and an anchored obligation a practice document explains (--anchor, required and single; --intent is echoed back verbatim). It warns, names the document and quotes the clause, and never refuses or blocks — always exit 0, and it writes nothing. Anchored obligations only: prose has no id, so prose disagreement cannot reach it.
emerik explainer cite <id>Record which clause of a practice document authorized what you did — --clause, --act and --moment (authoring | implementation | retro) are required; --anchor names the member Anchor when the clause is an anchored obligation, and --goal appends one progress note. A citation, never an attestation: it is committed on your branch, so it rides the same pull request as the work and a reviewer can check it against the diff. The output carries a paste-ready block for the PR body.
emerik explainer remove <id>Retire a practice document’s record with a tombstone (--reason records why). A stale practice is removed, not flagged — a warned document is still in the retrieval pool. The next index refresh drops the document from the pool; emerik never deletes your file, so delete it yourself in the same change.

Finding the right practice

Practice documents are indexed in the same global search index as Pins and Anchors, as the document’s whole text in one unit — affordable precisely because one explainer covers one practice. There is no separate explainer index: it is the one table, narrowed by kind. It lives under .emerik-local/, so document vectors are never committed and a fresh clone rebuilds them from the committed documents alone.

The reader this is built for is your agent, so search is agent-first: the explainer_search MCP tool answers “how do we do X here”, and there is deliberately no CLI search command — humans have emerik explainer list and the documents themselves. Search returns pointers at real files, never a copy of the prose — so there is exactly one version of the practice, and the agent reads it where you would.

Explainers and Anchors are both retrievable

A practice usually has both: prose that carries the reasoning, and Anchors that carry the enforceable obligation. The overlap is deliberate — an agent that finds the Anchor learns the rule, and an agent that finds the explainer learns why it exists and what to do at the edges.

The honesty loop

This is the whole promise in one worked example: a green check must go red the moment the invariant it guards is broken. Start from the constraint Anchor above.

Terminal
$ emerik verify                          # invariant holds
 conventions OK — no violations
✓ BLOCK  constraint 01KWH64…  grep — grep matched: TOKEN_TTL_SECONDS = 3600
1 Anchor(s) checked · 1  · 0 blocking
exit 0

$ sed -i '' 's/3600/7200/' src/auth.ts   # break the invariant

$ emerik verify                          # drift introduced
⛔ BLOCK  constraint 01KWH64…  grep — grep found no match for: TOKEN_TTL_SECONDS = 3600
1 Anchor(s) checked · 0  · 1 blocking
exit 1
Why this matters

A drift detector that can never detect drift is worse than none — it lends false confidence. Always test an Anchor by forcing it to fail, not only by confirming it passes.

Retrieve context

emerik retrieve searches the local index by exact text and meaning, then ranks Pins and Anchors together. Symbols, error messages, and plain-language questions all use the same path. This is the main retrieval call for AI agents.

Each result also carries context from the coupling graph:

  • governedBy — the Anchors and practice documents that govern the path this hit is bound to. What you found, and who owns it.
  • explainedBy — for an Anchor, the practice documents that explain it, flagged when one of them is known-wrong.
  • leaseVolatility — active write leases whose territory covers this hit. Someone is working here right now; read this before you edit there.

Decoration never reorders the results. A hit keeps the rank relevance gave it — knowing more about an artifact is not evidence that it answers your question better.

Has the code moved? — drift

Every hit carries a drift verdict, computed by re-reading the bound file in your working tree and comparing it to the hash the artifact recorded when it was taken:

  • current — the file on disk still hashes to what the artifact recorded.
  • drifted — the file has moved ahead of the artifact. The hit is not hidden and the answer is not downgraded: the artifact is still a true statement about the code it was taken from. Read it, then read the file.
  • gone — the bound file is not on disk here.
  • unknown — the check is not answerable for this artifact. An Anchor governs globs rather than one hashed file (its freshness is emerik verify’s business, a check rather than a hash), and a Pin from a member repo that is not checked out here has no tree to be judged against.

The envelope totals the page under freshness.workingTree, and the plain-text render says it once at the bottom — 3 of 10 describe code that has moved.

Why a parse-derived index cannot do this

Tools that build their index by parsing your current source tree hold no record of what the code looked like when a fact about it was written down — so there is nothing to compare against, and “is this still true?” is not a question they can be asked. emerik can ask it because the hash lives in the artifact, committed beside the code. That is also why a drifted hit is surfaced rather than suppressed: emerik tells you what moved, it does not decide for you.

Narrowing the question

Five filters, all applied inside the index rather than to the results afterwards — so a narrowed answer is still a full-size ranking, not a top-20 cut down to three.

  • --path (repeatable) — a repo-relative file, or a directory ending in / for everything under it; globs are not accepted. See below — this one changes the shape of the answer.
  • --kind (repeatable) — pin, anchor or explainer. The default is Pins and Anchors; practice documents are explainer search’s corpus unless you ask for them here, and an explainer hit is always a pointer (title and path), never the prose.
  • --steward (an exact match on the steward named on the Anchor) and --enforcement (block, warn or advisory) — a Pin carries neither field, so either of these implicitly narrows the answer to Anchors.
  • --since — artifacts created at or after an ISO date (2026-08-01) or a date-time with a timezone (2026-08-01T09:30:00Z). A date-time with no timezone is refused: it would select different artifacts on two developers’ machines.

A filter set that admits nothing is not an empty list: the answer is insufficient with the cause filtersExcludedAll (see What the answer is worth).

emerik retrieve "how is the auth token TTL enforced?"
emerik retrieve "auth token TTL" --limit 5 --json
emerik retrieve "retry policy" --path src/core/queue/ --since 2026-08-01
emerik retrieve "" --path src/auth/token.ts        # what governs this file
emerik retrieve "how do we test" --kind explainer

Results carry a heat bar (▁▂▃▅▇) showing how frequently each Pin is referenced.

Ask before you edit — --path

Naming the files you are about to touch changes the answer. The Pins, Anchors and practice documents that govern those paths come back first, marked authoritative, and the semantic hits for your query, restricted to the same paths, follow below them. “This Anchor governs src/auth/token.ts” is a fact about the file you named; a 0.97 similarity is a guess about a different file, and a guess must never outrank a fact however confident it is. With --path set the query may be empty — “what governs this file” is a complete question on its own. In a workspace, <repo>:<path> names a member repo, exactly as blast radius does.

What the answer is worth

Every answer carries an envelope. exact means nothing degraded it; anything else names the reason and its remedy in one line — the index is not built, the search engine has no binary for this platform (the lexical floor answers instead), the stored vectors came from a different embedding model, the embedding model could not be loaded (the answer is still served from the full-text arm alone), the index and your substrate disagree about which artifacts exist (emerik index refresh), or the two halves of the index came from different builds (see receipts).

The envelope also carries a tier naming which engine answered: full (the hybrid index, both arms), lexical-only (the hybrid index without its semantic arm), floor (this machine has no hybrid search binary, so the lexical floor answered — exact identifiers and error strings still match, paraphrases do not; phrase the query in the words the code uses, and read an empty page as noMatch, not as “could not look”) or unavailable (nothing answered, and the note names the remedy). A floor answer is always a lower bound, by construction, and carries searchEngineUnavailable — true, the hybrid engine is not on this machine — without that meaning the answer failed.

An answer also carries an outcome: answered, or insufficient with a cause that says why it is empty. “Nothing matched” and “the index could not look” are different problems with different fixes, and they used to arrive as the same silence:

  • noMatch — a healthy, complete index genuinely holds nothing for this query. Go to the code; do not read absence as a hint.
  • filtersExcludedAll — your own narrowing admitted nothing. Widen or drop a filter.
  • noGoverningAnchor — nothing governs the paths you named. An exact zero over a complete projection, not a gap in the index.
  • budgetExhausted — the budget could not fit even the envelope; see budgets below.
  • indexStale, or one of the “could not look” causes (searchIndexNotBuilt, searchEngineUnavailable, embeddingUnavailable) — the remedy is in the note, as above.

The CLI and the graph_retrieve tool render these sentences from one table, so a person and an agent are never told different things about the same emptiness.

The relevance floor is a signal, not a number

Older versions cut results off below an absolute similarity score. The hybrid ranking fuses a full-text arm and a semantic arm, so its scores are relative to the result page rather than absolute — a fixed cutoff over them compares two things that are not comparable, and picking a new number would be a guess. What emerik reports instead is a fact: every hit carries lexicalMatch, and when no hit on a page contains a term you typed, the answer is labelled belowThreshold — every result is a semantic neighbour, so treat them as leads rather than facts and add an identifier or an error string for an exact match. Nothing is dropped for this. It is a label, not a threshold. Its one honest limit: it asks “does the word appear” using emerik’s own identifier splitter, which is not the full-text engine’s tokenizer — so it describes the text, not the engine’s score.

Budgets — --budget

--budget <n> caps an answer at n tokens. Whole hits are dropped from the bottom of the ranking until it fits — on a --path answer the semantic section empties before any authoritative hit is touched — and the envelope reports how many (budget.dropped, beside budget.tokens and budget.estimatedTokens; present only when a budget was given). Dropping for budget does not change the envelope’s verdict: the ranking was complete, you asked for less of it. Every hit that survives is complete and the response always parses: emerik never cuts a response mid-object, because a caller asking for less context should not get broken context. The estimate is characters ÷ 4 over the whole response as you receive it — hits and their decoration, the envelope with its own budget block, receipts and the note — so budget.estimatedTokens is a ceiling on what came back, never an undercount, and not a claim about any particular model’s tokenizer. A budget too small to fit even the envelope returns the envelope with every hit dropped, outcome: insufficient and the cause budgetExhausted, rather than failing.

The local index

Retrieval is powered by two local, ephemeral projections of your committed artifacts — the search index: one global table holding Pins, Anchors and practice documents together, searched by exact terms and by meaning at once, with the two rankings fused into one (that is what lets a symbol name, an error string and a vague description all find the same artifact); and the relational index: the derived coupling graph plus the read models built from your committed records. Both live under .emerik-local/ (gitignored) and are rebuilt from the source of truth on demand — the projections are disposable, the Pins, Anchors and documents are not. One command maintains both: each verb below does the search index, then the relational index, in a single run, and each is published by its own atomic swap, so a failure in one leaves the other — and the previous good build — untouched.

CommandWhat it does
emerik index rebuildRebuild both projections from scratch, then atomically swap them in. This is the only place the coupling graph is derived in full — and it resets the local flight recorder and the skew counter.
emerik index refreshIncrementally bring both projections up to date — only what changed is embedded. The relational index re-projects its record-derived tables here and leaves the coupling graph to the per-commit append; --full re-derives everything.
emerik index statusReport freshness of both projections and whether a rebuild is needed.

--json carries exactly two legs: search and relational. This changed in 0.5.0 — the retired Pin index’s report used to sit at the top level with the Anchor index’s beside it under anchors, and both keys are now gone rather than re-pointed at the search leg: a field naming a corpus that no longer exists is worse than a removed one. rebuild and refresh also report the generation they stamped. For practice documents the search index holds the document each record points at, and it is re-read only when a document is re-declared — never by watching the file, which is why a document edited without a re-declare simply serves its previous entry while the file itself stays authoritative. A query embeds exactly one thing — itself — however many artifacts you have. An index that is merely stale still serves: its hits are cross-checked against the live artifacts, so a superseded or deleted one never surfaces, and a refresh brings in what is new. When the two disagree about which artifacts exist — a Pin harvested since the last build, or one the store no longer holds — a retrieval answer says so (indexStale, with the counts attached) and names emerik index refresh as the remedy. That is decided by comparing the two sets, never by comparing commits: a code-only commit moves HEAD and changes no artifact, and warning about that would be noise.

0.5.0 changes the on-disk format of the search index (it gains a timestamp column so --since can be applied inside the engine, records each indexed artifact’s kind, and declares which projection the build holds — the hybrid table or the lexical floor). An index built by an earlier version is reported as not built, with its usual remedy, rather than queried with a column, or an engine, it does not have. One emerik index rebuild after upgrading covers this and the embedding-model change together. Nothing in .emerik/ changes, and nothing needs migrating: the projections are disposable.

Tiers — hybrid, lexical floor, nothing

The hybrid search engine ships as a platform binary, and some platforms do not have one — Intel Macs have none at all, and a regenerated lockfile can silently drop the binary for any platform. emerik does not assume the binary is there because the install succeeded: it attempts the load and reports what happened. There are three rungs, tried in order:

RungWhat answersWhat you get
HybridThe search index (exact terms + meaning, fused)The full answer — tier: "full".
Lexical floorNode’s built-in SQLite full-text search, over the same corpusExact identifiers, error strings and every filter still work; a paraphrase does not match. tier: "floor", always a lower bound, with a note saying what answered and what it cannot reach.
Nothingtier: "unavailable". The rung nobody wants: neither engine can load (no hybrid binary, and a Node older than 22.16 whose built-in SQLite has no full-text search) — or, transiently, the floor could answer but has not been built on this machine yet, in which case the note names the one emerik index rebuild that builds it.

The floor is conditional: it is built only when the hybrid engine cannot load, never alongside it, and never loaded on a machine whose hybrid engine works — so a healthy machine pays nothing for it, in disk, in time, or in startup. It also needs no embedding model, which is why a floor rebuild is a fraction of a hybrid one. emerik index status reports both halves of the picture: tier (which projection is published) and activeEngine (which engine this machine can load). When a floor build meets a machine whose hybrid engine has come back, the answer is needs rebuild — and emerik index rebuild (or the next refresh) promotes it. The reverse heals the same way: a hybrid build on a machine that has since lost its binary is rebuilt as the floor. Until that rebuild runs, the status line says the build cannot be read here and that retrieval answers nothing, instead of reporting it healthy — a status that looked fine while every query came back empty would be the wrong label on the one screen an operator checks first.

Why Node 22.16

The floor is Node’s built-in SQLite — the one search engine with no package, no prebuilt binary and no platform matrix, which is exactly what a floor has to be. Full-text search in it is a compile option that Node only carries from 22.16, so below that the floor cannot exist. emerik checks anyway, by creating a full-text table at load time rather than trusting the version number. Node prints an “experimental feature” warning when that module loads; emerik suppresses exactly that one line and passes every other warning through untouched.

Receipts, and what happens when the two halves disagree

Every rebuild or refresh stamps one receipt into both projections in the same pass: the commit it read, a generation id for the pass, and the embedding model that was live while it ran. It is the only way to ask a question neither projection could answer alone — did you two come from the same build?

When an answer needs both halves and the receipts disagree, emerik serves the half that actually ranked your results, drops the other half’s contribution entirely rather than decorating today’s ranking with yesterday’s facts, marks the answer a lower bound with that reason, and hands you both receipts so you can see which half is behind. It also starts one emerik index refresh in the background to heal itself — at most once per disagreement, and never while a build is already running. emerik dashboard counts how often that has happened since the last full rebuild. As always, emerik index rebuild is the escape hatch that fixes it outright.

Deleting .emerik-local/ is always safe. One emerik index rebuild regenerates both projections from your committed artifacts alone, and there is no migration step anywhere. Upgrading from an older version leaves two now-unused directories behind — .emerik-local/index/ and .emerik-local/anchor-index/, plus .emerik-local/explainer-index/ from before that. Nothing reads or writes them; delete them whenever you like.

Offline by default

The embedder (ONNX snowflake-arctic-embed-xs) runs locally. No query or content leaves your machine. The first embed downloads the model into .emerik-local/models/; every run after that is fully offline. If the model ever changes, emerik will not silently compare vectors made by two different models — it answers on exact terms only, says so, and one emerik index rebuild puts it right.

The indexes are not the only thing under .emerik-local/. Beside them sits the projection manifest (.emerik-local/projection/, also gitignored): one file per artifact directory, holding what that directory contained the last time emerik read it, keyed on the directory’s git tree hash plus anything uncommitted under it. When the key still matches, a command projects the live set without re-reading a single artifact file; when it has moved, only the changed files are re-read. It is what keeps the per-commit harvest and every substrate command at their month-one speed on a substrate of tens of thousands of Pins. Like the indexes it is an optimization, never a source of truth: a missing, stale or damaged manifest simply means the next read scans the store as it always did, and any full scan rebuilds it. There is no command to manage it — it maintains and heals itself, and deleting .emerik-local/ costs nothing but the next read.

Blast radius

Before you edit a file, the question that matters is what else moves when this moves. emerik answers it from a derived coupling graph built out of things you already committed — no language server, no AST, no new dependency:

  • Co-change — files that keep landing in the same commits, weighted by an exponential decay in commit age, with bulk sweeps (a rename or a formatter run) and one-off pairings filtered out. Merge commits are skipped: a merge’s diff is every file its branch touched, collapsed into one record, and the branch’s own commits have already said what moved together. Co-change catches what an import graph cannot: a migration and the model it migrates, a proto file and the client that speaks it, a test and the fixture it reads.
  • Import scanning — per-language import lines (including the wrapped, multi-line form a formatter produces) resolved to real repo paths. Package imports are skipped, because an edge to something outside your repo is a dependency list, not a blast radius.

Both cross repo boundaries by construction: in a workspace the graph is one table with no repo partition, and an answer reports how many repos it spans.

emerik graph blast-radius src/core/auth/session.ts
emerik graph blast-radius src/core/auth/session.ts --depth 3 --limit 50
emerik graph blast-radius 01J8ZQ...  # a Pin, an Anchor, or a practice document
blast radius of src/core/auth/session.ts — 7 coupled paths within 2 hops
  1-hop  src/core/auth/tokens.ts        [import 1.00]     governed by anchor
  1-hop  src/core/auth/refresh.ts       [co-change 0.61]  governed by pin
  2-hop  tests/auth/session.spec.ts     [co-change 0.23]

Every result carries the hop it sits at, the kind of coupling that reached it with its weight, and the artifacts that govern it — what moves and who owns it, in one answer. A seed can be a file path or an artifact id: an Anchor resolves to the paths its governs globs cover, a practice document to its own file.

It is a lower bound, and it says so

The edges are derived, not parsed. Dynamic imports, reflection, string-keyed dependency injection and code generation are invisible to it. So an empty answer means “nothing else is coupled that we can see”, never “nothing else is coupled” — and every answer carries a machine-readable reason when it is a tighter bound than usual: the relational index is not built; the answer was cut at --limit (raise it to see more); the seed resolved to more paths than one call traverses (narrow the seed); or the history behind the derivation was incomplete (a shallow clone). Each names its own remedy. The answer is a ranking and suggestion instrument; nothing gates on it.

The graph is derived in full by emerik index rebuild and kept current between rebuilds by the per-commit harvest, which appends each new commit’s coupling best-effort — a failure there is logged and swallowed, and can never fail your commit. Its tuning constants (the decay half-life, the bulk-commit cap, the pruning floor, the history window) ship as documented but honestly unvalidated defaults; a local, gitignored flight recorder in the same database records what was asked and what came back, so they can eventually be validated against real use rather than guessed at. The attention audit reads it — and writes back, into each row’s long-empty followOn column, what happened after that answer was served. emerik index rebuild still resets the whole recorder, labels included: it is derived state, and deleting it is always safe.

Heat & glacier

Every Pin has heat — a score blending recency-decay with how often it is referenced. Cold Pins can be frozen into a lossless glacier archive to keep the active index lean, and thawed back when they matter again. Freezing is a relocation, never a deletion: the Pin's identity and bytes are preserved.

emerik glacier freeze --threshold <heat>   # freeze Pins colder than a threshold
emerik glacier list                        # what's archived
emerik glacier thaw <pin-ulid>              # bring one back

Goals

A Goal is the concurrent work-unit: a team pursues one active Goal against the shared decision graph while other teams pursue theirs — many Goals run at once with no single-tree bottleneck. A Goal is a first-class, content-addressed, append-only artifact under .emerik/goals/: starting one mints an active Goal; advancing supersedes it with a progress entry; completing supersedes it with a completion stamp. The committed markdown brief under .emerik-goals/ stays the durable narrative — the Goal links it with --brief; they coexist.

A Goal carries a first-class priorityP0, P1, or P2, set with goal start --priority (default P1). It is the attention rank the orchestrator's simple-priority mode orders assignments by (P0 before P1 before P2), and it is carried through advance unchanged. The field is optional for append-only compatibility: a Goal committed before priority existed carries none and reads as P1.

A Goal also carries a person and a place — both optional, both recorded at write time, neither ever invented later. The owner is the acting git identity (user.email) stamped when the Goal is started: observed, never declared — there is no flag to assert it, an advance never re-points it, and a Goal started before the field existed (or without a git identity) simply has none, forever. Territories scope the Goal's work in the same vocabulary leases use — a path, a glob, or an opaque token — and they are what grounds the derived cycle time: when a completed Goal is read, its start is derived from the first observed evidence on its territories (the first lease claim there, or the first commit touching a path), never from createdAt — a declared field on a clock the measured party controls is never a duration origin. A Goal whose start left no observable evidence reports unobserved and says why; absence of evidence is a fact the read surface states, never a number it fabricates.

CommandWhat it does
emerik goal start "<title>"Start a team's active Goal (--team, --brief to link the committed brief, --priority P0/P1/P2 — default P1, repeatable --territory to scope it in the lease vocabulary); records the acting git identity as owner — observed, never declared — and flags recently-changed depended-on contracts as coordination events.
emerik goal advanceRecord progress (--note) or finish it (--complete); repeatable --territory sets the superseding Goal's territories — attach them now if it started without any; explicit values replace the previous set.
emerik goal statusShow a team's active Goal (or one by --id) — no active Goal is not an error. A completed Goal read by --id adds its owner, territories, and derived cycle — first observed evidence to close, or an honest unobserved when there is none.
emerik goal listLive Goals — active by default, --all includes completed; a bounded page (--limit / --cursor / --id, FR-39).
emerik dependency declareDeclare a contract-dependency on a published Anchor (--anchor, --team, --note) — never live code under lease.
emerik dependency statusShow the currency of a team's dependencies — current, changed (a coordination event), or retired.
emerik dependency listLive dependencies — --team / --anchor (lineage-aware) filters; a bounded page (--limit / --cursor / --id, FR-39).
One active per team

Starting a second Goal for a team while one is active is refused (GOAL_ACTIVE_EXISTS) — advance or complete the first. This is a local projection check, not distributed locking: two machines racing a start for one team can both land (the append-only union merges without corruption). The projection then reconciles the race deterministically — among a team's live active Goals it picks one winner (earliest createdAt, ties broken by lineage-origin id) as the Goal every team-selector read and write targets, and flags the rest as raced losers. Both stay visible in goal list; a loser carries a note naming the winner, its bare goal advance is refused with the same voice, and goal advance <loser> --complete retires it (the reconciliation move). No compare-and-swap, no goal ref, no committed-bytes change. Cross-machine write arbitration is still the Lease's job.

A coordination event at start, not a gate

When you start a Goal, emerik checks your team's declared contract-dependencies: if a depended-on Anchor changed since you declared it, goal start flags it after the STARTED line — “the ‘AuthGuard’ contract changed (2026-07-02…) — coordination event”, with the exact re-declare sync command. It is a heads-up to sync first, never an alarm: the Goal starts regardless (exit 0, never “conflict”) — a stale contract does not block starting work; the Lease is the only write arbiter. The --json payload stays exactly the Goal; agents pull the same report via dependency_status.

The flywheel

Completing a Goal outputs Pins that feed retrieval — Goals → Pins → RAG — because the Goal's committed brief and work commits are harvested by the hooks. The Goal surface itself mints no Pin: the emerik(goal): lifecycle commits are skipped by the harvest recursion guard. Learning is a byproduct of working.

Contract-dependencies

When your team builds on another team's work, you depend on the published Anchor — the contract — not on their live, leased-out code. It is the microservice-contract version pin: you declare a dependency on the contract you built against, and when the steward supersedes it, that change is an explicit coordination event you are notified about. The surface accepts only an Anchor id — it structurally cannot name live code, a path, or a leased territory, so “never against live code under lease” is a property of the shape, not a convention.

CommandWhat it does
emerik dependency declare --anchor <id>Declare a contract-dependency (--team, --note) on a live-head Anchor.
emerik dependency statusEach of a team's dependencies as current, changed, or retired.
emerik dependency listLive dependencies — --team / --anchor (lineage-aware “who depends on this contract”); a bounded page (--limit / --cursor / --id, FR-39).

Declared against the contract, notified when it changes

A dependency is an append-only artifact naming a published Anchor's id. It is recorded once; when the steward supersedes the contract, you find out four ways:

  • The event log. The emerik(anchor): supersede <id> commit is the coordination event — a git-native, durable record, replayable via emerik history.
  • Your read surface. emerik dependency status (and the dependency_status MCP tool) reports a moved contract as changed and names the new head — the pull-based notification the dependent polls.
  • The author's render. When the steward runs emerik anchor author --supersedes, the human output names the dependent teams affected — a courtesy heads-up at change time (the --json envelope is unchanged; the notification surface for agents is the pull-based status).
  • At Goal start. emerik goal start renders the coordination event automatically (human output) for any depended-on contract that moved — sync before building on it. It is a heads-up, not a gate: the Goal starts regardless. Agents on the MCP path pull the same report via dependency_status right after starting.

Sync is a re-declare

There is no separate sync verb: re-declaring on the new head mints a superseding Dependency (append-only), which dependency status tells you the exact command for. A same-head re-declare is refused (DEPENDENCY_EXISTS) — it is never a silent no-op. If two machines race that declare for the same team and anchor and both land, the byte-identical duplicates dedup at projection (kept: earliest) and count as one dependent — append-only, both files stay on disk.

A coordination event, not a conflict

A changed contract is surfaced in coordination-event voice — a heads-up to reconcile, never an alarm. A live dependent now drives the risk tier: a review that edits an Anchor with dependents is risk-tiered high and routed to a human — see Review at scale.

Exclusive-write leases

When several agents or machines write to the same repo, a lease gives one of them exclusive rights to a territory for a bounded time. Leases are recorded on a dedicated git ref (refs/emerik/leases) via compare-and-swap push — your git remote is the serialization point, so no lock server is needed. A lease expires on crash or stall, so turf never deadlocks.

Territories collide by path overlap, not just exact string — so mutual exclusion is mechanical, not a naming convention. A lease on src/auth/ blocks a claim on src/auth/login.ts (and the reverse), and glob forms like src/auth/** collide the same way. A bare token with no / or glob metachar (e.g. payments) is opaque — it collides on exact match only. The refusal names the overlapping lease so an agent's routing reads it unmistakably.

A single-segment directory is still a bare token

A trailing slash alone does not make a territory path-aware: billing/ normalizes to the bare token billing and collides on exact match only — it will not collide with billing/invoice.ts. Write a multi-segment path (src/billing/) or a glob (billing/**) when you want collisions detected across an entire module.

emerik says this out loud where it bites: when the territory you claim (emerik lease claim) or declare (emerik anchor author --territory) is a bare token that also names a real directory, one extra note names the trap and suggests billing/**. It is a hint, never a refusal — the claim or the Anchor is written exactly as before, the exit code is unchanged, and the note is absent from --json and suppressed by --quiet.

CommandWhat it does
emerik lease claim <territory>Claim exclusive write on a territory (--ttl, --holder, --retries, --wait). --goal <id> links the lease to the live Goal it serves — title and initiative stamped from the goal's recorded bytes at claim; an unknown id refuses before anything is claimed.
emerik lease renew <territory>Extend a lease you hold (--retries, --wait — a transiently-contended renew retries to success).
emerik lease release <territory>Give it back early (--retries, --wait).
emerik lease listActive leases as complete Now rows — each with its declared goal (when one was claimed) and the last commit touching its territory, or the stated absence when none is observable (add --remote for a fresh cross-machine read).

Since 0.8.0 a lease can also say what the work is for. Claiming with --goal <id> stamps the named live Goal's title and its recorded initiative attribution onto the lease at claim time — a declared link, resolved from the goal's recorded bytes: emerik never infers which goal a claim serves (two goals can legitimately share ground), and an id that names no live Goal refuses before anything is claimed. Absent stays absent: a claim without --goal, and every lease claimed before 0.8.0, simply carries no link — a fact every read surface reports as the absence it is. emerik lease list then answers "who is working on what, right now, under which goal" in one read: each lease shows its goal when one was declared, and the last commit touching its territory — real recency when a commit is observable, and a stated absence when it is not. An opaque territory names no path, so commits cannot be observed for it (a path is never guessed); a territory no reachable commit touches may mean work not yet started, or work landed outside this repo. Either way the row states the fact — never a zero standing in for the unknown, and never an error.

Two machines, one territory

Run this on two clones of the same remote at nearly the same instant. Exactly one wins.

This is signed off end-to-end: two clones with distinct git identities, driven over both the CLI and the MCP twins, exercise cross-machine claim, territory overlap, version-skew tolerance, and goal/fork/dedup races against a shared remote — each producing exactly one winner or one surfaced, reconcilable fork. Since the git ref CAS is client-agnostic, the serialization holds identically across physical machines — the executed pass ran against a local bare remote (a hosted remote and a literal second physical machine are a documented follow-up; see the committed sign-off record).

Machine A Machine B
A$ emerik lease claim payments --holder machineA --retries 0
⛔ BLOCK  territory 'payments' contended — another machine claimed it first   exit 1

B$ emerik lease claim payments --holder machineB --retries 0
▣ LEASED  payments · holder machineB · expires in 15m                        exit 0
Retries

--retries 0 fails fast. The default (5) uses jittered back-off so a transiently-contended claim, renew, or release retries to success instead of failing spuriously — all three mutating verbs share the one retry discipline.

Version skew — tolerate the known, refuse the rest, never open

A lease this client reads may have been written by a newer emerik. The read posture is deliberate and single-winner by construction: a lease whose six core fields still validate (with unknown extra fields stripped on read) is tolerated — honored as held and listed; a blob it cannot fully read is refused as held — its territory stays blocked and its bytes are preserved untouched. A client never grants a territory it cannot prove free, and never rewrites a lease it cannot fully read (renewing a newer-format lease is refused — renew it with that version; releasing one you hold still works). The lease's six fields are a frozen core: any future growth is additive and optional over them, so old and new clients coordinate safely. In 0.8.0 that growth happened exactly as promised: initiativeId and goalTitle joined as additive-optional fields, and an older client tolerates an enriched lease precisely as this posture prescribes — held, listed, releasable by its holder; only renew defers to the newer version.

Clock skew — bounded, and it never touches the winner

A lease's expiresAt is written from the writer's clock; liveness is judged against each reader's clock. The compare-and-swap that picks the winner of an open territory never consults a clock — the git ref store serializes it — so single-winner exclusivity is skew-independent. Skew moves only the expiry edge: a contender whose clock runs ahead of the holder's by S seconds could claim over an expired lease S seconds early. With the holder renewing at least M seconds (its own clock) before expiry and treating a failed renew as loss-of-lease, a live holder stays exclusive for |S| < M. At the shipped default ttl 900 s and a renew-at-half cadence, M = 450 s — three to four orders of magnitude above NTP-disciplined skew (typically < 0.1 s). The operating rule: renew before the margin, treat a failed renew as lost. (--wait is accumulated back-off, not a wall clock, so it is unaffected by skew.)

Stigmergic self-deconfliction

Agents coordinate the way ants do — by reading shared state and routing around each other, with no direct messaging and no orchestrator. Before it chooses where to work, an agent reads the trailemerik trail status (MCP trail_status):

  • The heat trail. A recently-worked path is hot — a signal that someone is active there. This is FR-4's heat read as a pheromone: one mechanism, two uses, the same heat number retrieval ranks by, never a second subsystem. Recency travels through git (a fresh Pin's committed createdAt), so "someone just worked here" is visible cross-machine; reference counts add same-machine reinforcement. Heat never blocks — a hot zone is a routing signal, not an error.
  • The lease markers. The active Leases on refs/emerik/leases are the authoritative contested signal — territory · holder · expiry, shown so others route around them.
CommandWhat it does
emerik trail statusRead the trail — hot zones + lease markers — to route around, no messaging (--remote for a fresh cross-machine read, --limit to cap the zones). Pure read; mutates no ref.

Declaring a territory turns the trail into a guard. When an author declares the territory it holds — emerik anchor author … --territory <t> (MCP anchor_author territory) — the write is consulted against the live Leases before it lands using the same path-overlap check a claim uses: a declared territory that path-overlaps another holder's live Lease refuses the write (LEASE_HELD, exit 1, no partial write) in coordination-event voice — never "conflict", never "locked". Your own or an open territory proceeds. Heat is never consulted at write time; only a live Lease arbitrates.

The routine case, and the rare one

The routine workload self-deconflicts this way — N agents make N trail reads and zero pairwise messages, so coordination never grows with the square of the swarm (layer 1, ~95% of the work — an Epic-8-measured target). The rare genuine collision that cannot be routed around — two agents that truly need the same territory — is arbitrated by the emerik-orchestrate skill (layer 2, the ~5% path), which reads this same trail, then applies drafted rules in order: wait out a short-lived Lease, else let the higher-priority Goal proceed (P0 over P1 over P2), else first-holder by CAS order, else split at a published Anchor (declare a contract-dependency and build against it), and finally surface it to the human driver. The orchestrator arbitrates who proceeds — it grants territory only through Leases and never breaks one.

Onboard a brownfield repo

Adopting emerik on an existing codebase is an agent workflow, not a manual runbook. Point your coding agent at the emerik-onboard skill and it drives the whole cold start: ensure the store is initialized, emerik seed --analyze, emerik seed, build the index, and land a committed onboarding summary grounded in the seeded graph — with each phase's landing verified from the live surface (via the --json reports and emerik index status) before the next phase starts. Retrieval works warm from the first query.

A repo with nothing to seed is not forced through the pipeline: when the analyzer reports codeUnits === 0 (or there is no HEAD), emerik-onboard says so and routes to the greenfield planning path (emerik-brainstorm + emerik-guide) instead of seeding nothing — the store is still left initialized with a fresh index.

On a multi-repo workspace, emerik-onboard takes the workspace route: the analyze → synthesize → seed loop runs per member with --repo (so every seeded Pin and every admitted doc Anchor carries its member's repo qualifier), and once all members are seeded an ecosystem synthesis phase maps how the members interact — every claimed interaction cites repo-qualified code locations read on both sides — landing a committed onboarding-ecosystem.md beside the per-member summaries, with shared cross-repo contracts offered through the same human admission gate as workspace-level constraint Anchors.

A Pin explains its region; it never contains it. The source is already addressed by the Pin's own binding, so copying the bytes into the body adds nothing you can act on. Without an agent, a Pin's content is a deterministic descriptor: what the file is, what it declares (the top-level export / def / class / resource names), the prose its author wrote at the top of it, and an explicit no agent synthesis label. It is short, honest, and retrievable — it keeps the file's real vocabulary — but it is a description, not an understanding. Prose documents (markdown and friends) are the one exception: they quote their own opening heading and paragraph, because that is the explanation.

When a coding agent drives the cold start it does better. After the first analyze pass it reads the enumerated code units and writes a short synthesized understanding of each into a gitignored handshake file .emerik-seed/synthesis.json (keyed by repo-relative path), then re-analyzes with --synthesis. Those files' Pin content becomes the agent's understanding — provenance-marked synthesis: agent, a record of how the content was produced, not a trust upgrade — while the binding stays on the code, so re-derive keeps it fresh from there. No live agent or model is ever loaded into the engine: the intelligence arrives as data through the same extractor seam the deterministic pass uses.

The depth pass is a loop against a stated target, not one write. A single pass writes a batch and stops whatever the repo's size, which is how a 1684-unit repo ends up 7.5% deepened and reports it as success. emerik-onboard names a coverage target up front, works the code units in priority tiers — entrypoints and route tables, then domain models, then service clients and integration boundaries, then config and infra, then the rest, with tests, generated code and locale bundles last — and re-runs analyze per batch until the target is met. Its verify gate is a ratio, N of M units deepened (X%), and a shortfall is a decision put to you, never a bare count that scrolls past. On a workspace every member's ratio is reported together, so you see the product, not five isolated numbers.

The same handshake works at commit time. The agent that just wrote the code understands it more cheaply than anyone ever will again, so emerik-implement writes .emerik-seed/synthesis.json for the files it changed before committing; the post-commit hook auto-discovers it, deepens those Pins, and then consumes and clears the file so stale prose can never re-stamp a later commit. An absent handshake is the ordinary case, not an error — those Pins simply keep their descriptors. In a workspace each member repo is deepened from its own checkout's handshake. The harvest report answers back with synthesized (how many of this run's Pins carry understanding), resynthesisNeeded (paths whose synthesized Pin was just superseded by a descriptor because your change drifted its source — prose about vanished code is dropped on purpose, and named so you can rebuild it), and unmatchedSynthesis (handshake entries that named a path this run produced no Pin for — a typo, a stale key, an empty value). Because the handshake is consumed and cleared either way, an unmatched entry would otherwise vanish along with the only file that would show it. It is a fact, never a gate: the run still exits 0.

Documents get the same two-tier treatment. Instead of admitting a whole file as one 2000-character dump, the analyzer splits each doc on its markdown headings into per-section candidates — the deterministic fallback, always available. When a coding agent drives the cold start it extracts the actual decision each section states (an ADR's "use PostgreSQL", not its prose) into a second handshake file .emerik-seed/doc-decisions.json and re-analyzes with --doc-decisions; those per-decision candidates replace the section chunks, marked synthesis: agent. Admission is then a real per-candidate review: bare emerik seed --json returns docCandidates — each with a stable candidateKey, its source and section, full content, and a proposed enforcement — and the steward admits them individually through an admissions manifest (below), each with its own steward and enforcement level. Two documents stating the same decision collapse to one candidate (and one Anchor). No Anchor is ever written without --approve-docs and a named steward — the machine still never mints an Anchor on its own.

Mining the review history

Seeding reads what the code says. Once the index is built, emerik-onboard runs one more phase that reads something the code cannot say: what a person understood about it, written into your merged pull-request review comments and normally lost the moment each request merged. The phase walks those reviews newest first over a bounded window — by default the most recent 50 merged pull requests or 12 months, whichever bound bites first — and it asks you to confirm or adjust that window before it starts. Whatever window it used, and whatever fell outside it, is reported: there are no silent caps. Every read is your agent's own gh; emerik still performs no network I/O.

Each comment is judged individually through the same rubric the emerik-address-review loop uses — stated once, shared, never copied — with one shift for backfill: these pull requests merged long ago, so the fix side is moot and a comment is judged for durable insight only. Most one-off comments honestly yield nothing, and that is the prior rather than a filter: a one-off that encodes a lasting non-obvious fact (a hidden invariant, a why-it-is-done-this-way) is still captured. A lesson whose code still lives is deposited through the same handshake channel as a live review, carrying the original comment date, the pull request and the reviewer; a lesson whose source is gone is never force-bound to a fake region — it is offered as an Anchor candidate instead.

Recurrence is counted across the whole mined window, so the substrate has promotion signal on day one rather than after months of repetition: a lesson taught in several reviews surfaces as one candidate carrying its observed count — never as a pile of duplicates, and never passed over as already captured — while every occurrence still deposits. Candidates are then reviewed one at a time, newest first, each with its source, its count, a proposed enforcement and a proposed steward, and each admitted or declined by an explicit human choice; nothing is written on the machine's initiative. The whole record — the window, the per-comment dispositions, the deposit counts, and what was not processed — lands in the committed onboarding summary.

A repository with no gh, no reachable remote or no merged pull requests simply has the phase reported as skipped, with its reason, and onboarding continues — a cold start with no review history to mine is still a cold start. On a workspace the phase mines the meta-repo's own merged pull requests; member repositories' review history is named as not yet mined rather than silently omitted.

Two ways in

A skill cannot be its own delivery vehicle — its premise is pre-init — so there are two documented doors:

  • Door 1 (recommended). Install emerik globally, run emerik init as the one manual command (it installs all twenty-two skills), then invoke emerik-onboard in your agent. Its first phase detects the store, records init as already landed, and continues with analyze.
  • Door 2 (fully agent-driven). Point the agent at the packaged assets/skills/emerik-onboard/SKILL.md inside the globally-installed package and follow it — the skill drives emerik init itself as phase 1, self-installing for every future session.
Provenance

Seeded artifacts are stamped origin: seed — a lower trust tier than reviewed Anchors. Machine analysis never mints Anchors directly; doc Anchors wait in a review queue, admitted only behind an explicit steward gate the human names.

The manual runbook advanced

Prefer to drive it yourself (or running in CI / a non-agent harness)? The same pipeline is two review-gated steps. It requires an initialized store — run emerik init first.

# 1) analyze — walks HEAD + ingests docs into a gitignored .emerik-seed/
emerik seed --analyze

# scaffolding is held out by default; add --admit-all to analyze every path
emerik seed --analyze --admit-all

# 2) seed — bulk-write the analysis into the graph (code → Pins)
# bare seed also lists per-candidate doc previews (docCandidates) in --json
emerik seed

# admit ALL doc candidates under one steward (the bulk gate)
emerik seed --approve-docs --steward you

# or review per candidate: admit only the keys in an admissions manifest,
# each with its own steward + enforcement (see below)
emerik seed --approve-docs --admissions .emerik-seed/admissions.json

Not everything in a tree belongs in a cold start. Analyze does not mint one unit per file: tests, migrations, locale bundles, generated and vendored trees, lockfiles and binary assets are not admitted, because one Pin apiece buries the entrypoints and domain models you actually ask about — on a real five-member workspace that scaffolding was 1941 of 4926 files, a third of it tests alone. The report says exactly what it held out and why, per rule (notAdmitted plus a breakdown like tests 398, locales 72, assets 55), and keeps that separate from skipped, which means unreadable or binary. Disagree with the call? --admit-all analyzes everything, and a repo whose every path matches a rule — a dedicated end-to-end suite — is analyzed whole automatically rather than reported as an empty repo. Admission is a cold-start rule only: when you later change a test file, the commit hook still pins it, because that is real work you just did.

In a multi-repo workspace, target a member repo with --repoemerik seed --analyze --repo backend then emerik seed --repo backend — and the seeded Pins land repo-qualified in the workspace store. Standing inside code/backend, --repo defaults to it.

Seed from generated behavior docs

Already documented the codebase with a tool that emits a coverage.json feature index (mapping each feature to its docs and the code paths they describe)? Point --analyze at it and each covered code file's Pin content becomes that feature's behavior contract — bound to the code, so re-derive keeps it fresh from there.

# enrich covered code Pins with feature contracts (auto-discovers
# the index via doc-agent.config.json when --coverage is omitted)
emerik seed --analyze --coverage docs/features/coverage.json
emerik seed

Seed with agent-synthesized understanding

Have an agent (or a script) that can describe each file? Write its output as a handshake file — a { formatVersion, entries } map of repo-relative path to synthesized text — and point --analyze at it with --synthesis. Covered files are seeded with that text, stamped synthesis: agent; every uncovered file falls back to the deterministic descriptor. The named file must exist (a missing --synthesis path is a hard error, unlike auto-discovered --coverage). The same flag exists on emerik harvest, where the hook also auto-discovers .emerik-seed/synthesis.json — so the build loop deepens its own Pins as it commits, not only at onboarding.

# swap agent understanding in through the extractor seam
emerik seed --analyze --synthesis .emerik-seed/synthesis.json
emerik seed

Extract per-decision doc candidates

Without a handshake, each document is split on its markdown headings into per-section candidates. To capture the actual decision a section states (rather than its prose), write a { formatVersion, decisions } map of doc sourcePath to the decisions it states — an empty array is the honest "no decisions here" signal — and point --analyze at it with --doc-decisions. A matched doc's candidates are replaced, stamped synthesis: agent; a handshake path matching no ingested doc is reported as unmatchedDocDecisions, never silently dropped. The named file must exist.

# replace section chunks with the agent's per-decision candidates
emerik seed --analyze --doc-decisions .emerik-seed/doc-decisions.json
emerik seed

Admit per candidate

Bare emerik seed --json lists every admittable candidate under docCandidates with a stable candidateKey. To admit a reviewed subset — each with its own steward and enforcement level — write an admissions manifest and pass it with --admissions. Listed keys are admitted; unlisted candidates stay pending; an unknown key is a hard error before anything is written. All three enforcement levels are accepted (you chose), but an admitted doc Anchor is checkless, so even block/warn still surfaces as ⚠ WARN in emerik verify — admission records a decision, it does not add a runnable contract.

# .emerik-seed/admissions.json
# { "formatVersion": 1, "admit": [ { "key": "<candidateKey>", "steward": "data", "enforcement": "advisory" } ] }
emerik seed --approve-docs --admissions .emerik-seed/admissions.json
One-time boost

The docs seed the content; the binding stays on the code. After the first edit to a covered file, harvest re-derives its Pin back to code-derived content — re-run the seed to refresh. The feature docs are used as Pin content, not minted as duplicate Anchors (pass --include-feature-docs to keep both).

Multi-repo workspaces

A product is rarely one repository. A workspace gives a multi-repo product one substrate: a meta-repo whose .emerik/ store describes several code repositories, each held as a git submodule under code/<name> and named in .emerik/workspace.json. Pins carry an optional repo qualifier on their source binding (sourceBinding.repo) and Anchors an optional repo — the workspace member the reference or check is scoped to — so two member repos with the same file path can never alias. Absent qualifier = the store repo itself, which is why a single-repo project is unchanged: it is simply a workspace of one — no manifest, no qualifiers, nothing new to learn.

Store discovery is workspace-aware: run any emerik command from inside a code/<repo> checkout and it finds the meta-repo's store — and the --repo flags (seed, anchor author) default to the member you are standing in. emerik verify runs a repo-qualified Anchor's check inside that member repo; an unknown repo name is an honest blocking error, never a silent pass.

CommandWhat it does
emerik workspace add <url>Register a code repo as a member: a git submodule under code/<name> plus a manifest entry, landed as one emerik(workspace): add <name> commit (--name for the kebab-case workspace name, --path to override the checkout path).
emerik workspace listThe manifest, plainly: every member with its recorded (gitlink) vs checked-out SHA.
emerik workspace statusThe consolidation view: per-repo drift (recorded vs checked-out), branch, dirty flag, and live Pin/Anchor counts — plus the auto-sync panel: in sync / ahead / behind against origin/main, the last push with its carry-along count, and any standing fault. The panel always renders — an opened cockpit shows its instruments, healthy or not. Below it, the positions readout: one line per teammate's clone — who (roster-attributed, raw git identity otherwise), which machine, each member repo's checked-out head, and how long ago — stale clones grayed out after 7 idle days. Position informs, never gates. An opted-out member is labeled wherever they appear: their rows carry · syncs manually, members with nothing recorded are listed as syncing manually, and on their own clone the sync panel reads auto-sync: manuala recorded choice, not decay. --refresh / --no-refresh steer the fetch-before-read; exit 0; --json like every command. Also the workspace_status MCP tool.
emerik workspace syncAdvance member repos to their remote tips, commit the bumps, and harvest exactly the ranges the pointers moved over (--repo <name>, repeatable, to sync only named members). Non-interactive, and CI is the scheduled writer: run by hand it still works and prints a one-line advisory saying so.
emerik workspace autosyncPush and integrate the meta-repo's main now — the same primitive the background auto-sync runs after every autocommit, and the retry-now command the warn lines point at. Exit 1 when the sync could not complete; exit 0 when auto-sync is inactive (a reported fact — off main, no remote). --auto is the flag the background trigger passes: with it the command honors a member's org.yml sync: manual opt-out and reports inactive (exit 0, remote untouched); a plain invocation runs regardless — you asked. --refresh / --no-refresh, --json.
emerik workspace reconcileThe reconciliation door: after resolving a paused clone's divergence by hand, clear the halt and re-prove convergence — the same sync primitive runs again with a forced fetch. --head <sha> (required) is the record's pre-rebase head, quoted exactly from emerik workspace status; a mismatch is refused quoting both shas and clears nothing. Exit 1 on still-halted / stale-confirmation (the reconcile did not complete); exit 0 on reconciled / not-halted / inactive (reported facts). Never resolves content — the emerik-reconcile skill is the guided path. Runs even under EMERIK_AUTOSYNC=off: you asked. Also the workspace_reconcile MCP tool.
emerik workspace snapshotRecord the release SHA-tuple: every member at its recorded gitlink plus the meta-repo commit, as one append-only snapshot artifact (--release <name>, required). CI-written at a tag or deploy.
emerik workspace checkoutReproduce a recorded release: move every member to its snapshot SHA (detached). Writes nothing, commits nothing; --dry-run prints the tuple and moves nothing.

Auto-sync — the substrate that ships itself

On a workspace, the meta-repo's main is the team's coordination plane: every harvested Pin, every planning artifact, every recorded position rides it. Keeping a clone converged with it used to be a human ritual — remember to push after emerik committed, remember to pull before trusting a read — and a forgotten step never failed loudly; it just served stale answers. Auto-sync makes both halves mechanical. The scope is deliberately narrow: the workspace meta-repo's main only — never member repos, never member code, and a single-repo store (a workspace of one) is member code, so nothing changes there at all.

The push half. Every substrate commit — a harvest, a re-derive, a compaction, a workspace bump — is followed by a detached background push, spawned after the command returns, so no git hook and no foreground command ever waits on the network. The child is a real, documented command — emerik workspace autosync --quiet --auto is what ps shows, not a mystery process — and a push publishes everything below the tip, human commits included, with the carry-along count saying so (pushed 3 commits incl. 1 non-emerik).

The read half. Every substrate-reading command or MCP call fetches and integrates first, behind a 60-second TTL, so a teammate's harvested Pin is visible to your next read with no git command in between. --refresh forces the fetch; --no-refresh skips it — the offline posture: no fetch, no network. A TTL-live healthy read costs zero git calls, so the steady state pays nothing.

Why the rebase is mechanical. When local and remote have both moved, the engine rebases — and it can, without judgment, because every commit class the machine writes is union-mergeable by construction: store artifacts are append-only files that two clones can never both create, each clone's position file has exactly one writer, and member-repo pointer bumps belong to CI alone. Divergence between machine-written commits therefore resolves cleanly every time — no human and no LLM anywhere on the path.

When it is not. A true conflict — almost always human-edited shared content — or a dirty tree pauses auto-sync for that one clone: the tree is restored exactly as it was, the observation is recorded, and local work is unaffected, indefinitely. Healthy sync is silent; a standing fault prints one warning on your next command, states what was observed, and names the remedy — the detail always lives in emerik workspace status. The way back in is the reconciliation door: resolve the divergence with real git, then emerik workspace reconcile --head <sha> quoting the standing record's head — proof the record was read — and the sync primitive re-proves convergence itself. The emerik-reconcile skill walks the whole repair.

The cargo. Each clone also records its position — every member repo's checked-out head plus when it was last observed — in a per-clone file under .emerik/positions/, written only when an emerik command observes a change and carried by the same background push. emerik workspace status renders the readout, attributed to people through the roster, with week-idle clones marked stale. Position informs, never gates: telemetry about where teammates' clones stand, never a gate on any read or write.

Two consent switches — a machine's, and a person's. They look similar and are not, and neither implies the other. EMERIK_AUTOSYNC=off is a machine's standing posture: set in the environment of a CI checkout or an offline laptop, it stands both automatic triggers down on that machine and is recorded nowhere. sync: manual on your own org.yml roster entry is a person's recorded choice: it covers every clone where one of your listed git identities is configured, and it is labeled wherever freshness is displayed — your own cockpit reads auto-sync: manual, your position rows carry syncs manually, and if you never record a position you are still listed on the status panel as syncing manually. Labeled, not invisible: chosen staleness is a stated fact, never mistaken for decay, and nobody is nagged about a state they chose. Both switches yield to an explicit ask — a plain emerik workspace autosync or a --refresh always runs, because you asked.

Registration never harvests

Adding a member registers it — it does not sweep the whole tree into Pins. Onboarding a member's knowledge is a deliberate step: emerik seed --analyze --repo <name> then emerik seed --repo <name>, the same review-gated pipeline as a single brownfield repo — or let emerik-onboard drive the whole workspace: the per-member loop plus the ecosystem synthesis phase that maps how the members interact.

Bring a multi-repo product in

The brownfield flow for a product made of several existing repositories:

mkdir acme-product && cd acme-product
git init
emerik init                        # the meta-repo: one store for the whole product

# register each code repo as a member (a submodule under code/)
emerik workspace add https://github.com/acme/backend.git
emerik workspace add https://github.com/acme/web.git --name frontend

# onboard each member's knowledge — registration never harvests
emerik seed --analyze --repo backend
emerik seed --repo backend
emerik seed --analyze --repo frontend
emerik seed --repo frontend

emerik workspace sync              # the ongoing loop: pull member tips + harvest what moved

The sync loop — drift rendered, then reconciled

The meta-repo records each member at a pinned SHA (the gitlink); the member's real history moves on. emerik workspace status renders that drift — it never has to be discovered — and emerik workspace sync reconciles it: the bump commit carries real code changes (a normal commit subject, so it harvests), and harvest sweeps exactly the ranges the pointers moved over into repo-qualified Pins.

Terminal
$ emerik workspace add https://github.com/acme/backend.git
 added workspace repo backend at code/backend
  next: `emerik seed --analyze --repo backend` to onboard its knowledge

# … a teammate lands work on backend's main; pull it into the member checkout …
$ git -C code/backend pull

$ emerik workspace status
 backend  code/backend (main)  recorded 4f2a91c · checked out 8c07d13  12 Pin(s), 3 Anchor(s)  [DRIFT]
store repo: 5 Pin(s), 2 Anchor(s) · 1 drifting — `emerik workspace sync` reconciles

$ emerik workspace sync
 synced backend → harvested 4 Pin(s), re-derived 1, invalidated 0

# the harvested Pins are repo-qualified — bound to backend, never aliasing a frontend path

Machine-maintained refs

Member pointers are maintained by machines, not by engineers. A CI job runs emerik workspace sync on a schedule and pushes what it produced; nobody hand-advances a submodule ref, and nobody has to remember to. The command remains available to a person — run it locally whenever you want — but it is no longer the design's coherence mechanism. It is fully non-interactive: no prompt, no terminal required, --json for a machine caller, only the members that actually moved are bumped, and a run with nothing to do is an honest no-op that exits 0 and creates no commit.

The refs stay load-bearing precisely because they are the harvest trigger: a gitlink bump names the exact old → new range harvest sweeps, which is why the bump commit deliberately keeps an ordinary commit subject rather than emerik's own event-log grammar. Machines advance the pointers; the pointers still tell harvest what changed.

emerik never schedules, polls or pushes anything itself. CI runs the CLI; the host pushes:

# scheduled: keep the workspace's view of its members current
git checkout main
git submodule update --init
emerik workspace sync --json > sync.json
git push                           # only when sync reported members in `synced`

# at a tag or deploy: record what was actually released
emerik workspace snapshot --release "$TAG"
git push

Release snapshots — the app as released

"Which combination of member SHAs was v1.4.0?" should be a lookup, not an archaeology project. At a tag or deploy moment CI runs emerik workspace snapshot --release <name>, which mints one append-only snapshot artifact in the meta-repo store: the release name, the meta-repo commit it was cut at, and every member's SHA. It records what the meta-repo recorded — each member's committed gitlink, never a drifted local checkout — so the tuple is what was actually shipped. A member with no committed pointer blocks the whole run: a release snapshot is total, never partial.

Engineers never author or edit one. There is deliberately no flag to inject a member or a SHA — the tuple comes only from git's own records — and the append-only store plus git history are the audit trail. Reruns are safe: the same release with an identical tuple is a no-op, a genuinely re-cut release supersedes its predecessor (both cuts stay in history), and a new release name records fresh.

emerik workspace checkout <release> reproduces it: every recorded member moved to its recorded SHA with a detached HEAD, the normal submodule state. It is purely read-side — no store write, no commit, no harvest. It refuses before touching anything if a member's worktree is dirty or has no checkout on disk, skips a member already at its recorded SHA, fetches once and retries when a SHA is not present locally, and reports honestly which members moved if one fails part-way (rerun to converge). --dry-run prints the resolved tuple and moves nothing.

Terminal
$ emerik workspace snapshot --release v1.4.0
 recorded release v1.4.0 — 2 member repo(s) at meta 9d31c04 (01K4Z…)

# … weeks later, reproducing exactly what shipped …
$ emerik workspace checkout v1.4.0 --dry-run
· would reproduce release v1.4.0 — meta 9d31c04
  · backend   code/backend   4f2a91c
  · frontend  code/frontend  b7e0d52

$ emerik workspace checkout v1.4.0
 reproduced release v1.4.0 — meta 9d31c04
  → backend   code/backend   4f2a91c
  = frontend  code/frontend  b7e0d52 (already there)
Removing a member

Append-only holds here too: removing a member invalidates its Pins rather than deleting them, and a Pin whose repo qualifier names no workspace member is reported by freshness as unresolved — distinct from dangling, so a mis-edited manifest never reads as mass source loss.

Initiatives — the workspace intent ledger

With many people across many repos, "what is anyone working on?" needs one discovery point. An Initiative is a first-class, append-only ledger artifact declared once — a goal, a committed markdown brief, and a declared lineage (the branch it stacks on, plus optionally another initiative it builds on). Initiatives live in the meta-repo store, so they are visible from anywhere in the workspace — declare one from inside any member repo, on any branch, and it lands in the one shared ledger. Where the record lands is the meta repo; what it says about you comes from the repo you are standing in: the default steward is that repo's user.email and its default base branch is that repo's current branch, so a member with its own git identity is recorded honestly rather than under the meta repo's. The same holds for the four planning heads (brief / prd / architecture author) and their MCP twins. There is no start-gate: declaring is a fast-follow record, not a permission request. Closing an initiative writes an append-only superseding record marked closed — never a tombstone, so the ledger row stays discoverable.

CommandWhat it does
emerik initiative declareDeclare a new initiative — --goal (the intent), --finish-condition (required: one line stating when this is finished, in the human's own words), --doc-path (the committed markdown brief body), --lane (put it in the declared lane), --base-branch (the git ref it stacks on; defaults to the current branch of the repo you stand in), --stacks-on <ulid> (another initiative it builds on), --steward (defaults to the git identity of the repo you stand in). Lands one emerik(initiative): declare <ulid> commit in the meta-repo store; no start-gate.
emerik initiative amend <id>Amend a live initiative — an append-only superseding record carrying every field verbatim except the ones amended. --finish-condition sets (or replaces) the condition — the escape hatch for a record declared before finish conditions existed; --lane / --no-lane set or clear the declared lane. The only surface besides declare that ever sets either. An amend that would change nothing is refused rather than minting a record that says nothing happened.
emerik initiative listThe live initiatives (supersession-resolved), from the meta-repo or from inside any member. The human render shows the derived state — the position computed at read time, with the stall flag and the oldest blocker's age — never the stored status. --status is a record-lifecycle filter over the ledger (active / inactive / closed), which is a question about the log, not about state; --json carries the artifacts exactly.
emerik initiative close <id>Close a live initiative — an append-only superseding record with status closed (never a tombstone). The closure is the human's: --outcome (done / dropped / superseded / folded), --cause (didntDoTheWork / prevented, required iff the outcome is not done) and --statement (one line saying what actually happened). All three are required: a close that does not say what it must is refused with a message stating what a closure must say, and nothing is written. A closure is also all-or-nothing — a done closure carries no cause. The output prints the support check for a prevented claim. Closing a missing, already-superseded, or already-closed id fails cleanly (exit 1).
emerik initiative rollupAggregate what ended — every closure plus counts by outcome and cause per team and quarter. Read-only; an empty rollup exits 0.
emerik initiative sweepThe death ritual — flag every initiative idle past the TTL as inactive (an append-only superseding record), and close out the flags already merged into truth. A close-out writes a closure (outcome: dropped), so --cause and --statement carry the confirming human's words; run without both, every confirmed death is deferred with a note — nothing closed, nothing tombstoned, nothing invented on a person's behalf. --ttl-days <n> (default 30), --truth-ref <ref> (default refs/remotes/origin/HEAD), --quiet, --json. Sweeping is success: a minted flag and an honest no-op both exit 0; exit 1 only on a typed fault. When the workspace opts in to per-writer ledger refs, this is also the compaction moment: it folds ledger-ref records into the store and prunes the ones truth already carries.

The death ritual — inactivity, detected mechanically and confirmed by a human

Shared state needs a way to die, or the ledger becomes a graveyard of intents nobody dares delete. emerik's answer splits the job: death detection is mechanical, death confirmation is human. emerik initiative sweep finds every initiative with no artifact activity for about thirty days — where activity is the ledger record itself plus every live Pin whose prose cites that initiative, so the clock ticks only when someone works. There is no daemon, no scheduler and no server: the sweep runs when someone runs it (the emerik-prepare pre-flight runs it silently).

What it mints for an idle initiative is not a deletion but a record: an append-only superseding artifact marked inactive, committed on your current branch. That commit rides an ordinary pull request — the one ceremony professionals reliably perform. Merged means the review agreed it is dead. On the next sweep the merged flag is recognised (its record is present at the remote default branch) and the close-out runs: the initiative's orphan Pins are tombstoned — frozen ones included, never deleted — and the ledger row is closed with a superseding closed record carrying a real closure: outcome: dropped, plus the cause and statement the confirming human gave the run (--cause, --statement). A sweep run without both words defers every confirmed death with a note instead — closure is an act somebody performs, and emerik never chooses between we didn't do the work and we were prevented on a person's behalf. The row itself is never tombstoned; it stays discoverable forever.

Reject means revival. A flag that is not merged dies with its branch and owes no tombstone (branch substrate is scratch). If the work truly resumed, the next harvested Pin resets the clock; if it did not, a later sweep flags it again — deliberately, because the clock is the ration. The honest way to end an initiative on purpose is still emerik initiative close. One sweep never both flags and buries the same initiative: a flag minted seconds ago cannot yet be at the remote default branch.

Scratch vs. truth

With many people on many branches, the substrate needs one rule about which state counts. The rule is simple: branch substrate is scratch; merged substrate is truth. The Pins and Anchors on your own branch are private working scratch — they belong to that branch and die with it. Sibling and workspace substrate is read as shared truth only from the merged (default-branch) state, so unmerged work has a blast radius of zero until it merges: nobody else reads it, and it can be revised or dropped freely.

A corollary follows for cleanup: a never-merged artifact owes no tombstone. Deleting the branch is the cleanup, because that artifact was never truth in the first place — only an artifact that reached merged truth is ever tombstoned when it is retired.

Because the only moment scratch becomes truth is the merge, all substrate-sharing risk concentrates at that one gate. That is what makes the .emerik/ hunks of a pull request a first-class review surface — the point where new facts enter the shared graph — rather than lockfile noise to scroll past. Review them the way you review the code changes they travel with.

Per-writer ledger refs — optional, for a busy meta-repo

One shared ledger plus fifty engineers eventually means one hotspot: every declare, flag and close is a commit on the same default branch, and everybody's push serializes through it. The optional answer applies the scratch-versus-truth rule to the ledger itself. Turn it on by adding one field to the committed workspace manifest, .emerik/workspace.json:

{ "version": 1, "repos": [], "ledgerRefs": true }

Absent, the field is off, and every store behaves exactly as described above — this is opt-in, and the opt-in is itself a reviewed change to a committed file. Turned on, an initiative declare, close or swept inactive flag no longer commits to your branch. It lands as a blob on a git ref of your own — refs/emerik/ledger/<writer>, derived from your git identity — built entirely with git plumbing, so your working tree, your index and .emerik/ are never touched. Two different people therefore write to two different refs and cannot contend. Only your own two machines can race your one ref, and that race resolves by compare-and-swap with the same bounded retry the lease protocol uses.

Because that ref is addressed by your git identity, the opt-in requires you to have one. On a machine where user.email is unset — a throwaway CI runner, usually — a ledger write fails cleanly (exit 1) and tells you to configure it, rather than quietly filing everyone without an identity onto one shared ref and re-creating the very contention this mechanism removes. Set it once:

$ git config user.email you@example.com

Reads merge everything. emerik initiative list, the sweep and emerik verify's advisory all project the union of the committed store and every writer's ledger ref, deduplicated by id — a close written on one ref supersedes a declare committed on the branch, exactly as if they lived in the same place. If two people act on the same record at the same moment, the earlier one is the head and the other stays in the log without becoming live; the sweep says so in its notes.

Two of those readers refresh the refs from the remote first, so you see your teammates' fresh edge: emerik initiative list and emerik initiative sweep. emerik verify deliberately does not. It is a gate, its initiative line is advisory and never changes the exit code, and a gate must not fail because a network hiccup interrupted an advisory read — so verify reads the ledger refs your machine already has, as fresh as your last fetch, and stays entirely offline exactly as it always has.

Compaction runs inside emerik initiative sweep. Every ledger-ref record with no committed file yet is written into the store as an ordinary emerik(initiative): fold <ulid> commit on your current branch — which reaches the default branch through the ordinary pull request you push. emerik never opens that pull request and never pushes a branch. So main stays the compacted, human-blessed ledger and the refs are the fresh edge — the same rule as the rest of the substrate, applied one level down.

The honest limits, stated plainly. A record leaves its writer's ref only once its committed file is present at the truth ref: until the fold is merged the record stays on the ref, so a rejected fold loses nothing and a later sweep folds it again. A ledger blob this version cannot read is skipped from the projection, reported as a note, and preserved byte-for-byte forever — never pruned, never rewritten. And emerik diff compares the committed store between two git refs of a branch, so ledger refs are invisible to it by design: the merge gate reviews the folded record, which is the state that is actually becoming truth.

Pre-flight — start work on the right branch

At fifty engineers across twenty repos, branch hygiene stops being a habit anyone can hold. The emerik-prepare skill turns it into a substrate-enforced ritual you run before building on an initiative: it fetches, verifies your git lineage against the initiative's declared baseBranch and optional stacksOn, cuts a clean branch from that declared base, and stamps the initiative into the session. It runs as a dark cockpit — silent when everything is normal (at most one line), speaking only on three killer anomalies: a wrong initiative lineage, a dirty worktree carrying another initiative's changes, or a base that moved in a way that changes the decision. Each is a challenge with the observed state quoted and real options — never a confirm-normal yes/no — and consent is scoped to the initiative, so a declared lineage is the standing consent and a normal work-week produces zero prompts. It reads and writes only the shipped ledger surface (emerik initiative declare / list / close / sweep) plus plain git. The sweep is its one silent extra step: it runs the death ritual opportunistically after the ledger read, says nothing when nothing happened, and never asks a question.

The merge gate — review the substrate a pull request adds

Branch substrate is scratch and merged substrate is truth, so the merge is the one moment scratch becomes truth — and therefore the one place all substrate-sharing risk concentrates. emerik diff --base <ref> makes that gate reviewable. It reads the .emerik/ delta between the merge-base of the two refs and the head, and renders it as claims: what each new artifact actually asserts, quoted verbatim from its own text. No summarizing and no model at review time — the intelligence was front-loaded when the fact was harvested, and the diff only surfaces it, which is why two runs over the same range produce byte-identical output.

Every changed store file is classified and named:

  • Added — a new fact entering the shared graph.
  • Superseded — a new artifact replacing a named predecessor.
  • Tombstoned — a retraction, with its target and its reason.
  • Relocated — a freeze or a thaw: the same artifact, the same bytes, a different tier.
  • Removed — compaction; the working-tree copy goes, the bytes stay replayable in git.
  • Changed in place — an append-only violation, called out loudly. A committed artifact's bytes must never be edited; a change is a new artifact.
  • Unparseable — a store file that does not validate, named with its parse error rather than silently skipped.

On top of the claims it runs exactly three contamination checks — a deliberate hard cap, so the comment stays worth reading:

  1. Foreign lineage. A new Pin whose text cites a different initiative than the one the pull request declares. It is a plain string comparison against the declared initiative and the initiative that one stacks on — a declared lineage is the standing consent, so genuinely stacked work never fires. With no declared initiative passed the check degrades honestly: two or more distinct lineages cited across the branch still fire, one lineage is only a note.
  2. Untouched file. A new Pin bound to a file this range does not change. In a workspace, a Pin bound to a member repo is judged against the range that member's pointer actually moved over — and when the member checkout or its commits are unreachable, the check reports unverified with the reason rather than guessing either way.
  3. Dangling target. A new artifact superseding or tombstoning something that exists in neither the base nor this branch — substrate borrowed from someone else's unmerged work. Ordinary within-branch supersession never fires.

The command writes nothing — no artifact, no ref, no commit — and it exits 0 whenever the diff renders, flags or no flags. It reports; review decides. A team that wants a hard gate wires one host-side over --json, which carries the same entries and flags as structured data. Errors are the only non-zero exit: DIFF_BAD_REF for a ref that does not resolve or a pair with no common ancestor, DIFF_IO_ERROR otherwise.

Posting it is the host's job, never emerik's: emerik touches no network. CI runs the CLI and the host posts the comment under its own token. The whole job is fifteen lines, and this is all of it.

name: substrate diff
on: pull_request
jobs:
  substrate-diff:
    runs-on: ubuntu-latest
    permissions: { pull-requests: write }
    env:
      BASE: ${{ github.base_ref }}
      NUMBER: ${{ github.event.pull_request.number }}
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0, submodules: recursive }
      - run: emerik diff --base "origin/$BASE" > substrate-diff.md
      - run: gh pr comment "$NUMBER" --body-file substrate-diff.md

fetch-depth: 0 is what makes the merge-base resolvable, and submodules: recursive is what lets the untouched-file check reach into member repos instead of reporting unverified. The three environment values are the host's own — the pull request's base branch, its number, and the token the comment is posted under — read from the event payload rather than interpolated into the commands themselves, which is what keeps a branch name from being able to run as shell. Add --initiative <ulid> when your host records the pull request's declared initiative — how a pull request declares one is your wiring, never something emerik guesses.

The initiative spine

The initiative ledger answers what is declared. The spine answers the three questions a Monday morning actually asks: what should I work on, what is stuck and on whom, and what does nobody know how to finish. It adds no database and no status field — every answer is derived at read time from artifacts that already exist, which is why nothing here can go stale in the store.

Visibility, not enforcement

Nothing on the spine gates a write, warns, or nags. Pressure is exerted by sort order alone: the board puts what matters at the top and the oldest unanswered promise at the very bottom, and then gets out of the way. A blocked initiative is a fact on a line, not an alarm.

The departure board

emerik board is one line per live initiative, ordered into five tiers. Read it top-down; the order is the answer, so no surface re-ranks it — the CLI, the initiative_board MCP tool and the --json contract all render the same single query.

TierWhat sits there, and in what order
laneThe human's scarce declared lane, in any non-closed state — a blocked lane initiative is still tier one, and still visibly blocked. Look here first.
readyActive, with at least one open item nobody outside the team owes — something a person could pick up today. Nearly-done first, then the freshest motion.
activeThe rest of the live work: moving (a Goal closed under it this week) before not-moving, freshest motion first.
undefinedNobody has said what finished means. Oldest first — the oldest initiative nobody can define is the loudest question in the room.
blockedWaiting on someone outside the team, ordered so the oldest promise-to-check-back lands on the very last line — the most visible line of a scrolled terminal.

Closed initiatives are absent from the board by design; they have their own read, the closed rollup, below.

Terminal
$ emerik board
board: 3 initiatives  · lane 1  · ready 1  · blocked 1
lane
  Ship rate limiting  · blocked  · ◆  · left 2 ↑  · 0d ago  · waiting on design has to sign off the throttle copy · 0d · Sara  · id 01KZRQE6190YEWNFQZA6PM4CRJ
ready
  Move billing to webhooks  · active  · left 1 ↑  · 0d ago  · id 01KZRQE65JJWQFN61MV4T55H4Y
blocked
  Search relevance  · blocked  · left 1 ↑  · 0d ago  · waiting on legal has to sign the DPA · 0d · Legal  · id 01KZRQE69R9CKH7YRHV92YW8CW

Each row carries the goal text (the row's name), the derived state, the lane marker , how many items are left with the trajectory glyph — shrinking, growing, flat — when it last moved, the blocker note in the shape waiting on <what> · <age>d · <who>, and the id. A growing remainder is a discovery signal, not an alarm: the team found more of the truth.

FlagWhat it does
--team <name>Keep only initiatives whose steward is on that roster team. Requires a roster: asked of a workspace with no .emerik/org.yml it refuses (ROSTER_ABSENT, exit 1) with the roster's own reason — never an empty result that would read as “nobody”.
--state <s>Keep only these derived states — undefined / active / blocked, repeatable. Closed is never on the board, so it is not an option.
--readyKeep only what someone could pick up today.
--eventsAlso print the derived lifecycle stream, newest first, in a closed vocabulary: goal-created, goal-progressed, goal-closed, remainder-declared, remainder-resolved, initiative-blocked, initiative-closed. Closed initiatives do appear here: endings teach.
--refresh / --no-refreshRefresh remote state before reading: mirror the per-writer ledger refs (under the ledger-refs opt-in) and fetch the workspace meta-repo's main (auto-sync). --no-refresh is the offline posture: no fetch, no network. --refresh is an explicit ask, so it runs even for a member who syncs manually.

Facets subset the same query — they never re-sort it, so a faceted board is always a contiguous slice of the unfaceted one. --json emits the complete row model.

What is left — remainder items

Progress on an initiative is measured by what remains, never a percentage. A remainder item is one thing still left, declared when you discover it. Give it an --owner and it becomes a blocker — the same artifact, no special case — which is exactly what turns the initiative's derived state to blocked and puts the promise-to-check-back on the board's last line.

CommandWhat it does
emerik remainder declare <text>Declare one thing still left. --initiative <ulid> (required, must be a live head), --owner <who> (someone outside the team who owes it — this is what makes it a blocker), --by (defaults to the git identity).
emerik remainder resolve <id>Resolve a live item — --resolution done (it got done) or --resolution withdrawn (it stopped being part of finishing). An append-only superseding record, never a tombstone, so what was left and what became of it stays fully reconstructible.
emerik remainder reaffirm <id>Re-affirm a blocker — say it is still true. An append-only superseding record; it resets the check-back clock and never the item’s age. Only an unresolved, externally-owned item can be re-affirmed.
emerik remainder listWhat is left — --initiative scopes it (following the initiative's lineage, so an id from before an amend or close answers the same list as the live head), --open shows only what is still outstanding (resolved items are listed by default).
Terminal
$ emerik remainder declare "design has to sign off the throttle copy" --initiative 01KZRQE6… --owner Sara
$ emerik remainder list --initiative 01KZRQE6…
remainder: 2 left, 1 blocked of 2 recorded
  the docs page still has to ship  · id 01KZRQEG3ZY554FY99RD5FP1VH
  design has to sign off the throttle copy  · owed by Sara  · BLOCKED  · id 01KZRQEG8GH0NGK8NKKY5G0WHZ

A blocker is a promise to check back

A blocker carries two clocks. Its age is measured from the day it was declared and only ever grows — that is the 43d in the board's blocker note. Its check-back clock counts from the last time somebody actually said the blocker was still true. Past a threshold (default 21 days, tunable per workspace with spine.blockerAgingThresholdDays in .emerik/workspace.json), every surface that shows the blocker also asks about it, on one extra line.

Terminal
$ emerik board
board: 1 initiative  · blocked 1
blocked
  Ship rate limiting  · blocked  · left 1 →  · 43d ago  · waiting on design · 43d · Sara  · id 01KZRQE6…
    you said 'waiting on design' 43 days ago — still true?

$ emerik remainder reaffirm 01KZRQEG8GH0NGK8NKKY5G0WHZ
✓ re-affirmed remainder item 01KZRQEG8GH0NGK8NKKY5G0WHZ (superseding record 01KZRTW591G45SN7R5W16F62BT)
  design has to sign off the throttle copy  · owed by Sara  · BLOCKED  · id 01KZRTW591G45SN7R5W16F62BT
  check-back clock reset to 0 days — its age is unchanged, and nothing about it expires

The two honest answers are emerik remainder reaffirm — it is still true — and emerik remainder resolve. The third, saying nothing, is allowed forever: nothing auto-expires. An unreconfirmed blocker ages loudly and is never dropped, demoted, or hidden by any derivation. Re-affirming resets the check-back clock and never the age, so a blocker cannot stay young by ritual alone — and re-affirming is not motion: it says the work is still waiting, which is the opposite of something moving.

Saying what finished means

An initiative with no finish condition presents as undefined everywhere it is derived. That is not an error and nothing is migrated — it is the board asking a question only a person can answer. emerik initiative amend is the answer, and the only surface besides declare that ever sets the condition or the lane.

emerik initiative amend 01ARZ… --finish-condition "the login route rejects the 101st request in a minute"
emerik initiative amend 01ARZ… --lane        # put it in the declared lane
emerik initiative amend 01ARZ… --no-lane     # take it out

Both are the human's. emerik never derives, composes, or suggests a finish condition or a lane — the MCP tools and the shipped skills instruct agents to pass the human's stated words verbatim.

Attributing Goals to an initiative

emerik goal start --initiative <ulid> records which initiative a Goal serves. That attribution is what gives an initiative a heartbeat: a Goal closed under it this week is what makes it read as moving rather than merely open. Attribution is optional — an unattributed Goal stays perfectly valid, it just contributes no motion to any initiative.

The roster — who is on which team

.emerik/org.yml in the workspace store declares who is on which team. It is declared, engineer-owned data, and that is the whole point: who is on which team changes at org pace, not work pace. emerik deliberately refuses to derive a roster from git history or CODEOWNERS — observed contributors may seed a commented-out draft for engineers to confirm, and confirming somebody is uncommenting them.

CommandWhat it does
emerik roster statusShow the roster — present with team / member / identity counts, absent with the reason, or invalid with every named finding (exit 1 only when it is invalid).
emerik roster draftEmit a commented org.yml skeleton from observed git contributors. --write writes it (refusing when the file already exists). As written it parses as a valid but empty roster that resolves nobody — the bootstrap cannot silently fill it.
emerik roster listList the roster itself — every team and member, teams and members alphabetical, each member with its git identities, a stable opaque id, and the declared role / repos when the file declares them (version 3). A member with no git identities is marked — it cannot be matched to any recorded work — never dropped. Absent prints the reason at exit 0 (a state, not a fault); invalid exits 1 with every named finding. --json serves the same data machine-readably — the join surface for resolving a recorded git identity to a person.

The roster is used two ways, and the difference matters. As decoration — the team name beside a row — it never refuses: an unresolvable steward simply carries no team. As a questionemerik board --team platform — it refuses when there is no roster, because answering “nobody” would be a lie about people.

The roster also carries a person's auto-sync consent. A member may add sync: manual to their own entry to opt their clones out of workspace auto-sync — using the field means declaring version: 2 at the top of the file, so an older emerik refuses a v2 roster with an honest version error instead of misreading it (a v1 roster reads unchanged; absent means on, and sync: auto says the same thing explicitly). The choice is per-person: it covers every git identity the roster lists for the member, and therefore every clone where one is configured. And it is legible — the member is labeled as syncing manually on every surface that displays sync freshness, because an explicit recorded state should never read as silent decay.

Since schema version: 3 the roster also carries two display fields: role on a member (a free-form declared label — “engineer”, “tech lead”, whatever the org says) and repos on a team (the workspace repo names the team chiefly works in — display data, never validated against the workspace manifest). Both are optional, and using either means declaring version: 3 at the top of the file, so an older emerik refuses a v3 roster with an honest version error instead of misreading it (a v1 or v2 roster reads unchanged; an absent field means exactly what it means today: nothing).

Closing, and the closed rollup

Closing is an act, and it is a ritual: emerik initiative close <id> requires an --outcome, a --cause when the outcome is not done, and a --statement in the human's own words. A close that does not say what it must is refused with a message stating what a closure must say, and nothing is written — it refuses for missing words, never for what the words say. Records written before the vocabulary existed stay valid history: the requirement lives in the closing engine that both the command and the MCP tool call, not in the artifact schema, so nothing already committed is invalidated and there is no migration to run.

A complete close prints the support check beside the closure: either the number of externally-owned remainder items the record carries behind a prevented claim, or the honest unsupported-by-record line. It is displayed after the write, never before it: an unsupported closure still commits, carrying its mark.

Closed initiatives leave the board, so emerik initiative rollup is where they are read: the listing of what ended, and the counts per team and quarter. It diagnoses the system — a quarter of didntDoTheWork reads as overcommitment, a quarter of prevented reads as external dependency — and it never ranks, scores, or compares people. The steward rides the listing as the record of who closed the record; it is never a grouping key.

Terminal
$ emerik initiative rollup
rollup: 2 closures  · 1 unsupported by record
  2026-Q3  · team platform  · 2 closed  · done 1  · dropped 1  · cause prevented 1  · unsupported-by-record 1
  Move billing to webhooks  · done  · closed 2026-08-11  · team platform  · "webhooks have been live for a week"  · id 01KZRQF0N6F0T57DMN3E6J6129
  Search relevance  · dropped  · prevented  · unsupported-by-record  · closed 2026-08-11  · team platform  · "legal never came back on the DPA"  · id 01KZRQER1Y3X89X2GMZ4K1YJM0

unsupported-by-record marks a closure that claims we were prevented while the record carries no remainder item anybody outside the team ever owed. It is displayed, never enforced — it did not block the close and it never will; it says only that the record does not back the claim. A closure written before the outcome vocabulary existed aggregates under an explicit unspecified bucket and lists with its fields absent: emerik reports the absence rather than inventing an outcome on a person's behalf. --json emits the whole report, and --no-refresh reads offline.

Two different rollups

emerik rollup <domain> (top-level) distils a domain's decision history into a narrative Anchor — a write. emerik initiative rollup aggregates closures — a pure read. One distils decisions; the other counts endings.

The sidecar contract — one substrate, any window

Epic 31 did not add a subsystem. It widened surfaces that already existed — the Goal, the roster, the board, the metrics, the lease — until they compose into one contract: every question a project window asks is answered from the substrate, from recorded facts and honest absences, and no window ever has to derive anything itself.

A goal carries a person and a place. The owner is the acting git identity, recorded when the Goal starts and carried verbatim through every supersession — observed at write time, never declarable on someone's behalf, never attributed after the fact. Territories scope the Goal's work in the same vocabulary leases use, and they are what makes an honest cycle time derivable at all: first observed evidence on the territory — a lease claim, a commit — never a declared start field.

The roster turns identities into people. Recorded work carries git identities; .emerik/org.yml — engineer-owned, declared, versioned — says who they are. Resolution is an explicit read-side join, always in one direction: an identity the roster does not list is presented raw for a human to place, never fuzzy-matched and never dropped, and people are always listed alphabetically — an ordering, never a ranking.

The board row answers whole. An initiative's row carries its lifetime closed-goal count beside the weekly one, the full aged remainder list — blockers and plain items alike — and feed events name their recorded actor. Nothing on the row is a score; every number is a count of recorded artifacts this read can see.

The ledger serves what finished. One row per Goal that ever completed: cycle, diff facts, rework-after-close, human-trace components, land record — where 0 is never conflated with unknown. What could not be observed says so, with the reason, as a fact in the payload.

The lease links live work back. A claim can declare the Goal it serves — resolved from the goal's recorded bytes, never inferred from territory overlap — and the lease list serves the complete now-picture: who holds what, under which goal and initiative, and how recently a commit actually touched that ground, with "nothing observable" a stated absence rather than a zero.

One contract, then. Recorded facts at write time; derivations in the engine, once, under both the CLI and the MCP surface; canonical ids so the same initiative joins across every read; and absence always a stated state — observed: false with the reason, an owner a goal simply does not have, an identity no roster lists. Any window over the substrate — the terminal, an agent on the MCP surface, anything built above them — reads the same facts and the same absences, and none of it polices anyone: the substrate describes work, it does not render verdicts about people.

The mirror signals — a person sees their own first

Epic 32 adds the product's only per-person surface, and its ordering is the design: the engineer sees their own signals before anyone else sees anything. emerik metrics mirror (MCP metrics_mirror) defaults to your own row — resolved from the acting git identity, the same one recorded on your goals — and --all serves every declared roster member's row: the same rows, from the same computation, alphabetical always. There is no PM-only dossier, no way to point the surface at one other person, and no export of any kind — per-person data never leaves the instrument.

What the signals are. Evidence about a person's own recorded work, computed once in core and served identically to every window: goals closed, test-contact fraction, median cycle hours, goals without a recorded human trace, tenure as observed in this repo's history, and churn on the person's own recent lines. Every metric ships its own derivation, so a number is never a black box; and under 10 closed goals or under 30 days of observed tenure the row is a typed refusal that states everything that is missing and carries no metric at all — a thin number does not exist on the wire, so every surface refuses identically. Seeing your own refusal is the mirror working.

What the signals are not. Never a score, never a ranking, never a comparison across people, never exportable. Rows are ordered alphabetically by declared name and never by a metric — pressure lives in sort order, and there is none here. Each field is meant to be read against the same person's earlier evidence, not against a colleague's.

One metric refuses outright. Review-comment density — the volume of human review comments a person's changes drew, per unit of change — has no honest source: the store's only human review record names the reviewer who taught, never whose change drew the comment nor its size. So the metric is a typed refusal rather than a substitute: agent review output is never counted as human, no proxy or blend is served, and a number appears only when a capture recording both facts exists. Refusing is the design, not a gap being papered over.

And one signal cannot fire at all. The planning record calls it the YOLO signature: agent-produced work landing at scale and speed with no recorded human engagement with the reasoning. It is defined as a strict conjunction of six components — high agent-synthesized share, no human trace, zero review artifacts, no remainder appended at close, a large diff, a short derived cycle — and it fires only when every one holds at once: never any-of, never a weighted sum, never one component alone. Today two of the six have no honest source in the store, so the conjunction cannot fire — and the payload says so structurally: its fired field is the literal type false, no partial evidence is served in its place, and if it ever fires it will describe an observed pattern in recorded work — a set of facts to open — never characterise a person.

History & time

Because the substrate is append-only on git, the commit history is the decision log. Three read-and-maintain commands operate over time.

CommandWhat it does
emerik historyReplay the .emerik/ decision log, or one artifact's lineage — the why behind a fact.
emerik compactGarbage-collect superseded Pins (keep the latest fact; removed bytes stay replayable in git). Try --dry-run first.
emerik rollup <domain>Distil a domain's decision history into a retrievable narrative rollup.
emerik history <artifact-ulid>     # lineage + why
emerik compact --dry-run           # preview the GC, mutate nothing
emerik compact                     # report the before→after .emerik/ byte delta

Land a branch

An emerik-assisted change leaves a trail on its branch: the code commits, plus the harvest, Anchor, Lease and review commits emerik made along the way. That trail is exactly what you want while the work is in flight — and exactly what you do not want in the trunk's history. emerik land squashes it into one commit without losing a single decision.

CommandWhat it does
emerik landSquash the current branch's range into ONE commit — emerik(land): squash <run-id> — with the per-decision timeline folded into its body and the pre-squash head kept under refs/emerik/archive/<run-id>.

It refuses unless all of these hold, and says which one failed: you are not on the trunk, the working tree is clean, a live Review covers this head and is approved, emerik verify is green, and the range carries at least two commits. Error codes are LAND_ON_TRUNK, LAND_DIRTY_TREE, LAND_NO_APPROVED_REVIEW, LAND_VERIFY_FAILED, LAND_NOTHING_TO_SQUASH and LAND_TREE_MISMATCH.

FlagWhat it does
--dry-runRun every gate, print the range, the folded event count and the exact commit message; rewrite nothing.
--yesSkip the interactive confirmation. Required on a non-TTY or with --json.
--trunk <ref>Ref the range is measured against. Defaults to @{upstream}, then main, then master.
--summary <text>A free-text paragraph placed above the folded timeline in the commit body.

A worked example — five commits become one

Before. The branch carries the code and the decisions emerik recorded around it:

git log --oneline main..feature
9f2c1ab emerik(review): open 01J0R…      # the quorum's routing record
4d81e0c emerik(pin): create 01J0Q…       # a harvested decision
b77a3e5 feat(pay): refund
2ac09d1 emerik(pin): create 01J0P…
e50fb42 feat(pay): capture

Preview, then land:

emerik land --dry-run                         # gates, range, event count, message preview
emerik land --summary "payments: idempotent capture"

After. One commit — and the tree is byte-identical to what was reviewed:

git log --oneline main..feature
7c0e94d emerik(land): squash 01J0S…

Its body is the timeline. Nothing is dropped — a store commit whose subject does not parse still gets its line:

payments: idempotent capture

Emerik-Timeline: 1
Emerik-Archive: refs/emerik/archive/01J0S…
Emerik-Event: 2026-07-20T09:04:11+00:00 dev@example.com 2ac09d1… emerik(pin): create 01J0P…
Emerik-Event: 2026-07-20T09:11:38+00:00 dev@example.com 4d81e0c… emerik(pin): create 01J0Q…
Emerik-Event: 2026-07-20T09:12:02+00:00 dev@example.com 9f2c1ab… emerik(review): open 01J0R…

So emerik history is unchanged by the squash: it unfolds those lines back into the same events, in the same order, with the same who and when, each still carrying its original commit id. Those ids stay resolvable because the archive ref keeps the pre-squash head reachable — which is also what lets emerik history <artifact-ulid> still reconstruct an artifact that was created and compacted away inside the squashed range.

That holds however many times you land. If you land, keep working, re-review and land again, the second squash folds the first land's commit into its own timeline — and emerik history reads straight through it, so the decisions from before the first land are still there, still in order, still pointing at the commits they were made on.

The archive ref is the undo button: git reset --hard refs/emerik/archive/<run-id> puts the branch back exactly as it was. It is not optional and it is never overwritten. Archive refs are local until you share them — git push <remote> 'refs/emerik/archive/*'.

land rewrites only the branch you are standing on. It never pushes and never touches the trunk — a branch that already has an upstream will need a force-push afterwards, and the merge itself stays yours to make.

Use with an AI agent

emerik init connects supported coding agents to your repo's committed knowledge. Before changing code, an agent can retrieve the relevant Pins and Anchors, then work inside a Lease.

Start here — the guided path

Start with emerik-guide. Tell it what you need:

  • Orient“orient me”, “where am I?”, “what should I do next?” It derives where the repo stands from live substrate facts (index_status, anchor_list, lease_list, a committed Goal brief) and the installed skills' own declarations, then recommends the next step as a short menu — never a hardcoded script.
  • Tour“give me the emerik tour”, “show me how this works”. A hands-on walkthrough of the whole loop, each leg delegating to the real skill.
  • Step-through“walk me through emerik-goal step by step”. It wraps any suite skill with its gates made explicit, confirms at each gate, and reports what was read from and deposited into the graph.

What emerik init wired

Every line below is written only for the agents your selection names — see the init section for the full per-agent list, the removal rules and the degradations.

  • the emerik mcp stdio server, registered as an MCP server named emerik ({ "command": "emerik", "args": ["mcp"] }), in whichever project MCP config the agents you selected read — .mcp.json, .cursor/mcp.json, .codex/config.toml, opencode.json, .gemini/settings.json or .agents/mcp_config.json. Always merged additively, so your other servers are untouched. Windsurf and GitHub Copilot have no project-scoped file emerik can write, so it prints what to add by hand instead.
  • .claude/skills/ and .agents/skills/ — the twenty-two shipped skills, byte for byte, in whichever of the two trees your selection needs (Claude Code reads the first; the other seven agents read the cross-tool second): emerik-brainstorm, emerik-brief, emerik-prd, emerik-architect, emerik-breakdown, emerik-discover, emerik-goal, emerik-plan, emerik-implement, emerik-review, emerik-address-review, emerik-guide, emerik-retro, emerik-orchestrate, emerik-onboard, emerik-prepare, emerik-scope, emerik-status, emerik-skillsmith, emerik-explain, emerik-consult and emerik-reconcile.
  • the four reviewer roles emerik-review fans out (blind-hunter, edge-case-hunter, acceptance-auditor, substrate-auditor), installed into whichever subagent directory the agents you selected read — .claude/agents/, .codex/agents/, .opencode/agents/, .gemini/agents/, .agents/agents/ or .github/agents/.
  • a delimited <!-- emerik:begin -->…<!-- emerik:end --> block in each instructions file your selection claims, so every harness reads the same context: AGENTS.md and .agents/rules/emerik.md are created when claimed, while CLAUDE.md and GEMINI.md are decorated only when you already have them.
One boundary

An agent touches the graph only through the MCP tools — never .emerik/ on the filesystem and never the engine directly. The tools are the sanctioned, audited surface; the store stays append-only and honest.

The sixty-two tools

The MCP surface is exactly sixty-two noun_verb tools, each a thin wrapper over the same engine the CLI calls:

ToolWhat it does
graph_retrieveAnswer a question with one hybrid search over Pins and Anchors — exact terms and meaning fused into a single ranking — with each hit decorated from the coupling graph (who governs it, which practice documents explain it, and whether anyone holds a write lease over that territory right now); decoration never changes a hit’s rank. Optional paths (the files you are about to edit — their governors come back first, as facts that cannot lose to a similarity score), kinds, steward, enforcement, since and budget. Every hit says whether the code it describes has moved (drift, read from your working tree) and whether it contains a term you typed (lexicalMatch). Every answer carries an envelope saying what it is worth and an outcome; an empty answer always names a cause, so “nothing matched” and “the index could not look” are never the same silence. The envelope’s tier says which engine answered — floor means this machine has no hybrid search binary and the lexical floor answered instead, so exact identifiers and error strings still match and paraphrases do not. Practice documents are explainer_search’s corpus unless you pass kinds: ["explainer"].
graph_blast_radiusGiven a file path or an artifact id, name what else moves with it — 2 or 3 hops over the derived coupling graph, each result carrying its hop, the kind of coupling that reached it (co-change or import) with its weight, and the Pins, Anchors and practice documents that govern it. The edges are DERIVED, not parsed (decayed co-change plus import-line scanning — no AST), so the answer is a lower bound on real coupling and the envelope says when it is a tighter one and why. An empty answer over a complete derivation means “nothing else is coupled that we can see”. depth 1–3 (default 2); limit caps the whole answer, nearest hops first — a seed that resolves to several paths still returns at most limit results — and a capped answer is reported as truncated, never passed off as a small radius. Read-only.
anchor_listList the live Anchors and their checks / enforcement levels. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries (an excerpt, never the full content). Narrow engine-side with subtype / enforcement / steward / governsPath; page with limit + cursor while nextCursor is present; pass id for the full Anchor — on every bounded list tool id resolves within the same call's filters, so an id outside the narrowed set returns an empty page (“not in this filtered set”, not “no such artifact”).
anchor_authorAuthor a steward Anchor, or promote a reviewed Pin (the --approve review gate is preserved). Optional territory/holder gate the write on the live Leases — a territory held by another refuses it (LEASE_HELD).
goal_startStart a team's active Goal — the concurrent work-unit (links a committed brief with --brief). Optional territories (the lease vocabulary) scope the work and ground the derived cycle time; the acting git identity is recorded as owner — observed, never passed in.
goal_advanceRecord progress on — or --complete — a Goal (append-only supersession). territories sets the superseding Goal's scope (replaces; attach later if it started without any). Completing an initiative-attributed Goal accepts remainder: what finishing it revealed is still left.
goal_statusShow a team's active Goal (or one by id); null when none, never an error. A completed Goal read by id carries a derived cycleobserved: true with firstObservedAt and unrounded cycleHours, or observed: false with the reason: no evidence is a reported fact, never a fabricated start.
goal_listList the live Goals — active by default, --all includes completed. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
dependency_declareDeclare a contract-dependency on a published Anchor — never live code under lease.
dependency_statusReport each dependency as current, changed (a coordination event), or retired.
dependency_listList live dependencies — team / anchorId (lineage-aware) filters. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
lease_claimClaim exclusive write on a territory before writing. goalId links the lease to the live Goal the work runs under — title and initiative stamped from the goal's recorded bytes at claim, never invented; an unknown id refuses before anything is claimed.
lease_renewExtend a Lease you hold.
lease_releaseGive a territory back.
lease_listInventory the active Leases as complete Now rows — claim facts, the declared goal link when one was claimed (initiativeId canonicalized to join board rows), and lastCommit on each territory: a union to branch on — an observed commit with minutes as computed, or observed: false with the reason. An absence is a stated fact, never a zero; holder-to-person resolution is your join through roster_list.
trail_statusRead the stigmergic trail — hot zones (heat) + lease markers — an agent routes around, no messaging (--remote, --limit).
dashboard_snapshotRead the substrate live view — active exclusive-write leases (“who is working now”), live Goals, the departure board's top slice, contention hot zones, the rework rate, the review funnel, retrieval recall (SM-4, projected from the recorded replay runs — a union on measured, both arms kept apart, and the tool never runs a replay), and store/index health (both local projections — search and relational — each with its own state and staleness, the search leg’s published tier and loadable activeEngine, and how many answers have seen the two disagree since the last rebuild), in one point-in-time snapshot (see Substrate live view). Read-only.
workspace_statusPer-repo drift + substrate counts across the workspace, plus the sync block — this clone's auto-sync cockpit: active or inactive with the reason, ahead/behind vs origin/main, last push with carry-along counts, standing retry queue and reconciliation state. The positions block is recorded clone telemetry: who (roster-attributed, else the raw git identity), machine, each member repo's checked-out head, and lastSeen, stale after 7 idle days. An opted-out member reports sync.reason: 'opted-out' with an optOut record; their rows carry syncsManually, and positions.manualMembers lists opted-out members with nothing recorded — labeled, not invisible; chosen staleness is not decay. Observational — informs, never gates. Read-only (the MCP twin of emerik workspace status; see Multi-repo workspaces).
workspace_reconcileThe reconciliation door: clear a standing auto-sync halt and mechanically re-prove convergence. Requires confirmHeadSha equal to the standing record's headSha — quoting the observed state is the proof the record was read; a mismatch is refused with both shas and clears nothing. On a valid token the halt clears and the sync primitive re-runs with a forced fetch — still-halted answers honestly with a fresh record when the divergence still conflicts. Never resolves content, never touches member repos; the git work comes first, and the emerik-reconcile skill walks it. The MCP twin of emerik workspace reconcile.
roster_listList the declared roster (.emerik/org.yml) as data — every team and member with git identities, a stable member id (opaque — never parse it), and the declared role / repos when the file declares them (version 3). Alphabetical by name — an ordering, never a ranking. The join to a person is the exact git identity (strip @<hostname> from a lease holder first); an identity no member lists is unresolved — show it raw and ask a human, never fuzzy-match, never drop — and a member with no identities is marked, never omitted. An absent roster is a present: false success with the reason verbatim. Read-only; never derived from git history (the MCP twin of emerik roster list; see The roster).
review_openRecord + route an agent-quorum review — cleared by quorum, or escalated to a human when it edits a high-dependent Anchor. Declares edited Anchors with anchorIds, verdicts with verdicts, the steward's digest scope with domain.
review_listList the live routing records — routing filters cleared / escalated, domain filters the digest scope. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries (reviewer prose elided to verdictCount / findingCount); pass id for the full Review.
review_blessBless an escalated review — clear the change on the steward's authority, through code. Mints a superseding decision Review (optional note, self-asserted by); no status field.
review_adjustRequest adjustment on an escalated review — send the change back for rework, through code (a superseding decision Review).
steward_resolveResolve stewardship for a code path — the Anchor(s) that govern it and the accountable steward, most-specific first (inverted CODEOWNERS). Deliberately not paged: constraint discovery for a path is complete and never truncated.
steward_constraintsList a steward-agent's standing constraint set — its constraint-Anchors, in accrual order (the design process is the training signal). Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
antibody_mintMint an antibody (a constraint learned from a past failure) attached to a domain steward — a block antibody requires a human bless before it binds.
antibody_blessBless a pending high-impact antibody so it binds — the human gate before a block antibody can fail CI (OQ-8).
antibody_listList a domain steward's antibodies — the constraints minted from past failures, with their bless state (the bless/TTL descriptor is kept on every entry). Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
antibody_retractRetract a false antibody — an append-only Tombstone (no in-place delete); it no longer binds future agents.
metrics_reworkReport the drift-driven rework rate (SM-1) and its governed-path control — a read-only derivation over the decision log; a baseline plus an optional ongoing window, each with the governed / ungoverned split beside it.
metrics_review_timeReport the review funnel per Goal — the agent-quorum auto-clear rate + a drafted human-review-load proxy (reviewer-hours are not instrumented).
metrics_ledgerThe closed-goal delivery ledger — one row per Goal that ever completed (supersession-resolved, newest close first: a time ordering, never a ranking), each carrying its derived cycle, diff facts (lines, commits, and distinct files touched — a path touched in several commits counts once), rework-after-close, human-trace components, and land record. diff and land are unions a consumer must branch on — observed: false / landed: false carry the reason as notes, never zeros standing in for the unknown. reworkWindowWeeks tunes the rework window (default 4 — a PRD assumption, echoed back); touchedTests is a named path-shape heuristic, never ground truth; the human trace refuses the unjoinable appended-remainder component rather than approximating it. Evidence about goals, never a person score. Read-only (the MCP twin of emerik metrics ledger).
metrics_recallRetrieval recall replayed over this repo’s own citations — SM-4’s first measurement, reported as a floor, never a point estimate (see Instrumentation — retrieval recall). A union to branch on: measured: false names an absent history (no runs / no ledger — call again with run: true) or a damaged one (ledger unreadable / newer ledger version, whose remedy is deleting the local ledger); measured: true carries the latest run as persisted, the headline k, the whole trend (one point per recorded run, oldest first), a delta that is absent below two runs rather than zero, the lower-bound envelope with each cause’s remedy, and both definition strings. The two arms — act-verbatim and footprint — are reported separately and never merged. Reads by default (one small local file); run: true measures a fresh run first, bounded by citation count and writing only under .emerik-local/. Measures retrieval, never a person. Read-only (the MCP twin of emerik metrics recall).
metrics_hit_rateThe warning hit-rate — of the reviews escalated to a human, the share the substrate had already warned about before the review was opened (SM-5, see Instrumentation — the warning hit-rate). Takes no parameters. Three mechanisms, and a hit needs only one: anchor (a high tier — the routing engine’s own recorded cause), antibody (a live binding antibody, minted before the review and unexpired at its instant, one of whose governed globs matches a file the findings named), explainer (a live practice document predating the review whose live member anchor is one the review declared). byMechanism counts escalations, so the three may sum past hits; attributions carries the ids and what each matched. Stated as a floorepistemic is always lower-bound — with withoutPaths naming the findings coverage boundary. overridden reports blessed, sent back, and blessed-then-reworked. The denominator is the same live escalated set metrics_review_time counts, and zero escalations is a designed success. Measures the substrate, never a person. Read-only (the MCP twin of emerik metrics hit-rate).
metrics_attentionThe attention audit — not whether retrieval surfaced the right artifact, but whether anyone then leaned on it (see Instrumentation — the attention audit). Labels each recorded flight with what happened next — cited / edited / ignored, with NULL meaning not yet judged and never ignored — and, per live goal, sorts the governing artifacts over the touched paths into surfacedAndCited, surfacedNotCited (the ignored arm), citedNotSurfaced and noRecordedConsultation, with the goal’s rework split beside the ignoring — correlation, never cause. A union to branch on: measured: false names unavailable / not-built / partial (a damaged local index, whose remedy is a rebuild) / no-flights. Optional goal narrows to one live goal; an unknown id comes back as unknownGoal inside a measured report. The recorder is machine-local, gitignored and rebuild-wiped, so surfaced is a floor and noRecordedConsultation is a ceiling on the accusation — report it as “no recorded consultation”. Writes only the followOn labels back into the local recorder; nothing in .emerik/ moves. Measures the substrate, never a person (the MCP twin of emerik metrics attention).
metrics_mirrorThe mirror — the only per-person surface, engineer-first (FR-123): answers for the acting git identity of the repo the server stands in (no identity input — it cannot be pointed at somebody else); all serves every declared member's row, alphabetical always. A union to branch on: available: false names no-roster / identity-unset / identity-undeclared with the fix; a row under the floors (10 goals / 30 days tenure, echoed) is a typed refusal carrying no metric at all; review density always refuses, and unreviewedDelivery is always fired: false — the strict six-component conjunction cannot fire while two components have no honest source. Evidence about a person's own work — never exportable, never ranked, never a score. Read-only (the MCP twin of emerik metrics mirror; see The mirror signals).
brief_authorAuthor a brief — the durable, append-only planning head; supersedes revises the live one (steward defaults to the git identity of the repo the agent stands in).
prd_authorAuthor a prd (product requirements document) — same append-only grammar as brief_author.
architecture_authorAuthor an architecture (a solution/architecture design) — same append-only grammar as brief_author.
brief_listList the briefs — live by default, all includes superseded/tombstoned history (returns the live head with supersedes). No brief_status tool — the live head is served here. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
prd_listList the prds — live by default, all includes history. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
architecture_listList the architectures — live by default, all includes history. Bounded (FR-39): returns { items, total, nextCursor } — a page of summary entries; narrow with the filters, page while nextCursor is present, pass id for the full artifact.
initiative_declareDeclare an initiative — the workspace intent ledger record (goal, committed brief, declared lineage: baseBranch + optional stacksOn); lands in the meta-repo store, no start-gate. finishCondition is required and is the human's stated condition, passed verbatim — never composed on their behalf; the optional declaredLane is set only when the human says so. See Multi-repo workspaces and The initiative spine.
initiative_listList the live initiatives (supersession-resolved) — optional status filter (active / inactive / closed). No initiative_status tool — the live head is served here.
initiative_closeClose a live initiative — an append-only superseding record with status closed (never a tombstone); the ledger row stays discoverable. The closure is the human's: an outcome (done / dropped / superseded / folded), a cause (didntDoTheWork / prevented, required iff the outcome is not done) and one closingStatement in their words — optional today, and all-or-nothing: a half-specified closure is refused.
initiative_amendAmend a live initiative — set its finishCondition, or set/clear the declared lane (an append-only superseding record). Both are the human's: pass their stated words verbatim and set the lane only when they say so. See The initiative spine.
initiative_sweepSweep the ledger for inactivity — flag initiatives idle past the TTL (optional ttlDays, default 30) as an append-only record that rides a pull request to review, and close out flags already merged into truth (optional truthRef). A close-out writes a closure, so cause and closingStatement are the confirming human's words, passed verbatim; without both, every confirmed death is deferred with a note and nothing is closed or tombstoned. See the death ritual.
initiative_boardRead the departure board — every live initiative, tier-ordered, with what is left, where it is going, and who it is waiting on. The same query the CLI renders, so an agent asking “what should I work on” reads the exact ordering a human sees, and never re-ranks it. Mirrors the CLI flags: team / state / ready / events / refresh. Read-only; closed initiatives are absent; a team question with no roster refuses (ROSTER_ABSENT). Each row carries lifetime goalsClosed (the completed goals this read can see — evidence, never a score) and the full aged open-remainder list, blockers and plain items alike. Remainder- and initiative-kind events name their actor — the recorded declarer or steward on the artifact, never who performed the act.
initiative_rollupAggregate the closed initiatives — every closure (outcome, cause, statement, when — and now its finish condition, goals closed, and the still-open remainder, aged from declaration) plus counts by outcome and cause per team and quarter. It diagnoses the system, never a person, and never refuses (it asks no team question). Read-only; refresh:false reads offline.
remainder_declareDeclare one thing still left on an initiative — progress is what remains, never a percentage. An externalOwner makes it a blocker (the same artifact, no special case).
remainder_resolveResolve a live remainder item — done or withdrawn — as an append-only superseding record, never a tombstone.
remainder_reaffirmRe-affirm a blocker — record that what you are waiting on is still true. Resets the check-back clock and never the item’s age; only an unresolved, externally-owned item can be re-affirmed.
remainder_listList what is left — optional initiativeId scope (it follows the initiative's lineage: any id in the lineage — the board row's id included — answers the same list) and open-only filter. Ships with the writes: you cannot resolve what you cannot list.
index_statusReport whether the two local projections are built and fresh — the hybrid search index under search, the relational index under relational. The search leg also reports tier (which projection is published: full or the lexical floor) and activeEngine (which engine this machine can load: lancedb, node:sqlite or none). The two can disagree in either direction; a build this machine cannot load is still built but is not served, and the description names what each direction answers instead of reporting it healthy. The retired Pin and Anchor legs are gone from the payload as of 0.5.0.
index_refreshAbsorb newly-committed Pins, Anchors and practice documents into the local index — one call maintains both projections, embedding only what changed, and stamps both with the same generation receipt. It also carries the search leg across tiers: a lexical-floor build is promoted to hybrid once this machine can load the hybrid engine, and a hybrid build is rebuilt onto the floor if the binary has gone. The relational leg re-projects its record-derived tables here; the coupling graph is re-derived in full by emerik index rebuild.
explainer_declareRecord a practice document that already exists in the repo — one document, one practice. It reads and hashes the file; it never writes prose. anchors names the member Anchors that carry the practice’s obligation (declared on the explainer, never on the Anchor, so an Anchor may belong to two practices or none); every id must be live, and a dead one is refused with its successor named. Re-declaring the same path supersedes the previous record, and is also how membership is updated — so repeat anchors on every re-declare of a document that has members: omitting it while a member is live is refused (nothing written) with the current live ids named, and detachAnchors: true is the deliberate removal. The document is normative: code that disagrees with it is the defect, and code drift never stales it.
explainer_listList the live practice documents. Each entry is the record plus its read-time state: drifted means the file has moved ahead of the search index — not that the document is stale; the file always wins — missing means the file is not on disk here, and problem names a record whose own path cannot be read here at all (nothing reads or indexes it). knownWrong means a member Anchor is no longer live, so the document is known to be wrong until it is updated — information, never an error; re-declare with the live ids to clear it. region says where the practice applies, derived from the live members’ governed paths: paths, repoWide, or none (nothing enforces it yet). Each entry also carries its derived citation facts: citations (chain-aggregated), lastCitedAt, byMoment, heat and cold — all computed per read from the committed citations, never stored on the record. Heat is an instrument, never a gate, and cold means unused, not wrong. paths narrows the page to the practices whose region covers at least one given path — filtered in the engine before pagination, so total stays honest — and a practice with region none always passes: the region narrows relevance, never grants it. Bounded (FR-39): returns { items, total, nextCursor } — page with limit + cursor while nextCursor is present, pass id for one record.
explainer_searchAnswer “how do we do X here” over the practice documents. Returns pointers at real files — never a copy of the prose — so there is exactly one version of the practice. Each hit carries drifted and knownWrong: read the document either way, and read its successor Anchors alongside it when it is known-wrong. Hits also carry the citation facts (citations, byMoment, heat, cold) as information only — ranking is by fused lexical+semantic relevance — exact terms and meaning searched together over the one global index — and heat is never blended in, so a rarely-cited practice is exactly as findable as a heavily-cited one. Each hit carries its derived region too, so relevance to the work at hand can be judged from the hit itself before the document is opened. The answer carries an envelope beside its results and note, so an agent can read what the answer is worth rather than parse it out of a sentence.
explainer_conflictName the conflict between what you are about to do and an anchored obligation a practice document explains. Give it the Anchor id (and optionally your intent, echoed back); it returns the clause verbatim, the declared enforcement, and every practice document that explains it. It fires on anchored obligations only, never on prose disagreement — prose has no id — and it never blocks: warned: true is a success, not an error. The answer carries both ways forward by callable name: explainer_declare to amend the explanation (the override is the amendment), or anchor_author + review_open to change the obligation itself. A superseded Anchor is answered with its live successors, not refused.
explainer_citeRecord which clause of a practice document authorized what you did. This is a citation, not an attestation — never use it to log that you read something; name the clause you leaned on and the act it authorized. It is committed on your branch, so it rides the same pull request as the code and a reviewer checks it against the diff. moment (authoring | implementation | retro) is required — it is the axis the team reads to learn when practices actually get reached for. anchor is for a clause that is an anchored obligation of that same document (validated as a live member); most clauses are prose, and an anchorless citation is normal. goal appends one progress note, validated before anything is written. The answer carries prBlockpaste it into the pull-request body, the one place a human sees the citation while deciding to approve — plus the practice’s heat after your citation.
explainer_removeRetire a practice document that no longer describes how work is done here. Removed, not flagged: there is no “stale” marker, because a warned document is still in the retrieval pool — it still comes back from search and still shapes work. Ask a human first; a read surface calling a document a deletion candidate is an invitation to ask, never an instruction to remove. Cold means unused, not wrong — a quiet practice may be perfectly correct. Afterwards, delete the document yourself in the same change (deleteTheDocument names the file); emerik has never written or deleted prose. Historical citations survive, inert.

emerik verify stays a shell gate, not a tool — it is the CI / verification step (exit 1 on any block-level failure), so an agent runs it from the terminal.

The twenty-two shipped skills

emerik-brainstorm

Idea → grounded ideation, from anywhere — including a fresh repo with zero Pins (an empty substrate is a valid entry state, not a special mode). It reads what the substrate already committed first (index_statusgraph_retrieveanchor_list), and when those come back empty it says so and starts from a blank slate. It facilitates the session, deposits any constraint stated at the moment of statement as a constraint-Anchor through the review gate (anchor_author, confirmed with steward_constraints), then lands a committed session doc under .emerik-planning/ that harvest captures — and hands off to emerik-brief.

emerik-brief

Idea → durable brief. It grounds in the substrate, picks up the brainstorm session doc when one exists (a brief from scratch is valid too), and writes the brief body from a template. It commits the body under .emerik-planning/ before minting the artifact, then mints the brief via brief_author (--doc-path, --steward) referencing the committed body. A revision supersedes the live head (append-only, checked with brief_list first), never an in-place edit. The brief is the input the planning head grows from; it hands off to emerik-prd to grow a full PRD, or to emerik-goal for a delivery-sized slice.

emerik-prd

Brief → durable PRD. It reads the live brief the PRD grows from (brief_list, surfacing a fork if more than one live head exists, routing back to emerik-brief if none), then writes the PRD body from a template — descriptive features stay prose (harvested Pins), while each binding requirement is offered as a candidate constraint-Anchor at the moment of statement through the review gate (anchor_author with an owner-consented enforcement level — BLOCK / WARN / ADVISORY — confirmed with steward_constraints; a withheld consent is recorded as declined). It commits the body under .emerik-planning/ — collision-checked, so a revision writes a new file — before minting the prd via prd_author (--doc-path, --steward); a revision supersedes the live head (append-only, checked with prd_list first). It hands off to emerik-architect to turn the PRD into a solution design, or to emerik-goal for a delivery-sized slice.

emerik-architect

PRD → durable architecture. It grounds in the substrate — including the standing Anchors that already govern the code (anchor_list) — reads the live prd the design answers (prd_list, surfacing a fork if more than one live head exists, routing back to emerik-prd if none), then writes the architecture body from a template. Each decision that governs a code region is offered as a candidate governing Anchor at the moment of statementanchor_author --subtype contract --governs '<glob>' (the inverted-CODEOWNERS surface, confirmed with steward_resolve) — carrying a real grep/test/lint check where expressible, and an honest checkless advisory/warn sticky-note where not (an agent check reports skipped/WARN today; block only with a runnable check). Non-regional constraints keep the constraint-Anchor leg. The consent gate is conversational and agent-side — the machine never mints an Anchor unprompted. It commits the body under .emerik-planning/ — collision-checked, so a revision writes a new file — before minting the architecture via architecture_author (--doc-path, --steward); a revision supersedes the live head (append-only, checked with architecture_list first). The governing Anchors are the enforcement surface, so the document and the enforcement surface are the same thing. It hands off to emerik-breakdown to sequence the prd and architecture into ordered Goal briefs, or to emerik-goal for a single delivery-sized slice.

emerik-breakdown

A planning head → an ordered sequence of Goal briefs the engineering loop picks up directly. There are two sanctioned entries. On the founder path it grounds in the substrate, reads BOTH live heads (prd_list and architecture_list, surfacing a fork on either if more than one live head exists, routing back to emerik-prd or emerik-architect if either is missing), then writes and commits one initiative brief at .emerik-planning/initiative-<slug>.md — collision-checked, committed before any Goal brief cites it. On the PM path it accepts a scoped epic brief directly — the live brief head whose docPath is .emerik-goals/<epic-slug>/epic-brief.md, found through brief_list — with no live prd and architecture required; it still pulls whatever planning context the substrate has and states plainly what was absent. On that path each acceptance criterion arrives as a deposited candidate constraint-Anchor, and the breakdown ratifies it: one superseding anchor_author (--supersedes the candidate, --governs the code region, the engineering steward named) that blesses the contract and binds it to the region in one act — a candidate engineering declines to ratify stays live and is named unratified, never silently dropped. There is no separate initiative file on the PM path (epic = initiative, one-to-one): the epic brief IS the map, its Sequence section filled and the doc re-committed before any Goal brief cites it. If the project manager re-scopes mid-flight, the brief's supersession is the change record — a re-run states which not-yet-started Goals the change invalidates and names (but never touches) the ones already started; deliberately not a correct-course workflow. Each Goal brief is the standard Goal-brief shape plus a leading Sequence block — its initiative citation, a P0/P1/P2 priority (dependency-consistent: a brief never outranks a predecessor it depends on), and explicit Depends on: lines — written sequence-prefixed under .emerik-goals/<nn>-<slug>.md so lexical order is the breakdown order, its guardrails citing real Pin and Anchor ids with their levels. The initiative layer is a documented convention, not a mechanism: no schema field, no new work-unit type — the Goal stays the sole work unit. It hands off to emerik-goal per brief (goal_start --brief --priority, read from the Sequence block) and to emerik-orchestrate for the whole sequence (the swarm honors each brief's Depends on: lines; independent briefs fan out in parallel).

emerik-discover

Fuzzy problem → grounded exploration. It reads the substrate first (index_statusgraph_retrieveanchor_list), facilitates the exploration, then lands outcomes honestly: a decision summary you commit (harvest captures the Pins), and candidate constraint-Anchors offered through the review gate.

emerik-goal

Intent → Goal brief with cited guardrails. It reads the substrate first (index_statusgraph_retrieveanchor_list), then shapes a Goal and testable acceptance criteria whose guardrails cite the Pin and Anchor ids they must honor, with their enforcement levels. The brief is committed under .emerik-goals/ (harvest captures it), then started as a first-class Goal via goal_start (linking the brief with --brief) and handed to emerik-plan.

emerik-plan

Goal → committed plan doc. It refreshes the substrate (index_statusindex_refresh), runs retrieval over the Goal and each acceptance criterion, lists the standing constraint-Anchors, and drafts a plan whose every step cites the Pin and Anchor ids it relies on — then lands it as checkbox steps in .emerik-goals/<slug>-plan.md, the Goal brief's sibling, committed and harvested. The plan survives the chat window: a fresh session — or a different model — picks up the next unchecked step cold.

emerik-implement

Plan step → implementation, under coordination discipline and test-first rigor: claim a Lease, write the failing test first (watch it fail), implement to green (red-green-refactor), let the harvest hook capture the commit, run emerik verify green, then release the Lease. A step is done only when the new tests exist and pass and verify exits 0 — never claimed-but-unrun. Starts cold when it has to: goal_status → the committed plan doc → the first unchecked box, flipping each finished step's checkbox in the same commit as its code. On fixing a defect it offers a witness: a checked Anchor through the review gate that would have caught it, so the regression gate lives in the substrate. And it is extremely critical of its own TODOs: one it could finish now gets finished, not left; one bigger than the step trips a circuit breaker back to the driver — "this is bigger than the plan assumed — split it into its own Goal(s)?" — and only a genuinely blocked one may stand, recorded in deferred-work.md with its reason and unblock condition.

emerik-review

Change → adversarial review over the substrate. It fans out asymmetric reviewers — Blind Hunter (diff only), Edge Case Hunter (diff + repo), Acceptance Auditor (diff + Goal brief) — plus the Substrate Auditor, which audits the diff against the live graph (anchor_list / graph_retrieve for block/warn Anchors, lease_list for Lease discipline, harvest honesty). It triages every finding into exactly one bucket (decision-needed / patch / defer / dismiss), commits a review record under .emerik-reviews/ (harvest captures it), and offers recurring failures as antibody-Anchor candidates through the review gate. It then derives one verdict per reviewer layer and records the routing decision with review_open — see Review at scale. It never ends silently on undone work: a defer must be earned (a stated reason plus what would unblock it — actionable-but-large work is escalated for a split, never deferred), every TODO the diff introduces goes through triage, and the review closes with a deferral report — everything that could not be done, each with its reason — offering, with consent, to fold items into the active Goal, mint a cleanup Goal, or split oversized work into its own Goals.

emerik-guide

The guided front door, in three modes. Orient derives where the repo stands from live substrate facts and the installed skills' declarations, then recommends the next step as a short menu — never a hardcoded script. Tour is a hands-on walkthrough of the whole loop, each leg delegating to the real skill. Step-through wraps any suite skill with its gates made explicit, confirming at each gate and reporting what was read from and deposited into the graph.

emerik-retro

Completed Goal → lessons back in the substrate. It reconstructs what happened from the record first — emerik history (the decision log), the harvested Pins and the heat (graph_retrieve), the governing Anchors (anchor_list) — facilitates the lessons, then lands them four ways and no others: constraint-shaped lessons as antibody-Anchor candidates through the review gate (the bless loop is live — a Steward disposes); procedure-shaped lessons — the same multi-step procedure re-derived across Goals, each occurrence cited from the substrate — as skill candidates the skillsmith drafts and only a human admits, rejection leaving no artifact; practice-shaped lessons — how work is done here, re-derived across Goals — as explainer candidates handed to emerik-explain, cheaper than a skill because a document needs no lint gate, a declined offer leaving no artifact; and action items in a committed summary under .emerik-retros/ that harvest captures. The proposal bar is deliberately high — a retro with zero skill or explainer candidates is the normal outcome.

emerik-orchestrate

Active Goals → a two-layer swarm. It reads the substrate first (goal_list, trail_status, dependency_status), carves non-overlapping assignments, and fans out one host-agent subagent per Goal or plan step — each in its own git worktree running emerik-implement (sequential in-session where subagents are unavailable). Layer 1 is hands-off: the heat trail and the Leases deconflict the routine ~95%. Layer 2 is the rare genuine collision, arbitrated by drafted rules in order — wait out a short Lease, higher-priority Goal proceeds (P0/P1/P2), first-holder by CAS order, split at a published Anchor, else the human driver. It grants territory only through Leases — it holds none, claims none on a subagent's behalf, and never breaks one.

It orders the carved assignments before fanning out. Simple P0/P1/P2 priority is the default on every run. A market-maker mode is an opt-in, evaluable A/B option — never the default: it prices an attention bid (impact × risk) minus a congestion cost on contested territory (overlapping a trail_status hot zone or a live lease marker), nudging non-conflicting Goals first and degrading to simple ordering on any tie. It orders, it never gates — Leases stay the only write arbiter. The mode is announced and recorded so runs are A/B-comparable (OQ-4); the decision criteria — escalation share, time-to-complete, rework rate — are targets Epic-8 instrumentation measures.

emerik-onboard

Brownfield cold start → a warm substrate, as an agent workflow. Its premise is pre-init, so it requires nothing to start. Phase 0 preflights the git repo and checks for a HEAD; phase 1 is an idempotent ensure-init (store absent → emerik init, committed; store present → recorded pre-landed); then emerik seed --analyze and emerik seed mint the origin: seed Pin/Anchor graph, emerik index rebuild makes retrieval warm, and a committed summary under .emerik-planning/onboarding-<slug>.md is written from the seeded graph (real Pin and Anchor ids) — committed before the final emerik index refresh so the harvested Pins land. Each phase's landing is verified from the live surface (the --json reports, emerik index status) before the next begins. Once the index is built it also mines the merged pull-request review history newest-first over a bounded window you confirm, triaging each comment through the shared emerik-address-review rubric so the substrate starts with day-one recurrence counts — and a repo with no gh, no reachable remote or no merged pull requests has that phase reported as skipped, never failed. Doc Anchors are admitted only behind an explicit, consent-gated emerik seed --approve-docs --steward <id> — the machine never mints an Anchor unprompted. Between seeding and the index build it proposes a consent-gated starter set of practice documents (explainers), cannibalized once from the docs it already read (README, CONTRIBUTING, architecture docs) — read once, never linked, the originals left untouched and thereafter irrelevant; seeded explainers bind immediately and are rough on purpose, because a bad one costs one conversation at emerik-explain's wholesale-rework door. A repo with nothing to seed (codeUnits === 0, or no HEAD) is detected as greenfield and routed to emerik-brainstorm + emerik-guide with the store kept initialized. It hands off to emerik-discover, emerik-goal or emerik-guide — nothing auto-runs.

emerik-prepare

The pre-flight before work on an initiative — run as a dark cockpit: when everything is normal the panel is dark and it says almost nothing (at most one line). Silently it fetches, reads the initiative ledger, runs the inactivity sweep, verifies your git lineage against the declared baseBranch and optional stacksOn, cuts a clean branch from that declared base (the base is mandated; the branch name stays your team's), and stamps the initiative into the session so the work prose it grounds — and the Pins harvested from it — carry the lineage. It speaks only on exactly three killer anomalies: a wrong initiative lineage, a dirty worktree carrying another initiative's changes, and a decision-changing behind-remote. Each is a challenge — the observed state quoted verbatim (real command output), then real options to pick between — never a confirm-normal yes/no. Consent is scoped to the initiative: a declared lineage is the standing consent, so a normal work-week produces zero prompts and a contaminated worktree exactly one. On the clean branch it hands off to emerik-goal, emerik-plan and emerik-implement.

emerik-reconcile

The repair path when workspace auto-sync pauses. A pause is deliberate: emerik's own writes are append-only and cannot collide, so a true conflict on the meta-repo's main is almost always human-edited shared content — and rather than guess, the engine restores the clone exactly as it was, records what it observed, and waits, with local work untouched and nothing escalating. This skill walks the repair as a challenge-response: it grounds first — reads the standing record and quotes it verbatim (reason, when, the pre-rebase headSha, the remote tip, git's own words), never acting on an assumed halt — then forks on the diagnosis: a dirty tree (the worktree, not history) or a true conflict (local and remote commits genuinely collide), inspected with real git, subagents welcome for the legwork. The engineer chooses between real options with their consequences attached — commit, stash or discard the dirt; rebase-and-resolve, or adopt the remote version and re-apply local intent — never a confirm-normal yes/no, and never a force-push, a remote history rewrite, or a member-repo touch. It ends at the mechanical door: workspace_reconcile with the quoted headSha as the challenge token — the halt clears and the sync primitive re-proves convergence itself; still-halted loops honestly back to the diagnosis rather than clearing blind.

emerik-scope

A project manager scopes an epic-sized increment for engineering — a facilitated session, no technical surface. It grounds first: before any scope is written it sweeps the substrate through the read tools (anchor_list for the standing commitments and their levels, goal_list for the work in flight and how big similar work turned out, graph_retrieve per product area for the hot Pins) and presents what it finds in outcome language only — never a raw pin body, a file path or a ULID in the PM-facing doc. On a conflict with a governing Anchor it pushes back with three honest moves: keep, narrow, or explicitly propose superseding the commitment. The PM's sources — design files, documents, boards, pasted notes — come in through whatever connectors the session has and are synthesized with a citation, never admitted verbatim; a missing connector degrades to ask-for-paste/export plus a pointer to the PM workstation setup page, never a hard failure. It lands one committed epic brief at .emerik-goals/<epic-slug>/epic-brief.md — a stakeholder-ready body (problem, intended outcome, audience, in/out of scope, outcome-level acceptance criteria each with a P0/P1/P2 priority, dependencies, open questions) plus a machine appendix (the ids consulted, the source links, the deposited candidate-Anchor id per criterion, an empty sequence the breakdown fills). Each acceptance criterion is deposited as a candidate constraint-Anchor through anchor_author — the criterion in outcome language on the Anchor, the PM as steward, territory-unbound and checkless (no --governs, no check flags), so emerik verify surfaces it as a warn sticky note and never enforces it until it is ratified. The deposit is consent-gated per candidate: the machine never mints an Anchor unprompted. The PM commits the doc, the harvest hook captures it, and a brief artifact is minted with the PM as steward. It hands off to emerik-breakdown, which accepts the epic brief directly — no live prd and architecture required — ratifies each deposited candidate (blessing it and binding it to the code region in one act), and carves the initiative into an ordered set of Goal briefs.

emerik-status

The report-back half of the project manager's loop: how is my epic doing? It rolls one initiative up into a narrative a stakeholder can be handed as written — and it is read-only, deriving every claim from what is already committed. It resolves the initiative in either of its two homes (a scoped epic brief at .emerik-goals/<epic-slug>/epic-brief.md, or the founder path's committed map under .emerik-planning/), walks the Goal briefs whose sequence block cites it, joins them to real work through goal_list with completed Goals included, translates each in-flight Goal's latest progress entry into outcome language, and joins the reviews that trace to those Goals. Which acceptance criteria engineering has signed is read structurally — no field says "candidate", so a live constraint-Anchor that is territory-unbound and checkless is still pending (a warn sticky note, surfaced and never enforced), while one superseded by an Anchor that binds its region is ratified. The report reads headline · delivered · in flight · not yet started · the contract · reviews · changes, in outcome language only — never a pin body, a file path or a ULID above the delimiter; every id consulted sits below it in a machine footnote. Empty reads are said plainly (scoped, nothing started), a founder-path initiative's missing criteria contract is stated rather than faked, and a re-scope shows up as the brief's own supersession. Nothing is minted and nothing is written: when the rollup surfaces drift worth acting on, it hands back to emerik-scope.

emerik-address-review

The live pull-request loop: address the review on PR 42. It reads the review threads with your agent's own gh (emerik never touches the network), and for each actionable comment retrieves what the substrate already knows before acting, implements the fix, and commits it so the harvest hook captures the code — the commit deliberately preceding any deposit, because a review binding hashes committed source at HEAD. Then it triages each comment through one rubric of exactly four dispositions: a no-residue nit (fixed, nothing minted), a durable lesson (deposited as a review-origin Pin through the --review handshake), a generalizable rule (an Anchor candidate--from-pin with its check but without --approve is a no-write preview, and the skill never approves on its own initiative) and a “never again” (an antibody candidate, minted and stopped — the human bless still gates it). A lesson taught again in a later pull request is recurrence: the promotion signal, never a duplicate, so it is deposited and surfaced for promotion with its observed count. The run reports a per-thread resolution table — addressed / captured / promoted-candidate / skipped, with artifact ids — and states what it did not process; no silent caps. Replies to GitHub and pushes are opt-in per action, never silent.

emerik-consult

The pre-flight read: the practices that govern the work, surfaced before it starts. The scoping, Goal, planning and implementation skills invoke it at entry, so it fires without the agent choosing — and never otherwise: nothing intercepts prompts, and the read reaches exactly as far as the routed flows do, a documented boundary. It grounds the index, establishes what the work is about to touch, narrows the practice documents to that footprint (explainer_list with paths; explainer_search for a "how do we do X here" question — the region only ever narrows relevance, never grants it, so an anchorless practice is never hidden), then reads the few relevant documents whole from their docPath — pointers, never prose; at most three by default, said aloud when more matched. A known-wrong practice is surfaced first, routed to emerik-explain's wholesale-rework door. A noticed contradiction with an anchored obligation goes through explainer_conflict; a clause the work then actually leans on is cited with explainer_cite at that moment — deliberately, never automatically, because reads warm nothing. It warns and never blocks: every surfaced fact is information the agent carries into the work. Consult reads; emerik-explain writes — authoring and rework always route there.

emerik-explain

Practice knowledge → a normative document, through two doors. An explainer covers exactly one practice ("how we write tests here") and it is normative: it states how work is done here, so code that disagrees with it is the defect — only a change of intent stales it, never code drift. Author — a human explains, the LLM writes, during ordinary feature and bugfix work, never scoped to a ticket (the unit is project context). The prose comes first and is the generative source of its enforcement: every sentence that states an obligation is pulled out as a member Anchor through the review gate, minted at finish so one pull request carries prose and anchors together and a rejected branch's anchors die with the branch — while a document with no anchors is legal and honest, unenforceable by construction: a documented boundary, not a defect. Wholesale rework — a badly-wrong document is rewritten completely through prompting, the same conversational act that authored it: one conversation regardless of how wrong, never incremental correction of individual warnings. Re-declaring the same path supersedes the record and clears a known-wrong marking; changing an obligation still goes through the review-gated Anchor path — the rework door never stands a check down. The discriminator that routes here: a procedure you execute is a skill (the skillsmith's business); a practice you follow is an explainer. Done means emerik verify actually ran and exited 0, output quoted.

emerik-skillsmith

The builder's engine: one engine, three doors, and every door ends at the same deterministic gate. Fold an existing skill in — the sweep classifies what the repository already carries and prints the interrogation contract; the skill is then audited one at a time against the live Anchors, every finding citing the specific Anchor at block or warn, a block conflict resolved by consent-gated rewrite surgery or the skill left out, a warn conflict folded in annotated, and the whole thing admitted through the one door. Bringing a folded skill into the mechanics-and-slots partition is offered, never required. Author a new team skill — from the shipped scaffold: a frontmatter whose entry and exit states are drawn from the declared vocabulary, a dialect from the two-word vocabulary, and a body that is partitioned from birth, so the skill can be regenerated later instead of only hand-edited. Customize a shipped skill — without ever editing a shipped file: the team's durable intent is elicited per named soft slot and recorded as an intent record before anything is written, and only then is the installed file re-synthesized — mechanics verbatim, frontmatter and marker lines untouched, supporting files never touched. The hard rule that binds all three: done is reported only when emerik skill lint has actually run and exited 0, with its output quoted — never on claimed conformance, which is emerik-implement's honesty loop applied to skill authoring.

The reviewer roles emerik-review fans out over ship under .claude/agents/ (blind-hunter, edge-case-hunter, acceptance-auditor, substrate-auditor) — each a subagent with only its allowed inputs, so a defect one reviewer rationalizes away another cannot. Those reviewers are the agent quorum — their verdicts feed the risk-tiered routing record below; the steward's batched digest of those records ships too — emerik review [domain] collapses a day of output into a few team-level rows.

Review at scale — agent quorum + risk-tiered routing

When N independent agents adversarially agree a change is safe, it can clear without a human — but only a change below the risk tier. The Review artifact is the durable, append-only record of that decision: it names the change, the reviewers' verdicts, the edited Anchors and their live-dependent counts, and the routing outcome. emerik review open (MCP review_open) records + routes it; emerik review list projects the live records. Opening a review orders the outcome — it never gates a write; Leases stay the only write arbiter, and it exits 0 on both outcomes.

CommandWhat it does
emerik review [domain]Open the batched digest — the one v1 TUI screen. Boilerplate collapses to a single faint ✓ N cleared row; only escalated changes surface as attention rows; the header reads N cleared · M need you. An optional domain scopes it (exact match). Keyboard-only (j/k move, drill in, bless, adjust, / filter, q quits); non-TTY / --json print the static digest / model. --web opens the review surface in the browser instead — on-demand, localhost, ephemeral (TTY only; --json/--quiet/non-TTY keep today's behavior).
emerik review open <subject>Record + route an agent-quorum review — --anchor (repeatable, the edited Anchors), --verdict <reviewer>:<safe|unsafe> (repeatable), --goal, --domain (the steward's digest scope, declared), --record (repo-relative by contract — an absolute path is refused). --finding <file>:<line>:<severity>:<title> (repeatable, severity ∈ P0/P1/P2) pins structured findings to exact lines; --from-branch (with explicit --base/--head override) stores the reviewed range as commit SHAs so review --web can regenerate the diff live. Exits 0 whether cleared or escalated.
emerik review listLive routing records — --routing cleared|escalated and --domain filter; a bounded page of summary entries (--limit / --cursor / --id, FR-39).
emerik review bless <id>Bless an escalated review — clear the change on the steward's authority, through code (a superseding decision Review). Optional --note, --by (defaults to the git identity).
emerik review adjust <id>Request adjustment on an escalated review — send the change back for rework, through code. Optional --note, --by.

The engine owns these drafted OQ-9 defaults — engine-owned constants, tunable when Epic-8 instrumentation measures the queue reduction (never reported as achieved before then):

  • Quorum — a normal-tier change clears only with three distinct reviewers, unanimously safe. Any unsafe, a short quorum, or a missing layer escalates.
  • Risk tier — a change that edits an Anchor with at least one live dependent is high tier and is routed to a human regardless of the verdicts. Quorum can never override the tier.
  • Spot-auditone in ten auto-cleared reviews is flagged for a human spot-check, deterministically derived from the review's id (unpredictable before mint, verifiable after).

That last guarantee is structural, not a convention (the SM-C2 guardrail): the Review schema re-derives the tier and routing from the artifact's own recorded evidence, so a committed review whose bytes claim cleared on a high-tier change simply fails to parse — and fails emerik verify. A cleared-high artifact cannot exist. Routing speaks in coordination-event voice — cleared by quorum or escalated — routed to a human, never "conflict".

The batched digest — emerik review [domain]

Review is owned and scheduled — the steward starts it; it never pushes as an interrupt stream (no watcher, daemon, or notification surface exists anywhere in emerik). emerik review [domain] opens the one v1 TUI screen: a single full-height panel — a fixed header (surface + domain + summary), a scrollable digest body, and a fixed key-hint footer — that reflows legibly at 80 columns.

  • Boilerplate collapses to a single faint ✓ N cleared row, whether N is 12 or 12,043 — the collapse row is the scale answer.
  • Only escalated changes surface as their own attention rows: a selection caret + a state glyph + the change subject (bold) + fan-out:NN (the blast radius — the max declared dependents; omitted when the change names no Anchors). Reviews carry no heat, so there is no heat-bar.
  • A spot-audited clear earns its own dim · spot-audit row (it still counts in N cleared; M need you counts escalated only).
  • The header voice is byte-exact: 198 cleared · 2 need you., an all-clear 198 cleared · 0 need you., and an empty scope No changes since last review.
  • Domain scopes the digest by exact match — declared at review open --domain, never inferred; an undomained review appears only in the unscoped digest.

The screen is keyboard-only (j/k or / move the caret, q quits) and honest under constraint: NO_COLOR reads identically because every state is glyph + text, and a non-UTF-8 / dumb terminal falls back to ASCII glyphs. A piped / non-TTY run prints the same digest as static text; --json emits the digest model.

Drill in & resolve — review-as-code

Press on a flagged row to drill in: an inline accordion (one level deep, never a second window) shows why it escalated, the change-as-code (the reviewer verdicts + the record pointer), and the fan-out list — the edited Anchors with their dependent counts. Resolve it through code, not a heavy approval form: b blesses the change (clears it on the steward's authority) and a requests adjustment (sends it back for rework). Each mints a superseding decision Review — the escalated record is retired by supersession, not flipped by a status field (there is none). / filters the attention rows; esc collapses. The same resolutions are scriptable off-terminal: emerik review bless <id> / emerik review adjust <id> (and the MCP review_bless / review_adjust twins), each carrying an optional --note.

Read the change in the browser — emerik review --web

For a change too large to read in the terminal, emerik review --web opens the same review as a browser-grade surface: the diff regenerated live from git with syntax highlighting, each finding badged on its exact line (severity P0/P1/P2 shown as its BLOCK/WARN/ADVISORY affordance), boilerplate hunks collapsed with expand-on-demand, and the Anchor fan-out. It holds emerik's spine: the server is on-demand, ephemeral, stateless, zero-new-dependency, localhost-only — a 127.0.0.1 HTTP server on an ephemeral port with a one-time capability token in the opened URL, torn down on exit (Ctrl-C / browser-close / idle timeout). No always-on daemon, no database: git and .emerik/ stay the only truth and the page is a pure stateless projection over them (the HTTP sibling of the on-demand emerik mcp server). It is additive — the TUI screen stays, and --json/--quiet/non-TTY never launch it.

The reviewed change is captured as code, not snapshotted: emerik review open <subject> --from-branch stores the range as resolved commit SHAs (base = merge-base(HEAD, upstream/trunk), head = HEAD; --base/--head override), and the browser regenerates the diff from that range at view time — no committed diff bytes, yet the exact reviewed change. A review with no stored range degrades to the metadata view (why + verdicts + fan-out), never a fabricated diff. Bless and request-adjustment from the page call the same token-gated, review-as-code ops as the terminal — minting superseding Reviews, no new approval model — and the surface refetches so the resolved row leaves the attention set.

Impact-based routing & authority

When a change edits an Anchor, the dependency graph is the reviewer-routing graph: the teams that declared a contract-dependency on it are auto-enlisted as the review audience (the drill-in lists them, per Anchor). Review is team-level — a verdict is one team's voice (self-asserted; emerik keeps no per-person membership), so any one member clearing counts for the whole team and hundreds of per-person approvals collapse to a handful of team-level reviews. And the core owner — the Anchor's steward — can ship a wide-fan-out central change on their authority (a review bless): dependents are notified and can review adjust through code, but full consensus is not required. The review is never skipped — a high-fan-out change escalates by construction and reaches the steward, who ships with review rather than gating on stop-the-world consensus (pairs with versioned / expand-contract migration, OQ-10).

Forked supersession — surfaced and reconciled

A forked Anchor supersession (two Anchors racing to supersede one predecessor) is walked as a multi-edge lineage: dependentsOf counts the union across every branch, so a change's blast radius is never under-counted and review tiering can no longer under-tier it. The fork is surfaced, never silently resolved — dependency status reports the deterministic primary head and lists all live heads, and emerik history <id> names the divergent branch. The steward reconciles it by supersede-to-merge: author the merged successor over one head and tombstone the other, after which every branch's dependents re-point to the single live head. No new command and no committed-bytes change — the existing surfaces compose.

Stewardship — inverted CODEOWNERS

Decisions own code. An Anchor declares the code region it stewards with --governs <glob> (repeatable), and emerik steward resolve <path> (MCP steward_resolve) maps a path back to the Anchor(s) that govern it and the accountable steward, layered most-specific first — the inverse of a CODEOWNERS file (the decision owns the code, not a path→owner list). Every Anchor carries a required steward (human email or agent id — OQ-7 leaves per-domain agent-vs-human assignment open); a Pin at a governed path inherits the governing Anchor's steward. Stewardship is permanent and travels with the graph — it sits above transient Leases: resolving stewardship consults no lease, and a lease never changes who stewards. An ungoverned path resolves to nothing — a fact, not an error.

A steward-agent carries a standing constraint set — the constraints stated during PRD / scoping / design, each authored as a permanent subtype:'constraint' Anchor attached to the steward (emerik anchor author --subtype constraint --steward <who>). The design process is the steward-agent's training signal: the set evolves append-only as constraints accrue, retrievable in accrual order via emerik steward constraints <steward> (MCP steward_constraints) — a bounded page of summary entries (--limit / --cursor; --id for one constraint's full text). Note the deliberate asymmetry: a steward's accrued constraint set is an inventory and is paged, while emerik steward resolve <path> stays complete and never truncated — constraint discovery for a code region must surface every governing Anchor. The review skill retrieves and applies a domain's constraint set when it reviews a change there.

The antibody loop —

A code-review rejection (or a recurring failure / gotcha) mints an antibody: a durable subtype:'antibody' constraint-Anchor attached to the domain steward, so every future agent there inherits the learned constraint — "never make the same mistake twice." An antibody is learned memory, not a current error — it carries the violet marker (never enforcement red) and its enforcement level. The bless gate (threshold drafted, OQ-8): a high-impact antibody — one at block enforcement, which could fail CI for every agent in the domain — is minted pending; it surfaces but does not bind or gate emerik verify until a human emerik antibody blesses it. A warn / advisory antibody is low-impact and binds on mint. Future agents retrieve a domain's antibodies with emerik antibody list <steward> (MCP antibody_list) — a bounded page of summary entries that keeps each antibody's bless / TTL state, so a long-accrued antibody set is paged (--limit / --cursor) rather than dumped, and --id fetches one antibody's full text.

A false antibody must not permanently bind every future agent, so there are three append-only retirement paths (no in-place delete, AR-14). Retractemerik antibody retract <id> (MCP antibody_retract) writes a Tombstone; the antibody drops from the graph and stops binding. Supersedeemerik antibody mint --supersedes <id> replaces a wrong antibody with a corrected one. Expire — an antibody minted with --expires-at stops binding once its TTL passes. And an antibody-vs-Anchor conflict is steward-declared and resolved by retracting (--conflicts-with <anchorId>) or superseding, leaving one unambiguous live constraint (the rule is drafted, OQ-16). With this, Epic 6 — governance, stewardship & the antibody loop — is complete.

Instrumentation — the rework rate and its control

emerik's whole thesis is that stale context drives rework — rebuilding work already marked "done". emerik metrics rework (MCP metrics_rework) measures it, as a read-only derivation over the .emerik/ decision log — it writes nothing, exactly like emerik history. The headline is a decision verb-share (SM-1): the share of decision commits whose verb rebuilds (supersede), retracts (tombstone) or sends a change back for rework (adjust) an already-recorded fact; every other verb (create, mint, bless, …) is progression. It reports a baseline (the full log) and, with --since, an ongoing window, and carries the ~2% directional target and the rough ~5–10% baseline band in its machine-readable shape so a matched control run is directly comparable.

A number with nothing to compare it against is not evidence, so the same command reports the governed-path split beside it: the same decisions, divided by whether the touched path already had a governing artifact when the decision was committed. The governed set is read out of the same body map the local index builds — an Anchor's governs territory expanded over the tracked tree, plus the paths practice documents are written at, each credited only if it was already minted at that decision's own instant. A pinned path is not a governed path: a Pin's own presence never counts as governance, because a metric that cannot come back bad is not a measurement.

The two rates are printed side by side and never merged — no delta, no verdict. A governed rate worse than the ungoverned one is a real, reportable result, and reading it is the operator's job rather than the tool's. The split scopes itself to decisions that resolve to a Pin, because a Pin has exactly one recorded path; anchor decisions, review decisions and every path-less family are counted in an honest outside the split bucket rather than forced onto a side, and a decision whose bytes no longer resolve (a compacted Pin) is counted as unattributable with its reason. Every rework decision inside the split carries its own row — the commit, the path, the bucket and the artifact that governed it — so a claim can be checked rather than believed. Read the governed share as a floor: governors are judged against the current live set, territories expand against the current tracked tree, and attribution is best-effort against today's store — all three can only undercount. What remains future is effort weighting: a decision is one decision here, whatever it cost.

emerik metrics review-time (MCP metrics_review_time) measures the review funnel per Goal (SM-2), read-only over the Review artifacts + live Goals. It reports two things. (a) The auto-clear rate — the share of changes the agent quorum cleared without a human (routing = "cleared") — is solid from the bytes; a steep rise is the goal. (b) Human-review loadhumanReviews, the count of changes escalated to a human — is a drafted proxy: emerik instruments no per-reviewer timer, so actual reviewer-hours are not available and the report says so verbatim rather than fabricating hours from wall-clock. Both are reported per Goal (with the Goal's completed state), and the counter-metrics still apply — a high auto-clear rate must never be gamed past a genuine blast-radius change (SM-C2, structural in the risk-tiered routing). With this, Epic 8 — instrumentation — is complete.

Instrumentation — retrieval recall (SM-4)

emerik metrics recall (MCP metrics_recall) was the first number on this page stated as a floor rather than a point estimate — the eval loop’s opening move, since joined by the warning hit-rate, the governed-path split of the rework rate, and the attention audit. Every citation your agents committed records that one clause of one practice document authorized one act. Replay that act as a retrieval query, ask whether the cited artifact came back, and you have scored retrieval against ground truth the team already owns. That is SM-4 (“time-to-relevant-context / retrieval precision”), and until now it had never been measured at all.

It reads by default. The bare command reports the recorded run history — one small local file, no embedder, no index, nothing to wait for. --run (MCP run: true) is the opt-in that measures a fresh run first: bounded by citation count rather than corpus size, writing only under .emerik-local/, and never inside emerik verify. Runs accumulate in .emerik-local/eval/replay-ledger.json, so what you read is a trend, not a single flattering reading: one point per recorded run, oldest first, with the latest-versus-previous delta lifted out. Below two runs there is no delta at all — a first run says so rather than reporting a slope of zero.

Two arms, never merged. act-verbatim replays the recorded act; footprint narrows the same act to the live goal's declared territories — the shape agents actually issue — and exists only where a goal declared them, so its N is smaller by construction. Averaging them would hide both. Beside the recall curve sit the negatives, which are two instruments rather than one: a raw-index probe that can legitimately come back bad (dead ids served between rebuilds — index hygiene), and a surface assertion that reads zero by construction (dead knowledge stays dead) — a non-zero reading there is a defect, never a score to interpret.

The envelope rides the number. Every run is lower-bound with cause replayedAgainstCurrentIndex, and there is no code path to exact: the replay scores against today's index, the ULID cutoff drops hits minted at or after the citation so retrieval is never credited for artifacts that did not yet exist, and what the cutoff cannot undo is marked rather than smoothed. Each cause ships with its remedy sentence. The definition rides the payload too, naming the known bias out loud — agents cite what they were shown, so recall measured this way is partly self-fulfilling — and naming the counter-metric: the goal is reading the right context, never reading less. An absent history and a damaged one are different facts with different remedies, and both report at exit 0 as the states they are.

Instrumentation — the warning hit-rate (SM-5)

emerik metrics hit-rate (MCP metrics_hit_rate) asks the question the substrate has always been claimed to answer and never been made to prove: did the substrate already know? Of the reviews that escalated to a human, what share had a warning already recorded before the review was ever opened? That is SM-5 turned into a number that can come back bad, instead of a standing anecdote. It is a pure read over artifacts already committed to .emerik/: no new capture, no schema change, nothing written anywhere.

Three mechanisms, each byte-tight, and an escalation is a hit if any of them attributes it. anchor — the review's risk tier is high, so the routing engine's own recorded cause is a declared edited Anchor carrying live dependents. antibody — a live, binding antibody, minted before the review and unexpired at the review's instant, one of whose governed path globs matches a file the review's findings named. explainer — a live practice document that predates the review, one of whose live member anchors is exactly one the review declared it edited: an anchored obligation is a clause of that document. One escalation may be credited by several, so the per-mechanism counts are counts of escalations and are never summed into one number. Every attribution carries the ids and what each matched, so a claim can be checked rather than believed.

Read the number as a floor. The envelope is lower-bound and there is no code path to exact. Two causes, both of which only undercount: liveness is judged against the current live set with a cutoff at each review's own recorded instant, so a warning that was live then but has since been retracted or superseded is not credited; and the antibody arm can only be evaluated for reviews that recorded structured findings, with withoutPaths counting the escalations that recorded none. Three readings were deliberately refused, each for its own stated reason: region coverage errs covered by design (a region only ever narrows relevance, it never grants it); stewardship over finding paths would let one broad governed glob claim foreknowledge of nearly every escalation; and a repo-qualified antibody is dropped from the path match, because a finding records no repo to match its territory against. An antibody that declares no governed paths matches nothing — a declared-nothing territory is a stated coverage boundary, never a wildcard. A metric that cannot fail is not a metric.

And what happened after the human looked. The overridden block reports the escalations a human blessed through, the ones sent back for adjustment (the warning vindicated at once), and the ones blessed and then reworked anyway — a declared anchor superseded or tombstoned after the resolution. That join reads store artifacts and nothing else, and it is narrow on purpose: it is evidence that an overridden warning was later acted on, never proof that the override caused the rework.

One scope correction, stated rather than smoothed. The familiar “~95% self-resolve” figure is a coordination-layer target — leases and the heat trail deconflicting routine work — and no byte in the store records its denominator, so nothing here measures it and it remains an unmeasured target. What is measured on your own bytes is the auto-clear rate (the share of reviews that resolve with no human at all) and, of the escalated remainder, this rate. Zero escalations is a designed success reported at exit 0, never an error and never NaN. And like every eval-loop number, it measures the substrate, never a person: no author, reviewer, steward or approver appears in any field.

Instrumentation — the attention audit

emerik metrics attention (MCP metrics_attention) asks the question underneath every other retrieval number on this page. Recall asks whether retrieval surfaced the right artifact. This asks whether anyone then looked at it — and, per goal, whether the contracts governing the files that were edited were ever consulted at all. It is the honesty loop closing: “these files were edited under governing artifacts with no recorded consultation” becomes a readable, evidence-backed sentence instead of a suspicion.

It populates the flight recorder’s long-empty column. Every served retrieval has been recorded locally since the graph shipped — the surface, the query, the ids that came back, the envelope — with one column, followOn, deliberately left blank for the day something could fill it honestly. This fills it by projection over evidence already committed: citations and the decision log. Nothing new is captured and no agent is asked to report on its own behaviour. The vocabulary is closed — cited (a later citation credits something the flight surfaced), edited (a later decision touches it — any verb; attention is touch, not only rework), ignored (neither, and later work demonstrably happened) — and a flight with no later evidence at all is left unlabelled rather than called ignored, because stamping it would manufacture the audit’s worst accusation out of recency alone. When several apply, cited beats edited beats ignored, and evidence naming the successor of a surfaced artifact still counts.

Four buckets, per live goal. For each goal that declares territories, the audit takes the paths that goal’s own decisions touched, finds the anchors and practice documents governing them, and sorts each governor into exactly one of: surfaced and cited (retrieval put it in front of an agent and an agent leaned on it), surfaced, not cited — the ignored arm, and the one worth reading twice, cited, not surfaced (leaned on with no recorded flight, so the consultation happened somewhere this recorder cannot see), and no recorded consultation. A goal that declares no territories is skipped by name rather than silently, because territory is what makes “the paths this goal touched” answerable at all.

Read it as a floor, and know which way it leans. The envelope is lower-bound and there is no code path to exact. The recorder is machine-local and gitignored, every full emerik index rebuild wipes it, and the read is capped at the newest rows — so what was surfaced can only ever be undercounted. That asymmetry is the point: because surfaced is a floor, no recorded consultation is a ceiling on the accusation. A governor in that bucket may well have been read on a colleague’s machine, before the last rebuild, or through a surface nothing records. Every sentence the tool prints says no recorded consultation, never never consulted, and it names artifacts and paths, never people.

Rework is reported beside the ignoring, never as its cause. Each goal’s rework decisions are split by whether the touched path had at least one consulted governor — surfaced-and-cited or cited-not-surfaced; a surfaced-and-ignored governor is precisely not consultation, or the split could not fail — and both counts ship with their own denominators, side by side. “Ignoring did not correlate with rework” is a fully renderable result, and the reader draws the conclusion. This arm is also the other half of the recall number’s stated bias: agents cite what they were shown, and surfaced, not cited measures exactly the gap that bias hides.

What it writes. Alone in the metrics family, this one is not a pure read — but the exception is narrow and derived: it writes the followOn labels back into the local flight recorder under .emerik-local/index/, wiped with the index it annotates. Nothing in .emerik/ is touched, no ref moves, no commit is made, and a failed label write changes nothing about the report. Every surfaced state — no engine, no index, an empty recorder, no live goals, an unknown --goal — is exit 0 with its own words. The emerik-review skill reads one goal’s retro-brief aloud after the commit, as an honesty line and never a gate; at zero it says nothing at all.

The delivery ledger

emerik metrics ledger (MCP metrics_ledger) is the closed-goal read surface — one row per Goal that ever completed (a completed-then-superseded or tombstoned goal still appears; that history is exactly the ledger's business), newest close first — a time ordering, never a ranking. Each row answers what actually got finished, how big it was, whether it touched tests, whether it came back, whether a human engaged, and whether it landed — and every number is evidence with a stated derivation, where 0 is never conflated with unknown. The diff facts (diffLines, touchedTests — walked over the Goal's territories within its derived cycle window) and the land record (read from the emerik land squash commit's own folded timeline, retroactively — never inferred from merge topology) are observed / unobserved and landed / not-landed unions whose false arms carry the reason as notes: an unobserved diff or an unlanded goal is a fact the surface states, never an error and never a zero. reworkedSince counts supersede / tombstone / adjust decisions joined to the goal through recorded bytes within a tunable window after close — default four weeks, a PRD assumption (OQ-4) echoed in every payload — and each row says whether the window has even elapsed, so "not reworked yet" and "not reworked through the whole window" never read the same. The test-path classifier is a named heuristic over path shape (directory segments like tests/, basenames like *.test.*): it reads no file content, and its rule rides in every payload so a classified count is never mistaken for ground truth. The human trace ships its components (review decisions, escalations, progress notes) and refuses the one component the bytes cannot join — a remainder appended at close records no goal linkage — rather than approximating it. Rows are goals, never people: evidence, not a score.

The decision-log subject grammar (emerik(<type>): <verb> <id>) parses every shipped artifact familypin, anchor, lease, tombstone, goal, dependency, review, and the planning families brief/prd/architecture. So a Goal, Review, Dependency, or planning decision now surfaces typed in emerik history and counts in the rework denominator, rather than landing in unparsed. In particular a steward's adjustment-requested review resolution (emerik(review): adjust <id>) — a change literally sent back for rework, the closest real analogue the substrate has — is classified as rework alongside supersede and tombstone; a bless stays progression. Because these families now parse, the rate's denominator includes their decisions, and a planning supersede (a PRD or architecture revision) counts as rework under the same verb rule. Only a genuinely non-grammar .emerik/ commit (e.g. a hand-written chore: note) is still surfaced honestly as unparsed and never counted.

Those planning heads are first-class substrate: a brief, prd, or architecture is authored via emerik brief|prd|architecture author (or the MCP brief_author / prd_author / architecture_author twins) — a durable, append-only record whose prose lives in the committed markdown its --doc-path points at. A revision is a new head with --supersedes (never an in-place edit), so planning history replays, supersedes, and verifies like every other decision; emerik brief|prd|architecture list and status read the live head(s).

A worked example — goal to verified change

The loop opens with emerik-goal: you hand it an intent, and it shapes a Goal brief — Goal statement, testable acceptance criteria, and guardrails that cite the live Pins and Anchors they must honor — committed under .emerik-goals/, then started as a first-class Goal with goal_start --brief and handed to the plan skill. (You can also state a Goal and its acceptance criteria inline to the plan skill; the committed brief, the first-class Goal, or the pasted-in version all work.) From there the loop runs end-to-end:

Goal, stated to emerik-plan
# Goal Add rate-limiting to the login route.
# Acceptance criteria
#  1. Repeated failed logins are throttled.
#  2. Credentials are never written to a log.
01

Plan — grounded in the substrate

emerik-plan calls index_refresh so the plan reflects the latest committed facts, runs graph_retrieve for the Goal and each criterion, and lists live Anchors with anchor_list. A recently-moved Anchor is surfaced as a coordination note (a heads-up, not an alarm). It drafts checkbox steps that cite what they stand on, then commits the plan beside the Goal brief — the durable input to implementation, not chat:

 plan grounded in 3 Pins, 2 Anchors

- [ ] Step 1 — Add a retry counter to the login handler. Relies on Pin
         01J…A7 (login flow). Must honor constraint-Anchor 01J…D9
         "no unbounded retries" (BLOCK). P1.
- [ ] Step 2 — Redact credentials before logging. Relies on Pin 01J…B3
         (logger). Must honor antibody-Anchor 01J…E4 "never log
         credentials" (BLOCK). P0.

# the engineer commits the plan — the harvest hook fires
 .emerik-goals/rate-limit-login-plan.md

A step that cites no Pin or Anchor is grounded in raw code, not the substrate — the skill sends it back to retrieval. The committed doc means the next session (or a cheaper model) resumes from the first unchecked box — no need to keep this window open.

02

Implement — test-first, lease-guarded, verify-gated

emerik-implement picks up the first unchecked step from the committed plan doc (via goal_status — a fresh session needs nothing else), claims a Lease on the territory before writing, then writes the failing test first and watches it fail — implementing to green (red-green-refactor) only once the test proves the behavior is missing. It commits with the step's checkbox flipped (the post-commit hook harvests the new Pins for you — you never hand-craft them), runs emerik verify, and releases the Lease. The step is done only when the new tests exist and pass and verify is green — never on claimed-but-unrun tests.

next step: - [ ] Step 1 — Add a retry counter …   (.emerik-goals/rate-limit-login-plan.md)

▣ LEASED  auth/login · holder plan-agent · expires in 15m

# … edit, flip the box to [x], then commit — the harvest hook fires automatically …

$ emerik verify
 2 Anchor checks passed · no drift                                 exit 0

 released auth/login
Exact lexicon

The skills keep the vocabulary precise — Pin, Anchor, Goal, Lease, Steward, Antibody, Glacier; enforcement block / warn / advisory; priority P0/P1/P2 — and always write the product name emerik lowercase.

The skill format — mechanics and slots

Every shipped skill is partitioned into two kinds of region. Mechanics sections carry the substrate contract — lease, Pin and Anchor discipline, the entry and exit states, harvest honesty, the tool surfaces, the hand-offs. Slots are named soft regions — tone, framing, house conventions — and they are the only place a team's own voice belongs. The partition is total: every line of a skill is in exactly one region, so nothing sits in an undefined zone. The markers that draw the boundaries are HTML comments, invisible to the rendered page and to the agent reading the skill, so the partition changes nothing about what a skill says.

The shipped catalog records each skill's slot names and a content hash over its mechanics sections. emerik skill lint is the gate that checks it — deterministically, with no model anywhere in the loop, exactly the way emerik verify gates the store:

  • the catalog carries an entry for the skill, byte-synced with its frontmatter (name, description, entryState, exitState);
  • its dialect and every entry/exit state it names are drawn from the declared vocabulary;
  • the slot structure is intact — nothing unclosed, nested, duplicated, non-camelCase or unpartitioned;
  • the mechanics sections are byte-equal to the shipped baseline.

It exits 0 only when all of that holds, and names every finding specifically — never a bare failure. With no arguments it lints every skill under .claude/skills/; pass one or more directories to narrow the scan. A team's customization lives beside the catalog as an intent record at .emerik/skills/intents/<skill>.json — a durable statement of what the team wants, per named slot, rather than an edit to the file:

{ "skill": "emerik-implement", "slots": { "voice": "We frame tests as BDD." } }

An intent that addresses a slot the skill does not declare is a lint finding, so a customization can never reach into the mechanics — which is what makes breaking emerik through customization inexpressible rather than merely discouraged.

The skills membrane — one door in, and a patrol

Most repositories already carry skills of their own. They join the emerik workflow through exactly one door — emerik skill ingest — and until they do, a skill that was never ingested does not exist to emerik workflows. That is a MUST, not a preference: the whole point of a substrate that governs how agents work is that nothing governs them from the side.

Run it with no arguments and it sweeps both wired surfaces — .claude/skills/ and .agents/skills/ — classifying every skill it finds as shipped, admitted or un-ingested, and printing the interrogation contract it expects you to follow. The sweep is a listing, never a gate: it always exits 0. emerik init offers the same sweep the first time it wires a repository — one offer line per skill, never a bulk import — and the emerik-onboard skill walks it as part of a cold start.

The interrogation is done by the agent, one skill at a time, and it is deliberately not automatable:

  • audit the skill against the built-in discipline and the live Anchors, and make every finding cite the specific Anchor or discipline it violates, at block or warn;
  • a block conflict resolves exactly two ways — consent-gated rewrite surgery (the agent drafts the minimal conformant diff, the team approves it) or the skill stays out;
  • a warn conflict folds in annotated: emerik's rule wins at runtime and the conflict is recorded with --warn, surfaced by every later sweep, never suppressed.

Admission writes one file — .emerik/skills/catalog.json, beside the intent records — holding the skill's name, its directory, and a content hash per file. No Pin is minted and nothing is committed: the catalog alone is the admission record, and your own pull request is the last gate. Re-ingesting after a legitimate edit updates the entry in place, and that diff rides the review like any other change.

emerik verify then patrols what is installed, on every run. Hashes are always recomputed from the bytes on disk — the catalogs are records, never authorities — so a shipped skill must still match the packaged baseline in its mechanics sections and its supporting files, and a team skill must still match its admission entry file for file. An unregistered skill is skill-unregistered, a changed admitted file is admission-drift, a tampered or smuggled supporting file is supporting-drift, and any of them exits 1. Slot content is deliberately exempt: customization stays free exactly where it is safe.

The membrane runs in both directions. When a new Anchor lands through the review gate, it is audited against the admitted skills too, and a block-level collision stops the landing until whoever introduced it resolves the conflict — amend the skill, downgrade the level, or consciously supersede. A warn-level collision is surfaced, never suppressed. So there is no drift window on either side: nothing instructs your agents that did not come through the door, and no contract lands that quietly contradicts what came through it.

Building a skill — emerik-skillsmith

The membrane says what a skill must satisfy; the emerik-skillsmith skill is the engine that walks a person there. It has three doors and they all end in the same place — a real emerik skill lint run that exited 0.

  • Fold an existing skill in. Run the sweep first: it classifies everything the repository carries and prints the interrogation contract, which the skill then follows as printed rather than restating. One skill at a time, every finding citing its specific Anchor or discipline at block or warn; a block conflict resolved by the minimal conformant diff the team approves, or the skill stays out; each warn conflict recorded with --warn; then admission, then the gate. The directory name and the frontmatter name are kept equal — the directory is the admission identity, the frontmatter name is the routing identity. Bringing a folded skill into the mechanics-and-slots partition is offered and never required: an admitted team skill is exempt from it by design.
  • Author a new team skill. The skill ships a scaffold: a frontmatter whose entry and exit states are drawn from the declared vocabulary (checked mechanically on every skill, catalog entry or not), a dialect from the two-word vocabulary, and a body carrying the partition markers from the start — so the skill can be regenerated from its baseline later instead of only hand-edited. A new skill is still a non-shipped skill: it goes through the same interrogation and the same door.
  • Customize a shipped skill. Intent first, then synthesis. The team's durable wish per named soft slot is written to .emerik/skills/intents/<skill>.json before anything is generated; only then is the installed SKILL.md re-synthesized from baseline plus intent — mechanics verbatim, frontmatter and marker lines byte-untouched, supporting files never touched. The gate validates the intent record itself, so an intent that reaches for a slot the skill does not declare is a finding rather than a silent edit.

The rule that makes the whole thing honest is the same one emerik-implement lives by: done is reported only when the gate has actually run and exited 0, with its output quoted — never on claimed conformance. Nothing here is committed for you; your own pull request stays the last gate.

Upgrading — the regeneration ceremony

A new version of emerik ships new baselines. Your installed skills are carrying your customizations. emerik skill upgrade is what reconciles the two, and it is never a merge: the installed SKILL.md is a derived file, and the durable source of truth is the intent record, not the text. Every region of the regenerated skill comes wholesale from exactly one source — nothing is patched, spliced or three-way merged.

What regenerates. The mechanics sections, the frontmatter, the supporting files and every soft slot you never customized are written verbatim from the new packaged baseline. A supporting file the baseline does not carry is reported and left where it is — emerik tells you it is there, you decide whether it goes.

What survives. Your intent records. A slot with a standing intent keeps its currently installed text, carried forward word for word so the skill stays usable, and is listed as owing re-synthesis — with the intent statement quoted, because the intent says what you want, while the carried text only says what you wrote against the old mechanics. When the baseline moved, every intent-bearing slot is listed: that re-synthesis against the new mechanics is the ceremony.

What to review after. Three things, and the command names each of them specifically:

  • Slots owed synthesis. Your agent rewrites each listed slot from its quoted intent, editing only inside the slot markers, and finishes at emerik skill lint exit 0 per skill with the output quoted. Do this in the same sitting: once a carried-forward slot sits beside the new mechanics it reads as applied, so the pending list is printed by the run that creates it and not again.
  • Intents that need re-fitting. If a standing intent reaches for a slot the new baseline no longer declares, that skill is not regenerated — its files are left byte for byte as they were — and it is reported for re-fitting through emerik-skillsmith. Never a silent drop. Whether an intent still makes sense beside new mechanics is a judgment call your agent makes during synthesis; emerik reports only what it can actually compute.
  • Customizations a re-wire flattened. Re-installing the agent wiring restores every installed file to its packaged bytes by design — your customized skill included. The intent record survives that, and this ceremony is the recovery: a slot that reads exactly like the baseline while an intent addresses it is reported as owing synthesis, even when nothing else changed.

Slots owed synthesis and reported extra files are honest, everyday states — the command exits 0 on them. It exits non-zero only for what is actually wrong: an intent needing a re-fit, a regenerated file that fails its own closing lint, or a skill it had to skip because something underneath it is broken — a packaged baseline that will not parse, or an admission record that is present but unreadable.

Pointing it at a directory is a convenience, not an escape hatch: a named directory has to hold a SKILL.md and sit on one of the two host surfaces. Anything else is refused by name before a single byte is written — the command regenerates installed skills, and it writes nowhere else.

One last thing, and the command says it on every run: run the upgrade with a frontier model. The deterministic half is emerik's and is the same every time; the synthesis half is a writing job against fresh mechanics, and regeneration quality is a function of the surgeon. Upgrade day deserves the best one you have.

PM workstation setup

Project managers use emerik-scope to define an epic without writing code or running commands. Complete this checklist before the first session.

  • Access to the repository. The session reads existing commitments and stores the epic brief beside the code. Open the engineers' clone or your own copy where the agent can reach it.
  • An agent harness. The skill runs inside a coding agent (for example Claude Code or Cursor). That is where the session happens: you talk, it grounds the scope in the substrate and drafts the brief.
  • Your sources connected. Connect the design files, document stores, and project boards you use. If a source is unavailable, the session asks you to paste or export it.
  • emerik already initialized. Engineering sets emerik up on the repository once; you do not run any setup command yourself. If a scoping session says the substrate is not there yet, that is the signal to ask your engineering team to initialize it.

Then ask your agent to scope the epic. It grounds the scope in the repository, combines your sources, and commits a stakeholder-ready brief that engineering can break down directly.

Later, ask the same agent for a status report. It summarizes what shipped, what is active, what has not started, which acceptance criteria engineering signed, and what changed during re-scoping. The report is ready to forward.

The companion app

emerik team (apps/pm) is a read-only desktop app for project managers. It shows written briefings, current work, delivery measures, undefined initiatives, and cited answers. A local Claude Code instance acts as the analyst. The app presents the same facts as the CLI without exposing its technical interface.

The app includes its own emerik binary and reads only through the --json interface. It does not depend on PATH or a system Node installation. It cannot write priorities, closures, assignments, or any other substrate field.

Requirements

A local Claude Code installation is required to write briefings and answer questions. It runs under the customer’s own installation and billing. Without it, the app does not start the analyst. macOS ships first; Windows is planned next.

Builds are produced, signed, and notarized by Bloomteq, and installed copies pick up new versions through the app’s own signed update channel — nothing to download twice, nothing to configure on the reading side.

Substrate live view

emerik dashboard combines leases, Goals, the work trail, and delivery measures in one terminal view. It is read-only: it writes no artifact, changes no ref, and runs no daemon. Live refresh lasts only while the screen is open.

“Active agents” means active Leases. emerik does not run agents or record token, cost, or session telemetry. It knows who holds each exclusive-write Lease, which territory it covers, and when it expires. The dashboard labels those records as Leases. Review data reports how many changes reached a person; it does not estimate reviewer hours.

Nine sections, each a projection of one existing read:

SectionWhat it shows
agentsActive exclusive-write Leases — territory, holder, time-to-expiry, and which are yours.
goalsLive Goals — active and completed counts, then priority · team · title with the recorded progress log.
initiativesThe departure board's top slice — the total, how many are in the declared lane, how many are blocked, how many are undefined — then the lane rows and the one top-ready initiative. Computed by the same board query, never a second opinion; a glance, with emerik board one keystroke away for the whole inventory. It reads offline, so the 2-second live loop never touches the network.
contentionThe stigmergic trail's hot zones — the paths to route around, no messaging needed.
reworkThe drift-driven rework rate (the SM-1 decision verb-share) with its target and rough baseline band. A glance only — the governed / ungoverned split that gives the number its control costs a projection walk, so it lives on emerik metrics rework. See Instrumentation — the rework rate.
reviewThe review funnel — the agent-quorum auto-clear rate and the count escalated to a human.
retrievalRetrieval recall (SM-4) at the headline k, read from the recorded replay runs — both arms side by side, the act-verbatim arm’s N, how many runs are on record, and the signed delta since the previous one. Stated as a floor; a degraded run says so. The dashboard never runs a replay — this is a projection of the recorded history, and emerik metrics recall --run is what measures a new one. With nothing measured yet the line says so and names that command; a damaged run history is its own sentence with its own remedy. See Instrumentation — retrieval recall.
storeLive artifact counts per store directory.
indexThe local index, one leg per projection: search and relational, each with its state, indexed count and stale count, then how many edges the coupling graph holds. The search leg also carries tier and activeEngine — which projection is published (hybrid or lexical floor) and which engine this machine can load. A skew count appears only when it is non-zero — the number of answers since the last rebuild in which the two projections carried different generation receipts, so retrieval served the primary and dropped the decoration. emerik index rebuild heals it and resets the count.
FlagWhat it does
--linePrint the one-line statusline projection once and exit — for embedding in a shell prompt or editor statusline. Never starts the live loop.
--jsonThe whole snapshot as one camelCase envelope, once. No screen, no loop.
--watch <secs>The live-refresh interval for the interactive screen (default 2). The loop runs only while the screen is open.
--remoteMirror the authoritative remote lease ref first for the freshest cross-machine view — a read-sync fast-forward, never a claim, CAS, or push.

On a real terminal it opens the live screen: r forces an immediate refresh, q (or Ctrl-C) quits and clears the timer. Piped, redirected, in CI, or under --quiet it prints the same lines once as plain text — grep-friendly, no screen.

a glance at the swarm
emerik dashboard                   # the live screen (r = refresh, q = quit)
emerik dashboard --watch 5         # refresh every 5s instead of 2s
emerik dashboard --quiet           # the same view, printed once
emerik dashboard --json | jq .agents
what the static view prints
emerik dashboard · 2026-07-14T12:00:00.000Z
2 active leases · 3 active goals · 1 hot zone.

agents (active exclusive-write leases): 2 · 1 yours
  ▸ billing  · holder you@laptop (you)  · expires in 30m
  ▸ payments  · holder agent-7@ci  · expires in 1h 3m
goals: 3 active · 1 completed
  ▸ P0 platform · Ship the payments contract  · 2/2 progress
initiatives: 4 initiatives · 1 in the declared lane · 1 blocked · 0 undefined
  ▸ lane · Ship rate limiting  · blocked  · left 2
  ▸ ready · Move billing to webhooks  · active  · left 1
contention: 1 hot zone · route around, no messaging needed
  ▸ src/pay.ts  · heat 0.82
rework: 12.5% (SM-1 verb-share; split in metrics rework) · target 2.0% · band 5.0%–10.0%
review: auto-clear 80.0% · 2 escalated to a human · 10 reviews
retrieval: recall@10 act-verbatim 62.5% (SM-4 floor) · footprint 50.0% · 8 replayed · 3 run(s) · +12.5 pts
store: 214 live artifacts  · pins 186 anchors 21 goals 4 reviews 3
index: search built 186 indexed 0 stale · relational built 186 indexed 0 stale · 412 edges

Embedded in a statusline, the one-liner is the headline counts on a single line. It is deliberately minimal and stays that way — the initiative slice and the retrieval reading live on the screen, not in your prompt:

the statusline projection
$ emerik dashboard --line
emerik · 2 leases (1 yours) · 3 goals · 1 hot · rework 12.5% · auto-clear 80.0% · index built

Agents read the same view through the MCP tool dashboard_snapshot, which returns the identical snapshot shape over the identical substrate reads.

Continuous integration

Use emerik verify as a required CI step. It exits 1 when a block-level Anchor fails. The same run checks the skills membrane; an un-ingested skill or changed mechanics region also fails the job.

.github/workflows/ci.yml
- name: emerik verify
  run: |
    pnpm install --frozen-lockfile
    pnpm build
    ./bin/run.js verify        # fails the job on drift (exit 1)

The gate is incremental by default: it re-runs only the checks whose inputs moved and re-parses only the artifacts that changed, so CI time stays flat as Anchors accumulate. On a fresh runner there is no ledger to reuse, so the first run is a complete pass and says so; caching .emerik-local/verify/ between jobs is optional, never required. Add --full when you want the complete pass unconditionally — for example on a nightly job, or when a check depends on something outside the repository's own content.

The pre-push hook installed by emerik init re-derives Pin freshness before a push; pair it with the CI gate above for defence in depth.

Conventions

  • Command grammar is emerik <noun> <verb>; flags are kebab-case; the global flags --json, --quiet, and --no-color work everywhere.
  • JSON is camelCase everywhere — artifacts, --json output, and any future MCP surface.
  • IDs are ULIDs; the source content hash lives inside the artifact, never in the filename.
  • Append-only: a change is a new artifact with supersedes; a deletion is a tombstone.
  • --quiet suppresses success output only — failures always print.

Command reference

CommandPurpose
initInitialize emerik in a git repo — store, ignore rules, hooks, and agent wiring for the agents you select. On a terminal it asks which coding agents this repo uses (pre-checked by what the repo already carries) and remembers the answer as agents: in .emerik/config.yaml, so a re-init after cloning heals exactly that set. It never prompts under --json, with a redirected stdin/stdout, or with CI set: --agents claude-code,cursor names the set outright (comma-separated ids from claude-code, cursor, codex, opencode, gemini-cli, antigravity, windsurf, github-copilot — an unknown id exits 1 with the valid list and writes nothing), and without it the persisted list wins, then detection, then the harness-agnostic baseline (AGENTS.md + .agents/skills/) with the fallback stated in the report. --no-agents wires nothing at all. De-selecting an agent removes what a previous init wrote for it — registry-derived, entry-scoped (a shared AGENTS.md or .agents/skills/ survives while any selected agent still reads it, your own bytes are never touched, and a file emerik cannot parse is left alone with a warning), and reported one line per file even under --quiet. Only an answer removes: --agents, the prompt and a committed agents: key do, while a detected or baseline selection, --no-agents, a cancelled prompt and an empty selection remove nothing.
harvestHarvest source-bound Pins from a commit; re-derive stale ones. A Pin's body is a deterministic descriptor of its region — never its source. --synthesis <file> swaps the agent's own understanding in for the files it names (stamped synthesis: agent) — one named file, applied to this repo's own changed files only, so a workspace member bumped by the same commit keeps its descriptor; in hook mode .emerik-seed/synthesis.json is auto-discovered per repo, then consumed and cleared (so --synthesis is exclusive with --hook: a named file that is missing or malformed is a hard failure, and hook mode must never swallow it). The report answers with synthesized, resynthesisNeeded and unmatchedSynthesis (entries that named a path no Pin claimed — reported, never fatal). --review <file> deposits human PR review lessons from a handshake file instead (exclusive with --hook / --ref).
anchor authorAuthor a steward Anchor, or promote a reviewed Pin (--territory/--holder gate the write on the live Leases — a territory held by another refuses it; --repo scopes the contract to a workspace member). When the local search index is built and now behind, the output names it in one line — the Anchor you just wrote is not in the index serving retrieval until emerik index refresh runs. A hint, never a gate: nothing is refreshed for you and the exit code is unchanged.
anchor listInventory Anchors with lineage. Bounded (FR-39): narrow with --subtype / --enforcement / --steward / --governs-path, page with --limit (1–10 000) + --cursor, and pass --id for the full Anchor; entries are summaries (an excerpt, not the full content) and a truncated page ends with an explicit showing N of M — continue with --cursor … footer.
verifyRun Anchor checks + convention lint — the CI gate. Incremental by default (FR-41): unchanged, previously-passing checks resolve from a last-verified ledger in .emerik-local/verify/ and unchanged artifacts are not re-parsed, while the checks that do run execute in parallel; --full forces the complete pass, and a missing or unreadable ledger degrades to one automatically with the reason printed.
retrieveAnswer a context query with one hybrid search over Pins and Anchors, each hit decorated with who governs it and who holds a lease there, and each saying whether the code it describes has moved. --path (repeatable), --kind (repeatable), --steward, --enforcement, --since, --budget, --limit, --refresh / --no-refresh (fetch the workspace meta-repo's main before reading; offline posture). With --path the governors of the named files come first and the query may be empty. Every answer carries an envelope that says what it is worth and an outcome; an empty answer always names why it is empty.
index rebuild · refresh · statusManage the two local projections — the hybrid search half over Pins, Anchors and practice documents, and the relational half (the derived coupling graph plus the record-derived read models) — both maintained per invocation, both stamped with one generation receipt.
graph blast-radius <seed>Name what else moves when a file or artifact moves, traversed over the derived coupling graph (--depth 1–3, default 2; --limit caps the whole answer and reports the cut as truncation; --json). The seed is a repo-relative path, a repo-qualified <repo>:<path>, or an artifact id. Each result carries its hop, the coupling that reached it with its weight, and the artifacts that govern it. The edges are derived (decayed co-change + import-line scanning, no AST), so the answer is a lower bound and says so with a named reason and remedy. An unbuilt index is an exit-0 answer naming the remedy, never an error. Read-only.
explainer declareRecord a practice document that already exists in the repo (--path, --title, --description; --repo scopes it to a workspace member, --synthesis records whether the prose was agent- or human-written). --anchor is repeatable and names the member Anchors that carry the practice’s obligation — each must be a live Anchor, and a dead id is refused with its live successor named. One document, one practice — if it needs two titles it is two documents. Re-declaring the same path supersedes the previous record, which is how the pointer catches up after an edit and how membership is updated; there is no separate relink verb. A re-declare must repeat the membership: omitting --anchor while a member Anchor is still live is refused (nothing is written) and the refusal names the current live ids, while --detach-anchors removes membership deliberately and leaves the prose unenforceable.
explainer listList the live practice documents, with a note on any whose file has changed since it was declared or is not on disk here, and a known-wrong marking on any whose member Anchor is no longer live. Informative only — never a warning, always exit 0. --paths <path> (repeatable) narrows the page to the practice documents whose derived region covers at least one given path, filtered before pagination so the counts stay honest; a practice with region none always passes — the region narrows relevance, never grants it. Bounded (FR-39): --limit / --cursor page it and --id serves one record; a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }.
explainer show <id>Show one record and where to read it, plus its member Anchors (each with its enforcement level and check), its derived region (a path union, repo-wide, or none) and any known-wrong marking. It never prints the document itself — the file is the one copy, and it is authoritative. Always exit 0: the enforcement lives on the member Anchors, in emerik verify.
explainer conflictName a conflict between an intent and an anchored obligation a practice document explains (--anchor, required and single — one call names one conflict; --intent is optional and echoed back verbatim so the answer is self-contained). It names every claiming document, quotes the clause, and states the declared enforcement — then gets out of the way: it never refuses and never blocks, and every answer exits 0 except a malformed id. It writes nothing. The threshold is structural: the input is an Anchor id, so prose disagreement cannot reach it. A superseded or retracted obligation is an answer naming its live successors, never a refusal.
explainer cite <id>Record which clause of a practice document authorized what you did (--clause, --act, --moment required; --anchor for a clause that is an anchored obligation of that same document, --goal to append one progress note, validated first so a bad id writes nothing). A citation, never an attestation — it names something a reviewer can check, and because it is committed on your branch it rides the same pull request as the work. The output carries a paste-ready block for the PR body, the one place a human sees it while deciding to approve, and --json carries the practice’s heat AFTER your citation (citations, lastCitedAt, byMoment, heat, cold) so the loop closes in one call — the same fields the MCP twin returns. Citations accumulate into heat on every read surface; heat is an instrument and never re-ranks, excludes or gates anything.
explainer remove <id>Retire a practice document’s record with a tombstone (--reason). A stale practice is removed, not flagged: a warned document is still in the retrieval pool, still coming back from search and still shaping work, so explicit closure beats silent decay. A document uncited past the cold line (≈60 days, derived from the shipped heat constants) surfaces as a deletion candidate on show and list — cold means unused, not wrong, and it stays fully in the pool until a human removes it. emerik never deletes your file; delete it in the same change.
glacier freeze · thaw · listArchive cold Pins losslessly; bring them back.
goal start · advance · status · listThe concurrent work-unit — one active Goal per team against the shared graph; start takes --priority (P0/P1/P2, default P1), the optional --initiative <ulid> attribution (which is what gives an initiative its heartbeat; omit it and the Goal is simply unattributed), and flags recently-changed depended-on contracts as coordination events. A repeatable --territory on start and advance scopes the Goal in the lease vocabulary and grounds the derived cycle a completed Goal's status reports; the acting git identity is recorded as its owner — observed, never declared. advance --complete on an initiative-attributed Goal takes a repeatable --remainder <text> — what finishing it revealed is still left — and, without it, prints the question once and never blocks. list is bounded (FR-39): --limit / --cursor page it and --id shows one full artifact; a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }.
dependency declare · status · listContract-dependency on a published Anchor; notified when the contract changes. list is bounded (FR-39): --limit / --cursor page it and --id shows one full artifact; a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }.
lease claim · renew · release · listExclusive-write territory leases via git-ref CAS. claim --goal links a lease to the live Goal it serves (title and initiative stamped from the goal's recorded bytes); list serves the complete Now row — the goal link plus last-commit recency per territory, with absences stated, never zeros.
trail statusRead the stigmergic trail — hot zones + lease markers — to route around, no messaging (--remote, --limit).
review [domain]Open the batched review digest (the one v1 TUI screen) — boilerplate collapses to one faint row, only escalated changes surface; an optional domain scopes it. Keyboard-only; non-TTY / --json print the static digest / model.
review open · listRecord + route an agent-quorum review — cleared by quorum, or escalated to a human when it edits a high-dependent Anchor (--anchor, --verdict, --domain, --routing filter on list). list is bounded (FR-39): --limit / --cursor page it and --id shows one full artifact; a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }.
review bless · adjustResolve an escalated review through code — bless clears it on the steward's authority, adjust sends it back; each mints a superseding decision Review (--note, --by).
steward resolveResolve stewardship for a code path — the governing Anchor(s) + accountable steward, most-specific first (inverted CODEOWNERS, FR-17). Deliberately not paged: constraint discovery for a path is complete and never truncated.
steward constraintsList a steward-agent's standing constraint set — its constraint-Anchors in accrual order (FR-18). Bounded (FR-39): --limit / --cursor page it and --id shows one full artifact; a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }.
antibody mint · bless · list · retractThe antibody loop (FR-19) — mint a constraint learned from a failure (--supersedes/--expires-at), bless a pending high-impact one (OQ-8), list a domain's antibodies — bounded (FR-39): --limit / --cursor page it and --id shows one full artifact, a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor } — and retract a false one (a Tombstone, AR-14).
metrics reworkReport the drift-driven rework rate (FR-26) and its governed-path control (FR-127) — a read-only derivation over the decision log; a baseline plus an optional --since ongoing window, each with the governed / ungoverned split beside it.
metrics review-timeReport the review funnel per Goal (FR-26, SM-2) — the agent-quorum auto-clear rate + a drafted human-review-load proxy (reviewer-hours are not instrumented). Read-only.
metrics ledgerThe closed-goal delivery ledger (FR-119) — every Goal that ever completed, newest close first, each with its derived cycle, diff facts over its territories (diffLines / files — distinct paths across the matched commits / touchedTests — a named path-shape heuristic, never ground truth), rework within a tunable window after close (--rework-window weeks, default 4 — a PRD assumption; each row says whether the window has even elapsed), the human-trace components, and the land record read from emerik land's own folded timeline. An unobserved diff or an unlanded goal is stated with its reason, never rendered as zero. --team / --since subset rows and never change their numbers; an empty ledger is a note at exit 0. Read-only.
metrics recallRetrieval recall over this repo’s own citations (FR-125) — SM-4’s first measurement, reported as a floor and as a trend over recorded runs: both arms (act-verbatim and footprint) side by side and never merged, the negatives (a raw-index probe that can read low, and a surface assertion that must read zero), the lower-bound envelope with each cause’s remedy, and a signed delta that appears only once a second run exists. Reads the recorded runs by default — one small local file, no embedder; --run measures a fresh run first (bounded by citation count, writing only under .emerik-local/) and exits 1 only if that run itself faults. Nothing measured yet, and a damaged run history, are distinct states reported at exit 0 with their own remedies. Read-only. See Instrumentation — retrieval recall.
metrics hit-rateThe warning hit-rate (FR-126) — of the reviews escalated to a human, the share the substrate had already warned about, with per-mechanism attribution. Three mechanisms (a high-tier anchor, a binding antibody governing a finding’s file, a practice document whose live member anchor the review declared), any one of which makes a hit; the per-mechanism counts are counts of escalations and are never summed. Stated as a floor, with the coverage boundary surfaced as a number and the excluded readings (region coverage, plain-anchor stewardship, repo-qualified antibodies) named in the definition with their reasons. Also reports what happened after the human looked: blessed, sent back, blessed-then-reworked. No flags beyond the global ones; zero escalations is a designed success at exit 0. A pure read — writes nothing, anywhere. See Instrumentation — the warning hit-rate.
metrics attentionThe attention audit (FR-128) — not whether retrieval surfaced the right thing, but whether anyone looked at it. Labels every recorded flight with what happened next (cited / edited / ignored; no later evidence at all leaves it unjudged rather than ignored) and, per live goal, sorts the governing artifacts over the paths that goal touched into surfaced-and-cited, surfaced-not-cited, cited-not-surfaced and no-recorded-consultation — with the goal’s rework reported beside the ignoring, never as its cause, both counts with their own denominators. --goal <id> narrows to one live goal; a goal with no declared territories is skipped by name. Stated as a floor: the flight recorder is machine-local, gitignored and reset by every full emerik index rebuild, so surfaced is undercounted and “no recorded consultation” is a ceiling on the suspicion, never proof nobody looked. The one write in the metrics family, and it is derived state: the followOn labels, into the local recorder they annotate. Every surfaced state is exit 0. See Instrumentation — the attention audit.
metrics mirrorYour own mirror signals (FR-123) — the per-person evidence row for the acting git identity (git config user.email), served to the engineer first: goals closed, test contact, median cycle, human trace, tenure, own-line churn, the review-density refusal, and the conjunction that cannot fire on partial evidence. Under the floors (10 goals / 30 days observed tenure) the row is a typed refusal stating what is missing — a designed success at exit 0, like every refusal here (no roster, no acting identity, an undeclared identity shown raw with the org.yml fix). --all serves every declared member's row — same rows, same computation, alphabetical, never ranked; there is no flag for one other person and no export of any kind. Read-only. See The mirror signals.
dashboardThe substrate live view — active exclusive-write leases (“who is working now”), live Goals, contention hot zones, the rework rate, the review funnel, retrieval recall (SM-4, read from the recorded replay runs — the view never runs one), and store/index health — the index panel reports both local projections (search, relational), each with its own state and staleness, plus the skew count — in one terminal screen (--line for a statusline one-liner, --json, --watch, --remote). Read-only; no daemon.
brief author · list · statusThe native planning head for a product brief (FR-33) — author commits a durable, append-only brief (--title, --doc-path the committed markdown body, --steward, --supersedes to revise the live one); list the live briefs (--all includes superseded history) — bounded (FR-39): --limit / --cursor page it and --id shows one full artifact, a truncated page ends with an explicit showing N of M — continue with --cursor … footer, and --json carries { items, total, nextCursor }; and status shows the live head(s).
prd author · list · statusThe native planning head for a PRD (FR-33) — same append-only grammar as brief: author (--supersedes revises), list (--all, bounded — --limit / --cursor / --id), status.
architecture author · list · statusThe native planning head for a solution/architecture design (FR-33) — same append-only grammar: author (--supersedes revises), list (--all, bounded — --limit / --cursor / --id), status.
seed --analyze · seedCold-start a brownfield repo into the graph (--repo targets a workspace member, on both). Analyze holds scaffolding out by default — tests, migrations, locales, generated/vendored trees, lockfiles, binary assets — reporting notAdmitted with a per-rule breakdown; --admit-all analyzes every path instead. --synthesis <file> swaps the agent's understanding in for the files it names.
workspace addRegister a code repo as a workspace member — a git submodule under code/<name> plus a manifest entry, one commit (--name, --path); never harvests.
workspace listThe workspace manifest — every member with its recorded (gitlink) vs checked-out SHA.
workspace statusThe consolidation view — per-repo drift, branch, dirty flag, live Pin/Anchor counts, the always-rendered auto-sync panel, and the positions readout: who is where, per clone, stale after 7 idle days; opted-out members labeled as syncing manually (exit 0; --refresh / --no-refresh).
workspace syncAdvance member repos to their remote tips, commit the bumps, and harvest exactly the moved ranges (--repo repeatable). CI is the scheduled writer — a manual run still works and says so.
workspace autosyncPush and integrate the meta-repo's main now — the background auto-sync's own primitive, runnable by hand (--refresh / --no-refresh; --auto honors the org.yml opt-out — the background trigger's flag; exit 1 when the sync could not complete, exit 0 on inactive).
workspace reconcileClear a reconciliation halt after resolving the divergence by hand, then re-prove convergence (--head <sha> required, quoted from workspace status; exit 1 when the reconcile could not complete).
workspace snapshotRecord the release SHA-tuple — every member at its recorded gitlink plus the meta commit, as one append-only artifact (--release, required). CI-written; reruns are no-ops.
workspace checkoutReproduce a recorded release — every member moved to its snapshot SHA, detached. Writes nothing, commits nothing (--dry-run).
initiative declare · amend · list · close · sweepThe workspace intent ledgerdeclare a unit of intent (goal, --finish-condition, committed brief, declared lineage, optional --lane), amend the finish condition or the lane (--finish-condition, --lane / --no-lane), list the live heads with their derived state (--status filters the record lifecycle, not the state), close one deliberately (--outcome and --statement required, --cause required unless the outcome is done; the output carries the support check), and sweep the TTL death ritual (--ttl-days, --truth-ref; --cause / --statement are the words a close-out's closure carries — without both, confirmed deaths are deferred with a note). Every change is an append-only superseding record; the row is never tombstoned. Sweeping exits 0 whether it flagged something or nothing.
initiative rollupAggregate the closed initiatives — the listing (outcome · cause · statement · when — plus each closure's finish condition, goals closed, and its still-open remainder, aged from declaration and still counting) plus counts by outcome and two-bucket cause per team and quarter (--refresh / --no-refresh, --json). Diagnoses the system, never a person. Read-only; an empty rollup is a note at exit 0. Distinct from top-level rollup, which distils decision history into an Anchor.
boardThe departure board — every live initiative on one line, tier-ordered (declared lane → ready → active → undefined → blocked), with what is left, the trajectory glyph, when it last moved and who it is waiting on. Facets subset the same query and never re-sort it: --team, --state (repeatable), --ready; --events adds the derived lifecycle stream; --refresh / --no-refresh (offline). Read-only, exit 0 — its one refusal is --team with no roster.
remainder declare · resolve · reaffirm · listWhat is left on an initiative — declare one thing still outstanding (--initiative required, --owner makes it a blocker, --by), resolve it (--resolution done|withdrawn, an append-only superseding record), reaffirm a blocker (it is still true — resets the check-back clock, never the age), and list it (--initiative, --open). Progress is measured by the remainder, never a percentage.
roster status · draft · listThe declared roster (.emerik/org.yml) — status reports present-with-counts / absent-with-the-reason / invalid-with-named-findings, draft emits a commented skeleton from observed git contributors (--write, refusing to overwrite), list serves the roster itself: teams and members alphabetical, each member with git identities, a stable opaque id, and the declared role / repos (version 3) — a member with no identities marked, never dropped. Engineer-owned and declared: never derived from git history or CODEOWNERS.
diffThe merge gate — render the .emerik/ delta of a range as reviewable claims plus the three contamination flags (--base required, --head defaults to HEAD, --initiative, --json). Read-only; exit 0 whenever it renders, flags or not; exit 1 only on DIFF_BAD_REF / DIFF_IO_ERROR.
historyReplay the decision log or an artifact's lineage.
compactGC superseded Pins; keep the latest fact.
landSquash an approved branch into one emerik(land) commit — the per-decision timeline folded into its body, the pre-squash head kept under an archive ref (--dry-run, --yes, --trunk, --summary). Gated on an approved Review and a green verify.
rollupDistil a domain's history into a narrative rollup.
skill lintThe skill-format gate — check installed skills against the shipped slots-and-baseline format: catalog entry present and byte-synced with the frontmatter, dialect + entry/exit states drawn from the declared vocabulary, slot structure intact, mechanics sections byte-equal to the shipped baseline, and any intent record under .emerik/skills/intents/ valid and addressing declared slots only. With no arguments it lints every skill under .claude/skills/; pass one or more directories to narrow it. Exit 0 only when everything holds; every finding is named specifically (--json).
skill ingestThe skills membrane — the one door a non-shipped skill joins the workflow through. With no arguments it sweeps .claude/skills/ and .agents/skills/, classifies every skill as shipped / admitted / un-ingested, and prints the per-skill interrogation contract (a listing, never a gate: exit 0 whenever it can list honestly — an admission record that is present but unreadable is refused instead, because a listing built on it could only lie). With one or more directories it admits them — whole-file hashes recomputed into .emerik/skills/catalog.json, WARN-level conflicts recorded with --warn (repeatable). Shipped names are refused; no Pin is minted and nothing is committed (--json).
skill upgradeThe regeneration ceremony — re-derive installed skills from the new shipped baseline plus your standing intent records, never a merge. Mechanics, frontmatter, supporting files and un-customized slots are written verbatim from the new baseline; a slot with a standing intent keeps its installed text, carried forward, and is listed as owing re-synthesis with the intent quoted. An intent reaching for a slot the new baseline no longer declares leaves that skill byte-untouched and reports it for re-fitting through emerik-skillsmith. Supporting files the baseline does not carry are reported, never deleted; nothing under .emerik/ is written. With no arguments it covers every shipped skill on .claude/skills/ and .agents/skills/; pass directories to narrow it — a named directory must hold a SKILL.md and sit on one of those two surfaces, or it is refused by name. Prints the ceremony contract and the frontier-model note on every run; exit 0 on slots owed synthesis, non-zero on an intent needing a re-fit, a failed closing lint, a skill skipped because its packaged baseline or the admission record could not be read, or a run-level problem — a self-check finding that belongs to no skill directory, or an intents directory that is present but unreadable — reported in the report’s own problems (--json).
mcpStart the stdio MCP server exposing the substrate as agent tools.

Every command supports --help. Run emerik <command> --help for the full flag list.