Skip to main content

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

FieldRequiredDefaultDescription
nameYes-Agent identifier (e.g., code-reviewer)
descriptionNo""What this agent does. Shown to the orchestrator
toolsNo[]List of tools the agent can use
modelNo"inherit"LLM model profile to load and use for the subagent ("inherit" uses the parent agent's model)
skillsNo[]List of skill names for this agent (see Skill Loading Precedence for resolution order).
max_iteration_per_runNoNoneMaximum iterations per run. Must be strictly positive, or None for the default value.
colorNoNoneRich color name (e.g., "blue", "green") used by visualizers to style this agent's output in terminal panels
mcp_serversNoNoneMCP server configurations for this agent (see MCP Servers)
hooksNoNoneHook configuration for lifecycle events (see Hooks)
permission_modeNoNoneControls how the subagent handles action confirmations (see Permission Mode)
profile_store_dirNoNoneCustom 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):

PriorityLocationScope
1{project}/.agents/agents/*.mdProject-level (primary)
2{project}/.faheem-code/agents/*.mdProject-level (secondary)
3~/.agents/agents/*.mdUser-level (primary)
4~/.faheem-code/agents/*.mdUser-level (secondary)
  • my-project/
    • .agents
      • agents
        • code-reviewer.md
        • tech-writer.md
        • security-auditor.md
    • src/
    • ...

Rules:

  • Only top-level .md files are loaded (subdirectories are skipped)
  • README.md files 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

AgentToolsDescription
general-purposeterminal, file_editor, task_trackerGeneral-purpose agent for tasks requiring a combination of capabilities. Used as the fallback when no agent name is specified.
code-explorerterminalRead-only codebase exploration agent. Finds files, searches code, reads source — never creates or modifies anything.
bash-runnerterminalCommand execution specialist. Runs shell commands, builds, tests, linters, and git operations. Returns concise reports instead of raw output.
web-researcherbrowser_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.

PrioritySourceDescription
1 (highest)Programmatic register_agent()Registered first, never overwritten
2Plugin agents (Plugin.agents)Loaded from plugin agents/ directories
3Project-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 Tool objects
  • Appends the Markdown body to the parent system message via AgentContext(system_message_suffix=...)
  • Respects the model field ("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 execution
  • user_prompt_submit — Runs before processing user messages
  • session_start / session_end — Run when conversation starts/ends
  • stop — 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 with file_

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:

ModeBehavior
always_confirmRequires user approval for all actions
never_confirmExecutes all actions without approval
confirm_riskyOnly 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 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.

Bring your own provider key
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
Faheem Code Cloud key
# 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