orchestratr
referenceSDK reference

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:

  1. a generated protocol client covering every socket method 1:1. everything the CLI can do, the SDK can do.
  2. 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 with orcr … --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.kill

the 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.release

run() 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" })waitlastResponse. 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?): OrcrContext

the 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.logs

LoopCreateOptions 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.ls

start 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.snapshot

the 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:

classcode
NotFoundnot_found
InvalidRequestinvalid_request
StateConflictstate_conflict
Blockedblocked
Timeouttimeout
IntegrationMissingintegration_missing
TranscriptUnavailabletranscript_unavailable
EnvironmentErrorenvironment_error
ServerErrorserver_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.

On this page