Skip to main content

Cloud Agent

query() starts the bundled qodercli locally by default. With the cloud agent option (TypeScript's options.experimentalCloudAgent, Python's options.experimental_cloud_agent), the SDK switches to the Qoder Cloud Agent runtime—agents and sessions run in Qoder Cloud containers while the local side 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.

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—MCP servers / settings / hooks / plugins / local permissions / checkpointing—are unsupported on the Cloud runtime; passing them throws synchronously.

Prerequisites

  • Personal Access Token (PAT): generate at qoder.com/account/integrations; see SDK Authentication. The Cloud runtime only accepts access-token auth (accessToken() / accessTokenFromEnv(); Python: access_token() / access_token_from_env()); local login state or job tokens fail synchronously.
  • Cloud environment_id: required when creating a session. Get it from the Qoder console or the management API.
export QODER_PERSONAL_ACCESS_TOKEN="<your-pat>"
export QODER_CLOUD_AGENT_ENVIRONMENT_ID="<your-env-id>"

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:
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);
  }
}
After the run, grab session_id from the final result message—later turns can continue this session with it (see Multi-turn).

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.

Mounting files into a session

After uploading a file via the Files API, mount it into the session container with session.create.resources:
session: {
  create: {
    environment_id,
    resources: [
      { type: 'file', file_id: 'file_abc123', path: '/workspace/data.json' },
    ],
  },
}

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:
experimentalCloudAgent: {
  agent: { id: 'agent_xxx' },
  session: { create: { environment_id } },
}

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.
// 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.

Multi-turn conversation with QoderSDKClient (Python)

Python's QoderSDKClient offers higher-level Cloud session management—connect() creates/resolves the Cloud session, and later query() calls reuse it turn by turn with no manual session_id tracking:
import asyncio
import os

from qoder_agent_sdk import (
    QoderAgentOptions,
    QoderSDKClient,
    ResultMessage,
    access_token_from_env,
)


async def main():
    environment_id = os.environ["QODER_CLOUD_AGENT_ENVIRONMENT_ID"]

    client = QoderSDKClient(
        options=QoderAgentOptions(
            auth=access_token_from_env(),
            experimental_cloud_agent={
                "agent": {"create": {"name": "demo", "model": "ultimate"}},
                "session": {"create": {"environment_id": environment_id}},
            },
        )
    )

    # connect() creates the Cloud session; optionally pass a first-turn prompt
    await client.connect("My favorite color is teal. Reply with: noted.")

    # Consume first turn messages
    async for msg in client.receive_messages():
        if isinstance(msg, ResultMessage):
            break

    # Turn 2: call query() directly — session is already bound
    await client.query("What is my favorite color?")
    async for msg in client.receive_messages():
        if isinstance(msg, ResultMessage):
            print(msg.result)  # → "teal"
            break

    await client.disconnect()


asyncio.run(main())
Note: The Cloud runtime does not support client.set_model(), client.reload_plugins(), MCP OAuth, or other local-CLI control methods — calling them raises ValueError.

Consuming SSE events

The Cloud runtime pushes the session's event stream back over SSE. The SDK wraps each event as a cloud agent event message (TypeScript: cloud_agent_event; Python: CloudAgentEventMessage):
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:
FieldDescription
eventCloud event name (e.g. user.message, agent.message, session.status_idle)
idEvent ID in the SSE stream; usable as a replay anchor
dataCloud event payload (includes turn_id and other fields)
uuid (Python)SDK-generated unique ID for deduplication
session_idCloud session ID this event belongs to

History replay isolation

When reusing an existing session, SSE first replays past turns' events—the SDK isolates by turn_id: only the current turn's session.status_idle produces the terminal result; historical events won't end your query early.

SSE tuning

experimentalCloudAgent: {
  session: { id: sessionId },
  stream: {
    afterId: 'evt_xxx',         // start replay after this event ID
    deltaFlushIntervalMs: 250,  // delta merge / flush interval (SDK default if omitted)
  },
}
Compatibility: in Python, afterId / deltaFlushIntervalMs (camelCase) are also accepted and recognized at runtime.

Abnormal close

If SSE disconnects before the current turn reaches a terminal state, the SDK synthesizes an error result (subtype != 'success', is_error: true) for uniform upstream handling.

Terminal state: contents of the result

FieldDescription
subtypesuccess or an error subtype
is_errorBoolean; whether the turn ended abnormally
session_idCloud session ID (backfilled by the SDK on the create branch)
resultAgent's text reply for the turn (multiple text blocks are concatenated)
usage / model usage / total_cost_usdBackfilled from the current turn's span.model_request_end.usage

Constraints at a glance

  • agent and session each treat id / create as mutually exclusive (enforced by TypeScript union types).
  • When passing an existing session.id, do not also pass agent.
  • session.create must include environment_id explicitly.
  • The Cloud runtime doesn't support local CLI top-level options: model, agent, MCP servers, settings, hooks, plugins, permission modes, etc. throw synchronously.
  • The Cloud runtime doesn't support model switching, plugin reloads, MCP OAuth, or other runtime control methods—only iterating messages and closing the session (TypeScript's q.close(), Python's client.disconnect()).

Error codes

TypeScript code / Python exceptionTrigger
cloud_agent_auth_requires_access_token / CloudAgentUnsupportedAuthErrorNon-PAT auth such as local login state / job token
cloud_agent_api_error / CloudAgentApiErrorCloud OpenAPI non-2xx, or SSE channel failure