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.
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.
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 enableor from pnpm.io.
Installation
emerik v0.8.0 installs from source. Clone the repository, install dependencies, and build the CLI bundle.
Clone & build
git clone <emerik-repo-url> emerik
cd emerik
pnpm install
pnpm build # tsc --noEmit + tsup + oclif manifest → dist/
Verify the binary runs
./bin/run.js --version # → emerik/0.8.0 …
./bin/run.js --help # show the full command tree
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
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.
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.
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.
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.
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.
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.
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.
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.
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/.gitignoreand a root.gitignorerule for the ephemeral local index.- a
post-commithook (auto-harvest) and apre-pushhook (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 did | What gets wired |
|---|---|
--no-agents | Nothing at all. No skills, no MCP registration, no
AGENTS.md — just the store, the ignore rules and the hooks. |
--agents claude-code,cursor | Exactly 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 yet | Whatever 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 detected | The 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-consultandemerik-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 nextinit, theemerik verifyskill patrol covers both surfaces, and neither is ever harvested as knowledge..claude/agents/— only when Claude Code is selected. The four reviewer roles theemerik-reviewskill fans out:blind-hunter,edge-case-hunter,acceptance-auditorandsubstrate-auditor. Copied verbatim, tool allow-list included..codex/agents/— only when Codex CLI is selected. The same four roles as Codex subagent TOML:name,descriptionanddeveloper_instructionscarrying the role's instructions unchanged. Nothing else is written —model,sandbox_modeand 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.mdextension. The extension is part of the contract: Copilot loadsx.agent.mdand does not loadx.md..mcp.json— only when Claude Code is selected. Registers theemerik mcpserver (merged additively, other servers untouched)..cursor/mcp.json— only when Cursor is selected. The sameemerik mcpserver, samemcpServersshape, merged additively the same way..codex/config.toml— only 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 dottedmcp_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.json— only when OpenCode is selected. At the repo root (not under.opencode/), under OpenCode's ownmcpkey and in its own shape:{"type": "local", "command": ["emerik", "mcp"], "enabled": true}. A$schemayou already have is preserved; emerik never adds one..gemini/settings.json— only when Gemini CLI is selected. The server undermcpServers— exactly the keygemini mcp add -s projectwrites. emerik adds a server and nothing more: it never touchescontext.fileNameor anything else that would change what Gemini reads..agents/mcp_config.json— only when Antigravity is selected. The server undermcpServers, 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.md— only when Antigravity is selected. Created; emerik owns that file outright.CLAUDE.md— only when Claude Code is selected and the file already exists. Never created.GEMINI.md— only when Gemini CLI is selected and the file already exists. Never created — see the note below.
initheals 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
emerikkey undermcpServers(or OpenCode'smcp), 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 anAGENTS.mdcarrying your team's own conventions keeps every one of them. AnAGENTS.mdor.agents/rules/emerik.mdthat emerik created and that now holds nothing else is deleted. ACLAUDE.mdorGEMINI.mdis 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 wasmcp.jsongoes; a.claude/still holding yoursettings.local.jsonstays. - 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 namedwindsurfbecause its whole surface isAGENTS.mdplus.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 missing →
HARVEST_REVIEW_FILE_MISSING, exit1. - The file is malformed — bad JSON, a wrong
formatVersion, or any structurally invalid entry →HARVEST_REVIEW_HANDSHAKE_INVALID, exit1, 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 withHARVEST_REVIEW_SOURCE_UNRESOLVED; every other valid entry still mints and the run exits0. 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:
- 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.
- 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”.
- 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. - 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:
| Disposition | What lands |
|---|---|
| No-residue nit | Nothing. The fix is the whole outcome — most review comments are honestly one-offs. |
| Durable lesson bound to source | One handshake entry → a review-origin Pin on the settled region. |
| Generalizable rule | An 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:
| Thread | Disposition | What was done | Artifacts |
|---|---|---|---|
| #42 · pool.ts:118 | addressed | bounded the worker pool; committed | — |
| #42 · pool.ts:118 | captured | durable lesson deposited on the fixed region | Pin 01J… |
| #42 · retry.ts:64 | promoted-candidate | recurrence (taught in 3 reviews) — Anchor preview shown, awaiting confirmation | preview only |
| #42 · legacy.ts:9 | skipped | source 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.
| Flag | Options | Meaning |
|---|---|---|
--subtype | contract · constraint · steward · antibody | What kind of contract this is. |
--enforcement | block warn advisory | What happens when the check fails. |
--check-kind | grep · test · lint · fitness · agent | How 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 repo — emerik 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.
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-refor--check-kindchanged; - 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
HEADtree plus a digest of every dirty path, so the invalidation is deliberately conservative: agrepjudges 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 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.
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.
$ 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:
$ 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:
$ 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.
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:
$ 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:
$ 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.
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.
$ 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:
$ $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.
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:
$ 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 warn | The verify-time gate | |
|---|---|---|
| Judges | Your intent, before the work | Your code, as it stands |
| Fires | When your agent pulls it — it noticed a contradiction and asked | On every emerik verify, pushed by the Anchor’s own check |
| Answer | Names the document and the clause. Always exit 0 | Passes or fails at the Anchor’s declared level. Exit 1 on a blocking failure |
| Repair | Amend the practice — or proceed; the choice is yours | Fix 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:
$ 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.
$ 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:
$ 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.
$ 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.
| Command | What it does |
|---|---|
emerik explainer declare | Record 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 list | List 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 conflict | Name 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.
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.
$ 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
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 isemerik 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.
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,anchororexplainer. The default is Pins and Anchors; practice documents areexplainer 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,warnoradvisory) — 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.
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.
| Command | What it does |
|---|---|
emerik index rebuild | Rebuild 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 refresh | Incrementally 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 status | Report 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:
| Rung | What answers | What you get |
|---|---|---|
| Hybrid | The search index (exact terms + meaning, fused) | The full answer — tier: "full". |
| Lexical floor | Node’s built-in SQLite full-text search, over the same corpus | Exact 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. |
| Nothing | — | tier: "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.
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.
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.
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 priority — P0, 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.
| Command | What 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 advance | Record 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 status | Show 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 list | Live Goals — active by default, --all includes completed; a bounded page (--limit / --cursor / --id, FR-39). |
emerik dependency declare | Declare a contract-dependency on a published Anchor (--anchor, --team, --note) — never live code under lease. |
emerik dependency status | Show the currency of a team's dependencies — current, changed (a coordination event), or retired. |
emerik dependency list | Live dependencies — --team / --anchor (lineage-aware) filters; a bounded page (--limit / --cursor / --id, FR-39). |
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.
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.
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.
| Command | What it does |
|---|---|
emerik dependency declare --anchor <id> | Declare a contract-dependency (--team, --note) on a live-head Anchor. |
emerik dependency status | Each of a team's dependencies as current, changed, or retired. |
emerik dependency list | Live 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 viaemerik 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--jsonenvelope is unchanged; the notification surface for agents is the pull-based status). - At Goal start.
emerik goal startrenders 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 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 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.
| Command | What 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 list | Active 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).
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 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.
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.
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
trail — emerik 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/leasesare the authoritative contested signal — territory · holder · expiry, shown so others route around them.
| Command | What it does |
|---|---|
emerik trail status | Read 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 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 initas the one manual command (it installs all twenty-two skills), then invokeemerik-onboardin 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.mdinside the globally-installed package and follow it — the skill drivesemerik inititself as phase 1, self-installing for every future session.
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 --repo —
emerik 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
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.
| Command | What 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 list | The manifest, plainly: every member with its recorded (gitlink) vs checked-out SHA. |
emerik workspace status | The 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: manual — a 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 sync | Advance 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 autosync | Push 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 reconcile | The 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 snapshot | Record 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 checkout | Reproduce 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.
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.
$ 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.
$ 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)
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.
| Command | What it does |
|---|---|
emerik initiative declare | Declare 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 list | The 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 rollup | Aggregate what ended — every closure plus counts by outcome and cause per team and quarter. Read-only; an empty rollup exits 0. |
emerik initiative sweep | The 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:
- 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.
- 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.
- 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.
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.
| Tier | What sits there, and in what order |
|---|---|
| lane | The 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. |
| ready | Active, 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. |
| active | The rest of the live work: moving (a Goal closed under it this week) before not-moving, freshest motion first. |
| undefined | Nobody has said what finished means. Oldest first — the oldest initiative nobody can define is the loudest question in the room. |
| blocked | Waiting 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.
$ 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.
| Flag | What 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. |
--ready | Keep only what someone could pick up today. |
--events | Also 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-refresh | Refresh 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.
| Command | What 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 list | What 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). |
$ 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.
$ 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.
| Command | What it does |
|---|---|
emerik roster status | Show 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 draft | Emit 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 list | List 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
question — emerik 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.
$ 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.
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.
| Command | What it does |
|---|---|
emerik history | Replay the .emerik/ decision log, or one artifact's lineage — the why behind a fact. |
emerik compact | Garbage-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.
| Command | What it does |
|---|---|
emerik land | Squash 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.
| Flag | What it does |
|---|---|
| --dry-run | Run every gate, print the range, the folded event count and the exact commit message; rewrite nothing. |
| --yes | Skip 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 mcpstdio 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.jsonor.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-consultandemerik-reconcile.- the four reviewer roles
emerik-reviewfans 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.mdand.agents/rules/emerik.mdare created when claimed, whileCLAUDE.mdandGEMINI.mdare decorated only when you already have them.
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:
| Tool | What it does |
|---|---|
| graph_retrieve | Answer 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_radius | Given 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_list | List 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_author | Author 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_start | Start 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_advance | Record 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_status | Show a team's active Goal (or one by id); null when none, never an error. A completed Goal read by id carries a derived cycle — observed: true with firstObservedAt and unrounded cycleHours, or observed: false with the reason: no evidence is a reported fact, never a fabricated start. |
| goal_list | List 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_declare | Declare a contract-dependency on a published Anchor — never live code under lease. |
| dependency_status | Report each dependency as current, changed (a coordination event), or retired. |
| dependency_list | List 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_claim | Claim 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_renew | Extend a Lease you hold. |
| lease_release | Give a territory back. |
| lease_list | Inventory 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_status | Read the stigmergic trail — hot zones (heat) + lease markers — an agent routes around, no messaging (--remote, --limit). |
| dashboard_snapshot | Read 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_status | Per-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_reconcile | The 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_list | List 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_open | Record + 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_list | List 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_bless | Bless 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_adjust | Request adjustment on an escalated review — send the change back for rework, through code (a superseding decision Review). |
| steward_resolve | Resolve 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_constraints | List 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_mint | Mint 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_bless | Bless a pending high-impact antibody so it binds — the human gate before a block antibody can fail CI (OQ-8). |
| antibody_list | List 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_retract | Retract a false antibody — an append-only Tombstone (no in-place delete); it no longer binds future agents. |
| metrics_rework | Report 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_time | Report the review funnel per Goal — the agent-quorum auto-clear rate + a drafted human-review-load proxy (reviewer-hours are not instrumented). |
| metrics_ledger | The 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_recall | Retrieval 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_rate | The 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 floor — epistemic 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_attention | The 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_mirror | The 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_author | Author 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_author | Author a prd (product requirements document) — same append-only grammar as brief_author. |
| architecture_author | Author an architecture (a solution/architecture design) — same append-only grammar as brief_author. |
| brief_list | List 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_list | List 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_list | List 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_declare | Declare 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_list | List the live initiatives (supersession-resolved) — optional status filter (active / inactive / closed). No initiative_status tool — the live head is served here. |
| initiative_close | Close 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_amend | Amend 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_sweep | Sweep 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_board | Read 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_rollup | Aggregate 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_declare | Declare 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_resolve | Resolve a live remainder item — done or withdrawn — as an append-only superseding record, never a tombstone. |
| remainder_reaffirm | Re-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_list | List 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_status | Report 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_refresh | Absorb 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_declare | Record 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_list | List 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_search | Answer “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_conflict | Name 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_cite | Record 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 prBlock — paste 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_remove | Retire 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_status → graph_retrieve
→ anchor_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 statement — anchor_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_status → graph_retrieve → anchor_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_status
→ graph_retrieve → anchor_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_status → index_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.
| Command | What 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 list | Live 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-audit — one 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 clearedrow, 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-auditrow (it still counts inN cleared;M need youcounts escalated only). - The header voice is byte-exact:
198 cleared · 2 need you., an all-clear198 cleared · 0 need you., and an empty scopeNo 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).
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). Retract — emerik antibody retract <id>
(MCP antibody_retract) writes a Tombstone; the antibody drops from
the graph and stops binding. Supersede — emerik 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 load — humanReviews, 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 family — pin, 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 Add rate-limiting to the login route.
# Acceptance criteria
# 1. Repeated failed logins are throttled.
# 2. Credentials are never written to a log.
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.
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
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>.jsonbefore anything is generated; only then is the installedSKILL.mdre-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 lintexit0per 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.
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:
| Section | What it shows |
|---|---|
| agents | Active exclusive-write Leases — territory, holder, time-to-expiry, and which are yours. |
| goals | Live Goals — active and completed counts, then priority · team · title with the recorded progress log. |
| initiatives | The 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. |
| contention | The stigmergic trail's hot zones — the paths to route around, no messaging needed. |
| rework | The 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. |
| review | The review funnel — the agent-quorum auto-clear rate and the count escalated to a human. |
| retrieval | Retrieval 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. |
| store | Live artifact counts per store directory. |
| index | The 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. |
| Flag | What it does |
|---|---|
--line | Print the one-line statusline projection once and exit — for embedding in a shell prompt or editor statusline. Never starts the live loop. |
--json | The 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. |
--remote | Mirror 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.
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
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:
$ 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.
- 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-colorwork everywhere. - JSON is camelCase everywhere — artifacts,
--jsonoutput, 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. --quietsuppresses success output only — failures always print.
Command reference
| Command | Purpose |
|---|---|
| init | Initialize 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. |
| harvest | Harvest 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 author | Author 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 list | Inventory 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. |
| verify | Run 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. |
| retrieve | Answer 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 · status | Manage 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 declare | Record 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 list | List 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 conflict | Name 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 · list | Archive cold Pins losslessly; bring them back. |
| goal start · advance · status · list | The 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 · list | Contract-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 · list | Exclusive-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 status | Read 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 · list | Record + 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 · adjust | Resolve 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 resolve | Resolve 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 constraints | List 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 · retract | The 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 rework | Report 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-time | Report 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 ledger | The 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 recall | Retrieval 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-rate | The 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 attention | The 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 mirror | Your 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. |
| dashboard | The 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 · status | The 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 · status | The 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 · status | The 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 · seed | Cold-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 add | Register a code repo as a workspace member — a git submodule under code/<name> plus a manifest entry, one commit (--name, --path); never harvests. |
| workspace list | The workspace manifest — every member with its recorded (gitlink) vs checked-out SHA. |
| workspace status | The 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 sync | Advance 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 autosync | Push 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 reconcile | Clear 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 snapshot | Record 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 checkout | Reproduce a recorded release — every member moved to its snapshot SHA, detached. Writes nothing, commits nothing (--dry-run). |
| initiative declare · amend · list · close · sweep | The workspace intent ledger — declare 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 rollup | Aggregate 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. |
| board | The 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 · list | What 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 · list | The 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. |
| diff | The 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. |
| history | Replay the decision log or an artifact's lineage. |
| compact | GC superseded Pins; keep the latest fact. |
| land | Squash 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. |
| rollup | Distil a domain's history into a narrative rollup. |
| skill lint | The 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 ingest | The 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 upgrade | The 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). |
| mcp | Start the stdio MCP server exposing the substrate as agent tools. |
Every command supports --help. Run emerik <command> --help for the full flag list.