Agents

Run an agent loop

One call that drives this computer until the task is done: the loop screenshots, asks a model what to do, performs it, and repeats. Answered by the control plane rather than forwarded to a hypervisor, and it can run for minutes.

The computer must already be RUNNING. This endpoint will not start one for you: starting is billable, and it is not a decision to make on somebody’s behalf because they sent a prompt. A stopped or suspended computer is refused with status 409, and so is one another agent run is already driving — that one with reason: "contention", because it ends when the other run does, as does one whose disk is still being made.

WITH STREAMING, THE DEFAULT, THOSE REFUSALS ARRIVE ON A 200. Every refusal found after the request is accepted — an unknown computer (404), one not running or already driven (409), a credential refused part way through, the model API’s own errors, and on an OpenAI run a batch, bash command or screenshot that failed — arrives on the stream as event: error with {request_id, error, status, reason?, cost_usd?, top_up_url?, usage, steps}, where status is the status it would have had and steps is the LIST of AgentSteps taken before it failed — the list stream: false calls steps_taken, not the count AgentResult.steps carries on done (an unexpected failure of the stream itself sends {request_id, error, status: 500} alone). Only stream: false answers them as HTTP statuses. What is checked before the request is accepted — the key, the role, the rate budget, the body, X-Model-Key — is an HTTP status either way.

Send X-Model-Key to use your own Anthropic or OpenAI key. The key is never stored; the provider bills you directly and no Mandala AI credits are spent. Select OpenAI with a gpt-*, o-series or computer-use-preview model, or provider: openai. A key whose prefix belongs to the other provider — an sk-ant- key for OpenAI, or any other sk- key for Anthropic — is a 400. OpenAI uses the Responses API with native computer batches and a bash function on this desktop. It sends prompts, screenshots and command output to OpenAI, using stored responses to continue the run. Each OpenAI batch is admitted in full against max_steps and the rate budget before any action runs; a batch that exceeds the remaining allowance is not partially executed. A batch, bash command or screenshot that fails ends an OpenAI run as a failure rather than with a stop: it carries the status, message and any reason the platform answered that call with, and the steps already taken — for a batch, the actions the desktop acknowledged, and the failed one with its error when the desktop said which it was. Nothing is replayed, so inspect the desktop before continuing. A command that runs and exits non-zero is not a failure; its output goes back to the model. OpenAI requires your own key. Funded runs are metered by Mandala. Without that header, eligible accounts can use funded AI credits when enabled. Funded runs require a plan, a supported Stripe billing country, and at least $0.25 available for the first model call; otherwise they are refused before streaming (402 with top_up_url for insufficient credits; 400, 403 or 503 otherwise, as each of those statuses says). Funded runs use the platform system prompt and tools, default to claude-sonnet-5-5, and also allow claude-opus-5-5. Custom system prompts and other initially selected models require your own key. Anthropic default fallback routing is enabled and can serve a broader model; each billed attempt uses that model’s rate. Each model call reserves $0.25, then settles actual usage at 1.3× list price (deployment configurable). A completed call can overrun its reserve or max_cost_usd; its full cost is charged before another call is admitted. Usage beyond remaining credits creates debt that survives credit expiry; future grants and top-ups settle it first. If credits become unavailable mid-run it ends with credits_exhausted; the cost cap ends with max_cost. Solo/Studio/Swarm monthly grants are $5/$10/$50 and expire at billing-period end. $10/$25/$50 top-ups expire 12 months from purchase. Without a plan, existing credits freeze while their original expiry continues; a purchase paid after plan removal is retained for issuance on resubscription if unexpired. Funded prompts, screenshots and tool results are sent to Anthropic under Mandala’s account. See pricing, terms and subprocessors.

The response STREAMS as text/event-stream by default, because a run can go a long time between anything worth saying and a buffered answer would be a minute of silence followed by everything at once. Send stream: false for a single JSON body at the end instead. On the hosted API a request that is not streaming is cut off at the edge after about 120 seconds (a 524 with no body), and the run’s result — its usage and the steps it took — is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. Keep streaming, the default, for anything that may take longer.

A RUN IS KEPT IN A THREAD. Unless you send store: false, a run that acted on the desktop, or finished with an answer, is kept as one exchange — the prompt, and the run’s closing text and how it ended (aborted for one stopped part way, which may have done more than it reported) — in a thread on this computer, and its thread_id comes back on done (or the stream: false body) and on a failure’s error. Send that thread_id with the next prompt to continue the conversation: the model is given the earlier turns (the newest 64,000 characters of them, with a note when some are left out) and told they are done, and acts only on the new prompt. A run refused before it acted keeps nothing and starts no new thread; a continuation still names the thread you sent, while it exists. An account keeps 1000 threads: a new one takes the place of the least recently used that this key could delete, and when there is none (a workspace key whose workspace holds none, say) the run is not kept and has no thread_id — delete threads you no longer need. Threads are listed and read under /agent/threads, and are deleted with their computer.

A CLAUDE RUN CAN ZOOM. Except on claude-opus-4-5 to 4-7 and claude-sonnet-4-6, which use the older computer tool, the loop serves the computer toolset’s zoom: a close-up cut from a fresh capture of the screen, of the rectangle the model names in the last full screenshot’s coordinates, so it shows the screen as it is now, which can have changed since that screenshot. A zoom is a step (action: "zoom") and counts toward max_steps. One that is refused — no full screenshot shown yet, a rectangle outside it, or a capture replaced before it could be cropped — is a step with error, and the loop continues. OpenAI runs have no zoom.

EVERY ACTION THE LOOP TAKES SPENDS YOUR RATE BUDGET, the same budget your own calls draw on. An Anthropic step costs what the calls it makes would cost you: an input and the screenshot that shows it, one call for bash, a cursor read or a bare screenshot, and two screenshots for a zoom. A key action with repeat: n is one input per press and then the screenshot, n + 1 calls, so a single step can cost up to 101 (repeat is at most 100). OpenAI reserves, before each batch runs, the price of one input call for every action in it plus one screenshot for the screenshot after it; its initial screenshot and each bash call cost one call each. A run that exhausts it stops where it is and ends with stop: "rate_limited" rather than failing: the steps already taken are reported, and the work already done to the desktop stands. Treat it the way you treat max_steps — as a run that did not finish — and wait rather than raising anything.

A RUN CAN BE REFUSED PART WAY THROUGH. The API key it is running on is re-checked before every model call and before every action or OpenAI batch, so a key deleted — or a membership removed — mid-run stops the run where it is. That arrives as an error event on the stream (or the single JSON body when stream is false) carrying reason: "revoked" beside error, plus the usage and the steps already taken, which are real and are billed. Most of what this endpoint refuses carries NO word: the model API’s own failures and a stopped or suspended computer arrive without one. A computer another run is already driving, like one whose disk is still being made, carries reason: "contention". The run’s opening computer lookup, and an OpenAI run’s batch, bash command or screenshot, forward whatever the platform classified its own refusal as, so any of the words above can reach you through this frame. Read it the way you read reason anywhere else here, and treat an absent word as no classification.

Requires the member role or higher.

post/computers/{id}/agent

Request

  • Base URL: https://app.mandala.computer/api/v1
  • URL: https://app.mandala.computer/api/v1/computers/{id}/agent
  • Auth: HTTP bearer

Path parameters

idstring required

The computer id.

Headers

X-Model-Keystring

Your own key for the selected Anthropic or OpenAI provider. Omit for eligible Anthropic-funded runs. A blank header is refused, and so is a key whose prefix belongs to the other provider (sk-ant- for OpenAI, any other sk- key for Anthropic).

Request body

promptstring required

What you want done, in plain language. Required.

systemstring

A system prompt for the model. Requires your own X-Model-Key.

max_cost_usdnumber

Funded run admission budget in USD. Actual cost can exceed this by the final model call; BYOK ignores it.

max_stepsinteger

How many actions the loop may take before it stops. Defaults to 20, and 100 is the most it accepts — a value past that is a 400 rather than a quietly shorter run. A STEP IS ONE ACTION ON THE DESKTOP, not one exchange with the model, and the two do not line up in either direction: one model reply may ask for several actions and spends a step on each, while a reply that asks for none — or a paused turn, resubmitted — costs model tokens and no step at all. Nor does every step take a screenshot; a bash call or a cursor read does not, and a zoom is a step that takes two. So this bounds the WORK, and bounds your Anthropic spend only loosely. For funded runs, max_cost_usd limits admission but the final call can overrun it. For BYOK, manage spend with your provider. A run that reaches max_steps ends with stop: "max_steps" and the work already done to the desktop stands. null is the same as leaving it out.

provider'anthropic' | 'openai'

Optional provider. Inferred from claude-, gpt-, o-series and computer-use-preview model ids; defaults to Anthropic when neither is supplied. Required for an unknown model namespace. A recognized model/provider mismatch is refused. OpenAI requires X-Model-Key.

modelstring

A model id for the selected provider. Funded default: claude-sonnet-5-5; claude-opus-5-5 is also selectable. BYOK supports Anthropic and OpenAI computer-capable models. Defaults: claude-opus-5-5 for Anthropic, gpt-6.1-sol for OpenAI. Funded Anthropic fallback routing can use other models.

streamboolean

Send false for one JSON body at the end instead of an event stream. This endpoint streams by default, so omitting it gives you text/event-stream. Only the JSON booleans true and false change the default; any other value, the string "false" included, is read as if it were left out. On the hosted API a request that is not streaming is cut off at the edge after about 120 seconds (a 524 with no body), and the run’s result — its usage and the steps it took — is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. Keep streaming, the default, for anything that may take longer.

thread_idstring

A thread to continue: thr_ and twenty-four hex characters, from an earlier run’s thread_id. Its earlier turns are given to the model as the conversation so far, and this run’s exchange is added to it. A thread of another computer is a 409, as is a full one (100 turns); one this key cannot see is a 404.

storeboolean

Whether to keep this run’s exchange in a thread. Default true: a run that does something is kept, in the thread you named or a new one, and its thread_id comes back on the result. false keeps nothing, and cannot be sent with thread_id.

Response

An SSE event log of what the loop did, or one JSON body when stream is false.

The stream’s events are step (an AgentStep, one per action), text ({text}, what the model said), done (the fields below without steps_taken: the run finished or stopped) and error (see above: the run failed; its steps is an AgentStep list, not a count). With stream: false the answer is the fields below, steps_taken included, and a run that fails answers its status with an AgentFailure body — {error, reason?, cost_usd?, top_up_url?, usage, steps_taken}.

The stream opens at once with a : run starting comment line, before the first model call, and sends : keepalive comment lines while the run goes quiet. Lines beginning : carry nothing and can be ignored.

cost_usdnumber

Cumulative AI credit charge in USD, present on funded runs only.

stepsinteger required

How many actions the loop took — a count; the actions themselves are steps_taken.

stopstring required

Why the loop ended: end_turn (the model finished), max_steps (it reached max_steps), refusal (the model declined), pause_turn (the model kept pausing its turn), rate_limited (your rate budget ran out mid-run), credits_exhausted (funded credits unavailable), max_cost (the next reservation would exceed max_cost_usd), or another stop reason the model itself gave, such as max_tokens. OpenAI also reports incomplete for an unfinished response and safety_check when action approval is needed. Only end_turn means the task was finished; the others are a run that stopped, and the work it did to the desktop stands.

textstring required

The model’s closing text — its answer, or why it could not get there.

thread_idstring

The thread this exchange was kept in; send it as thread_id to continue. Absent with store: false.

Changes

No recorded changes to this operation across all 1 revision of this API.