Introduction
Every AI agent, from the simplest automation to the most sophisticated multi-agent system, follows the same fundamental pattern: Perceive-Reason-Act. This loop is the heartbeat of agentic systems, repeating until the goal is achieved or the agent decides to stop.
The Universal Pattern: Perceive your environment, reason about what to do, act on your decision, observe the results, and repeat. Master this loop and you understand the foundation of all agent systems.
1def agent_loop(goal: str):
2 """The fundamental agent loop pattern."""
3 state = initialize_state(goal)
4
5 while not should_stop(state):
6 # 1. PERCEIVE: Gather context about the current situation
7 context = perceive(state)
8
9 # 2. REASON: Decide what action to take
10 action = reason(context, goal, available_tools)
11
12 # 3. ACT: Execute the chosen action
13 result = act(action)
14
15 # 4. OBSERVE: Update state based on results
16 state = observe(state, result)
17
18 return state.final_resultPerceive: Gathering Context
The perception phase gathers all relevant information the agent needs to make an informed decision. This includes:
- Goal state: What are we trying to achieve?
- Current state: Where are we now?
- History: What have we done so far?
- Environment: What's around us? (files, data, etc.)
- Constraints: What limitations exist?
1class PerceptionSystem:
2 """Gathers context for agent decision-making."""
3
4 def perceive(self, state: AgentState) -> Context:
5 """Compile all relevant context for the current decision."""
6
7 return Context(
8 # The goal we're working toward
9 goal=state.goal,
10
11 # Progress made so far
12 progress=state.progress_summary(),
13
14 # Recent actions and their results
15 recent_history=state.get_recent_actions(n=10),
16
17 # Relevant memories from long-term storage
18 relevant_memories=self.memory.recall(state.goal, k=5),
19
20 # Available tools and their descriptions
21 available_tools=self.get_tool_descriptions(),
22
23 # Current environment state (files, data, etc.)
24 environment=self.scan_environment(state),
25
26 # Any constraints or limitations
27 constraints=self.get_constraints(state),
28 )
29
30 def scan_environment(self, state: AgentState) -> dict:
31 """Scan the current working environment."""
32 return {
33 "current_directory": os.getcwd(),
34 "open_files": state.open_files,
35 "modified_files": state.modified_files,
36 "pending_changes": state.pending_changes,
37 }
38
39 def get_tool_descriptions(self) -> list[dict]:
40 """Get descriptions of all available tools."""
41 return [
42 {
43 "name": tool.name,
44 "description": tool.description,
45 "parameters": tool.parameters,
46 }
47 for tool in self.tools
48 ]Context Window Management
Reason: Making Decisions
The reasoning phase is where the LLM shines. Given the context, the agent must decide:
- What tool to use (or whether to use one at all)
- What parameters to pass to the chosen tool
- Whether the goal is achieved (and we should stop)
- Whether to ask for help (escalate to human)
1class ReasoningSystem:
2 """Uses LLM to decide the next action."""
3
4 def reason(self, context: Context) -> Action:
5 """Decide the next action based on context."""
6
7 # Build the decision prompt
8 prompt = self.build_decision_prompt(context)
9
10 # Call the LLM with tool definitions
11 response = self.llm.generate_with_tools(
12 prompt=prompt,
13 tools=context.available_tools,
14 system_prompt=self.system_prompt,
15 )
16
17 # Parse the response into an action
18 return self.parse_response(response)
19
20 def build_decision_prompt(self, context: Context) -> str:
21 """Construct the prompt for decision-making."""
22 return f"""
23You are an AI agent working to accomplish a goal.
24
25## Goal
26{context.goal}
27
28## Progress So Far
29{context.progress}
30
31## Recent Actions
32{self.format_history(context.recent_history)}
33
34## Relevant Context
35{self.format_memories(context.relevant_memories)}
36
37## Current Environment
38{self.format_environment(context.environment)}
39
40## Available Actions
41You can use the following tools:
42{self.format_tools(context.available_tools)}
43
44Or you can:
45- finish: Complete the task with a final result
46- ask_human: Request help or clarification
47
48## Instructions
49Based on the context above, decide your next action.
50Think step by step about what would best advance toward the goal.
51"""
52
53 def parse_response(self, response: dict) -> Action:
54 """Parse LLM response into a structured action."""
55 if response.get("tool_calls"):
56 tool_call = response["tool_calls"][0]
57 return Action(
58 type="tool",
59 name=tool_call["name"],
60 params=tool_call["input"],
61 )
62 elif "finish" in response.get("text", "").lower():
63 return Action(type="finish", result=response["text"])
64 else:
65 return Action(type="think", thought=response["text"])Reasoning Quality is Everything
Act: Taking Actions
The action phase executes the chosen action. This is where the agent interacts with the world:
1class ActionSystem:
2 """Executes agent actions."""
3
4 def act(self, action: Action) -> ActionResult:
5 """Execute the chosen action."""
6
7 match action.type:
8 case "tool":
9 return self.execute_tool(action.name, action.params)
10 case "finish":
11 return ActionResult(
12 success=True,
13 type="finish",
14 output=action.result,
15 )
16 case "ask_human":
17 return self.request_human_input(action.question)
18 case "think":
19 # Internal reasoning, no external action
20 return ActionResult(
21 success=True,
22 type="think",
23 output=action.thought,
24 )
25 case _:
26 return ActionResult(
27 success=False,
28 type="error",
29 error=f"Unknown action type: {action.type}",
30 )
31
32 def execute_tool(self, tool_name: str, params: dict) -> ActionResult:
33 """Execute a specific tool."""
34 tool = self.tools.get(tool_name)
35 if not tool:
36 return ActionResult(
37 success=False,
38 type="error",
39 error=f"Tool not found: {tool_name}",
40 )
41
42 try:
43 result = tool.execute(**params)
44 return ActionResult(
45 success=True,
46 type="tool",
47 tool_name=tool_name,
48 output=result,
49 )
50 except Exception as e:
51 return ActionResult(
52 success=False,
53 type="error",
54 tool_name=tool_name,
55 error=str(e),
56 )Error Handling in Actions
Actions can fail. Robust agents handle failures gracefully:
1def execute_with_retry(
2 self,
3 action: Action,
4 max_retries: int = 3,
5) -> ActionResult:
6 """Execute an action with retry logic."""
7
8 for attempt in range(max_retries):
9 result = self.act(action)
10
11 if result.success:
12 return result
13
14 # Log the failure
15 self.logger.warning(
16 f"Action failed (attempt {attempt + 1}/{max_retries}): "
17 f"{result.error}"
18 )
19
20 # Decide whether to retry
21 if not self.is_retryable_error(result.error):
22 break
23
24 # Wait before retrying (exponential backoff)
25 time.sleep(2 ** attempt)
26
27 return result # Return the last (failed) resultObserve: Processing Results
After acting, the agent must observe and process the results to update its state:
1class ObservationSystem:
2 """Processes action results and updates state."""
3
4 def observe(
5 self,
6 state: AgentState,
7 action: Action,
8 result: ActionResult,
9 ) -> AgentState:
10 """Update state based on action results."""
11
12 # Record what happened
13 state.add_action_record(
14 action=action,
15 result=result,
16 timestamp=datetime.now(),
17 )
18
19 # Update progress assessment
20 state.progress = self.assess_progress(state, result)
21
22 # Check for goal completion
23 if result.type == "finish":
24 state.completed = True
25 state.final_result = result.output
26
27 # Check for errors that require replanning
28 if not result.success:
29 state.errors.append(result.error)
30 state.needs_replanning = self.should_replan(state, result)
31
32 # Store in memory if significant
33 if self.is_significant(result):
34 self.memory.add(
35 content=f"Action: {action.name}, Result: {result.output}",
36 metadata={"type": "action_result"},
37 )
38
39 return state
40
41 def assess_progress(
42 self,
43 state: AgentState,
44 result: ActionResult,
45 ) -> float:
46 """Assess how much progress was made (0.0 to 1.0)."""
47 # This can be simple heuristics or LLM-based assessment
48 if result.type == "finish":
49 return 1.0
50 elif result.success:
51 return min(state.progress + 0.1, 0.9) # Incremental progress
52 else:
53 return state.progress # No progress on failure
54
55 def should_replan(
56 self,
57 state: AgentState,
58 result: ActionResult,
59 ) -> bool:
60 """Determine if we need to create a new plan."""
61 # Replan if multiple consecutive failures
62 if state.consecutive_failures >= 3:
63 return True
64 # Replan if a critical assumption proved wrong
65 if "not found" in result.error.lower():
66 return True
67 return FalseThe Complete Loop
Here's how all the pieces fit together:
1class Agent:
2 """Complete agent implementation with Perceive-Reason-Act loop."""
3
4 def __init__(
5 self,
6 llm: LLM,
7 tools: list[Tool],
8 memory: Memory,
9 max_iterations: int = 50,
10 ):
11 self.perception = PerceptionSystem(memory, tools)
12 self.reasoning = ReasoningSystem(llm)
13 self.action = ActionSystem(tools)
14 self.observation = ObservationSystem(memory)
15 self.max_iterations = max_iterations
16
17 def run(self, goal: str) -> AgentResult:
18 """Execute the agent loop to accomplish a goal."""
19
20 # Initialize state
21 state = AgentState(goal=goal)
22
23 for iteration in range(self.max_iterations):
24 # 1. PERCEIVE: Gather context
25 context = self.perception.perceive(state)
26
27 # 2. REASON: Decide action
28 action = self.reasoning.reason(context)
29
30 # Log for observability
31 self.log_decision(iteration, action)
32
33 # 3. ACT: Execute action
34 result = self.action.act(action)
35
36 # 4. OBSERVE: Update state
37 state = self.observation.observe(state, action, result)
38
39 # Check termination conditions
40 if state.completed:
41 return AgentResult(
42 success=True,
43 output=state.final_result,
44 iterations=iteration + 1,
45 )
46
47 if state.needs_replanning:
48 self.replan(state)
49 state.needs_replanning = False
50
51 # Max iterations reached
52 return AgentResult(
53 success=False,
54 output="Max iterations reached",
55 iterations=self.max_iterations,
56 )Visualizing the Loop
1┌─────────────────────────────────────────────────────┐
2│ AGENT LOOP │
3├─────────────────────────────────────────────────────┤
4│ │
5│ ┌──────────┐ │
6│ │ GOAL │ │
7│ └────┬─────┘ │
8│ ▼ │
9│ ┌──────────┐ ┌──────────┐ │
10│ │ PERCEIVE │ ──▶ │ REASON │ │
11│ └──────────┘ └────┬─────┘ │
12│ ▲ │ │
13│ │ ▼ │
14│ ┌──────────┐ ┌──────────┐ │
15│ │ OBSERVE │ ◀── │ ACT │ │
16│ └──────────┘ └──────────┘ │
17│ │ │
18│ ▼ │
19│ ┌──────────────┐ │
20│ │ Goal Complete?│ │
21│ └──────┬───────┘ │
22│ No │ Yes │
23│ ▲ │ ▼ │
24│ │ │ ┌──────────┐ │
25│ └────┘ │ FINISH │ │
26│ └──────────┘ │
27└─────────────────────────────────────────────────────┘Summary
The Perceive-Reason-Act loop is the foundation of all agent systems:
- Perceive: Gather context (goal, state, history, environment)
- Reason: Use LLM to decide the next action
- Act: Execute the chosen action (tool call, finish, etc.)
- Observe: Process results and update state
- Loop: Repeat until goal is achieved or max iterations
The Power of the Loop: This simple pattern enables remarkably complex behavior. In the next section, we'll explore each core component in more detail.