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
taskscapability, 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 requiresrunsandthreads. - Tasks switched on for the deployment. Your operator decides this. Where it is off, the routes
on this page answer
404and no turn is offered a task tool, whatever the key says. - The tool's own capability:
toolingfor an API connector or an MCP server,toolingandcomputerfor a script,toolingwithgenerate_imageamong the key's system tools for an image tool,toolingwithtranscribe_audioamong the key's system tools for a transcription tool,toolingwithcomputeramong the key's system tools and thecomputercapability for a shell tool,agentsfor an agent,workflowsfor 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 withoutthreads, 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: falsenever 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 withbuiltin__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
computercapability), even while a task is using it. The task's command may run for what is left of itsmax_secondsand of the computer's age, rather than the two minutes a call inside a turn gets; a command limit set on the key'scomputercapability still shortens it. - The computer is metered while it is kept, in
sandbox_secondsfrom 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_safeoff, the task endsfailedwith the codeinterrupted, and the model readsTask was interrupted; it may or may not have completed. - With
retry_safeon, Core sends the same call again, with the sameIdempotency-Keyheader 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_requiredends the taskfailedwith the codeinput_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
requiredneeds modealways. 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¶
- Reading a trace: how a background tool is drawn in a conversation, from its start to the turn that read the result.
- Run a turn in the background: the whole turn in the background, which is a different thing from one tool call of a turn.
- Give an agent to a turn as a tool.