---
title: Agent Loop
tags: [event-loop, tool-execution, hooks]
description: "How Strands agents reason, plan, and act. Understand the model-driven loop that orchestrates tool use, reflection, and goal completion."
---

A language model can answer questions. An agent can *do things*. The agent loop is what makes that difference possible.

When a model receives a request it cannot fully address with its training alone, it needs to reach out into the world: read files, query databases, call APIs, execute code. The agent loop is the orchestration layer that enables this. It manages the cycle of reasoning and action that allows a model to tackle problems requiring multiple steps, external information, or real-world side effects.

This is the foundational concept in Strands. Everything else builds on top of it.

## How the Loop Works

The agent loop operates on a simple principle: invoke the model, check if it wants to use a tool, execute the tool if so, then invoke the model again with the result. Repeat until the model produces a final response.

```mermaid
flowchart LR
    A[Input & Context] --> Loop

    subgraph Loop[" "]
        direction TB
        B["Reasoning (LLM)"] --> C["Tool Selection"]
        C --> D["Tool Execution"]
        D --> B
    end

    Loop --> E[Response]
```

The diagram shows the recursive structure at the heart of the loop. The model reasons, selects a tool, the tool executes, and the result feeds back into the model for another round of reasoning. This cycle continues until the model decides it has enough information to respond.

What makes this powerful is the accumulation of context. Each iteration through the loop adds to the conversation history. The model sees not just the original request, but every tool it has called and every result it has received. This accumulated context enables sophisticated multi-step reasoning.

## A Concrete Example

Consider a request to analyze a codebase for security vulnerabilities. This is not something a model can do from memory. It requires an agent that can read files, search code, and synthesize findings. The agent loop handles this through successive iterations:

1. The model receives the request to analyze a codebase. It first needs to understand the structure. It requests a file listing tool with the repository root as input.

2. The model now sees the directory structure in its context. It identifies the main application entry point and requests the file reader tool to examine it.

3. The model sees the application code. It notices database queries and decides to examine the database module for potential SQL injection. It requests the file reader again.

4. The model sees the database module and identifies a vulnerability: user input concatenated directly into SQL queries. To assess the scope, it requests a code search tool to find all call sites of the vulnerable function.

5. The model sees 12 call sites in the search results. It now has everything it needs. Rather than requesting another tool, it produces a terminal response: a report detailing the vulnerability, affected locations, and remediation steps.

Each iteration followed the same pattern. The model received context, decided whether to act or respond, and either continued the loop or exited it. The key insight is that the model made these decisions autonomously based on its evolving understanding of the task.

## Messages and Conversation History

Messages flow through the agent loop with two roles: user and assistant. Each message contains content that can take different forms.

**User messages** contain the initial request and any follow-up instructions. User message content can include:

- Text input from the user
- Tool results from previous tool executions
- Media such as files, images, audio, or video

**Assistant messages** are the model's outputs. Assistant message content can include:

- Text responses for the user
- Tool use requests for the execution system
- Reasoning traces (when supported by the model)

The conversation history accumulates all three message types across loop iterations. This history is the model's working memory for the task. The conversation manager applies strategies to keep this history within the model's context window while preserving the most relevant information. See [Conversation Management](conversation-management.md) for details on available strategies.

## Tool Execution

When the model requests a tool, the execution system validates the request against the tool's schema, locates the tool in the registry, executes it with error handling, and formats the result as a tool result message.

The execution system captures both successful results and failures. When a tool fails, the error information goes back to the model as an error result rather than throwing an exception that terminates the loop. This gives the model an opportunity to recover or try alternatives.

## Loop Lifecycle

The agent loop has well-defined entry and exit points. Understanding these helps predict agent behavior and handle edge cases.

### Starting the Loop

When an agent receives a request, it initializes by registering tools, setting up the conversation manager, and preparing metrics collection. The user's input becomes the first message in the conversation history, and the loop begins its first iteration.

### Stop Reasons

Each model invocation ends with a stop reason that determines what happens next:

- **End turn**: The model has finished its response and has no further actions to take. This is the normal successful termination. The loop exits and returns the model's final message.
- **Tool use**: The model wants to execute one or more tools before continuing. The loop executes the requested tools, appends the results to the conversation history, and invokes the model again.
- **Cancelled**: The agent was stopped externally via `agent.cancel()`. See [Cancellation](#cancellation) below.
- **Limit turns** (`limit_turns` / `limitTurns`): The per-invocation turn budget was exhausted. See [Invocation Limits](#invocation-limits).
- **Limit total tokens** (`limit_total_tokens` / `limitTotalTokens`): The cumulative token budget was exhausted. See [Invocation Limits](#invocation-limits).
- **Limit output tokens** (`limit_output_tokens` / `limitOutputTokens`): The output token budget was exhausted. See [Invocation Limits](#invocation-limits).
- **Max tokens**: The model's response was truncated because it hit the token limit. This is unrecoverable within the current loop. The model cannot continue from a partial response, and the loop terminates with an error.
- **Stop sequence**: The model encountered a configured stop sequence. Like end turn, this terminates the loop normally.
- **Content filtered**: The response was blocked by safety mechanisms.
- **Guardrail intervention**: A guardrail policy stopped generation.

The limit stop reasons indicate graceful budget exhaustion. The agent's message history remains in a valid state, and you can reinvoke with a higher budget or different prompt.

Both content filtered and guardrail intervention terminate the loop and should be handled according to application requirements.

### Extending the Loop

The agent emits lifecycle events at key points: before and after each invocation, before and after each model call, and before and after each tool execution. These events enable observation, metrics collection, and behavior modification without changing the core loop logic. See [Hooks](hooks.md) for details on subscribing to these events.

### Cancellation

The `agent.cancel()` method provides a way to stop the loop from outside, such as on a client disconnect, a timeout, or a UI "Stop" button. Calling `cancel()` sets an internal signal that the agent checks at key checkpoints. The cancel signal clears automatically when the invocation completes, so the agent is immediately reusable.

<Tabs>
  <Tab label="Python">

  The agent checks for cancellation at two checkpoints:

  | Checkpoint | Behavior | Note |
  |---|---|---|
  | Model response streaming | Partial output is discarded | Usage metrics may be inaccurate since the stream is closed before the model sends its final metadata event |
  | Before tool execution | Tool calls are skipped with error results added to maintain valid conversation state | |

  The agent returns a result with `stop_reason="cancelled"`. `cancel()` is thread-safe and idempotent. Calling it multiple times or from different threads is safe.

  ```python
  import threading
  import time

  from strands import Agent


  def timeout_watchdog(agent: Agent, timeout: float) -> None:
      """Cancel the agent after a timeout period."""
      time.sleep(timeout)
      agent.cancel()


  agent = Agent()

  # Cancel from a background thread after 30 seconds
  watchdog = threading.Thread(target=timeout_watchdog, args=(agent, 30.0))
  watchdog.start()

  result = agent("Analyze this large dataset")
  watchdog.join()

  if result.stop_reason == "cancelled":
      print("Agent was cancelled due to timeout")
  ```

  Cancellation differs from [interrupts](../interrupts.mdx) in that it stops the agent entirely rather than pausing for human input. Interrupts allow the agent to resume from where it left off; cancellation does not.
  </Tab>
  <Tab label="TypeScript">

  The agent checks for cancellation at four checkpoints:

  | Checkpoint | Behavior | Note |
  |---|---|---|
  | Top of each loop cycle | Agent stops before the next model invocation | |
  | During model response streaming | Partial output is discarded | Usage metrics may be inaccurate since the stream is closed before the model sends its final metadata event |
  | Before tool execution | All pending tool calls are skipped with error results | |
  | Between sequential tool executions | Remaining tool calls are skipped with error results | |

  The agent returns a result with `stopReason: 'cancelled'`. `cancel()` is idempotent — calling it multiple times is safe.

  ```typescript
  --8<-- "user-guide/concepts/agents/agent-loop.ts:cancel_timeout"
  ```

  #### External cancellation signals

  You can also pass your own `AbortSignal` into `invoke()` or `stream()` via the `cancelSignal` option. The agent composes it with its internal controller using `AbortSignal.any()`, so both `agent.cancel()` and the external signal can trigger cancellation independently. This is useful for declarative timeouts, custom `AbortController` workflows, or framework-driven cancellation on client disconnect.

  ```typescript
  --8<-- "user-guide/concepts/agents/agent-loop.ts:cancel_external_signal"
  ```

  #### Cancellation within tool execution

  The SDK automatically checks for cancellation before and between tool invocations (see checkpoints above). However, once a tool callback is running, cancellation is **cooperative** — only the tool itself can respond mid-execution. Tools can participate by forwarding the signal to APIs that accept `AbortSignal`, or by polling `cancelSignal.aborted` between steps. If a tool does neither, it runs to completion and the agent resumes cancellation handling after the tool returns.

  ```typescript
  --8<-- "user-guide/concepts/agents/agent-loop.ts:cancel_signal"
  ```
  </Tab>
</Tabs>

### Invocation Limits

To cap how much work an agent does in a single invocation, pass
a `limits` object. You can bound turns (loop iterations), output
tokens, or total tokens. All three are optional.

<Tabs>
  <Tab label="Python">

  ```python
  from strands import Agent

  agent = Agent()

  result = agent(
      "Summarize this document",
      limits={
          "turns": 5,
          "output_tokens": 2000,
          "total_tokens": 10000,
      },
  )

  if result.stop_reason == "limit_turns":
      print("Hit turn budget")
  elif result.stop_reason == "limit_total_tokens":
      print("Hit token budget")
  ```

  The same parameter works with `invoke_async` and
  `stream_async`. Each cap must be a positive `int`.

  </Tab>
  <Tab label="TypeScript">

  ```typescript
  --8<-- "user-guide/concepts/agents/agent-loop.ts:limits_basic"
  ```

  The same option works with `stream`. Each cap must be a
  positive finite number.

  </Tab>
</Tabs>

Limits are checked at the top of each loop iteration, not
mid-call. A single turn can overshoot the token budget, but the
check fires before the next turn starts. Tools requested by the
previous turn always run to completion before the limit check
fires. The agent's message history stays in a valid,
reinvokable state.

Limits apply to the current invocation only. A reused agent
starts each call with fresh counters.

When multiple caps trip simultaneously, the reported stop reason
follows priority order: turns, then total tokens, then output
tokens.

## Common Problems

### Context Window Exhaustion

Each loop iteration adds messages to the conversation history. For complex tasks requiring many tool calls, this history can exceed the model's context window. When this happens, the agent cannot continue.

Symptoms include errors from the model provider about input length, or degraded model performance as the context fills with less relevant earlier messages.

Solutions:

- Reduce tool output verbosity. Return summaries or relevant excerpts rather than complete data.
- Simplify tool schemas. Deeply nested schemas consume tokens in both the tool configuration and the model's reasoning.
- Configure a conversation manager with appropriate strategies. The default sliding window strategy works for many applications, but summarization or custom approaches may be needed for long-running tasks. See [Conversation Management](conversation-management.md) for available options.
- Decompose large tasks into subtasks, each handled with a fresh context.

### Inappropriate Tool Selection

When the model consistently picks the wrong tool, the problem is usually ambiguous tool descriptions. Review the descriptions from the model's perspective. If two tools have overlapping descriptions, the model has no basis for choosing between them. See [Tools Overview](../tools/index.md) for guidance on writing effective descriptions.

### MaxTokensReachedException

When the model's response exceeds the configured token limit, the loop raises a `MaxTokensReachedException`. This typically occurs when:

- The model attempts to generate an unusually long response
- The context window is nearly full, leaving insufficient space for the response
- Tool results push the conversation close to the token limit

Handle this exception by reducing context size, increasing the token limit, or breaking the task into smaller steps.

## What Comes Next

The agent loop is the execution primitive. Higher-level patterns build on top of it:

- [Conversation Management](conversation-management.md) strategies that maintain coherent long-running interactions
- [Hooks](hooks.md) for observing, modifying, and extending agent behavior
- Multi-agent architectures where agents coordinate through shared tools or message passing
- Evaluation frameworks that assess agent performance on complex tasks

Understanding the loop deeply makes these advanced patterns more approachable. The same principles apply at every level: clear tool contracts, accumulated context, and autonomous decision-making within defined boundaries.
