Repository navigation
bug(agents): the agent roster is hardcoded in two places and has drifted 3 one way, 9 the other #14194
Description
Problem
The set of agents is declared in two places that cannot see each other, and they have
drifted apart in both directions:
.claude/agents/*.md— one definition per agent, carrying its model tierautobot-backend/agent_tier_classifier.py—AGENT_TIER_MAP, a hardcoded dict of agent
names carrying their prompt-cache tier
Two different axes, which is fine. The problem is that both hardcode the agent roster, and
nothing keeps the rosters in sync.
Measured against the current tree — 23 definitions on disk, 29 names in the map:
On disk, absent from the map (silently fall through to a default cache tier):
conversation-compacter
memory-curator
repo-sweeper
In the map, with no definition on disk (dead entries):
audio-processing backend-engineer code-generation data-analysis
image-analysis orchestrator sentiment-analysis
summarization translation
How it fails
Silently, in the direction that costs money. An agent missing from AGENT_TIER_MAP does not
error — it takes the default, so a newly added agent is cache-classified by accident rather
than by decision. Nothing reports it. The nine dead entries are the same defect aged: they
were presumably real agents once, and their removal never propagated.
This is the failure mode where an absent entry and a correct entry look identical from the
outside.
Disclosure
Two of the three unmapped agents (memory-curator, repo-sweeper) were added by me in
#14135 and #14139. I added agent definitions without knowing this second registry existed, and
so widened the drift. conversation-compacter predates that. I have deliberately not
guessed cache tiers for them: AgentTier is a cache-hit-rate classification whose correct
value depends on how much prefix an agent shares, which is a judgement the owner of that
module should make, not a value I should invent to make a count match.
Change
Make one side derivable from the other, or make the divergence loud:
- Preferred — derive the roster.
AGENT_TIER_MAPkeys come from the definitions on disk;
the module supplies only the cache-tier value per agent. One roster, one place to add an
agent. - Minimum — a guard test. A test that fails when a name exists in one source and not the
other, in both directions. Cheap, and it converts a silent default into a build failure. - Assign cache tiers for the three unmapped agents and retire (or restore) the nine dead
entries, whichever matches reality.
Option 2 is the one that must ship regardless; without it any fix regrows, which is the
pattern recorded for the canonical-source duplication umbrella.
Acceptance criteria
- Adding an agent definition cannot leave
AGENT_TIER_MAPsilently stale — enforced by a test, not convention - The test fails in both directions (on disk but unmapped; mapped but no definition)
-
conversation-compacter,memory-curator,repo-sweeperhave deliberate cache tiers - The nine dead entries are removed or their definitions restored, with the choice stated
- The test asserts the drift itself, not a hardcoded expected count that a future agent addition would simply update
Discovered while auditing #14135 / #14139 for regressions. Related: canonical-source
duplication umbrella #13916 (two implementations of one concept become one reused core).
Activity
fix(test): register the isolated types module in sys.modules so enum …
docs(test): name the real cause — dataclasses._is_type, not enum (#14194
fix(agents): sync AGENT_TIER_MAP with both agent rosters and add a dr…
github-actions commentedon Aug 17, 2026
PR #14389 (merged to
Dev_new_gui) references this issue with a close keyword.If this issue is fully resolved, close it manually. If work remains, no action is needed.
mrveiss commentedon Aug 17, 2026
Closed — landed in
Dev_new_gui.backend-engineergone fromAGENT_TIER_MAP; drift guard presentThis issue's own numbers were wrong, and the PR measured rather than trusted them. #14194 described 3 unmapped names and 9 dead entries. Measured reality:
backend-engineer(no code consumers; distinct from the realsenior-backend-engineer)AgentTypemembers that a disk-only scan of.claude/agents/*.mdcannot seeImplementing the issue as written would have deleted eight keys that 90 live
agent_type="..."call sites depend on.Two rosters, not one — and deliberately not merged.
AGENT_TIER_MAPis fed by.claude/agents/*.md(Claude Code dev subagents) andagents/agent_orchestration/types.py::AgentType(AutoBot's internal task-agent taxonomy). Collapsing them into one disk-derived source would have brokenorchestrator's tier-4 selection, which drives a functionally different base prompt. One canonical map fed by two sources, with a bidirectional drift guard, is the right shape when the rosters serve different systems.Key spelling is hyphenated on purpose.
AgentType.SYSTEM_COMMANDS.valueis"system_commands", butget_agent_tiernormalises_→-before lookup, so the map keys are dash-form and the guard test applies the same transform. The mismatch is the first thing a reviewer notices and the resolution is one function away.A CI failure during review was a genuine defect in the guard test, fixed here. The test loads
types.pyviaspec_from_file_locationto avoidagents/__init__.py's eager KB/LLM imports — correct — but executed it without registering it insys.modules.types.py:169declares a@dataclass, and withfrom __future__ import annotationsin play,dataclasses._is_typeresolves names viasys.modules.get(cls.__module__).__dict__(CPython 3.14dataclasses.py:814), which returnedNone. Reproduced standalone: without registrationAttributeError, with it OK. Now registers beforeexec_moduleand pops infinally, matchingrunner_deferral_test.pyandseed_agents_test.py.The general rule that fell out:
spec_from_file_location+exec_modulealone is safe only for a module declaring no dataclass.Follow-up not filed but worth noting: four doc references to
backend-engineersurvive (docs/developer/VLLM_PROMPT_OPTIMIZATION_INTEGRATION.md:232,239,469andREADME_ASYNC_VALIDATION.md:37), one inside a capabilities map, so those docs now name a key that does not exist.