automagik-genie
CLAUDE.md351 starsChanged 7 months agoApache-2.0View on GitHub
Claude Code overlay: read and follow the canonical shared repository contract in AGENTS.md first. This file adds Claude-specific command and operational detail; shared rules belong in AGENTS.md. docs/ is a symlink to .docs-vendor/genie/ where .docs-vendor is a git submodule of automagik-dev/docs (Mintlify, public site at automagik.dev). Engineers see and edit docs/ as if it were a regular subfolder of the genie repo — the submodule machinery is mostly invisible. Workflow when editing docs: CI in automagik-dev/genie runs actions/checkout@v4 with submodules:…
- Reads credentials
- Commits and pushes
1 other project uses a version of this.
Adopted by
Most recently taken up by
- automagik-dev/genieword for word, first seen Apr 2, 2026
Other versions1 project uses a version of this.
Word for word · 1
- automagik-dev/genie★ 351100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository21 other files it gives its agents.
- AGENTS.mdAGENTS.md
- mikro/AGENTS.mdAGENTS.md
- skills/refine/prompts/claude.mdCLAUDE.md
- authoringSkill
- brainstormSkill
- councilSkill
- deslopSkill
- docsSkill
- fixSkill
- genie-hacksSkill
- genieSkill
- mergeSkill
- refineSkill
- reportSkill
- researchSkill
- reviewSkill
- skill-auditSkill
- verifySkill
- wishSkill
- workflySkill
- workSkill
The file
We split this CLAUDE.md into the rules it gives an agent
9 rules. About 0 are common, 8 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 208
Prefer linear code when a function reads as one workflow (CLI command body, orchestration step, request handler).
- Very specific: line 210
Only suppress with biome-ignore lint/complexity/noExcessiveCognitiveComplexity: when extraction would obscure a linear flow or break a …
- Distinctive: line 243
When reviewing comments from automated bots (CodeRabbit, Gemini, Codex):
- Distinctive: line 245
Read the actual code: before accepting any finding — bots often misread control flow
- Distinctive: line 257
Test alongside implementation, not after — tests are a spec, not a safety net.
- Distinctive: line 258
If something is hard to test, the abstraction is wrong.
- Distinctive: line 260
Keep PRs focused on a single abstraction change.
- Distinctive: line 273
Test CLI commands as a user would invoke them, not just as unit tests exercise them.
- Distinctive: line 282
Every contributor PR makes an advocate — celebrate contributions specifically, not generically.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Genie CLI
Claude Code overlay: read and follow the canonical shared repository contract in AGENTS.md first. This file adds Claude-specific command and operational detail; shared rules belong in AGENTS.md.
Commands
bun run check # Full gate: typecheck + lint + dead-code + test + cargo test --locked (fails, never skips, without cargo)
bun run build # Bundle to dist/genie.js (bun target, minified, single file)
bun run typecheck # tsc --noEmit
bun run lint # biome check .
bun run dead-code # bunx knip (has pre-existing false positives for biome/commitlint/husky)
bun test # All tests
bun test src/lib/v5/task-state.test.ts # Single fileDocs
docs/ is a symlink to .docs-vendor/genie/ where .docs-vendor is a git submodule of automagik-dev/docs (Mintlify, public site at automagik.dev). Engineers see and edit docs/ as if it were a regular subfolder of the genie repo — the submodule machinery is mostly invisible.
- Operator-facing pages (e.g.,
docs/installation.mdx,docs/security/key-rotation.mdx,docs/incident-response/canisterworm.mdx) appear on the public Mintlify site atautomagik.dev/genie/.... - Engineering-internal pages live under
docs/_internal/(architecture deep-dives, observability internals, agent-frontmatter contracts, CLI reference dumps, spawn-flow runbooks, detector specs). These are excluded from the public Mintlify build via**/_internal/inautomagik-dev/docs/.mintignore— visible inside the genie repo, hidden from public docs.
Workflow when editing docs:
# Make changes (the symlink follows into .docs-vendor/genie/)
$EDITOR docs/installation.mdx
# Commit + push the docs change to automagik-dev/docs
cd .docs-vendor
git checkout -b feat/<topic>
git add genie/installation.mdx
git commit -m "docs(genie): ..."
git push -u origin feat/<topic>
gh pr create --base main
# After the docs PR merges, bump the genie superproject pointer
cd .. # back to genie repo root
git submodule update --remote .docs-vendor
git add .docs-vendor
git commit -m "chore: bump .docs-vendor to docs main"CI in automagik-dev/genie runs actions/checkout@v4 with submodules: recursive for any workflow that needs docs content (docs-lint.yml, runbook-test.yml); the rest of CI ignores the submodule.
Never bump the pointer before the docs PR merges. git submodule update --remote .docs-vendor moves .docs-vendor to the docs default branch, so a bump taken while the docs change is still on a feature branch either picks up an unrelated main or — if you stage the submodule while it sits on your own branch — records a commit that main does not contain, and every fresh clone then fails git submodule update --init with reference is not a tree. A genie PR that depends on unmerged docs states the exact bump commands in its body and leaves the pointer alone; git status showing .docs-vendor as modified is expected in that window and is not staged.
Stable is declared for Linux and macOS. .github/workflows/ci.yml runs a unit-darwin leg on macos-latest as a required check alongside the linux one, so a change that breaks either platform does not ship. Windows is not a target; do not add a platform literal to a test without checking it against both legs.
Architecture
src/genie.ts CLI entry point (commander)
src/lib/ Core modules (install/update lifecycle, paths, config)
src/lib/codex-config.ts Backup-first removal of the obsolete Genie loopback OTel exporter
src/lib/genie-home.ts GENIE_HOME resolution and the per-agent home paths built on it
src/lib/v5/ v5 state engine — SQLite, zero-daemon ("lightweight body")
genie-db.ts Per-repo .genie/genie.db open/init (worktree-aware, WAL)
sqlite-open.ts Shared bun:sqlite open primitive (WAL, busy_timeout, typed errors)
task-state.ts Task / dependency / ready-set state machine
base-state.ts Integration-branch resolution + recorded wish-base state (context verb)
TAXONOMY.md The docs-in-git / state-in-SQLite contract
src/lib/skills-installer.ts The skills.sh channel — pinned CLI, local delivered source, install record
src/term-commands/ CLI command handlers (board, context, init, mikro, orca, shortcuts, task, ...)
src/term-commands/orca.ts `genie orca` — a one-release retirement stub (notice on stderr, exit 2)
src/term-commands/mikro.ts `genie mikro call|bench|coach|fixtures|init` — registration over scripts/mikro/*, imported in place
scripts/mikro/ The mikro microagent runtime (call/bench/coach/facts/boundary); NOT relocated under src/
skills/ Skill prompt files (brainstorm, wish, work, review, etc.)
.genie/ Per-repo state: git-tracked wishes/INDEX.md/brainstorms/*/DESIGN.md; brainstorm notes + genie.db gitignoredCLI Commands
Eighteen top-level commands (run genie <command> --help for detail):
| Command | Purpose | |
board | Kanban view derived by query (no stored view state); --board, --wish, --json | |
idea <text...> | Capture an idea into the roadmap board Idea lane (creates the board if absent) | |
doctor | Diagnostic checks on the genie installation; --fix-global-db is the one repair verb it owns (backs up <GENIE_HOME>/genie.db, drops only the per-repo tables that do not belong in it, leaves everything else in the file byte-for-byte) | |
init | Scaffold per-repo state and retire proven Genie-owned project MCP registrations: the marker-owned .codex/config.toml route, and in .mcp.json only a genie server whose command is a genie binary with args exactly ["mcp"] (backed up first; every other server and key preserved byte-for-byte; a symlinked file is skipped) | |
install | Post-install finisher — authenticated delivery, v4 cleanup (--skip-v4-cleanup), and non-Codex convergence | |
config | Read the resolved global config: config get <dotted.key> prints one schema key's value (budgets.maxEscalationsPerGroup is the per-group repair budget the shipped fix skill reads); --json adds {key, value, source} | |
desktop | Open the Automagik desktop app with Genie preloaded (writes genie to the app home's preload; the AppImage also gets --preload=genie): the build for this machine from automagik-dev/automagik's releases (omni-desktop's signed builds until Automagik has one), checked against SHA256SUMS, cached with omni desktop's in $AUTOMAGIK_HOME/app/<tag> (default ~/.automagik, also where the preload goes) | |
context | Resolve spawn context: wish/group branch + base SHA, or the integration branch (versioned JSON); --plan previews | |
metrics | Opt-in lifecycle capture, OFF until metrics enable writes <GENIE_HOME>/metrics/capture.on: each card event then also appends one machine-local metadata line (runtime session id, genie version, event identity) to <GENIE_HOME>/metrics/events.jsonl; metrics disable removes the switch and keeps the ledger; metrics status [--json]; metrics export [--since 7d] [--json] [--out f] verifies each line against its task_events row on (db, task, event, kind, at) and reports time and tokens per lifecycle transition, reading tokens from the runtime's own session log (Claude Code, Codex, pi/OMP) and leaving usage null — never 0 — when that log is not on the host, plus an offload bucket per interval from mikro's own run ledgers (repo + window; another provider, priced by mikro; null when the repo has no mikro ledger on the host, and null — never a measured 0 or a partial total — when any ledger directory or file in scope cannot be read); --phoenix projects the intervals into the operator's Phoenix (--endpoint/--project [--api-key-env VAR] --save → <GENIE_HOME>/metrics/export.json; no default target, exit 2 and nothing sent when unconfigured), posting only span ids not yet stored and exiting 1 unless every one reads back. USD is the runtime's own price (pi/OMP); `metrics prices update [--from <url | file>]— the only verb that fetches prices (export never does), default LiteLLM'smodel_prices_and_context_window.json— stores an optional table at<GENIE_HOME>/metrics/prices.json(one atomic rename, header{source, fetchedAt, sha256, models}with the source URL stripped of userinfo, query and fragment; exit 2 refused, 1 network or write failure, the old table untouched either way) that the export reads from disk to price calls the runtime left unpriced, by exact model id then the part after the last/, Claude's 1-hour cache writes at cache_creation_input_token_cost_above_1hrand a prompt past an entry'sabove<N>k_tokensthreshold at that tier, a missing model or a used token kind without a rate staying null and a zero-token call never counting as priced; with no table the export is byte-identical, with one each usage gainscostSource (runtime/table/mixed/null) and pricedCalls, summary rows gain calls/pricedCalls/costSource (pricedandcostSourcetext columns) and spansgenie.cost.priced_calls, and the runtime's price always wins; metrics prices status [--json]. Capture sends nothing anywhere, and GENIE_METRICS=off` disables it per process (the env can never enable) |
mikro | The mikro microagent runtime, on PATH — mikro call <agent> hands its tail to scripts/mikro/call.ts as typed (genie's global options excepted); init, fixtures --from-commits, bench and coach are the per-repository growth loop | |
setup | Configure Genie. --orchestration-mode is a hidden one-release retirement stub: standalone exits 0 and orca exits 2, each with a notice that Orca mode is retired and a stale orchestration.mode key in config.json is harmless | |
orca | Retired with the Orca integration: a visible one-release stub that accepts any arguments (genie orca mirror … included), prints a retirement notice on stderr, writes nothing and exits 2. Removed in the next release | |
shortcuts | Manage tmux keyboard shortcuts | |
task | Task state (SQLite, zero-daemon) | |
uninstall | Remove the Genie CLI and the recorded skills-channel install (plus the pre-record genie skill dirs legacy-skills.ts proves are genie's). It no longer removes plugin-era host assets — the marker-owned retirement module was deleted in v6, so ~/.claude/plugins/, ~/.codex/agents/genie-*.toml and the rest are the operator's to remove (UPGRADING.md: Removing plugin-era leftovers by hand) | |
update | Update Genie CLI to the latest GitHub Release | |
wish | Wish-document verbs for any repository — wish lint [--dir <repo>] runs the structural lint over <repo>/.genie/wishes (0 clean / 1 findings), writes nothing, and never trips the workspace gate; wish report [runId] [--append] [--summary] [--variant <name>] [--record <path>] prints a workflow run's tokens and time per stage from its saved record plus the outcome the workflow already returned (review verdict, round verdicts, repairs, gate exit, checks, and each mikro offload the scout and review reported — agent, ok, cost, seconds, facts used, notReported kept, never dropped; a brainstorm's round and WRS) and a partial flag when a stage carries no tokens, --append adds one version-3 row to <GENIE_HOME>/metrics/wish-runs.jsonl (a runId already there is refused), --summary averages that ledger per workflow and variant with SHIP rate, mean repairs, partial count and mean mikro offload cost beside it (pre-outcome rows count in n only; an unpriced offload never counts as $0) (exit 2: unknown runId, missing field, duplicate) | |
help | genie help [command] |
Task subcommands
genie task create --title 'x' [--agent <name> --why <reason>] # Create a task; --agent/--why declare routing (pair required)
genie task list # List tasks (with filters)
genie task checkout <id> --worker w # Atomically claim a ready task for a worker
genie task move <id> --to <lane> # Move a card to a lane defined by its board (--to, never --lane)
genie task heartbeat <id> # Record a liveness heartbeat for a claimed card (takes NO --worker)
genie task status <id> # Task detail, dependencies, stage log (shows the declared assignment)
genie task set-wish <id> --wish w # Attach/re-point wish identity on an existing card (--clear removes)
genie task assign <id> --agent <name> --why <reason> # Declare/reassign which roster agent works a card (--clear removes)
genie task adopt <id> --board <ref> --lane <name> # One-time placement of a laneless (pre-board) card onto a board lane
genie task report <id> --worker <name> -- '<text>' # Engineer's one handoff message per claim (outcome; what/where; validation)
genie task comment <id> --worker <name> -- '<text>' # Orchestrator gate pointer (review verdict, done, blocked); bounded to 4000 bytes, no control chars
genie task delete <id> # Hard-delete a card (refused while other cards depend on it)
genie task done <id> # Orchestrator only: mark reviewed work done + recompute ready set
genie task export # Emit the complete DB state as JSON
genie task export --write # Write .genie/roadmap.json (diverged-sync resolution: keep local board)
genie task import [--replace] # Restore genie.db from .genie/roadmap.json (resolution: take snapshot)
genie task sync # Three-way reconcile genie.db <-> roadmap.json (run by git hooks on pull/commit); when both sides moved but only card timelines differ, events are unioned by identity and republished (`merged`) — the card conversation is global state
genie task forge-sync # genie-rs forge-sync: record opened/merged/closed PRs/MRs via gh/glab, one pass (tail passed through untouched)
genie task v7-status <id> # genie-rs status: one v7 task, its lane and events (GENIE_RS_BIN → <GENIE_HOME>/plugins/genie-rs/bin → target/release)
genie task v7-list # genie-rs list: this repository's v7 tasks with their lanes
genie task start <gh-N|gl-N> # genie-rs start: a task worktree at <repo>.worktrees/<id>-<slug> from the freshly fetched base, branch <type>/<id>-<slug>, plain `git push` locked out; `--title` mints a gn-<seq> id, `--lead` required; prints {task, branch, worktree, lead, base_sha}; 2 for an opened or merged task, 1 for a failed fetch
genie task push # genie-rs push: from a task worktree, HEAD to its recorded branch only (2 for any other branch, dev, main, master or the base)--agent names a roster agent (claude|codex|pi|hermes|prime) and requires --why;
--why alone is rejected too. Assignment is declaration-only (no checkout gate)
and serializes on the lane path only — non-lane/laneless readers do not see it,
by design.
Mikro subcommands
genie mikro call <agent> --prompt '<text>' [--dir repo] [--agents-dir dir] [--agents-ref ref] [--facts auto] [--boundary none|bwrap] [--raw]
genie mikro init [--dir repo] # seed the shipped wish-context + review-prep into a repository
genie mikro fixtures --from-commits <range> --agent <wish-context|review-prep> [--dir repo] [--out path] [--max n] [--force]
genie mikro bench <agent> [--dir repo] [--fixtures path] [--agents-dir dir] [--reps n] [--only a,b] [--tag round=N] [--write-evidence]
genie mikro coach <agent> [--dir repo] [--reps 2] [--null-control] [--band file.json] [--proposal file.json]call declares no options of its own: the tail after the agent name reaches
runCallCli in scripts/mikro/call.ts as typed, so a flag added to the runtime
needs no second edit in src/term-commands/mikro.ts. ONE exception — genie's own
global options (-V/--version, -h/--help, --no-interactive) are consumed
by the program wherever they appear, so mikro call wish-context --prompt -V
prints the version and runs nothing, and a trailing --help prints Commander's
help; shell quoting does not change the argv token, so prefix the value with a
space (--prompt " -V") or use --prompt-file. enablePositionalOptions() sits on
the mikro group, never on the program, because the program-wide form would
change how every other command parses. Exit codes: 0 ok; 1 not ok, which also
covers mikro call with NO agent (Commander's missing-argument path through
genie's global error handler); 2 for the runtime's own usage refusals — an
unregistered agent NAME (the registry in schemas.ts owns the name and the
answer schema), a registered one with no prompt, a bad --boundary. The group is
exempt from the v4 workspace gate.
bench, coach, fixtures and init follow the same registration shape over
runBenchCli / runCoachCli / runFixturesCli / runInitCli, so each verb and its
bun scripts/mikro/<file>.ts spelling are one code path. They add one exit code to the
vocabulary: 2 is a refusal that cost nothing — a fixture set or an agent that is not
on disk, a range that could be an option, an existing fixture file without --force.
scripts/mikro/README.md §"Growing agents in another repository" is the operator
sequence, and genie mikro init prints it, because an installed host has no README.
State File Locations (SQLite + git-tracked docs)
| State | Location | Scope | Format |
| Task / board / wish state | <repo>/.genie/genie.db | Per-repo, shared across worktrees | SQLite (bun:sqlite) |
| Wishes / reviewed designs / INDEX | <repo>/.genie/{wishes,brainstorms/*/DESIGN.md,INDEX.md} | Per-repo, git-tracked (other brainstorm notes are gitignored, machine-local) | Markdown |
| Board snapshot (CANONICAL roadmap) | <repo>/.genie/roadmap.json | Per-repo, git-tracked | JSON — genie.db materializes from it via three-way task sync (git hooks: post-merge/post-rewrite/pre-commit; baseline in gitignored .genie/roadmap-sync; excludes machine-local hire_roster) |
Worktrees share the main repo's .genie/genie.db via git rev-parse --git-common-dir. There is exactly ONE database — the per-repo one — and it uses WAL; the machine-scope ~/.genie/genie.db left with the Omni runner and v6 writes nothing to that path (see the one-genie.db gotcha). Documents live in git; operational state lives in SQLite.
Environment Variables
| Var | Effect |
GENIE_HOME | Relocates ALL global state from ~/.genie (config.json, skills-install.json, state-backups/, templates/, worktrees/) |
GENIE_AGENT_NAME | Worker identity for task claims and stage-log entries (resolveWorkerIdentity; the default for task checkout --worker) |
GENIE_AGENT_ID | Fallback worker identity when GENIE_AGENT_NAME is unset; both floor at cli |
GENIE_METRICS | off disables opt-in lifecycle capture for one process; it can never enable it (only genie metrics enable does) |
GENIE_WORKTREES_DIR | Override the worktrees base the doctor launch-residue check and review snapshots use (default <GENIE_HOME>/worktrees) |
Build
Single-file bundle: bun build src/genie.ts --outdir dist --target bun --minify-syntax --minify-whitespace --external bun inlines all six runtime deps (commander, @inquirer/prompts, zod, @sigstore/bundle, @sigstore/protobuf-specs, @sigstore/verify) into dist/genie.js (~2MB). Only the bun builtin is external. The shebang #!/usr/bin/env bun makes it executable; chmod +x is applied after build.
Testing
- Framework:
bun:test(import from'bun:test') - Pattern: colocated
*.test.tsnext to source - Fixtures: tmpdir with cleanup in afterEach
- Git tests: real git repos in
/tmp, not mocks - Concurrency tests:
Promise.allSettled()pattern - Isolation: set
process.env.GENIE_HOMEto tmpdir to isolate global state (bothgenie.dbfiles resolve under it) - SQLite tests:
sqlite-open.tsuses WAL +busy_timeout, so concurrent-writer tests surface clean claim-conflicts, notSQLITE_BUSYflake
Code Style
- Biome: single quotes, 2-space indent, 120 line width, trailing commas
- Conventional commits (commitlint)
- No
console.login source (biome rule, relaxed in tests)
Cognitive-complexity budget
Biome's noExcessiveCognitiveComplexity is set to maxAllowedComplexity: 25 (warn-level) for src/** and packages/**. Treat 25 as a ceiling for linear workflows, not a target.
- Prefer linear code when a function reads as one workflow (CLI command body, orchestration step, request handler). Helpers extracted purely to reduce a score under 25 usually add indirection without clarity.
- Split when there is a real boundary: a distinct policy decision, an IO concern, a state-machine transition, a presentation/data divide, or reused logic with at least two callers.
- Only suppress with
biome-ignore lint/complexity/noExcessiveCognitiveComplexity:when extraction would obscure a linear flow or break a tested invariant. The comment must explain the reason — never just "complexity". - Score >25 is review-triggering architecture debt, not a hard error. The budget command names every current hotspot; record intentional follow-up work in a dedicated refactor wish rather than opportunistic edits.
- Drift is enforced by
bun run lint:complexity-budgetincheck,check:fast, pre-push, and CI. Raising any budget ceiling requires updatingscripts/complexity-budget.tswith a written justification.
Gotchas
Two delivery paths (issue #2967): this floor is injected into every session and every subagent; the subsystem deep-dives live in path-scoped .claude/rules/*.md that Claude Code loads only when it reads a file matching the rule's paths: glob — a grep-only or plan-without-read session loads none of them, which is exactly why the rules below stay below.
Placement criterion (enforced by src/__tests__/claude-rules-drift.test.ts): a gotcha moves into .claude/rules/<topic>.md only when (a) its subject is derivable from a path glob covering the code it constrains, and (b) an owner decision has scoped it out of the floor — #2967 moved the skills-installer and release families. A gotcha with no derivable glob, a must-never-forget incident rule that must survive a grep-only session, and anything not yet scoped stays in this floor. Non-Claude runtimes get the load-bearing invariants in AGENTS.md (## Skills channel and release gotchas) and read the rules files as plain files.
Rules index — every .claude/rules/*.md file and its paths: globs, one bullet each:
.claude/rules/skills-installer.md(paths:src/lib/skills-installer*.ts,src/lib/skills-agents*.ts,src/lib/legacy-skills*.ts,scripts/legacy-skills-catalog*.ts) — the one skills channel: pinned CLI over the local delivered tree, pre-spawn retirement, legacy leftovers proven by description, collision snapshots, detected agents, the one install record..claude/rules/workflows-channel.md(paths:src/lib/workflows-installer*.ts,.claude/workflows/**,scripts/workflow-front-door-parity*.ts,skills/**) — the.claude/workflowsdelivery: one optional field on the same record, backup-first replacement, invocation by explicit script path..claude/rules/dsh-skills-source.md(paths:plugins/dsh-genie-board/**) —~/.agents/skillsis the DSH body's skill source; never add actx.skillsprovider for genie's library..claude/rules/release-pipeline.md(paths:src/genie-commands/update*.ts,src/lib/delivery-evidence-verify*.ts,.github/workflows/version.yml,.github/workflows/release.yml,.github/workflows/release-publish.yml,.github/workflows/sign-attest.yml) — offline-first release verification, the orphaned dev tag, the gate-free release chain (no approval, admit, guard, security gate or smokes since 2026-11-05), argv-only post-delivery convergence.- The product MCP server, the UI bridge, and BOTH their retirement stubs are gone — v5 kept
genie mcpandgenie ui-bridgeas stubs that wrote a stable diagnostic to stderr and exited 1; v6 removed the verbs themselves, so neither parses any more and Commander answersunknown command. A retired verb that still parses is a promise, and a major is the one release allowed to break it. What stays is the HOST-STATE observation, which was never about the verb:genie initstill retires proven Genie-owned historical routes (in.mcp.jsononly the deadgenie mcpentry — a genie binary with args exactly["mcp"]— backing the file up first and preserving every other server byte-for-byte; plus the marker-owned.codex/config.tomlroute), andgenie doctorstill warns while such an entry is present —src/lib/codex-project-mcp.tsis live code, not a fossil. No plugin ships an MCP declaration or launcher, and the bridge's private transport (mcp-server.ts), tool registry (mcp-tools.ts), and change watcher (bridge-watcher.ts) were deleted earlier. - Genie has one lifecycle authority — its own board — and Orca mode is retired —
orchestration.modeleft the config schema, which is a non-strictz.object, so an old config holding{"orchestration":{"mode":"orca"}}(or any garbage value) still parses whole, keeps every other key, and drops the stale one on the next config write;src/lib/genie-config.test.tsand the stale-mode blocks in thev5-task,v5-boardandcontexttests pin thattask create,task sync,boardandcontextwork under it. Nothing gates a lifecycle verb on configuration any more: no preAction refusal insrc/genie.ts, notask sync/context --plancarve-outs, no guard inopenDbor the roadmap writers.genie setup --orchestration-modesurvives one release as a hidden stub (standaloneexit 0,orcaexit 2, both printing the retirement notice on stderr and writing nothing) and is removed in the release after; do not grow it back into a switch. - The Orca integration is retired, but the payload keeps an EMPTY
plugins/genie/— the plugin, the adapter, the card mirror and their publishing are deleted, and the source tree carries noplugins/genie.scripts/build-binary.shstill recreates${STAGE}/plugins/genieempty beside the.agents/.claude-plugincompat members: every earlier binary's update path requires that physical directory (scanPhysicalTree) and the top-level member set is frozen at the 8INSTALL_PAYLOAD_MEMBERS, so dropping it breaksgenie updateon every older host.genie orcastays one release as a visible stub (any arguments, notice on stderr, exit 2, nothing written), which is why'orca'stays inWORKSPACE_EXEMPTand the top-level count stays sixteen; remove the stub, its exemption and its row together. - One
genie.db, and a machine-scope path that must stay empty — v6 writes exactly one database, the per-repo.genie/genie.db(genie-db.ts, task/board/wish). The machine-scope~/.genie/genie.dbheld the Omni approval queue and inbox and left with the runner;global-db.tsis deleted andresolveGlobalDbPathmoved tosrc/lib/genie-home.tsbeside every otherGENIE_HOMEpath. The per-repo opener still REFUSES that path (GlobalDbPathError) because an older host still carries the file and both stampeduser_version = 1, andgenie doctor --fix-global-dbis the one repair for a host contaminated before the refusal landed. - Codex integration health is native state, not OTel — the old Genie exporter at
127.0.0.1:14318has no relay and is removed bymigrateDeadGenieOtelinsrc/lib/codex-config.ts: an exact-match, backup-first migration on thegenie updatepath. It was deliberately kept out of the plugin-eralegacy-integration-retirement.ts(deleted in v6, its compat window closed) and is unaffected by that module's removal. Preserve unrelated OTel settings anddisable_paste_burst. - Genie is zero-daemon, with no optional daemon either — the Omni runner (
genie omni serve) was the last one and was removed in v6 along with its NATS transport, its approval queue, its config section, itsOMNI_*env vars and its skill. Every command is fork-and-exit; no resident processes. The legacy-retirement paths that clean omni-era host assets (config.json.bak-pre-omniinlegacy-v4.ts, the omni skill's descriptions inlegacy-skills-catalog.ts) stay, because that is how an old host gets cleaned. genie mikro callis the mikro runtime IMPORTED IN PLACE, and it never resolves an agent throughimport.meta.url—src/term-commands/mikro.tsimportsscripts/mikro/call.ts; the files are NOT relocated undersrc/, because under thesrc/**Biome rules they score 29/55/33 against a ratchetedmaxScore: 42and a move is a decomposition project, not a delivery step (scripts/**has the complexity rule off,tscfollows the import, knip reports onlysrc/**). Theimport.meta.mainblock is an exportedrunCallCli(argv): Promise<number>and the guard calls it, so a bundled or compiled binary runs no foreign entry block (src/term-commands/mikro.test.tsproves it against the built bundle) and the module carries no top-levelawait. Agent files resolve--agents-dir→ the INVOKING checkout's.mikro/agents/<agent>/at the trusted ref (git toplevel of cwd, never--dir, which is the tree under review, and never that checkout's working tree either) →<GENIE_HOME>/templates/mikro/agents; the oldimport.meta.url-relative default cannot work inside a compiled binary, where it resolves under/$bunfsand holds no agent at all. The answer names the winner inagentSource(flag,repo@<ref>,shipped) and the why inagentSourceReason. The trusted ref is--agents-refor the base branchorigin/HEADnames, taken as the LOCAL branch whenorigin/<base>is an ancestor of it; the agent is materialized withgit archiveinto a 0700 temp dir that is proven byte-equal togit show <ref>:<path>and disposed on every exit path, and without the flag every file the mikro runtime loads from--diris compared against that ref for EVERY--dir— the invoking checkout included, exemption removed — so an uncommitted edit to one of them refuses the run and names--agents-diras the escape (scripts/mikro/trusted-source.ts). The compared list is ONE constant MIRRORING mikro's ownloadConfig(1.260909.1,~/.mikro/mikro/src/config.ts:814-857) —.mikro/{mikro.yaml,TOOLS.md,SYSTEM.md,CRITERIA.md}and the pre-rename.rlmx/{rlmx.yaml,TOOLS.md,SYSTEM.md,CRITERIA.md}, which is the branch that fires on every repository without a.mikro/mikro.yaml(VALIDATE.mdis excluded because a microagent's own one overrides it unconditionally atmcp/server.ts:585); re-derive it whenever the mikro version floor moves. A compared path that is not a regular file (a directory or a symlink under one of those names) is refused without being read. When the invoking checkout is a git repository that resolves NO trusted ref — noorigin/HEAD, a stale one after a branch rename, or an--agents-refnaming no commit — the configuration fails CLOSED: the agent degrades to the shipped default (genie's own payload), but any compared file present in--dirrefuses the run at zero cost naming all three remedies. Only Decision 8's non-git carve-out still falls back to the directory comparison, and that carve-out must be PROVEN:probeCheckoutis three-valued (toplevel/not-a-repository/unanswered), and only git's own clean "not a git repository" WITH no.gitentry found walking up from the cwd reaches it. A git that could not answer — asafe.directoryrefusal (exit 128 under Docker/CI/sudo/a shared checkout), an unreadable index, a.gitFILE whose gitlink points nowhere, or nogiton PATH — is treated as in-repo with no trusted ref and fails closed; collapsing those into "no checkout" handed a real checkout's own PR configuration the same-directory exemption.benchandcoachdeliberately take that escape and measure the working tree, so they may only be pointed at a tree the operator trusts. The tail after the agent name reaches the runtime AS TYPED, with exactly one exception: genie's own global options (-V/--version,-h/--help,--no-interactive) are consumed by the program wherever they appear, somikro call wish-context --prompt -Vprints the version and runs nothing —enablePositionalOptions()is on themikroGROUP and shields the tail from the group's options, not from the program's, and putting it on the program would change how every other command parses.mikro callwith NO agent exits 1 (Commander's missing-argument path through genie's global error handler); 2 is the runtime's own usage refusal. The trusted root is the invoking checkout for every source that is not the flag — never<GENIE_HOME>/templates— and with the flag it stays two levels above the agents dir, which is the operator's authoring path.MIKRO_AGENTS_DIRis only WRITTEN into the child environment, never read as an input. The run ledger goes to<root>/.mikro/runsonly when the repository tracks a.mikro/directory (or is no git checkout at all); otherwise to<GENIE_HOME>/mikro/runs/<basename>-<sha256(root)[:8]>, so a repository that never opted into mikro grows no untracked files. Amikromissing from PATH is classified by the spawn error's CODE (ENOENT/EACCES), never by its wording — Node saysspawn mikro ENOENT, Bun saysExecutable not found in $PATH— and answers oneunavailable:error withok: falseand exit 1, with no second attempt. The two default agents ship INSIDEtemplates/(scripts/build-binary.shstages.mikro/agents/{wish-context,review-prep}/{agent.yaml,SYSTEM.md}into${STAGE}/templates/mikro/agents/): the tarball's top-level member set is frozen at the 8INSTALL_PAYLOAD_MEMBERS, andtemplates/already converges to<GENIE_HOME>/templateson both install and update, executed by old and new binaries alike. There is deliberately no second tracked copy of those files in the repository.- Another repository grows its own agents with the installed genie alone —
initseeds,fixturesderives truth from COMMITS,bench/coachmeasure the WORKING TREE — the motivating case commits straight to its base branch and has no PRs to derive fixtures from.genie mikro init [--dir <repo>]copies the shippedwish-contextandreview-prepinto<repo>/.mikro/agents/(seedAgents,scripts/mikro/init.ts), adds.mikro/runs/to.gitignoreonce, writes nothing outside<repo>, REFUSES an agent directory that already exists rather than overwriting a specialized prompt, and refuses a symlinked.mikro/agentsrather than following it out of the tree. It then prints the sequence — commit, push, fixtures, bench, coach — plus the host prerequisites genie does not ship (mikro>= 1.260909.1 on PATH, adeepseek-apiprovider withdeepseek-flashin~/.mikro/settings.json,DEEPSEEK_API_KEY), because an installed host has no README; whether the PUSH is needed is PROBED withresolveTrustedReffor that repository rather than asserted, since Decision 8's local-base rule makes a committed-but-unpushed agent the trusted one exactly whenorigin/<base>is an ancestor of the local<base>.mikro-coachships (SHIPPED_AGENTSinscripts/build-binary.shis the two workers PLUS the coach) but is never SEEDED: it is the tool a coaching round runs, so it must resolve from the shipped set on a host whose repository never carried it —scripts/release-docs.test.tspins the shipped list againstSEEDED_AGENTS.genie mikro fixtures --from-commits <range> --agent <wish-context|review-prep>is mechanical and re-runnable by hand: files =git show --name-only(a rename contributes the NEW path), prompt =Intent: <subject>or the parent comparison, id = the first 12 characters of the sha (never--short, whose length followscore.abbrev), merge commits / root commits for review-prep / empty commits / any commit naming a path HEAD no longer carries SKIPPED with the reason — the verifier scores the TREE, not the commit — and the output byte-stable across runs of the same argv so a rebuild is a real diff. Fixtures resolve--fixtures-><dir>/.mikro/fixtures/<agent>.json-><dir>/scripts/mikro/fixtures/<agent>.json(Decision 12; the last is genie's own legacy location and genie has no.mikro/fixtures, so its rounds are unchanged), andbench/coachFAIL UPFRONT at zero cost when the resolved fixture set or<agent>/agent.yamlis not on disk, naminggenie mikro init/genie mikro fixtures --from-commitsinstead of handingrunAgenta path that holds nothing. A coaching round in another repository has noscripts/mikro/bench.tsto spawn:benchCommandresolves that local file first, then a sibling ofBun.main, then the bundled entry, then the compiled binary's ownmikro bench. An EMBEDDEDBun.main(/$bunfs/root/...,B:\~BUN\...on Windows) is recognized by its PATH and never byexistsSync, which answers TRUE for it inside the running binary: the fixture that said otherwise made the round handgenie /$bunfs/root/genie mikro benchto another process, which exits 1 onunknown commandand leaves no bench record to find. A compiled probe binary is the proof, and one test compiles it. An agent with noEVIDENCE.md(a freshly seeded one) is not a crash: the coach prompt says the file does not exist and asks fornull, because a cited path the tree lacks fails the citation gate. The no-flag bench RECORD is still byte-identical to every round before any of this. - Lifecycle metrics are transparent, opt-in and runtime-agnostic — never an agent-called span API — owner rule (2026-10-04): instrumentation adds no LLM action, no agent stage and no daemon, and genie presumes no one's setup (genie and Phoenix are both open source). Capture is a side effect of the ONE live card-event writer (
appendTaskEventInTx): only while<GENIE_HOME>/metrics/capture.onexists (written bygenie metrics enable, never by an env var —GENIE_METRICS=offcan only disable) does it append one line to the machine-local<GENIE_HOME>/metrics/events.jsonl— and only for a per-repo database, an absolute<root>/.genie/genie.dbwherever the repository lives (isCanonicalRepoDb, string checks only): an event of any other database (a test'srace.db, a scratch file) is skipped silently, andgenie metrics exportreads past such lines in an older ledger without ever pruning it, reporting them asignored <n>(text and--json) only when there are some, so a ledger with none exports byte-identically; off, it costs one failedstat(measured 5.9 µs against a 245 ms task verb) andGENIE_HOMEstays byte-identical, whichsrc/term-commands/metrics.test.tspins. The runtime session id lives ONLY there —task_eventsis published into the git-trackedroadmap.json, so it must never carry one — and it is read from what each runtime exports to its tool shells, most specific marker first because markers leak inward through nested shells: OMP setsOMPCODE=1ANDCLAUDECODE=1and exports NO session id (OMP 18.6.1, verified live), so an OMP line is{source: 'pi', id: null}plus itscwdand the export joins OMP's own session file by itssession.cwdheader and the interval window (sessionMatch: 'window', two candidates'ambiguous'→ usage null), and only when NO OMP session has that cwd — the agent ran genie from another checkout of the repository — the tie is evidence alone, whatever a session's header cwd says: among the OMP sessions alive at the event, over the tool calls that were EXECUTING when the event was written (call record → itstoolResultrecord, ±OMP_TOOL_CALL_SLACK_MS;OMP_TOOL_CALL_WINDOW_MSonly when no result is logged) in each session's log and the subagent logs beside it, a session MENTIONS the card when any decoded argument string of any tool names its id as a whole word, and is PROVEN when a bash call with no here-document invokesgenie [--no-interactive] task <write verb> <id>at command position of one simple command with the id as the verb's own operand (invokesCardWrite— a documented recognizer over the verbs' registered options, not a shell parser). Exactly one PROVEN session and no OTHER session with a mention is matched, still labelled'window'; one proven beside another session's mention (even a concurrentgenie task statuspoll), or two proven, is'ambiguous'; none proven is null. Inherent limit: a writer whose log never names the card (./report.sh,genie task done $ID) is invisible, and a long-running proven call claims every event of its card inside its interval. That fallback is reached only by an event whosedbis<root>/.genie/genie.dbwith the capture cwd inside a checkoutrepoCheckoutslists for that root; CodexCODEX_THREAD_ID(equal to the rolloutsession_meta.session_id); piPI_SESSION_ID/PI_SESSION_FILE; Claude CodeCLAUDE_CODE_SESSION_IDlast.resolveAuthorKinduses the same order (OMP and pi →pi),scripts/test-preload.tsstrips every marker so the runtime running the gate never leaks into a test, andhumanis only the fallback, never evidence of a person. The line is written before commit and a rollback reuses the AUTOINCREMENT id, so the join key is(db, task, event, kind, at), never(db, event). Tokens come only from the runtime's own session log, read bygenie metrics exportafter the run; no log on the host means usage null, never 0. Phoenix is a projection: no default endpoint or project anywhere (mikro's spans included —MIKRO_PHOENIX_PROJECTplus an endpoint, both named by the operator), deterministic per-install span ids, post only ids not yet stored, done only on exact-id readback. bun run dead-code(knip) has pre-existing false positives for biome/commitlint/husky devDeps — not regressions.genie doctorprints ONEmode driftline, never one per drifted entry —checkWorktreeModes(src/genie-commands/doctor-modes.ts) returns a single aggregated result naming at most five entries in the fixed orderrefused → mixed → wider → stricterplus a+<n> moreremainder, and the full list rides--jsonunderchecks[].modeDrift.entries. The per-entrydescribeEntryform printed ~10k lines over an uncapped worktree scan on the dogfood host, which is how a realrefusedrow — a probe error or a planted symlink, the one disposition--fixwill never touch — became indistinguishable from noise. The ordering is the point:refusedandmixedare unfixable and escalate the headline to warn,wideris what--fixtightens, andstricteralone stays an informational pass, so naming five in that order always surfaces the rows an operator must decide about first — EXCEPT the one stricter sub-case that carries a manual remedy, a 755-indexed file that lost its+xbit, which is named FIRST inside thestricterbucket and rides--jsonwith its ownsuggestion(chmod 755 <path>); restoring an executable bit is a WIDENING, so it is the operator's decision and--fixwill never take it.budgets.maxEscalationsPerGroupis the repair budget, read from the config and never restated in prose — the fix skill resolves it withgenie config get budgets.maxEscalationsPerGrouprather than naming the constant, so the skill and<GENIE_HOME>/config.jsoncannot drift. Everybudgets.*key is a conservative default with a schema.max()ceiling (escalations 5, Fable calls 10, councils per brainstorm 3): configuration may only TIGHTEN a gate, and a hand-edited value past the ceiling fails the whole parse, soloadGenieConfigreturns defaults andgenie config getreportssource: default— never the out-of-range number.genie doctorechoes the resolved value as one read-onlybudgets: maxEscalationsPerGroup=<n> (<default|file>)line and never writes a config file. Beside it,genie doctorprints one read-only, informational, always-passcontext budget:line showing the resolved Claude Code auto-compact window — theCLAUDE_CODE_AUTO_COMPACT_WINDOWenv var first, then theautoCompactWindowkey ofsettings.jsonin the Claude config dir, else the Claude Code default with 400k named as the recommendation — with the value and its source underchecks[].contextBudgetin--json.- Wish state is persisted by the orchestrator, never the reviewer — reviewer verdicts are SHIP/FIX-FIRST/BLOCKED evidence; durable WISH statuses are
DRAFT,FIX-FIRST,APPROVED,IN_PROGRESS,BLOCKED,SHIPPED, andSUPERSEDED(a terminal record of work that shipped, or was approved, and was later removed or replaced — it names what replaced it). SessionStart,genie, and resume routing consume that vocabulary. A chat verdict does not advance state until the invoking orchestrator appends review evidence and updates WISH.md. tasks.wishis a lifecycle slug, valid from brainstorm-dir creation — the roadmap board is the one tracker —tasks.wishis no longer "the WISH.md slug post-pour"; it is the single stable slug a card carries from.genie/brainstorms/<slug>/creation onward, threading Idea → Brainstorm → Wish → Work → Review → Done (a card can hold a slug while still in an early lane; the slug is identity, not proof a wish exists). Hand-written.genie/INDEX.mdprose stays authored by humans, but thejar: index-lane driftdoctor check lint-checks it against placement truth: for each INDEX entry it takes the firstbrainstorms/<slug>/orwishes/<slug>/link, joins the roadmap cardWHERE tasks.wish = slug, and verifies the card's lane against the section (Raw→Idea, Simmering→Brainstorm, Ready→Brainstorm/Wish, Poured→Wish/Work/Review/Done). It is warning-level and never flips doctorok:false; linkless/cardless/laneless entries reportunlinked(neverdrift), a link whose target is missing on disk — or resolves outside.genie/, which is rejected without a stat so../traversal is never a path-existence oracle — reportsbroken(decided before the lane comparison, so it outranksdrift;#anchorsuffixes are stripped and a barewishes/<slug>/link resolves against the directory), and the per-entry states ride--jsonunderchecks[].indexLane.entries. The check line warns whendrift > 0orbroken > 0; human output names everybrokenentry and at most fiveunlinkedones, then counts the remainder.evaluateIndexLaneDriftstays pure — the caller injects bothlaneForSlugand the target-existence resolver.
PR Review Rules
When reviewing comments from automated bots (CodeRabbit, Gemini, Codex):
- Read the actual code before accepting any finding — bots often misread control flow
- Check if behavior is pre-existing — extracted/moved code inherits existing tradeoffs, not new bugs
- Trace fallback chains — bots flag the first code path without checking if later candidates handle the edge case
- Distinguish theoretical from practical — "could happen if X" is not a bug if X never occurs in real usage
- Never blindly accept severity ratings — a bot labeling something CRITICAL doesn't make it critical. Verify actual impact
- Check idempotency — many "collision" or "race" concerns are mitigated by idempotent operations the bot didn't trace
Engineering Discipline
- Type boundaries first — input shapes, output shapes, error variants. Implementation follows naturally.
- APIs before implementations — the surface is the contract, the code is the detail.
- Plugin architecture is not optional; every capability is a pluggable unit with a defined interface.
- Test alongside implementation, not after — tests are a spec, not a safety net.
- If something is hard to test, the abstraction is wrong.
- DX is first-class — the framework must be obvious to a new contributor in under 30 minutes.
- Keep PRs focused on a single abstraction change; mixed concerns belong in separate branches.
- Deprecate loudly, remove decisively — never let dead code haunt the codebase.
- Elegance means fewer moving parts, not fewer lines.
QA Discipline
- Assume code is broken until a failing test proves it can be fixed, and a passing test proves it stays fixed.
- Edge cases are the real interface — test the boundaries of every command, flag, and plugin contract.
- CLI correctness includes exit codes, stderr output, and error message format — not just happy-path stdout.
- Plugin contracts are sacred — any deviation between declaration and consumption is a defect, not a difference.
- Watch it fail for the right reason before marking it pass.
- Build a failure inventory first: what are the ten most likely ways this could break?
- Regression log: if something broke once, a test permanently owns that scenario.
- Test CLI commands as a user would invoke them, not just as unit tests exercise them.
- Report blockers immediately — a workaround is a hidden defect.
Release Discipline
- Shipping cadence is a promise — missed releases erode trust faster than bugs do.
- DX friction is a product bug, not a support ticket. Top-5 DX issues tracked at all times.
- Scope freeze 3 days before release — no scope additions in the final window.
- Breaking changes require a deprecation story before landing.
- Every contributor PR makes an advocate — celebrate contributions specifically, not generically.
- Triage incoming issues within 24 hours: label, assign, prioritize.
- Sprint summary is one page: shipped, blocked, next.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

