Autonomous Agent Loops
Build robust ReAct autonomous agent loops with termination conditions, state history management, and recursion limits.
TL;DR
- Call the model, run its chosen tool, append the
observation, repeat. - Cap every loop with a
maxIterationscounter so runaway agents stop. - Store each observation in a
historyarray for the model to read.
ReAct Loop Architecture
While Loop FrameworkCore iterative dispatch cycle powering autonomous reasoning.
let iterations = 0;
const maxIterations = 10;
while (iterations < maxIterations) {
iterations++;
const res = await callModel(messages);
if (!res.hasToolUse) return res.finalAnswer;
await handleTools(res.toolUse);
}Termination EvaluatorEvaluate whether agent has satisfied original user objective.
function isComplete(res: ModelResponse): boolean {
// Stop reason is end_turn and no pending tools
return res.stop_reason === 'end_turn' &&
!res.content.some(b => b.type === 'tool_use');
}Recursion Depth GuardThrow explicit boundary exception if iteration ceiling breached.
if (iterations >= maxIterations) {
logger.warn('Agent exceeded iteration ceiling');
return 'Agent reached maximum iteration limit.';
}State And Memory Management
Append-Only HistoryMaintain chronological audit trail of agent steps and data.
messages.push({
role: 'assistant',
content: step.content,
});
messages.push({
role: 'user',
content: step.toolResults,
});Observation CompressionTruncate oversized tool payloads to preserve context budget.
function compressResult(raw: string, maxChars = 1500) {
if (raw.length <= maxChars) return raw;
return raw.slice(0, maxChars) + '... [truncated]';
}Scratchpad ReflectionInject periodic evaluation directives into conversational state.
if (iterations === 5) {
messages.push({
role: 'user',
content: 'Pause and evaluate progress to goal.',
});
}Loop Defenses And Safeguards
Repetition DetectorDetect cyclic tool calling and break repetitive loops.
const isLooping = history.slice(-3).every(
h => h.name === currentTool && h.args === currentArgs
);
if (isLooping) {
injectWarning('Repeated action detected.');
}Token Spend CapTrack cumulative token usage and abort if budget exceeded.
totalTokens += res.usage.total_tokens;
if (totalTokens > 50000) {
throw new Error('Agent exceeded token budget');
}Graceful Fallback ExitReturn partial summary when iteration limits are encountered.
return await synthesizeSummary({
originalGoal: goal,
completedActions: executedSteps,
});Production Agent Observability
Step Telemetry EventEmit telemetry event after each completed reasoning turn.
telemetry.track('agent_step', {
iteration: iterations,
tool: currentTool,
tokens: res.usage.total_tokens,
durationMs: stepDuration,
});Agent Run State MachineTrack lifecycle states: starting, running, paused, failed.
type AgentState =
'idle' | 'running' | 'paused' | 'done';
let state: AgentState = 'running';
// Expose state via websocket for UI visualizationCancellable AbortSignalPermit user to cancel running agent loop at any step.
if (abortSignal.aborted) {
throw new Error('Agent execution cancelled by user');
}Tips
- Cap every agent loop with a
maxIterationscounter so a confused agent stops before it burns through your token budget. - Append each thought and tool result to a
historyarray so the agent remembers what it already tried.
Warnings
- Never run a
whileloop without a hard iteration ceiling, because a reasoning cycle can drain your API credits in minutes. - Avoid letting
historygrow forever, because long tool outputs eventually overflow the context window unless you summarize or prune them.
In Practice
Executes a bounded while loop that reasons, invokes tools, tracks state, and terminates upon completion.
- Initialize conversation state and iteration counter.
- Execute while loop with hard iteration boundary limit.
- Inspect model response for tool invocation requests.
- Append tool execution results to state and continue loop.
async function runAgent(goal: string, maxSteps = 3) {
const h = [{ role: 'user', text: goal }];
let step = 0;
while (step < maxSteps) {
step++;
const res = await callMock(h);
if (res.type === 'answer') return res.text;
h.push({ role: 'assistant', text: res.tool });
const out = `Done: ${res.tool}`;
h.push({ role: 'user', text: out });
}
return 'Exceeded maximum steps';
}
async function callMock(h: any[]) {
return h.length < 2
? { type: 'tool', tool: 'search' }
: { type: 'answer', text: 'Docs verified' };
}
console.log(await runAgent('Analyze logs'));FAQ
ReAct stands for Reasoning and Acting. The model alternates between reasoning about the current situation, executing an action (calling a tool), observing the outcome, and repeating until the objective is accomplished.
The loop terminates when the model returns a final text answer without requesting further tool use, when the maximum iteration threshold is reached, or when an unrecoverable runtime exception occurs.
Track recent tool invocations and their arguments. If the exact same tool and parameters appear three times in sequence, inject a system warning urging the agent to try an alternate approach.