Skip to content

Concepts · durable

Durable Events

When to consume run-stream receipts and when to publish session lifecycle changes

Read this when
  • Wiring durable-agent activity to a UI, log, or event bus
  • Choosing between the run stream and WithPublisher
  • Handling initialization, branch, fork, or compaction notifications

This page gives you the two durable event channels and the rules for choosing between them.

Durable agents expose two event channels because a run and a session have different lifetimes. The stream returned by Run reports progress within one turn. A Publisher reports lifecycle changes that happen outside a run.

Choose the channel by the lifetime of the change

ChannelScopeCarriesUse it for
Run(...).Events()One runLifted agent.Event values and persistence receiptsShowing model and tool progress for the active turn
Publisher.PublishSession lifecycleInitialization, branch, fork, and compaction eventsNotifying application infrastructure about agent-owned mutations

The split prevents lifecycle changes from waiting for a later run before consumers can observe them. It also keeps a run stream self-contained. Draining one stream tells you only what happened during that run.

Run events double as persistence receipts

Every event on the run stream has type EventAgent and carries the original event in Event.Agent. Most events only lift inner-agent progress. Two boundaries add durability information:

  • agent_start carries the durable input entries and the resulting LeafID.
  • message_end carries the completed message entry and the resulting LeafID.

The durable agent forwards each boundary only after AppendEntries succeeds. A consumer that receives the event can therefore treat its entries as persisted. Ephemeral entries never appear in a receipt because the store never receives them.

Lifecycle events never appear on this stream. Consume them through WithPublisher instead.

Configure one lifecycle publisher

WithPublisher accepts a Publisher or PublisherFunc. Every agent created through Fork inherits the same publisher.

publisher := durable.PublisherFunc(func(event durable.Event) {
switch event.Type {
case durable.EventSessionInit:
log.Printf("session %s ready at %s", event.SessionID, event.LeafID)
case durable.EventSessionBranched:
log.Printf("leaf moved from %s to %s", event.FromID, event.LeafID)
case durable.EventSessionForked:
log.Printf("session %s forked from %s", event.SessionID, event.ParentID)
case durable.EventSessionCompacted:
log.Printf("compacted at %s", event.LeafID)
}
})
da, err := durable.New(ctx, durable.Model(lm),
durable.WithStore(store),
durable.WithSessionID("user-42"),
durable.WithPublisher(publisher),
)

The publisher receives only successful lifecycle effects. Event.Agent is nil for these events. Fields not listed below remain zero-valued.

EventPublished afterRelevant fields
EventSessionInitNew creates or resumes a ready agentSessionID, and LeafID is empty when fresh and set to the resume leaf otherwise
EventSessionBranchedBranch moves the in-memory leafFromID, LeafID
EventSessionForkedFork creates the child and copies its active pathSessionID for the child, ParentID for the source
EventSessionCompactedCompact appends the compaction entryEntries with one CompactionEntry, LeafID

A successful fork publishes EventSessionForked for the source and then EventSessionInit for the child. Both calls go to the inherited publisher. If an operation returns an error, it publishes nothing. A compaction that has nothing to summarize also publishes nothing.

Delivery stays application-owned

Publish runs synchronously after the effect succeeds and outside the agent’s locks. Keep the callback fast or hand the event to a queue that your application owns. If callers mutate agents concurrently, the publisher must also be safe for concurrent calls.

The package does not buffer, retry, persist, or fan out lifecycle events. Those policies belong to the application because delivery requirements vary independently from transcript persistence. Without WithPublisher, lifecycle events are discarded and agent behavior is unchanged.

Application metadata changes do not publish

The durable agent owns the session ID, transcript tree, and active leaf. Application metadata lives in the application’s own storage. It never passes through the agent or its store. The agent cannot observe a metadata change, so it does not publish an update event.

If metadata changes need notifications, emit them in the application service that performs them. This keeps the publisher aligned with mutations the durable agent actually performs. It also avoids rebuilding a hidden session-state channel.

  • Sessions — ownership of session records and transcript history
  • Transcript Tree — how branch, fork, and compaction change the active path
  • Entries — which values are durable enough to appear in receipts
  • Agent Streaming — the inner event lifecycle lifted onto each run stream