---
title: Agent
description: Agent = ReAct. The loop primitive that thinks, acts (tool call), observes the result, and repeats until done.
---

import CodeFile from '@/components/CodeFile.astro';

> A user asks *"what's the weather in Paris?"*. Your code can't just hit a weather API — the LLM has to decide which tool to call, with what args, then read the result, then decide whether to call another tool or respond. **That decide → act → observe → repeat is what makes an Agent an Agent**, not a function call.

## Agent = ReAct

The Agent primitive **is** the ReAct loop (Yao 2022). One iteration:

```
LLM call → route → [tool calls → loop] OR [final answer]
```

Each iteration: the LLM produces text + optional tool calls. If tools were called, the framework executes them, appends results to the message history, and starts another iteration. If no tools were called (or `maxIterations` is hit), the loop exits with the final text.

**If it doesn't loop-with-tools, it isn't an Agent — it's an [LLMCall](/agentfootprint/getting-started/key-concepts/#1-llmcall--one-call).**

## Build an agent

`Agent.create({ provider, model })` → builder. `.system(...)` sets the system prompt. `.tool(...)` registers tools (each with a JSON schema). `.build()` finalizes:

<CodeFile path="examples/core/02-agent-with-tools.ts" region="build" title="examples/core/02-agent-with-tools.ts (region: build)" />

The framework owns the iteration loop. You declare what tools the agent has; the LLM decides when (and with what args) to call them; the framework dispatches and feeds results back.

## Observe the loop

Because the framework owns the loop, observability is just attaching listeners — no SDK, no agent-instrumentation wrapper:

<CodeFile path="examples/core/02-agent-with-tools.ts" region="observe" title="examples/core/02-agent-with-tools.ts (region: observe)" />

59 typed events fire across 16 domains during a single `agent.run()`. See the [Observability guide](/agentfootprint/guides/observability/) for the full taxonomy.

## maxIterations

Every Agent has a `maxIterations` cap (default 10). The loop exits when:

1. The LLM returns text with no tool calls (normal completion)
2. `maxIterations` is reached (forced exit; final iteration's text becomes the result)
3. A tool throws (propagates as an error; subject to `withRetry` if wrapped)
4. The agent pauses via `askHuman` / `pauseHere` (`run()` returns a `RunnerPauseOutcome` instead of a string — narrow it with the `isPaused()` guard)

Tune `maxIterations` to your tool budget. Tool-heavy agents (research, code-gen) commonly run 15–30; chat agents 3–5.

## Identity for multi-tenant memory

If your agent uses memory (`.memory(...)`), every `.run()` call must include an `identity` so memory is scoped per tenant / principal / conversation:

```ts
await agent.run({
  message: 'How long do refunds take?',
  identity: { tenant: 'acme', principal: 'alice', conversationId: 'thread-42' },
});
```

Without an identity, memory falls back to a global namespace — fine for single-user prototypes, dangerous in production multi-tenant apps. See [Memory guide](/agentfootprint/guides/memory/).

## Composition with other Agents

An Agent is a runner, just like LLMCall, Sequence, Parallel, Conditional. Use them as steps in larger graphs:

```ts
import { Sequence } from 'agentfootprint';

const research = Sequence.create()
  .step('plan', plannerLLM)
  .step('execute', researchAgent)
  .step('summarize', summarizerLLM)
  .build();
```

There is no separate "multi-agent" class — agents *are* the building blocks. See [Patterns](/agentfootprint/getting-started/key-concepts/#patterns--named-compositions) for Reflexion / ToT / Debate / Map-Reduce / Swarm recipes.

## Anti-patterns

- Don't subclass Agent for a "smarter" agent — use `.skill()` / `.instruction()` to inject behavior. Subclassing breaks composition.
- Don't put async I/O in tool `execute`'s synchronous setup — it runs every iteration. Cache outside; pure dispatch inside.
- Don't bypass `maxIterations` to "let the agent decide" — every loop has a cost ceiling. Set it explicitly; observe `agentfootprint.agent.iteration_end` to track.

## Next steps

- [Tools guide](/agentfootprint/guides/tools/) — `defineTool`, MCP integration, permission gating
- [Memory guide](/agentfootprint/guides/memory/) — `.memory(defineMemory({...}))` for cross-run state
- [Skills, explained](/agentfootprint/guides/skills-explained/) — LLM-activated body + tools
