Self-hosted agents
Jobs and results
Queue work for your agents, find out which device runs it, cancel it, and get the results back.
Create a job
Organizations queue jobs from AI clients and scripts with the MCP tool agent_job_create, using a key with agents:run. exec jobs also need agents:write on the key. The platform's apps use the agents API.
| Field | |
|---|---|
type | http.batch, browser.batch or exec. |
input | The job's input, as described in Job types. Up to 300,000 characters of JSON. |
hosts | The hosts the job will contact, such as ["www.example.com"] (up to 50; *.example.com wildcards allowed). Used to pick a device; see below. |
label | A name shown in Cloud (up to 120 characters). |
ttlSeconds | How long the job may wait for a device, from 60 seconds to 7 days (the default). |
callbackUrl | An https URL to notify when the job finishes. |
callbackSecret | Sent back in the callback's x-si-callback-secret header (MCP only). |
browser.batch input is validated when the job is created, and a mistake fails the request with the field at fault. The result is the job's id and when it stops waiting, such as { "jobId": "job_…", "expiresAt": "2026-10-12T09:30:00.000Z" } (the API returns expiresAt in epoch seconds).
Which device runs it
Jobs wait in a queue. When a device polls, it claims the oldest queued job it's allowed to run:
- The device has the job type's capability (HTTP requests, Browser pages or Shell commands).
- For
exec: the device has full network access, or it's on an allowlist and its agent limits shell commands to that allowlist (see Shell commands on an allowlist). - For the others: the device has full network access, or every host in
hostsis on its allowlist.
List every host the job needs in hosts. A device on an allowlist claims a job whose hosts are all allowed; if the job then contacts a host that isn't, that part fails with network_denied.
A poll looks at the organization's 25 oldest queued jobs. Agents keep a connection open to the platform, which tells them about new jobs at once; they also poll every 60 seconds (every 15 seconds without the connection), so a job starts within moments once an eligible device is free.
Why a job is still queued
Cloud → Agents explains each queued job:
- no agents are connected;
- the agents that could run it are offline; or
- what each agent lacks: a capability, hosts outside its allowlist, or full network access for shell commands.
Request access next to an agent files network requests for the hosts it lacks (they appear under Network requests for approval) and emails the agent's owner what else the job needs. Nothing changes until someone approves; only the agent's owner can add capabilities or allow full network access (see Approval).
A job that no device claims within its ttlSeconds expires and is never run.
Cancel a job
Cancel from Cloud → Agents, with the MCP tool agent_job_cancel (agents:run), or the API.
- A queued job is canceled at once.
- A running job is
cancelinguntil its device stops: between requests or pages, and shell commands are stopped. The device sends a heartbeat every 20 seconds while it works, and hears about the cancel at once over its connection or with the next heartbeat. - A running job whose device hasn't been heard from for 2 minutes is canceled at once.
Canceled jobs keep no results. Jobs that already finished can't be canceled.
Statuses
| Status | |
|---|---|
queued | Waiting for an eligible device. |
claimed | A device is running it (shown as running, or canceling after a cancel). |
done | The device ran it and uploaded the results. |
failed | The job couldn't run, for example because of invalid input or a missing capability. error says why (up to 2,000 characters). |
canceled | Canceled before it finished. |
expired | Nobody claimed it within its ttlSeconds. |
done means the job ran, not that everything in it succeeded: requests and pages that failed are reported in the results, each with its own error.
Cloud → Agents shows recent jobs with their status, device, the device's last heartbeat and the error.
Results
The agent uploads the results as JSON. They're kept for 7 days.
Read a job with agent_job_get (or the API); once it's done, resultsUrl is a link to download the results, valid for an hour. Read the job again for a fresh link. Long jobs are fine: an agent running a job for more than 45 minutes gets a new upload link before it finishes.
Callbacks
With a callbackUrl, the platform posts to it when the job is done, has failed or was canceled:
POST /your/callback HTTP/1.1
content-type: application/json
x-si-callback-secret: <callbackSecret>
{ "jobId": "job_…", "status": "done", "resultsUrl": "https://…" }A failed or canceled job sends its status and "error" instead of resultsUrl. The header is sent only when the job has a callbackSecret; check it before trusting the request.
- Answer with a 2xx status within 60 seconds.
- A 5xx response, a timeout or a connection failure is retried, about 90 seconds apart, for up to 5 attempts in all.
- A 4xx response isn't retried.
callbackUrlmust be anhttpsURL with a domain name.
The resultsUrl in a callback is valid for an hour, like any other.