Skip to content

bug(agents): the agent roster is hardcoded in two places and has drifted 3 one way, 9 the other #14194

Closed

Description

@mrveiss

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 tier
  • autobot-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:

  1. Preferred — derive the roster. AGENT_TIER_MAP keys come from the definitions on disk;
    the module supplies only the cache-tier value per agent. One roster, one place to add an
    agent.
  2. 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.
  3. 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_MAP silently 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-sweeper have 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

  1. github-actions commented on Aug 17, 2026

    @github-actions
    Contributor

    PR #14389 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(agents): sync AGENT_TIER_MAP with both agent rosters and add a drift guard (#14194)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  2. mrveiss commented on Aug 17, 2026

    @mrveiss
    OwnerAuthor

    Closed — landed in Dev_new_gui.

    Artifact Value
    PR #14389 (squash-merged)
    Verified in base backend-engineer gone from AGENT_TIER_MAP; drift guard present
    Checks 10/10 required green, all 12 python-suite shards SUCCESS, 66 checks, 0 failures

    This 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:

    • 1 genuine orphan: backend-engineer (no code consumers; distinct from the real senior-backend-engineer)
    • 8 unmapped, not 3
    • 8 of the 9 "dead" entries were alive — live AgentType members that a disk-only scan of .claude/agents/*.md cannot see

    Implementing 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_MAP is fed by .claude/agents/*.md (Claude Code dev subagents) and agents/agent_orchestration/types.py::AgentType (AutoBot's internal task-agent taxonomy). Collapsing them into one disk-derived source would have broken orchestrator'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.value is "system_commands", but get_agent_tier normalises _ → - 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.py via spec_from_file_location to avoid agents/__init__.py's eager KB/LLM imports — correct — but executed it without registering it in sys.modules. types.py:169 declares a @dataclass, and with from __future__ import annotations in play, dataclasses._is_type resolves names via sys.modules.get(cls.__module__).__dict__ (CPython 3.14 dataclasses.py:814), which returned None. Reproduced standalone: without registration AttributeError, with it OK. Now registers before exec_module and pops in finally, matching runner_deferral_test.py and seed_agents_test.py.

    The general rule that fell out: spec_from_file_location + exec_module alone is safe only for a module declaring no dataclass.

    Follow-up not filed but worth noting: four doc references to backend-engineer survive (docs/developer/VLLM_PROMPT_OPTIMIZATION_INTEGRATION.md:232,239,469 and README_ASYNC_VALIDATION.md:37), one inside a capabilities map, so those docs now name a key that does not exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions