Runner provides the interface to run Agents, responsible for session management and event stream processing. The core responsibilities of Runner are: obtain or create sessions, generate an Invocation ID, call Agent.Run, process the returned event stream, and append non-partial response events to the session.
🎯 Key Features
💾 Session Management: Obtain/create sessions via sessionService, using inmemory.NewSessionService() by default.
🔄 Event Handling: Receive Agent event streams and append non-partial response events to the session.
🆔 ID Generation: Automatically generate Invocation IDs and event IDs.
📊 Observability Integration: Integrates telemetry/trace to automatically record spans.
✅ Completion Event: Generates a runner-completion event after the Agent event stream ends.
# Enter the example directory.cdexamples/runner
# Set API key.exportOPENAI_API_KEY="your-api-key"# Basic run.gorunmain.go
# Use Redis session.dockerrun-d-p6379:6379redis:alpine
gorunmain.go-sessionredis
# Custom model.gorunmain.go-model"gpt-4o-mini"
💬 Interactive Features
After running the example, the following special commands are supported:
/history - Ask AI to show conversation history.
/new - Start a new session (reset conversation context).
/exit - End the conversation.
When the AI uses tools, detailed invocation processes will be displayed:
// Execute a single conversation.eventChan,err:=r.Run(ctx,userID,sessionID,message,options...)// With run options (currently RunOptions is an empty struct, reserved for future use).eventChan,err:=r.Run(ctx,userID,sessionID,message)
Provide Conversation History (auto-seed + session reuse)
If your upstream service maintains the conversation and you want the agent to
see that context, you can pass a full history ([]model.Message) directly. The
runner will seed an empty session with that history automatically and then
merge in new session events.
Option A: Use the convenience helper runner.RunWithMessages
msgs:=[]model.Message{model.NewSystemMessage("You are a helpful assistant."),model.NewUserMessage("First user input"),model.NewAssistantMessage("Previous assistant reply"),model.NewUserMessage("What’s the next step?"),}ch,err:=runner.RunWithMessages(ctx,r,userID,sessionID,msgs,agent.WithRequestID("request-ID"))
Example: examples/runwithmessages (uses RunWithMessages; runner auto-seeds and
continues reusing the session)
Option B: Pass via RunOption explicitly (same philosophy as ADK Python)
msgs:=[]model.Message{/* as above */}ch,err:=r.Run(ctx,userID,sessionID,model.Message{},agent.WithMessages(msgs))
When []model.Message is provided, the runner persists that history into the
session on first use (if empty). The content processor does not read this
option; it only derives messages from session events (or falls back to the
single invocation.Message if the session has no events). RunWithMessages
still sets invocation.Message to the latest user turn so graph/flow agents
that inspect it continue to work.
// Configuration options supported by Redis.sessionService,err:=redis.NewService(redis.WithRedisClientURL("redis://localhost:6379"),redis.WithSessionEventLimit(1000),// Limit number of session events.// redis.WithRedisInstance("redis-instance"), // Or use an instance name.)
🤖 Agent Configuration
Runner's core responsibility is to manage the Agent execution flow. A created Agent needs to be executed via Runner.
// Create a basic Agent (see agent.md for detailed configuration).agent:=llmagent.New("assistant",llmagent.WithModel(model),llmagent.WithInstruction("You are a helpful AI assistant."))// Execute Agent with Runner.r:=runner.NewRunner("my-app",agent)
Generation Configuration
Runner passes generation configuration to the Agent:
// Create tools (see tool.md for detailed configuration).tools:=[]tool.Tool{function.NewFunctionTool(myFunction,function.WithName("my_tool")),// More tools...}// Add tools to the Agent.agent:=llmagent.New("assistant",llmagent.WithModel(model),llmagent.WithTools(tools))// Runner runs the Agent configured with tools.r:=runner.NewRunner("my-app",agent)
Tool invocation flow: Runner itself does not directly handle tool invocation. The flow is as follows:
Pass tools: Runner passes context to the Agent via Invocation.
Agent processing: Agent.Run handles the tool invocation logic.
Event forwarding: Runner receives the event stream returned by the Agent and forwards it.
Session recording: Append non-partial response events to the session.
Multi-Agent Support
Runner can execute complex multi-Agent structures (see multiagent.md for details):
import"trpc.group/trpc-go/trpc-agent-go/agent/chainagent"// Create a multi-Agent pipeline.multiAgent:=chainagent.New("pipeline",chainagent.WithSubAgents([]agent.Agent{agent1,agent2}))// Execute with the same Runner.r:=runner.NewRunner("multi-app",multiAgent)
// The Invocation created by Runner contains the following fields.invocation:=agent.NewInvocation(agent.WithInvocationAgent(r.agent),// Agent instance.agent.WithInvocationSession(&session.Session{ID:"session-001"}),// Session object.agent.WithInvocationEndInvocation(false),// End flag.agent.WithInvocationMessage(model.NewUserMessage("User input")),// User message.agent.WithInvocationRunOptions(ro),// Run options.)// Note: Invocation also includes other fields such as AgentName, Branch, Model,// TransferInfo, AgentCallbacks, ModelCallbacks, ToolCallbacks, etc.,// but these fields are used and managed internally by the Agent.
// Handle errors from Runner.Run.eventChan,err:=r.Run(ctx,userID,sessionID,message,agent.WithRequestID("request-ID"))iferr!=nil{log.Printf("Runner execution failed: %v",err)returnerr}// Handle errors in the event stream.forevent:=rangeeventChan{ifevent.Error!=nil{log.Printf("Event error: %s",event.Error.Message)continue}// Handle normal events.}
// Use context to control lifecycle.ctx,cancel:=context.WithCancel(context.Background())defercancel()// Ensure all events are consumed.eventChan,err:=r.Run(ctx,userID,sessionID,message)iferr!=nil{returnerr}forevent:=rangeeventChan{// Process events.ifevent.Done{break}}
import("context""fmt""trpc.group/trpc-go/trpc-agent-go/model""trpc.group/trpc-go/trpc-agent-go/runner")// Check whether Runner works properly.funccheckRunner(rrunner.Runner,ctxcontext.Context)error{testMessage:=model.NewUserMessage("test")eventChan,err:=r.Run(ctx,"test-user","test-session",testMessage)iferr!=nil{returnfmt.Errorf("Runner.Run failed: %v",err)}// Check the event stream.forevent:=rangeeventChan{ifevent.Error!=nil{returnfmt.Errorf("Received error event: %s",event.Error.Message)}ifevent.Done{break}}returnnil}
📝 Summary
The Runner component is a core part of the tRPC-Agent-Go framework, providing complete conversation management and Agent orchestration capabilities. By properly using session management, tool integration, and event handling, you can build powerful intelligent conversational applications.