Agent-zero a0-development
Development guide for extending and building features for the Agent Zero AI framework. Covers architecture, tools, extensions, API endpoints, agent profiles, projects, prompts, and skills — with correct paths, imports, and patterns matching the current codebase.
git clone https://github.com/agent0ai/agent-zero
T=$(mktemp -d) && git clone --depth=1 https://github.com/agent0ai/agent-zero "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/a0-development" ~/.claude/skills/agent0ai-agent-zero-a0-development && rm -rf "$T"
skills/a0-development/SKILL.mdAgent Zero Development Guide
This skill provides comprehensive, accurate guidance for extending and building features for Agent Zero. Use it when you need to:
- Understand the architecture and project layout
- Create new Tools for agent capabilities
- Add Extensions to hook into the framework lifecycle
- Build API Endpoints for the Web UI
- Create Agent Profiles (subordinates) with custom prompts
- Understand and extend the Prompt System
- Create Skills (see the dedicated
skill for the full wizard)create-skill - Work with Projects and workspace configuration
Path convention: Throughout this guide,
refers to the framework root — this is/a0/inside Docker, or your local repository root in development. All paths are relative to this root./a0/
[!IMPORTANT] Plugins are the primary way to extend Agent Zero. Most new tools, extensions, and prompts should be packaged as plugins. For all plugin tasks (create, review, manage, debug, contribute), load the
skill which routes to the appropriate specialist. This guide covers the underlying framework patterns that plugins build upon.a0-plugin-router
Related skills:
a0-plugin-router (plugin tasks) | create-skill (skill creation wizard) | a0-create-plugin | a0-review-plugin | a0-manage-plugin | a0-contribute-plugin | a0-debug-plugin
Architecture Overview
Project Layout
/a0/ # Framework root ├── agent.py # Core Agent + AgentContext + AgentConfig classes ├── initialize.py # Agent initialization logic ├── models.py # Model definitions ├── run_ui.py # Web UI entry point │ ├── tools/ # Core tools (search, response, browser, etc.) ├── extensions/ │ ├── python/ # Python lifecycle extensions │ │ ├── <hook_point>/ # e.g., agent_init/, system_prompt/, etc. │ │ │ └── _NN_name.py # Numbered extension files │ │ └── _functions/ # Implicit @extensible decorator extensions │ └── webui/ # JavaScript WebUI extensions │ └── <hook_point>/ # e.g., json_api_call_before/ │ └── name.js ├── api/ # Flask API endpoint handlers ├── helpers/ # Framework utilities and base classes │ ├── tool.py # Tool + Response base classes │ ├── extension.py # Extension base class + @extensible decorator │ ├── api.py # ApiHandler base class │ ├── files.py # File operations + prompt reading │ ├── plugins.py # Plugin system manager │ ├── print_style.py # Console output formatting │ └── ... # Many more utility modules │ ├── prompts/ # Core prompt fragments (system, tools, framework) ├── agents/ # Agent profiles (subordinate specializations) │ ├── default/ # Base profile (inherited by others) │ ├── agent0/ # Main user-facing agent │ ├── developer/ # Developer subordinate │ ├── hacker/ # Security subordinate │ ├── researcher/ # Research subordinate │ └── _example/ # Example profile with tool + extension samples │ ├── plugins/ # Core plugins (tools, extensions, prompts) │ ├── _code_execution/ # Terminal/Python/Node.js execution │ ├── _memory/ # Persistent memory system │ ├── _text_editor/ # File read/write/patch │ ├── _model_config/ # LLM model selection │ ├── _infection_check/ # Prompt injection safety │ └── ... # More core plugins │ ├── skills/ # Core skills (SKILL.md bundles) ├── knowledge/ # Knowledge base files ├── webui/ # Web UI frontend ├── docs/ # Documentation │ └── usr/ # User-space (survives updates) ├── agents/ # User-created agent profiles ├── plugins/ # User-installed plugins ├── skills/ # User-created skills ├── knowledge/ # User knowledge base files ├── extensions/ # Standalone user extensions (created on demand; prefer plugins instead) ├── projects/ # Project workspaces (created on demand when user adds projects via UI) └── workdir/ # Default working directory
Key Architecture Patterns
- Plugin-first design — Most capabilities (tools, extensions, prompts) are delivered via plugins in
(core) or/a0/plugins/
(user)./a0/usr/plugins/ - Extensions execute in numeric order — Files named
,_10_*.py
, etc. run sequentially within each hook point._20_*.py - Tools inherit from
— All tools implement theTool
method returning aexecute()
.Response - Shared
— Enables state persistence across agents in a conversation.AgentContext - Async/await throughout — All tool execution, extensions, and API handlers are async.
- Prompt fragments compose — System prompts are assembled from named fragments with includes and variable substitution.
- Profile inheritance — Agent profiles inherit from
and override specific prompt fragments.default/ - User-space separation — Everything under
survives framework updates./a0/usr/
Agent Loop
The core execution cycle works as follows:
- User message arrives (via UI or API)
- System prompt assembly — prompt fragments are composed with includes and variable substitution
- LLM call — the assembled prompt + conversation history is sent to the model
- Response parsing — the framework parses the LLM response looking for JSON tool calls
- Tool execution — if tool calls are found, each tool's
method is called and the result is appended to historyexecute() - Loop continues — steps 3-5 repeat until the agent produces a
tool call (which ends the loop) or a loop limit is reachedresponse
Extensions fire at each stage (e.g.,
monologue_start, before_main_llm_call, tool_execute_before, etc.), allowing plugins to observe and modify behavior at every point.
Creating Tools
Tools are how agents interact with the world. Each tool inherits from the
Tool base class.
Import Path
from helpers.tool import Tool, Response
Tool Base Class
# /a0/helpers/tool.py @dataclass class Response: message: str # Text response shown to agent break_loop: bool # True = stop agent message loop additional: dict[str, Any] | None = None # Extra metadata for history class Tool: def __init__(self, agent: Agent, name: str, method: str | None, args: dict[str,str], message: str, loop_data: LoopData | None, **kwargs) -> None: self.agent = agent self.name = name self.method = method # For tools with sub-methods (e.g., "skills_tool:load") self.args = args self.loop_data = loop_data self.message = message async def execute(self, **kwargs) -> Response: pass # Override this # Lifecycle hooks (called automatically): async def before_execution(self, **kwargs): ... async def after_execution(self, response: Response, **kwargs): ...
Where Tools Live
| Location | Purpose |
|---|---|
| Core framework tools (search, response, call_subordinate, etc.) |
| Plugin-provided tools (code_execution, memory, text_editor) |
| Profile-specific tool overrides |
| User plugin tools |
Example: Creating a Tool
Based on the actual
_example profile in /a0/agents/_example/tools/example_tool.py:
# my_tool.py from helpers.tool import Tool, Response class MyTool(Tool): async def execute(self, **kwargs): # Get arguments — kwargs contains the tool_args from the agent's JSON input_data = kwargs.get("input", "") # Do something result = f"Processed: {input_data}" # Return response return Response( message=result, # Shown to the agent break_loop=False, # Don't stop the agent loop )
[!IMPORTANT] Every tool needs a corresponding prompt fragment so the agent knows how to use it. Create a file named
in the appropriateagent.system.tool.<tool_name>.mddirectory. See the Prompt System section.prompts/
Tool Best Practices
- Always handle errors gracefully — return error messages in
, don't crashResponse - Access agent context via
self.agent.context - Use
to support sub-methods (e.g.,self.method
,my_tool:action1
)my_tool:action2 - Use
to read arguments with defaultskwargs.get() - For long operations, use
orself.set_progress()
to show statusself.add_progress() - Access
for loop state (iteration count, timing, etc.) — this is theself.loop_data
instance passed during tool dispatchLoopData
Creating Extensions
Extensions hook into specific lifecycle points in the agent framework.
Import Path
from helpers.extension import Extension
Extension Base Class
class Extension: def __init__(self, agent: "Agent | None", **kwargs): self.agent: "Agent | None" = agent self.kwargs = kwargs def execute(self, **kwargs) -> None | Awaitable[None]: pass # Override this — kwargs are hook-point-specific
Extensions can be sync or async. If
returns anexecute(), the framework willAwaitableit automatically. Theawaitparameter is nullable because some hook points (likeagentorstartup_migration) fire before an agent exists.banners
Extension File Location
Extensions live in directories named by their hook point. The path structure is:
extensions/python/<hook_point>/_NN_name.py
Where
_NN_ is a numeric prefix controlling execution order (e.g., _10_, _20_, _50_).
| Source | Path |
|---|---|
| Core extensions | |
| Plugin extensions | |
| User extensions | |
| Agent profile extensions | |
| User plugin extensions | |
Python Extension Hook Points
Complete list of available hook points:
| Hook Point | When It Fires | Common Use |
|---|---|---|
| Agent is initialized | Load configs, set defaults |
| System prompt is being assembled | Inject prompt content |
| Agent monologue begins | Pre-processing, state setup |
| Before message processing loop | Pre-loop setup |
| Before prompt assembly in loop | Modify prompt inputs |
| After prompt assembly in loop | Add context (memory recall lives here) |
| Before the LLM API call | Modify prompts, add context |
| Before utility model calls | Modify utility prompts |
| When response streaming begins | Initialize stream handlers |
| Per response chunk received | Transform output, collect data |
| Response streaming complete | Finalize, analyze full response |
| Reasoning/thinking stream begins | Monitor reasoning |
| Per reasoning chunk | Collect reasoning data |
| Reasoning stream complete | Analyze reasoning |
| Before a tool runs | Validation, logging, safety checks |
| After a tool runs | Post-process results |
| Before adding to history | Modify history entries |
| After tool result added to history | Log tool results |
| After message processing loop | Post-loop cleanup |
| Agent monologue complete | Memorization, cleanup |
| Entire processing chain done | Final cleanup |
| Background job loop tick | Periodic background tasks |
| Error is being formatted | Custom error messages |
| Framework startup | Data migrations |
| Startup banners displayed | Add custom banners |
| Embedding model changed | Reload vector stores (fired programmatically, not a directory-based hook) |
| User message from UI | Pre-process user input |
| WebSocket client connects | Session setup |
| WebSocket client disconnects | Session cleanup |
| WebSocket event received | Handle custom WS events |
The @extensible
Decorator (Implicit Extension Points)
@extensibleAny framework function decorated with
@extensible automatically gets two extension points:
_functions/<module_path>/<qualname_path>/start _functions/<module_path>/<qualname_path>/end
The path mapping converts Python module paths and qualified names using
/ separators:
- Module
→agent.pyagent - Class method
→Agent.handle_exceptionAgent/handle_exception - Full path:
_functions/agent/Agent/handle_exception/start
For nested modules like
helpers.history, a method History.add would map to _functions/helpers/history/History/add/start.
For example, a function
Agent.handle_exception in module agent creates:
_functions/agent/Agent/handle_exception/start_functions/agent/Agent/handle_exception/end
Extensions in these directories receive a
data dict with:
— positional args (mutable)data["args"]
— keyword args (mutable)data["kwargs"]
— set this to short-circuit the functiondata["result"]
— set to adata["exception"]
to force-raiseBaseException
This is used by plugins like
_error_retry to wrap core agent methods.
WebUI Extensions (JavaScript)
Client-side extensions live under
extensions/webui/<hook_point>/:
| Hook Point | When It Fires |
|---|---|
| Before a JSON API request |
| After a JSON API response |
| Before a fetch API request |
| After a fetch API response |
| Register custom message renderers |
| Before messages are rendered |
| After messages are rendered |
| WebSocket push to client |
Example: Creating an Extension
Based on the actual
_example profile in /a0/agents/_example/extensions/agent_init/_10_example_extension.py:
# extensions/python/agent_init/_15_my_extension.py from helpers.extension import Extension class MyExtension(Extension): async def execute(self, **kwargs): # Access the agent agent = self.agent context = agent.context # Extension logic — kwargs content depends on the hook point agent.agent_name = "CustomAgent" + str(agent.number)
Extension Execution Order
Extensions execute in numeric order based on filename prefix:
_10_first.py # Runs first _20_second.py # Runs second _50_third.py # Runs third
Use 10-number increments to leave room for future extensions.
Creating API Endpoints
API endpoints serve the Web UI and external clients using Flask.
Import Path
from helpers.api import ApiHandler from flask import Request, Response
ApiHandler Base Class
class ApiHandler: def __init__(self, app: Flask, thread_lock: ThreadLockType): self.app = app self.thread_lock = thread_lock # Override these class methods to configure behavior: @classmethod def requires_loopback(cls) -> bool: return False # Restrict to localhost @classmethod def requires_api_key(cls) -> bool: return False # Require API key @classmethod def requires_auth(cls) -> bool: return True # Require auth session @classmethod def get_methods(cls) -> list[str]: return ["POST"] # HTTP methods @classmethod def requires_csrf(cls) -> bool: return cls.requires_auth() # CSRF protection # Implement this: async def process(self, input: dict, request: Request) -> dict | Response: pass # Utility: get or create an agent context def use_context(self, ctxid: str, create_if_not_exists: bool = True) -> AgentContext: ...
Where API Endpoints Live
| Location | Purpose |
|---|---|
| Core API endpoints |
| Plugin API endpoints |
| User plugin API endpoints |
Endpoints are auto-discovered by filename. The route is derived from the filename (e.g.,
my_endpoint.py -> /api/my_endpoint).
Example: API Endpoint
# api/my_endpoint.py from helpers.api import ApiHandler from flask import Request, Response from agent import AgentContext class MyEndpoint(ApiHandler): @classmethod def get_methods(cls) -> list[str]: return ["GET", "POST"] async def process(self, input: dict, request: Request) -> dict: param = input.get("param", "default") # Get or create agent context ctxid = input.get("context", "") context = self.use_context(ctxid) return { "result": f"processed {param}", "context": context.id, }
Creating Agent Profiles
Agent profiles define specialized subordinates with custom prompts and behaviors.
Profile Directory Structure
agents/<profile-name>/ +-- agent.yaml # Required: profile metadata +-- prompts/ # Optional: prompt overrides | +-- agent.system.main.role.md # Role definition (most common override) | +-- agent.system.main.communication.md # Communication style | +-- agent.system.tool.<name>.md # Tool-specific prompts +-- tools/ # Optional: profile-specific tools | +-- my_tool.py +-- extensions/ # Optional: profile-specific extensions +-- <hook_point>/ +-- _NN_extension.py
agent.yaml Format
The actual format is simple YAML with only three fields:
title: Developer description: Agent specialized in complex software development. context: Use this agent for software development tasks, including writing code, debugging, refactoring, and architectural design.
| Field | Purpose |
|---|---|
| Display name shown in UI and agent selection |
| Brief description of the agent's specialization |
| Instructions for when to delegate to this profile |
[!NOTE] There is no per-profile model configuration, temperature, or allowed_tools in the profile YAML. Model configuration is managed by the
plugin. Tool availability is controlled by plugin activation._model_config
Where Profiles Live
| Location | Purpose |
|---|---|
| Core profiles (default, agent0, developer, hacker, researcher) |
| User-created profiles (survives updates) |
Prompt Override Mechanism
Profiles inherit all prompts from the
default/ profile. To customize behavior, place prompt files with the same name in your profile's prompts/ directory. The framework searches profile-specific prompts first, then falls back to the default.
The most common override is
agent.system.main.role.md which defines the agent's role and specialization.
Example: Creating a Profile
# /a0/usr/agents/data-analyst/agent.yaml title: Data Analyst description: Agent specialized in data analysis, visualization, and statistical modeling. context: Use this agent for data analysis tasks, creating visualizations, statistical analysis, and working with datasets in Python.
<!-- /a0/usr/agents/data-analyst/prompts/agent.system.main.role.md --> ## Your role You are a specialized data analysis agent. Your expertise includes: - Python data analysis (pandas, numpy, scipy) - Data visualization (matplotlib, seaborn, plotly) - Statistical modeling and hypothesis testing - SQL queries and database analysis - Data cleaning and preprocessing ## Process 1. Understand the data and the question 2. Choose appropriate tools and methods 3. Execute analysis with code_execution_tool 4. Visualize results when applicable 5. Provide clear interpretation of findings
Reference: The _example
Profile
_exampleThe framework includes a complete example profile at
/a0/agents/_example/ that demonstrates:
- Custom tool:
/a0/agents/_example/tools/example_tool.py - Custom extension:
/a0/agents/_example/extensions/agent_init/_10_example_extension.py - Tool prompt:
/a0/agents/_example/prompts/agent.system.tool.example_tool.md - Role prompt:
/a0/agents/_example/prompts/agent.system.main.role.md
Prompt System
Agent Zero assembles system prompts from named fragments using includes and variable substitution.
Prompt File Naming Convention
Prompt files follow a dot-separated naming scheme:
agent.system.main.md # Main system prompt (entry point) agent.system.main.role.md # Role definition agent.system.main.communication.md # Communication style agent.system.tool.<name>.md # Tool usage instructions agent.system.tools.md # Tools overview agent.system.projects.main.md # Project system agent.system.secrets.md # Secret handling agent.system.skills.md # Skills listing agent.system.datetime.md # Current date/time agent.context.extras.md # Context extras fw.*.md # Framework messages (errors, hints, etc.)
Where Prompts Live
| Location | Priority | Purpose |
|---|---|---|
| Highest | Profile-specific overrides |
| High | User profile overrides |
| Normal | Plugin-provided prompts |
| Normal | User plugin prompts |
| Base | Core framework prompts |
The framework searches directories in priority order and uses the first match found.
Include Mechanism
Prompts can include other fragments using double-brace
include directives.
The syntax uses opening double-brace, the keyword, and closing double-brace:
| Directive | Purpose |
|---|---|
| Include a named prompt fragment |
| Include another fragment |
| Include the same file from the next lower-priority directory |
The
include original directive is particularly useful for extending rather than fully replacing a prompt — your override can include the base version and add to it.
Variable Substitution
Prompts support
{{variable_name}} placeholders that are replaced at render time with values passed from the framework or plugin configuration.
Conditional Blocks
Prompts support conditional rendering based on variables.
Reading Prompts in Code
# From within an Agent method: content = self.read_prompt("fw.some_message.md", variable1="value1") # From helpers: from helpers.files import read_prompt_file content = read_prompt_file("template.md", _directories=[...], var="value")
Creating Skills
Skills are reusable instruction bundles that the agent loads on demand via the
skills_tool. Each skill lives in a directory containing a SKILL.md file with YAML frontmatter.
| Location | Purpose |
|---|---|
| Core skills (shipped with framework) |
| User-created skills (survives updates) |
The agent interacts with skills through JSON tool calls:
{"tool_name": "skills_tool:list", "tool_args": {}} {"tool_name": "skills_tool:load", "tool_args": {"skill_name": "my-skill"}}
For the complete skill creation wizard — including SKILL.md format, frontmatter fields, directory structure, best practices, and examples — load the
skill.create-skill
Working with Projects
Projects provide isolated workspaces with custom configuration.
Projects are typically created and managed via the Web UI. The
directory and.a0proj/are auto-generated when you create a project through the UI.project.json
Project Structure
/a0/usr/projects/<project-name>/ +-- .a0proj/ | +-- project.json # Project configuration | +-- agents.json # Per-project agent overrides | +-- variables.env # Non-sensitive variables | +-- secrets.env # Encrypted secrets | +-- memory/ # Project-specific memory | +-- index.faiss | +-- index.pkl | +-- embedding.json +-- <project-files>/ # Your project files (working directory)
project.json Format
{ "title": "My Project", "description": "Project description", "instructions": "Markdown instructions for the agent when this project is active", "color": "#3a86ff", "git_url": "", "memory": "own", "file_structure": { "enabled": true, "max_depth": 5, "max_files": 20, "max_folders": 20, "max_lines": 250, "gitignore": ".a0proj/\nvenv/\n**/__pycache__/\n**/node_modules/\n**/.git/\n" } }
| Field | Purpose |
|---|---|
| Display name |
| Brief description |
| Markdown injected into agent system prompt when project is active |
| UI accent color (hex) |
| Optional Git repository URL |
| for project-specific memory, or shared |
| Controls the working directory tree shown to the agent |
Plugin System Overview
Plugins are the primary extension mechanism in Agent Zero. A plugin can bundle tools, extensions, prompts, API endpoints, helpers, and UI components into a self-contained package.
For all plugin tasks — creating, reviewing, managing, contributing, or debugging plugins — load the
skill, which routes to the appropriate specialist skill.a0-plugin-router
Core Plugins
The framework ships with these core plugins in
/a0/plugins/:
| Plugin | Purpose |
|---|---|
| Terminal, Python, Node.js code execution |
| Persistent vector memory system |
| File read/write/patch with line numbers |
| LLM model selection and configuration |
| Browser automation and web interaction |
| Prompt injection safety checks |
| Retry on critical exceptions |
| Email communication via IMAP/SMTP |
| Telegram bot integration |
| Branch chats from any message |
| Persistent behavioral rules (*.promptinclude.md) |
| Install plugins from ZIP/Git/Hub |
| Security scanning for plugins |
| Plugin manifest and code validation |
Common Patterns Reference
Accessing Agent Context
# Shared across all agents in a conversation context = self.agent.context data = context.data # dict-like shared state # Store data data["my_key"] = my_value # Retrieve data value = data.get("my_key", default)
Using File Helpers
from helpers import files # File operations content = files.read_file("path/to/file") files.write_file("path/to/file", content) exists = files.exists("path/to/file") # Read and render a prompt file content = files.read_prompt_file("template.md", _directories=[...], var="value")
Console Output
from helpers.print_style import PrintStyle PrintStyle.hint("Informational message") PrintStyle.warning("Warning message") PrintStyle.error("Error message") PrintStyle(font_color="#85C1E9").print("Custom styled output")
Error Handling
from helpers.tool import Response try: result = await risky_operation() except Exception as e: PrintStyle.error(f"Operation failed: {e}") return Response(message=f"Error: {e}", break_loop=False)
Development Workflow
When building features for Agent Zero:
1. Choose Your Extension Point
| Want to... | Use |
|---|---|
| Add a new agent capability | Tool (in a plugin) |
| Hook into agent lifecycle | Extension (in a plugin) |
| Add Web UI functionality | API endpoint + WebUI extension |
| Create a specialized agent | Agent profile |
| Bundle reusable instructions | Skill |
| Package everything together | Plugin (recommended) |
2. Develop in User Space
- New plugins ->
/a0/usr/plugins/<name>/ - New profiles ->
/a0/usr/agents/<name>/ - New skills ->
/a0/usr/skills/<name>/ - New extensions ->
/a0/usr/extensions/python/<hook_point>/
3. Test and Iterate
- Local dev: Run
(default port 50001 atpython run_ui.py
)http://localhost:50001 - Docker: Restart the container or use the UI restart button; check logs with
docker logs -f <container_name> - Test with minimal input first
- Verify in the Web UI
4. Contributing
For contribution guidelines, see
/a0/docs/contribution.md. For plugin contributions to the community Plugin Index, load the a0-contribute-plugin skill.
Best Practices
DO
- Use the plugin system for new features (see
skill)a0-create-plugin - Follow existing code patterns and conventions
- Write clear docstrings and comments
- Handle errors gracefully in tools and extensions
- Create prompt fragments for every tool (
)agent.system.tool.<name>.md - Develop in
directories to survive updates/a0/usr/ - Test with the
profile as a reference_example - Use
imports (notfrom helpers.*
)from python.helpers.*
DON'T
- Modify files in
or/a0/plugins/
directly (use usr/ space)/a0/tools/ - Hardcode paths or configuration values
- Skip creating prompt files for tools
- Ignore the plugin system (it's the intended extension mechanism)
- Mix sync and async code carelessly
- Access internal structures when helpers exist
Quick Reference: Key Files
| File | Purpose |
|---|---|
| Core , , classes |
| + base classes |
| base + decorator |
| base class |
| File ops + prompt reading |
| Plugin system manager |
| Console output formatting |
| Reference example profile with tool + extension |
| Main system prompt entry point |