Protocols API¶
SubAgentDepsProtocol¶
subagents_pydantic_ai.SubAgentDepsProtocol
¶
Bases: Protocol
Protocol for dependencies that support subagent management.
The only method the library calls is clone_for_subagent, which decides what
a delegated subagent inherits from its parent. Your deps class may be frozen
or use slots -- the library never writes attributes onto it.
Example
Earlier versions also required a subagents: dict[str, Any] attribute. The
library never read it, so every application carried a field for nothing; the
requirement is gone. A deps class that still declares it satisfies this
protocol unchanged.
Source code in src/subagents_pydantic_ai/protocols.py
clone_for_subagent(max_depth=0)
¶
Create a new deps instance for a delegated subagent.
Return a new instance rather than self. The library relies on each
delegation getting its own deps, and a shared instance leaks state
between concurrent subagents.
A subagent typically inherits shared resources (a backend, a filesystem) and gets fresh task-specific state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_depth
|
int
|
Remaining nesting budget, one less than the parent's
|
0
|
Returns:
| Type | Description |
|---|---|
SubAgentDepsProtocol
|
A new deps instance configured for the subagent. |
Source code in src/subagents_pydantic_ai/protocols.py
MessageBusProtocol¶
subagents_pydantic_ai.MessageBusProtocol
¶
Bases: Protocol
Protocol for message bus implementations.
The message bus enables communication between agents, supporting both fire-and-forget messages and request-response patterns.
Implementations can use different backends: - In-memory (default): Uses asyncio queues - Redis: For distributed multi-process setups - Custom: Any backend implementing this protocol
Example
bus = InMemoryMessageBus()
# Register an agent
queue = bus.register_agent("worker-1")
# Send a message
await bus.send(AgentMessage(
type=MessageType.TASK_UPDATE,
sender="parent",
receiver="worker-1",
payload={"status": "starting"},
))
# Request-response pattern
response = await bus.ask(
sender="parent",
receiver="worker-1",
question="What is your status?",
task_id="task-123",
timeout=30.0,
)
Source code in src/subagents_pydantic_ai/protocols.py
| Python | |
|---|---|
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
send(message)
async
¶
Send a message to a specific agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
AgentMessage
|
The message to send. Must have a valid receiver. |
required |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the receiver is not registered. |
Source code in src/subagents_pydantic_ai/protocols.py
ask(sender, receiver, question, task_id, timeout=30.0)
async
¶
Send a question and wait for a response.
This implements a request-response pattern where the sender blocks until the receiver answers or the timeout expires.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sender
|
str
|
ID of the asking agent. |
required |
receiver
|
str
|
ID of the agent to ask. |
required |
question
|
Any
|
The question payload. |
required |
task_id
|
str
|
Task ID for correlation. |
required |
timeout
|
float
|
Maximum time to wait for response in seconds. |
30.0
|
Returns:
| Type | Description |
|---|---|
AgentMessage
|
The response message from the receiver. |
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If no response within timeout. |
KeyError
|
If the receiver is not registered. |
Source code in src/subagents_pydantic_ai/protocols.py
answer(original, answer)
async
¶
Answer a previously received question.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
original
|
AgentMessage
|
The original question message. |
required |
answer
|
Any
|
The answer payload. |
required |
register_agent(agent_id)
¶
Register an agent to receive messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
str
|
Unique identifier for the agent. |
required |
Returns:
| Type | Description |
|---|---|
Queue[AgentMessage]
|
A queue where messages for this agent will be delivered. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If agent_id is already registered. |
Source code in src/subagents_pydantic_ai/protocols.py
| Python | |
|---|---|
unregister_agent(agent_id)
¶
Unregister an agent from the message bus.
After unregistration, messages sent to this agent will raise errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
str
|
The agent to unregister. |
required |
Source code in src/subagents_pydantic_ai/protocols.py
get_messages(agent_id, timeout=0.0)
async
¶
Get pending messages for an agent.
Non-blocking retrieval of all pending messages in the agent's queue.
Optionally waits up to timeout seconds for at least one message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
str
|
The agent to get messages for. |
required |
timeout
|
float
|
Maximum time to wait for a message (0 = no wait). |
0.0
|
Returns:
| Type | Description |
|---|---|
list[AgentMessage]
|
List of pending messages (may be empty). |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the agent is not registered. |
Source code in src/subagents_pydantic_ai/protocols.py
The in-memory bus, TaskManager, and DynamicAgentRegistry are documented on
their own pages: Message Bus and Registry.
Usage Examples¶
Implementing SubAgentDepsProtocol¶
from dataclasses import dataclass, field
from typing import Any
@dataclass
class MyDeps:
"""Custom dependencies implementing SubAgentDepsProtocol."""
subagents: dict[str, Any] = field(default_factory=dict)
database_url: str = ""
api_key: str = ""
def clone_for_subagent(self, max_depth: int = 0) -> "MyDeps":
"""Create isolated deps for subagent."""
return MyDeps(
subagents={} if max_depth <= 0 else self.subagents.copy(),
database_url=self.database_url, # Share read-only config
api_key=self.api_key,
)
Implementing Custom Message Bus¶
from subagents_pydantic_ai import MessageBusProtocol, AgentMessage
class RedisMessageBus:
"""Redis-based message bus for distributed systems."""
def __init__(self, redis_url: str):
self.redis = Redis.from_url(redis_url)
async def send(self, message: AgentMessage) -> None:
channel = f"agent:{message.receiver}"
await self.redis.publish(channel, message.json())
async def receive(
self,
agent_id: str,
timeout: float | None = None,
) -> AgentMessage | None:
# Implementation...
pass
async def subscribe(self, agent_id: str) -> None:
pass
async def unsubscribe(self, agent_id: str) -> None:
pass
Using TaskManager¶
from subagents_pydantic_ai import TaskManager, InMemoryMessageBus
bus = InMemoryMessageBus()
manager = TaskManager(message_bus=bus)
# Create a task
handle = await manager.create_task(
subagent_name="researcher",
description="Research Python async",
)
# Check status
status = await manager.get_task_status(handle.task_id)
# Answer a question
await manager.answer_question(handle.task_id, "Use asyncio")
# Cancel a task
await manager.cancel_task(handle.task_id, hard=False)