Lifecycle
Understanding the request lifecycle and hook system in BaseAgent.
Request Lifecycle
graph TD
Request["Incoming Request"] --> Connection["on_connection hooks"]
Connection --> Message["on_message hooks"]
Message --> Tools{"Tool calls?"}
Tools -->|Yes| BeforeTool["before_toolcall hooks"]
BeforeTool --> Execute["Execute tool"]
Execute --> AfterTool["after_toolcall hooks"]
Message --> Handoff{"Handoff needed?"}
Handoff -->|Yes| BeforeHandoff["before_handoff hooks"]
BeforeHandoff --> RouteAgent["Route to agent"]
RouteAgent --> AfterHandoff["after_handoff hooks"]
Tools --> Response["Generate response"]
Handoff --> Response
Response --> Chunks["on_chunk hooks"]
Chunks --> Finalize["finalize_connection hooks"]
Lifecycle Hooks
Available Hooks
- on_connection - Request initialized
- on_message - Each message processed
- before_toolcall - Before tool execution
- after_toolcall - After tool execution
- on_chunk - Each streaming chunk
- before_handoff - Before agent handoff
- after_handoff - After agent handoff
- finalize_connection - Request complete
Note
finalize_connection runs for cleanup even when a prior hook raises a structured error (for example, a 402 payment/auth error). Implement finalize hooks to be idempotent and safe when required context (like a payment token) is missing.
Hook Registration
from webagents.agents.skills import Skill
from webagents.agents.skills.decorators import hook
class AnalyticsSkill(Skill):
@hook("on_connection", priority=10)
async def track_request(self, context):
"""Track incoming request"""
print(f"New request: {context.completion_id}")
return context
@hook("on_message", priority=20)
async def analyze_message(self, context):
"""Analyze each message"""
message = context.messages[-1]
print(f"Message role: {message['role']}")
return context
@hook("on_chunk", priority=30)
async def monitor_streaming(self, context):
"""Monitor streaming chunks"""
chunk_size = len(context.get("content", ""))
print(f"Chunk size: {chunk_size}")
return context
Hook Priority
Hooks execute in priority order (lower numbers first):
class SecuritySkill(Skill):
@hook("before_toolcall", priority=1) # Runs first
async def validate_security(self, context):
"""Security check before tools"""
tool_name = context["tool_call"]["function"]["name"]
if self.is_dangerous(tool_name):
raise SecurityError("Tool blocked")
return context
class LoggingSkill(Skill):
@hook("before_toolcall", priority=10) # Runs second
async def log_tool_usage(self, context):
"""Log tool execution"""
self.log_tool(context["tool_call"])
return context
Context During Lifecycle
Connection Context
@hook("on_connection")
async def on_connect(self, context):
# Available in context:
# - messages: List[Dict]
# - stream: bool
# - peer_user_id: str
# - completion_id: str
# - model: str
# - agent_name: str
# - agent_skills: Dict[str, Skill]
return context
Message Context
@hook("on_message")
async def on_msg(self, context):
# Same as connection + current message
current_message = context.messages[-1]
role = current_message["role"]
content = current_message["content"]
return context
Tool Context
@hook("before_toolcall")
async def before_tool(self, context):
# Additional context:
# - tool_call: Dict with function details
# - tool_id: str
return context
@hook("after_toolcall")
async def after_tool(self, context):
# Additional context:
# - tool_result: str (execution result)
return context
Streaming Context
@hook("on_chunk")
async def on_chunk(self, context):
# Additional context:
# - chunk: Dict (OpenAI format)
# - content: str (chunk content)
# - chunk_index: int
# - full_content: str (accumulated)
return context
Practical Examples
Request Logging
class RequestLogger(Skill):
@hook("on_connection")
async def start_logging(self, context):
self.start_time = time.time()
self.request_id = context.completion_id
await self.log_request_start(context)
return context
@hook("finalize_connection")
async def end_logging(self, context):
duration = time.time() - self.start_time
await self.log_request_complete(
self.request_id,
duration,
context.get("usage", {})
)
return context
Content Filtering
class ContentFilter(Skill):
@hook("on_message", priority=5)
async def filter_input(self, context):
"""Filter inappropriate input"""
message = context.messages[-1]
if message["role"] == "user":
filtered = self.filter_content(message["content"])
context.messages[-1]["content"] = filtered
return context
@hook("on_chunk", priority=5)
async def filter_output(self, context):
"""Filter streaming output"""
content = context.get("content", "")
if self.is_inappropriate(content):
context["chunk"]["choices"][0]["delta"]["content"] = "[filtered]"
return context
Performance Monitoring
class PerformanceMonitor(Skill):
def __init__(self, config=None):
super().__init__(config)
self.metrics = {}
@hook("before_toolcall")
async def start_timer(self, context):
tool_id = context["tool_id"]
self.metrics[tool_id] = {"start": time.time()}
return context
@hook("after_toolcall")
async def record_duration(self, context):
tool_id = context["tool_id"]
duration = time.time() - self.metrics[tool_id]["start"]
await self.record_metric(
"tool_duration",
duration,
{"tool": context["tool_call"]["function"]["name"]}
)
return context
Best Practices
- Use Priorities - Order hooks appropriately
- Return Context - Always return modified context
- Handle Errors - Gracefully handle exceptions
- Minimize Overhead - Keep hooks lightweight
- Thread Safety - Use context vars for state