Agent Loop: Perception-Reasoning-Action-Reflection¶
The Core Abstraction¶
Every agentic system, regardless of framework or complexity, implements a loop with four phases:
- ┌─────────────────────────────────────────────┐
- PERCEIVE │
- Parse goal, read environment │
- ┬──────────────────────────┘
│
- ┌──────────────────▼──────────────────────────┐
- REASON │
- What should I do? (via LLM) │
- ┬──────────────────────────┘
│
- ┌──────────────────▼──────────────────────────┐
- ACT │
- Execute tool or generate output │
- ┬──────────────────────────┘
│
- ┌──────────────────▼──────────────────────────┐
- REFLECT │
- Is goal achieved? Did it work? │
- ┬──────────────────────────┘
│
- ┌──────────▼──────────┐
- Goal Achieved? │
- / \ │
- Y N ────────┐
│ │ │
- ┘ (Loop again)
▼
RETURN RESULT
Phase 1: Perceive¶
What it does: Understand the current state and what needs to happen
Inputs: - The goal (from user or system) - Current environment state - Available tools - Past experiences (from memory)
Outputs: - Formatted context for the LLM - Understanding of what can be done
Example:
def perceive(goal: str, state: Dict, tools: List) -> str:
"""Format current situation for reasoning"""
context = f"""
Goal: {goal}
Current State: {state}
Available Tools:
{format_tools(tools)}
Previous Attempts: {load_memory()}
"""
return context
Key Questions Answered: - What is the goal? - What's the current state? - What can I do? - What have I tried before?
Phase 2: Reason¶
What it does: Decide what action to take next
Inputs: - Perceived context from Phase 1 - Goal and constraints - System prompt guiding behavior
Outputs: - Decision on next action - Reasoning trace (why this action?) - Parameters for the action
Example:
def reason(context: str, system_prompt: str) -> Decision:
"""Use LLM to decide next action"""
response = llm.generate(
system_prompt=system_prompt,
user_message=context
)
# Parse response into structured decision
return parse_decision(response)
# Output: Decision(tool="search", args={"query": "..."})
Key Capabilities: - Understanding natural language goals - Analyzing complex situations - Selecting appropriate tools - Reasoning about trade-offs - Generating explanations
Important: This is where the LLM shines. It's reasoning step-by-step via token generation, not mystical neural magic.
Phase 3: Act¶
What it does: Execute the decision by calling tools
Inputs: - Decision from Phase 2 - Tool definitions - Current state
Outputs: - Tool result (success or error) - Updated state - Observation for reflection
Example:
def act(decision: Decision, tools: Dict) -> Observation:
"""Execute the decided action"""
try:
tool_func = tools[decision.tool]
result = tool_func(**decision.args)
return Observation(
success=True,
result=result,
tool=decision.tool
)
except Exception as e:
return Observation(
success=False,
error=str(e),
tool=decision.tool
)
Key Aspects: - Tool interface standardization (function calling) - Error handling and recovery - State updates - Observation recording
Phase 4: Reflect¶
What it does: Evaluate results and decide whether to continue
Inputs: - Original goal - Action taken - Result from tool - History of attempts
Outputs: - Did we achieve the goal? - Should we continue? - What did we learn? - What's next?
Example:
def reflect(goal: str, result: Observation, history: List) -> Decision:
"""Evaluate whether to continue or stop"""
if goal_achieved(goal, result):
return Decision(action="STOP", reason="Goal achieved")
elif max_iterations_reached(history):
return Decision(action="STOP", reason="Max iterations")
elif error_is_recoverable(result.error):
return Decision(action="CONTINUE",
reason="Error recoverable, will retry")
else:
return Decision(action="STOP", reason="Unrecoverable error")
Three Possible Outcomes: 1. Goal Achieved → Stop and return result 2. Error but Recoverable → Try different approach 3. Unrecoverable Error → Escalate or fail gracefully
Complete Loop Example¶
class AgentLoop:
def run(self, goal: str) -> Result:
"""Execute agent loop until termination"""
state = initial_state(goal)
history = []
while True:
# Phase 1: Perceive
context = self.perceive(goal, state)
history.append(("perceive", context))
# Phase 2: Reason
decision = self.reason(context)
history.append(("reason", decision))
# Phase 3: Act
observation = self.act(decision)
history.append(("act", observation))
state = update_state(state, observation)
# Phase 4: Reflect
reflection = self.reflect(goal, observation, history)
history.append(("reflect", reflection))
# Check termination condition
if reflection.should_stop():
return Result(
goal=goal,
result=observation.result,
history=history,
success=reflection.success
)
# Continue loop with updated state
Loop Variations¶
The Simple Loop (MVP)¶
Perceive → Reason → Act → Reflect → (Continue or Stop)
The Planning Loop¶
Perceive → Reason → Plan → Execute → Reflect → (Continue or Stop)
The Reflection Loop¶
Perceive → Reason → Act → Reflect → Critique → (Adjust or Continue)
Loop Behavior Patterns¶
Fast Loop (Quick Iteration)¶
Perception cost: Low (quick state check)
Reasoning cost: Low (simple decision)
Action cost: High (actual work done here)
Reflection cost: Low (binary check)
Speed: Fast
Quality: Medium
Cost: Low-Medium
Careful Loop (Deliberate)¶
Perception cost: Medium (detailed analysis)
Reasoning cost: High (deep reasoning)
Action cost: Low (only when certain)
Reflection cost: High (detailed evaluation)
Speed: Slow
Quality: High
Cost: High
State Management During Loop¶
State must include: - Current goal/subgoal - Completed steps - Current observations - Available resources - Time/iteration count
State should NOT include: - Full conversation history (too expensive) - All possible tools (only relevant ones) - Unrelated context
class AgentState:
goal: str
completed_steps: List[str]
current_observation: str
iteration_count: int
available_tools: List[str]
confidence_score: float
def should_continue(self):
return (self.iteration_count < MAX_ITERATIONS
and self.confidence_score > THRESHOLD)
Termination Conditions¶
An agent loop terminates when:
| Condition | Outcome |
|---|---|
| Goal achieved | Success ✅ |
| Max iterations reached | Timeout ⏱️ |
| Unrecoverable error | Failure ❌ |
| Cost limit exceeded | Resource limit 💰 |
| Escalation required | Handoff to human 👤 |
| Time limit exceeded | Deadline ⏰ |
Loop Efficiency Metrics¶
| Metric | Good Range | Why It Matters |
|---|---|---|
| Iterations per goal | 2-5 | Too many = inefficient, too few = incomplete |
| Time per iteration | 1-10 sec | Balance between speed and quality |
| Tool success rate | > 80% | High errors mean poor planning |
| Goal success rate | > 90% | Production-grade agents should succeed >90% |
Debugging Agent Loops¶
If agent gets stuck in loop: - Check termination conditions - Verify goal is achievable - Check if reflection is working - Add loop counter limit
If agent stops too early: - Check goal achievement logic - Verify reflection isn't too strict - Check if tools are actually working
If agent is slow: - Optimize perception (simpler state) - Use simpler reasoning model - Combine multiple actions per loop
Key Takeaways¶
- The loop is the execution model - Everything happens through repeated cycles
- Each phase serves a purpose - Removing any phase breaks the system
- Simplicity is powerful - Even basic loops solve real problems
- Reflection is critical - Without it, agents don't know when to stop
- State management is hard - Keep state lean and focused
Next Steps¶
- Read Design Philosophy - Principles for effective loops
- Jump To Patterns - See how loops manifest in patterns
Last Updated: August 9, 2026