A harness is the thing Relay controls for an agent. The broker only executes
serializable HarnessConfig data. SDK "adapters" are helpers that return those
configs before a spawn request is sent.
Relay supports two runtime categories:
| Runtime | Use for | Broker capabilities |
|---|---|---|
pty | Terminal-backed CLIs such as Codex or Claude Code | stream, input, resize, snapshot, delivery, release |
headless | Non-terminal sessions such as OpenCode app-server | delivery, release |
When runtime is headless, driver defaults to app_server.
Naming
| Name | Meaning |
|---|---|
| Harness config | Concrete pty or headless JSON the broker can validate and run |
| Harness adapter | SDK helper that returns a harness config |
| Named harness | SDK-side shortcut in new AgentRelay({ harnesses }) |
harnessConfig | Spawn field carrying the concrete config to the broker |
The broker boundary is always explicit config data. Named harnesses are resolved
by the SDK before spawn; Relaycast and multi-broker spawns should send
harnessConfig directly.
Claude PTY Config
Use a named harness when the command is stable across spawns:
import { AgentRelay } from '@agent-relay/sdk';
const relay = new AgentRelay({
harnesses: {
'company-claude': {
runtime: 'pty',
command: 'claude',
args: [
'--dangerously-skip-permissions',
'--append-system-prompt',
'Follow the company review rubric.',
'{modelArgs}',
'{args}',
],
modelArgs: ['--model', '{model}'],
env: {
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
},
},
},
});
await relay.spawnAgent({
name: 'ClaudeReviewer',
cli: 'company-claude',
task: 'Review the current diff.',
model: 'opus',
args: ['--verbose'],
});The SDK turns company-claude into an inline harnessConfig for that spawn.
The broker does not keep a named harness registry.
Codex Per-Spawn Config
Use direct harnessConfig when setup produces a different config for each
spawn, such as creating or resuming a Codex session:
import { AgentRelay, type ResolvedHarnessConfig } from '@agent-relay/sdk';
function codexResume(sessionId: string, cwd: string): ResolvedHarnessConfig {
return {
runtime: 'pty',
command: 'codex',
args: ['resume', sessionId],
cwd,
env: {
PATH: process.env.PATH ?? '',
CODEX_HOME: process.env.CODEX_HOME ?? '',
},
sessionId,
};
}
const relay = new AgentRelay();
const cwd = process.cwd();
const task = 'Review the current diff.';
const sessionId = await createCodexSession({ cwd, task });
await relay.spawnAgent({
name: 'CodexReviewer',
cli: 'codex',
task,
cwd,
harnessConfig: codexResume(sessionId, cwd),
});Do not copy the whole process environment into env. Pass only the keys the
harness needs.
OpenCode Headless Config
Use headless for an agent that already exists inside an app-server session.
OpenCode serve is the first supported protocol:
import { AgentRelay, type ResolvedHarnessConfig } from '@agent-relay/sdk';
function opencodeSession(input: {
endpoint: string;
sessionId: string;
pid?: number;
}): ResolvedHarnessConfig {
return {
runtime: 'headless',
protocol: 'opencode',
endpoint: input.endpoint,
sessionId: input.sessionId,
host: { ownership: 'attached', pid: input.pid },
release: 'abort',
};
}
const relay = new AgentRelay();
await relay.spawnAgent({
name: 'OpenCodeWorker',
cli: 'opencode',
runtime: 'headless',
task: 'Inspect the repo.',
harnessConfig: opencodeSession({
endpoint: 'http://127.0.0.1:4096',
sessionId: 'ses_123',
pid: 34567,
}),
});For OpenCode, Relay delivers messages to POST /session/:id/prompt_async.
For now, app-server configs must use protocol: 'opencode', an http or
https endpoint, a non-empty sessionId, and attached host ownership.
broker-owned app-server hosts are reserved for later broker supervision.
Relaycast Spawns
Agent-crafted spawns should send the full config:
{
"agent": {
"name": "CodexReviewer",
"cli": "codex",
"task": "Review the current diff.",
"harnessConfig": {
"runtime": "pty",
"command": "codex",
"args": ["resume", "session_123"],
"sessionId": "session_123"
}
}
}Relay intentionally avoids a broker-local harness registry for now. A spawn request should be self-contained so behavior does not depend on hidden runtime state or which broker receives it.
See Also
- Harness runtime config - Exact config shapes
- Spawning an agent - High-level spawn APIs
- Event handlers - SDK event subscriptions
- Broker HTTP / WS API - Broker API routes