This project has been archived by its maintainers, and is no longer receiving any updates.
named-subagents
Themed, non-repeating nicknames for Claude Code subagents — a userspace port
of Codex's per-instance nickname_candidates,
grown into a full identity layer.
When you fan out several Claude Code subagents, parallel instances of one role
are three identical Explore labels. Codex instead gives every spawned instance
a distinct human-legible nickname. Claude Code has no such field — requested
in anthropics/claude-code#9206,
closed "not planned" — so this library emulates it, and goes further:
nicknames are themed to the kind of task, never repeat across runs, and
each one knows who it's named after.
🧭 Hudson [Explore] map the auth module ← explorers explore
🤔 Plato [architect] why was event-sourcing chosen ← philosophers ponder
🔍 Bosch [debugger] root-cause the flaky test ← detectives debug
Quick start: the Claude Code plugin
claude plugin marketplace add Bobby-cell-commits/named-subagents
claude plugin install named-subagents@named-subagents
Needs python3 (3.8+) on PATH; nothing else to install. New sessions show a
distinct name for every subagent in the live task tree, for any agent type:
● main
○ Diesel 🔧 Diesel · run the hostname check
○ Hudson 🧭 Hudson · list /etc/apt
○ Planck 🔬 Planck · research the release cycle
(Captured from a real run on Claude Code 2.1.283: a general-purpose, an
Explore and a research-subagent dispatch.)
What the plugin does, with four hooks:
- Names every dispatch (
PreToolUse). It sets the Agent tool'snamefield, which the task tree shows in its left column, and writes<emoji> Name · taskinto the label, which finish notices quote. Names are themed by the task and drawn from the full pool (~395 names) without repeats. Two live agents never share a name, becausenameis also the addressSendMessageroutes by; names equal to another local session's title are skipped too. Anamethe model chose itself is left alone. - Tells each agent who it is (
SubagentStart): the agent gets its name and is asked to open its report with[Name]. - Frees names when agents finish (
SubagentStop), keyed by agent ID, so a resumed agent (SendMessageto a finished one) keeps its name. - Fails loud (
Stop). After each agent finishes, the hook checks that Claude Code recorded the name it set. If a future Claude Code version stops honoringname, you see anamed-subagents: …notice at the end of the turn instead of silently losing the names.
It costs nothing in the prompt: no agent files, no extra lines in the model's
agent list. NAMED_SUBAGENTS_HOOK_DISABLE=1 turns it off.
Upgrading from 0.5/0.6? Those versions wrote callsign agent files
(Bosch.md, Hudson.md, …) into ~/.claude/agents. They still load into every
session's agent list and cost about 57–183 tokens each. Remove them with
named-subagents roster uninstall (--dry-run first to preview). It deletes
only files carrying the roster marker, never your own agents.
Already installed the hooks with named-subagents hook install? Run
named-subagents hook uninstall first. While that install is present the
plugin's hooks stand down, so the two never both name one dispatch
(NAMED_SUBAGENTS_PLUGIN_FORCE=1 overrides).
The rest of this README covers the library and CLI underneath the plugin.
The idea in one line
A subagent has two names: its role (what it is — Explore, worker, …,
native via subagent_type / .claude/agents/*.md) and its nickname (which
instance — Hudson, Nansen, …). This library adds the second.
| Codex | Claude Code native | This library |
|---|---|---|
agent name (identity + routing) |
subagent_type |
— (unchanged) |
nickname_candidates (per-instance display) |
(none) | ✅ themed pools |
| — | — | ✅ non-repeat across runs (ledger) |
| — | — | ✅ task → theme auto-matching |
| — | — | ✅ install-once auto-namer (one hook, every fan-out) |
| — | — | ✅ pins, bios, custom themes, stats, doctor |
Install
pip install named-subagents # Python 3.8+, zero dependencies
npm i named-subagents # Node ≥ 16, ESM, zero dependencies, types shipped
Both ship the same 395-name registry and a named-subagents CLI. Or vendor it:
drop the named_subagents/ folder (Python) or js/named_subagents.mjs +
registry.json (JS) into your repo — stdlib/node: builtins only.
Auto-namer: install once, names every fan-out
Everything below the fold is opt-in — you call assign/allocate and wire the
names in yourself. The auto-namer removes that step: one Claude Code hook that
nicknames every subagent dispatch automatically. A parallel fan-out that used to
show three identical Explore labels shows three distinct explorers, with zero
code on your side.
named-subagents hook install # register the hooks (SubagentStart + task capture) in ~/.claude/settings.json
named-subagents hook status # verify: installed? ledger path? names used so far
That's the whole setup. New Claude Code sessions now inject a task-themed
identity block (e.g. "You are Durga (a guardians & sentinels callsign),
one of several parallel agents…" for a security review) into every subagent's own
context, so parallel results come back attributed by nickname. Pause it any time
with NAMED_SUBAGENTS_HOOK_DISABLE=1 (no uninstall needed), or remove it with
named-subagents hook uninstall.
How it works. Two hooks. An output-free PreToolUse capture (matcher
Agent|Task) reads each dispatch's task and pushes it onto a small per-session
FIFO queue; a SubagentStart hook (matcher *) pops the oldest role-matching
entry, allocates a themed, non-repeating nickname (the same ledger + generation
machinery as the CLI, lock-serialized so a parallel fan-out never collides on a
name) and returns hookSpecificOutput.additionalContext carrying the identity
block — task-themed for generic roles (general-purpose, worker), role-themed
for informative ones (Explore, Plan, …), with a role fallback whenever no
captured task is available. additionalContext is additive — when several
hooks fire, each one's context is appended and none clobbers the others — so the
nickname reaches the subagent even alongside your own hooks.
Why this shape (and not updatedInput). 0.4.0/0.4.1 rewrote the dispatch via a
PreToolUse hook returning updatedInput — which Claude Code silently drops for
the Agent tool when more than one PreToolUse hook runs
(claude-code#15897,
#39814). Symptom: the
ledger advances but subagents show no nickname. Both current hooks are immune —
the capture hook returns nothing, and additionalContext is additive. If you
installed < 0.4.2, re-run named-subagents hook install: it migrates the legacy
entry (hook status flags a lingering one with ⚠ legacy).
Honest limits. Claude Code has no per-instance display-name field: the type
label (Explore) is unchanged and the nickname rides inside the subagent's context,
not as a UI badge. There is no cross-event correlation key, so the queue pairs
dispatches to subagents by FIFO order + role matching — validated live on Claude
Code 2.1.207 (including batched event orderings), though in a same-role fan-out of
different-category tasks a reordered start could in principle swap two siblings'
themes (each still gets a distinct, validly-themed name). The [Hudson] self-tag in
the agent's reply is best-effort — an agent may ignore the preamble. The hook
fails open: any error, or a future event rename, degrades to a normal un-named
dispatch, never a broken one.
Safety. hook run never exits non-zero (a broken namer must never break your
fan-out), never changes your permission posture (it returns only additionalContext,
never auto-allow), and never auto-loads the untrusted ./.named-subagents.json.
install/uninstall back up settings.json, refuse to touch malformed JSON, and
only ever add or remove their own entry.
| Env var | Effect |
|---|---|
NAMED_SUBAGENTS_LEDGER |
ledger path (default ~/.local/state/named-subagents/hook-ledger.json) |
NAMED_SUBAGENTS_QUEUE_DIR |
task-queue dir (default ~/.local/state/named-subagents/queue/; per-session files, self-clean) |
NAMED_SUBAGENTS_HOOK_DISABLE=1 |
pause the hooks (passthrough) without uninstalling |
NAMED_SUBAGENTS_HOOK_BIO=1 |
add the figure's one-line bio to the preamble |
One port per machine. The Python and JS hooks work identically but guard the ledger and task queue with different lock primitives (
flockvs anO_EXCLlockfile). Install one runtime's hooks per machine — or, if you really want both, point them at separate state viaNAMED_SUBAGENTS_LEDGER/NAMED_SUBAGENTS_QUEUE_DIR. Sharing state across both ports still fails open (worst case: a dispatch goes un-named or falls back to role theming), never corrupt.
Name mode: names in the live task tree (v0.7.0)
The auto-namer above delivers identity into the subagent's context only. Name
mode also makes the name visible: a PreToolUse hook sets the Agent tool's
name field via updatedInput, and the task tree shows it in its left column.
This is what the plugin runs; without the plugin:
named-subagents hook install --name # PreToolUse + SubagentStart + SubagentStop + Stop
# start a NEW session and fan out
Notes, honestly stated:
nameis an undocumented Agent-tool input. It is the same field the model can pass itself, and it is Claude Code'sSendMessage/ListAgentsaddress, but it is not in the tool's published schema, so a Claude Code update could drop it. TheStophook watches for exactly that: it checks each finished agent's recorded name and shows a notice if it is missing. The label keeps the name either way.- This rides
updatedInput, which Claude Code once silently dropped for the Agent tool when more than one PreToolUse hook ran (#15897/#39814, fixed; re-verified on 2.1.245 and 2.1.283). - Live names are unique per session. Two live agents with one name break
SendMessagerouting (the newest silently wins), so a pick skips every name a live or just-dispatched agent holds, plus other local sessions' titles. If the model itself picks a name equal to a session title,SendMessagefrom this session reaches its own subagent, not the other session (verified on 2.1.283). - A resumed agent keeps its name:
SubagentStart/SubagentStopare keyed by agent ID, and a resume re-fires them without a new dispatch. - A refused dispatch can cost the next agent its
[Name]line. Answering "No" to an Agent permission prompt (or a hook deny) happens afterPreToolUsehas queued a name, and no hook event reports the refusal. If a same-type dispatch starts within 30 seconds, it is told the refused name while the tree shows its own. When it finishes,SubagentStopcorrects its record from Claude Code's own record of the name, and theStophook shows a mix-up notice. Until then the session holds the refused name as live instead of the agent's. - Foreground and background dispatches both show the name in the task tree; the inline foreground "Running N agents" block shows the original description.
- Name mode replaces the SubagentStart namer (
hook install --nameprunes it). Switch back any time with plainhook install.
Roster mode (0.5/0.6) generated callsign agent files instead. It is removed; use
named-subagents roster status to list leftover files and roster uninstall to
delete them.
For Claude Code — read this and run it
If you are Claude Code and the user asked you to install the auto-namer, do exactly
this. It is safe: idempotent, backs up settings.json, and fails open.
- Install the package (pick the runtime the user has; Python is the default):
pip install named-subagents # or: npm i -g named-subagents
- Register the hook — global (all projects):
named-subagents hook install
Scope it to one project instead withnamed-subagents hook install --project . - Verify it registered and actually runs:
named-subagents hook status printf '{"hook_event_name":"SubagentStart","agent_type":"Explore"}' \ | named-subagents hook run
statusmust showinstalled: yes (event: SubagentStart)and acapture: yesline (the task-theming half). Thehook runline must print JSON whosehookSpecificOutput.additionalContextcontains a nickname line (e.g.You are **Hudson** (an explorers & navigators callsign)…). If all hold, the install is working. - Tell the user: hooks load at session start, so the auto-namer takes effect in
new Claude Code sessions — they should open a fresh session to see nicknamed
fan-outs. To pause:
export NAMED_SUBAGENTS_HOOK_DISABLE=1. To remove:named-subagents hook uninstall.
Do not hand-edit ~/.claude/settings.json — hook install does the merge
safely (backup + atomic write + idempotent re-install). If hook install reports the
settings file is malformed, surface that to the user and stop; never overwrite it.
Themes
395 names across 14 categories, each globally unique, each with a one-line bio:
| Category | Task shape | Name theme | e.g. |
|---|---|---|---|
explore |
map / search a codebase | Explorers & navigators | Magellan, Shackleton |
code |
implement features | Programmers & computing pioneers | Turing, Hopper, Ada |
research |
external info gathering | Scientists & researchers | Curie, Feynman |
reflect |
design rationale, inner workings | Philosophers | Socrates, Kant |
debug |
root-cause hunting | Detectives | Holmes, Poirot |
test |
edge cases, fuzz, adversarial | Tricksters | Loki, Anansi |
review |
critique, verdict | Judges & jurists | Solomon, Ginsburg |
security |
audit, threat model | Guardians & sentinels | Argus, Heimdall |
design |
UI / UX / visual | Artists & designers | DaVinci, Rams |
data |
analysis, stats, ML | Mathematicians & statisticians | Gauss, Noether |
orchestrate |
plan, coordinate | Strategists & generals | SunTzu, Napoleon |
docs |
technical writing | Writers & authors | Orwell, Borges |
build |
infra / refactor / perf | Engineers & inventors | Tesla, Brunel |
default |
catch-all | Celestial (stars) | Orion, Vega |
Pools are deliberately diverse (Ibn Battuta, Zheng He, Ada Lovelace, Ramanujan, Confucius, Hypatia, Murasaki, …) — good practice, and larger pools mean rarer generation cycling.
Usage
from named_subagents import Registry, Ledger, plan_fanout
reg = Registry.load()
ledger = Ledger(".named-subagents-ledger.json") # non-repeat across runs
plan = plan_fanout(
["map the auth module", "map the billing module", "map the search module"],
reg, ledger=ledger, role="Explore",
)
for a in plan:
# a.agent_kwargs() -> {subagent_type, description, prompt}, Agent-tool-ready
print(a.emoji, a.nickname, a.subagent_type, "—", a.bio)
import { Registry, Ledger, planFanout } from "named-subagents";
const reg = Registry.load();
const ledger = new Ledger(".named-subagents-ledger.json");
const plan = planFanout(["map auth", "map billing", "map search"],
reg, { ledger, role: "Explore" });
Cross-language parity is CI-enforced: same md5-seeded ordering, same registry, same ledger format — identical inputs give byte-identical outputs, and either language can continue a ledger the other wrote.
The generated prompt prepends a persona preamble telling the agent to begin
its report with [Hudson], so parallel results come back attributed by
nickname. Add with_bio=True / withBio: true (CLI: --bio-in-prompt) and
each agent also learns who it's named after.
Attribution never depended on the agent complying. The nickname is already
in the dispatch metadata — it is the display label your runner shows — so it
is deterministic regardless of what the agent writes. The [Hudson] self-tag is
only for the case where you have the raw report text and nothing else; for
that path, attribute(nickname, report) / attribute() verifies the prefix and
repairs it (missing → prepended, wrong nickname → replaced; idempotent).
CLI
named-subagents categories # the 14 themes
named-subagents resolve --task "audit auth for injection" # -> security
named-subagents resolve --task "audit auth" --explain # ...+ why (keywords, scores)
named-subagents allocate --category reflect --count 3
named-subagents assign --role Explore --task "map the router" \
--count 4 --ledger .ledger.json # Agent payloads
named-subagents assign --role Explore --task "map the router" --count 3 --format table # human-readable
named-subagents init # scaffold a starter config (--cwd / --path / --force)
named-subagents bio Heimdall # who is this figure?
named-subagents stats --ledger .ledger.json # pool burn-down, generations
named-subagents doctor # self-checks — incl. a live auto-namer self-test
named-subagents hook install # auto-name every fan-out (see "Auto-namer" above)
named-subagents hook install --name # name mode: names in the live task tree (what the plugin runs)
named-subagents roster uninstall --dry-run # list 0.5/0.6 roster agent files; drop --dry-run to delete them
named-subagents hook status # is the hook installed? ledger usage
Recording a demo?
scripts/record-demo.shruns a short, narrated walkthrough suitable forasciinema rec(or pipe the cast to a GIF) — see its header for the exact commands.
Stable identities: pins
Always call the security agent Argus:
named-subagents assign --task "audit the release" --category security --pin security=Argus
Pinned names bypass the ledger (a stable identity recurs by design) and are
reserved out of normal draws, so nobody else can be issued Argus.
Recycle or burn names
named-subagents release --category explore --name Hudson --ledger .ledger.json # recycle
named-subagents retire --category explore --name Columbus --ledger .ledger.json # never again
release returns a short-lived agent's name to the pool; retire removes a
name permanently (every generation). If retire/pins empty a pool entirely you
get a clear PoolExhaustedError up front.
Custom themes & config
--config PATH, $NAMED_SUBAGENTS_CONFIG, or
~/.config/named-subagents/config.json (and, opt-in, a project-local
./.named-subagents.json — see the security note below):
{ "pins": { "security": "Argus" },
"categories": { "starships": { "theme": "Star systems", "emoji": "🚀",
"keywords": ["fleet"], "names": ["Enterprise", "Rocinante"] } },
"extend": { "explore": { "names": ["Kupe"] } } }
New categories are added, same-key categories replace the bundled one, extend
appends to an existing pool. Everything is re-validated on load — global
uniqueness and a strict name pattern (see SECURITY.md; custom
names are untrusted input that ends up inside agent prompts).
Since 0.3 the project-local ./.named-subagents.json is opt-in — it is the
one untrusted-input surface (a repo you cloned controls it), so it is not
auto-loaded unless you pass --cwd-config or set NAMED_SUBAGENTS_CWD_CONFIG=1.
--no-cwd-config (or NAMED_SUBAGENTS_NO_CWD_CONFIG=1) forces it off and wins
over any opt-in. Explicit --config PATH, $NAMED_SUBAGENTS_CONFIG, and the
home config are always honored (deliberate or user-owned).
Collision-avoidance against real agents
--avoid-installed (or plan_fanout(..., avoid_installed=True)) scans
.claude/agents/ + ~/.claude/agents/ frontmatter and guarantees nicknames
are disjoint from your installed agent names — case-insensitive, at the base-name
level, enforced at draw time.
Orchestrator adapters
named-subagents assign --task "…" --count 4 --format workflow # Workflow snippet
named-subagents assign --task "…" --count 4 --format swarm # swarm YAML fragment
named-subagents assign --task "…" --count 4 --format labels # generic JSON
named-subagents assign --task "…" --count 4 --format table # human-readable table
Doctor (self-awareness)
named-subagents doctor checks: registry integrity (uniqueness, sanitization,
bios coverage), ledger health, pin validity, installed-agent collisions,
version consistency across __init__.py/pyproject.toml/package.json, and —
when both runtimes are present — a live Python↔JS parity probe. Non-zero exit
on failure; --json for machines.
/named-fanout skill
A ready-made Claude Code skill wrapping the CLI lives in
skill/named-fanout/:
cp -r skill/named-fanout ~/.claude/skills/
How non-repeat works
allocate() draws from the category pool in a deterministic md5-seeded order,
records used names in the ledger, and skips them next time. When a pool is
exhausted it advances a generation and suffixes names (Magellan,
Magellan·2, …). A display name is never issued to two concurrently-live
holders, and is never reused at all unless you explicitly release it.
Allocation is deterministic given (category, ledger-state) — resume- and
re-run-safe (the same reason Claude Code Workflows ban Math.random).
Routing is best-effort
Category resolution is explicit category > subagent_type match > task-keyword match > default (the auto-namer hook puts the task first for generic roles —
see above). The keyword layer is a heuristic, not a classifier — for
guaranteed themes pass category= or role= explicitly.
Where this sits (community landscape)
COMMUNITY.md surveys 11 community Claude Code agent projects.
The whole ecosystem names agents by functional role; none provide
per-instance nicknames, task-themed names, or a non-repeating ledger. This is
an identity layer that composes under orchestrators like claude-swarm,
metaswarm, or claude-flow rather than competing with them — that's what the
--format adapters are for.
Tests
python3 tests/test_named_subagents.py # library suite incl. state-machine campaigns
node js/test_named_subagents.mjs # twin suite (mirror-kept)
python3 tests/test_hook.py # auto-namer: task capture, fail-open, concurrency, install merge-safety
node js/test_hook.mjs # auto-namer mirror suite
scripts/parity_check.sh # cross-language gate (both runtimes, incl. the capture→pop chain)
CI runs Python 3.8/3.12/3.13, Node 18/20/22, install smokes for both package managers, the parity gate, plus ruff lint and library-core coverage.
Files
| Path | Role |
|---|---|
named_subagents/ |
Python package (reference impl) + canonical registry.json |
js/ |
JS/ESM npm package (twin port; js/registry.json is generated at pack time) |
tests/ |
Python suites (unit, auto-namer hook, resolution eval) |
examples/ |
runnable demo (demo.py) |
skill/named-fanout/ |
Claude Code skill wrapping the CLI |
docs/COMMUNITY.md |
ecosystem survey |
SECURITY.md / CONTRIBUTING.md |
threat model / parity discipline |
docs/RELEASING.md |
release process |
Contributing
Contributions welcome — see CONTRIBUTING.md for the
cross-language parity discipline. Bug reports and ideas via
issues.
License
Metadata
Release files for named-subagents 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| named_subagents-0.7.0.tar.gz | 115.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| named_subagents-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 190.0 kB
Release files / named_subagents-0.7.0.tar.gz
| Download URL | named_subagents-0.7.0.tar.gz |
|---|---|
| Size | 115.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6794bc8c41a260cfc6adbb029b8c89e02a82e43daa8f1c726c7327e9b9b0b1b2
|
|
BLAKE2b-256 checksum How to use checksums |
75982c043ebe5fc428fe1cb1eb9fde78b25c10109a0952ca900b39611885096e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / named_subagents-0.7.0-py3-none-any.whl
| Download URL | named_subagents-0.7.0-py3-none-any.whl |
|---|---|
| Size | 74.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00c3dd7d7e727641fa679aea4e769947ff532b73fe897c3d40218950d9c36a80
|
|
BLAKE2b-256 checksum How to use checksums |
45af9798f4296dce0cb04b57b19bef351821500c62af57b057ec2f8f998bad66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logSecurity for named-subagents 0.7.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for named_subagents-0.7.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.