Use Spaces from an agent
Give an AI coding agent the Spaces tools through cua daemon mcp, and run agents inside a Space as long-lived threads.
Give an AI coding agent the Spaces tools through cua daemon mcp, and run agents inside a Space as long-lived threads.
The cua MCP server gives an agent 33 Spaces tools plus sandbox and computer tools. For the machine the agent runs on, use Cua Driver instead.
cua agents setup adds the cua skills and MCP server to detected agents
(supported agents; the
installer offers it after sign-in):
cua agents detect # read-only
cua agents setup --agents claude,codex --yes
cua agents status
cua agents remove # removes only what cua addedBy hand:
claude mcp add cua -- cua daemon mcp
codex mcp add cua -- cua daemon mcpcua daemon mcp starts cua daemon if needed, so every agent on the machine
shares the same Spaces, sandboxes and streams. Narrow the grant with
--permissions spaces:readonly (permissions).
list_spaces / create_space {reuse} -> pick, reuse or create a Space
space_bash {space, command} -> run commands inside it
send_file {space, path} -> drop a host file into ~/Downloads (sha256-verified)
stream_endpoint -> a ticketed media URL
list_tools / call_tool -> cua-driver tools in the Space, or any declared service
delete_space {space} -> delete it and stop meteringspace is an id or a name. A tool whose feature the Space lacks returns
capability_missing.
| Tool | Does |
|---|---|
agent_capabilities | Ready harnesses (claude-code, openai-codex). |
agent_start {space, agent, prompt, show} | Start a detached run; show tails it in a terminal on the Space desktop. |
agent_message {space, run_id, text, force} | Next turn; refused mid-turn unless force. |
agent_status, agent_list, agent_stop | State, output tail, stop. |
Runs live in the guest (~/.spaces-agents/<run_id>/) and survive the client
going away. The daemon copies your agent's credential file (for example
~/.claude/.credentials.json) into the Space at start;
CUA_SPACES_AGENT_CREDENTIALS_HOME=none copies nothing.
In TypeScript a run is a thread:
import { embedded } from "@trycua/cua"
import { startThread } from "@trycua/cua/spaces"
const bot = await startThread(embedded().spaces(), {
agent: "claude-code",
prompt: "Open the repo in the Space and run the tests",
placement: { type: "dedicated", on: "local", deleteOnClose: true },
})
await bot.send("Now fix the failing test")
console.log(await bot.status())
await bot.close() // stops the run and deletes the Spaceplacement shared ({ type: "shared", space, acknowledgeNoIsolation: true })
reuses a Space you name; adoptThread(spaces, spaceId, runId) reattaches.
The lower-level calls, one run per thread and one message per turn. The TypeScript and Rust tabs are from the examples in other languages, the Swift tab from Cua Bots. A message sent mid-turn is refused (or queued) unless forced; show the reason.
const report = await space.agentStart(bot.agent, prompt, false, options)
const t = new BotThread(bot, report.runId, space)const r = await this.space.agentMessage(this.runId, message, false)
if (!r.ok) {
this.fold.note(this.turns.length, `Refused: ${r.reason}`)
return { accepted: false, reason: r.reason }
}space_bash, send_file, teleport_app and hotspot_start act with your
authority. Grant spaces:readonly to agents that only need to look.