Q68: sessions are bounded in time, tool calls per turn and goal audits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 11:50:26 -04:00
1 parent c38b424f4b
commit 4325dfa874
6 files changed
+116 -20

No files matched your search

+15 -8
View File
@@ -8,9 +8,11 @@ import type { AgentTurnMessage, ToolCall, ToolDefinition } from "./provider-tool
import { isComposioTool, executeComposioTool } from "./composio/composio-tool-bridge";
import { capToolOutput, compactToolHistory } from "./run-context-compaction";
import { fitContextBudget } from "./context-budget";
import { throwIfRunStopped, untilStopped } from "./run-control";
import { MAX_TOOL_CALLS_PER_TURN, throwIfRunStopped, untilStopped } from "./run-control";
const MAX_ITERATIONS = 12;
/** Each goal audit runs its own model loop, so a session gets only a couple. */
const MAX_GOAL_AUDITS_PER_SESSION = 2;
const STEP_LIMIT_TEXT = "I used this session's steps and will continue in the next one.";
export interface AgentRunLoopInput {
@@ -52,18 +54,14 @@ export function runTools(externalTools: ToolDefinition[]): ToolDefinition[] {
return [...AGENT_TOOLS, ...WORKSPACE_TOOLS, ...WEB_TOOLS, ...externalTools];
}
/**
* Drives a real tool-use loop against the live provider: prompt the model with
* the agent tools, execute every tool call it makes, feed results back, and
* repeat until the model returns a turn with no tool calls (it is done) or the
* iteration cap is hit (which throws rather than silently truncating).
*/
/** Runs the model with the agent tools until it stops calling them; step, time, tool-call and audit limits end it as a checkpoint or refuse the excess. */
export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopInput): Promise<AgentRunLoopResult> {
const allTools = runTools(input.externalTools ?? []);
const messages: AgentTurnMessage[] = [{ role: "user", text: input.initialUserTurn }];
let toolCallCount = 0;
let lastRequestId = "";
let lastText = "";
let goalAudits = 0;
for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration += 1) {
const compaction = compactToolHistory(messages);
@@ -106,8 +104,17 @@ export async function runAgentLoop(deps: AgentRunLoopDeps, input: AgentRunLoopIn
lastText = turn.text.trim() || lastText;
messages.push({ role: "assistant", text: turn.text, toolCalls: turn.toolCalls });
const results: Array<{ toolCallId: string; toolName: string; output: string }> = [];
for (const call of turn.toolCalls) {
for (const [index, call] of turn.toolCalls.entries()) {
throwIfRunStopped();
if (index >= MAX_TOOL_CALLS_PER_TURN) {
results.push({ toolCallId: call.id, toolName: call.name, output: `ERROR: not run: this turn asked for ${turn.toolCalls.length} tool calls; the limit is ${MAX_TOOL_CALLS_PER_TURN}. Repeat the ones you still need.` });
continue;
}
if (call.name === "complete_goal" && goalAudits >= MAX_GOAL_AUDITS_PER_SESSION) {
results.push({ toolCallId: call.id, toolName: call.name, output: `ERROR: this session already ran ${MAX_GOAL_AUDITS_PER_SESSION} goal audits. Close the gaps they named, then try again next session.` });
continue;
}
if (call.name === "complete_goal") goalAudits += 1;
toolCallCount += 1;
let output: string;
if (isComposioTool(call.name)) {