Skip to content

Run a tool in the background

A tool call normally blocks its turn: the model waits for the answer before it goes on. A task is one tool call that outlives the round that made it. The model starts the call, receives a task id at once and carries on with the conversation; Core runs the call separately and adds the result to the thread when it arrives. Use it for a call that takes longer than a turn should wait: a report that takes minutes to build, an export, or a specialised agent working through a long job.

This page covers which tools can run this way, how you allow it, what the model sees, and the routes your application reads and cancels tasks with. The concept behind tools is explained in Tools.

What you need

  • The tasks capability, on in the organisation's ceiling and on the key. In the console the switch is Let a turn start an agent in the background and read its result when it is done, under Permitted capabilities and on the key. It requires runs and threads.
  • Tasks switched on for the deployment. Your operator decides this. Where it is off, the routes on this page answer 404 and no turn is offered a task tool, whatever the key says.
  • The tool's own capability: tooling for an API connector or an MCP server, tooling and computer for a script, tooling with generate_image among the key's system tools for an image tool, tooling with transcribe_audio among the key's system tools for a transcription tool, tooling with computer among the key's system tools and the computer capability for a shell tool, agents for an agent, workflows for a workflow.
  • A turn stored on a thread. A result that arrives later needs a thread to land on, so a turn with store: false, or under a key without threads, cannot start a task.

Which tools can run as a task

Eight kinds of tool can be started in the background:

Tool Name the model sees Where you allow it
An agent offered as a tool agent__<name> on the agent version
An API connector api__<identifier> on the connector version
A tool of an MCP server mcp__<server>__<tool> on the server's registration, for all its tools
A script script__<name> on the script version
A SQL connector sql__<name> on the connector version
An image tool builtin__generate_image__<model> for the organisation, through /tools/{name}/background
A transcription tool builtin__transcribe_audio__<model> for the organisation, through /tools/{name}/background
A shell tool builtin__computer_exec, builtin__computer_write_file, builtin__computer_read_file for the organisation, one setting per tool, through /tools/{name}/background
A workflow workflow__<name> nowhere: a workflow always starts in the background

A renamed tool follows the tool it stands for. Every other tool Core supplies, such as search, memory, datastore and mail tools, always runs inside its turn.

A workflow has no setting. It only ever runs in the background, so calling workflow__<name> directly starts it and answers a task id, as a tool set to always does, and a turn that cannot hold a task is not offered it. It takes no max_seconds (the run's own deadline applies) and no retry_safe. Let an agent start it describes it.

A tool that signs in as a person is never started in the background. An API connector or an MCP server whose authentication is the person's own login always runs inside its turn, whatever its setting says. You can still store the setting; it takes effect when the authentication is another kind.

Image tools

An image tool has no version or registration of its own, so the organisation says how it may run: one setting per tool, kept for the organisation and read by every key of it. It has the same three fields as below. Without a setting, an image tool waits for its answer.

curl -s -X PUT "$CORE_URL/tools/builtin__generate_image__gpt-image-1/background" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"mode": "allowed", "max_seconds": 300}'

GET on the same path reads the setting (never when there is none), and DELETE, or a PUT with "mode": "never", removes it. A person who administers the organisation uses /tenants/{tenant_id}/tools/{name}/background. Only image tools, transcription tools and the three shell tools take a setting: any other tool answers 422 with a sentence that says where its own setting lives. The console edits it from the Tools page (In the console).

An image task draws the image when Core picks it up, under the key's policy as it is then, and reads any reference images with the key's access then. If you narrow the policy, or remove the setting, between the start and the pickup, the task fails and the image model is never called. retry_safe for an image tool means a second call to the image provider when Core had to send it again; leave it off unless that is acceptable. Core meters one image either way, but the provider may charge the deployment for both calls.

A key that hides personal data starts an image in the background too, and the image provider reads the same placeholders it would in the turn. The model writes <PERSON_1> in its prompt, the tool receives the real name, and Core puts the placeholder back before the prompt leaves for the provider. For a task, Core keeps the entries of the placeholder table that the prompt holds, encrypted on the task, and the worker uses them to hide the names again; they are deleted when the task ends. If Core cannot decrypt them when it picks the task up, the task fails with pseudonymisation_failed and the provider is never called. The task's own arguments hold the real values, as the conversation's items do: hiding personal data is about what a provider reads, not about what Core stores for your organisation.

Each entry of GET /tools and GET /tools/{name} carries background, which says what holds now and where it is stored: mode, max_seconds, retry_safe, stored_on (connector_version, script_version, mcp_server or organisation; null for a tool that cannot run in the background) and reason (person_login when a person's own login makes it never).

Transcription tools

A transcription tool takes the organisation's setting exactly as an image tool does, one setting per tool, so a long recording can be transcribed while the conversation goes on (Transcribe audio):

curl -s -X PUT "$CORE_URL/tools/builtin__transcribe_audio__whisper-large-v3/background" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"mode": "allowed", "max_seconds": 600}'

The task reads the file and transcribes it when Core picks it up, under the key's policy and access as they are then; a policy narrowed since the start fails the task before the recording is sent. retry_safe means a second call to the vendor when Core had to send it again, which the vendor may charge for; Core bills the seconds once either way. On a key that hides personal data the prompt is handled as an image task's prompt is, above. The recording itself is never hidden (Hide personal data from the model), and the transcript arrives on the thread as an untrusted tool result.

Shell tools

The three shell tools take the organisation's setting as an image tool does, one setting per tool, so you can let a long command run in the background and keep file writes and reads in the turn:

curl -s -X PUT "$CORE_URL/tools/builtin__computer_exec/background" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"mode": "allowed", "max_seconds": 1800}'

A shell task runs on the conversation's computer. It sees the files the turn wrote there, unless it waited in the queue until that computer was gone, and what it writes stays for the turns after it. Unlike a script task, which gets a computer of its own, the computer is the one the turns of the thread share:

  • It stays while the task needs it. The turn that started the task ends without removing the computer, and so does any turn that ends before the task's result has been read: the wake that answers the result finds it. The turn that reads the result removes it when it ends; without such a turn, Core removes it about ten minutes after the task, at most fifteen, unless your operator changed those times. A task started before the thread had a computer makes one. A second turn running on the thread while another holds the computer gets a computer of its own, which it does not share, and a turn with store: false never uses the conversation's computer.
  • Deleting the thread removes it, shortly after, whatever is running on it; so does erasing the person the thread belongs to.
  • One command at a time. While a task's command runs, a shell call the model makes in a turn is not queued behind it: it answers at once The computer is busy with a background task., and the model can check on the task with builtin__task_status. A task that finds a turn's command running waits for it to finish.
  • Its maximum lifetime still holds. A computer is removed when it reaches the age your operator allows (an hour unless they changed it, or the shorter limit on the key's computer capability), even while a task is using it. The task's command may run for what is left of its max_seconds and of the computer's age, rather than the two minutes a call inside a turn gets; a command limit set on the key's computer capability still shortens it.
  • The computer is metered while it is kept, in sandbox_seconds from when it was made until it is removed, to the key whose turn made it.

retry_safe for a shell tool means running the command again after Core lost the call, on the same computer if it is still there: turn it on only when running the command twice is harmless.

Allow a tool to run in the background

An agent, an API connector, an MCP server and a script each carry a background setting. Without it, every call waits for its answer.

Field Meaning
mode never (the default): every call waits for its answer. allowed: the model may start the tool in the background as well as call it directly. always: a direct call starts it in the background and returns a task id, wherever the turn can hold a task
max_seconds how long a task running this tool may take, in seconds, at least 1. Leave it out to use the limit your operator set for the deployment (an hour unless they changed it). You can shorten that limit and never lengthen it: a larger value is cut to it
retry_safe connectors, MCP servers, scripts, image tools, transcription tools and shell tools only: whether Core may send the call again after an interruption. false by default, see When a call is interrupted

An agent has no retry_safe: an agent task is a background run, which resumes from its last completed round.

Through the API

Send background when you create an agent or publish a version of it (POST /agents, POST /agents/{agent_id}/versions), when you create or change an API connector (POST /api_connectors, PATCH /api_connectors/{connector_id}) or a SQL connector (POST /sql_connectors, PATCH /sql_connectors/{connector_id}), when you register or change an MCP server (POST /mcp_servers, PATCH /mcp_servers/{server_id}) and when you create a script or write a version of it (POST /scripts, POST /scripts/{script_id}/versions).

curl -s -X PATCH "$CORE_URL/api_connectors/tool_0KR7dX..." \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"background": {"mode": "allowed", "max_seconds": 900, "retry_safe": true}}'

On a connector the setting is part of what the connector does, so changing it records a new version. On a PATCH, "background": null sets it back to never, and a body without the field leaves it as it is. On a script it is part of the version: a version written without it is never, and a version that changes only this setting is a new version.

In the console

The console holds the same setting as the section Asynchronous tool, on an agent, an API connector, a script and an MCP server: see Asynchronous tool. A script's form has the same section, started from the latest version.

Every tool's setting is also read in one place. Open Tools and choose Background under the search box: each tool is listed as Cannot, Allowed or Always, with the longest run and where the setting is stored, and the cards in the overview carry a mark for the tools that may run in the background. A tool that cannot says why when a person's own login is the reason. The same state, with the same edit button, is on a tool's own page and on the system tools page.

The setting stays with the tool, so the button depends on where it lives:

  • Image tools, transcription tools and the shell tools: Edit opens a dialog. Choosing Never removes the organisation's setting.
  • An MCP server: Edit opens the same dialog and saves it on the server.
  • An API connector or a script: the setting is part of a version. New version of the connector or New version of the script opens the form for the next version, and saving there makes one.
  • A SQL connector: the setting is part of the connector's version too. New version of the connector opens the SQL connector's form for the next version, and saving there makes one.
  • An agent: the setting is part of the agent's version too. Go to the agent opens the agent's page; editing the agent there and saving makes the next version.
  • A workflow always runs in the background, and a source that signs in as a person never does; neither is edited. An alias follows its target, so change the setting on the target.
  • A tool with no setting, such as a knowledge search tool or a datastore tool, always waits for its answer. The list says This tool has no background setting.

Screenshots of the Background view and the dialog are owed, and the overview screenshot on Tools in the console predates the view switch and the background marks.

What the model sees

When a turn can hold a task and offers at least one tool that allows it, Core adds five tools to that turn. You do not name them in tools, no toolset contains them and they are not in the Overview of Tools.

Tool Arguments Answer
builtin__task_start tool, arguments {"task_id": "...", "status": "queued"}, or one fixed sentence when the start is refused
builtin__task_status task_id, or none for every open task of the thread status, tool, start time, deadline and, when the tool reported one, the last progress line
builtin__task_result task_id, optionally wait_seconds the result when the task completed; its status while it runs, and when it failed, was cancelled or expired. wait_seconds waits a short while for the task to end, at most as long as one tool call may take
builtin__task_cancel task_id the status after the cancellation
builtin__task_send task_id, input agent tasks only: the message is added to the agent's own thread, and the agent reads it before its next round

The model chooses by which tool it calls. Calling api__report directly is the ordinary call that waits. Calling builtin__task_start with {"tool": "api__report", "arguments": {...}} starts the same call as a task. A tool that allows it says so in its description, so the model knows it has the choice. A tool set to always starts as a task on a direct call too.

The key's policy decides a start twice: the name builtin__task_start, and then the tool that is started, by its own name and labels. A rule about Tools therefore covers a tool whichever way it is started.

A start is refused with one fixed sentence when the thread or the organisation is at its limit (Limits), or when the tool named is not one this turn offers with background allowed. Those sentences, and the ones under How a result comes back, are Core's own and always in English.

An agent task is a background run of that agent on a thread of its own, under your key and for the same person. A task read names both as run_response_id and run_thread_id, and you can follow it as any background run.

An API connector, MCP or script task is checked again when Core picks it up: if the key has been revoked, the tool removed, the policy narrowed or the background setting withdrawn since the start, the task fails with a code and nothing is called.

A script task runs on a computer of its own, made when Core picks the task up and removed when the script returns, never on the conversation's computer. The files the starting turn was given are copied in under /workspace/inputs/, as for a script called in a turn. The script may run for most of the task's max_seconds, within the computer's maximum lifetime, rather than the two minutes a call inside a turn gets unless your operator changed it; a version's own timeout_seconds, and a command limit set on the key's computer capability, still shorten it. The files it writes come back as described below.

How a result comes back

When a task ends, Core adds one item to the thread that names the task and the tool and says how it ended.

The task What the model reads
completed Task <id> (<tool>) completed. Its result: followed by the result, cut at 32 KiB
failed Task <id> (<tool>) failed. For a connector or MCP call, followed by what a direct call would have answered
was cancelled Task <id> (<tool>) was cancelled.
ran past its max_seconds Task <id> (<tool>) expired before it finished.
was interrupted Task <id> (<tool>) failed. followed by Task was interrupted; it may or may not have completed.

A result is information for the model and never an instruction, as with every tool result (Tool-result trust). The same holds for a progress line and for whatever an agent task says.

Files come back as artifacts. A file or an image that a connector, an MCP tool, a script or an image tool answers in the background is kept as an artifact, as after a direct call, and the item that carries the result refers to it: at most ten per call, each at most 25 MiB, for a key with the artifacts capability. A file past those limits, and every file for a key without artifacts, is left out and the result says so. What an agent task produced stays on that agent's own thread.

While the turn that started the task is still running, the result joins its next round.

After that turn has ended, Core starts one turn on the same thread to answer the result: a wake. It is a background run under the same key and for the same person, with the tools the starting turn had and no new input. Your application learns of it as of any background run: by reading the thread or through events. A wake follows only a turn that completed. A wake that starts a task of its own is woken for that result too, up to a limit your operator sets (three in a row unless they changed it); past it the result is added to the thread and nothing is started.

To leave results on the thread for your own next turn instead, send "task_wake": false on POST /responses, or set wake to false in the key's tasks capability to decline for every turn of that key.

Core announces each task as an event: thread.task.started, thread.task.completed, thread.task.failed, thread.task.cancelled and thread.task.expired. Each start is metered as one task_start (Usage through the API). A tool run in the background costs what a direct call costs: each time a connector, an MCP tool or a script runs for a task, it is metered as one tool_invocation under the tool's own name, once even when Core had to send it again. A script's computer is metered in sandbox_seconds as for a direct call, and the conversation's computer for as long as a shell task keeps it. A transcription task bills its seconds of audio once, as one audio_transcription row.

Being told when a task ends

An organisation can ask to be told when a task ends. Switch on Task finished under Kinds of notification (Notifications): one mail or Slack message per task that completed, failed or expired, naming the tool and the task, never the result.

When a call is interrupted

Core can lose a call while it is out, for instance during a restart. It then cannot know whether your endpoint acted on the call.

  • With retry_safe off, the task ends failed with the code interrupted, and the model reads Task was interrupted; it may or may not have completed.
  • With retry_safe on, Core sends the same call again, with the same Idempotency-Key header as the first time. After repeated interruptions the task ends as interrupted all the same.

Turn retry_safe on only when your endpoint treats a repeat with the same Idempotency-Key as the call it already handled. For a script, a repeat runs it again from the start on a fresh computer: turn retry_safe on when running it twice is harmless. An agent task needs no such promise: it resumes from its last completed round.

Limits

Your operator sets these for the deployment. The numbers are the values a deployment has unless the operator changed them.

Limit Value Past it
How long one task may run 3600 seconds; shortened per tool with max_seconds the task ends expired
Open tasks on one thread 8; a key can lower it with max_open_per_thread in its tasks capability the start is refused: This conversation has too many open tasks.
Open tasks in the organisation 1000 the start is refused: Too many tasks are running; try again later.
Wakes in a row 3 the result is added to the thread, nothing is started
Tasks started by a task none: an agent running as a task is offered no task tools —

An open task is one that is queued or running.

MCP servers with tasks of their own

An MCP server can declare that it runs a tool as a task itself and answers with a handle (MCP revision 2025-11-25). Core records what each tool declares when it reads the server. When such a tool is started as a task, Core asks the server to run it as the server's own task and then looks in on it at intervals instead of holding a connection open: at the interval the server names, within bounds your operator sets.

  • Progress. The server's status message becomes the task's progress: one line, at most 200 characters. A server that sends progress notifications during a call Core keeps open for a task fills the same field.
  • A server that asks for input fails the task. A task has nobody to ask, so input_required ends the task failed with the code input_required.
  • Cancelling and expiry tell the server. Core sends the server a cancellation, once. The task's status does not wait for the server's answer.

Three things to know when you register such a server:

  • Deleting a thread does not stop the server's task. The task goes with its thread, and the server's own task runs on until the deadline Core gave it at the start.
  • Files and images in the server's answer are left out: unlike a call Core holds itself, a result Core collects from the server's own task does not keep them.
  • A tool that declares tasks required needs mode always. Core sends a call as the server's task only when the call is itself started as a task. A direct call inside a turn is an ordinary call, which such a tool does not accept. Set the server to Always so every call starts as a task.

Read and cancel tasks from your application

Whoever may read a thread may read its tasks. The examples use made-up ids.

List a thread's tasks

curl -s "$CORE_URL/threads/thr_0KQ9aB.../tasks?limit=20" -H "Authorization: Bearer $CORE_KEY"
{
  "items": [
    {
      "object": "task",
      "id": "task_0KR8mT...",
      "thread_id": "thr_0KQ9aB...",
      "response_id": "resp_0KQ2mR...",
      "call_round": 0,
      "call_id": "call_1",
      "call_id_sha256": null,
      "tool": "api__report",
      "tool_version": 3,
      "arguments": { "customer": "acme", "year": 2025 },
      "status": "running",
      "created_at": "2026-03-02T09:14:07Z",
      "deadline_at": "2026-03-02T09:29:07Z",
      "ended_at": null,
      "progress": null,
      "result": null,
      "error_code": null,
      "run_response_id": null,
      "run_thread_id": null,
      "depth": 1,
      "result_position": null,
      "delivered_response_id": null
    }
  ],
  "next_cursor": null
}

Pass next_cursor as cursor for the next page.

Read one task

curl -s "$CORE_URL/tasks/task_0KR8mT..." -H "Authorization: Bearer $CORE_KEY"

The answer is one task object as above. Once the task has ended (other fields as above):

{
  "object": "task",
  "id": "task_0KR8mT...",
  "status": "completed",
  "ended_at": "2026-03-02T09:21:40Z",
  "result": "{\"report_url\": \"https://your-app.example/reports/acme-2025\"}",
  "error_code": null,
  "result_position": 14,
  "delivered_response_id": "resp_0KQ4pV..."
}
Field Meaning
status queued, running, completed, failed, cancelled or expired
result what the task answered, cut at 32 KiB; null until it ended, and for a task that expired or was cancelled
error_code why a task failed, such as interrupted, key_revoked, tool_unavailable, background_withdrawn, pseudonymisation_failed, cost_limit or input_required; otherwise null
response_id, call_round, call_id the response and the tool call that started the task, as they appear in the trace. A very long call_id is null, and call_id_sha256 then holds its SHA-256
result_position the position on the thread of the item that holds the result; null until then
delivered_response_id the response whose turn first read the result; null while no turn has
run_response_id, run_thread_id for an agent task, the response of the background run it runs as, and that run's thread
labels the labels of the turn that started it, copied when it started; no rule reads them

Cancel a task

curl -s -X POST "$CORE_URL/tasks/task_0KR8mT.../cancel" -H "Authorization: Bearer $CORE_KEY"
{ "object": "task", "id": "task_0KR8mT...", "status": "cancelled", "ended_at": "2026-03-02T09:16:02Z" }

Core answers 200 with the task (shortened here). Cancelling a task that is already cancelled answers the same; a task that ended any other way answers 409. A call that is out when you cancel is stopped shortly after.

Send a message to an agent task

curl -s -X POST "$CORE_URL/tasks/task_0KR9cW.../messages" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"input": "Leave out the invoices from before 2024."}'
{ "object": "task", "id": "task_0KR9cW...", "tool": "agent__billing", "status": "running" }

Core answers 200 with the task (shortened here). The message is added to the agent's own thread, and the agent reads it before its next round. input holds 1 to 64,000 characters. A task that has ended, and every task that is not an agent task, answer 409.

Cancelling and messaging change the work, so they belong to the person the task was started for. On a thread several people use, another person's request can read the task and cannot cancel or message it.

Every route answers 404 when tasks is not on for the key or the deployment, and for a task or thread the key does not reach.

See also