Skip to content

The surface runtime ​

CodexSurface is the high-level Node runtime. Create it in the trusted host: Electron main for a desktop app, or a server-side session/process owner for a website. Let it own app-server for the lifetime of the application surface or pooled user session.

Both scaffolds create it through CodexAppBackend, so configure the same options under surfaceOptions there. Create a surface directly when integrating without a scaffold or backend modules.

ts
import path from 'node:path';
import { createCodexSurface } from '@codex-app-sdk/backend';

const surface = createCodexSurface({
  clientInfo: {
    name: 'my_app',
    title: 'My App',
    version: '0.1.0',
  },
  codexHome: path.join(hostDataDirectory, 'codex-home'),
  approvalMode: 'ask',
  permissionMode: 'read-only',
  conversationLimit: 50,
});

Important options ​

OptionMeaning
clientInfoIdentity sent during app-server initialization
codexHomeCODEX_HOME for the spawned child only; never mutates the parent process
cwdDefault workspace for new/resumed conversations; omitted by default
conversationDefaultsHost-owned default model and reasoning effort for explicit and implicit creation
approvalModeRaw host policy: ask or never
permissionModeRaw host policy: read-only, workspace-write, or full-access
approvalPresetInitial preset when advertised by app-server
conversationLimitNumber of summaries loaded during bootstrap
loadingStrategylazy demand-paging or eager progressive full-history hydration
autoSelectFirstConversationWhether bootstrap selects the first catalog item
extensionsHost configuration hooks and dynamic tools
mcpServersTrusted default MCP definitions
onUnknownNotificationObservation seam for newer app-server notifications
onListenerErrorReceives exceptions thrown by subscriber callbacks; each subscriber is isolated, and the default is a process warning
transportExplicit Codex command, arguments, environment, and timeouts

History loading is independent from Vue's DOM rendering strategy. See History and performance before changing either default.

No implicit working directory ​

The SDK does not send a working directory unless the host configures one. It also does not use cwd as an implicit conversation-list filter.

ts
const surface = createCodexSurface(); // no cwd is sent

Query a workspace explicitly when the product needs it:

ts
const workspaceThreads = await surface.listConversations({
  cwd: '/absolute/workspace/path',
});

Host-owned conversation defaults ​

Fixed product experiences can own the default model, reasoning effort, and service tier:

ts
const surface = createCodexSurface({
  conversationDefaults: {
    model: 'a-model-id-known-to-this-host',
    reasoningEffort: 'medium',
    serviceTier: 'priority',
  },
});

Defaults apply when explicit values are omitted from createConversation() and when the SDK creates a conversation automatically for a first message, goal, or review. Model IDs and efforts must be available in the connected account's app-server catalog.

Connect and close ​

ts
const snapshot = await surface.connect();

console.log(snapshot.status); // ready
console.log(snapshot.authentication);

await surface.close();

A signed-out app-server connection can still be ready; authentication is separate state. Fatal transport errors move the surface to error. Calling connect() again starts and initializes a fresh app-server process.

Snapshots and events ​

ts
const unsubscribeState = surface.onStateChange((snapshot) => {
  renderGlobalState(snapshot);
});

const unsubscribeEvents = surface.onEvent((event) => {
  if (event.type === 'tool.completed') refreshBusinessState();
});

unsubscribeState();
unsubscribeEvents();

Snapshots are authoritative. Events are ordered and emitted after the matching state mutation.

Readiness is not a turn outcome ​

An app-server thread/status/changed notification with idle clears runtime busy/active state and allows queued prompts to start (unless a new turn/start is pending). It emits conversation.activityChanged, not turn.completed. Idle alone cannot tell the SDK whether the previous turn succeeded, failed, or was interrupted.

The previous turn retains its ID and last-known status until an authoritative turn/completed or terminal error arrives. A late completion settles that turn without clearing a newer active or pending turn. If the terminal notification never arrives, its outcome remains unconfirmed; hosts must not infer success or interruption from readiness. Explicit history reads remain available separately, and bounded waits may time out.

See the complete Node runtime API.

For transport ownership, continue with Electron integration or Web integration.

Released under the Apache License 2.0.