# Tools

Typed, versioned programs that clients, agents, and other tools call inside their own transaction or start to run on their own.

A **tool** is an action that can be called. Its `tool` block gives it a name, a description, and an input schema; its handler does the work. Clients call tools through the API, agents call the tools they declare, MCP servers offer tools to agents outside Twisp, and tools call other tools. Every call is an [execution](https://www.twisp.com/docs/actions/triggers-and-executions.md#executions-as-records) with its input, output, and caller.

Most tools you write have kind `tool`: a short Starlark program that reads and writes the ledger through GraphQL. A built-in [model](https://www.twisp.com/docs/actions/models.md) and a [callable agent](https://www.twisp.com/docs/reference/actions/agents.md#callable-agents) are tools too, with handlers of their own.

## Schemas

A tool's `inputSchema` is a JSON Schema for the call's input, and its optional `outputSchema` describes what it returns. Input is validated before the handler runs, and output before the call succeeds, so a caller can rely on both. The `name` and `description` are what an agent's model sees, so write the description as when and why to call the tool.

Prefer narrow tools with specific inputs, such as `lock_account(account_id, reason)`, to tools that accept anything. The schema is part of the contract: it decides what a caller, including a model, can ask for. See [Tools](https://www.twisp.com/docs/reference/actions/tools.md) for every field.

## The Starlark runtime

A tool of kind `tool` holds a program in `config.src`, with `runtime: "starlark"`. The program defines `run(event)`; for a call, `event["data"]` is the input, and the return value is the output.

The program has a small, fixed set of capabilities:

- `graphql(query, variables)` runs a GraphQL operation and returns its data. Later calls read earlier writes.
- `cel(expression, bindings)` evaluates a CEL expression.
- `tools.<name>(input)` calls a tool the action declares.
- `fail(message, hint=None)` fails the call with a message and a hint for the caller.

Every [CEL function](https://www.twisp.com/docs/reference/cel.md) is also available directly in Starlark, so a program computes exactly with money and decimals: `money("100", "USD") * 2`, `decimal.Round(x, "half_even", 2)`. The program has no filesystem, network, or process access, and its steps and calls are bounded. [Writing Starlark](https://www.twisp.com/docs/actions/starlark.md) shows how to write, test, and debug a program, and the [Starlark reference](https://www.twisp.com/docs/reference/actions/starlark.md) has the runtime's rules and limits.

## Calling and starting

There are two ways to run a tool, and they differ in which transaction the work belongs to.

|                   | Call: `invokeAction`                                                  | Start: `startAction`                                                              |
| ----------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| When it runs      | Now, synchronously, inside the request transaction.                   | Later, on its own, after the request commits.                                     |
| What you get back | The finished execution, with its output or error.                     | The execution, `QUEUED`.                                                          |
| Its writes        | Commit or roll back with the rest of your request.                    | Commit with the execution's own outcome, invocation by invocation.                |
| Retries           | None. Repeat the call with the same `executionId` to read the result. | The action's [execution settings](https://www.twisp.com/docs/reference/actions/actions.md#execution-settings). |
| Version           | `actionVersion` is required.                                          | `actionVersion` is optional; omitted, it runs the current version.                |
| Authority         | Yours, narrowed by the tool's policies.                               | Yours, narrowed by the tool's policies, checked again at each invocation.         |

**Call** a tool when its result is part of your request: you want the output now, or its writes must commit together with your own. A call that fails returns a `FAILED` execution with a `code`, a `message`, and a `hint`; none of its writes remain, and the rest of your request can still commit. See [Calling a tool](https://www.twisp.com/docs/reference/actions/tools.md#calling-a-tool).

**Start** a tool, or a callable agent, when the work should not hold your request open, such as an agent's whole run. The start commits with your request, so a request that rolls back started nothing, and the execution then runs with retries as a triggered execution does. It runs under your authority, never the action's own client. A tool with a session cannot be started, and an execution cannot start one; call it instead. See [Starting a tool](https://www.twisp.com/docs/reference/actions/tools.md#starting-a-tool).

Both take an `executionId` you choose. Repeating a request with the same identifier and input returns the execution it made instead of running the tool again, so a client can retry safely.

## Nested calls

A tool declares the tools it calls in its `tools` list and calls them with `tools.<name>(input)`. Each call, including each `graphql` and `cel` call, is a child execution of the calling tool, so the call tree is recorded exactly as it ran.

A call and everything beneath it is one atomic unit. When a nested call fails, its writes and those of every call beneath it roll back, and by default the calling tool fails too. Pass `check=False` to handle the failure instead: the call returns a result with `ok`, `output`, and `error`, the failed call's writes are already gone, and the program decides what to do next. A write conflict or a passed deadline cannot be handled this way; it fails the whole request.

Nesting is limited to 8 tool levels below the first call and 1,024 calls beneath one call. See [Nested calls](https://www.twisp.com/docs/reference/actions/tools.md#nested-calls).

## Authority

A tool never grants its caller anything. Each operation a tool performs must be allowed by the caller and by the `policies` of every tool on the call stack, so each layer can only narrow what the layers above it allow. A tool with no policies can perform no operation: list the access its program needs. See [Policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain) and [Authority and Audit](https://www.twisp.com/docs/actions/authority-and-audit.md).

## Native tools

Every tenant has a set of native tools without creating them; [Native Tools](https://www.twisp.com/docs/actions/native-tools.md) covers them all. Two are useful everywhere:

- `graphql` runs a GraphQL operation. Its own policies allow everything, so a `graphql` call can do exactly what the layers above it allow.
- `cel` evaluates a CEL expression and performs no operation.

A tool program can call both without declaring them. An agent calls them only when it lists them in `tools`.

To give an agent GraphQL access narrower than its own policies, create your own action of kind `graphql` with your own name, description, and policies, such as a `query` tool that allows only `SELECT`. It takes the native tool's schemas, and its policies narrow what the native tool's allow. See [Native tools](https://www.twisp.com/docs/reference/actions/tools.md#native-tools).

```graphql
mutation CreateQueryTool($id: UUID!) {
  actions {
    createAction(
      id: $id
      input: {
        name: "query"
        kind: "graphql"
        config: { version: 1 }
        tool: { name: "query", description: "Read ledger records with a GraphQL query." }
        policies: [{ effect: ALLOW, actions: [SELECT], resources: ["financial.*"] }]
      }
    ) {
      actionId
      pending
    }
  }
}
```

## Tool references

An action's `tools` list names each tool it may call, in order. A reference can do two more things:

- **Pin a version.** A reference with a `version` always calls exactly that version: 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 starts, and every call in that execution uses the version it resolved. Either way, each invocation records the version it used.
- **Fix part of the input.** `input` maps top-level input properties to CEL expressions, evaluated before the tool runs. Fixed values replace whatever the caller supplies, so an agent's model cannot change them. An expression can read the triggering `event`, the calling tool's `tool.input`, the execution's `execution.input`, and, with a session, `session.id` and `session.input`.

```graphql
tools: [
  {
    name: "get_account_activity"
    version: 4
    input: { account_id: "string(session.input.account_id)" }
  }
]
```

Here a session agent always calls version 4, and only ever for the account its session was started for. Pin versions where a change of behavior must be a deliberate edit of the caller; leave them unpinned where callers should pick up fixes automatically. A reference can also require a person's [approval](https://www.twisp.com/docs/reference/actions/sessions.md#approvals) before each call, for an agent with a session. See [Tool references](https://www.twisp.com/docs/reference/actions/tools.md#tool-references) and [Versions](https://www.twisp.com/docs/reference/actions/tools.md#versions).

## Example

A `lock_account` tool locks an account and opens a case for operations. Locking is the point of the call, so a failure there fails it. Opening the case is secondary, so the program handles its failure with `check=False`.

```graphql
mutation CreateLockAccount($id: UUID!, $src: String!) {
  actions {
    createAction(
      id: $id
      input: {
        name: "lock_account"
        kind: "tool"
        config: { runtime: "starlark", src: $src }
        tool: {
          name: "lock_account"
          description: "Lock an account so it blocks posting, and open a case for operations."
          inputSchema: {
            type: "object"
            properties: {
              account_id: { type: "string", format: "uuid" }
              reason: { type: "string" }
            }
            required: ["account_id", "reason"]
            additionalProperties: false
          }
        }
        tools: [{ name: "open_case", version: 2 }]
        policies: [
          { effect: ALLOW, actions: [SELECT, INSERT, UPDATE], resources: ["financial.Account.*"] }
        ]
      }
    ) {
      actionId
      version
      pending
    }
  }
}
```

```python
def run(event):
    data = event["data"]
    graphql(
        "mutation Lock($id: UUID!) { updateAccount(id: $id, input: {status: LOCKED}) { accountId } }",
        {"id": data["account_id"]},
    )
    case = tools.open_case({"account_id": data["account_id"], "note": data["reason"]}, check=False)
    return {"locked": True, "case_opened": case.ok}
```

Once the tool is `ACTIVE`, call version 1:

```graphql
mutation Lock($executionId: UUID!) {
  actions {
    invokeAction(
      id: "<lock_account actionId>"
      input: {
        executionId: $executionId
        actionVersion: 1
        input: {
          account_id: "70eec1a6-eac1-47aa-a507-e18f2d486425"
          reason: "Card reported stolen"
        }
      }
    ) {
      status
      invocation {
        output
        error {
          code
          message
          hint
        }
      }
    }
  }
}
```

If the update is denied or fails, the call returns `FAILED` and the account is unchanged. If `open_case` fails, its writes roll back, the lock stays, and the output says `case_opened: false`. The call's execution lists both nested calls as `children`, and the lock commits only when your request does.

## Next steps

- Follow [Building Transactional Actions](https://www.twisp.com/docs/tutorials/advanced-actions.md) for a Starlark tool with atomic rollback, safe reruns, and a direct call.
- Read [Skills](https://www.twisp.com/docs/actions/skills.md) to give agents guidance for using tools, and [Agents](https://www.twisp.com/docs/actions/agents.md) to let a model call them.
- Read [MCP Servers](https://www.twisp.com/docs/actions/mcp-servers.md) to offer tools to agents outside Twisp.
- See the [Tools reference](https://www.twisp.com/docs/reference/actions/tools.md) for every field, limit, and error.
