File-based agents
A ready-to-run example is available here.
File-based agents let you define specialized sub-agents using Markdown files. Each file declares the agent's name, description, tools, and system prompt — the same things you'd pass to register_agent() in code, but without writing any Python.
This is the fastest way to create reusable, domain-specific agents that can be invoked via delegation.
Agent file format
An agent is a single .md file with YAML frontmatter and a Markdown body:
---
name: code-reviewer
description: >
Reviews code for quality, bugs, and best practices.
<example>Review this pull request for issues</example>
<example>Check this code for bugs</example>
tools:
- file_editor
- terminal
model: inherit
---
# Code Reviewer
You are a meticulous code reviewer. When reviewing code:
1. **Correctness** - Look for bugs, off-by-one errors, and race conditions.
2. **Style** - Check for consistent naming and idiomatic usage.
3. **Performance** - Identify unnecessary allocations or algorithmic issues.
4. **Security** - Flag injection vulnerabilities or hardcoded secrets.
Keep feedback concise and actionable. For each issue, suggest a fix.
The YAML frontmatter configures the agent. The Markdown body becomes the agent's system prompt.
Frontmatter fields
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | - | Agent identifier (e.g., code-reviewer) |
description | No | "" | What this agent does. Shown to the orchestrator |
tools | No | [] | List of tools the agent can use |
model | No | "inherit" | LLM model profile to load and use for the subagent ("inherit" uses the parent agent's model) |
skills | No | [] | List of skill names for this agent (see Skill Loading Precedence for resolution order). |
max_iteration_per_run | No | None | Maximum iterations per run. Must be strictly positive, or None for the default value. |
color | No | None | Rich color name (e.g., "blue", "green") used by visualizers to style this agent's output in terminal panels |
mcp_servers | No | None | MCP server configurations for this agent (see MCP Servers) |
hooks | No | None | Hook configuration for lifecycle events (see Hooks) |
permission_mode | No | None | Controls how the subagent handles action confirmations (see Permission Mode) |
profile_store_dir | No | None | Custom directory path for LLM profiles when using a named model |
<example> tags
Add <example> tags inside the description to help the orchestrating agent know when to delegate to this agent:
description: >
Writes and improves technical documentation.
<example>Write docs for this module</example>
<example>Improve the README</example>
These examples are extracted and stored as when_to_use_examples on the AgentDefinition object. They can be used by routing logic (or prompt-building) to help decide when to delegate to the right sub-agent.
Directory conventions
Place agent files in these directories, scanned in priority order (first match wins):
| Priority | Location | Scope |
|---|---|---|
| 1 | {project}/.agents/agents/*.md | Project-level (primary) |
| 2 | {project}/.faheem-code/agents/*.md | Project-level (secondary) |
| 3 | ~/.agents/agents/*.md | User-level (primary) |
| 4 | ~/.faheem-code/agents/*.md | User-level (secondary) |
- my-project/
- .agents
- agents
- code-reviewer.md
- tech-writer.md
- security-auditor.md
- agents
- src/
- ...
- .agents
Rules:
- Only top-level
.mdfiles are loaded (subdirectories are skipped) README.mdfiles are automatically skipped- Project-level agents take priority over user-level agents with the same name
Built-in agents
The faheemcode-tools package ships with built-in sub-agents as Markdown files in faheemcode/tools/preset/subagents/.
They can be registered via register_builtins_agents() and become available for delegation tasks.
By default, all agents include finish tool and the think tool.
Available built-in sub-agents
| Agent | Tools | Description |
|---|---|---|
| general-purpose | terminal, file_editor, task_tracker | General-purpose agent for tasks requiring a combination of capabilities. Used as the fallback when no agent name is specified. |
| code-explorer | terminal | Read-only codebase exploration agent. Finds files, searches code, reads source — never creates or modifies anything. |
| bash-runner | terminal | Command execution specialist. Runs shell commands, builds, tests, linters, and git operations. Returns concise reports instead of raw output. |
| web-researcher | browser_tool_set + MCP (fetch, tavily) | Web research specialist. Searches the web, navigates documentation, and extracts information from URLs. |
When enable_browser=False, browser-dependent agents like web-researcher are not registered.
Registering built-in sub-agents
Call register_builtins_agents() to register all built-in sub-agents. This is typically done once before creating a conversation:
from faheemcode.tools.preset.default import register_builtins_agents
# Register all built-in sub-agents (including web-researcher)
register_builtins_agents()
# Or without browser-dependent agents (excludes web-researcher)
register_builtins_agents(enable_browser=False)
Overall priority
When the same agent name is defined in multiple places, the highest-priority source wins. Registration is first-come first-win.
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | Programmatic register_agent() | Registered first, never overwritten |
| 2 | Plugin agents (Plugin.agents) | Loaded from plugin agents/ directories |
| 3 | Project-level file-based agents | .agents/agents/*.md or .faheem-code/agents/*.md |
| 4 (lowest) | User-level file-based agents | ~/.agents/agents/*.md or ~/.faheem-code/agents/*.md |
Auto-registration
The simplest way to use file-based agents is auto-registration. Call register_file_agents() with your project directory, and all discovered agents are registered into the delegation system:
from faheemcode.sdk.subagent import register_file_agents
agent_names = register_file_agents("/path/to/project")
print(f"Registered {len(agent_names)} agents: {agent_names}")
This scans both project-level and user-level directories, deduplicates by name, and registers each agent as a delegate that can be spawned by the orchestrator.
Manual loading
For more control, load and register agents explicitly:
from pathlib import Path
from faheemcode.sdk import load_agents_from_dir, register_agent, agent_definition_to_factory
# Load from a specific directory
agents_dir = Path("agents")
agent_definitions = load_agents_from_dir(agents_dir)
# Register each agent
for agent_def in agent_definitions:
register_agent(
name=agent_def.name,
factory_func=agent_definition_to_factory(agent_def),
description=agent_def.description,
)
Key functions
load_agents_from_dir()
Scans a directory for .md files and returns a list of AgentDefinition objects:
from pathlib import Path
from faheemcode.sdk import load_agents_from_dir
definitions = load_agents_from_dir(Path(".agents/agents"))
for d in definitions:
print(f"{d.name}: {d.tools}, model={d.model}")
agent_definition_to_factory()
Converts an AgentDefinition into a factory function (LLM) -> Agent:
from faheemcode.sdk import agent_definition_to_factory
factory = agent_definition_to_factory(agent_def)
# The factory is called by the delegation system with the parent's LLM
The factory:
- Maps tool names from the frontmatter to
Toolobjects - Appends the Markdown body to the parent system message via
AgentContext(system_message_suffix=...) - Respects the
modelfield ("inherit"keeps the parent LLM; an explicit model name creates a copy)
load_project_agents() / load_user_agents()
Load agents from project-level or user-level directories respectively:
from faheemcode.sdk.subagent import load_project_agents, load_user_agents
project_agents = load_project_agents("/path/to/project")
user_agents = load_user_agents() # scans ~/.agents/agents/ and ~/.faheem-code/agents/
Using with delegation
File-based agents are designed to work with the TaskToolSet. Once registered, the orchestrating agent can delegate tasks to them by name through the task tool's subagent_type parameter:
from faheemcode.sdk import Agent, Conversation, Tool
from faheemcode.sdk.subagent import register_file_agents
from faheemcode.tools.delegate import DelegationVisualizer
from faheemcode.tools.task import TaskToolSet
register_file_agents("/path/to/project") # Register .agents/agents/*.md
# Set up the orchestrator with the task tool
main_agent = Agent(
llm=llm,
tools=[Tool(name=TaskToolSet.name)],
)
conversation = Conversation(
agent=main_agent,
workspace="/path/to/project",
visualizer=DelegationVisualizer(name="Orchestrator"),
)
To learn more about agent delegation, follow our comprehensive guide.
Example agent files
Code reviewer
---
name: code-reviewer
description: >
Reviews code for quality, bugs, and best practices.
<example>Review this pull request for issues</example>
<example>Check this code for bugs</example>
tools:
- file_editor
- terminal
---
# Code Reviewer
You are a meticulous code reviewer. When reviewing code:
1. **Correctness** - Look for bugs, off-by-one errors, null pointer issues, and race conditions.
2. **Style** - Check for consistent naming, formatting, and idiomatic usage.
3. **Performance** - Identify unnecessary allocations, N+1 queries, or algorithmic inefficiencies.
4. **Security** - Flag potential injection vulnerabilities, hardcoded secrets, or unsafe deserialization.
Keep feedback concise and actionable. For each issue found, suggest a concrete fix.
Technical writer
---
name: tech-writer
description: >
Writes and improves technical documentation.
<example>Write docs for this module</example>
<example>Improve the README</example>
tools:
- file_editor
---
# Technical Writer
You are a skilled technical writer. When creating or improving documentation:
1. **Audience** - Write for developers who are new to the project.
2. **Structure** - Use clear headings, code examples, and step-by-step instructions.
3. **Accuracy** - Read the source code before documenting behavior. Never guess.
4. **Brevity** - Prefer short, concrete sentences over long explanations.
Always include a usage example with expected output when documenting functions or APIs.
Advanced features
MCP servers
File-based agents can define MCP server configurations inline, giving them access to external tools without any Python code:
---
name: web-researcher
description: Researches topics using web fetching capabilities.
tools:
- file_editor
mcp_servers:
fetch:
command: uvx
args:
- mcp-server-fetch
filesystem:
command: npx
args:
- -y
- "@modelcontextprotocol/server-filesystem"
---
You are a web researcher with access to fetch and filesystem tools.
Use the fetch tool to retrieve web content and save findings to files.
The mcp_servers field uses the same format as the MCP configuration — each key is a server name, and the value contains command and args for launching the server.
Environment variable resolution
All string values in MCP server configurations support ${VAR} (and $VAR) environment variable references, which are resolved from os.environ at load time. This lets you forward secrets and dynamic paths without hard-coding them in Markdown:
---
name: api-agent
description: Agent with MCP server using environment-based secrets.
mcp_servers:
my-server:
command: ${PLUGIN_ROOT}/bin/server
args:
- --config
- ${PLUGIN_ROOT}/config.json
env:
API_KEY: ${MY_API_KEY}
remote:
type: http
url: ${API_BASE}/mcp
headers:
Authorization: Bearer ${AUTH_TOKEN}
---
An agent that connects to MCP servers configured via environment variables.
Environment variable resolution applies recursively to all string fields — command, args, url, headers, env, and any other string values in the server config. If a referenced variable is not set, the placeholder is left unchanged (e.g., ${NONEXISTENT_VAR} stays as-is).
Hooks
File-based agents can define lifecycle hooks that run at specific points during execution:
---
name: audited-agent
description: An agent with audit logging hooks.
tools:
- terminal
- file_editor
hooks:
pre_tool_use:
- matcher: "terminal"
hooks:
- command: "./scripts/validate_command.sh"
timeout: 10
post_tool_use:
- matcher: "*"
hooks:
- command: "./scripts/log_tool_usage.sh"
timeout: 5
---
You are an audited agent. All your actions are logged for compliance.
Hook event types:
pre_tool_use— Runs before tool execution (can block with exit code 2)post_tool_use— Runs after tool executionuser_prompt_submit— Runs before processing user messagessession_start/session_end— Run when conversation starts/endsstop— Runs when agent tries to finish (can block)
Each hook matcher supports:
"*"— Matches all tools- Exact name — e.g.,
"terminal"matches only that tool - Regex patterns — e.g.,
"/file_.*/"matches tools starting withfile_
For more details on hooks, see the Hooks guide.
Permission mode
Control how a file-based agent handles action confirmations with the permission_mode field:
---
name: autonomous-agent
description: Runs without requiring user confirmation.
tools:
- terminal
- file_editor
permission_mode: never_confirm
---
You are an autonomous agent that executes tasks without manual approval.
Available modes:
| Mode | Behavior |
|---|---|
always_confirm | Requires user approval for all actions |
never_confirm | Executes all actions without approval |
confirm_risky | Only requires approval for actions above a risk threshold (requires a security analyzer) |
When permission_mode is omitted (or set to None), the subagent inherits the confirmation policy from its parent conversation.
For more details on security and confirmation policies, see the Security guide.
Agents in plugins
Plugins bundle agents, tools, skills, and MCP servers into reusable packages. Learn more about plugins here.
File-based agents can also be bundled inside plugins. Place them in the agents/ directory of your plugin:
- my-plugin/
- .plugin
- plugin.json
- agents
- code-reviewer.md
- tech-writer.md
- .plugin
Plugin agents use the same .md format and are registered automatically when the plugin is loaded. They have higher priority than file-based agents but lower than programmatic register_agent() calls.
Ready-to-run example
This example uses AgentDefinition directly. File-based agents are loaded into the same AgentDefinition objects (from Markdown) and registered the same way.
"""Example: Defining a sub-agent inline with AgentDefinition.
Defines a grammar-checker sub-agent using AgentDefinition, registers it,
and delegates work to it from an orchestrator agent. The orchestrator then
asks the builtin default agent to judge the results.
"""
import os
from pathlib import Path
from faheemcode.sdk import (
LLM,
Agent,
Conversation,
Tool,
agent_definition_to_factory,
register_agent,
)
from faheemcode.sdk.subagent import AgentDefinition
from faheemcode.tools.delegate import DelegationVisualizer
from faheemcode.tools.task import TaskToolSet
# 1. Define a sub-agent using AgentDefinition
grammar_checker = AgentDefinition(
name="grammar-checker",
description="Checks documents for grammatical errors.",
tools=["file_editor"],
system_prompt="You are a grammar expert. Find and list grammatical errors.",
)
# 2. Register it in the delegate registry
register_agent(
name=grammar_checker.name,
factory_func=agent_definition_to_factory(grammar_checker),
description=grammar_checker.description,
)
# 3. Set up the orchestrator agent with the task tool
llm = LLM(
model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL"),
usage_id="file-agents-demo",
)
main_agent = Agent(
llm=llm,
tools=[Tool(name=TaskToolSet.name)],
)
conversation = Conversation(
agent=main_agent,
workspace=Path.cwd(),
visualizer=DelegationVisualizer(name="Orchestrator"),
)
# 4. Ask the orchestrator to delegate to our agent
task = (
"Please delegate to the grammar-checker agent and ask it to review "
"the README.md file in search of grammatical errors.\n"
"Then ask the default agent to judge the errors."
)
conversation.send_message(task)
conversation.run()
cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost
print(f"\nTotal cost: ${cost:.4f}")
print(f"EXAMPLE_COST: {cost:.4f}")
You can run the example code as-is.
export LLM_API_KEY="your-api-key"
export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" # or openai/gpt-4o, etc.
cd software-agent-sdk
uv run python examples/01_standalone_sdk/42_file_based_subagents.py
# https://app.faheemcode.ai/settings/api-keys
export LLM_API_KEY="example-user-api-key"
export LLM_MODEL="faheemcode/claude-sonnet-4-5-20250929"
cd software-agent-sdk
uv run python examples/01_standalone_sdk/42_file_based_subagents.py
Next steps
- TaskToolSet - Delegate work to specialized sub-agents
- Skills - Add specialized knowledge and triggers to agents
- Plugins - Bundle agents, skills, hooks, and MCP servers together
- Custom Agent - Create agents programmatically for more control