Cloudstation

Agent Controls

Read a conversation's state, switch its model, tune compaction, restrict its tools, fork it, toggle its extensions, run durable tasks, and share state between your project's agents.

A conversation's agent keeps running between your messages. These routes let your backend inspect and steer that agent without sending it a message. They take the same x-api-key and x-client-id headers as the Agents API, and every path below starts with https://charlie-back.cloud-station.io.

The tool gate, fork, and extensions work on agents that run on the CloudStation runtime. A conversation whose agent runs on the Claude Code harness answers 422.

Conversation State

GET /v1/projects/{project_id}/conversations/{conversation_id}/state

Returns the agent's status, model, context_tokens and context_window, its open tasks and open durable tasks, next_wake_at, and usage.

GET /v1/projects/{project_id}/conversations/{conversation_id}/tools

Returns the agent's tools, its host_tools (the durable task tools, search_history, read_state, update_state), mcp_servers, thinking_level, compaction_at_tokens, and tool_restriction_active.

Model, Thinking, and Compaction

RouteBodyEffect
POST .../conversations/{conversation_id}/model{"model": "..."}Switches the live agent's model. 409 with the reason when the switch is refused.
POST .../conversations/{conversation_id}/thinking{"level": "off" | "low" | "medium" | "high" | "max"}The agent's later turns use this thinking level.
POST .../conversations/{conversation_id}/compact{"instructions"?: "..."}Compacts the agent's context now. Answers 202 with a task receipt.
POST .../conversations/{conversation_id}/compaction/threshold{"at_tokens": 60000} or {"at_tokens": null}Later runs compact once the context passes this size. The minimum is 8000. null restores the default.

An agent can still find what a compaction dropped from its context. Every agent has the search_history tool, which searches the agent's own stored messages and tool calls by words, tool name, row kind, and time range.

Tool Gate

Restrict the tools an agent may use, between messages or while it works.

PATCH /v1/projects/{project_id}/conversations/{conversation_id}/tools
BodyEffect
{"allowed_tools": ["Read", "WebFetch", "mcp__my-tools__search"]}The agent may use only these tools.
{"allowed_tools": null}Lifts the restriction.
{"read_only": true}The agent keeps only tools that read and search: Read, Glob, Grep, Ls, WebSearch, WebFetch, search_history, read_state, and the jev tools. This wins over allowed_tools.
{ "agent_id": "a1b2c3d4", "tool_gate": ["Read", "WebFetch"], "live": true }

live: true means a turn in progress took the change. That turn refuses any further call to a removed tool, and the agent sees the refusal. When live is false, the next run starts with the new set. A body with neither field returns 400.

Fork a Conversation

Start a new conversation from a point in an existing one, for example to try a different instruction without disturbing the original.

POST /v1/projects/{project_id}/conversations/{conversation_id}/fork
{
  "entry_id": "optional transcript entry to end at; the whole history when omitted",
  "conversation_id": "optional id for the new conversation",
  "name": "optional display name"
}

The new agent starts with the source's history up to and including entry_id. It keeps the source's template, model, prompt, thinking level, compaction threshold, tool gate, MCP servers, and extensions. The source is not changed and can keep running. The answer is 201:

{
  "conversation_id": "support-42-fork-9f3a1c2e",
  "id": "…",
  "platform": "api",
  "agent_id": "…",
  "adw_id": "…",
  "source_conversation_id": "support-42",
  "entry_id": null,
  "entries": 18,
  "name": "…"
}

Send messages to the new conversation_id as usual. Without a conversation_id in the body, the new id is <source>-fork-<8 characters>.

StatusMeaning
403Only the conversation's creator can fork it.
409The conversation_id you named already exists.
422entry_id is not on the conversation's current history, or the agent runs on the Claude Code harness.

Telegram and WhatsApp conversations fork into API conversations.

Extensions

Turn an agent's MCP servers and skills on or off, and attach tool rules.

GET   /v1/projects/{project_id}/conversations/{conversation_id}/extensions
PATCH /v1/projects/{project_id}/conversations/{conversation_id}/extensions
{
  "mcps": { "my-tools": false },
  "skills": { "research-deep": false },
  "tool_rules": [
    { "tool": "Bash", "deny_when": "rm\\s+-rf", "reason": "no recursive deletes" }
  ]
}
  • mcps: a server switched off loses its mcp__<name>__* tools at once, including during a turn in progress.
  • skills: applies from the agent's next run.
  • tool_rules: replaces the agent's rules. null clears them and an omitted field keeps them. A rule names a tool (or * for every tool) and a regular expression matched against the call's JSON arguments. A match denies the call before it runs, and the agent sees the reason. The limit is 64 rules. An invalid regular expression returns 400.

GET returns mcps (each with name and enabled), disabled_mcps, disabled_skills, and tool_rules. It lists the MCP servers the agent was given by its template or hire. Platform servers such as jev are not listed but can still be switched off by name.

Durable Tasks

An agent can run long, multi-phase work as a durable task. The task saves its progress at each phase and runs each paid or side-effecting call only once. It can also sleep for minutes or days without holding a process. After a crash or restart, it resumes from its last saved phase. Ask for it in your message or the agent's template, for example: "Run this as a durable task. Fetch each feed once, save the items after each feed, then sleep for an hour and repeat."

GET  /v1/projects/{project_id}/conversations/{conversation_id}/durable-tasks
GET  /v1/projects/{project_id}/conversations/{conversation_id}/durable-tasks/{task_id}
POST /v1/projects/{project_id}/conversations/{conversation_id}/durable-tasks/{task_id}/cancel
StatusMeaning
pendingWaiting for the agent's next turn.
runningThe agent is working on it.
sleepingPaused until wake_at.
doneFinished. The output is in result.
failedEnded with an error.
cancelledCancelled through the API. Cancelling a background task also stops its worker.
parkedRestarted 5 times without saving progress. A new message resumes it.

The message stream sends task.started, task.checkpoint, task.effect, task.sleeping, task.pending, task.done, task.failed, task.cancelled, and task.parked events with task_id, kind, and phase.

Project Shared State

Each project has one JSON document that all its agents and your backend read and write. Use it for counters, queues, and status that several agents share, so your app does not have to pass them between agents. Agents use the read_state and update_state tools. Your backend uses these routes:

GET   /v1/projects/{project_id}/state?path=/orders/42
PATCH /v1/projects/{project_id}/state
GET   /v1/projects/{project_id}/state/log?since=0&limit=100

A read returns {"project_key", "revision", "updated_at", "value"}. Without path you get the whole document, and a path that does not exist reads as null.

A PATCH applies one operation atomically and returns the document after the write. Paths are JSON pointers.

BodyEffect
{"op": "set", "path": "/orders/42/status", "value": "shipped"}Writes the value and creates missing parents.
{"op": "delete", "path": "/orders/42"}Removes the path.
{"op": "increment", "path": "/count", "by": 1}Adds by, which can be negative. A missing number starts at 0.
{"op": "append", "path": "/queue", "value": {"id": 7}}Appends to the array and creates it if missing.

Add "expect_revision": 7 to write only if the document is still at revision 7. If another writer changed it first, the answer is 409 with the current document, so read again and retry. Add "agent_id" to record who wrote. Every write increases revision. The document is capped at 256 KiB.

The log lists changes after the since revision, each with revision, agent_id, op, path, value, and created_at. Pass next_since as since on your next call.

The document belongs to one sandbox, by default the project owner's current one. Add ?sandbox_id= when the project's agents run on another. Only the project's messaging owner or a team admin can use these routes.

Next Steps