See the project root AGENTS.md for repository-wide policies and workflows.
This package (openhands.sdk.subagent) centralizes subagent discovery and registration.
It exists so that contributors (human or agentic) can answer:
- “Where did this agent come from?”
- “Why did this definition win over the other one?”
without reverse-engineering LocalConversation and the loader.
- File-based agents: Markdown files (
*.md) with YAML frontmatter. - Plugin agents:
Plugin.agents(already parsed by the plugin loader; registered here). - Programmatic agents:
register_agent(...)(highest precedence, never overwritten). - Built-in agents:
subagent/builtins/*.md(lowest precedence; used only as a fallback).
Relevant implementation files:
load.py: filesystem discovery + parse-error handling.schema.py: Markdown/YAML schema and parsing rules.registry.py: registry API + “first registration wins” semantics.conversation/impl/local_conversation.py: the call order that establishes precedence.
Project-level (higher priority than user-level):
{project}/.agents/agents/*.md{project}/.openhands/agents/*.md
User-level:
~/.agents/agents/*.md~/.openhands/agents/*.md
Notes:
- Only the top-level
*.mdfiles are scanned.- Subdirectories (e.g.
{project}/.agents/skills/…) are ignored.
- Subdirectories (e.g.
README.md/readme.mdis always skipped.- Directory iteration is deterministic (
sorted(dir.iterdir())).
If a single file fails to parse (invalid YAML frontmatter, malformed Markdown, etc.), loading must:
- log a warning (with stack trace), and
- continue scanning other files.
(See load_agents_from_dir in load.py.)
Once an agent name is registered in the global registry (_agent_factories), later
sources must not overwrite it.
This is enforced by using:
register_agent(...)(raises on duplicates; used for programmatic registration)register_agent_if_absent(...)(skips duplicates; used for plugins, file agents, builtins)
When a LocalConversation becomes ready, it establishes the following priority:
- Programmatic
register_agent(...)(pre-existing; must never be overwritten) - Plugin-provided agents (
Plugin.agents→register_plugin_agents) - Project file-based agents
{project}/.agents/agents/*.mdthen{project}/.openhands/agents/*.md
- User file-based agents
~/.agents/agents/*.mdthen~/.openhands/agents/*.md
- SDK built-ins (
subagent/builtins/*.md)
This is the order implemented by:
LocalConversation._ensure_plugins_loaded()→ registers plugin agentsLocalConversation._register_file_based_agents()→ registers project/user file agents, then built-ins
File-based loading has two layers of “first wins” deduplication:
- Within a level (
load_project_agents/load_user_agents):.agents/agentswins over.openhands/agentsfor the same agent name.
- Across levels (
register_file_agents):- project wins over user for the same agent name.
If you change these rules, update the unit tests in tests/sdk/subagent/.
Supported YAML frontmatter keys (see AgentDefinition.load in schema.py):
name(default: filename stem)descriptiontools(default:[])- accepts either a string (
tools: ReadTool) or a list
- accepts either a string (
model(default:inherit)inheritmeans “use the parent agent’s LLM instance”- any other string means “copy parent LLM and override the
modelfield”
color(optional)
Unknown keys are preserved in AgentDefinition.metadata.
The Markdown body content becomes the agent’s system_prompt.
Currently, when the agent is instantiated, this is applied as:
AgentContext(system_message_suffix=agent_def.system_prompt)
meaning it is appended to the parent system message (not a complete replacement).
tools values are stored as tool names (list[str]) and mapped at instantiation time to:
Tool(name=tool_name)
No validation is performed at load time beyond “stringification”.
The loader extracts <example>…</example> tags from description (case-insensitive)
into AgentDefinition.when_to_use_examples.
These examples are used for triggering / routing logic elsewhere.
---
name: code-reviewer
description: |
Reviews code changes.
<example>please review this PR</example>
<example>can you do a security review?</example>
tools:
- ReadTool
- GrepTool
model: inherit
color: purple
# Any extra keys are preserved in `metadata`:
audience: maintainers
---
You are a meticulous code reviewer.
Focus on correctness, security, and clear reasoning.User docs for Markdown agents live in the docs repo. If you change any of the invariants above, update both this file and the user docs.
- Docs PR tracking this feature: OpenHands/docs#358