Skip to content

Commit cffbdfc

Browse files
authored
Merge pull request #2208 from cogvel/fix/auto-stop-max-iterations
feat(runtime): add auto-stop for max iterations in non-interactive mode
2 parents 9dfb2e8 + 039447f commit cffbdfc

File tree

5 files changed

+33
-0
lines changed

5 files changed

+33
-0
lines changed

pkg/a2a/adapter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func runDockerAgent(ctx agent.InvocationContext, t *team.Team, agentName string,
5050
session.WithMaxConsecutiveToolCalls(a.MaxConsecutiveToolCalls()),
5151
session.WithMaxOldToolCallTokens(a.MaxOldToolCallTokens()),
5252
session.WithToolsApproved(true),
53+
session.WithNonInteractive(true),
5354
)
5455

5556
// Create runtime

pkg/evaluation/save.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func SessionFromEvents(events []map[string]any, title string, questions []string
5959
sess := session.New(
6060
session.WithTitle(title),
6161
session.WithToolsApproved(true),
62+
session.WithNonInteractive(true),
6263
)
6364

6465
// Add user questions as initial messages.

pkg/mcp/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ func CreateToolHandler(t *team.Team, agentName string) func(context.Context, *mc
164164
session.WithMaxOldToolCallTokens(ag.MaxOldToolCallTokens()),
165165
session.WithUserMessage(input.Message),
166166
session.WithToolsApproved(true),
167+
session.WithNonInteractive(true),
167168
)
168169

169170
rt, err := runtime.New(t,

pkg/runtime/loop.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
190190
r.executeNotificationHooks(ctx, a, sess.ID, "warning", maxIterMsg)
191191
r.executeOnUserInputHooks(ctx, sess.ID, "max iterations reached")
192192

193+
// In non-interactive mode (e.g. MCP server), auto-stop instead of
194+
// blocking forever waiting for user input.
195+
if sess.NonInteractive {
196+
slog.Debug("Auto-stopping after max iterations (non-interactive)", "agent", a.Name())
197+
198+
assistantMessage := chat.Message{
199+
Role: chat.MessageRoleAssistant,
200+
Content: fmt.Sprintf(
201+
"Execution stopped after reaching the configured max_iterations limit (%d).",
202+
runtimeMaxIterations,
203+
),
204+
CreatedAt: time.Now().Format(time.RFC3339),
205+
}
206+
207+
addAgentMessage(sess, a, &assistantMessage, events)
208+
return
209+
}
210+
193211
// Wait for user decision (resume / reject)
194212
select {
195213
case req := <-r.resumeChan:

pkg/session/session.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ type Session struct {
8686
// ToolsApproved is a flag to indicate if the tools have been approved
8787
ToolsApproved bool `json:"tools_approved"`
8888

89+
// NonInteractive indicates the session is running in a non-interactive context
90+
// (e.g. MCP server, A2A adapter, evaluation framework) where there is no user
91+
// to provide input. This is distinct from ToolsApproved which can also be set
92+
// in interactive TUI sessions when a user approves all tools.
93+
NonInteractive bool `json:"non_interactive,omitempty"`
94+
8995
// HideToolResults is a flag to indicate if tool results should be hidden
9096
HideToolResults bool `json:"hide_tool_results"`
9197

@@ -544,6 +550,12 @@ func WithToolsApproved(toolsApproved bool) Opt {
544550
}
545551
}
546552

553+
func WithNonInteractive(nonInteractive bool) Opt {
554+
return func(s *Session) {
555+
s.NonInteractive = nonInteractive
556+
}
557+
}
558+
547559
func WithHideToolResults(hideToolResults bool) Opt {
548560
return func(s *Session) {
549561
s.HideToolResults = hideToolResults

0 commit comments

Comments
 (0)