The current version of the cloud agents interface is in open beta and is built on separating a durable agent from individual runs. Creating an agent queues the first run immediately; subsequent calls add new runs to the same agent. An attempt to start a second parallel run for the same agent returns a conflict with a clear reason - the agent is busy. The open beta is not a formality here: details may change, so the integration is worth building with the shape of the responses isolated in a single layer rather than smeared across the whole codebase.
That separation is worth accepting as an architectural decision rather than a detail. It means the conversation's state outlives an individual request and that you build the work queue on your side. An integration designed on a fire-and-forget assumption will sooner or later hit the busy conflict and will have to decide what to do: wait, queue or create a new agent. That decision is not technical but semantic - continuing a conversation and starting a fresh task give different results, and choosing between them has to be deliberate.
It helps to see agent creation in full once. Below is a request with the task text, a list of repositories with a starting ref, the working mode and an explicit refusal to create a pull request automatically. Note that last parameter: the decision whether to open a pull request is better made deliberately than received by default. The starting ref matters just as much: it defines the state of the code the agent works from, and a run started from a stale branch yields a plausible change that conflicts with everything that has happened since.
Creation has documented limits, and knowing them before designing is useful: the number of repositories, the number and size of images, the number of custom roles and the number of external servers described inline in the request are all capped. Those numbers are not trivia - they define what can fit into one run at all and hint at when a task should be split. Hitting them usually indicates not a shortage of platform capability but too broad a task statement: if a task needs two dozen repositories at once, the limit is not the problem.
A separate parameter decides where the changes will land. By default the agent creates a new branch with a recognizable prefix. Enabling work on the current branch pushes changes straight into the starting ref or the head of a pull request - and that is exactly the case where branch protection on the provider's side must be especially strict. An autonomous process writing directly into a working branch requires the same guarantees as a human, except it will not stop on its own and will not pause to consider that somebody else is working on that very branch right now.
A run's event stream is arranged as an ordinary server event stream: statuses, messages, thinking, tool calls, interaction updates, heartbeats, the result, errors and a completion marker. A consumer needs to do three things: reconnect, process events idempotently and finish only on a terminal state. Cancellation is supported separately - without it a long run cannot be stopped programmatically.
Of those three requirements the last is broken most often, and it is worth examining. The heartbeat exists precisely to distinguish long work from a dropped connection: a pause between meaningful events is normal, a missing heartbeat is not. An integration that considers the work finished at the last received event behaves predictably badly: on a dropped connection it reports success, and a few minutes later a branch appears in the repository from a run it had written off as complete. The symptom of that mistake is recognizable: results arrive after your own reports of readiness, and part of the runs stays forever in an intermediate state in your dashboard. It is cured by one rule - finish only on a terminal status, and on a drop reconnect and read the stream to its end.
Cancellation deserves a separate word, because it is not about convenience. An autonomous run spends time and money until it is stopped, and without programmatic cancellation the only available lever is waiting. Cancellation is worth wiring to your own timeouts straight away rather than later: duration here is defined by the task, and setting a sensible ceiling is easier than explaining an unexpected bill. There is also a boundary: cancellation stops the work but does not undo what is already done. If the run managed to push a branch, that branch stays - and that is exactly why protection on the provider's side remains the last line rather than duplication.
The engineering conclusion is simple: treat a run as a long-lived stateful operation rather than a function call. Then a queue, reconnections, event deduplication, cancellation and conflict handling appear naturally. All of that is dull infrastructure work, but it is exactly what separates an integration that survives its first network glitch from a demo.
The typical failures are predictable. Designing an integration without accounting for only one active run. Finishing on the last received event instead of a terminal state. Enabling writes to the current branch without strengthening protection on the provider's side. Starting work from a stale starting ref. And not implementing cancellation, leaving waiting as the only way to stop.
curl --request POST \
--url https://api.cursor.com/v1/agents \
-u "$CURSOR_API_KEY:" \
--header 'Content-Type: application/json' \
--data '{
"prompt": {"text": "Add setup troubleshooting to README"},
"repos": [{
"url": "https://github.com/acme/app",
"startingRef": "main"
}],
"mode": "plan",
"autoCreatePR": false
}'
# a new branch is created by default; writing to the current one is enabled deliberately