Harness runtime config

Reference for the concrete HarnessConfig objects that SDK adapters send to the broker, covering runtime categories and serializable spawn configuration.

Use this page when you need the exact HarnessConfig shape. For a shorter guide with Codex, Claude, and OpenCode examples, start with Harnesses.

The broker only executes serializable config data. SDK adapters can help you build that data, but the broker does not call SDK functions after spawn and does not keep a named harness registry.

Runtime Categories

RuntimeUse forBroker capabilities
ptyTerminal-backed CLIs such as Codex or Claude Codeprocess spawn, PTY stream, input, resize, snapshot, delivery, release
headlessNon-terminal sessions such as OpenCode app-serversession delivery, release

When runtime is headless, driver defaults to app_server.

Terms

TermMeaning
Harness configConcrete pty or headless JSON the broker can validate and run
Harness adapterSDK or userland helper that returns a harness config
Named harnessSDK-side shortcut in new AgentRelay({ harnesses })
harnessConfigSpawn field carrying the concrete config to the broker

Named harnesses are local SDK ergonomics. The SDK resolves them to an inline harnessConfig before the spawn request reaches the broker.

PTY Config

Use pty for terminal-backed coding harnesses:

type PtyHarnessConfig = {
  runtime: 'pty';
  command: string;
  args: string[];
  cwd?: string;
  env?: Record<string, string>;
  sessionId?: string;
  delivery?: {
    mode?: 'pty-injection';
    format?: 'relay-block';
  };
  metadata?: Record<string, unknown>;
};

Example:

const harnessConfig = {
  runtime: 'pty',
  command: 'codex',
  args: ['resume', sessionId],
  cwd,
  env: {
    PATH: process.env.PATH ?? '',
    CODEX_HOME: process.env.CODEX_HOME ?? '',
  },
  sessionId,
} satisfies ResolvedHarnessConfig;

The broker owns the spawned process, PTY stream, raw input, resize, snapshots, message injection, and release behavior for this runtime.

Headless App-Server Config

Use headless for a non-terminal agent session controlled through an app server. OpenCode is the first supported protocol:

type HeadlessAppServerHarnessConfig = {
  runtime: 'headless';
  driver?: 'app_server';
  protocol: 'opencode' | string;
  endpoint: string;
  sessionId: string;
  auth?: {
    type: 'bearer' | 'basic' | 'none';
    token?: string;
    username?: string;
    password?: string;
  };
  host?: {
    ownership?: 'broker-owned' | 'attached';
    pid?: number;
  };
  release?: 'abort' | 'detach' | 'delete';
  metadata?: Record<string, unknown>;
};

Example:

const harnessConfig = {
  runtime: 'headless',
  protocol: 'opencode',
  endpoint: 'http://127.0.0.1:4096',
  sessionId: 'ses_123',
  host: { ownership: 'attached', pid: 34567 },
  release: 'abort',
} satisfies ResolvedHarnessConfig;

For now, app-server configs are attach-only. host.ownership: 'broker-owned' is reserved until the broker owns app-server lifecycle supervision. If host.pid is provided, the broker reports that PID as the harness process ID.

Adapter Pattern

An adapter should return config data:

function companyClaude(): ResolvedHarnessConfig {
  return {
    runtime: 'pty',
    command: 'claude',
    args: [
      '--dangerously-skip-permissions',
      '--append-system-prompt',
      'Follow the company review rubric.',
    ],
  };
}

Register stable configs by name:

const relay = new AgentRelay({
  harnesses: {
    'company-claude': companyClaude(),
  },
});

Use an inline harnessConfig when setup changes per spawn, such as creating a Codex session:

const sessionId = await createCodexSession({ cwd, task });

await relay.spawnAgent({
  name: 'CodexReviewer',
  cli: 'codex',
  task,
  harnessConfig: {
    runtime: 'pty',
    command: 'codex',
    args: ['resume', sessionId],
    cwd,
    sessionId,
  },
});

Do not copy the whole process environment into env, and do not put secrets in metadata. env and auth are visible to the broker, so pass explicit allowlists.

Spawn Payloads

POST /api/spawn accepts harnessConfig:

{
  "name": "CodexReviewer",
  "cli": "codex",
  "task": "Review the current diff.",
  "harnessConfig": {
    "runtime": "pty",
    "command": "codex",
    "args": ["resume", "session_123"],
    "sessionId": "session_123"
  }
}

The broker rejects harnessId. Relaycast spawns that need custom behavior should also send a full inline harnessConfig, which keeps each spawn self-contained across local, remote, and multi-broker deployments.