> ## Documentation Index
> Fetch the complete documentation index at: https://cognis.vasanth.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents and the loop

> What an agent is in Cognis: a graph with a model node, a tool node, and a stop condition. AgentBuilder wires it for you.

An agent is a model that can call tools and decide when to stop. Behind the scenes, every Cognis agent is a compiled `Graph<AgentState>` with a small set of nodes — a model node that asks the LLM what to do, a tool-dispatch node that runs the calls, and edges that loop until the model has nothing left to do.

You can build that graph by hand, but you usually don't need to. `AgentBuilder` carries the opinion that *most agents look the same*, and gives you a builder for the parts that change.

## What an agent is, exactly

```
       ┌──────┐
 ──→   │ model │  ──→  emits tool calls?
       └──────┘             │
            ▲      yes ─────┴───── no ──→ done
            │                            
       ┌────────┐                        
       │  tools  │  ←── dispatch each call
       └────────┘
```

That's the whole loop. Everything Cognis adds — memory, middleware, multi-agent, human-in-the-loop — sits around this loop without changing its shape.

## When to use an agent

* The model needs to **make multiple decisions** before answering.
* You want it to **reach for tools** (search, calculator, API lookups, your domain functions).
* You want a **transcript** the user can replay.

If you just need a one-shot answer, call `Client::invoke(messages)` directly.
If you need branching that doesn't fit the loop shape, drop down to a [`Graph<S>`](/core-ideas/graphs).

## Quick example

```rust theme={null}
use std::sync::Arc;
use cognis::prelude::*;
use cognis::{AgentBuilder, Calculator};
use cognis_llm::Client;

let mut agent = AgentBuilder::new()
    .with_llm(Client::from_env()?)
    .with_tool(Arc::new(Calculator::new()))
    .with_system_prompt("You are a math assistant.")
    .with_max_iterations(4)
    .build()?;

let resp = agent.run(Message::human("What is 23 * 17 + 4?")).await?;
println!("{}", resp.content);
```

`agent.run(impl Into<Message>)` returns an `AgentResponse` carrying:

* `content: String` — the final assistant reply.
* `messages: Vec<Message>` — the full transcript, including tool calls and tool results.

## How it works

When you call `agent.run`:

* **Memory seeds the conversation.** If you set one (`with_memory`), the agent prepends those messages.
* **The model is invoked with current messages and tool definitions.** `with_max_iterations` caps how many times the loop can ask.
* **If the reply has tool calls**, the tool dispatcher runs them, threads the results back as `Message::tool` entries, and loops.
* **If it doesn't**, the loop exits and the model's last reply becomes `resp.content`.
* **Middleware wraps each model call.** Retry, fallback, rate limits, redaction, prompt caching, planning — all hook into this loop without rewriting it.

## Customization knobs

`AgentBuilder` exposes everything you'd want to change about that loop:

| Method                                           | What changes                                          |
| ------------------------------------------------ | ----------------------------------------------------- |
| `.with_llm(Client)`                              | The model. Required.                                  |
| `.with_tool(Arc<dyn Tool>)` / `.with_tools(...)` | What the agent can call.                              |
| `.with_system_prompt(...)`                       | Role / persona / rules.                               |
| `.with_memory(impl Memory)`                      | What context survives between `run` calls.            |
| `.with_max_iterations(n)`                        | Hard cap on loop iterations.                          |
| `.with_max_tool_calls(n)`                        | Hard cap on total tool invocations.                   |
| `.with_filesystem(Arc<dyn Backend>)`             | Mounts a virtual filesystem the model can read/write. |
| `.with_approver(Arc<dyn Approver>)`              | Human-in-the-loop tool gating.                        |
| `.stateful() / .stateless()`                     | Persist or reset state across `run`.                  |
| `.with_graph(graph)`                             | Replace the default ReAct graph entirely.             |

Pages that go deep on each of these:

* [Tools](/building-agents/tools) — define tools, validators, the tool macro.
* [System prompts and memory](/building-agents/memory) — Buffer, Window, TokenBuffer, SummaryBuffer, Entity, Knowledge-Graph, Vector, Hybrid.
* [Multi-agent orchestration](/building-agents/multi-agent) — Sequential, Supervisor, ParallelVote, RoundRobin.
* [Middleware](/building-agents/middleware) — the catalog and how to compose.
* [Human-in-the-loop](/building-agents/hitl) — interrupts and approvers.

## When the loop isn't enough

Sometimes you want behavior the loop can't express — fan-out across heterogeneous workers, persistence-aware time travel, structured handoffs between agents with different state shapes. Then you have two paths:

* **Multi-agent orchestration.** `MultiAgentOrchestrator::new(strategy).add(...).run(...)` runs several agents under a strategy.
* **Hand-built graph.** `Graph<MyState>::new()....compile()?` then either drive it directly, or `Agent::wrap(graph)` to slot it into the agent surface.

Both keep `AgentResponse` as the output type, so callers don't change.

## See also

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/get-started/quickstart">
    The fastest path to a working agent.
  </Card>

  <Card title="Tools" icon="screwdriver-wrench" href="/building-agents/tools">
    Define what the agent can do.
  </Card>

  <Card title="Multi-agent" icon="people-arrows" href="/building-agents/multi-agent">
    When one agent isn't enough.
  </Card>

  <Card title="Graphs" icon="diagram-project" href="/core-ideas/graphs">
    The substrate behind the loop.
  </Card>
</CardGroup>
