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:
1 parent
c38b424f4b
commit
4325dfa874
6 files changed
+116
-20
No files matched your search
@@ -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)) {
|
||||
|
||||
Reference in new issue
Block a user