Skip to main content
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.

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.

Prerequisites

  • Personal Access Token (PAT): generated at qoder.com/account/integrations; see 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.
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);
  }
}
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).

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.

Consuming SSE events

The Cloud runtime streams session events back over SSE. The SDK wraps each event as a cloud_agent_event message:
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)
session_idCloud session ID this event belongs to

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.

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)
  },
}

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.

The result terminal

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 / modelUsage / total_cost_usdBackfilled from the current turn’s span.model_request_end.usage

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.

Error codes

codeWhen it triggers
cloud_agent_auth_requires_access_tokenNon-PAT auth was used (e.g. qodercliAuth() / jobToken())
cloud_agent_api_errorCloud OpenAPI returned non-2xx, or the SSE channel failed