AgentSession
Lifecycle, GenerateReply/Say, UpdateAgent, events, and effective models.
AgentSession runs one live conversation: it binds to a transport's I/O,
resolves the effective models, drives the turn loop, owns the active agent, and
emits events. It is transport-oblivious — the same session runs over console,
WebRTC, or Twilio.
Construction
func NewAgentSession(opts AgentSessionOptions) (*AgentSession, error)
func MustNewAgentSession(opts AgentSessionOptions) *AgentSessionHigh-level: Config + NewSession
NewSession is the ergonomic constructor. Its Config mirrors the typed options
but its model fields take either a "provider/model[:variant]" string
(resolved via modelspec once the provider plugin is
imported) or a concrete model interface. It resolves the strings and delegates
to NewAgentSession — nothing you can do with Config is unavailable in the
typed form.
func NewSession(cfg Config) (*AgentSession, error)
func MustNewSession(cfg Config) *AgentSessionsession, err := agents.NewSession(agents.Config{
STT: "openai/gpt-4o-mini-transcribe",
LLM: "openai/gpt-4o",
TTS: "openai/tts-1:alloy",
// or a concrete model: LLM: openai.NewLLM(openai.LLMOptions{...})
})Config fields: STT/LLM/TTS/VAD (any — spec string or model), plus
Turn, Tools, Input, Output, UserData, MaxToolSteps, Telemetry,
SessionState, and the State* fields — the same meanings as AgentSessionOptions.
AgentSessionOptions
| Field | Type | Notes |
|---|---|---|
STT / VAD / LLM / TTS | model interfaces | Session-default models; per-agent overrides win when set. |
Input | SessionInput | Audio transport.AudioInput, Video transport.VideoInput. |
Output | SessionOutput | Audio transport.AudioOutput, Text transport.TextOutput. |
Tools | any | Session-level tools (tool.Entry, []tool.Entry, Toolset, or *tool.Context). |
Turn | TurnOptions | Turn-detection, endpointing, interruption — see TurnOptions. |
MaxToolSteps | int | Tool-response rounds per reply before the model must answer. Defaults to 3. |
UserData | any | Arbitrary per-session data, reachable from tools via RunContext. |
Telemetry | telemetry.Tracer | Sink-neutral spans/metrics; defaults to no-op. |
SessionState | sessionstate.Provider | Durable state + context compilation; nil uses the in-memory default. |
StateGroupID / StateUserID / StateSessionID / StateMetadata | Identify the session to a state provider (ignored by the default). |
Lifecycle
func (s *AgentSession) Start(ctx context.Context, opts StartOptions) error
func (s *AgentSession) Started() bool
func (s *AgentSession) Close(ctx context.Context) errorStartOptions carries the initial Agent, optional Input/Output bindings,
and an optional Transport Binding. Start makes the agent active (running its
OnEnter) and begins the turn loop. Close tears the session down, running the
active agent's teardown under the same lock as an agent swap.
Transport bindings
A Binding lets a transport connect and supply the session I/O from inside
Start, instead of wiring Input/Output by hand:
type Binding interface {
Connect(ctx context.Context) (SessionInput, SessionOutput, error)
}
// Optional: transports that run post-start work (e.g. a room mirroring state)
type StartedBinding interface {
Binding
Started(s *AgentSession)
}
session.Start(ctx, agents.StartOptions{Agent: a, Transport: roomio.Bind(connectOpts, opts)})Start calls Connect before starting; explicit Input/Output fields
override the binding's I/O per field. If the binding implements io.Closer and
Start fails after Connect, its Close is invoked to release resources.
roomio.Bind is the built-in binding; console and WebSocket keep their own
Run/Handler entrypoints.
Driving speech
func (s *AgentSession) GenerateReply(opts GenerateReplyOptions) (*SpeechHandle, error)
func (s *AgentSession) Say(text string, opts SayOptions) (*SpeechHandle, error)
func (s *AgentSession) SayStream(text <-chan string, opts SayOptions) (*SpeechHandle, error)
func (s *AgentSession) Run(ctx context.Context, opts RunOptions) (*RunResult, error)
func (s *AgentSession) Interrupt()GenerateReplystarts an LLM-driven reply — see GenerateReplyOptions.Sayspeaks fixed text (no LLM);SayStreamspeaks a channel of text chunks.Rungenerates one reply and blocks until playout finishes, returning aRunResult— a turn/test helper.Interruptstops the current playout.
Speech is scheduled one SpeechHandle at a time, so replies never overlap.
Agents & models
func (s *AgentSession) CurrentAgent() *Agent
func (s *AgentSession) UpdateAgent(ctx context.Context, agent *Agent) error
func (s *AgentSession) EffectiveModels() EffectiveModels
func (s *AgentSession) EffectiveTools(ctx context.Context) (*tool.Context, error)
func (s *AgentSession) History() *llm.ChatContextUpdateAgent performs a hard handoff (cancels in-flight speech, runs
OnExit/OnEnter). Swaps are serialised against Close and tool-driven
handoffs. EffectiveModels returns the resolved STT/VAD/LLM/TTS for the current
agent (override else session default).
State & events
func (s *AgentSession) AgentState() AgentState
func (s *AgentSession) UserState() UserState
func (s *AgentSession) SetUserState(state UserState)
func (s *AgentSession) UserData() any
func (s *AgentSession) SetUserData(userData any)
func (s *AgentSession) SubscribeEvents(ctx context.Context, buffer int) events.Subscription[Event]
func (s *AgentSession) SubscribeAgentState(ctx context.Context) events.Subscription[AgentState]
func (s *AgentSession) SubscribeUserState(ctx context.Context) events.Subscription[UserState]AgentState is initializing → idle → listening → thinking → speaking → closed;
UserState is listening / speaking / away.
Event types (Event.Type) include: item_added, agent_state_changed,
user_state_changed, user_input_transcribed, metrics_collected,
usage_updated, speech_created, agent_false_interruption,
user_turn_exceeded, error, close.
Observing the session
SubscribeEvents returns a subscription you range over; the buffer argument
sizes its channel (events are dropped for a slow consumer past the buffer). This
is the primary way to log or react to a session:
sub := session.SubscribeEvents(ctx, 32)
defer sub.Close()
for ev := range sub.Events {
switch ev.Type {
case agents.EventItemAdded:
log.Printf("item: %v", ev.Item)
case agents.EventError:
log.Printf("session error: %v", ev.Error)
case agents.EventClose:
return
}
}SubscribeAgentState / SubscribeUserState are narrower streams of just the
state transitions.
Reading history
History() returns the shared conversation as an *llm.ChatContext. Iterate its
items to inspect what was said — for logging, summarization, or debugging:
for _, item := range session.History().Items() {
if m, ok := item.(*llm.ChatMessage); ok {
log.Printf("%s: %s", m.Role, m.TextContent())
}
}History is shared across agents in the session, not per-agent — a handoff
doesn't reset it. To seed prior turns, set AgentOptions.ChatContext when
building the initial agent.