Autonomous Agent Loops
Build robust ReAct autonomous agent loops with termination conditions, state history management, and recursion limits.
TL;DR
- Implement autonomous decision cycles using the
react-patternloop. - Enforce strict termination limits using a configurable
max-iterationscounter. - Accumulate conversational observations inside a persistent
state-historyarray.
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 agent execution loops at a maximum threshold using
max-iterationsto prevent runaway token expenditure. - Store intermediate thoughts and tool results in an append-only
state-historyarray to give the agent persistent memory.
Warnings
- Never run agent loops without hard iteration ceilings because cyclic reasoning traps can rapidly consume your
api-credits. - Avoid unbounded memory accumulation by summarizing or pruning tool outputs in multi-turn loops to avoid
context-windowoverflow.
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.