Agent Router Setup — spawn the right agent, not the habitual one
sonnet status / install / upgrade / enable / disable / uninstall / purge project only opt-in experimentalWhat it does
/brewtools:agent-router-setup installs one PreToolUse hook, matcher Agent, that checks whether the main loop picked the right agent for a spawn. When a project agent from .claude/agents/ or a brewcode specialist clearly owns the task and the main loop reached for a generic type instead, the hook denies the spawn and names the real expert. When several agents look equally plausible, it only appends a nudge listing the top candidates — never a block. Everything else stays silent.
The check runs at zero cost by default: a deterministic Node script, no model call, under 100 ms including node startup. An optional second tier adds an LLM judge for the cases tier 1 leaves as a nudge — but that tier is opt-in for a reason stated plainly below, not hidden in a footnote.
The skill itself never edits hook logic. It decides a mode from the canonical set status | install | upgrade | enable | disable | uninstall | purge, plus the extra level fast|strict, then delegates every file, settings.json and config change to the brewcode:hook-creator agent following a fixed runbook.
Warning
EXPERIMENTAL. Opt-in, installed per project only, and not registered in brewtools/hooks/hooks.json — installing the brewtools plugin does nothing until this skill runs and you confirm.
When to use
| Situation | Suggested invocation |
|---|---|
Repo has project agents but the main loop keeps reaching for general-purpose | /brewtools:agent-router-setup install |
| Check what is currently wired without changing anything | /brewtools:agent-router-setup status |
| Pick up a new hook version after a brewtools update, same level | /brewtools:agent-router-setup upgrade |
| Curious about the LLM judge tier, want the cost spelled out first | /brewtools:agent-router-setup level strict |
| Drop the LLM judge, keep the free deterministic check | /brewtools:agent-router-setup level fast |
| Stop enforcing without deleting anything | /brewtools:agent-router-setup disable |
| Remove everything, including the config | /brewtools:agent-router-setup purge |
Example
/brewtools:agent-router-setup install
Expected flow: the skill prints the current status table (hook_file=no tier1_refs=0 enabled=n/a level=n/a roster=4), states the plan — copy agent-router.mjs into .claude/hooks/, write .claude/brewtools/agent-router.json with level: "fast", merge one PreToolUse entry into .claude/settings.json — asks one question (level, default fast), then delegates to brewcode:hook-creator and re-prints the status table showing hook_file=yes tier1_refs=1 enabled=true level=fast.
Workflow
- Status first, always
Before any mode runs, the skill checks the hook file,
settings.jsonwiring, the config and the size of the local agent roster, and prints a state table. Nothing is installed or removed blind. - Mode decided from free text
No arguments defaults to
status. Otherwise the skill reads intent —install,upgrade,enable,disable,uninstall,purge, pluslevel fast/level strict— including RU triggers like “поставь” or “снеси”. Ambiguity between install and a removal verb triggersAskUserQuestioninstead of a guess. - Plan stated before any question
Plain text, before anything is asked: current roster size, exact paths that will change, exact
level. If the roster is empty, the skill says so up front — only the 4 intent rules can ever fire. - Only the level is asked
The single
AskUserQuestionthis skill ever raises.strict’s option text carries the parallel-hooks cost verbatim, not buried in a final report. Scope is never asked — there is only one, project. - File work delegated to hook-creator
One
brewcode:hook-creatorspawn per mode copiesagent-router.mjs, merges or strips thesettings.jsonentries (tier-2’s judge prompt is inlined, never copied as a file), and writes or editsagent-router.json. - Refreshed status printed
The status block re-runs. Wiring changes (install/upgrade/level/uninstall/purge) need a new Claude Code session; config value changes (
enabled,genericTypes,minScore,margin,intents) are read live.
Technical details
Tier 1 decision order
It allows as early as it can — an ambiguous case becomes a nudge, never a block:
| # | Check | Result |
|---|---|---|
| 1 | tool is not Agent | allow |
| 2 | agent_id present — a subagent issued the spawn | allow; only the main loop is policed |
| 3 | enabled:false, or a config that exists but does not parse | allow |
| 4 | picked type is already a project agent | allow |
| 5 | picked type is not on genericTypes — a specialist/built-in | allow. An omitted subagent_type is normalized to general-purpose first and policed like one |
| 6 | intent regex over the task text (skill/agent/hook authoring, bash scripts) | deny, naming the expert — a project agent covering the same intent outranks the plugin specialist |
| 7 | score the task against .claude/agents/*.md frontmatter | one clear winner -> deny naming it; several plausible -> nudge with top 3; nothing -> silent allow |
| 8 | anti-loop guard | the same task is denied at most once per session |
| 9 | any error | fail open |
neverFlag defaults to Explore, Plan, statusline-setup, output-style-setup. Config and roster are read from the nearest ancestor of cwd holding a .claude dir (up to 16 levels up), fresh on every call.
Two tiers
| Tier | Wiring | Cost | Status |
|---|---|---|---|
1 — default (level fast) | {"type":"command","command":"node",...}, PreToolUse matcher Agent | deterministic, zero tokens, under 100 ms including node startup | production |
2 — opt-in (level strict) | {"type":"agent",...}, model claude-haiku-4-5-20251001 | one model call on every Agent spawn | wired but not yet behaviorally verified — do not assume it catches anything until it has been tested against real payloads |
Warning
Why tier 2 is opt-in, not triggered by tier 1’s doubt. Claude Code runs all hooks matching an event in parallel; none can skip another. Tier 1 has no way to gate tier 2 — it cannot say “only run the judge when I’m unsure.” Once level strict is wired, the judge fires on every Agent spawn, and its own fast exit is the only cost control available. That is the whole reason fast is the default and the recommendation.
Modes
| Mode | Effect | Hook file | settings.json | Config | tmp markers |
|---|---|---|---|---|---|
status (default) | report only | — | — | — | — |
install | wire tier 1 (+ tier 2 if strict) | copied | entry merged | written | — |
upgrade | re-emit from the current plugin version at the already-configured level | re-copied | entries re-merged | values preserved | kept |
enable | enabled:true | kept | kept | edited | kept |
disable | enabled:false, hook stays wired as a no-op | kept | kept | edited | kept |
uninstall | unwire | deleted | entries stripped | kept | kept |
purge | full wipe | deleted | entries stripped | deleted | deleted |
level fast (extra) | drop the tier-2 entry | kept | tier-2 stripped | level:"fast" | kept |
level strict (extra) | add the tier-2 entry (judge prompt inlined) | kept | tier-2 appended | level:"strict" | kept |
Canonical order first, extras after — the same seven verbs every -setup skill uses. upgrade never asks a question and never changes a setting: it reads level back out of the existing config and replays the install so a plugin update reaches the project with a fresh agent-router.mjs and a freshly inlined judge prompt. Nothing installed -> it becomes an install and the level question is asked. Every mode is idempotent and re-runnable; foreign hook entries are never touched.
Config
Path: <project>/.claude/brewtools/agent-router.json.
{
"enabled": true,
"level": "fast",
"genericTypes": ["general-purpose", "worker"],
"neverFlag": ["Explore", "Plan", "statusline-setup", "output-style-setup"],
"minScore": 3,
"margin": 2,
"version": "X.Y.Z",
"content_version": "X.Y.Z",
"generated_by": "brewtools:agent-router-setup",
"last_updated": "YYYY-MM-DD"
}
| Key | Meaning |
|---|---|
enabled | only exactly false turns it off |
level | a record of what is wired; editing it by hand does not add/remove the tier-2 entry |
genericTypes | the spawn types policed at all |
neverFlag | never flagged regardless of task text |
minScore | minimum roster score before a project agent can win |
margin | how far the winner must lead the runner-up before it is a deny rather than a nudge |
intents | optional override of the built-in intent table — REPLACES it wholesale, does not merge. Adding one custom route silently drops the other three unless you copy DEFAULT_INTENTS out of agent-router.mjs first |
content_version | the PRIMARY staleness signal — the release whose generator logic produced this config. status compares it against assets/INSTALL.md’s own header, and separately compares the installed hook file’s header against the plugin’s asset copy; a mismatch on either pair means stale=yes |
version / generated_by / last_updated | version is INFORMATIONAL only — it names the release that last wrote the file, bumps on every release even with no shape change, and gets re-stamped by enable/disable without touching the hook file. Never read it as a staleness signal |
Note
enabled defaults true and only an exact false disables it — the opposite polarity from agent-deadline-setup (absent or anything but true means OFF). Do not assume the two opt-in hooks behave alike.
Version and ownership
install/upgrade/enable/disable/both level operations write agent-router.json’s version, content_version, generated_by: "brewtools:agent-router-setup" and last_updated at top level. agent-router.mjs and assets/INSTALL.md carry a matching brewcode-meta: content_version=X.Y.Z header.
Staleness is judged on content_version, never on version: status diffs the installed hook file’s header against the plugin’s asset copy, and the config’s content_version against the INSTALL.md header, and reports stale=yes on either mismatch. version alone proves nothing — a plain enable/disable re-stamps it to the current plugin while the hook file on disk stays untouched. Only a re-copy (install or upgrade) clears staleness.
Fails open, everywhere
Bad input, unparsable config, a missing roster, an unwritable tmp dir — every one of these lets the spawn through instead of blocking the session. A config that exists but fails to parse turns the whole feature off, not a fallback to defaults.
Related
Brewtools overview
All brewtools skills and agents in one place.
Agent Deadline Setup
Another opt-in installer hook for subagents — soft wall-clock budget instead of routing.
Setup Status
Read-only dashboard across every -setup skill — installed, stale or missing, with the hand-run command for each.
GitHub source
SKILL.md, the install runbook, the tier-1 hook and the tier-2 judge prompt.
Updating plugins
/brewtools:plugin-update to check and update the brewcode plugin suite in one command.
See the FAQ for details.