AgentPool is a unified agent orchestration framework that enables YAML-based configuration of heterogeneous AI agents. It bridges multiple protocols (ACP, AG-UI, OpenCode, MCP) and supports native PydanticAI agents, Claude Code agents, Goose, and other external agents.
Core Philosophy: Define once in YAML, expose through multiple protocols, enable seamless inter-agent collaboration.
# Install with uv (recommended)
uv sync --all-extras
# Install specific extras
uv sync --extra coding --extra server# Run all tests (excludes slow and acp_snapshot by default)
uv run pytest
# Run with coverage
uv run pytest --cov-report=xml --cov=src/agentpool/ --cov-report=term-missing
# Run specific test markers
uv run pytest -m unit # Unit tests only
uv run pytest -m integration # Integration tests only
uv run pytest -m slow # Include slow tests
uv run pytest -m acp_snapshot # ACP snapshot tests
# Run single test file
uv run pytest tests/test_specific.py
# Run with verbose output
uv run pytest -vv
# Run tests in parallel
uv run pytest -n auto# Main command: runs all
duty lint
# Lint with ruff
uv run ruff check src/
# Format check
uv run ruff format --check
# Format code
uv run ruff format src/
# Type checking with mypy
uv run --no-group docs mypy src/# Run agent directly
agentpool run <agent_name> "prompt text"
# Start ACP server (for IDEs like Zed)
agentpool serve-acp config.yml
# Start OpenCode server
agentpool serve-opencode config.yml
# Start MCP server
agentpool serve-mcp config.yml
# Start AG-UI server
agentpool serve-agui config.yml
# Start OpenAI-compatible API server
agentpool serve-api config.yml
# Watch for triggers
agentpool watch --config agents.yml
# View analytics
agentpool history stats --group-by modelThe codebase is organized into focused packages under src/:
-
agentpool/- Core agent frameworkagents/- Agent implementations (native, ACP, AG-UI, Claude Code)delegation/- AgentPool orchestration, Team coordination, message routingmessaging/- Message processing, MessageNode abstraction, compactiontools/- Tool framework and implementationstool_impls/- Concrete tool implementations (bash, read, grep, etc.)models/- Pydantic data models and configuration schemasprompts/- Prompt management and templatingstorage/- Interaction tracking and analyticsmcp_server/- MCP server integrationrunning/- Agent execution runtimesessions/- Session managementhooks/- Event hooks systemobservability/- Logging and telemetry (Logfire)
-
agentpool_config/- Configuration models (separated for clean imports)- YAML schema definitions for agents, teams, tools, MCP servers
-
agentpool_server/- Protocol serversacp_server/- Agent Communication Protocol serveropencode_server/- OpenCode TUI/Desktop serveragui_server/- AG-UI protocol serveropenai_api_server/- OpenAI-compatible API servermcp_server/- Model Context Protocol server
-
agentpool_toolsets/- Reusable toolset implementationsbuiltin/- Built-in toolsets (code, debug, subagent, file_edit, workers)mcp_discovery/- MCP server discovery with semantic search- Specialized toolsets (composio, search, streaming, etc.)
-
agentpool_storage/- Storage providerssql_provider/- SQLAlchemy-based storagezed_provider/- Zed IDE storage integrationclaude_provider/- Claude storage integrationopencode_provider/- OpenCode storage integration
-
agentpool_cli/- Command-line interface -
agentpool_commands/- Command implementations -
agentpool_prompts/- Prompt templates -
acp/- Agent Communication Protocol implementationclient/- ACP client implementationsagent/- Agent-side protocol implementationschema/- Protocol schemas and typesbridge/- ACP bridge for connecting agentstransports/- Transport layer (stdio, websocket)
All processing units (Agents, Teams) inherit from MessageNode[TInputType, TOutput]. This provides:
- Unified interface for message processing via
process() - Connection management (forwarding outputs between nodes)
- Hook system for intercepting messages
- Type-safe input/output handling
# Both agents and teams are MessageNodes
agent: MessageNode[ChatMessage, ChatMessage]
team: MessageNode[ChatMessage, TeamRun]
# Nodes can be connected
agent.add_connection(other_agent) # Forward messages to other_agentAgentPool is a BaseRegistry[NodeName, MessageNode] that:
- Manages lifecycle of all agents and teams
- Provides dependency injection (shared_deps)
- Handles connection setup from YAML config
- Coordinates resource cleanup
Teams combine multiple agents:
- Sequential (chain):
agent1 | agent2 | agent3- Output flows through pipeline - Parallel:
agent1 & agent2 & agent3- All process same input concurrently - YAML configuration: Define teams in manifest with mode and members
Tools follow PydanticAI's tool pattern with AgentPool extensions:
- Tools are typed functions with Pydantic schemas
- Can access
AgentContextfor agent-specific state - Support
subagenttool for delegation - Built-in toolsets provide common functionality (code editing, bash, grep)
AgentPool acts as a protocol adapter:
- Agent defined once in YAML (with type: native/acp/agui/claude)
- Pool loads and manages agent lifecycle
- Server exposes agent through chosen protocol (ACP/AG-UI/OpenCode/OpenAI API)
- Client interacts via standardized protocol
Native Agents (type: native)
- PydanticAI-based agents with full framework features
- Direct model integration (OpenAI, Anthropic, Google, Mistral, etc.)
- Tool support, structured output, streaming
- Most flexible and feature-rich
ACP Agents (type: acp)
- External agents implementing Agent Communication Protocol
- Examples: Goose, Codex, custom ACP servers
- Communicate via stdio or websocket
Claude Code Agents (type: claude)
- Direct integration with Claude Code CLI
- Specialized for code-related tasks
- Access to Claude Code's tool ecosystem
AG-UI Agents (type: agui)
- Remote agents implementing AG-UI protocol
- HTTP-based communication
- Useful for distributed agent architectures
File Agents (type: file)
- Agent behavior defined by file content/prompts
- Lightweight for simple use cases
Storage Providers: Track all agent interactions
- SQL-based with SQLModel/SQLAlchemy
- Per-agent or shared database
- Analytics via CLI:
agentpool history stats
Observability: Logfire integration
- Structured logging with context
- Trace agent execution
- Performance monitoring
- Disabled in tests via env vars
YAML-First Design:
AgentsManifestis the root config model- Supports inheritance via
INHERITfield - Inline schema definitions with Schemez
- Environment variable substitution
- Jinja2 templating in prompts
Key Config Sections:
agents: Agent definitionsteams: Multi-agent teamsresponses: Structured output schemasmcp_servers: MCP server configurationsstorage: Interaction tracking configobservability: Logging/telemetry configworkers: Background worker definitionsjobs: Scheduled tasks
- Python 3.13+ required (use modern syntax: match/case, walrus operator)
- Follow PEP 8 via Ruff
- Google-style docstrings (no types in Args section)
- Type hints required (checked with mypy --strict)
- Use
from __future__ import annotationsfor forward references
- Tests use pytest (not in classes)
- Fixtures in
tests/conftest.py - TestModel from pydantic-ai for agent testing
- Disable observability in tests (see conftest.py)
- Markers:
@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.slow
# Avoid circular imports - use TYPE_CHECKING
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from agentpool.delegation import AgentPool
# Config models are in agentpool_config to avoid circular deps
from agentpool_config.teams import TeamConfigWhen adding new tools:
- Create implementation in
agentpool/tool_impls/<tool_name>/ - Define config model in
agentpool_config/if complex - Register in appropriate toolset (
agentpool_toolsets/) - Add tests in
tests/tool_impls/
New agent types require:
- Config model in
agentpool/models/(inherit from base, settypediscriminator) - Implementation in
agentpool/agents/ - Add to
AnyAgentConfigunion inmanifest.py - Update manifest loading in
pool.py
New protocol servers:
- Inherit from
BaseServerinagentpool_server/base.py - Implement protocol-specific handlers
- Use
AggregatingServerif wrapping multiple agents - Add CLI command in
agentpool_cli/
async with AgentPool("config.yml") as pool:
agent = pool.get_agent("agent_name")
result = await agent.run("prompt")# Find relevant tests
pytest tests/path/to/test_file.py -k "test_pattern"
# Quick sanity check (unit tests only)
pytest -m unit --no-cov
# Full validation
pytest && mypy src/ && ruff check src/- Enable verbose logging (set
OBSERVABILITY_ENABLED=true) - Check storage database for interaction history
- Use
TestModelfor isolated testing - Add
--log-cli-level=DEBUGto pytest
- Examples in
site/examples/*/config.yml - Schema reference auto-generated from Pydantic models
- Validate with:
python -m agentpool_config.manifest config.yml
AgentPool and Agents use async context managers - always use async with:
async with AgentPool(manifest) as pool:
async with pool.get_agent("name") as agent:
result = await agent.run("prompt")MCP servers are spawned as subprocesses - pool cleanup handles termination.
Use ProcessManager from anyenv for external process management.
Use UPath (universal_pathlib) not Path - supports remote filesystems (s3://, gs://, etc.)
Prefer string shorthand in YAML: model: "openai:gpt-4o"
Fallback models: type: fallback, models: [primary, backup]
The project uses entry points for extensibility:
agentpool_toolsets- Register custom toolsetsfsspec.specs- Filesystem implementations (ACP)universal_pathlib.implementations- Path implementations
src/agentpool/delegation/pool.py- AgentPool orchestrationsrc/agentpool/agents/agent.py- Native agent implementationsrc/agentpool/messaging/messagenode.py- Base abstractionsrc/agentpool/models/manifest.py- Configuration schemasrc/agentpool/tools/tool.py- Tool frameworksrc/agentpool_server/acp_server/acp_agent.py- ACP server agent wrappersrc/acp/client/protocol.py- ACP client interfacesrc/acp/agent/protocol.py- ACP agent interface
- Main docs: phil65.github.io/agentpool
- Built with MkNodes (see
mkdocs.yml) - Auto-generated from docstrings and examples. Utilities in agentpool/docs/
Native Agent
from agentpool.agents import Agent
def greet(name: str) -> str:
"""Greet someone."""
return f"Hello, {name}!"
async with Agent(
name="my_agent",
model="openai:gpt-4o-mini", # Required: model string or Model instance
system_prompt="You are a helpful assistant",
tools=[greet], # Callables or import paths like "mymodule:my_tool"
) as agent:
async for event in agent.run_stream("Greet Alice"):
...ACP Agent
from agentpool.agents.acp_agent import ACPAgent
async with ACPAgent(
command="goose", # Required: executable name
args=["acp"], # Required: command arguments
name="goose_agent",
cwd="/path/to/project",
) as agent:
async for event in agent.run_stream("Write code"):
...Claude Code Agent
from agentpool.agents.claude_code_agent import ClaudeCodeAgent
async with ClaudeCodeAgent(
name="claude_coder",
model="claude-sonnet-4-20250514", # Optional: defaults to latest
cwd="/path/to/project", # Optional: defaults to current directory
) as agent:
async for event in agent.run_stream("Refactor this code"):
...Config (config.yml)
agents:
coder:
type: native
model: "openai:gpt-4o-mini"
system_prompt: "You are an expert Python developer"
tools:
- name: bash
enabled: true
- name: read
enabled: truePython Code
from agentpool.delegation import AgentPool
from agentpool.agents.events import (
PartDeltaEvent,
ToolCallStartEvent,
ToolCallCompleteEvent,
StreamCompleteEvent,
)
async with AgentPool("config.yml") as pool:
agent = pool.get_agent("coder")
# Stream events (run_stream returns AsyncIterator, not a context manager)
async for event in agent.run_stream("Read setup.py and list dependencies"):
match event:
case PartDeltaEvent(delta=text):
# Stream text chunks as they arrive
print(text, end="", flush=True)
case ToolCallStartEvent(tool_name=name):
print(f"\n[Tool starting: {name}]")
case ToolCallCompleteEvent(tool_name=name, tool_result=result):
print(f"\n[Tool {name} completed: {result}]")
case StreamCompleteEvent(message=msg):
# Final complete message with full content
print(f"\n\nComplete response: {msg.content}")Rules:
- ALWAYS use uv for all python related tasks.
- DO NOT USE getattr and hasattr in very rare exceptions. Always provide full type safety.
- Maximum type safety.
- never resort to shortcuts, never leave out stuff with TODOs unless explicitely asked.