# Tools

Define tools, call or start them, nest calls, use native tools, and pin tool versions.

A `tool` block makes an action callable. Tools have kind `tool`, which requires a `tool` block, and their `config` holds the implementation: a `runtime`, `starlark`, and its program in `src`. See the [`tool` handler](https://www.twisp.com/docs/reference/actions/tools.md#the-tool-handler). A built-in [model](https://www.twisp.com/docs/reference/actions/models.md) is a tool whose schemas come from its API. An [agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents) with a `tool` block is callable too. Validation rejects invalid schemas and names, programs that fail to compile, a `tool` block on a kind other than `tool`, `agent`, or a [native tool](https://www.twisp.com/docs/reference/actions/tools.md#native-tools)'s, and the native tools' names. When the action is saved, a GraphQL document the program passes to `graphql()` as a literal or a constant, directly or through its own functions, is validated against the schema, with its `operation_name` and the variables it requires, and each tool the program names with `tools.<name>` must be declared; the error says how the document reaches the call. A document the program builds or chooses at run time, such as one alternative of a conditional or one element of a loop, or passes to a call with a `check` argument other than `True`, is validated when the call runs.

| Field          | Type                     | Meaning                                                                                                                                                                                                                                                                     |
| -------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | `String!`                | Name the model sees: 1 to 64 letters, digits, underscores, or hyphens.                                                                                                                                                                                                      |
| `description`  | `String!`                | When and why to call the tool.                                                                                                                                                                                                                                              |
| `inputSchema`  | `JSON`                   | JSON Schema 2020-12 for the call input, with `type: "object"`. External references are not supported; references into `$defs` are. Required, except for your own action of a [native tool's](https://www.twisp.com/docs/reference/actions/tools.md#native-tools) kind, which takes the native tool's schemas when it is omitted. |
| `outputSchema` | `JSON`                   | Optional JSON Schema 2020-12 for the output. It applies to every execution of the action, including those started by its triggers.                                                                                                                                          |
| `session`      | `ActionToolSessionInput` | The session each call is recorded in, for an action with a `session`. See [Tool sessions](https://www.twisp.com/docs/reference/actions/sessions.md#tool-sessions).                                                                                                                                       |
| `output`       | `ActionToolOutputInput`  | How an agent that calls the tool keeps its results: `{offload: AUTO}` (the default) or `{offload: NEVER}`. See below.                                                                                                                                                       |

`output.offload` says whether an [agent](https://www.twisp.com/docs/reference/actions/agents.md) may set the tool's large results aside, out of its context. With `AUTO`, a result larger than the agent's `harness.context.offload.over_tokens` is replaced by a preview and a reference the agent retrieves it with. The preview keeps the result's shape: an array shows how many items it has, its first items and its last, and how many are left out; an object shows every key, its values shortened the same way; text shows its start and end. With `NEVER`, every result stays whole in the agent's context, whatever its size: use it for a tool whose whole result the agent needs, and keep such results small, since each one stays in the context for the rest of the turn. Any other value is rejected.

Input is validated before the handler runs, and output is validated before the call succeeds. Validation does not coerce values or apply schema defaults. JSON nesting is limited to 32 levels, and integers must fit a 64-bit signed or unsigned value. Top-level `inputSchema` property names must be letters, digits, and underscores, not starting with a digit, and not a Python keyword such as `from` or `class`, because an agent's code calls a tool with one keyword argument per top-level property; nested property names are not restricted.

```graphql
mutation CreateTool($id: UUID!, $src: String!) {
  actions {
    createAction(
      id: $id
      input: {
        name: "get_account_activity"
        description: "Read recent activity for one account"
        kind: "tool"
        config: { runtime: "starlark", src: $src }
        tool: {
          name: "get_account_activity"
          description: "Read recent activity for one account."
          inputSchema: {
            type: "object"
            properties: { account_id: { type: "string", format: "uuid" } }
            required: ["account_id"]
            additionalProperties: false
          }
          outputSchema: { type: "object" }
        }
        policies: [{ effect: ALLOW, actions: [SELECT], resources: ["financial.Account.*"] }]
      }
    ) {
      actionId
      version
      status
      pending
    }
  }
}
```

## Calling a tool

`invokeAction` runs a published, enabled tool or [callable agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents), or loads a [skill](https://www.twisp.com/docs/reference/actions/skills.md), synchronously, inside the request transaction and under the caller's principal. The caller needs `SELECT` and `INVOKE` on the tool's action record and `SELECT` and `INSERT` on its execution records (`financial.ActionExecution`), and the tool's own reads and writes are authorized by the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain): the caller's policies and the tool's own must both allow them. A call to a [tool with a session](https://www.twisp.com/docs/reference/actions/sessions.md#tool-sessions) also finds or creates the session and records the call under the caller's principal, so a direct caller also needs `SELECT` and `INSERT` on the tool's sessions and session inputs. Grant them on `financial.Session.indexes.*` and `financial.SessionInput.indexes.*`, and on `financial.Session.document` and `financial.SessionInput.document` with the assertion `context.document.action_id == uuid("<tool actionId>")`; an assertion on `context.document` cannot be evaluated for an index resource. Without the session grants the call fails and records nothing. The session is created in its own transaction before the call runs, so a caller allowed to create sessions but not session inputs can leave a new session with no history. An agent's client receives these permissions for each tool it references, including the session grants for a tool with a session. The tool's writes and its execution commit or roll back with the caller; a call never runs asynchronously or in a transaction of its own.

**Which versions can be called.** Whether a tool can be called depends on the action, not the version: its `status` must be `ACTIVE`, its current definition enabled, and no disable or delete pending. A tool that has never published, `status: UNPUBLISHED`, cannot be called at any version, and a pending disable or delete stops every version at once. While the action is callable, any stored version that is enabled and has a `tool` block can be called, including one an update wrote since the last publication and older versions such as version 1. This is what lets a reference without a `version` call the latest version as soon as it is written, and a tool written earlier in the same request be called before it publishes.

| Input field     | Type    | Meaning                                                                                                                                                                                                                  |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `executionId`   | `UUID!` | Identifier for the call, chosen by you. Repeating a call with the same identifier, tool, version, and input returns the committed execution without running the tool again; reusing it with different input is an error. |
| `actionVersion` | `Int!`  | The tool version to call. Its schemas and implementation are used exactly.                                                                                                                                               |
| `input`         | `JSON!` | JSON object validated against the version's input schema.                                                                                                                                                                |

```graphql
mutation CallTool($id: UUID!, $executionId: UUID!, $version: Int!) {
  actions {
    invokeAction(
      id: $id
      input: {
        executionId: $executionId
        actionVersion: $version
        input: { account_id: "70eec1a6-eac1-47aa-a507-e18f2d486425" }
      }
    ) {
      executionId
      status
      event {
        eventType
        data
      }
      invocation {
        output
      }
    }
  }
}
```

The execution's event has type `action.invoked`, its `data` is the effective input, and its record fields are null.

A call is one atomic unit: its writes, and those of every call it makes, commit or roll back together. A call that fails returns a `FAILED` execution whose `invocation.error` carries a `code`, a `message`, and, where one helps, a `hint` saying what to change. Invalid input, `fail()` in the program, a GraphQL error from validation, a business rule, or permissions, a CEL error, an exceeded limit, a tool that does not finish within its own `responseTimeout` (`action_response_timeout`), and invalid output fail this way. A permission error's hint names the denied operation and resource. None of the failed call's writes remain, the caller's own work is unaffected, and the failed call is recorded with its input and error when the caller commits. A write conflict, a passed deadline, or an interrupted transaction instead fails the request with a retryable error, because nothing else in the transaction can succeed: roll back and retry in a new transaction. A call the caller cannot make, such as a tool version its execution did not resolve, a disabled tool, or a closed session, fails before any work and is not recorded. Repeating a call with the same `executionId` returns its recorded execution, succeeded or failed. An agent can call only the tool versions its invocation resolved, and the tools its loaded skills made available; any other call fails with a permission error carrying the code `tool_not_declared`.

## Starting a tool

`startAction` queues a tool, or a [callable agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents), to run on its own, and returns its execution at once, `QUEUED`. Use it for work that should not hold your request open, such as an agent's whole run. The execution, and the work that runs it, commit or roll back with the request transaction: a request that rolls back started nothing. The execution then runs as an execution started by a trigger does, under the action's own [execution settings](https://www.twisp.com/docs/reference/actions/actions.md#execution-settings), with retries and recovery, each invocation in transactions of its own, so its writes commit with its outcome. Read it with `execution` and cancel it with `cancelExecution`.

| Input field     | Type    | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `executionId`   | `UUID!` | Identifier for the new execution, chosen by you. Repeating a start with the same identifier, principal, action, version, and input returns the execution it started, whatever its status, without starting it again: retry with the same identifier to learn what committed. Your token's claims and the time of the request are not compared, so a retry with a refreshed token matches. The same identifier with a different principal, action, version, or input is an error. |
| `actionVersion` | `Int`   | The version to run. Omit it to run the action's current version; the execution records the version it selected and runs it for its lifetime. A retry that omits it matches the version the execution selected, even after a newer version is published.                                                                                                                                                                                                                          |
| `input`         | `JSON!` | JSON object validated against the version's input schema before anything is queued.                                                                                                                                                                                                                                                                                                                                                                                              |

```graphql
mutation StartReview($id: UUID!, $executionId: UUID!) {
  actions {
    startAction(
      id: $id
      input: {
        executionId: $executionId
        input: { account_id: "70eec1a6-eac1-47aa-a507-e18f2d486425" }
      }
    ) {
      executionId
      status
      actionVersion
      startedBy {
        executionId
      }
    }
  }
}
```

The action must be published, enabled, and have a `tool` block, and starting it requires the `INVOKE` permission on it, as a call does. You also need `SELECT` on the action record and `SELECT` and `INSERT` on its execution records (`financial.ActionExecution`). A start you may not make, an action that cannot be started, and invalid input fail before anything is queued, and are not recorded. A tool with a [session](https://www.twisp.com/docs/reference/actions/sessions.md#tool-sessions) cannot be started: call it instead. A start made from inside an execution, such as through the `graphql` tool, is rejected: a tool or agent calls what it needs instead. To run a tool or agent from an MCP session, previewed or for real, [invoke it](https://www.twisp.com/docs/reference/actions/mcp.md#invoke) with `invoke`, on a server that lists it.

**Authority carries over.** A started execution runs under your authority, exactly as if you had called it: every operation it performs must be allowed by your policies and then by the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain) of the started version and the tools it calls. Starting work never lends it the action's own client. Your authority is read again at each invocation, from a cache that keeps a client's policies for at most 5 minutes, so revoking your client or narrowing its policies stops queued and retried work at its first invocation that starts 5 minutes after the change, or sooner. The start also records your token's claims, and an invocation that would begin after the token's expiry is refused: the execution fails with `action_access_denied` and a hint that the credential it was started with expired. A token without an expiry is governed by your client record alone.

A started agent runs without a session, as it does when called: its turn message holds its input, and each start is a fresh, stateless run. The execution's `startedBy` records how it was started; for your start it names no starting execution. Some executions are started by another execution, such as an MCP server's request that creates a task or invokes an agent: their `startedBy` names that execution, and [lineage](https://www.twisp.com/docs/reference/actions/executions.md#lineage) links the two.

## Nested calls

A tool declares the tools it calls in `tools`, as an agent does, and its program calls them through `tools`. Every call a tool makes, to a declared tool or to a [native tool](https://www.twisp.com/docs/reference/actions/tools.md#native-tools), is a child execution of the calling tool: its `parentExecutionId` is the calling tool's execution and its `callSequence` is its position among that invocation's calls, so following `children` walks the whole call tree. A nested call's identity derives from the calling execution, its invocation, and the call's sequence, so the same program with the same input makes the same calls. A nested call runs under the caller's principal and needs no separate `INVOKE` grant: the pin in the calling tool's definition authorizes it. Its operations must be allowed by every layer of the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain) above it and by the called tool's own policies. A failed nested call fails the calling tool unless the program handles it with `check=False`; a failed tool rolls back everything beneath it.

Nesting is limited to 8 tool levels below the first call, and one call and everything beneath it make at most 1,024 calls, including native tool calls. A call past either limit fails with `action_call_limit_exceeded`.

## Native tools

Every tenant has these tools without creating them. They are actions like your own: `actions(where: { native: true })` lists every one, `action(id:)` reads one by its well-known `actionId`, and every call is an execution with its input and output. `invokeAction` and `startAction` run one that acts for no caller, such as `graphql` or `cel`, with `INVOKE` on it; one that acts for its caller, such as `program` for an MCP server or `read_turn_input` and the agent tools for an agent, runs only as its caller's call. Their names are reserved. Their definitions are fixed at version 1, except a [model's](https://www.twisp.com/docs/reference/actions/models.md#versions-and-retirement), which gains a version with each release. Each carries its own policies, a layer of the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain) like any tool's: `graphql` allows everything, so a `graphql` call can do exactly what the layers above it allow, and `cel` allows nothing, since it performs no operations. Every tool program can call `graphql` and `cel` without declaring them; an agent calls them only if it lists them in `tools`. [MCP servers](https://www.twisp.com/docs/reference/actions/mcp.md) also list the native `program`, `invoke`, `get_task`, and `cancel_task` tools, which act for the server and can be listed only by one.

| Name      | Input                                                                                         | Output                                                                                                                                                                                                                                      |
| --------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graphql` | `query` (string, at most 64 KiB), optional `variables` (object) and `operation_name` (string) | `data`, the response's data, as JSON: large integers can lose precision. A response with errors fails the call with code `action_graphql_error`, or `action_access_denied` for a permission error, and lists the errors in `error.details`. |
| `cel`     | `expression` (string, at most 16 KiB), optional `bindings` (object)                           | `value`. Domain values, such as money and decimals, keep their types. A compile or evaluation error fails the call with `action_cel_error`.                                                                                                 |

A tool, an agent, or an MCP server can also declare a built-in [model](https://www.twisp.com/docs/reference/actions/models.md), such as `typesafe_jev`, in its `tools`. The other native tools are documented with what offers them: `program`, `invoke`, `get_task`, and `cancel_task` under [MCP servers](https://www.twisp.com/docs/reference/actions/mcp.md#programs), the agent tools under [Agent tools](https://www.twisp.com/docs/reference/actions/agents.md#agent-tools), and the memory and history tools under [Memory and History](https://www.twisp.com/docs/reference/actions/memory.md). [Native Tools](https://www.twisp.com/docs/actions/native-tools.md) explains when to use each.

You can create your own action of kind `graphql` or `cel`, with your own name, tool name, description, and policies, such as a `graphql` tool named `query` with `SELECT`-only policies. It takes the native tool's schemas, and its `config` names the version it runs, `{"version": 1}`, the current one when omitted. Its policies narrow what the native tool's allow.

## Tool references

`tools` lists, in order, the tools an action may call. Each entry names a tool by its registry `name`, can name the `version` to call, and can fix part of its input.

| Field      | Type                 | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ---------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`     | `String!`            | Registry name of the tool action, or of a native tool.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `input`    | `JSON`               | Fixed input: top-level input properties mapped to CEL expressions. A tool's references read `event`, for an execution started by a trigger, and `tool.input`, the calling tool's effective input; other actions' references read `event` and `execution.input`, and a [callable agent's](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents) also read `tool.input`. With a `session`, both also read `session.id` and `session.input`. Fixed values replace values supplied by the caller. |
| `approval` | `ActionToolApproval` | `NONE` (default) or `REQUIRED`. With `REQUIRED`, each call waits for a person's approval; see [Approvals](https://www.twisp.com/docs/reference/actions/sessions.md#approvals). Only an agent with a `session` can require it.                                                                                                                                                                                                                                                                                   |
| `version`  | `Int`                | The tool version to call. Omit it to call the tool's latest version.                                                                                                                                                                                                                                                                                                                                                                                                               |

When the definition is written, each name resolves to the tool's action, and reads return it as `actionId`; `action` reads that action at `actionVersion`, or at its current version for a reference that calls the latest. A missing, duplicate, disabled, or deleted tool, a `version` the tool does not have, or two tools with the same `tool.name`, fails the write, as do references that lead back to the action, through its tools, their tools, and its skills. A tool written earlier in the same request can be referenced before it publishes. Only handler kinds that call tools accept a `tools` list: of the kinds listed under [Handlers](https://www.twisp.com/docs/reference/actions/actions.md#handlers), only `tool` and `agent` do. A tool's program calls its declared tools by their `tool.name`.

### Versions

A reference with a `version` calls exactly that version, always: its schemas, program, references, and policies. Updating the tool never changes what the reference calls. A reference without a `version` calls the tool's latest version, resolved when each execution of the action starts: each invocation, each session turn, and each call of a tool or [callable agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents). Every call in that execution uses the version it resolved. Either way the invocation records, in its `tools` and `skills`, the version each reference resolved to in `actionVersion`, and each call's execution names the version it ran, so an audit always has exact versions. On a definition, `actionVersion` is the reference's `version`, or null for one that calls the latest.

A reference that calls the latest version cannot know the schema of a version written later. When the version it resolves to does not accept its fixed input, the action still runs, and each call to that tool fails with `action_input_invalid` and a hint to pin a version that accepts the fixed input or to change it. Nor can it know a later version's name: when the versions an execution resolves give two of its tools the same name, or give a tool the name of one of its skills, the execution fails with `action_failure`, naming both references, and a hint to pin one of them or rename a tool. A call never re-enters a version already running on its call stack; such a call fails with `action_call_limit_exceeded`. A [replay](https://www.twisp.com/docs/reference/actions/executions.md#replays) resolves each reference without a `version` to the version the replayed invocation resolved, so it reruns the turn with the tools and skills the turn used.

A tool's fixed input is resolved once per call of the tool, before its program runs; an expression that cannot be evaluated, or a value that fails the schema of a pinned version, fails the tool before it runs. An agent's fixed input is resolved once per invocation, before the agent runs, and stored with the invocation; redelivering the invocation reuses the stored values. For a session turn, `session.id` and `session.input` read the owner's session, so fixed input over `session.input` confines a tool to the value the session was started with. An expression that cannot be evaluated, or a value that fails the schema of a pinned version, fails the invocation before the agent runs. Fixed values replace any values the model supplies, and the call's execution records the merged input.

## The `tool` handler

Runs a tool's program. The config holds exactly `runtime` and `src`: the runtime is `starlark`, and `src` is a [Starlark](https://www.twisp.com/docs/reference/actions/starlark.md) program of at most 64 KiB. Kind `tool` requires a `tool` block.

```json
{
  "runtime": "starlark",
  "src": "def run(event):\n    return graphql('query { __typename }')\n"
}
```

The program defines `run(event)`. For a call, `event["data"]` is the call's input; for an execution started by a trigger, `event` is the typed [event](https://www.twisp.com/docs/reference/actions/executions.md#the-event). The value `run` returns becomes the invocation's `output`. The program calls the ledger with `graphql()`, CEL with `cel()`, and the tools it declares with `tools.<name>(input)`, and fails with `fail(message, hint=None)`. See [Starlark](https://www.twisp.com/docs/reference/actions/starlark.md) for the language, its globals and values, failure handling, transactions, and limits, and [Building Transactional Actions](https://www.twisp.com/docs/tutorials/advanced-actions.md) for a provisioning, rollback, and rerun example.
