@@ -32,6 +32,10 @@ import (
3232 mcptools "github.com/docker/cagent/pkg/tools/mcp"
3333)
3434
35+ type SessionStore interface {
36+ UpdateSession (ctx context.Context , sess * session.Session ) error
37+ }
38+
3539// UnwrapMCPToolset extracts an MCP toolset from a potentially wrapped StartableToolSet.
3640// Returns the MCP toolset if found, or nil if the toolset is not an MCP toolset.
3741func UnwrapMCPToolset (toolset tools.ToolSet ) * mcptools.Toolset {
@@ -94,16 +98,18 @@ type Runtime interface {
9498 CurrentWelcomeMessage (ctx context.Context ) string
9599 // EmitStartupInfo emits initial agent, team, and toolset information for immediate display
96100 EmitStartupInfo (ctx context.Context , events chan Event )
101+
97102 // RunStream starts the agent's interaction loop and returns a channel of events
98103 RunStream (ctx context.Context , sess * session.Session ) <- chan Event
99104 // Run starts the agent's interaction loop and returns the final messages
100105 Run (ctx context.Context , sess * session.Session ) ([]session.Message , error )
101106 // Resume allows resuming execution after user confirmation
102107 Resume (ctx context.Context , confirmationType ResumeType )
103- // Summarize generates a summary for the session
104- Summarize (ctx context.Context , sess * session.Session , events chan Event )
105108 // ResumeElicitation sends an elicitation response back to a waiting elicitation request
106109 ResumeElicitation (_ context.Context , action tools.ElicitationAction , content map [string ]any ) error
110+
111+ // Summarize generates a summary for the session
112+ Summarize (ctx context.Context , sess * session.Session , events chan Event )
107113}
108114
109115type ModelStore interface {
@@ -121,7 +127,6 @@ type LocalRuntime struct {
121127 toolMap map [string ]ToolHandler
122128 team * team.Team
123129 currentAgent string
124- rootSessionID string // Root session ID for OAuth state encoding (preserved across sub-sessions)
125130 resumeChan chan ResumeType
126131 tracer trace.Tracer
127132 modelsStore ModelStore
@@ -133,6 +138,7 @@ type LocalRuntime struct {
133138 elicitationEventsChannelMux sync.RWMutex // Protects elicitationEventsChannel
134139 ragInitialized atomic.Bool
135140 titleGen * titleGenerator
141+ sessionStore SessionStore
136142}
137143
138144type streamResult struct {
@@ -158,12 +164,6 @@ func WithManagedOAuth(managed bool) Opt {
158164 }
159165}
160166
161- func WithRootSessionID (sessionID string ) Opt {
162- return func (r * LocalRuntime ) {
163- r .rootSessionID = sessionID
164- }
165- }
166-
167167// WithTracer sets a custom OpenTelemetry tracer; if not provided, tracing is disabled (no-op).
168168func WithTracer (t trace.Tracer ) Opt {
169169 return func (r * LocalRuntime ) {
@@ -183,6 +183,12 @@ func WithModelStore(store ModelStore) Opt {
183183 }
184184}
185185
186+ func WithSessionStore (store SessionStore ) Opt {
187+ return func (r * LocalRuntime ) {
188+ r .sessionStore = store
189+ }
190+ }
191+
186192// New creates a new runtime for an agent and its team
187193func New (agents * team.Team , opts ... Opt ) (* LocalRuntime , error ) {
188194 modelsStore , err := modelsdev .NewStore ()
@@ -199,6 +205,7 @@ func New(agents *team.Team, opts ...Opt) (*LocalRuntime, error) {
199205 modelsStore : modelsStore ,
200206 sessionCompaction : true ,
201207 managedOAuth : true ,
208+ sessionStore : session .NewInMemorySessionStore (),
202209 }
203210
204211 for _ , opt := range opts {
@@ -610,6 +617,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
610617 CreatedAt : time .Now ().Format (time .RFC3339 ),
611618 }
612619 sess .AddMessage (session .NewAgentMessage (a , & assistantMessage ))
620+ _ = r .sessionStore .UpdateSession (ctx , sess )
613621 return
614622 }
615623 case <- ctx .Done ():
@@ -707,6 +715,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
707715 }
708716
709717 sess .AddMessage (session .NewAgentMessage (a , & assistantMessage ))
718+ _ = r .sessionStore .UpdateSession (ctx , sess )
710719 slog .Debug ("Added assistant message to session" , "agent" , a .Name (), "total_messages" , len (sess .GetAllMessages ()))
711720 } else {
712721 slog .Debug ("Skipping empty assistant message (no content and no tool calls)" , "agent" , a .Name ())
@@ -1023,14 +1032,14 @@ func (r *LocalRuntime) processToolCalls(ctx context.Context, sess *session.Sessi
10231032 r .runAgentTool (callCtx , def .handler , sess , toolCall , def .tool , events , a )
10241033 case ResumeTypeReject :
10251034 slog .Debug ("Resume signal received, rejecting tool handler" , "tool" , toolCall .Function .Name , "session_id" , sess .ID )
1026- r .addToolRejectedResponse (sess , toolCall , def .tool , events )
1035+ r .addToolRejectedResponse (ctx , sess , toolCall , def .tool , events )
10271036 }
10281037 case <- callCtx .Done ():
10291038 slog .Debug ("Context cancelled while waiting for resume" , "tool" , toolCall .Function .Name , "session_id" , sess .ID )
10301039 // Synthesize cancellation responses for the current and any remaining tool calls
1031- r .addToolCancelledResponse (sess , toolCall , def .tool , events )
1040+ r .addToolCancelledResponse (ctx , sess , toolCall , def .tool , events )
10321041 for j := i + 1 ; j < len (calls ); j ++ {
1033- r .addToolCancelledResponse (sess , calls [j ], def .tool , events )
1042+ r .addToolCancelledResponse (ctx , sess , calls [j ], def .tool , events )
10341043 }
10351044 callSpan .SetStatus (codes .Ok , "tool call canceled by user" )
10361045 return
@@ -1066,17 +1075,17 @@ func (r *LocalRuntime) processToolCalls(ctx context.Context, sess *session.Sessi
10661075 r .runTool (callCtx , tool , toolCall , events , sess , a )
10671076 case ResumeTypeReject :
10681077 slog .Debug ("Resume signal received, rejecting tool handler" , "tool" , toolCall .Function .Name , "session_id" , sess .ID )
1069- r .addToolRejectedResponse (sess , toolCall , tool , events )
1078+ r .addToolRejectedResponse (ctx , sess , toolCall , tool , events )
10701079 }
10711080
10721081 slog .Debug ("Added tool response to session" , "tool" , toolCall .Function .Name , "session_id" , sess .ID , "total_messages" , len (sess .GetAllMessages ()))
10731082 break toolLoop
10741083 case <- callCtx .Done ():
10751084 slog .Debug ("Context cancelled while waiting for resume" , "tool" , toolCall .Function .Name , "session_id" , sess .ID )
10761085 // Synthesize cancellation responses for the current and any remaining tool calls
1077- r .addToolCancelledResponse (sess , toolCall , tool , events )
1086+ r .addToolCancelledResponse (ctx , sess , toolCall , tool , events )
10781087 for j := i + 1 ; j < len (calls ); j ++ {
1079- r .addToolCancelledResponse (sess , calls [j ], tool , events )
1088+ r .addToolCancelledResponse (ctx , sess , calls [j ], tool , events )
10801089 }
10811090 callSpan .SetStatus (codes .Ok , "tool call canceled by user" )
10821091 return
@@ -1146,6 +1155,7 @@ func (r *LocalRuntime) runTool(ctx context.Context, tool tools.Tool, toolCall to
11461155 CreatedAt : time .Now ().Format (time .RFC3339 ),
11471156 }
11481157 sess .AddMessage (session .NewAgentMessage (a , & toolResponseMsg ))
1158+ _ = r .sessionStore .UpdateSession (ctx , sess )
11491159}
11501160
11511161func (r * LocalRuntime ) runAgentTool (ctx context.Context , handler ToolHandlerFunc , sess * session.Session , toolCall tools.ToolCall , tool tools.Tool , events chan Event , a * agent.Agent ) {
@@ -1199,9 +1209,10 @@ func (r *LocalRuntime) runAgentTool(ctx context.Context, handler ToolHandlerFunc
11991209 CreatedAt : time .Now ().Format (time .RFC3339 ),
12001210 }
12011211 sess .AddMessage (session .NewAgentMessage (a , & toolResponseMsg ))
1212+ _ = r .sessionStore .UpdateSession (ctx , sess )
12021213}
12031214
1204- func (r * LocalRuntime ) addToolRejectedResponse (sess * session.Session , toolCall tools.ToolCall , tool tools.Tool , events chan Event ) {
1215+ func (r * LocalRuntime ) addToolRejectedResponse (ctx context. Context , sess * session.Session , toolCall tools.ToolCall , tool tools.Tool , events chan Event ) {
12051216 a := r .CurrentAgent ()
12061217
12071218 result := "The user rejected the tool call."
@@ -1215,9 +1226,10 @@ func (r *LocalRuntime) addToolRejectedResponse(sess *session.Session, toolCall t
12151226 CreatedAt : time .Now ().Format (time .RFC3339 ),
12161227 }
12171228 sess .AddMessage (session .NewAgentMessage (a , & toolResponseMsg ))
1229+ _ = r .sessionStore .UpdateSession (ctx , sess )
12181230}
12191231
1220- func (r * LocalRuntime ) addToolCancelledResponse (sess * session.Session , toolCall tools.ToolCall , tool tools.Tool , events chan Event ) {
1232+ func (r * LocalRuntime ) addToolCancelledResponse (ctx context. Context , sess * session.Session , toolCall tools.ToolCall , tool tools.Tool , events chan Event ) {
12211233 a := r .CurrentAgent ()
12221234
12231235 result := "The tool call was canceled by the user."
@@ -1231,6 +1243,7 @@ func (r *LocalRuntime) addToolCancelledResponse(sess *session.Session, toolCall
12311243 CreatedAt : time .Now ().Format (time .RFC3339 ),
12321244 }
12331245 sess .AddMessage (session .NewAgentMessage (a , & toolResponseMsg ))
1246+ _ = r .sessionStore .UpdateSession (ctx , sess )
12341247}
12351248
12361249// startSpan wraps tracer.Start, returning a no-op span if the tracer is nil.
@@ -1418,6 +1431,7 @@ func (r *LocalRuntime) Summarize(ctx context.Context, sess *session.Session, eve
14181431 }
14191432 // Add the summary to the session as a summary item
14201433 sess .Messages = append (sess .Messages , session.Item {Summary : summary })
1434+ _ = r .sessionStore .UpdateSession (ctx , sess )
14211435 slog .Debug ("Generated session summary" , "session_id" , sess .ID , "summary_length" , len (summary ))
14221436 events <- SessionSummary (sess .ID , summary , r .currentAgent )
14231437}
0 commit comments