The Agent Loop
The agent loop is the beating heart of any harness. It's the control structure that determines how the agent thinks, acts, and adapts until it completes a task or needs human input.
The Core Cycle
At its simplest, every agentic harness runs some variation of this loop:
graph TD
A[User Input/Goal] --> B[Build Context]
B --> C[Think/Plan]
C --> D{Need to Act?}
D -->|Yes| E[Select Tools]
E --> F[Check Permissions]
F --> G[Execute Tools]
G --> H[Observe Results]
H --> I[Update State]
I --> B
D -->|No| J[Respond to User]
J --> K{Task Complete?}
K -->|No| B
K -->|Yes| L[Done]
style A fill:#e1f5fe
style C fill:#fff3e0
style G fill:#f3e5f5
style J fill:#e8f5e9
style L fill:#c8e6c9
Loop Phases
| Phase | Purpose | What Happens |
|---|---|---|
| 1. Input | Receive goal/task | User message, continuation, or sub-agent callback |
| 2. Context Building | Assemble relevant information | System prompt, history, files, memory, tools, project context |
| 3. Reasoning/Planning | Decide next action | Model thinks about what to do next given goal and context |
| 4. Tool Selection | Choose appropriate tools | Model returns tool calls with arguments |
| 5. Permission Check | Validate safety | Check against allowlists, rules, and approval requirements |
| 6. Execution | Run tools | Execute in sandbox/environment, handle errors |
| 7. Observation | Process results | Format results, check for errors, update understanding |
| 8. Evaluation | Decide whether to continue | Is task complete? Need more info? Blocked? |
| 9. Response | Communicate with user | Text response, summary, or ask for clarification |
Loop Variants
ReAct (Reason + Act)
The most common pattern. Model alternates between reasoning and acting.
while not done:
thought = model.think(context)
if thought.action == "tool_call":
result = execute(thought.tool, thought.args)
context.add_observation(result)
elif thought.action == "respond":
return thought.response
Plan-Execute
Plan the entire approach first, then execute steps systematically.
plan = model.plan(goal) # Decompose into steps
for step in plan:
result = execute_step(step, context)
context.update(result)
if needs_replan(result):
plan = model.replan(goal, plan, results)
return summarize(results)
Thought-Tool-Thought
Model can interleave multiple tool calls and reasoning in one turn.
response = model.generate(context, tools=tools)
for tool_call in response.tool_calls:
result = execute(tool_call)
context.add(tool_call, result)
# Model sees all results and decides next step
Control Flow Decisions
The harness must decide: - When to stop - Task complete, error unrecoverable, user asked to stop - When to ask user - Ambiguous requirements, destructive action, missing info - When to delegate - Task is complex, can be parallelized, needs specialization - When to replan - Approach not working, new information discovered - When to compact context - Approaching token limits
Loop Termination Conditions
| Condition | Description | Action |
|---|---|---|
| Task Complete | Goal achieved | Summarize and stop |
| User Response | Model gives final answer with no tool calls | Present response, wait for input |
| Max Turns | Exceeded turn limit | Stop with explanation |
| Max Cost | Exceeded budget | Stop with cost summary |
| User Interrupt | User cancels/stops | Gracefully stop |
| Error Threshold | Too many consecutive failures | Ask for help or stop |
| Timeout | Task taking too long | Warn and potentially stop |
State Between Iterations
The harness must maintain state across loop iterations:
interface AgentState {
sessionId: string;
messages: Message[]; // Conversation history
taskList: Todo[]; // Current tasks
toolResults: ToolResult[];
memory: Memory[];
filesRead: Set<string>;
subAgents: SubAgent[];
turnCount: number;
tokensUsed: TokenCount;
status: 'thinking' | 'tool_call' | 'responding' | 'done';
}
Implementation Patterns
Turn-Based Loop
async def run_loop(goal, max_turns=50):
state = initialize_state(goal)
for turn in range(max_turns):
context = build_context(state)
response = await model.generate(context)
if response.tool_calls:
for call in response.tool_calls:
if not has_permission(call):
await ask_user(call); continue
result = await execute_tool(call)
state.add_tool_result(call, result)
elif response.text:
state.add_message(response.text)
if is_complete(response): break
state.turn_count += 1
return state.summary()
Event-Driven Loop
More flexible, handles streaming and interrupts better.
class AgentLoop:
async def run(self, input):
await self.emit('start', input)
while self.should_continue():
await self.emit('thought')
action = await self.decide_next_action()
if action.type == 'tool':
await self.execute_tool(action)
elif action.type == 'respond':
await self.respond(action)
break
elif action.type == 'delegate':
await self.spawn_subagent(action)
await self.check_interrupts()
await self.emit('end')
Best Practices
- Keep the harness in control - The loop logic should be deterministic, not fully delegated to the model
- Limit turns - Prevent infinite loops with reasonable limits
- Checkpoint often - Save state after significant actions
- Handle interrupts - Allow user to pause/stop/correct
- Log everything - Every iteration should be traceable
- Compact aggressively - Don't let context grow unbounded
- Parallelize safely - Only parallelize independent tool calls
- Validate before acting - Permission checks before execution
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Runaway loops | Agent keeps calling tools without progress | Track tool call history, detect loops, add circuit breaker |
| Context bloat | Too much history, slow and expensive | Summarize old turns, prune irrelevant files |
| Blind execution | Running dangerous commands without checking | Permission system, dry-run mode |
| Lost state | Crash loses progress | Checkpointing after each turn/tool batch |
| Poor recovery | One tool failure derails everything | Try alternatives, ask user, continue if possible |
Related Notes
- Harness Architecture — Overall architecture
- Task Management — Planning and task tracking
- Error Recovery — Handling failures
- Sub Agents — Delegation
- Context Builder — Building context
Sources
- ReAct paper (Yao et al., 2022)
- Plan-and-Solve (Wang et al.)
- Anthropic - Building Effective Agents
- OpenAI Agents SDK patterns
- LangGraph agent patterns