Skip to content

Key Architectural Patterns

ProtocolEventConsumerMixin

ProtocolEventConsumerMixin (in src/agentpool_server/mixins.py) provides a reusable event consumer lifecycle for protocol servers. It extracts the common pattern of subscribing to the EventBus, running an async consumer loop, and cleaning up on shutdown.

Why it exists: Before this mixin, OpenCode and ACP each implemented their own event consumer loop independently. The code was duplicated, and ACP's implementation was missing features like SpawnSessionStart handling and recursive child subscription. The mixin centralizes the loop mechanics while letting each protocol define its own event conversion.

Which protocols use it: - ACP (acp_server/handler.py): Adopted in Phase 1. Uses scope="session" with explicit child consumers created in _on_spawn_session_start for sync subagents (skips background tasks with spawn_mechanism="task"). - OpenCode (opencode_server/session_pool_integration.py): Adopted in Phase 2. Uses scope="session" with all hooks implemented (_before_consumer_loop, _handle_event, _on_spawn_session_start, _after_consumer_loop). Handles ToolPart registration and OpenCodeEventAdapter. - AG-UI (agui_server/server.py): Adopted in Phase 3. Uses scope="session" with minimal implementation (stateless HTTP, child consumer started in _on_spawn_session_start). - OpenAI API (openai_api_server/server.py): Adopted in Phase 3. Uses scope="session" with minimal implementation (stateless HTTP, child consumer started in _on_spawn_session_start).

Key hooks: - _before_consumer_loop(session_id): Set up per-session context (e.g. create an event converter). - _handle_event(session_id, event): Convert and deliver the event. May raise ConsumerShutdown to stop the loop. - _on_spawn_session_start(session_id, event): React to subagent spawning. Default is no-op. - _after_consumer_loop(session_id): Clean up per-session context. Only called if the consumer actually started.

Thread safety: start_event_consumer is idempotent and serializes concurrent calls for the same session via per-session locks.

MessageNode Abstraction

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

Deprecation: agent_pool property

MessageNode.agent_pool is deprecated since M2 with DeprecationWarning. M3.5 completed the migration of all ~64 remaining call sites across ACP server, OpenCode server, core agents, factory, and talk. The agent_pool property and setter remain as a deprecated shim for backward compatibility. HostContext.pool is kept as a temporary escape hatch for ~6 skill-related accesses (to be removed in the skill-service-extraction change). Full property removal is tracked as an optional task (T7.x) or will be done in M4.

Migration: Use MessageNode.host_context instead, which returns an immutable HostContext with the same infrastructure fields. host_context is sourced from AgentPool.get_context() and provides access to MCP manager, storage, and registry without exposing the full mutable pool object.

# OLD (M1) — deprecated
pool = node.agent_pool
mcp = node.agent_pool.mcp

# NEW (M2+) — recommended
ctx = node.host_context
mcp = ctx.mcp  # if ctx is not None

The _agent_pool backing field and setter remain for internal wiring during pool registration, but all read access should migrate to host_context.

# 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_agent

Deprecation: Runtime Dynamic Connections

MessageNode.connect_to() and ConnectionManager.create_connection() are deprecated. These methods allow runtime mutation of agent topology, which conflicts with the immutable graph model used by pydantic-graph.

Migration path: Define connections in YAML (graph: or connections: sections) or use GraphBuilder programmatically instead of calling connect_to() at runtime. The deprecated methods continue to work but will emit a DeprecationWarning.

AgentPool as Registry

AgentPool 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

Team Patterns

New graph-based approach (recommended): Teams are compiled into pydantic-graph workflows: - Sequential: Chained Steps via edges (agent1 -> agent2 -> agent3) - Parallel: Fork + Join (agent1 & agent2 & agent3) - YAML configuration: Define workflows in the graph: section

Legacy syntax (still supported): - 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

See the Graph Architecture documentation for full details.

Tool System

Tools follow PydanticAI's tool pattern with AgentPool extensions: - Tools are typed functions with Pydantic schemas - AbstractCapability is the primary abstraction for providing tools, instructions, and change notifications. Each capability wraps a pydantic-ai Toolset and contributes to the agent's compiled tool list. - Can access AgentContext (injected via RunContext.deps) for agent-specific state including DelegationService for subagent spawning - Support subagent tool for delegation (routes through DelegationService, not directly to AgentPool) - Built-in toolsets provide common functionality (code editing, bash, grep) - ResourceSource is a separate protocol for read-only data access (MCP resources, skill content), orthogonal to AbstractCapability

Protocol Bridging

AgentPool acts as a protocol adapter: 1. Agent defined once in YAML (with type: native or acp) 2. Pool loads and manages agent lifecycle 3. Server exposes agent through chosen protocol (ACP/AG-UI/OpenCode/OpenAI API) 4. Client interacts via standardized protocol