A plugin for repository-centric agentic delivery, run from your own terminal on your own credentials — authored for Claude Code, and read on Codex and Cursor from generated mirrors of the same files.
One lead persona — Kurapika, who names which of six declared work-modes he is holding before he acts —
plus a small roster of focused independents, and thirty-eight skills that take a backlog, a pull request or
a release from where it is to the human gate where a person decides. Every deterministic step that has a
verb is a verb from the Nen CLI: Nen detects, computes, formats and
verifies; the skill supplies only the judgment a binary cannot. Where no verb exists yet, the residue is
named per skill in docs/ab/ rather than quietly improvised.
No GitHub App. No bot identity. Nothing here merges main, publishes a release, or casts a review vote.
The current release is whatever
.claude-plugin/plugin.json'sversionsays, and this line names no number on purpose — a version repeated in prose is a version that drifts, and this one sat four bumps behind before anybody read it. The releases named below are history, and each stays true of the release it names. Hatsu is the local plane of the Akatsuki system, and it succeeds the local plane of a predecessor system — the frozen reference implementation — which it also serves live today: the original seventeen skills were ported name-for-name and proven against that system's real backlog beforev0.1.0was cut. The evidence is indocs/ab/, one file per skill — dated records of the port against nen0.1.0.v0.3.0reconciled every skill and persona with nenv0.3.0, and every repin since lives innen/contract.json, which is the only place the pinned nen is written down.v0.4.0adds the way of working: ten skills that make the local loop itself explicit, two configuration files that hold every parameter of it (nen/contract.json→projectandnen/workflow.json), and two harness hooks — a stop bell and a refusal to commit on the trunk.v0.5.0adds the PR side: eight more skills —mukai,murasaki,hanten,gyo,kotoamatsukami,shibari,enandjujutsu— the rename ofdrivetosharingan, and the three agent definitions that side needs: Feitan, Chrollo and Illumi.v0.6.0closes the release side:susanoo(archive and packaging),kagutsuchi(non-production upload, per target) andmugetsu(publication, per target, G3) — so four of the five phases only you may call have a skill; the fifth is the merge, which has none because no agent performs it.v0.7.0adds no skill and adds two surfaces: the same thirty-eight skills and eight personas, generated into Codex and Cursor layouts undersurfaces/— see Surfaces. Attribution ruling, 2026-09-12: prospective commits carry the truthful canonicalHatsu-AgentorAkatsuki-Agentpersona/plane trailer. They never carry model, surface, runtime, or session attribution. The final## Agent attributionPR-body section records actual participants with canonical persona, contribution, and evidence;docs/AGENT-ATTRIBUTION.mdis the shape.docs/WORKFLOW.mdis the authority.
The phase ownership ruling separates focused checkpoint tests, aka regression and mukai coverage. All phases use the discovery protocol to reconcile and capture concrete gaps under standing filing authority, without duplicate issues or unrelated implementation. Device-record migration is tracked in LAUNCH-MIGRATION.md; Hatsu #49 must be addressed before any new release tag is considered.
Three of these are the same on every surface, and then each surface brings its own host program.
nen >= 0.7 |
a hard dependency — see The Nen contract. A later 0.x satisfies it unless that release declared a breaking change, which nen decides and says: the binary ships a compatibility floor and nen shu tools prints it. You do not need to install it yourself; the warm-up does it, checksum-verified. One exception, on Codex — the box below the surface table. |
git + gh, authenticated |
the skills read and write GitHub as you. |
a nen/contract.json in the repository you point Hatsu at |
the only thing Hatsu asks of your project. It declares what your build, test, lint, archive and deploy commands are, so nothing here is bound to a language, a framework, a build system or a product. A repository that declares none gets the git half of every skill and its own documented commands, said plainly rather than guessed at. |
On the installed plugin path, nothing here needs jq, yq or Python: one binary, plus git and gh.
(The repository's own CI is a separate matter — scripts/plugin_bump_check.sh uses jq, but nothing an
installed copy runs does.)
| Claude Code | Codex | Cursor | |
|---|---|---|---|
| the host | Claude Code. The claude plugin subcommands below are its own |
the codex CLI. No minimum is established; the build every record here was made on is codex-cli 0.149.0 (codex --version) |
cursor-agent — 2026.01.* or newer, and this one is a real floor. See the box below |
| signing in | the harness's own | codex login. codex login status answers Logged in using ChatGPT |
cursor-agent login. cursor-agent status answers ✓ Logged in as <you> |
| where it reads the skills from | the installed plugin, in place — nothing is written into your repository | <repo>/.agents/skills/<name>/, placed there by the warm-up |
<repo>/.cursor/skills/<name>/, the same |
| and the personas | claude/agents/, in place |
<repo>/AGENTS.override.md — one untracked file, as prose |
<repo>/.cursor/agents/<persona>.md — one subagent file each |
| how it got there | claude plugin install |
a checkout on the host, a one-time bootstrap, then the warm-up — On Codex | the same — On Cursor |
| the caveat that bites first | none | AGENTS.override.md replaces your AGENTS.md in the instruction envelope rather than joining it, so the warm-up copies yours into it verbatim first and never writes the tracked file |
the skill name space is flat and global — shared with Cursor's own built-ins and with every other plugin on the host |
A
cursor-agentthat predates skills support takes your prompt, runs your commands and exits0with not one of the thirty-nine loaded. With the mirror installed exactly as the warm-up mandates,2025.09.18-39624efanswered a discovery probe with the whole replyNO SKILLS VISIBLE, seventeen bytes — and the control that settles it is that the same build cannot see a plaincp -Rcopy either: it has no skills mechanism at all, and reached its answer by grepping the working tree.The floor is a month, not a build id. Cursor's CLI changelog dates "Skills, rules, and commands in the CLI" to its January 2026 entry and groups by month, while
cursor-agent -vprintsYYYY.MM.DD-<sha>— so there is no exact version string to pin and the comparison is the date part. The build every Cursor fact in this README was measured on is2026.09.08-6caf4ff. The warm-up printscursor-agent -vbeside the install count and refuses to claim the surface below the minimum, because an install that succeeded onto a build that cannot read it is the exact shape of an unperformed step reported as a passing one.
nenauto-installs itself on Claude Code and on Cursor. Put it on the host yourself before a sandboxed Codex run. The bootstrap resolves and verifies the binary into${XDG_CACHE_HOME:-$HOME/.cache}/nen(nen/contract.json→dependency.bootstrap.flags), which is outside the directorycodex exec -s workspace-writemakes writable — so a session under that sandbox has nowhere to install to. A Cursor session did run the bootstrap itself, checksum-verified, and put the result on its own sessionPATH.
claude plugin marketplace add zheref/hatsu
claude plugin install hatsu@hatsuThen, in Claude Code:
/kurapika
To install from a local checkout instead — for development, or to run a branch:
git clone https://github.com/zheref/hatsu.git
claude plugin marketplace add ./hatsu
claude plugin install hatsu@hatsuConfirm what landed:
claude plugin list # hatsu@hatsu — Version: <the version you just installed>
claude plugin details hatsu@hatsu # the full component inventoryOpen a new Claude Code session and run /hatsu:hatsu-warmup; it is already registered by the plugin loader.
/kurapika is the normal first request and runs that same warm-up before beginning the loop. This is the
fresh-install path Claude Code has that Codex and Cursor do not: discovery happens before the first
invocation, without writing into the target repository.
Neither surface has a plugin loader, so the first install is a human act and it is a git clone. There
is nothing on either surface that would fetch Hatsu; what the warm-up automates is the refresh, every
session, of a root that already exists. That is a boundary rather than a step somebody forgot to write, and
a warm-up that cannot find the root reports NOT INSTALLED and stops.
One of these two, not both — the second refuses on a destination that already exists:
# the tip
git clone https://github.com/zheref/hatsu.git ~/.hatsu
# — OR — pinned to a release (the tags are at https://github.com/zheref/hatsu/tags)
git clone --branch <tag> --depth 1 https://github.com/zheref/hatsu.git ~/.hatsuThen, once:
export HATSU_PLUGIN_ROOT="$HOME/.hatsu" # put this in your shell profile$HATSU_PLUGIN_ROOT is the form that works on all three surfaces, and it is the one to prefer. It
locates the checkout for Hatsu; it does not register a skill with Codex or Cursor. On those two
surfaces, run the one-time, non-skill bootstrap below before you try to invoke hatsu-warmup. The
warm-up then resolves its root from that variable first, then from a path handed to the invocation
($hatsu-warmup <path>, /hatsu-warmup <path>), and only then from $CLAUDE_PLUGIN_ROOT.
$CLAUDE_PLUGIN_ROOTis Claude Code's variable and it is not inert on the other two. On the host these records were made on it is exported from~/.zshrcand points at a different plugin, which every Codex and Cursor session on that host inherits. So a candidate root is checked for what it is — the top-level"name"of its own.claude-plugin/plugin.json, compared whole, readinghatsu, plus one second, independent fact about the same directory: thatclaude/skills/is there. (Those two, and no more — the check does not parse the manifest'sskillsvalue.) Top-level is structural, not "the first one": the manifest is read as the one shape Claude Code's tooling writes —JSON.stringify(x, null, 2): line one{, every inner line indented at least two spaces, the last line}— and a key at two spaces is top-level unless a two-space line opened a nested block above it. So anamenested anywhere never matches whatever the key order, two top-level matches fail, and any other shape is refused rather than parsed — minified, tab- or four-space-indented, keys at column zero, a nested block laid out at two spaces: not what the tooling writes, and refusal is the safe direction. The captured path must also be the same directory as the candidate, must contain no newline (the root is handed to later shells as one quoted line), andcdruns withCDPATHcleared so nothing but the path is captured. It is never merely checked for containing asurfaces/directory. Shape is not identity: a wrong root that happened to have the right shape would install somebody else's skills into your repository with no error anywhere. Every rejected candidate is named by path in the report, even when a later one succeeded, because a stale variable in a shell profile is a thing to fix and this is where it becomes visible.
From the repository you want to work in, seed the one skill Codex must discover first:
"$HATSU_PLUGIN_ROOT/scripts/surface_bootstrap.sh" --surface codex --target . --bootstrapThis is intentionally a non-skill command: it copies only
.agents/skills/hatsu-warmup/, verifies that an existing destination is Hatsu-owned before replacing it,
and records the local installation through Git's info/exclude. It never touches .gitignore or a tracked
destination. Now restart or open Codex in that repository and invoke the discovered skill:
$hatsu-warmup
That warm-up refreshes the complete surface every session. What it places in your repository:
<repo>/.agents/skills/<name>/ |
one cp -R per mirrored skill directory — 39, the thirty-eight plus hatsu-warmup itself — from $HATSU_PLUGIN_ROOT/surfaces/codex/, re-copied every session so a target is at most one warm-up behind the plugin |
<repo>/AGENTS.override.md |
untracked, written whole: your own AGENTS.md verbatim first, then the personas between a BEGIN/END hatsu personas marker pair |
Copies, not symlinks, and the reason is what Codex advertises. Codex lists a skill under its
frontmatter name, namespaced by the plugin manifest above the directory the path resolves to — so a
symlink into this checkout is listed as hatsu:aka, while a cp -R of the same directory is listed as the
bare ren, which is what the mirrors' own $<name> spelling needs. A symlink drags a second trap with it:
the mirrored bodies carry relative links, and through a symlink ../../../nen/workflow.json resolves into
this plugin's policy file rather than your repository's. A link that resolves into the plugin is more
dangerous than one that dangles — a dangling link is an agent reporting it could not read something; a
resolving one is an agent answering confidently from the wrong file.
What the warm-up refuses, and these are hard limits rather than preferences:
- A destination it did not create is left untouched, and named in the report. A previous Hatsu install
is replaced; a tracked path is always somebody else's, whatever it looks like. Thirty-nine ordinary
words are being claimed at once —
build,file,en,ao,ren— so a collision is not a rare case, and the warm-up would rather install thirty-seven and say so than overwrite one file it did not write. - It never writes your
.gitignore. Everything it places is excluded through the repository's owninfo/exclude, found withgit rev-parse --git-path info/exclude— which in a linked worktree resolves to the main repository's file, shared by every checkout of it, and is stated in the report by path for exactly that reason..gitignoreis a tracked file in somebody else's repository: writing it lands in their diff, their review and their history, and imposes this plugin's layout on every other contributor. - It never writes a tracked
AGENTS.md— not appended to, not touched. The personas go to the override file, which is the warm-up's own; a human who wants them in their history commits them themselves.
First-run check, from the Hatsu checkout — it writes nothing and needs no credential:
scripts/surface_mirror_check.shTo update, pull the checkout (or check out a newer tag) and run $hatsu-warmup again: the unconditional
re-copy is what refreshes the target. A copy is not self-healing, and the one failure mode it has is a
session that never warmed up serving last month's wording with no error anywhere. If you changed a skill,
regenerate the mirrors in the same commit — Surfaces has the two commands and the check.
Then read Using Hatsu on Codex.
Check the version first — below 2026.01 the install succeeds and the surface sees nothing:
cursor-agent -v # 2026.09.08-6caf4ff on the host these records were made onThen, from the repository you want to work in, seed the one skill Cursor must discover first:
"$HATSU_PLUGIN_ROOT/scripts/surface_bootstrap.sh" --surface cursor --target . --bootstrapIt links only .cursor/skills/hatsu-warmup/, preserving the same collision, tracked-file and
info/exclude safeguards as the full warm-up. Restart or open Cursor in that repository, then invoke:
/hatsu-warmup
The warm-up refreshes the complete Cursor surface every session:
<repo>/.cursor/skills/<name>/ |
one symlink per mirrored skill directory — 39 — pointing at $HATSU_PLUGIN_ROOT/surfaces/cursor/<name> |
<repo>/.cursor/agents/<persona>.md |
one markdown subagent file each — 8 — symlinked from $HATSU_PLUGIN_ROOT/surfaces/cursor/agents/ |
Symlinks are honest here, and that is measured rather than assumed. Four controlled probes on
2026.09.08-6caf4ff found a skill through a symlink inside the workspace and through one pointing
outside it, and listed a symlink into this plugin checkout under its bare name — so Codex's
namespacing behaviour does not reproduce on Cursor and the mirrors' /<name> spelling works as
printed.
The first two refusals apply here too — a destination the warm-up did not make is left alone and named,
and your .gitignore is never written (info/exclude, again). There is no third, because there is no
AGENTS.md on this surface; what takes its place is that .cursor/agents/ is a directory a Cursor user
is expected to keep their own subagents in, which makes it the likeliest collision of all. Before it
installs anything the warm-up lists every name already standing under .cursor/skills/ and reports
what it found by name — and says plainly what that listing cannot see: a same-named skill from another
plugin elsewhere on the host may shadow the mirror, and is invisible from inside your repository.
First-run check, from the Hatsu checkout:
scripts/surface_mirror_check.shTo update, pull the checkout and run /hatsu-warmup again. The links point into the checkout, so
pulling it is most of the update; re-running the warm-up is what repairs a link the target lost and what
re-prints the version and the collision list.
Then read Using Hatsu on Cursor.
The loop is the same on every surface: open the session once, hand it the request, let ren run the
turn, then call the two phases that are yours. The request goes on the invocation line — ren refuses
a turn with no request and never infers one from what the session was last doing — and every later request
is simply the next message in the same thread: in a repository whose nen/workflow.json describes this loop,
an ordinary request is a ren turn.
What the first turn does before any of your change is authored: breath fetches, fast-forwards the trunk,
cuts {model}/{persona}/{descriptor} from its fresh tip and proves the declared checks on it. Then
rasengan authors the change and focused tests, kokusen runs focused tests plus iteration checks and commits locally, amaterasu builds, installs and launches the declared
target, rikugan publishes the turn report and jutaisho rings the bell. Nothing is pushed until you say so.
/kurapika Add a "Clear all" action to the notifications list, with a confirmation sheet
/kurapika is the session opener: it runs the Nen dependency warm-up (the contract, the binary, the floor)
and then takes the request as the first ren turn, with Kurapika as the persona for the whole thread. From
there:
<the next request, as a plain message> # another ren turn on the same branch
/hatsu:aka # lint, squash, catch up, final-tree regression, push — when YOU decide the branch goes up
/hatsu:mukai # catch-up, review, matching aka regression evidence, coverage bar, evidence, one PR, then en's landing watch
/hatsu:ren <request> invokes the loop directly and is the right call in a session that is already warmed
up; on a cold session prefer /kurapika, which runs the warm-up first. The merge is yours, in GitHub; en
reports when the PR is Ready and keeps watching until it lands.
$hatsu-warmup # once per session: refreshes the mirrors, verifies nen
$ren Add a "Clear all" action to the notifications list, with a confirmation sheet
$aka
$mukai
Kurapika is the persona here through the untracked AGENTS.override.md the warm-up writes; there is no
separate summon. The very first time on a checkout, the mirrors are placed as On Codex
describes. Headless, the request is the prompt — codex exec -C <repo> -s workspace-write --add-dir … -m "$sol" '$ren <request>' —
and the full, verified form is under Using Hatsu on Codex → Headless.
/hatsu-warmup # once per session: links .cursor/skills and .cursor/agents (the links persist), verifies nen and cursor-agent's version
/ren Add a "Clear all" action to the notifications list, with a confirmation sheet
/aka
/mukai
Personas are .cursor/agents/<persona>.md, and /ren runs as Kurapika. Headless:
cursor-agent -p --output-format text --model "$grok" -f '/ren <request>' — the full form, with the model
id resolved rather than remembered, is under Using Hatsu on Cursor → Headless.
Hatsu's skills do not improvise shell. Every deterministic step is a nen verb, and the dependency on that
binary is hard, version-ranged, and fail-closed with auto-install.
nen/contract.json is the single source of truth. Every version, ref, URL and
command echoed anywhere else — this README included — is a convenience copy of a value that lives there.
Where a copy disagrees with the contract, the contract wins and the copy is the bug. The
hatsu-warmup skill executes it at the start of every session, before any
other Nen-owned work.
The file sits where nen itself looks for a repository's dependency declaration — nen/contract.json, in
nen's own shape (dependency.version_probe as an argv array, dependency.bootstrap nested inside
dependency) — so that nen validates it rather than merely tolerating it:
nen schema check --repo <this checkout>
# ok nen/contract.json dependency (nen >= 0.10, pinned v0.10.0), project (2 lanes: plugin, plugin-bump-guard; 11 verbs; 1 toolchain entry)
# ok nen/workflow.json coverage 80/85/90 (touched), branch '{model}/{persona}/{descriptor}' off 'main', checks: lint(Four taxonomy rows print above those two — three FAIL (nen/labels.json, nen/repos.json,
nen/colors.yml) and one warn (nen/gates.json) — and the command exits 1: Hatsu ships no taxonomy of
its own, schema check requires the three of a repository that carries one, and only warns on the fourth,
which pr ready can take by --gates instead. The two ok rows are the ones this repository owns. There
is no schemas/ fallback at this pin — it was removed in nen v0.5.0, so nen/ is the only directory any
taxonomy-reading verb reads from, and a repository carrying a file only under schemas/ is refused with the
migration named.) Every
Hatsu-authored key beside nen's own — the zero-major caveat, the two install paths, the halt template, the
no-jq rule — is preserved verbatim by nen's loader and read by nothing in nen. There is deliberately no
second copy: it was nen.contract.json at the root through v0.2.0.
Since v0.4.0 the same file also carries a project block — Hatsu's own lane, written by hand because
nen shu detect --repo . answers no lane detected for a bash-and-markdown repository. lint is
claude plugin validate . --strict; every other verb of the family is an explicit unsupported seat
stating in this repository's words why it does not exist, because a seat is exit 4, a stated fact, while an
omission is exit 2, a broken declaration. Its policy half, nen/workflow.json, is
validated by nen at the pinned build — the second ok row above, with a malformed key reported as a
FAIL by pointer — and docs/WORKFLOW.md documents both.
Current pin, echoed for convenience: nen >= 0.10, with the bootstrap installing v0.10.0.
Those are two values and they move independently. Hatsu adopts approval_policy: review-round-only,
introduced in v0.10.0; this feature requirement raises its minimum even though Nen's compatibility
floor remains 0.7.
The range is nen's answer, not this README's arithmetic — and not the warm-up's either. The binary
ships COMPATIBLE_MINOR_FLOOR (src/version.ts), the lowest minimum pin that build satisfies, and
nen shu tools applies it, prints it as compat floor: on every run, and carries it in --json as
compatibleMinorFloor. Hatsu reads that verdict off the nen row and never recomputes it.
The rule, in the maintainer's words on 2026-09-10: exact minor is fine, unless there is a breaking
change. A nen release whose CHANGELOG ### Breaking / consumer notes section carries a real bullet
sets the floor to its own minor; one that carries none leaves it where it stands and goes on accepting the
pins already written. So a pin of 0.A is satisfied by a build 0.B.z with floor 0.F when A ≤ B,
A ≥ F, and B is at or below that build's own minor:
| the pin | the build | floor | verdict |
|---|---|---|---|
0.7 |
0.7.0 |
0.7 |
ok — >=0.7.0 <0.8.0; a pin's own minor always satisfies it |
0.7 |
0.8.0 |
0.7 |
ok — >=0.7.0 <0.9.0. v0.8.0 declared no breaking notes, so no repin |
0.6 |
0.7.0 |
0.7 |
below the floor — no build of the 0.7 line satisfies it; repin 0.6 → 0.7 |
0.9 |
0.8.0 |
0.7 |
the binary is older than the pin — install the pinned ref |
1.4 |
any | — | >=1.4.0 <2.0.0; above major zero the floor is not consulted at all |
It is fail-closed at both ends. An older binary never certifies a newer line it cannot know about, and a pin below the floor is refused by name however new the host's binary is. The widening only ever adds versions, which is why nen's own notes record that shipping it was not itself a breaking change.
Why the floor is 0.7, and what would move it again. At major version zero SemVer 2.0.0 clause 4 makes
the minor the breaking-change vehicle, and nen's last three minors are the proof: v0.5.0 removed
something a consumer could rely on (the schemas/ fallback), the first release since v0.1.0 to do so;
v0.6.0 changed three behaviours in place; and v0.7.0 changed four more, none of them announced by a
new flag — nen stage triage gained the local-config and large detectors, so a tree that answered
exit 0 answers exit 1 on the same bytes; every relative own-path flag (--body-file, --out,
--input, --efforts, --original, --table, and canon mirror's five) resolves against --repo's root
instead of the process's directory; a missing or malformed --target exits 2 rather than 1 across
sixteen verbs, and so does an unreadable caller-named input on split verify, changelog and
canon mirror check; and nen pr ready reads nen/gates.json's dependabot_carve_out, so an
unchanged file can turn a not-ready into a ready. That explains the original 0.7 minimum. A minimum also moves when Hatsu adopts a new capability:
device.extract requires 0.9; the explicit review-round-only approval policy raises the current
minimum to 0.10. Breaking changes remain the other reason to raise it.
pinned_ref may move on
its own to a newer release inside the range, which is exactly what v0.7.0 → v0.8.0 was. The familiar
"compatible within a major" reading applies from 1.0 onward, and the contract is bumped to say so
when nen gets there.
Two cases, two paths, chosen by the probe (nen --version) and never by preference:
| Probe result | What the warm-up does |
|---|---|
| nen absent | Fetches nen's own published bootstrap/nen.sh at the pinned ref and runs it. This is the sole chicken-and-egg carve-out for shell anywhere on a Hatsu path: nen bootstrap is a nen subcommand, so it presupposes the binary that is missing. |
| nen present, out of range | Re-pins through the verb: nen bootstrap --ref <pinned> --source zheref/nen --script <fetched script>. --script is required — the verb runs the bootstrap rather than reimplementing it, so it needs the script on disk. |
Either way:
- The bootstrap is checksum-verified. It fetches nen's published
SHA256SUMSand refuses bytes that do not verify. A checksum failure is never retried — retrying one is how a fail-closed guard becomes a fail-open one by attrition. - The script is fetched to a file and then run — never
curl … | bash. It reads${BASH_SOURCE[0]}underset -u, so a pipe kills it before it parses its own arguments. - The bootstrap is never vendored here. Hatsu fetches nen's own script at the pinned ref, every time, so there is no second, unreviewed copy to drift from the manifest it verifies against.
An absent or out-of-range nen is not a halt; it is an auto-install. The session halts only when the
bootstrap itself fails, and then it prints the exact command for you to run yourself, and stops.
There is no LLM-improvised fallback for a Nen-owned operation, ever. If nen is unavailable and the
bootstrap failed, the operation does not happen — not with raw gh, not with a shell equivalent, not from
memory. Reporting that is the correct outcome; substituting a hand-rolled equivalent is not.
docs/ROSTER.md is the authority on who exists and what standing they have; the agent
definitions in claude/agents/ are the authority on what each one does. Commit messages
carry the truthful canonical Hatsu-Agent or Akatsuki-Agent trailer, never model, surface, runtime, or
session attribution. The final ## Agent attribution PR-body section names
each actual participant's canonical persona, contribution, and evidence. Author and committer metadata
remain the configured identity of the actor.
Defined at claude/agents/kurapika.md, summoned with /kurapika
(claude/commands/kurapika.md). He trained all six Nen types, and his
canonical trick is not power but conditions: a binding accepted in advance, stated out loud, paid in
full. The six types are his
work-modes, and naming the mode in play is not decoration — it tells you which authority he believes he
is holding, so you can catch him holding the wrong one before he acts on it.
| Mode | Lane | Where its work stops |
|---|---|---|
| Enhancer | Product code — edit, build and test locally, open the PR. Never merges, never votes, never self-reviews. | the merge gate — yours |
| Conjurer | Canon & governance authoring — constitutions, handbooks, schemas, agent definitions, taxonomies, thresholds. Conjured contracts with conditions: a clause states what it binds, what it costs, when it lapses, and what happens when it is broken. | the policy gate — yours |
| Transmuter | Machinery — Nen verbs and their tests, scaffolding, hooks, workflows, generators, plugin manifests, contract files. The standing transmutation is improvised shell → deterministic verb. | the policy gate — yours |
| Manipulator | GitHub-side ops — drives, wakes, labels, retargets, cascades, thread stewardship. | drives to a gate, crosses none |
| Emitter | Release & fan-out — the tag cut, changelog collation, preflight, the repin fan-out. | prepares a release; never publishes one |
| Specialist | Product intake — a raw thought elicited into a decision-complete brief, filed only on explicit confirmation. | epic approval — yours |
Seven, beside Kurapika: six ratified, and one — Illumi — provisioned rather than ratified and marked as
such. Each has a discipline he delegates to rather than absorbing. The last three landed at v0.5.0, with
the PR side that needs them.
| Agent | Discipline | Status |
|---|---|---|
| Gon | Mission-scoped trusted delegate. He always asks: what is the mission · which gates may I cross · under what conditions · when does the grant expire · where is it logged. | Ratified as an agent. His delegation grammar is a DRAFT — so he crosses no gate. See below. |
| Hisoka | UI/UX review and quality measurement, before a PR is ever posted — the visual-evidence set, and the cheap objective things measured on your own machine: contrast ratios, target sizes, type scale, reduced-motion, artifact delta. | Ratified |
| Phinks | Adversarial pre-release QA, under the proven-finding discipline — every hypothesis class gets a recorded disposition, and nothing is filed that is not proven: a committed test failing 3/3 against the candidate, or a measured number with its method block. From v0.5.0 also a pre-PR trigger, on a release-adjacent change set — a new way to be called, never a new authority, and the 3/3 floor still applies. |
Ratified |
| Uvogin | Performance testing — the fixed seven metrics on every pre-release run, with pinned tooling, regression thresholds, in-repo baselines, and a five-field method block per number. | Ratified |
| Feitan | Security, and security only — auth flows, secrets and credential handling, network and storage boundaries, data minimisation, the supply chain. He cites the inherited SEC-{n} rules by id, resolved from the handbook set and never from memory, and never runs an exploit against anything live. |
Ratified 2026-09-09; definition at v0.5.0 |
| Chrollo | Architecture and handbook conformance — the UZF-{n} core, exactly one resolved stack handbook, and the repository's own architecture notes, each cited by id or by path. He is where a coverage-floor breach or a missing unit test is routed. He reviews the handbooks; he never authors them. |
Ratified 2026-09-09; definition at v0.5.0 |
| Illumi | The long watch — en's step 6, when a landing watch must outlive the session that started it. Read-only through nen watch until, under the mandatory cap; he wakes Kurapika and acts on nothing. His frontmatter carries no Edit, Write or MultiEdit, and Bash — which every observation needs — is held to a stated command allowlist in his own definition rather than to a construction. |
PROVISIONED, not ratified — en's watch only; see below |
The clause that would make a gate-crossing grant valid — mission · gates · conditions · expiry · logging — is drafted here at
docs/delegation-grammar-DRAFT.mdand ratified elsewhere: with the rewritten constitution at the migration tracker (private), a G4-class review (this is OPEN-2 indocs/ROSTER.md). No grant can be given today, because there is no valid form for one to take. Gon does the work, takes it to the gate, and stops there, exactly as every agent does by default. A delegate that acts on a draft has ratified the draft by itself.
These rows are open sub-decisions, and the ruling has been made only in part. It is the maintainer's, not this repository's. What follows is recorded verbatim as proposals. Killua has no definition and may not be acted as; Illumi's definition covers the ruled half only, and refuses the three engines below by name.
| Agent | Proposed role | Status |
|---|---|---|
| Illumi — the unruled half | Proposed: the long-running loop engines: backlog-loop, futon, senkei |
Still OPEN. The 2026-09-09 ruling provisioned him for en's long watch and nothing else |
| Killua | Proposed: delegate-run watchdog paired with Gon — a Gon mission never runs unwatched — plus fast single-object interventions | OPEN — no definition, and none implied by Illumi's |
Killua's row touches Gon's grammar and must not be collapsed into it. If ratification adopts the pairing,
watched becomes a mandatory condition on every Gon grant; if it does not, watched stays optional or is
dropped. Neither is assumed.
When work arrives that plainly wants one of them: do it in the fitting Kurapika mode and name the gap. Naming it is what eventually gets the ruling made. Standing up the agent instead closes an open question with nobody deciding it.
Extensible professional-profile agents, adopted as needed — a list of shapes the roster can grow into.
Two were activated on 2026-09-09 — Chrollo and Feitan — and their definitions landed at v0.5.0, so their
rows now live under The independents above. The five below are bench only, with no activation implied.
| Bench member | Profile | Standing |
|---|---|---|
| Machi | Integration surgery | Bench |
| Shalnark | Automation | Bench |
| Kortopi | Scaffolding | Bench |
| Pakunoda | Repo forensics | Bench |
| Shizuku | Cleanup | Bench |
None of these five has a definition in claude/agents/, none is listed in plugin.json, and none may be
acted as — activation is a decision about standing, not a licence to improvise the agent, which is why
Chrollo and Feitan could not be acted as between their activation and their definitions landing one release
later. Adopting another remains a deliberate act with its own decision.
docs/ROSTER.md § Rulings of 2026-09-09 is the authority.
Thirty-eight, invoked as hatsu:<name>. Longer descriptions in
claude/skills/README.md.
| Skill | |
|---|---|
backlog-state |
The whole backlog as one gate-oriented table — every open issue, its PRs, the gate it sits at, what it needs next. Read-only. |
backlog-board |
The identical sweep and computation, painted as an HTML gate board published as an Artifact. Read-only. |
backlog-loop |
Drives a repository's backlog to zero open actionable issues, in severity order, as gate-ready PRs. Also cuts the release tag and opens the consumers' repin PRs at declared severity-batch boundaries — never publishes a release. |
backlog-synthesis |
Groups open issues sharing a clause, a machinery file or a root cause into one consolidated issue, attaches the originals as sub-issues and closes them — behind a plan you approve first. |
bankai-handbooks |
Resolves which handbooks govern a repo and scenario, and which rule-ID prefix each one owns, so a citation is never improvised. |
bankai-quality |
Resolves the adversarial-test tooling, performance tooling and QA rules for a repo's scenario, before a release is cut. |
build |
Takes one issue from wherever it sits to a delivery PR standing ready at its human gate. |
file |
Files one well-formed, correctly-labelled, non-duplicate issue — reconciled against the open backlog first. |
futon |
Takes one whole severity band from open issues to PRs with an actor behind them, then gates the terminal step you typed — it clears its own gate and hands the cut to getsuga; it never cuts a tag itself. |
getsuga |
Cuts a release tag locally, end to end — preconditions, one folded release-proposal PR you merge, the post-merge tag, the fan-out and the consumers' repin PRs. The release unit is susanoo's; publication is mugetsu's. Never publishes a release. |
izanagi |
Repeats a task that acts until a condition holds, under a mandatory iteration cap. No cap, no run. |
izanami |
Repeats a read-only task until a condition holds. It looks, reports, and stops. |
jujisho |
Splits a mixed working copy into up to two stacked branches and PRs, by axis, proving nothing was left behind. |
pr-state |
Reports one PR's readiness as the deterministic gate's verdict, quoted, with the conjunct that failed. Read-only. |
senkei |
Inventories a consuming product repo's own backlog and states a Ready/not-Ready call for every open PR. It writes as well as reads: it re-runs failed checks (nen run rerun-failed) and fires bankai:wake/iterate on a stalled PR. Never merges. |
sharingan |
Drives one open PR to readiness at its gate and stops there — first blocking condition, threads, wakes. Renamed from drive at v0.5.0; the behaviour is unchanged and hatsu:drive no longer resolves. |
tensho |
Turns a dirty working copy into one PR, reviewing every file before staging it, then hands that PR to sharingan's engine to reach its gate. |
Nine atomic, one composite. docs/WORKFLOW.md is the authority on the loop, the two
configuration files behind it, and the phases only you can call.
| Skill | ||
|---|---|---|
breath |
atomic | Warm-up, once per effort. On the base branch and clean: fetch, fast-forward, cut {model}/{persona}/{descriptor} from the fresh trunk, then prove the declared iteration checks on that fresh tip — a base that does not build stops the effort before any of it is written. Asks only on a dirty tree; never discards a tree it has not inspected. |
rasengan |
atomic | The change itself — the authoring phase. Reads the request, resolves the stack from the declaration, plans and writes the change on the branch breath cut, running the declared iteration checks as its own feedback while it works. It commits nothing, stays inside the request's scope, and never lowers a bar to make a check pass. |
kokusen |
atomic | Verify, then commit — automatically, locally. The declared iteration checks and applicable focused tests over the finished tree (the checkpoint gate is here), red refused with the failing check quoted; then staging triage with an ask on every flagged file and never a secret, then the formatted message. Commits, and only commits. |
amaterasu |
atomic | Launch, every turn. Builds the configured target and starts it from your working directory, never a worktree. A disconnected device is reported by name. |
tsukuyomi |
atomic | Tests health. Runs the required suites, parses the results, fixes and re-runs — or stops at G5. It never patches a test to make it pass. |
rikugan |
atomic | The rich report — turn, landing, final — rendered from an HTML template, never markdown. Only the final one is written to the git-ignored Reports/. |
jutaisho |
atomic | The bell. Rings the notification ladder you declared, and drops the marker the Stop hook reads. |
ao |
atomic | Pull from the base. Rebase if unpushed, merge if not; mechanical conflicts resolved, a semantic one raised as a G5 with both sides shown. Never pushes. |
aka |
atomic | Push — yours to call. Lint → squash the unpushed commits → ao → final-tree regression → push. No PR, and no agent ever prompts for it. |
ren |
composite | The per-request loop: breath (prove the base) → rasengan (author the change) → kokusen (verify, then commit) → amaterasu → rikugan → jutaisho, looping until you call the next phase. It never pushes. |
Five atomic, three composite. mukai is yours to call; everything else here is something it runs.
docs/WORKFLOW.md § 5 is the authority.
| Skill | ||
|---|---|---|
hanten |
atomic | Adversarial review, pre-PR. Classifies the change set by scope and spawns one reviewer per scope — UI → Hisoka, security-bearing → Feitan, architecture/handbook → Chrollo, performance → Uvogin, release-adjacent → Phinks — each titled hanten · <persona> · <model alias>, never on the frontier tier. One fixed finding shape: rule id · severity · evidence · proposed fix. Kurapika fixes or pushes back with a reason; an unsettled finding is a G5. |
gyo |
atomic | The coverage bar. Touched-file line coverage against the 80/85/90 ladder, reported band by band; adds tests until every touched file clears the minimum, and raises a G5 when one honestly cannot. It never lowers the bar. |
kotoamatsukami |
atomic | End-to-end / UI tests. Runs the declared UI suite where a repository declares one; the re-recorded snapshots are what feeds the evidence table. An unsupported seat is quoted, never routed around. |
shibari |
atomic | Composes and opens the PR — why, how, what changes for the consumer, how to verify, a diagram where a flow changed, the evidence table, the checklist, Closes #N. One PR, from the last pushed commit; requests reviewers and hands it to en. Never labels a gate, never merges. |
jujutsu |
atomic | Device pairing. Walks you through trusting and registering a physical device — iOS: Developer Mode and devicectl; Android: USB debugging and adb — and lands it as a launch target through a PR. It writes the declaration and nothing else. |
murasaki |
composite | Pull + push. ao → the declared checkpoint checks on the merged tree → aka-owned lint/regression → push, only if the branch is already published. A red merged tree goes to rasengan to be authored. Never squashes, never force-pushes. |
mukai |
composite | The review-and-PR phase — yours to call. murasaki → hanten → matching aka regression evidence → gyo → publish proved updates → evidence → shibari, which opens the PR and starts en. Four of the five G5 stops live inside it. |
en |
composite | The landing watch, capped. Landing report → sharingan → murasaki when behind → sharingan → jutaisho at Ready → watch until merged → the final report. A watch with no cap does not run; where one must outlive the session, the watch itself is handed to Illumi, read-only. |
All three atomic. Two of them are yours to call, per target — that is what makes them the last two rows
of the five. docs/WORKFLOW.md § 4 is the authority.
| Skill | ||
|---|---|---|
susanoo |
atomic | Archive and packaging. Runs the lane's declared archive and produces the distributable locally. It uploads nothing and signs nothing — Nen never synthesises signing material — and an unsupported seat is quoted, never routed around. This is the release unit getsuga folds into the release PR and the two phases below send. |
kagutsuchi |
atomic | Non-production upload — yours to call, per target. The plan is always printed (nen shu deploy --target <name>, no --run); --run acts only on your own call naming the target, and never from a composite. --target is required with no default, even where exactly one destination is declared. |
mugetsu |
atomic | Publication — yours to call, per target, G3. Only on your recorded per-target go, with the preflight green and the tag already cut. One target per call, and never from getsuga, futon or en. This is the only phase that reaches other people's users. |
The boundary was always the governance, not the file. These three were named phases before they were skills, and the loop stopped at them then exactly as it does now.
Plus hatsu-warmup — the Nen contract, executing — and the /kurapika
summon command.
v0.4.0 writes the loop down. Two files hold every parameter of it, and the split matters:
nen/contract.json → project says what nen executes (lanes, per-verb argv,
preconditions, hosts, deploy targets, launch targets), and nen/workflow.json says what
the workflow decides (branch shape, which declared verbs run per iteration, the coverage ladder, reports,
notifications, the commit-trailer allow-list, the model matrix). A wrong project block runs the wrong
command, loudly. A wrong workflow.json runs the right command at the wrong moment, silently. Keeping them
apart is what keeps the second class of mistake visible.
renruns on every request and never pushes. Five phases are yours to call, and no agent ever prompts for them:aka(push),mukai(review and PR), the merge,kagutsuchi(non-production upload) andmugetsu(publish, G3).mukaiis the whole PR side, in a fixed order —murasaki, thenhanten's scope-routed review, then matching aka-owned regression evidence, thengyo's extraction-only coverage bar, then the evidence, thenshibariopening one PR and startingen's capped landing watch. Reviewers advise and never vote; the merge stays yours.- Only a genuine G5 interrupts you — red required tests, touched-file coverage under the ladder's
minimum, a semantic merge conflict, an unsettled adversarial finding, a stuck-PR escalation. Five, and
nothing else. A stop is
nen stop's banner, the report link, and the question asked through your surface's own native option picker. - Branches read
{model}/{persona}/{descriptor}, and every subagent is titled<skill> · <persona> · <model alias>— what ran, as whom, on what. A subagent is never given the frontier model tier; that tier is where your own conversation lives. hooks/hooks.jsonships two harness hooks, and they are discovered automatically at that path: aStopbell that notifies and plays a sound when a gate stop is waiting, and aPreToolUseguard onBashthat refuses agit commitorgit pushwhile you are standing on the base branch. The guard parses the command — quoted spans masked, the line split into segments, git's global options walked past — soecho 'git commit'is not a write andgit -C <dir> commitis judged in<dir>; it fails closed only where the branch it can see is not the branch the write would land on. Both are POSIXsh, use nojq, and otherwise no-op rather than block on anything they cannot read.- Two provenance trailers, one per plane — and no AI attribution trailer is ever recorded.
Hatsu-Agent: <persona>is what a local Hatsu session writes;Akatsuki-Agent: <persona>belongs to an Akatsuki roster agent on the autonomous CI plane, and nothing here writes it. Both are admitted bynen/workflow.jsonso that onecommit-msghook passes a commit from either plane — admitting a key is not licence to write it. Each names the system's own provenance, not a model claiming authorship, which is why there is no third. SetincludeCoAuthoredBy: falsein your Claude Code settings so the harness stops addingCo-Authored-By:. Enforcement is three-layered, and at the pinned build the third layer is the binary's: (a)kokusenandakarefuse to write such a trailer — agent-side, and it is what Hatsu itself carries; (b) the target repository'scommit-msghook, whichnen scaffold initgenerates fromcommits.allowedAttributionTrailers(KroApple and kro-pwa carry one) — and from nenv0.6.0that hook's automated half is derived from the repository's own policy, requiring the one key--agent-trailerresolved to plus the optionalcommits.runTrailer, rather than a fixed pair; (c)nen commit format --repoandnen wc squashrefusing the trailer outright at exit2, naming the file and the keys it admits. Layer (b) stays target-dependent — a repository that has not been scaffolded with the hook has (a) and (c) and no hook, and that is said plainly rather than promised as mechanical.
nen's shu family runs whatever a target repository declares in its nen/contract.json project
block — and nothing else — so build, futon, Gon, Hisoka, Phinks and Uvogin now start a piece of work with
nen shu warmup (--dry-run first, then bare: clean → fresh trunk → your branch → the declared build),
check a fresh host with nen shu tools, and verify with nen shu build/test/lint/coverage, each
with --dry-run printing the exact argv first. Kurapika's Transmuter mode stands a repository up with
nen shu detect → nen scaffold init (or nen scaffold new for a tree that does not exist yet) →
nen schema check → nen shu tools. nen shu deploy --target <name> prints a plan and sends nothing;
--run is the maintainer's word at G3 and no skill here adds it. And nen issue comment replaces the
raw gh issue comment two skills used to carry as residue. A repository that is not one of nen's seven
stacks (this one included: nen shu detect --repo . answers no lane detected) gets the git half of the
warm-up and its own documented commands, said plainly — the full rules are in
claude/agents/kurapika.md § The shu verbs.
Every skill ships with its own record in docs/ab/. For each of the original seventeen that
is an A/B: the mechanics before the port, the mechanics after, and a live transcript against a real backlog
showing the same verdict reached with fewer improvised commands — nen invocations where the old transcript
had raw gh. For each of the ten added at v0.4.0 it is the same evidence in the same shape, minus the
"before": the verbs exercised live with their exit codes, the residue that has no verb at the pinned nen, and
the findings the exercise filed against the binary. The surface was proven before this version was cut; it is
not an aspiration.
Those transcripts were recorded against repositories that are not public, so every private repository
name in this repository is replaced by a stable placeholder. The legend, and what is deliberately left
alone, are in docs/PUBLIC-REDACTION.md.
For you, as a public reader, rollback is simply uninstalling Hatsu — claude plugin uninstall hatsu@hatsu. Nothing server-side changed, so there is nothing else to undo.
Reinstalling the predecessor bankai plugin is the maintainer's own path back, not a public one: its marketplace is private, and the links to it in this repository resolve only for someone who already has access.
Hatsu is authored once and read on three agent surfaces. On Claude Code it is a plugin and nothing else is needed. On Codex and Cursor there is no plugin loader, so the same skills and personas are generated into each surface's own layout and committed here, and the warm-up places them into the repository you are standing in.
| Claude Code | Codex | Cursor | |
|---|---|---|---|
| you type | hatsu:rasengan |
$rasengan |
/rasengan |
| skills read from | the installed plugin | .agents/skills/<name>/ |
.cursor/skills/<name>/ |
| personas read from | claude/agents/ |
AGENTS.override.md, as prose — an untracked file that replaces your AGENTS.md in the envelope, so the warm-up copies yours into it verbatim first and never writes the tracked one |
.cursor/agents/<persona>.md |
| turn-end hook | yes | no — the bell rings in-session and says so | no — the same |
| in-session subagent | yes | no — a reviewer is a second codex exec run in its own worktree |
yes |
reviewer tier deep |
opus |
sol |
grok — Cursor-native only |
| installing it | On Claude Code | On Codex | On Cursor |
| using it | the rest of this README | Using Hatsu on Codex | Using Hatsu on Cursor |
Everything the warm-up puts in your repository is excluded through .git/info/exclude — never your
.gitignore, which is a tracked file of yours and not this plugin's to edit.
The mirrors under surfaces/codex/ and surfaces/cursor/ are
generated, not authored — one command per surface, run from the repository root, every file carrying a
GENERATED by nen surface mirror marker:
nen surface mirror generate --source claude/skills --agents claude/agents \
--surface codex --out surfaces/codex --invocation-prefix "hatsu:"
nen surface mirror generate --source claude/skills --agents claude/agents \
--surface cursor --out surfaces/cursor --invocation-prefix "hatsu:"--invocation-prefix is what rewrites every hatsu:<name> in a body — its own description included —
into that surface's spelling, so a reader of either mirror is told to type something that actually works
there. hatsu: is caller data; nen hard-codes no system's vocabulary. Edit
claude/skills/<name>/SKILL.md, regenerate, and commit both;
scripts/surface_mirror_check.sh fails a mirror that has drifted, and
says skipped, not passed on a nen too old to carry the verb.
docs/SURFACES.md is the authority — what each surface reads, what is generated
versus authored, the regeneration command, the check, and the exact headless invocation for a validation
run on each.
Everything above about the loop, the gates and the roster is true here. What changes is the spelling,
where a delegate comes from, who rings the bell, and which aliases the model matrix answers with. Nothing
in this section is product- or stack-specific: it is the same thirty-eight skills reading your repository's
own nen/contract.json.
You type $<name> — $ren, $aka, $mukai, $hatsu-warmup. That is the spelling the mirrored bodies
carry, and it is honest because the warm-up installs by cp -R: Codex lists a skill under its
frontmatter name, namespaced by the plugin manifest above the directory the path resolves to, so a copy
is advertised bare (ren) while a symlink into this checkout would be advertised as hatsu:ren.
Unchanged, name for name. $ren runs on every request — $breath on the first turn, then $rasengan,
$kokusen, $amaterasu, $rikugan, $jutaisho — and it never pushes. Five phases are yours to call,
and no agent ever prompts for them: $aka (push), $mukai (review and PR), the merge, $kagutsuchi
(non-production upload) and $mugetsu (publish, G3). A genuine G5 stop is still the banner, the
report link, the lettered options with a star on the report, and the question asked through this
surface's own option picker — AskUserQuestion is Claude Code's name for that, and what the rule binds
is the shape: a stop rendered as a paragraph ending in a question mark is a stop you have to compose an
answer to. The picker is not the turn-end hook; the hook is what escalates a bell (below), and its
absence here says nothing about how a question is put.
A persona on this surface is prose, in AGENTS.override.md, and there is no per-persona file.
$hanten's reviewers are ## <persona> sections of one generated document.
Prose does not win an identity argument with the host. On a host whose user-level instructions say "introduce yourself as X", every
codex execrun opened as X while doing the work as Kurapika, in Kurapika's discipline. So the name in a Codex transcript is neither evidence the persona loaded nor evidence it did not. The record of who acted is the one Hatsu writes:--whoonnen stop, thewhofield of.nen/last-stop.json, and theHatsu-Agenttrailer — all three carry the persona whatever the surface calls itself.
Codex has no in-session subagent, verified against codex exec --help rather than remembered: there is
no spawn-a-delegate flag anywhere in it. So $hanten raises a reviewer as a second codex exec process
in its own worktree, and the isolation the Agent tool gives for free has to be made by hand first:
rev="$(git rev-parse --show-toplevel)/.claude/worktrees/hanten-<scope>" # the reviewer's own checkout
git worktree add "$rev" HEAD # the isolated copy — hanten's own act
# `sol` is the TIER ALIAS; -m wants the host's ID for it. Resolve, never remember.
sol="$(codex debug models | grep -o '"slug":"[^"]*sol"' | cut -d'"' -f4)"
[ -n "$sol" ] || { echo "codex debug models lists no 'sol' slug — G5, the reviewer cannot be raised" >&2; exit 1; }
codex exec -C "$rev" -s workspace-write \
--add-dir "$(git -C "$rev" rev-parse --path-format=absolute --git-common-dir)" \
-m "$sol" -o "$rev/finding.json" "<the scope, the base, the paths, and the required finding shape>"A fresh worktree carries none of the warm-up's placed files, and that is fine only because the prompt carries the review.
.agents/skills/andAGENTS.override.mdare excluded, not tracked, sogit worktree adddoes not reproduce them: a reviewer told to invoke a mirrored skill in$revwould find none. The invocation above hands it the scope, the base, the paths and the required finding shape in the prompt, which is what makes it work; a review that genuinely needs the mirror needs the warm-up run in$revfirst.
-s workspace-write is the narrow choice and is deliberate: the reviewer writes its test, its note and
its finding document inside its own worktree, and it is not danger-full-access. It is not a claim that
nothing outside the worktree is writable — measured on this host with codex sandbox -c sandbox_mode='"workspace-write"', a write to /tmp succeeded and a write to $HOME was refused with
"Operation not permitted". A reviewer that needs to bypass a sandbox to read a diff is not reviewing a
diff.
There is no turn-end hook on this surface, so hooks/hooks.json is read by nobody
here and $jutaisho's in-session path is not a fallback — it is the only path there is. The skill writes
the marker itself, runs whatever escalation rungs your repository's nen/workflow.json →
notifications.rungs declares, says which of them actually rang, and removes its own marker once the
stop has been answered, which on Claude Code the hook would have done. In the default list — push, os,
sound — rung 1 is the surface's own turn-end signal and rungs 2 and 3 are the ones below; a repository
that declares a shorter list has fewer, and the report names what it ran either way.
On a headless run the OS and sound rungs are
not applicable — no seat, and one of them lies about it. Acodex execrun has no Notification Center session and no audio device. Measured inside one on this host:osascript -e 'display notification …'exited0and delivered nothing (stderr: "NSNotificationCenter connection invalid"), andafplayexited1with "AudioQueueStart failed". Rung 2's exit code is not evidence it fired — read stderr, report the rung as having no seat, and never substitute another noise-maker. The stop itself still stands: the banner, the link, the options and the question were always the real bell.
Read from your repository's nen/workflow.json → models, never from memory, and the file carries
aliases only — "latest alias only, never a version".
| tier | role | Codex alias |
|---|---|---|
frontier |
orchestrator — your own session, and never a subagent |
astra |
deep |
reviewer |
sol |
fast |
worker, measurer |
terra |
economy |
luna |
An alias is a name; the id you type carries a version. There is no bare sol — codex debug models on
this host lists gpt-reserve, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5,
gpt-5.3-codex-spark, codex-auto-review. So the id is resolved at the moment of use, in a command
substitution, and a failed resolution is a G5 rather than a guess: raising a reviewer on some other
model is not a smaller version of raising the right one.
# the deep tier's id AS THE HOST SPELLS IT TODAY — resolved, never remembered
sol="$(codex debug models | grep -o '"slug":"[^"]*sol"' | cut -d'"' -f4)" # → gpt-5.6-sol here, today
[ -n "$sol" ] || { echo "codex debug models lists no 'sol' slug" >&2; exit 1; }
codex exec -C <repo> -s workspace-write \
--add-dir "$(git -C <repo> rev-parse --path-format=absolute --git-common-dir)" \
-m "$sol" "<prompt>"-C, --cd <DIR> is the working root; -s, --sandbox takes read-only, workspace-write or
danger-full-access, and workspace-write is the one to use. Add -o, --output-last-message <FILE>
when something downstream must read the answer, and --json for JSONL events. The skills must already be
in <repo>/.agents/skills/ — that is the warm-up, above.
-s workspace-writemakes the workspace writable, and in a linked git worktree essentially all of git's own state lives outside it —HEAD, the index,FETCH_HEADunder<main>/.git/worktrees/<name>/, and the objects,refs/,configandinfo/excludeunder<main>/.git/. Sogit fetch, the branch cut,git commit,git pushand the local exclude are all refused. Reproduced on a fixture, both ways: the samegit add && git commitdied at exit128— "Unable to create …index.lock: Operation not permitted" — and exited0with--add-diradded and nothing else changed.
--git-common-dir, not--git-dir: the latter answers<main>/.git/worktrees/<name>, which coversHEADand the index and leaves the objects,refs/andinfo/excludeoutside.--path-format=absoluteis passed because the bare form answers relatively in a primary checkout.The alternative is a standalone clone, whose
.gitis inside the workspace, so-s workspace-writealone suffices. Prefer the clone where the session is disposable; prefer--add-dirwhere the effort must land in your own repository. Either way--add-dirwidens the sandbox by exactly one directory and it is a git directory — it is not--dangerously-bypass-approvals-and-sandbox, and reaching for that because a git write failed trades a named hole for an unbounded one.
Answering a G5 stop is a second invocation, and it takes almost none of the flags above. A stop is a designed part of every run, so a headless pass will need one:
sol="$(codex debug models | grep -o '"slug":"[^"]*sol"' | cut -d'"' -f4)" # resolved here too
cd <repo> && codex exec resume --last -m "$sol" --skip-git-repo-check \
-c 'sandbox_workspace_write.writable_roots=["<repo git common dir>"]' \
-o <file> "<the answer>"codex exec resume --help lists neither -C/--cd, nor -s/--sandbox, nor --add-dir. So the two
substitutions are fixed and there is no third: the working root comes from the shell's own cd, and
every extra writable root comes from -c 'sandbox_workspace_write.writable_roots=[…]'. A resume that
forgets the second hits the Operation not permitted above on the turn after the stop was answered,
which reads like a new failure and is the old one. -m is resolved on the resume for the same reason it
is on the first call — a remembered id fails at the worse moment.
- Descriptions are truncated, and there is no length that fits. Codex prints "Skill descriptions were
shortened to fit the skills context budget" at session start, and the cut is one budget divided
across every skill the session can see — it moves with what else is installed. Measured here: with
51 skills visible the longest surviving description was 411 characters; with 87 visible every
Hatsu description was cut to 186–190, mid-clause,
ren's ending at "Use when ". The clause naming the invocation and the never-clauses is exactly what is lost, and exactly what a model-invocation decision is made from. - No in-session subagent. A reviewer is a second process and its isolation is
git worktree add, run by the skill before the reviewer starts. - No turn-end hook, and on a headless run no seat for the escalation rungs.
$CLAUDE_PLUGIN_ROOTis not this surface's variable — and it is not inert either: it may be exported from your shell profile pointing at some other plugin. Use$HATSU_PLUGIN_ROOT.nenmust already be onPATHunder-s workspace-write, because the bootstrap installs outside the workspace.- A persona is prose sitting below your own instruction layer, so the identity in a transcript proves nothing either way.
- A mirrored body's relative links resolve against your repository, so a sibling link works and a
../../../docs/…one dangles. That is the price of "the body verbatim", it is deliberate, and it is the safe direction: the mirrors are for an agent reading a skill, not for a human browsing a link tree.
Same loop, same gates, same roster. What changes is the spelling, how much of a description survives,
whose build you get when two plugins claim the name, who rings the bell, and which aliases the matrix
answers with.
You type /<name> — /ren, /aka, /mukai, /hatsu-warmup. Cursor lists a mirrored skill under
its bare frontmatter name, with no plugin namespace — even through a symlink into this checkout, which
carries a plugin manifest. Codex's namespacing behaviour does not reproduce here, so the /name spelling
the mirrors print is the one that works.
Unchanged: /ren on every request — /breath, /rasengan, /kokusen, /amaterasu, /rikugan,
/jutaisho — never pushing. Yours to call: /aka, /mukai, the merge, /kagutsuchi and /mugetsu
(G3). A G5 stop is the banner, the report link, the lettered options with a star on the report, and
the question asked through this surface's own option picker — the same four parts, and all four or it is
not a stop. As on Codex, the picker is not the turn-end hook: the missing hook is a fact about the
bell, below.
Cursor has subagents, so a persona is a file again — .cursor/agents/<persona>.md, one per persona,
placed by the warm-up. /hanten classifies the change set by scope and invokes the matching persona as
that surface documents; the reviewer runs at models.roles.reviewer = tier deep.
A persona's
model:pin does not translate between surfaces, and one of them collides.nen surface mirror generatecarriesmodelthrough verbatim — correctly, since it mirrors rather than translates — so Hisoka arrives on Cursor carryingmodel: sonnet, a Claude alias in a Cursor-native matrix. The rule ishanten's: report the pin unresolvable, fall back to the role's tier (grok), and state the substitution in the title. Never silently honoured, never silently dropped.
No turn-end hook here either, so /jutaisho writes the marker, rings the rungs your repository's
notifications.rungs declares in-session, says which of them actually rang, and removes its own marker
once the stop is answered. On an ordinary turn only rung 1 is owed — the surface's own turn-end signal —
and a surface without a hook is not a reason to be louder. A headless cursor-agent -p run has the
same missing seats as a headless codex exec one.
| tier | role | Cursor alias |
|---|---|---|
frontier |
orchestrator — never a subagent |
grok |
deep |
reviewer |
grok |
fast |
worker, measurer |
composer |
economy |
composer |
The Cursor column is Cursor-native only, and the file says why in its own words:
"Cursor-native only; provider models there are reserved for Bugbot". cursor-agent models lists provider
ids too — claude-opus-5-*, gpt-5.6-sol-*, gemini-3.7-* — and --help's own examples are provider
models: the CLI accepts them and this policy does not. Naming one here is out of policy, not a local
optimisation.
frontieranddeepname the same alias on this surface, and the rule survives that. "Never the frontier tier for a subagent" cannot be checked by reading the alias here, so it is enforced on the role: a reviewer is raised atmodels.roles.reviewer, never as an orchestrator. Say the tier and the alias — "tierdeep→grok" — so a transcript read afterwards is unambiguous.
# the deep/frontier tier's id AS THE HOST SPELLS IT TODAY — resolved, never remembered
grok="$(cursor-agent models | sed -n 's/^\(cursor-grok-[0-9.]*-high\) - .*$/\1/p' | sort -Vr | head -n 1)"
[ -n "$grok" ] || { echo "cursor-agent models lists no cursor-grok id" >&2; exit 1; }
cd <repo> && cursor-agent -p --output-format text --model "$grok" -f "<prompt>"
--model grokdoes not exist and nothing runs.grokis the alias the policy file carries; the command line needs the id the host is serving.cursor-agent modelsprints<id> - <label>, one per line, and the Cursor-native rows today arecursor-grok-4.6-{low,medium,high,xhigh}[-fast],cursor-grok-4.5-high[-fast]andcomposer-2.5[-fast]. The id is a two-axis choice — version and reasoning tier — so the rule is written down rather than left to each caller: newest version, plain-high, never-fast. Thesed/sort -Vrabove is that rule, executable; it resolves tocursor-grok-4.6-highon this host today, andcomposerresolves the same way tocomposer-2.5.
-p, --printis the non-interactive form;--output-formattakestext,jsonorstream-jsonand only works with--print.sort -Vis what picks the newest version, and macOS's own/usr/bin/sorthas it — verified on macOS26.4.1, where-Vrorderscursor-grok-4.6-highabovecursor-grok-4.5-high. On a host whosesortlacks it the substitution comes back empty and the[ -n "$grok" ]guard exits1rather than running on a wrong id: the fail-closed direction, and the reason the guard is written out.-fis--force, and it is NOT a file flag. It "force allow[s] commands unless explicitly denied"; the line above parses only because-ftakes no value and the prompt is a positional argument. The Codex block uses no such adjacency, so a reader copying one line is being invited to misread it.- The working root is the shell's own
cd, and that form works on every build. This build's--helpalso lists--workspace <path-or-name>,--add-dir <path>and-w, --worktree [name]. - There is no sandbox to widen, and that is a difference rather than an absence. Codex's whole
--add-dirbox exists because-s workspace-writecannot perform a single git write in a linked worktree. Cursor has no counterpart to that failure: a headless run fetched, cut a branch, rebased and first-published inside a linked worktree with no permission failure of any kind. - Answering a G5 stop is
--resume, a flag on the same command line — unlikecodex exec resume, so a stop is answered with the same line plus the id. Fix the id up front withcursor-agent create-chat, which prints one, rather than relying on--resume's "latest". The path is unexercised: no G5 fired in the recorded run, so--resumehas never been used in anger. - Give a
-prun a timeout. Observed once, on the pre-skills build: the process printed its complete answer and then stayed alive at 0% CPU twelve minutes later. There is no-o/--output-last-messageequivalent to read a result from, so a caller waiting on process exit rather than on output hangs. Not reproduced on2026.09.08-6caf4ff, where every run exited0.
- Roughly thirty characters of a description survive, and that is not a shorter Codex. Asked for the
length of one, a session answered "The description text is 30 characters long" — the description dies
inside its own first clause, taking the trigger, the invocation spelling and every never-clause with it.
Do not shorten a description to fit this: there is no length that survives, the other two surfaces
keep the tail, and a thirty-character description would be worse everywhere and no better here. What
follows instead is that on Cursor the skill
namedoes almost all of the routing work. - The name space is flat, global and shared. It is not only your repository's
.cursor/skills/: on this host one listing carried the thirty-nine mirrored skills plus Cursor's own built-ins plus this host's Claude Code plugin skills,buildanddriveamong them. Hatsu claims thirty-nine ordinary words at once —build,file,en,ao,ren,breath. The shadowing itself is inferred, not proven, and is written here as such: two probes tried to confirm it and could not, because the descriptions this surface keeps are far too short to tell two rivalbuildentries apart. It is a documented hazard, not a documented mechanism — and the warm-up's collision listing cannot see it, because it lives outside your repository entirely. - No turn-end hook.
$CLAUDE_PLUGIN_ROOTis not this surface's variable, and may point at another plugin. Use$HATSU_PLUGIN_ROOT.--modelis Cursor-native only. The CLI will accept a provider model; the policy will not.- A mirrored persona keeps its
model:pin verbatim, which may name a model this surface cannot resolve — reported and substituted, never silently either way. - One half is still unverified: which repository a mirror's relative
../../../nen/workflow.jsonlands in through a symlink was not re-tested here. The rule that carries it is the one the warm-up states — read the file in the repository the session is standing in, and where it has none, say so and use the defaults rather than falling back to this plugin's copy.
Hatsu drives work to a gate and stops there. One gate is partially delegated, and exactly
one: CON-25 names four exhaustive carve-outs under which release into build — applying the
building stage label — may be crossed without a per-issue confirmation, inside a named run that
logs every application and lapses when the run ends. Every other gate in the table below is
yours, without exception. Clause ids are the inherited constitution's, kept stable across the
rewrite; docs/ROSTER.md carries the same table.
| Gate | Clause | Delegable? |
|---|---|---|
| G1 — Epic approval — the human applies one delivery-mode label | CON-4 |
Never |
| G1-M — Release into build — applying the building stage label | CON-25 |
The one delegated crossing — only under CON-25's four exhaustive, named carve-outs |
G2 — Merge to main |
CON-5 |
Never by these agents. No agent here merges main, or its own PR anywhere. |
| G3 — Release go/no-go | CON-6 |
Never. Preparing a release is allowed; publishing is not. |
| G4 — Policy / spec change | CON-7 |
Never |
| G5 — Anything else human-only | CON-47 |
Never — its definition is "the decision is yours" |
No agent in this roster casts a request_changes review — for any reason, on any PR. They run on your
credentials, so GitHub records the vote as yours, and casting one manufactures your governance vote on a
PR you have not read. The substitutes are a wake label (for findings an automated reviewer already delivered)
and a filed issue (for a substantive finding of the agent's own).
Claude Code keys its plugin cache on .claude-plugin/plugin.json's version. Change a plugin-shipped
surface without bumping that field and the change is real in the repository and invisible on every machine
that already has the plugin installed — no error, no warning, the fix ships to nobody.
scripts/plugin_bump_check.sh, wired as the
plugin-bump-check workflow, fails a PR that tries. The guarded
surface is .claude-plugin/**, claude/**, nen/**, contracts/**, docs/ROSTER.md,
docs/delegation-grammar-DRAFT.md, hooks/**, templates/**, surfaces/**,
scripts/surface_bootstrap.sh and .mcp.json — everything an
installed runtime reads, the generated Codex and Cursor mirrors included: the warm-up reads plugin resources
from $CLAUDE_PLUGIN_ROOT, while first-run bootstrap reads its script and generated surface from the
canonical $HATSU_PLUGIN_ROOT checkout. Bump
version (patch for wording, minor for behaviour or a new skill, major for a breaking interface change —
which the minor carries while Hatsu is on 0.x, SemVer clause 4, the reading applied to nen's own line);
or, if a change provably cannot affect the shipped surface, write no plugin bump: <reason> in the PR
body. Recorded refuse/pass transcripts: docs/ab/plugin-bump-guard.md.
The check is required on main by the repository ruleset main: plugin-bump guard required
(enforcement: active), so a failing plugin-bump-check blocks the merge. It runs through
pull_request_target: GitHub selects the workflow at the trusted workflow SHA, and the job-level
same-repository guard skips fork PRs before assigning a runner. The PR head is checked out as data with no
persisted credential; executable guards, the workflow policy, and dependency pins come from the trusted
workflow checkout. The offline policy validator pins this workflow's trigger, job id, steps, permissions,
and runner so an edit cannot silently remove the required context or execute a PR-controlled guard.
claude plugin validate . --strict
scripts/surface_mirror_check.sh # the Codex/Cursor mirrors match their sourceThe second writes nothing and needs no credential. At the pinned build it runs the real check —
codex ok: 40, cursor ok: 47, exit 0. It exits 2 — saying so, rather than passing — when the nen on
your PATH has no surface verb, which at this pin means the binary is not the pinned one; see
docs/SURFACES.md § 4.
MIT — see LICENSE.