Callbacks
July 28, 2026 · View on GitHub
Two equivalent ways to hook the agent lifecycle.
Inline delegate fields
Quick, per-agent:
var agent = new Agent("monitored")
{
BeforeModelCallback = messages =>
{
Console.WriteLine($"[before_model] sending {messages?.Count ?? 0} messages");
return []; // empty dict = continue normally; non-empty = skip the LLM / override
},
AfterModelCallback = llmResult =>
{
Console.WriteLine($"[after_model] {llmResult?.Length ?? 0} chars");
return []; // empty = keep response; non-empty = override
},
};
There are six delegate slots: BeforeAgentCallback / AfterAgentCallback,
BeforeModelCallback / AfterModelCallback, BeforeToolCallback /
AfterToolCallback. The before/after-agent and before/after-tool variants take a
Dictionary<string, JsonElement> kwargs map.
CallbackHandler subclasses
Composable and reusable across agents. Override only the hooks you care about, then
register a list via Agent.Callbacks. Handlers run in list order and the first
non-empty return short-circuits.
internal sealed class ToolStartLogger : CallbackHandler
{
public override Dictionary<string, object>? OnToolStart(Dictionary<string, JsonElement> kwargs)
{
Console.WriteLine("[before_tool]");
return null; // observe only
}
}
var agent = new Agent("a") { Callbacks = [new ToolStartLogger()] };
// or: AgentBuilder.Create("a").WithCallbacks(new ToolStartLogger()).Build();
Hooks: OnAgentStart / OnAgentEnd / OnModelStart / OnModelEnd /
OnToolStart / OnToolEnd.
Return-value semantics
The return value is the control channel, and it is uniform across both styles:
- empty or
null— observe only; the run continues unchanged - non-empty — override. Before-hooks skip the underlying operation and use your value; after-hooks replace the result.
This is what makes callbacks usable for caching, redaction, and short-circuiting — not just logging.
Positions on the wire
Callback positions map to server task names: before_agent, after_agent,
before_model, after_model, before_tool, after_tool. Those names appear in
execution traces, which is useful when debugging.
Callbacks vs guardrails
Callbacks are general-purpose interception. Guardrails are the purpose-built path for validation with retry/raise/fix/escalate semantics — reach for those when the goal is enforcing a policy rather than observing a lifecycle.