Observing Agent events
run.Wait answers one question: how did the run end. Agent events answer a different one: what is happening right now.
Events serve the screen, Wait serves the application, and the two never conflict. Deltas stay best-effort, while
agent.Result.Final and agent.Result.Context are authoritative.
Picking the events to display
Most interfaces need only a few event types.
| Display need | Event |
|---|---|
| text as it arrives | agent.MessageUpdateEvent carrying generation.TextDeltaEvent |
| reasoning as it arrives | agent.MessageUpdateEvent carrying generation.ThinkingDeltaEvent |
| tool activity | agent.ToolExecutionStartEvent, ToolExecutionUpdateEvent, and ToolExecutionEndEvent |
| summary activity | agent.CompactionStartEvent and agent.CompactionEndEvent |
| run completion | agent.RunEndEvent, then Wait |
Consuming the stream
Start the run, iterate its event stream, then call Wait when the iteration ends.
run, err := assistant.Start(ctx, input)
if err != nil {
return err
}
for event := range run.Events() {
switch event := event.(type) {
case agent.MessageUpdateEvent:
switch update := event.AssistantMessageEvent.(type) {
case generation.TextDeltaEvent:
fmt.Fprint(os.Stderr, update.Delta)
case generation.ThinkingDeltaEvent:
fmt.Fprint(os.Stderr, update.Delta)
}
case agent.ToolExecutionStartEvent:
fmt.Fprintf(os.Stderr, "[tool] %s %s\n", event.Call.Name, event.Call.Arguments)
case agent.ToolExecutionEndEvent:
fmt.Fprintf(os.Stderr, "[done] %s error=%t\n", event.Call.Name, event.Result.IsError)
case agent.RunEndEvent:
if event.Err != nil {
fmt.Fprintln(os.Stderr, "run failed:", event.Err)
}
}
}
result, err := run.Wait()
if err != nil {
return err
}
fmt.Printf("final: %s\n", result.Final.Text())
In short, deltas are for display, agent.Result.Final is the completed response, and agent.Result.Context is the
continuation state.
Following the event order
Events nest runs, turns, messages, and tool executions.
RunStartEvent
→ optional compaction start/end
→ TurnStartEvent
→ prompt MessageStartEvent / MessageEndEvent
→ assistant MessageStartEvent
→ MessageUpdateEvent* carrying generation content events
MessageEndEvent
→ tool executions and tool-result messages
TurnEndEvent
→ more turns and compactions
RunEndEvent
The * means zero or more updates per message. Message events cover only messages admitted or produced during this run,
meaning new prompts, assistant attempts, and tool results. Messages already present in agent.Input.Context are request
input and do not emit historical events.
RunStartEvent is always first and RunEndEvent always last. If cancellation happens before prompts are admitted and
before compaction starts, the sequence can be RunStartEvent → RunEndEvent. If threshold compaction has started, its
CompactionStartEvent and matching CompactionEndEvent occur before RunEndEvent.
Reading the nested generation events
agent.MessageUpdateEvent.AssistantMessageEvent carries one generation content event with its ContentIndex, delta,
end value, and cumulative Partial. The rules in Observing generation events still apply.
generation.StartEvent, generation.DoneEvent, and generation.ErrorEvent are represented by Agent message and run
lifecycle events instead of being forwarded directly.
Knowing the twelve Agent events
| Event | Meaning |
|---|---|
agent.RunStartEvent | run began |
agent.RunEndEvent | run ended. Messages equals Result.NewMessages |
agent.TurnStartEvent | a turn began, covering prompt admission, an assistant response, or a continued tool turn |
agent.TurnEndEvent | response and ordered tool results ended |
agent.MessageStartEvent | a prompt, assistant attempt, or tool result began |
agent.MessageUpdateEvent | one generation content update |
agent.MessageEndEvent | a message ended with its final value |
agent.ToolExecutionStartEvent | a call was announced before lookup and validation |
agent.ToolExecutionUpdateEvent | provisional output from a running tool |
agent.ToolExecutionEndEvent | final result message and termination flag for one call |
agent.CompactionStartEvent | a summary rewrite began |
agent.CompactionEndEvent | rewrite succeeded with a record or failed with an error |
Three rules describe every tool event. ToolExecutionStartEvent appears even for unknown or malformed calls, and the
matching end event carries an error result. Parallel start events follow assistant call order, while end events follow
completion order. TurnEndEvent results follow assistant call order.
Consuming the stream carefully
run.Events() returns an iterator with one consumer. Calling Wait first selects result mode and leaves the event
queue empty. Starting iteration claims the event stream, so call Wait after iteration ends. Breaking the iteration
abandons the remaining queue but does not stop the run. Calling Wait repeatedly is safe.
Structural events and content start/end updates are retained. Incremental assistant deltas and tool updates may be
dropped when the consumer falls behind, with at most 128 such events queued at once. Delivered values are copies, and
errors keep their identity for errors.Is and errors.As.
Following events around cancellation and compaction
Run.Abort() cancels only its run, and a tool function that ignores cancellation can delay its end and the final
RunEndEvent.
Threshold compaction is optional maintenance. Planning errors, estimate failures, and no-op plans emit no compaction
pair. Once a rewrite starts, it emits CompactionStartEvent. A non-cancellation rewrite failure emits
CompactionEndEvent.Err, leaves the conversation unchanged, and the run continues. After a successful answer, a
non-cancellation failure preserves that answer and settles the run. Overflow recovery requires an attached Compactor,
and a failed required rewrite ends the run. Successful rewrites appear in agent.Result.Compactions. The
Compacting conversation context chapter covers both triggers.