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. A built-in model is a tool whose schemas come from its API. An agent 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'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.

FieldTypeMeaning
nameString!Name the model sees: 1 to 64 letters, digits, underscores, or hyphens.
descriptionString!When and why to call the tool.
inputSchemaJSONJSON 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 kind, which takes the native tool's schemas when it is omitted.
outputSchemaJSONOptional JSON Schema 2020-12 for the output. It applies to every execution of the action, including those started by its triggers.
sessionActionToolSessionInputThe session each call is recorded in, for an action with a session. See Tool sessions.
outputActionToolOutputInputHow an agent that calls the tool keeps its results: {offload: AUTO} (the default) or {offload: NEVER}. See below.

output.offload says whether an agent 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.

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, or loads a skill, 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: the caller's policies and the tool's own must both allow them. A call to a tool with a session 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 fieldTypeMeaning
executionIdUUID!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.
actionVersionInt!The tool version to call. Its schemas and implementation are used exactly.
inputJSON!JSON object validated against the version's input schema.
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, 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, 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 fieldTypeMeaning
executionIdUUID!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.
actionVersionIntThe 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.
inputJSON!JSON object validated against the version's input schema before anything is queued.
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 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 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 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 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, 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 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, which gains a version with each release. Each carries its own policies, a layer of the 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 also list the native program, invoke, get_task, and cancel_task tools, which act for the server and can be listed only by one.

NameInputOutput
graphqlquery (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.
celexpression (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, 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, the agent tools under Agent tools, and the memory and history tools under Memory and History. Native Tools 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.

FieldTypeMeaning
nameString!Registry name of the tool action, or of a native tool.
inputJSONFixed 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 also read tool.input. With a session, both also read session.id and session.input. Fixed values replace values supplied by the caller.
approvalActionToolApprovalNONE (default) or REQUIRED. With REQUIRED, each call waits for a person's approval; see Approvals. Only an agent with a session can require it.
versionIntThe 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, 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. 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 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 program of at most 64 KiB. Kind tool requires a tool block.

{
  "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. 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 for the language, its globals and values, failure handling, transactions, and limits, and Building Transactional Actions for a provisioning, rollback, and rerun example.

Previous
Actions