Skip to content

Commit b8443d8

Browse files
feat: add docs for multiagent hooks(Experimental) (#305)
1 parent 9cae964 commit b8443d8

File tree

2 files changed

+260
-0
lines changed

2 files changed

+260
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Multi-Agent Hooks [Experimental]
2+
3+
!!! warning "Experimental Feature"
4+
This feature is experimental and may change in future versions. Use with caution in production environments.
5+
6+
Multi-agent hooks extend the [hook system](../agents/hooks.md) to multi-agent primitives, enabling monitoring, debugging, and customization of multi-agent execution workflows. These hooks allow you to observe and modify behavior across the entire multi-agent lifecycle.
7+
8+
## Overview
9+
10+
Multi-agent hooks provide event-driven extensibility for orchestrators that coordinate multiple agents. Unlike single-agent hooks that focus on individual agent execution, multi-agent hooks capture orchestration-level events such as node transitions, orchestrator initialization, and overall invocation lifecycle.
11+
12+
Multi-agent hooks enable use cases such as:
13+
14+
- Monitoring multi-agent execution flow and node transitions
15+
- Debugging complex orchestration patterns
16+
- Adding validation and error handling at the orchestration level
17+
- Implementing custom logging and metrics collection
18+
19+
## Basic Usage
20+
21+
Multi-agent hook callbacks are registered against specific orchestration event types and receive strongly-typed event objects when those events occur during multi-agent execution.
22+
23+
### Registering Multi-Agent Hook Callbacks
24+
25+
You can register callbacks for specific events using `add_callback`:
26+
27+
```python
28+
# Create your orchestrator (Graph or Swarm)
29+
orchestrator = Graph(...)
30+
31+
# Register individual callbacks
32+
def my_callback(event: BeforeNodeCallEvent) -> None:
33+
print(f"Custom callback triggered")
34+
35+
orchestrator.hooks.add_callback(BeforeNodeCallEvent, my_callback)
36+
```
37+
38+
### Creating a Multi-Agent Hook Provider
39+
40+
The `HookProvider` protocol allows a single object to register callbacks for multiple events:
41+
42+
```python
43+
class MultiAgentLoggingHook(HookProvider):
44+
def register_hooks(self, registry: HookRegistry) -> None:
45+
registry.add_callback(MultiAgentInitializedEvent, self.log_initialization)
46+
registry.add_callback(BeforeMultiAgentInvocationEvent, self.log_invocation_start)
47+
registry.add_callback(AfterMultiAgentInvocationEvent, self.log_invocation_end)
48+
registry.add_callback(BeforeNodeCallEvent, self.log_node_start)
49+
registry.add_callback(AfterNodeCallEvent, self.log_node_end)
50+
51+
def log_initialization(self, event: MultiAgentInitializedEvent) -> None:
52+
print(f"Multi-agent orchestrator initialized: {type(event.source).__name__}")
53+
54+
def log_invocation_start(self, event: BeforeMultiAgentInvocationEvent) -> None:
55+
print("Multi-agent invocation started")
56+
57+
def log_invocation_end(self, event: AfterMultiAgentInvocationEvent) -> None:
58+
print("Multi-agent invocation completed")
59+
60+
def log_node_start(self, event: BeforeNodeCallEvent) -> None:
61+
print(f"Starting node execution: {event.node_id}")
62+
63+
def log_node_end(self, event: AfterNodeCallEvent) -> None:
64+
print(f"Completed node execution: {event.node_id}")
65+
66+
# Use with orchestrator
67+
orchestrator = Graph(hooks=[MultiAgentLoggingHook()])
68+
```
69+
70+
## Multi-Agent Hook Event Lifecycle
71+
72+
The following diagram shows when multi-agent hook events are emitted during orchestrator execution:
73+
74+
```mermaid
75+
flowchart LR
76+
subgraph Init["Initialization"]
77+
direction TB
78+
MultiAgentInitializedEvent["MultiAgentInitializedEvent"]
79+
end
80+
subgraph Invocation["Invocation Lifecycle"]
81+
direction TB
82+
BeforeMultiAgentInvocationEvent["BeforeMultiAgentInvocationEvent"]
83+
AfterMultiAgentInvocationEvent["AfterMultiAgentInvocationEvent"]
84+
BeforeMultiAgentInvocationEvent --> NodeExecution
85+
NodeExecution --> AfterMultiAgentInvocationEvent
86+
end
87+
subgraph NodeExecution["Node Execution (Repeated)"]
88+
direction TB
89+
BeforeNodeCallEvent["BeforeNodeCallEvent"]
90+
AfterNodeCallEvent["AfterNodeCallEvent"]
91+
BeforeNodeCallEvent --> AfterNodeCallEvent
92+
end
93+
Init --> Invocation
94+
```
95+
96+
### Available Multi-Agent Events
97+
98+
The multi-agent hooks system provides events for different states of multi-agent orchestrator execution:
99+
100+
| Event | Description |
101+
|------------------------------------|------------------------------------------------------------------------------------------------|
102+
| `MultiAgentInitializedEvent` | Triggered when multi-agent orchestrator is initialized |
103+
| `BeforeMultiAgentInvocationEvent` | Triggered before orchestrator execution starts |
104+
| `AfterMultiAgentInvocationEvent` | Triggered after orchestrator execution completes. Uses reverse callback ordering |
105+
| `BeforeNodeCallEvent` | Triggered before individual node execution starts |
106+
| `AfterNodeCallEvent` | Triggered after individual node execution completes. Uses reverse callback ordering |
107+
108+
## Multi-Agent Hook Behaviors
109+
110+
### Event Properties
111+
112+
Multi-agent hook events provide access to:
113+
114+
- **source**: The multi-agent orchestrator instance (for example: Graph/Swarm)
115+
- **node_id**: Identifier of the node being executed (for node-level events)
116+
- **invocation_state**: Configuration and context data passed through the orchestrator invocation
117+
118+
### Callback Ordering
119+
120+
Similar to single-agent hooks, After events (`AfterNodeCallEvent`, `AfterMultiAgentInvocationEvent`) use reverse callback ordering to ensure proper cleanup semantics.
121+
122+
### Accessing Orchestrator State
123+
124+
Multi-agent hooks can access the orchestrator instance directly through the `source` property, enabling inspection of the orchestrator's current state, configuration, and execution context.
125+
126+
## Advanced Usage
127+
128+
### Accessing Invocation State in Hooks
129+
130+
Like single-agent hooks, multi-agent hooks include access to `invocation_state`, which provides configuration and context data passed through the orchestrator's lifecycle.
131+
132+
```python
133+
class ContextAwareMultiAgentHook(HookProvider):
134+
def register_hooks(self, registry: HookRegistry) -> None:
135+
registry.add_callback(BeforeNodeCallEvent, self.log_with_context)
136+
137+
def log_with_context(self, event: BeforeNodeCallEvent) -> None:
138+
# Access shared context across all agents
139+
user_id = event.invocation_state.get("user_id", "unknown")
140+
session_id = event.invocation_state.get("session_id")
141+
142+
# Access orchestrator-specific configuration
143+
orchestrator_config = event.invocation_state.get("orchestrator_config", {})
144+
145+
print(f"User {user_id} executing node {event.node_id} "
146+
f"in session {session_id} with config: {orchestrator_config}")
147+
148+
# Use with shared state
149+
orchestrator = Graph(hooks=[ContextAwareMultiAgentHook()])
150+
result = orchestrator(
151+
"Process the request",
152+
user_id="user123",
153+
session_id="sess456",
154+
orchestrator_config={"max_retries": 3, "timeout": 30}
155+
)
156+
```
157+
158+
### Conditional Node Execution
159+
160+
Implement custom logic to modify orchestration behavior:
161+
162+
```python
163+
class ConditionalExecutionHook(HookProvider):
164+
def __init__(self, skip_conditions: dict[str, callable]):
165+
self.skip_conditions = skip_conditions
166+
167+
def register_hooks(self, registry: HookRegistry) -> None:
168+
registry.add_callback(BeforeNodeCallEvent, self.check_execution_conditions)
169+
170+
def check_execution_conditions(self, event: BeforeNodeCallEvent) -> None:
171+
node_id = event.node_id
172+
if node_id in self.skip_conditions:
173+
condition_func = self.skip_conditions[node_id]
174+
if condition_func(event.invocation_state):
175+
print(f"Skipping node {node_id} due to condition")
176+
# Note: Actual node skipping would require orchestrator-specific implementation
177+
```
178+
179+
## Best Practices
180+
181+
### Performance Considerations
182+
183+
Keep multi-agent hook callbacks lightweight since they execute synchronously:
184+
185+
```python
186+
class AsyncMultiAgentProcessor(HookProvider):
187+
def register_hooks(self, registry: HookRegistry) -> None:
188+
registry.add_callback(AfterNodeCallEvent, self.queue_node_processing)
189+
190+
def queue_node_processing(self, event: AfterNodeCallEvent) -> None:
191+
# Queue heavy processing for background execution
192+
self.background_queue.put({
193+
'node_id': event.node_id,
194+
'orchestrator_type': type(event.source).__name__,
195+
'timestamp': time.time()
196+
})
197+
```
198+
199+
### Orchestrator-Agnostic Design
200+
201+
Design hooks to work with different orchestrator types:
202+
203+
```python
204+
class UniversalMultiAgentHook(HookProvider):
205+
def register_hooks(self, registry: HookRegistry) -> None:
206+
registry.add_callback(BeforeNodeCallEvent, self.handle_node_execution)
207+
208+
def handle_node_execution(self, event: BeforeNodeCallEvent) -> None:
209+
orchestrator_type = type(event.source).__name__
210+
print(f"Executing node {event.node_id} in {orchestrator_type} orchestrator")
211+
212+
# Handle orchestrator-specific logic if needed
213+
if orchestrator_type == "Graph":
214+
self.handle_graph_node(event)
215+
elif orchestrator_type == "Swarm":
216+
self.handle_swarm_node(event)
217+
218+
def handle_graph_node(self, event: BeforeNodeCallEvent) -> None:
219+
# Graph-specific handling
220+
pass
221+
222+
def handle_swarm_node(self, event: BeforeNodeCallEvent) -> None:
223+
# Swarm-specific handling
224+
pass
225+
```
226+
227+
## Integration with Single-Agent Hooks
228+
229+
Multi-agent hooks complement single-agent hooks. Individual agents within the orchestrator can still have their own hooks, creating a layered monitoring and customization system:
230+
231+
```python
232+
# Single-agent hook for individual agents
233+
class AgentLevelHook(HookProvider):
234+
def register_hooks(self, registry: HookRegistry) -> None:
235+
registry.add_callback(BeforeToolCallEvent, self.log_tool_use)
236+
237+
def log_tool_use(self, event: BeforeToolCallEvent) -> None:
238+
print(f"Agent tool call: {event.tool_use['name']}")
239+
240+
# Multi-agent hook for orchestrator
241+
class OrchestratorLevelHook(HookProvider):
242+
def register_hooks(self, registry: HookRegistry) -> None:
243+
registry.add_callback(BeforeNodeCallEvent, self.log_node_execution)
244+
245+
def log_node_execution(self, event: BeforeNodeCallEvent) -> None:
246+
print(f"Orchestrator node execution: {event.node_id}")
247+
248+
# Create agents with individual hooks
249+
agent1 = Agent(tools=[tool1], hooks=[AgentLevelHook()])
250+
agent2 = Agent(tools=[tool2], hooks=[AgentLevelHook()])
251+
252+
# Create orchestrator with multi-agent hooks
253+
orchestrator = Graph(
254+
agents={"agent1": agent1, "agent2": agent2},
255+
hooks=[OrchestratorLevelHook()]
256+
)
257+
```
258+
259+
This layered approach provides comprehensive observability and control across both individual agent execution and orchestrator-level coordination.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ nav:
118118
- Interrupts: user-guide/concepts/interrupts.md
119119
- Experimental:
120120
- AgentConfig: user-guide/concepts/experimental/agent-config.md
121+
- MultiAgentHooks: user-guide/concepts/experimental/multi-agent-hooks.md
121122
- Safety & Security:
122123
- Responsible AI: user-guide/safety-security/responsible-ai.md
123124
- Guardrails: user-guide/safety-security/guardrails.md

0 commit comments

Comments
 (0)