Callbacks
Agency exposes a number of hooks. It's possible to write callbacks for these hooks in Agency or TypeScript. Here are both options.
Callbacks in Agency
callback("onNodeStart") as data {
print(`Node ${data.nodeName} started.`)
}Callbacks are scoped to the function or node they reside in. Callbacks in the global scope are always active.
Callbacks in TypeScript
import { main } from "agency"
const callbacks = {
onNodeStart: (data) => {
console.log(`Node ${data.nodeName} started.`)
}
}
const result = main(param1, param2, { callbacks })The last argument to main() is an options object. You can pass in a callbacks object here.
Note that:
- Callback bodies cannot raise interrupts.
- Callbacks can't change the flow of execution.
List of hooks
onAgentStart
Called when an agent starts executing.
nodeName: the name of the entry nodeargs: the arguments passed to the agentmessages: the initial message historycancel(reason?): call this to cancel the agent at any point.
onAgentEnd
Called when an agent finishes executing.
nodeName: the name of the entry node (the same node reported byonAgentStart)result: the result of running the agent
onNodeStart
Called when a graph node begins executing.
nodeName: the name of the node
onNodeEnd
Called when a graph node finishes executing.
nodeName: the name of the nodedata: the data returned by the node
onLLMCallStart
Called before an LLM call is made. Side-effect only — the callback's return value is ignored, and it cannot alter the messages sent to the LLM.
prompt: the prompt — either a string, or an array of text/attachment parts (redacted for logging)tools: the tools available to the LLM, each withname,description, andschemamodel: the model being usedmessages: the messages that will be sent
onLLMCallEnd
Called after an LLM call completes. Side-effect only — the callback's return value is ignored, and it cannot alter the messages stored in the thread.
model: the model that was usedresult: the full prompt result from the LLMusage: token usage statistics (if available)cost: estimated cost (if available)timeTaken: how long the call took in millisecondsmessages: the messages that were sent
onLLMRetry
Called just before the backend waits to retry an LLM call after a transient failure (see retries and timeouts). Side-effect only — it cannot change whether the retry happens.
attempt: the retry attempt number, starts at 1 (retry 1, 2, …)maxRetries: the configured max retry countdelayMs: how long the backend will wait before this retryreason: why we're retrying —"timeout","connectionLost","streamInterrupted","rateLimit","serverError", or"overloaded"detail: the raw provider message
onLLMTimeout
Called whenever an LLM call exceeds its per-call deadline (timeout), whether or not a retry follows.
limitMs: the deadline that was exceededattempt: the 0-based attempt that timed out
onOAuthRequired
Called when an MCP server needs OAuth authorization before it can be used.
serverName: the name of the server requesting authorizationauthUrl: the URL the user must visit to authorizecomplete: aPromise<void>that resolves once authorization finishescancel(): call this to cancel the authorization flow
onFunctionStart
Called when a function (tool) begins executing. Fires for every Agency def — both your own functions and the auto-imported stdlib functions (print, sleep, range, fetch, …). It does not fire for language built-ins such as interrupt, checkpoint, llm, or fork, which compile to dedicated constructs rather than function bodies.
functionName: the name of the functionargs: the arguments passed to the functionmoduleId: the module the function belongs to
onFunctionEnd
Called when a function (tool) finishes executing.
functionName: the name of the functiontimeTaken: how long the function took in milliseconds
onEmit
Called when agency code invokes emit(...). Receives whatever value was passed to emit.
onToolCallStart
Called when the LLM invokes a tool call.
toolName: the name of the tool being calledargs: the arguments passed to the tool
onToolCallEnd
Called when a tool call finishes.
toolName: the name of the toolresult: the result returned by the tooltimeTaken: how long the tool call took in milliseconds
onStream
Called during streaming LLM responses. The data is a tagged union with one of these types:
{ type: "text", text }— a chunk of streamed text{ type: "tool_call", toolCall }— a streamed tool call{ type: "done", result }— streaming is complete{ type: "error", error }— an error occurred during streaming
onTrace
Called for each trace line emitted during execution. Providing this callback automatically activates tracing for the execution. Receives a TraceEvent object:
runId: a unique id identifying this run (useful for distinguishing concurrent requests)line: the trace line, one of:{ type: "header", ... }— trace metadata (first line){ type: "chunk", hash, data }— content-addressed data block{ type: "manifest", ... }— checkpoint reference (one per step){ type: "footer", checkpointCount, chunkCount, timestamp }— emitted when execution completes
onThreadStart
Called when a thread or subthread begins.
threadId: the thread's id in slug form (e.g."t3")threadType: either"thread"or"subthread"parentThreadId: the parent thread's id in slug form, when presentlabel: the label fromthread(label: "...") { ... }, if anyisResumption:truewhen the thread is entered via continue/session
onThreadEnd
Called when a thread closes.
threadId: the thread's id in slug formlabel: the thread's label, if anyeagerSummarize: whether the thread was opened withthread(summarize: true)messages: aMessageJSON[]snapshot of the thread at close