Skip to main content
Version: 0.3.36

Agent Runs

Two APIs expose durable agent runs in @raisindb/client:

APITransportGet it withUse it for
AgentRunsWsApithe WebSocket connection you already holddb.runs()UIs: find a run, follow it live, control it, list its children
AgentRunsApiHTTP (+ SSE for streaming)httpClient.agentRuns(repo) or httpDb.agentRuns()servers and external agents: everything above, plus client-driven runs, child runs, mailbox, checkpoints, usage
import { RaisinClient, RaisinHttpClient } from '@raisindb/client';

// WebSocket
const db = client.database('myapp');
const runs = db.runs();

// HTTP
const http = new RaisinHttpClient('http://localhost:8080', { tenantId: 'default' });
const httpRuns = http.agentRuns('myapp'); // or http.database('myapp').agentRuns()

Subjects​

A run is about a subject node. Methods that take a subject accept { workspace, path?, node_id? } or the string form "workspace:/path":

import { subjectOf } from '@raisindb/client';

subjectOf('ai:/agents/assistant/inbox/chats/c1');
// { workspace: 'ai', path: '/agents/assistant/inbox/chats/c1' }

db.runs() — AgentRunsWsApi​

MethodReturns
create(options){ run_id, created, status }; returns the subject's live run with created: false if there is one
get(runId)AgentRunView ({ run, status, projection })
bySubject(subject, limit = 20)every run of the subject you may see, the live one first
latest(subject)the newest run of the subject, or null
list(status = 'running', limit = 50)your runs in one status
events(runId, afterSeq = 0, limit = 500)durable events after afterSeq
children(runId)[{ link, status, usage }]
control(runId, command, controlId?, capability?)AgentRunControlAck
stop(runId, reason?, controlId?)ack
pause(runId, controlId?)ack
resume(runId, controlId?)ack
steer(runId, input, controlId?)ack; the input is consumed at the next safe boundary
approve(runId, requestId, subjectDigest, reject?, controlId?)ack; pass { reason } as reject to reject
answer(runId, requestId, value, controlId?)ack
subscribe(runId, { afterSeq?, onEvent?, onEnd?, signal? })AgentRunSubscription

subscribe()​

subscribe is gap-free and resumable. It registers the listener first, then replays the durable log after afterSeq, holding live events until the replay is merged, so every seq is delivered exactly once and in order even when events arrive during the subscribe call. It ends when the run has ended and its log is complete.

const [live] = await runs.bySubject('ai:/agents/assistant/inbox/chats/c1');
const sub = await runs.subscribe(live.run.run_id, { afterSeq: 0 });

for await (const ev of sub) {
console.log(ev.seq, ev.kind.type);
}
// After a reconnect, resume from sub.lastSeq.

The subscription object:

interface AgentRunSubscription extends AsyncIterable<AgentRunEvent> {
readonly lastSeq: number; // the last seq delivered
readonly done: Promise<void>;
unsubscribe(): Promise<void>;
}

Instead of iterating, pass onEvent and onEnd(lastSeq) callbacks, and an AbortSignal to stop following.

AgentRunsApi (HTTP)​

It has the same read and control methods as db.runs() (create, get, bySubject, list, events, control, stop, pause, resume, steer, approve, answer), plus:

stream()​

for await (const ev of httpRuns.stream(runId, afterSeq, abortSignal)) {
// one AgentRunEvent per SSE `run-event`; the generator returns at `end`
}

Client-driven runs​

MethodWraps
acquire(runId)POST …/lease/acquire → { owner, epoch } (the fence)
renew(runId, fence) / release(runId, fence)lease renew / release
begin(runId, fence, { kind, input?, replay_safe?, non_interruptible?, for_call_id?, answers? })begin an operation → { op_id, … }
finish(runId, fence, opId, { outcome, payload?, tool_calls?, usage?, resume_key? })finish it
wait(runId, fence, requests)open approval, input or child requests and wait
complete(runId, fence, 'completed' | 'failed', { kind, code?, message?, detail? })end the run

See Drive an Agent Run from a Client for a complete loop.

Child runs, mailbox, checkpoints​

MethodNotes
spawnChild(runId, options)options: objective (required), budgets, onExceeded (default 'fail'), spawnKey, subject, asAgent, agentRef, input, executorConfig, reducer, inheritReducer, branch. Returns { child_run_id, child_no, created, budgets, resume_key }.
children(runId)the parent's child list
inspectChild(runId, childId, afterSeq?, limit?){ run, events, usage, checkpoint? }
controlChild(runId, childId, action, controlId?)action: { action: 'message', message }, { action: 'steer', input }, { action: 'interrupt', mode?, reason? }, { action: 'resume' }
messageChild, steerChild, interruptChild(runId, childId, reason?, mode = 'stop')shortcuts for controlChild
waitChild(runId, childId, fence, expiresAtMs?)a client-driven parent waits for a child
mailbox(runId)MailboxEntry[]
ackMailbox(runId, upTo){ remaining }
postToParent(runId, messageId, message){ mail_no }
checkpoint(runId, { fence?, operationId?, reason?, summary?, transcriptCutoff?, state?, largeRefs? })writes a checkpoint
readCheckpoint(runId, n?){ checkpoint, state }; the latest when n is omitted
usage(runId)own, children, reserved, total and spare usage

A ChildObjective is:

interface ChildObjective {
title: string;
instructions?: string;
context?: { mode: 'none' }
| { mode: 'recent_turns'; turns: number; items?: unknown[] }
| { mode: 'snapshot'; checkpoint_no?: number; data?: unknown };
allowed_tools?: string[]; // function paths; a trailing * is a prefix
allowed_writes?: Array<{ workspace: string; path?: string; ops?: string[] }>;
expected_artifacts?: Array<{ kind: string; locator?: unknown; description?: string; required?: boolean }>;
acceptance_checks?: Array<{ id: string; description?: string; check?: unknown; required?: boolean }>;
hand_back?: { required_fields?: string[]; schema?: unknown; description?: string };
}

Control ids​

Every control takes an optional controlId. Without one, a fresh id is generated with newControlId(). To retry safely after a network error, create the id yourself and send the same id again: the server answers { ack: 'duplicate', original_seq } instead of applying the control twice.

import { newControlId } from '@raisindb/client';

const id = newControlId('stop');
await runs.stop(runId, 'user pressed stop', id);
await runs.stop(runId, 'user pressed stop', id); // duplicate, no second stop

Types​

Exported types: AgentRunView, AgentRunEvent, AgentRunStatus, AgentRunCommand, AgentRunControlAck, AgentRunFence, AgentRunSubject, AgentRunSubscription, AgentRunSubscribeOptions, AgentRunChildView, CreateAgentRunOptions, CreateAgentRunResult, BeginOperationOptions, FinishOperationOptions, AgentRunsTransport, and the child-run types ChildObjective, ChildContext, ChildAction, SpawnChildOptions, SpawnChildResult, MailboxEntry, CheckpointOptions.

CreateAgentRunOptions:

interface CreateAgentRunOptions {
subject: { workspace: string; path: string; node_id?: string | null };
branch?: string;
agentRef?: string;
createKey?: string;
budgets?: Record<string, unknown>;
input?: unknown;
reducer?: { function_path: string; handler?: string }; // omit for client-driven
executorConfig?: Record<string, unknown>;
controlCapability?: string;
asAgent?: string;
}