Skip to content

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)
- Works for straightforward tasks - Single tool per iteration - Used in most 2025 production systems

The Planning Loop

Perceive → Reason → Plan → Execute → Reflect → (Continue or Stop)
- Plans multiple steps before acting - Better for complex multi-step tasks - More compute cost, better results for hard problems

The Reflection Loop

Perceive → Reason → Act → Reflect → Critique → (Adjust or Continue)
- Adds critiquing phase - Agent evaluates and improves own output - Used for quality-critical tasks


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

  1. The loop is the execution model - Everything happens through repeated cycles
  2. Each phase serves a purpose - Removing any phase breaks the system
  3. Simplicity is powerful - Even basic loops solve real problems
  4. Reflection is critical - Without it, agents don't know when to stop
  5. State management is hard - Keep state lean and focused

Next Steps


Last Updated: August 9, 2026