| title | Hooks |
|---|---|
| description | Subscribe to agent lifecycle events for debugging, metrics, and custom behavior |
| weight | 171 |
Your agent just failed a request. Instead of debugging blind, you want to know: What did the LLM receive? What tools were called? What did the tool return? Where did the budget go?
Hooks let you subscribe to events throughout the agent lifecycle. When something happens, your handler receives context—structured data about exactly what occurred.
Understanding agent behavior requires seeing inside the black box:
- What went to the LLM? System prompt, messages, tools
- What did the LLM return? Raw response before parsing
- What tools were called? With what arguments?
- What was remembered? What was forgotten?
- What did it cost? Token breakdown per call
You could add print statements everywhere, but that's fragile and unstructured. You need observable hooks that tell you exactly what happened.
Register handlers for lifecycle events:
from syrin import Agent, Hook, Model
agent = Agent(model=Model.Almock())
# Register a handler
agent.events.on(Hook.AGENT_RUN_END, lambda ctx: print(f"Done! Cost: ${ctx.cost:.4f}"))
result = agent.run("Hello")
# Output: Done! Cost: $0.000120What just happened: Your handler ran when the agent finished. You received an EventContext with all the details.
Every hook receives an EventContext—a dict with dot notation access:
def my_handler(ctx):
# Dot notation (preferred)
print(ctx.input)
# Dict notation (also works)
print(ctx["input"])The context fields vary by hook. See the Hooks Reference for each hook's context.
Run during the event:
agent.events.on(Hook.AGENT_RUN_END, lambda ctx: print("Done!"))Run before the event, can modify context:
def modify_temperature(ctx):
ctx["temperature"] = 0.5
agent.events.before(Hook.LLM_REQUEST_START, modify_temperature)Useful for:
- Changing LLM parameters (temperature, top_p)
- Adding custom headers
- Validating inputs
- Injecting context
Run after the event for logging/metrics:
def log_duration(ctx):
print(f"Duration: {ctx.duration_ms}ms")
agent.events.after(Hook.AGENT_RUN_END, log_duration)Note: The events API does not support decorator syntax. Use
agent.events.on(Hook.X, handler)directly.
total_cost = {"value": 0.0}
def track_cost(ctx):
total_cost["value"] += ctx.get("cost", 0)
agent.events.on(Hook.AGENT_RUN_END, track_cost)
# Make multiple requests
for i in range(10):
agent.run(f"Request {i}")
print(f"Total spent: ${total_cost['value']:.4f}")def log_tool(ctx):
print(f"Tool: {ctx.name}")
print(f" Arguments: {ctx.arguments}")
print(f" Result: {str(ctx.result)[:100]}")
def log_tool_error(ctx):
print(f"Tool failed: {ctx.name}")
print(f" Error: {ctx.error}")
agent.events.on(Hook.TOOL_CALL_END, log_tool)
agent.events.on(Hook.TOOL_ERROR, log_tool_error)def alert_threshold(ctx):
pct = ctx.threshold_percent
print(f"Budget at {pct}%!")
if pct >= 90:
send_alert(f"Budget critical: {pct}%")
def handle_exceeded(ctx):
print(f"Budget exceeded: ${ctx.used:.4f} of ${ctx.limit:.4f}")
agent.events.on(Hook.BUDGET_THRESHOLD, alert_threshold)
agent.events.on(Hook.BUDGET_EXCEEDED, handle_exceeded)def on_llm_start(ctx):
print(f"LLM call starting")
print(f" Iteration: {ctx.iteration}")
print(f" Model: {ctx.model}")
def on_llm_end(ctx):
print(f"LLM call complete")
print(f" Tokens: {ctx.tokens}")
print(f" Cost: ${ctx.cost:.4f}")
print(f" Content: {str(ctx.content)[:100]}")
agent.events.on(Hook.LLM_REQUEST_START, on_llm_start)
agent.events.on(Hook.LLM_REQUEST_END, on_llm_end)def on_store(ctx):
print(f"Stored: {ctx.content[:50]}...")
def on_recall(ctx):
print(f"Recalled: {len(ctx.memories)} memories")
for m in ctx.memories[:3]:
print(f" - {m.content[:50]}")
def on_forget(ctx):
print(f"Forgot: {ctx.forgotten_count} items")
agent.events.on(Hook.MEMORY_STORE, on_store)
agent.events.on(Hook.MEMORY_RECALL, on_recall)
agent.events.on(Hook.MEMORY_FORGET, on_forget)# Before: modify the LLM call
def add_context(ctx):
ctx["custom_field"] = "my_value"
ctx["temperature"] = 0.7
# After: record metrics
def record_latency(ctx):
metrics.histogram("llm_latency", ctx.duration_ms)
agent.events.before(Hook.LLM_REQUEST_START, add_context)
agent.events.after(Hook.LLM_REQUEST_END, record_latency)For common hooks, use shortcuts:
agent.events.on_start(lambda ctx: print("Started"))
agent.events.on_complete(lambda ctx: print(f"Done! Cost: ${ctx.cost:.4f}"))
agent.events.on_request(lambda ctx: print("LLM call"))
agent.events.on_response(lambda ctx: print(f"Tokens: {ctx.tokens}"))
agent.events.on_tool(lambda ctx: print(f"Tool: {ctx.name}"))
agent.events.on_error(lambda ctx: print(f"Error: {ctx.error}"))
agent.events.on_budget(lambda ctx: print(f"Budget: {ctx}"))Track everything:
def log_all(hook, ctx):
print(f"[{hook.value}] {ctx}")
agent.events.on_all(log_all)Output:
[agent.run.start] {'input': 'Hello'}
[llm.request.start] {'iteration': 0, 'model': 'gpt-4o'}
[llm.request.end] {'tokens': 45, 'cost': 0.000150}
[agent.run.end] {'cost': 0.000150, 'tokens': 45}
Register multiple handlers for the same hook:
agent.events.on(Hook.AGENT_RUN_END, handler1)
agent.events.on(Hook.AGENT_RUN_END, handler2)
agent.events.on(Hook.AGENT_RUN_END, handler3)All three run in registration order.
Handlers run synchronously in the agent's execution path:
# Good: fast in-memory operations
def track_cost(ctx):
total_cost += ctx.cost
agent.events.on(Hook.AGENT_RUN_END, track_cost)
# Bad: slow operations block the agent
def slow_operation(ctx):
requests.post("https://analytics.example.com", json=ctx) # Blocks!
agent.events.on(Hook.AGENT_RUN_END, slow_operation)import threading
def send_analytics(ctx):
def fire_and_forget():
requests.post("https://analytics.example.com", json=dict(ctx))
threading.Thread(target=fire_and_forget, daemon=True).start()
agent.events.on(Hook.AGENT_RUN_END, send_analytics)Not all hooks have all fields:
def safe_handler(ctx):
cost = ctx.get("cost", 0) # Use .get() with defaults
tokens = ctx.get("tokens", 0)
duration = ctx.get("duration", 0)
agent.events.on(Hook.AGENT_RUN_END, safe_handler)| Feature | Debug Mode | Custom Hooks |
|---|---|---|
| Setup | One flag (debug=True) |
Register handlers |
| Output | Rich console output | Your logic |
| Customization | Limited | Full control |
| Production | Not recommended | Recommended |
| Performance impact | Minimal | Depends on handler |
Debug mode is great for development. Custom hooks are better for production.
- Debugging Overview — Introduction to observability
- Hooks Reference — Complete hook reference
- Audit Logging — Persist events to files
- Playground — Visual observability panel