Skip to content

fix(agent): discover custom subagents from .claude/agents/ - #151

Merged
ericleepi314 merged 1 commit into
mainfrom
fix/custom-subagent-discovery
May 16, 2026
Merged

fix(agent): discover custom subagents from .claude/agents/#151
ericleepi314 merged 1 commit into
mainfrom
fix/custom-subagent-discovery

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

  • Ports the TypeScript custom-agent discovery layer (loadAgentsDir.ts, markdownConfigLoader.ts, loadPluginAgents.ts) to the Python port so on-disk subagents are visible to the Agent tool, the @agent-… mention resolver, and the tool prompt the model sees.
  • Fixes the original symptom: a user adding ~/.claude/agents/critic.md no longer gets Unknown subagent_type: critic. Available: general-purpose, Explore, Plancritic is now discovered and selectable.
  • Full parity: managed/user/project sources, plugin agents (namespaced as plugin:sub:dirs:base with elevated capabilities stripped), every frontmatter field, MCP-requirement filtering at both prompt-build and per-call dispatch, realpath-keyed cache. Project walk stops at the nearest .git ancestor so parent-of-repo agents don't leak.

Why

The consumer side (Agent tool resolver, _available_agents for @agent-… mentions) was wired in to read context.options.agent_definitions["active_agents"], but nothing in the Python tree ever populated that dict and no loader existed. The producer was simply missing.

What's in the diff

New modules (under src/):

  • utils/frontmatter_validators.py — fail-open validators for effort, positive_int, permission_mode, string_list, hooks
  • utils/markdown_config_loader.py — generic .claude/<subdir> walker with git-root boundary, dedup-by-realpath
  • agent/parse_agent_markdown.py — frontmatter → AgentDefinition, supports kebab + camelCase keys, rejects non-string name
  • agent/load_agents_dir.py — discovery orchestrator with last-wins merge [built-in, plugin, user, project, managed]; cached on os.path.realpath(cwd)
  • agent/load_plugin_agents.py — recursive walk, namespace plugin:sub:dirs:base, strips permission_mode/hooks/mcp_servers
  • agent/filter_agents_by_mcp.py — case-insensitive substring filter; built-ins exempt

Wire-up edits:

  • agent/agent_definitions.pyAgentSource literal extended with "project"/"managed" so policy-vs-user is distinguishable
  • tool_system/tools/agent.py_get_agent_definitions and _agent_prompt call the loader; make_agent_tool accepts a get_available_mcp_servers closure for prompt-time filtering
  • tool_system/defaults.py — threads the MCP closure through build_default_registry
  • repl/core.py_available_agents no longer flattens a wrapping dict (the old code extended a list with dict.values(), producing a single nested list that the mention resolver silently dropped); registry build passes a late-binding closure reading tool_context.mcp_clients
  • plugins/types.py + plugins/loader.pyLoadedPlugin.agents_paths read from manifest agentsPath/agentsPaths

Test plan

  • 31 new tests in tests/agent/ cover: user/project/managed discovery, project-over-user override, built-in override, managed-wins-over-project, malformed-frontmatter isolation, MCP filter, cache invalidation + realpath dedup, git-root boundary, @agent-<custom> mention resolution, plugin namespace collision, plugin trust stripping, parser camelCase aliases, non-string-name fallback, all-fields mapping, _available_agents flat-list contract.
  • 293 prior agent/skills/plugin tests still pass (tests/test_agent_*.py, tests/test_skills_*.py, tests/test_plugin_loader.py).
  • End-to-end smoke: writing ~/.claude/agents/critic.md (via CLAUDE_CONFIG_DIR override) makes critic appear in get_agent_definitions_with_overrides(), the rendered Agent tool prompt, and expand_agent_mentions(\"@agent-critic …\") resolution.
  • Manual: drop a real ~/.claude/agents/<name>.md and exercise via the live REPL to confirm the model sees the agent in the tool description.

Review

Reviewed by `critic` subagent over two rounds: initial REQUEST CHANGES (2 blockers + 5 majors), all addressed, then APPROVE. Four nit follow-ups also applied.

🤖 Generated with Claude Code

The Python port had the consumer side wired in (Agent tool resolver,
@agent- mention expansion) but no producer — no markdown loader, no
frontmatter parser, no directory walker, no plugin agent surface, no
MCP-requirement filter. A user dropping ~/.claude/agents/critic.md
got "Unknown subagent_type: critic. Available: general-purpose,
Explore, Plan" because get_built_in_agents() was the only source.

Port loadAgentsDir.ts + markdownConfigLoader.ts + loadPluginAgents.ts
to Python with full parity: managed/user/project sources, plugin
agents (namespaced as plugin:sub:dirs:base, with elevated capabilities
stripped), every frontmatter field, MCP-requirement filtering at both
prompt-build and per-call dispatch, realpath-keyed cache. Project walk
stops at the nearest .git ancestor so parent-of-repo agents don't leak.

31 new tests; 293 prior tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ericleepi314
ericleepi314 merged commit 4406b78 into main May 16, 2026
singlaamitesh pushed a commit to singlaamitesh/clawcodex that referenced this pull request Jul 7, 2026
…bagent-discovery

fix(agent): discover custom subagents from .claude/agents/
singlaamitesh pushed a commit to singlaamitesh/clawcodex that referenced this pull request Jul 7, 2026
- Codebase stats: 894 files / 177,428 lines (up from 167,034 on
  2026-05-14; ~+10.4k lines in two days).
- New news entries:
  * 2026-05-16 Image-handling parity (agentforce314#149/agentforce314#154/agentforce314#155/agentforce314#156) — Read tool
    TS image pipeline; @image.png mention fix; image tool_result list
    shape preserved; OpenAI-compat image/document block translation;
    validate_images_for_api promoted into BaseProvider._prepare_messages;
    BOM-aware text decoding.
  * 2026-05-16 Subagent + Bash reliability — custom subagent discovery
    from .claude/agents/ (agentforce314#151); Bash timeout vs ESC-abort distinction
    (agentforce314#152); async-subagent AbortController isolation tests (agentforce314#153);
    REJECT_MESSAGE on cancelled production path (agentforce314#150).
  * 2026-05-15 to 2026-05-16 ESC cancellation hardening across providers
    (agentforce314#144agentforce314#148) — mid-stream cancel under ~50ms for every provider;
    shared StreamAbortGuard; LiteLLM worker-thread iteration fix.
- Core Systems table:
  * Multi-Provider description extends to call out Anthropic→OpenAI
    image/document block translation.
  * New "Cancellation / Abort" row (✅) capturing the recent sweep.
  * New "Image Handling" row (✅) capturing Tier C parity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant