Loop Sessions
The loop-based regime implements a bounded reasoning-and-action pattern in which the model repeatedly selects the next admissible tool, observes the result, and continues until the task reaches a completion signal, an interactive pause, or a configured failure boundary.
Deliberative Step Contract
LoopAgentSession is defined in LLMAgents/AgenticSession.mjs. It is initialized over a tools object whose entries expose both a description and a handler. During construction, the runtime rejects any attempt to use the reserved names final_answer or cannot_complete as ordinary tool names and then injects those completion tools into the active tool surface automatically. The session also normalizes its execution options, including bounded defaults for maximum steps per turn, maximum error count, and retry count for expected-value validation.
When newPrompt() is called, the session may first run an optional preparation stage. That preparation stage is itself executed through a bounded loop session and may inject derived context into both the system prompt and the working user prompt before the main turn starts. The core turn is then created with explicit state for prompt text, final answer, retry count, used tools, and step history. The session appends the incoming prompt to its history and enters the running state.
From there, the runtime repeatedly builds a planner prompt through buildAgenticSessionPlannerPrompt() and asks the model for the next structured decision. The returned payload must resolve to a JSON object naming the next tool and the prompt that should be passed to that tool. The session therefore does not let arbitrary prose directly drive execution. Planning remains explicit at each step, even though the regime is adaptive rather than plan-first.
Intermediate Results and Interactive Continuation
The loop regime is more structured than a naive chat loop because every tool result is stored under a generated symbolic reference. Later tool prompts may reuse those earlier results through $$variable syntax, which is resolved immediately before the tool handler is called. This allows the planner to depend on explicit prior outputs without inlining large results back into the prompt. The session also records each tool call, the result reference associated with that call, and the evolving execution history.
Tool execution itself is mediated. Before a tool handler runs, the session temporarily sets agent.currentSession so the handler can access the live session when needed. After the call returns, the result is normalized for storage and written into the internal variable map. This temporary attachment is part of the runtime behavior, not merely a debugging convenience.
Interactive continuation is handled explicitly. If a tool result is a structured payload containing requiresConfirmation or requiresInput, the session moves into awaiting_input state, records the pending tool in history, and returns the interaction message. On the next prompt, the session first checks whether the new user message should continue that pending interaction. To do so, it uses interpretMessage() with the bounded intents accept, cancel, and update, together with a fresh-instruction heuristic. If the new message still belongs to the pending interaction, the session routes it back to the same tool rather than opening a new planner turn.
This pending-input behavior is what makes loop sessions usable as multi-turn governed workflows. In loop-based orchestration and Anthropic Skills, the surrounding subsystem may store the paused session in sessionMemory and resume it on the next user prompt.
Execution Bounds and Return Surface
The loop regime includes several independent stopping and recovery mechanisms. If a tool returns one of the reserved completion wrappers, the session converts that wrapper into the effective final answer. If the caller supplied an expected value for the turn, the session compares the returned completion against that expectation and may retry within a bounded number of attempts. If a tool repeats the same effective invocation and result several times in a row, the session stops rather than allowing trivial loops to continue. If tool errors or planner errors exceed the configured error budget, the session fails explicitly. If the step budget is exhausted, it returns a bounded failure response instead of continuing indefinitely.
The return surface is intentionally compact. getLastResult() returns the current last answer. getVariables() returns a small state object containing lastAnswer, the current status, and the number of failed turns. The session also exposes finalizeFailures(), which attempts to convert accumulated failed turns into a cannot_complete result through the reserved completion tool. The regime is therefore adaptive, but its externally visible surface remains narrow.
const session = await llmAgent.startLoopAgentSession(tools, 'Investigate the request', {
tier: 'plan',
maxStepsPerTurn: 8,
maxRetriesPerTurn: 3,
});
const answer = session.getLastResult();
const state = await session.getVariables();