SDK reference
the typed @orchestratr/sdk surface: the generated protocol client, the orcr.* convenience helpers, handles, scopes, watch, context, and the one error class per error code.
@orchestratr/sdk is a typed client of the socket API. it has two layers:
- a generated protocol client covering every socket method 1:1. everything the CLI can do, the SDK can do.
- convenience helpers on top (
orcr.*), the curated surface most workflows use. each helper documents exactly which protocol calls it makes; anything the SDK does, a shell script can do withorcr … --json.
this page is generated via TypeDoc from
sdk/ts/src/(client.ts,generated.ts,scope.ts,context.ts,errors.ts). the signatures below are the public surface; regenerate to pick up changes.
install a project with orcr scaffold, which pins the SDK to the CLI version. import the default singleton:
import { orcr } from "@orchestratr/sdk";paths are resolved client-side (the same grammar as the CLI) and sent as absolute selectors, so composed effective paths match the CLI exactly. the SDK auto-starts the server on first use and forwards the caller's ORCR_ID/ORCR_PATH from the environment for lineage.
orcr.agent
run
orcr.agent.run(opts: RunOptions): Promise<AgentHandle>RunOptions is { agent?, prompt, name?, path?, model?, effort?, cwd?, timeout?, gc? }. exactly one of name or path is required (naming is mandatory; passing both or neither throws InvalidRequest). gc is "auto" | "immediate" | "never". returns an AgentHandle immediately (this is agent run semantics; the agent runs in the background). calls agent.run.
wait, ls, kill (collections)
orcr.agent.wait(pattern?: string, opts?: { timeout?: string }): Promise<AgentWaitResult>
orcr.agent.ls(opts?: LsOptions): Promise<AgentRow[]>
orcr.agent.kill(pattern?: string, opts?: { force?: boolean }): Promise<AgentKillResult>collections take patterns relative to the current scope (/ for absolute). a missing pattern expands to <scope>/** inside a scope, and throws InvalidRequest at root (pass an explicit "**" if you truly mean every active agent). LsOptions is { pattern?, agent?, status?, managed?, unmanaged?, all? }. kill never prompts (it behaves like a -y CLI call). these call agent.wait, agent.ls, and agent.kill.
prepareAttach
orcr.agent.prepareAttach(target: string, opts?: { takeover?: boolean }): Promise<AttachHandle>returns an AttachHandle; attach is terminal-mediated, so there is no fake interactive method. calls agent.attach.prepare.
AgentHandle
the return of orcr.agent.run(). properties: uuid, path, name (the last segment), dataDir (the agent's data dir, equal to ORCR_AGENT_DATA_DIR).
handle.wait(opts?: { timeout?: string }): Promise<AgentWaitResult> // agent.wait, targets by uuid
handle.send(prompt: string): Promise<AgentSendResult> // agent.send
handle.logs(opts?: { tail?: number }): Promise<LogEntry[]> // agent.logs snapshot
handle.followLogs(opts?: { intervalMs?: number }): AsyncIterable<LogEntry> // polls agent.logs
handle.lastResponse(): Promise<string> // agent.logs --last-response; throws TranscriptUnavailable
handle.kill(opts?: { force?: boolean }): Promise<AgentKillResult> // agent.killthe handle targets by uuid, which is unambiguous across generations of a reused path. followLogs is a separate call from logs (streaming vs snapshot); it yields entries as they appear and stops once the agent has ended and its transcript is drained.
AttachHandle
the return of prepareAttach. properties: command (the exec argv), leaseId, uuid, path, ttlMs.
attach.run(): Promise<number> // spawn `command` (inherit stdio), heartbeat the lease, release on exit
attach.heartbeat(): Promise<void> // agent.attach.heartbeat
attach.release(): Promise<void> // agent.attach.releaserun() execs the attach command for you, heartbeats the lease while the child lives so GC will not park or reap the agent mid-attach, and releases on exit. use the manual command plus heartbeat/release primitives when driving the child yourself.
orcr.ask
orcr.ask(opts: SpawnOptions): Promise<string>the one-liner: documented sugar for agent.run({ …, gc: "immediate" }) → wait → lastResponse. naming rules are identical to run (name or path required). returns the response text, or throws TranscriptUnavailable if there is no identifiable response. calls agent.ask.
orcr.scope
orcr.scope<T>(scopePath: string, fn: (scope: string) => Promise<T>, opts?: { killOnThrow?: boolean }): Promise<T>runs fn in a new path scope. every relative path created or targeted inside fn (including collection patterns and no-arg collection sugar) resolves under scopePath. scopes are backed by AsyncLocalStorage, not a process global, so concurrent scopes (two fan-outs) never leak into each other. scopes nest (prefixes stack); a leading / resets to absolute. the base scope comes from context.fromEnv(), so the SDK running inside an agent or loop-run composes on top of its own path.
{ killOnThrow: true } barrier-kills <scope>/** before re-throwing, so a crash cleans up the whole subtree.
await orcr.scope("review", async () => {
await orcr.agent.run({ path: "fanout/file_1", prompt: "…" }); // → review/fanout/file_1
await orcr.agent.wait("fanout/*"); // → review/fanout/*
await orcr.agent.wait(); // → review/**
}, { killOnThrow: true });orcr.watch
orcr.watch(opts?: WatchOptions): Promise<Watch>snapshot-then-subscribe, the same stream orcr top renders. WatchOptions is { pattern?, agent?, status?, managed?, sinceSeq? }. the Watch is an async iterable of typed events, with snapshot (the pinned initial document) and snapshotSeq accessors and a close() method. calls watch.open.
const w = await orcr.watch({ pattern: "refactor/**" });
for await (const ev of w) { /* agent.status_changed, queue.promoted, … */ }orcr.context
orcr.context.fromEnv(env?): OrcrContextthe canonical env-derivation helper; never hand-parse ORCR_PATH. returns { kind: "agent" | "loopRun" | "root", id?, path?, scope?, dataDir?, parent?, loop? }. an agent is distinguished from a loop-run command by the presence of ORCR_AGENT_DATA_DIR; loop membership is detected via ORCR_LOOP_DATA_DIR. for an agent, scope is its path minus its name; for a loop run, scope is its full path. loop is { name, runId, path, dataDir } when set. see the environment reference.
orcr.loopNameFrom(path) extracts a loop name from a run path (the first segment).
orcr.loop
orcr.loop.create(opts: LoopCreateOptions): Promise<LoopRow> // loop.create
orcr.loop.pause(name: string): Promise<unknown> // loop.pause
orcr.loop.resume(name: string): Promise<unknown> // loop.resume
orcr.loop.rm(name: string, opts?: { killActive?: boolean }): Promise<unknown> // loop.rm
orcr.loop.ls(opts?: { all?: boolean; status?: string }): Promise<LoopRow[]> // loop.ls
orcr.loop.logs(name: string, opts?: { run?, source?, tail? }): Promise<LogLine[]> // loop.logsLoopCreateOptions is { name, cron?, onceAt?, maxConcurrency?, overlap?, timeout?, command, cwd? }: give one of cron or onceAt; command is the argv array; cwd defaults to process.cwd().
orcr.loop.run
orcr.loop.run.start(name: string): Promise<RunHandle> // loop.run.start
orcr.loop.run.stop(name: string, opts?: { runId?: string }): Promise<LoopRunStopResult> // loop.run.stop
orcr.loop.run.ls(name: string, opts?: { all?: boolean }): Promise<RunRow[]> // loop.run.lsstart returns the run row plus a computed runId and dataDir.
orcr.server and orcr.api
orcr.server.status(): Promise<ServerStatusResult> // server.status
orcr.server.stop(): Promise<ServerStopResult> // server.stop
orcr.server.handshake(): Promise<ServerHandshakeResult> // server.handshake
orcr.api.schema(): Promise<Record<string, unknown>> // api.schema
orcr.api.snapshot(): Promise<ApiSnapshotResult> // api.snapshotthe generated client
orcr.gen is the generated protocol client (GeneratedClient). it exposes one method per socket method (agentRun, agentWait, loopCreate, serverStatus, …) plus call(method, params) as an escape hatch for any method by name. the parity rule is that this client covers every socket method; sdk/ts/src/generated.ts also exports PROTOCOL_METHODS, STREAMING_METHODS, EVENT_KINDS, and ERROR_CODES as the source-of-truth lists.
errors
protocol failures become typed errors carrying { code, message, details }, one class per error code:
| class | code |
|---|---|
NotFound | not_found |
InvalidRequest | invalid_request |
StateConflict | state_conflict |
Blocked | blocked |
Timeout | timeout |
IntegrationMissing | integration_missing |
TranscriptUnavailable | transcript_unavailable |
EnvironmentError | environment_error |
ServerError | server_error |
all extend OrcrError (with .code and .details). StateConflict has a .forceRequired getter (true when details.reason === "force_required", the unmanaged-kill barrier). errorFromWire(err) reconstructs the right class from a wire error object.
import { orcr, StateConflict, Blocked } from "@orchestratr/sdk";
try {
await orcr.agent.kill("unmanaged/main/w6_p1");
} catch (e) {
if (e instanceof StateConflict && e.forceRequired) {
await orcr.agent.kill("unmanaged/main/w6_p1", { force: true });
}
}the SDK never prompts; destructive helpers behave like non-interactive CLI calls. see the SDK workflows guide and the patterns for worked examples.
orcr scaffold
the one purely-local command: it generates a ready-to-run TypeScript workflow project and runs npm install, without touching the server.
socket API and protocol reference
the wire contract for any language: transport and framing, version negotiation, the method catalogue, the event catalogue, error codes, and the herdr driver contract.