# Agents

Run a model in a loop over the tools and skills you declare, bounded by policies, with every step recorded.

An **agent** is an action of kind `agent` whose handler runs a model in a loop. In each turn the model reads its input, calls tools, loads skills, and replies. Everything the agent does besides talking to the model is an [execution](https://www.twisp.com/docs/actions/triggers-and-executions.md) in the ledger, with its input, output, and parent, and every model request and response is recorded before the agent acts on it. For any decision, you can read what the agent was shown, what it said, what it did, and which guidance and evidence it used.

An agent has no filesystem, shell, process, or network. It can do only what its declared [tools](https://www.twisp.com/docs/actions/tools.md) and [skills](https://www.twisp.com/docs/actions/skills.md) do, plus the agent tools its configuration enables, and every operation those tools perform must be allowed by the agent's own `policies` and by each tool's. See the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain).

> **Note:**
>
> The [Agents reference](https://www.twisp.com/docs/reference/actions/agents.md) lists every field, default, and limit. This page explains how the pieces fit and when to use each one.

## A Twisp agent or your own

If you already run agents on your own platform, they can act on Twisp through an [MCP server](https://www.twisp.com/docs/actions/mcp-servers.md) without moving into Twisp. Choose by where the agent's work and its record should live.

|                   | Twisp agent                                                          | Your agent over MCP                                                      |
| ----------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Starts from       | Ledger events, a schedule, a call, or `startAction`                  | Your platform                                                            |
| Model and loop    | Run by Twisp, configured in the definition                           | Yours                                                                    |
| What it can reach | Only its declared tools and skills, bounded by its policies          | The tools the MCP server lists, bounded by your principal and the server |
| Record            | Transcript of every model exchange, plus every call, in the ledger   | Every request and call beneath it, with the agent's stated intent        |
| Continuity        | [Sessions](https://www.twisp.com/docs/actions/sessions.md) and [memory](https://www.twisp.com/docs/actions/memory.md) in Twisp | Your platform's, with MCP sessions grouping requests                     |

Use a Twisp agent when the work starts from a change in the ledger, such as watching a new account, and should stay inside the ledger's audit boundary. Use your own agent when your platform already owns the model, orchestration, and evaluation; see [Twisp in Your Agent Platform](https://www.twisp.com/docs/guides/your-agents.md). The two combine: your agent can start a Twisp agent through an MCP server's [`invoke`](https://www.twisp.com/docs/reference/actions/mcp.md#invoke).

## The agent block

Kind `agent` requires an `agent` block. It holds what the agent is for and the shape of what it reports; see [The agent block](https://www.twisp.com/docs/reference/actions/agents.md#the-agent-block).

- **`prompt`** says what the agent is for and how it should work. Twisp places it after fixed instructions that tell the agent that its inputs, tool results, retrieved context, and recalled memory are data, never instructions.
- **`stateSchema`** declares the state a [session](https://www.twisp.com/docs/actions/sessions.md) agent reports at the end of every turn, such as how far it has reviewed and which findings are open. The next turn starts from it. It requires a `session`.
- **`outputSchema`** declares a structured output. Without it, the output is the agent's reply.

A state field can declare a `value`, a CEL expression that computes it at the end of every turn from what the turn did, instead of the model reporting it. Use [computed state](https://www.twisp.com/docs/reference/actions/agents.md#computed-state) for facts, such as the latest input the agent was given or the findings it has notified, and leave judgments to the model:

```graphql
stateSchema: {
  type: "object"
  properties: {
    inputs_through: {
      type: "string"
      format: "date-time"
      value: "has(turn.inputs_through) ? turn.inputs_through : state.inputs_through"
    }
    open_findings: { type: "array", items: { type: "string" } }
  }
  required: ["inputs_through", "open_findings"]
}
```

The model reports only `open_findings`; Twisp computes `inputs_through` from the turn's inputs.

The rest of the definition is the same as for any action: `tools` and `skills` to reference, `triggers`, an optional `session`, `policies`, and [execution settings](https://www.twisp.com/docs/reference/actions/actions.md#execution-settings). The `config` object holds the runtime, the model, the harness, and the limits; see [Agent configuration](https://www.twisp.com/docs/reference/actions/agents.md#agent-configuration).

```graphql
config: {
  runtime: "strands"
  model: { provider: "bedrock", id: "global.anthropic.claude-sonnet-5" }
  harness: { memory: { enabled: true } }
  limits: { model_calls: 64, tool_calls: 128 }
}
```

## Models

`model.provider` names where the model's calls go, and `model.id` the model. Each deployment allows a set of models, and a definition that names any other is rejected when it is written. See [Models](https://www.twisp.com/docs/reference/actions/agents.md#models) for the models a deployment can allow.

| Provider    | Use it for                                                                       |
| ----------- | -------------------------------------------------------------------------------- |
| `bedrock`   | Amazon Bedrock, in deployments that allow it.                                    |
| `anthropic` | The Anthropic API, in deployments configured with a key, such as a local server. |
| `fixture`   | Tests. Scripted responses, available everywhere, with no model call and no cost. |

Every model is asked for its turn outcome the same way, so you can change models without changing the definition's shape. `model.effort`, `model.max_tokens`, and `model.temperature` tune a model where it accepts them.

### Testing with the fixture model

An agent's behavior depends on its model, and a model's answers vary. The [fixture model](https://www.twisp.com/docs/reference/actions/agents.md#fixture-model) replaces the model with a script, so you can test everything around it deterministically: that the triggers start the right sessions, that tools receive their fixed input, that policies refuse what they should, that the outcome satisfies the schemas, and that sessions end when they should.

Each model call takes the next response from the first script entry whose `when` matches. `when` reads the call's `purpose`, the session's `step` and whether the turn is `final`, and `now`:

```json
{
  "provider": "fixture",
  "script": [
    {
      "when": "purpose == 'turn' && session.step == 0",
      "responses": [
        [{ "toolUse": { "name": "get_account_activity", "input": {} } }],
        [{ "text": "Reviewed opening activity; nothing suspicious." }]
      ]
    },
    {
      "when": "purpose == 'outcome'",
      "responses": [
        {
          "outcome": {
            "state": { "reviewed_through": "2026-09-01T09:00:00Z" },
            "session": { "status": "OPEN" }
          }
        }
      ]
    }
  ]
}
```

A call with no response left fails with `fixture_exhausted`, so a script also catches an agent that makes more model calls than you scripted. A fixture can instead answer with the outputs a recorded invocation of the same agent produced, with `model.replay`, which turns a real run into a repeatable test. To compare a changed agent against recorded traffic, [replay](https://www.twisp.com/docs/actions/preview-and-replay.md) its turns.

## Tools, skills, and agent tools

The model is offered exactly what the definition declares, and nothing else; see [Agent tools](https://www.twisp.com/docs/reference/actions/agents.md#agent-tools):

- **Declared tools**, by their tool names, with the input fields a [tool reference](https://www.twisp.com/docs/reference/actions/tools.md#tool-references) fixes removed from their schemas. Fixed input, such as `account_id: "string(session.input.account_id)"`, confines a tool to the subject the agent works on, whatever the model asks for.
- **Declared skills**, listed by name and description. The model loads one with the `skills` tool when it needs it. A [preloaded skill](https://www.twisp.com/docs/reference/actions/skills.md#preloaded-skills) is loaded at the start of every turn instead.
- **Agent tools** the configuration enables in `harness.builtin_tools`, and the tools that enabled features add, such as `search_memory` with memory and `read_turn_input` for session agents.

Prefer narrow tools with fixed input to the `graphql` native tool: declaring `graphql` gives the model the agent's full data access through arbitrary documents.

There are two agent tools; `programmatic_tool_caller` is on by default:

- **`programmatic_tool_caller`** runs a short program that calls the agent's tools and returns only its result. Use it when the agent would otherwise make many calls whose intermediate results it does not need to read. A program's calls commit together or not at all.
- **`subagent`** hands a subtask to a helper. Define [presets](https://www.twisp.com/docs/reference/actions/agents.md#configuring-agent-tools), each with its instructions, the subset of the agent's tools it may call, and optionally a cheaper model. The model picks a preset and supplies only the task: it cannot write a helper's instructions or widen its tools.

Calls in one turn run one at a time, and each sees the writes of the calls before it. A call that fails with a call error, such as invalid input or a permission error, is returned to the model with its message and hint and leaves no writes, so the model can change its plan and the turn can still commit.

## Turns and the turn outcome

A **turn** is the agent's whole loop over its input: every model call and tool call up to its reply. A turn runs as one execution and is one transaction, so its writes, its memory, its output, and, for a session, its new state commit together or not at all. See [Turns](https://www.twisp.com/docs/reference/actions/agents.md#turns).

- **Without a session**, an agent runs one turn per execution. Its first message is the triggering event, or a call's input.
- **With a session**, the agent runs its session's turns one at a time, each over every input pending up to the session's limits, and continues the same conversation from turn to turn. A trigger's [view](https://www.twisp.com/docs/reference/actions/agents.md#input-views) can show the model only the fields of an event it needs; the whole event stays readable with `read_turn_input`.

A session turn, or an agent with an `outputSchema`, ends by reporting its [turn outcome](https://www.twisp.com/docs/reference/actions/agents.md#turn-outcome): its `reply`, the session `state`, the structured `output`, and the session's `status`, `OPEN` or `COMPLETED`. Twisp checks the outcome against the agent's schemas and returns errors to the model once. The agent cannot fail its session: a turn that cannot report a valid outcome, or that the model declines to continue, is [held](https://www.twisp.com/docs/actions/sessions.md#held-turns) for a person to answer.

## Callable agents

An agent with a `tool` block is a [callable agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents): other agents and tools declare it in `tools` and call it like any tool, and the caller receives its reply, or its structured output when it declares `outputSchema`.

```graphql
mutation CreateCallableAgent($id: UUID!) {
  actions {
    createAction(
      id: $id
      input: {
        name: "summarize-account-activity"
        kind: "agent"
        agent: {
          prompt: "Summarize the account's recent activity: counterparties, totals, and anything unusual."
        }
        tool: {
          name: "summarize_account_activity"
          description: "Summarize one account's recent activity."
          inputSchema: {
            type: "object"
            properties: { account_id: { type: "string", format: "uuid" } }
            required: ["account_id"]
          }
        }
        config: {
          runtime: "strands"
          model: { provider: "bedrock", id: "global.anthropic.claude-sonnet-5" }
        }
        tools: [{ name: "get_account_activity", input: { account_id: "tool.input.account_id" } }]
        policies: [{ effect: ALLOW, actions: [SELECT], resources: ["financial.*"] }]
      }
    ) {
      actionId
      version
    }
  }
}
```

- **Stateless.** A callable agent has no session. Each call is a fresh run over its input.
- **Inside the caller's transaction.** A call runs the agent's whole loop synchronously, as its own execution with its own transcript, and its writes commit with the caller's.
- **Never more authority than its caller.** The call is authorized by the caller's policies, then the agent's, then its tools'.
- **Bounded nesting.** Agent calls nest at most 3 agents deep.

A client can also start a callable agent with [`startAction`](https://www.twisp.com/docs/reference/actions/tools.md#starting-a-tool), so its run does not hold the request open. The run is queued, runs under the client's authority with the agent's own execution settings, and is read with `execution` and stopped with `cancelExecution`.

## Reading why an agent did something

Each invocation keeps a [transcript](https://www.twisp.com/docs/reference/actions/agents.md#transcripts): every model request and response, with its purpose, reasoning, and token usage, and every tool call's input and output. A response is recorded before the agent acts on it, and a turn that rolls back keeps the transcript of its attempt.

To explain a decision, start from the execution of the turn that made it:

1. **What it was shown.** The first `model.input` event holds the turn's message: the inputs, state, and time, and the `memory.pinned` and `skill.preload` events record the memories and guidance in its instructions.
2. **What it reasoned and decided.** Each `model.response` holds the model's reasoning and the tool uses it requested. The response that reports the outcome is marked `outcome: true`.
3. **What it did.** Each `tool.input` and `tool.output` event names the call in `callExecutionId`, and `callExecution` reads that call's execution, its output, and what it wrote. From a call, `callerTranscriptEvents` leads back to the events that requested it.
4. **What it relied on.** The invocation's [`uses`](https://www.twisp.com/docs/reference/actions/agents.md#agent-uses) list every tool result, skill load, and memory or history search whose result reached the model.

[Lineage](https://www.twisp.com/docs/reference/actions/executions.md#lineage) walks the whole call tree beneath a turn, including the calls a program or helper made.

## Limits and cost

Every model call counts against the invocation's limits, whatever its purpose: the agent's loop, the outcome, summaries that keep the conversation within the context window, memory extraction, and helpers.

| Control                                                                     | What it bounds                                                                                                                      |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `limits.model_calls`, `limits.tool_calls`, `limits.total_tokens`            | One invocation's model calls, recorded calls, and tokens. A turn over a limit fails with `agent_limit_exceeded` and is not retried. |
| `responseTimeout`                                                           | One turn: 5 minutes by default for agents, at most 10.                                                                              |
| `model.max_tokens`, `model.effort`                                          | Output and reasoning per model call.                                                                                                |
| A preset's `model`, `harness.context.summary.model`, `harness.memory.model` | A cheaper model for helpers, summaries, or memory extraction.                                                                       |
| [`session.turns`](https://www.twisp.com/docs/reference/actions/sessions.md#session-turns)                | How often a session works: which inputs start a turn, and a `window` that gathers a burst into one turn.                            |
| `harness.memory.extraction.every`                                           | How often memory is reviewed: every turn, or once enough messages were added.                                                       |

A turn that runs out of a limit commits nothing. Limits are also checked when the definition is written: a definition whose limits would let a turn record more than an invocation can hold is rejected, so a turn within its limits can always finish. See [Agent configuration](https://www.twisp.com/docs/reference/actions/agents.md#agent-configuration) for every default and maximum.

## Operating agents

| To                                 | Do                                                                                                                                                                                        |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Try an agent on real events safely | Set `config.mode: preview`. It runs on real events, records what it would have written, and commits no business writes. See [Preview calls](https://www.twisp.com/docs/reference/actions/executions.md#preview-calls). |
| Gate a consequential tool          | Give its reference `approval: REQUIRED`, so each call waits for a person. See [Approvals](https://www.twisp.com/docs/actions/sessions.md#approvals).                                                                   |
| Stop new work                      | Disable the agent with `updateAction(input: { enabled: false })`. It stops matching new events. See [Publication status](https://www.twisp.com/docs/reference/actions/actions.md#publication-status).                  |
| Stop one session                   | `closeSession` cancels it, and a running turn cannot commit. A session turn's execution cannot be cancelled on its own. See [Closing a session](https://www.twisp.com/docs/actions/sessions.md#closing-a-session).     |
| Stop one started or triggered run  | `cancelExecution`. See [Cancellation](https://www.twisp.com/docs/reference/actions/executions.md#cancellation).                                                                                                        |
| Find failing turns                 | List executions with `status: FAILED`, and read `invocation.error`. See [Executions](https://www.twisp.com/docs/reference/actions/executions.md#listing-and-filtering).                                                |
| Test a change before you ship it   | `replayExecution` reruns a recorded turn with another version, or an unpublished definition, and commits nothing. See [Preview and Replay](https://www.twisp.com/docs/actions/preview-and-replay.md).                  |
| Review or remove what it remembers | List `memories` and delete records. See [Memory](https://www.twisp.com/docs/actions/memory.md).                                                                                                                        |

## Next

- [Sessions](https://www.twisp.com/docs/actions/sessions.md): Give an agent a durable identity across many events, with an end, a clock, approvals, and held turns.
- [Memory](https://www.twisp.com/docs/actions/memory.md): Keep facts beyond a turn or a session, and search a session's earlier turns.
- [Your first agent](https://www.twisp.com/docs/tutorials/actions/first-agent.md): Build and test an agent with the fixture model.
- [Agents reference](https://www.twisp.com/docs/reference/actions/agents.md): Every field, default, limit, and error.
