agents-go
Reference

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) *AgentSession

High-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) *AgentSession
session, 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

FieldTypeNotes
STT / VAD / LLM / TTSmodel interfacesSession-default models; per-agent overrides win when set.
InputSessionInputAudio transport.AudioInput, Video transport.VideoInput.
OutputSessionOutputAudio transport.AudioOutput, Text transport.TextOutput.
ToolsanySession-level tools (tool.Entry, []tool.Entry, Toolset, or *tool.Context).
TurnTurnOptionsTurn-detection, endpointing, interruption — see TurnOptions.
MaxToolStepsintTool-response rounds per reply before the model must answer. Defaults to 3.
UserDataanyArbitrary per-session data, reachable from tools via RunContext.
Telemetrytelemetry.TracerSink-neutral spans/metrics; defaults to no-op.
SessionStatesessionstate.ProviderDurable state + context compilation; nil uses the in-memory default.
StateGroupID / StateUserID / StateSessionID / StateMetadataIdentify 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) error

StartOptions 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()
  • GenerateReply starts an LLM-driven reply — see GenerateReplyOptions.
  • Say speaks fixed text (no LLM); SayStream speaks a channel of text chunks.
  • Run generates one reply and blocks until playout finishes, returning a RunResult — a turn/test helper.
  • Interrupt stops 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.ChatContext

UpdateAgent 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.

On this page