> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloud Agent (experimental)

By default, `query()` launches the bundled qodercli locally. Pass `options.experimentalCloudAgent` and the SDK switches to the Qoder Cloud Agent runtime instead — the agent and session run in a Qoder Cloud container, while the local process only sends requests and consumes the SSE event stream.

> **Status**: experimental / unstable. The API shape may change between minor versions; do not depend on unreleased fields in production code paths.

<div id="when-to-use" />

## When to use

* You don't want to manage qodercli, the bundled binary, or a local runtime
* You need a long-lived agent reused across machines (the agent is persisted in the Cloud)
* You want session context to live in the Cloud so multiple processes / hosts can resume it

Local-CLI-only capabilities — `mcpServers` / `settings` / `hooks` / `plugins` / local permissions / checkpoint — are **not** available under the Cloud runtime; passing any of them throws synchronously.

<div id="prerequisites" />

## Prerequisites

* **Personal Access Token (PAT)**: generated at [qoder.com/account/integrations](https://qoder.com/account/integrations); see [SDK Authentication](/en/cli/sdk/authentication). The Cloud runtime accepts only `accessToken()` / `accessTokenFromEnv()`. Passing `qodercliAuth()` / `jobToken()` throws synchronously.
* **Cloud `environment_id`**: required when creating a session. Get it from the Qoder console or the management API.

```bash theme={null}
export QODER_PERSONAL_ACCESS_TOKEN="<your-pat>"
export QODER_CLOUD_AGENT_ENVIRONMENT_ID="<your-env-id>"
```

<div id="first-call-create-agent-create-session" />

## First call: create agent + create session

The most common entry path — create a new Cloud Agent and immediately open a session for it to run a prompt:

```typescript theme={null}
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'Summarize this repository in one short paragraph.',
  options: {
    auth: accessTokenFromEnv(),
    experimentalCloudAgent: {
      agent: {
        create: {
          name: 'my-cloud-agent',
          model: 'ultimate',
          system: 'You are a concise code assistant.',
          tools: [
            {
              type: 'agent_toolset_20260401',
              enabled_tools: ['read', 'glob', 'grep'],
            },
          ],
        },
      },
      session: {
        create: {
          environment_id: process.env.QODER_CLOUD_AGENT_ENVIRONMENT_ID!,
          title: 'first-cloud-session',
        },
      },
    },
  },
});

for await (const msg of q) {
  if (msg.type === 'result') {
    console.log('done:', msg.subtype, msg.result);
  }
}
```

When the turn finishes, read `session_id` from the final `result` message — later turns use it to resume the same session (see [Multi-turn: resuming a session](#multi-turn-resuming-a-session)).

<div id="built-in-tool-allowlist" />

### Built-in tool allowlist

`tools[].enabled_tools` currently supports: `bash`, `write`, `glob`, `web_fetch`, `read`, `edit`, `grep`, `web_search`. Omit `tools` to give the agent no tools.

<div id="mounting-files-into-a-session" />

### Mounting files into a session

After uploading a file via the Files API, mount it into the session container with `session.create.resources`:

```typescript theme={null}
session: {
  create: {
    environment_id,
    resources: [
      { type: 'file', file_id: 'file_abc123', path: '/workspace/data.json' },
    ],
  },
}
```

<div id="reusing-an-existing-agent" />

## Reusing an existing agent

If you already have an `agent.id` (created via the console or a previous call), pass `agent: { id }` and skip `create`:

```typescript theme={null}
experimentalCloudAgent: {
  agent: { id: 'agent_xxx' },
  session: { create: { environment_id } },
}
```

<div id="multi-turn-resuming-a-session" />

## Multi-turn: resuming a session

Once you have a `session_id` from the first turn, the next call passes **only `session: { id }`** — do **not** include `agent`. The session already binds an agent, and combining the two throws synchronously.

```typescript theme={null}
// Turn 1: create agent + session
const first = query({
  prompt: 'My favorite color is teal. Reply with: noted.',
  options: {
    auth: accessTokenFromEnv(),
    experimentalCloudAgent: {
      agent: { create: { name: 'demo', model: 'ultimate' } },
      session: { create: { environment_id } },
    },
  },
});

let sessionId: string | undefined;
for await (const msg of first) {
  if (msg.type === 'result') sessionId = msg.session_id;
}

// Turn 2: continue the same Cloud session
const second = query({
  prompt: 'What is my favorite color?',
  options: {
    auth: accessTokenFromEnv(),
    experimentalCloudAgent: {
      session: { id: sessionId! },
    },
  },
});

for await (const msg of second) {
  if (msg.type === 'result') console.log(msg.result);  // → "teal"
}
```

Session context lives in the Cloud, so the script can restart or move between machines between turns — as long as you have the `session_id`, you can resume.

<div id="consuming-sse-events" />

## Consuming SSE events

The Cloud runtime streams session events back over SSE. The SDK wraps each event as a `cloud_agent_event` message:

```typescript theme={null}
for await (const msg of q) {
  if (msg.type === 'cloud_agent_event') {
    console.log(msg.event, msg.data);   // e.g. "user.message", "agent.message", "session.status_idle"
  } else if (msg.type === 'result') {
    // SDK synthesizes a result after receiving session.status_idle for the current turn
    console.log('turn end:', msg.subtype);
  }
}
```

Event shape:

| Field        | Description                                                                    |
| ------------ | ------------------------------------------------------------------------------ |
| `event`      | Cloud event name (e.g. `user.message`, `agent.message`, `session.status_idle`) |
| `id`         | Event ID in the SSE stream; usable as a replay anchor                          |
| `data`       | Cloud event payload (includes `turn_id` and other fields)                      |
| `session_id` | Cloud session ID this event belongs to                                         |

<div id="history-replay-isolation" />

### History replay isolation

When resuming an existing session, the SSE stream first replays history events. The SDK isolates by `turn_id`: only the **current turn**'s `session.status_idle` triggers the `result` terminal — historical events will not end your query early.

<div id="sse-tuning" />

### SSE tuning

```typescript theme={null}
experimentalCloudAgent: {
  session: { id: sessionId },
  stream: {
    afterId: 'evt_xxx',         // start replay after this event ID
    deltaFlushIntervalMs: 250,  // delta merge / flush interval (SDK default if omitted)
  },
}
```

<div id="abnormal-close" />

### Abnormal close

If SSE disconnects before the current turn reaches a terminal event, the SDK synthesizes an error `result` (`subtype !== 'success'`, `is_error: true`) so callers can handle it uniformly.

<div id="the-result-terminal" />

## The `result` terminal

| Field                                     | Description                                                             |
| ----------------------------------------- | ----------------------------------------------------------------------- |
| `subtype`                                 | `success` or an error subtype                                           |
| `is_error`                                | Boolean; whether the turn ended abnormally                              |
| `session_id`                              | Cloud session ID (backfilled by the SDK on the create branch)           |
| `result`                                  | Agent's text reply for the turn (multiple text blocks are concatenated) |
| `usage` / `modelUsage` / `total_cost_usd` | Backfilled from the current turn's `span.model_request_end.usage`       |

<div id="constraints-at-a-glance" />

## Constraints at a glance

* `agent` and `session` each have their own `id` / `create` union — they are mutually exclusive (enforced by the TypeScript types).
* When passing an existing `session.id`, **do not** also pass `agent`.
* `session.create` must include `environment_id` explicitly.
* The Cloud runtime rejects local-CLI-only top-level options: `model`, `agent`, `mcpServers`, `settings`, `hooks`, `plugins`, `permissionMode`, etc. Passing any of them throws synchronously.
* The Cloud runtime does not support `q.setModel()`, `q.reloadPlugins()`, MCP OAuth, etc. Only `for await` consumption and `q.close()` are guaranteed.

<div id="error-codes" />

## Error codes

| code                                     | When it triggers                                             |
| ---------------------------------------- | ------------------------------------------------------------ |
| `cloud_agent_auth_requires_access_token` | Non-PAT auth was used (e.g. `qodercliAuth()` / `jobToken()`) |
| `cloud_agent_api_error`                  | Cloud OpenAPI returned non-2xx, or the SSE channel failed    |

<div id="related-docs" />

## Related docs

* [SDK Authentication](/en/cli/sdk/authentication) — PAT acquisition and environment variables
* [Multi-turn Conversation](/en/cli/sdk/multi-turn-conversation) — multi-turn under the local runtime
* [API References](/en/cli/sdk/references) — full fields for `Options.experimentalCloudAgent` and `SDKCloudAgentEventMessage`
