# Cordis Services Catalog Every `ctx.` service a plugin can call: the exact public interface with original method JSDoc, plus the class JSDoc. This is one axis of the **wiring** reference a plugin author works against — the events a plugin listens to are the sibling [events catalog](events.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against. This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them. The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely. Detailed Context, Fiber, Registry, and Service APIs are generated in the [Cordis core API](core/context.md). ## `ctx.agentLoop` — `AgentLoop` Concrete agent factory and driver service. ```ts cordis-catalog /** * Create an agent and session under one caller-supplied identity, owned by * the accessing fiber. Constructor-driven config calls mint a fresh combined * id before entering this boundary. * @param id - shared agent/session identity. * @param options - concrete loop options. * @param meta - optional fresh-session workspace metadata. * @returns the published running agent. */ create(id: SessionId, options: AgentOptions = {}, meta: Pick = {}): Agent /** * Create an owned agent on a caller-supplied session id. * @param ownerCtx - caller context that structurally owns the lifecycle. * @param options - identities, session seed/metadata, loop options, setup, and cancellation. * @returns the published handle. */ async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise /** * Resume an owned agent from the configured persistence service. * @param ownerCtx - caller context that owns load, setup, and the live lifecycle. * @param options - persisted identity, loop options, setup, and cancellation. * @returns the published handle. */ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise ``` Types: [Agent](../core-data-structures/core.md) · [AgentOptions](../core-data-structures/core.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) Source: [`packages/core/agent-loop/src/index.ts:253`](../../packages/core/agent-loop/src/index.ts) ## `ctx.agents` — `AgentRegistry` Agent service (`ctx.agents`): tracks live agents and carries the initiating Agent through one process-local asynchronous driver chain. Agent *creation* is provided by whichever plugin implements the AgentFactory (`@deepseek-ai/dsh-agent-loop`), registered via setFactory. Initiator methods provide same-process causal attribution only. Ambient presence is neither liveness proof nor authorization; subjects and owners remain explicit, as does identity at worker, process, persistence, and wire boundaries. Returned Promise boundaries drain during teardown, except a nested lineage that starts an owning-fiber unload is excluded from its own drain. ```ts cordis-catalog /** * Read the Agent that initiated the inherited asynchronous driver chain. * Use this optional form for logging, tracing, metrics, or host attribution * that also supports agentless calls. When a parent creates a child, setup * reports the causal parent while `agentCtx.agent` identifies the child. * @returns the inherited Agent, or `undefined` outside an initiator boundary * and inside an explicit clearing boundary. * @throws when this service instance has been disposed. */ currentInitiator(): Agent | undefined /** * Read the initiating Agent and fail when no initiator boundary is active. * Use this for private helpers contractually below a driver, or for a * deployment-owned outbound request whose contract forbids agentless calls. * Generic or direct-call seams use optional lookup or explicit request fields. * @returns the inherited Agent. * @throws when no initiator is active or this service instance has been disposed. */ requireInitiator(): Agent /** * Run an operation with one exact Agent as its process-local initiator. The * exact synchronous value or Promise returned by the operation is preserved. * Custom drivers and test harnesses wrap their complete returned foreground * lifetime. * A queue or wire receiver may establish this boundary only after validating * explicit identity and resolving the exact live Agent; this method does neither. * Detached work remains owned by the subsystem that starts it. * @param agent - initiating Agent to inherit; presence is neither liveness proof nor authorization. * @param operation - synchronous or asynchronous operation to invoke. * @returns the exact value returned by `operation`. * @throws when the initiator scope is closing/disposed, or when `operation` throws. */ withInitiator(agent: Agent, operation: () => T): T /** * Run an operation inside a boundary that hides any inherited initiating * Agent. The exact synchronous value or Promise is preserved. * Use this while creating lazy shared timers, queue pumps, pool maintenance, * watchers, or exporters so they do not inherit the first Agent that happens * to initialize them. It clears only initiator attribution, not explicit * fields, and does not own or drain detached resources. * @param operation - synchronous or asynchronous operation to invoke without an initiator. * @returns the exact value returned by `operation`. * @throws when the initiator scope is closing/disposed, or when `operation` throws. */ withoutInitiator(operation: () => T): T /** * Register the agent-creation factory (the loop calls this on construction, * effect-scoped). A traced Cordis service is canonicalized to its concrete * target; each create/resume call is then traced through that caller's * context so ownership follows the caller without stacking proxy layers. * Throws if a factory is already registered. Returns the disposer; on * dispose the factory slot is cleared. * @param factory - the loop-owned factory {@link create}/{@link resume} delegate to. * @returns the disposer that clears the factory slot. The exact * Cordis effect disposer (single-shot): composite (generator) effects may * yield it directly — exact identity nests the teardown in order. */ setFactory(factory: AgentFactory): () => void /** * Create and publish a new agent through the registered factory. * Distinct from {@link register} (which records an already-constructed * agent): this constructs the agent and its session. Rejects if no factory is * registered or creation/setup fails. The resolved {@link AgentHandle} lets * the owner tear down exactly this agent. * @param options - shared identity, session seed/metadata, and agent options. * @returns the handle after setup, rollback-covered publication, and loop start complete. */ async create(options: CreateAgentOptions): Promise /** * Load a persisted session and resume an agent on it through the registered * factory. Rejects if no factory is registered; the factory rejects if * session persistence is not configured or persistence/setup fails. * @param options - persisted identity, configuration, and optional setup. * @returns the handle after setup, rollback-covered publication, and loop start complete. */ async resume(options: ResumeAgentOptions): Promise /** * Register a live agent. Throws if an agent with the same id is already * registered. Emits `agent/created` on registration and `agent/disposed` * when the calling fiber is disposed — both with the agent's scope carrier * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the * emits are scope-filtered regardless of which context invoked `register` * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always * requires passing the carrier). Returns the disposer. * @param agent - the already-constructed agent to record in the store. * @returns the EXACT Cordis effect disposer (single-shot; a repeat call * returns undefined without awaiting an in-flight teardown). Exact * identity is load-bearing: a composite (generator) effect that owns a * teardown ORDER — the agent factory's lifecycle chain — must yield THIS * function so Cordis nests the unregistration at that yield position; * yielding a wrapper would leave it disposing as a concurrent sibling on * owner unload, unregistering the agent (and emitting `agent/disposed`) * while its final turn is still draining. */ register(agent: Agent): () => void /** * Insert an already-constructed agent without announcing it. This is the * advanced ordered-lifecycle primitive used by the async agent factory: it * first completes setup while the agent is unpublished, then assigns the * returned detach closure into its pre-installed composite teardown before * calling {@link announce}. Ordinary callers use {@link register}. * @param agent - the prepared, unpublished agent. * @param owner - live agent whose scoped context created this agent, or * undefined for a top-level runtime root. This is runtime ownership, not * the resumed session's durable parent lineage. * @returns an idempotent closure that removes this exact entry and emits * `agent/disposed` with listener failures contained. When called from a * synchronous `agent/created` listener, removal and disposal wait until * that creation dispatch unwinds. */ enter(agent: Agent, owner: Agent | undefined): () => void /** * Announce an agent previously inserted with {@link enter}. * @param agent - the live inserted agent to announce. * @throws if `agent` is not the exact live registry entry for its id, or its * creation announcement already began (including a reentrant call from a * creation listener). */ announce(agent: Agent): void /** * Look up a live agent. * @param id - the shared agent/session id to look up. * @returns the agent, or undefined when no live agent has that id. */ get(id: SessionId): Agent | undefined /** * Test whether a live agent was created through one exact parent agent's * scoped context. Runtime ownership is independent of durable session * lineage and remains unambiguous when unrelated providers reuse an id. * @param id - the candidate child agent's shared agent/session id. * @param owner - the expected runtime creator agent. * @returns true only while the exact child entry is live under that owner. */ isOwnedBy(id: SessionId, owner: Agent): boolean /** * All live agents, in registration order. * @returns a fresh array; mutating it does not affect the registry. */ list(): Agent[] /** * All live top-level agents in registration order. A top-level agent was * created without an owning agent context; durable session lineage does not * affect this runtime relation, so a resumed fork may still be a root. * @returns a fresh array; mutating it does not affect the registry. */ roots(): Agent[] ``` Types: [Agent](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/index.ts) ## `ctx.approval` — `ApprovalService` Approval service that applies session policy before answerers and logs every ask/outcome pair to the requesting session. It exposes deterministic policy changes to the model through the cache-safe runtime-context snapshot. ```ts cordis-catalog /** * Ask the composed answerers to decide one readonly same-process request. * The service borrows the request, agent, session, and live signal directly. * The request requires an open turn because the audit pair must be enclosed * by the durable log's commit/replay boundary; an idle ask rejects before * appending anything. The answerer phase always produces an outcome: an * aborted signal yields `'cancelled'`, a missing or throwing answerer yields * `'unavailable'` (fail closed), and a rogue non-vocabulary return value is * normalized to `'unavailable'`. A failure that prevents either audit append * from committing still rejects because returning an unlogged decision would * violate the pair. Session contains post-commit observer failures, so an * authoritative append cannot reject the request or suppress its matching * audit event. * @param req - the pending decision (agent, tool identity, reason, signal). * @returns the closed outcome; `'allowed-once'` is the only grant. * @throws when no turn is open or either audit event fails before the session * append commit point. */ async request(req: ApprovalRequest): Promise /** * Read the session override without applying the configured default. * @param session - session whose log supplies the override. * @returns the last logged policy, or `undefined` without one. */ overrideOf(session: Session): ApprovalPolicy | undefined ``` Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalPolicy](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [Session](../core-data-structures/session.md) Source: [`packages/ui/user-approval/src/index.ts:193`](../../packages/ui/user-approval/src/index.ts) ## `ctx.bash` — `BashExecutor` (abstract seam) Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). Implementations must honor these semantics: - run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult. - start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr. - BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files. - A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload. ```ts cordis-catalog /** * Apply implementation-owned defaults and caps to a request before execution. * @param request - the caller's request; omitted fields get this * implementation's defaults, capped fields are clamped. * @returns the fully-specified spec to hand to {@link run}/{@link start}. */ abstract resolve(request: BashExecRequest): BashExecSpec /** * Run a command in the foreground; resolves when it finishes. * @param spec - a resolved spec from {@link resolve}, never a raw request. * @returns the outcome; nonzero exits, timeout kills, and abort kills * resolve with a descriptive result rather than reject. */ abstract run(spec: BashExecSpec): Promise /** * Start a background process and return its handle immediately. * @param spec - a resolved spec from {@link resolve}, never a raw request. * @returns the live process handle (reads, kill, quiescence promise). */ abstract start(spec: BashExecSpec): BashProcess ``` Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashProcess](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) Source: [`packages/bash/bash/src/index.ts:51`](../../packages/bash/bash/src/index.ts) ## `ctx.bashEnv` — `BashEnvRegistry` Registry (`ctx.bashEnv`) for trusted, per-execution `DSH_*` variables. The namespace is rebuilt for every model shell call: ambient `DSH_*` values are discarded by the executor, then the registry's current snapshot is injected. Built-in shell facts remain owned by the registry itself while plugins can register additional, enumerable facts with effect-scoped disposal. ```ts cordis-catalog /** * Register one environment contributor. Names and keys are unique; built-in * keys are reserved. Registration is disposed with the calling plugin fiber. * @param contributor - declared key ownership and per-execution resolver. * @returns the disposer that unregisters the contribution. */ register(contributor: BashEnvContributor): () => void /** * Build the trusted `DSH_*` snapshot for one shell tool execution. * @param execution - the current tool execution. * @returns an immutable environment overlay containing built-ins and current contributions. */ collect(execution: ToolExecution): DshEnvironment /** * Enumerate plugin-contributed variables without executing their resolvers. * @returns declarations sorted by environment variable name. */ list(): BashEnvVariableInfo[] ``` Types: [DshEnvironment](../core-data-structures/subprocess.md) · [ToolExecution](../core-data-structures/tools.md) Source: [`packages/bash/bash-env/src/index.ts:89`](../../packages/bash/bash-env/src/index.ts) ## `ctx.clientModuleHost` — `ClientModuleHostService` The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). ```ts cordis-catalog /** * Current composed entry graph (stable object between changes). * @returns the graph served as `window.__DSH_BOOT__`. */ graph(): WebBootGraph /** * Absolute path of an entry's client bundle. * @param id - entry id (package name). * @returns the path, or undefined for an unknown id. */ clientPath(id: string): string | undefined /** * Re-hash one bundle (the HMR watch's registration hook — the only entry * point through which bundle content changes reach the graph). * @param id - entry id (package name). * @returns the new rev, or undefined for an unknown id. */ rebuilt(id: string): string | undefined /** * Subscribe to bundle rebuilds; fires only when the re-hash changed the rev. * @param listener - receives the entry id and its new bundle rev. * @returns the unsubscriber. */ onRebuilt(listener: (id: string, rev: string) => void): () => void /** * Fires after any flush that recomposed the graph (row added/removed, or a * rebuilt rev change). Pull model: listeners re-read {@link graph}. * @param listener - notified with no payload. * @returns the unsubscriber. */ onGraphChanged(listener: () => void): () => void ``` Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts) ## `ctx.codeRuntime` — `CodeRuntime` (abstract seam) Registers one `ctx.codeRuntime` implementation. Program, budget, abort, and substrate failures resolve in CodeRunResult; only seam misuse rejects. Implementations bridge structured-cloneable bindings, materialize each declared namespace rejection class, treat programs as hostile peers, isolate runs from one another, and terminate and await in-flight runs during disposal. ```ts cordis-catalog /** * Execute one program against the request's bindings and capture what it * emitted. See the class doc for the resolution contract (error is a result * field; rejection means seam misuse only). * @param request - the program, its bindings, and the abort signal; the * request carries everything the runtime acts on, with no hidden defaults. * @returns the run's outcome: completion value (when transferable), the * ordered log capture, and the failure (if any). */ abstract run(request: CodeRunRequest): Promise ``` Types: [CodeRunRequest](../core-data-structures/code-runtime.md) · [CodeRunResult](../core-data-structures/code-runtime.md) Source: [`packages/code-runtime/code-runtime/src/index.ts:33`](../../packages/code-runtime/code-runtime/src/index.ts) ## `ctx.commands` — `CommandService` Human-command registry. Plain-context definitions are global; definitions registered through a command-injected child of an agent context shadow globals for that agent. ```ts cordis-catalog /** * Register a global or calling-agent-scoped command. * @param definition - discovery metadata and direct UI handler. * @returns the exact effect disposer that unregisters this definition. */ register(definition: CommandDefinition): () => void /** * List the effective immutable command descriptors for one agent. * @param agent - exact receiving agent and scoped-layer key. * @returns name-sorted descriptors after scoped shadowing. */ list(agent: Agent): readonly CommandDescriptor[] /** * Resolve one effective command definition. * @param agent - exact receiving agent and scoped-layer key. * @param name - command name without a slash. * @returns the scoped shadow or global definition. */ find(agent: Agent, name: string): CommandDefinition | undefined /** * Parse and execute a known command without sending it to the model. * * A resolved command's lifecycle is logged: `command/run` is appended * before the handler is invoked and `command/done` after settlement (a * thrown or aborted handler settles as `kind: 'error'`). Both are direct * log-only appends — no turn wraps them, and persistence drains them at * ordinary checkpoints. Admission misses (syntax or unknown name) log * nothing — they never entered a handler. A `command/run` append failure * fails the execution loud; a `command/done` append failure on the * handler-failure path is contained so the handler's own error stays the * reported failure. * * @param agent - exact receiving agent. * @param line - complete slash-command line. * @param signal - cancellation signal owned by the UI request. * @returns the settled execution (result + lifecycle pairing id), or * `undefined` when syntax or name does not resolve. */ async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise ``` Types: [Agent](../core-data-structures/core.md) · [CommandDefinition](../core-data-structures/commands.md) · [CommandDescriptor](../core-data-structures/commands.md) Source: [`packages/ui/commands/src/index.ts:278`](../../packages/ui/commands/src/index.ts) ## `ctx.compact` — `CompactService` (abstract seam) Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. The replacement user message uses COMPACT_CHECKPOINT_SOURCE so consumers recognize it independently of the backend. Load one implementation per context as `ctx.compact`. ```ts cordis-catalog /** * Consider automatic compaction for one explicit trigger. Pressure policy * uses the latest durable routed request, while context-overflow policy may * force a useful balanced reduction even below the normal threshold. Return * `null` when no safe range can be compacted. A single oversized retained * unit or request envelope cannot be repaired through surface compaction. * * @param agent - agent context owning the session surface and routing options. * @param trigger - normal pressure or provider-confirmed context overflow. * @param signal - cancellation signal; model-backed implementations must forward it. * @returns the compaction result, or `null` if no compaction was needed. */ abstract compactIfNeeded( agent: CompactAgentContext, trigger: CompactionTrigger, signal: AbortSignal, ): Promise /** * Explicitly compact useful history even below automatic pressure thresholds. * Implementations reserve idle turn admission synchronously before any * asynchronous work, select a useful range without writing on a no-op, then * append a standalone `compact/start` before summarization. That durable * marker is the compaction lock until one `compact/end` attempt. Later waking * prompts remain accepted in FIFO order and start only after the optional * durability checkpoint and admission release. Context injected while the * summary runs may sit between the marker pair; only the selected span must * remain stable. * * @param agent - idle agent whose durable history should be compacted. * @param signal - command-owned cancellation forwarded to summarization. * @returns the compaction result, or `null` when no safe useful range exists. * @throws {@link ManualCompactionError} for expected busy, changed-span, * summarization/shrink, commit-stage, or persistence failures, and the exact * abort reason when cancelled. Failed attempts remain visible in the log. */ abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, ): Promise /** * Forcibly compact a range of surface nodes into a single summary node. * `start` and `end` name an inclusive span by surface position, not numeric seq * order; replacements can make visible seqs non-monotonic. Both edges must be * balanced so assistant tool calls remain paired with their results. A model- * backed implementation forwards cancellation and rejects active, missing, * reversed, or unbalanced ranges. The target session is `agent.session`. * Its replacement user message must use {@link COMPACT_CHECKPOINT_SOURCE}. * Use {@link toolPairingBalancedBefore} and {@link toolPairingBalancedAfter} * for the edge checks. * * @param start - first surface seq, inclusive. * @param end - last surface seq, inclusive. * @param agent - context whose session is mutated and whose routing options guide summarization. * @param signal - optional cancellation; model-backed implementations must forward it. * @throws when compaction is active or the range is missing, reversed, or unbalanced. * @returns the appended event seqs, summary, replaced range, and token accounting. */ abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise ``` Types: [CompactionResult](../core-data-structures/compaction.md) · [CompactionTrigger](../core-data-structures/compaction.md) Source: [`packages/compact/compact/src/index.ts:80`](../../packages/compact/compact/src/index.ts) ## `ctx.credentials` — `Credentials` (abstract seam) Abstract credential service. Providers implement the four operations over their source layers; one seam-wide rule binds them all: an empty stored value is absent everywhere — `resolve` skips it, `describe` reports it unconfigured — so a blank never masquerades as a configured secret. ```ts cordis-catalog /** * Resolve one reference to its current value. Resolution is per call: * consumers re-resolve at each operation and must not cache across * operations — that per-operation read is what makes a changed credential * reach the next operation without a restart. * @param ref - the reference to resolve. * @returns the value and its source, or `undefined` while unconfigured. */ abstract resolve(ref: CredentialRef): Promise /** * Describe one reference for configuration surfaces without exposing the * value. * @param ref - the reference to describe. * @returns configured state, supplying source, and writability. */ abstract describe(ref: CredentialRef): Promise /** * Durably store one value in the provider-managed writable source. Rejects * while a read-only source shadows the reference — the write would appear * to succeed while resolution keeps returning the shadowing value — and * rejects an empty value (use {@link unset}). * @param ref - the reference to store. * @param value - the non-empty secret value. */ abstract set(ref: CredentialRef, value: string): Promise /** * Remove one reference from the provider-managed writable source; removing * an absent reference is a no-op. Rejects while a read-only source shadows * the reference, like {@link set}. * @param ref - the reference to remove. */ abstract unset(ref: CredentialRef): Promise ``` Types: [CredentialInfo](../core-data-structures/credentials.md) · [CredentialRef](../core-data-structures/credentials.md) · [ResolvedCredential](../core-data-structures/credentials.md) Source: [`packages/credentials/credentials/src/index.ts:77`](../../packages/credentials/credentials/src/index.ts) ## `ctx.directoryPicker` — `DirectoryPicker` (abstract seam) Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls. ```ts cordis-catalog /** * The backend's interaction capability. * @returns the discriminated capability consumers switch on. */ abstract capability(): DirectoryPickerCapability ``` Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts) ## `ctx.fs` — `FileSystem` (abstract seam) Abstract filesystem provider. Targets must preserve identity across aliases; reads expose regular UTF-8 text or typed errors, listings are stable and content-free, and mutations are atomic. Optional guards add stale protection without changing the unguarded provider contract. ```ts cordis-catalog /** * Resolve a model/plugin-supplied path into a stable {@link FsTarget}. May perform I/O (a * remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence * async even though the local backend only normalizes + realpaths. * * @param path - the path to resolve; relative paths resolve against `opts.cwd`. * @param opts - optional cwd override and cancellation signal. * @returns the stable target; the same file yields the same `targetKey`. */ abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise /** * Return target metadata, or `undefined` when the target does not exist. * @param target - the resolved target to stat. * @param signal - aborts the metadata round-trip. * @returns metadata only, never content; undefined for an absent target. */ abstract stat(target: FsTarget, signal?: AbortSignal): Promise /** * Return path metadata without following the final path component when it is a * symbolic link. This is intentionally path-shaped, not target-shaped: * {@link resolve} follows symlinks to produce the stable identity used by * normal reads/writes, while `lstat` lets a consumer reject the path itself * before that follow happens. * * `opts.cwd` follows {@link resolve}'s cwd rules. `undefined` means the path is * absent. * @param path - the path to inspect; relative paths resolve against `opts.cwd`. * @param opts - `cwd` overrides the backend's default base for relative paths. * @param signal - aborts the metadata round-trip. * @returns metadata only, never content; undefined for an absent path. */ abstract lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise /** * Read the whole regular text file as a single decoded string. * @param target - the resolved target to read. * @param signal - aborts the read. * @returns the full decoded UTF-8 content. */ abstract readText(target: FsTarget, signal?: AbortSignal): Promise /** * Stream the whole regular text file as decoded text chunks (same text * semantics as {@link readText}, for large files). The backend owns * cross-chunk UTF-8 decoding and binary rejection so the policy layer never * touches raw bytes. * @param target - the resolved target to read. * @param signal - aborts the stream, including between chunks. * @returns the chunk iterable, decoded and validated like {@link readText}. */ abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> /** * List direct children of a directory in stable name order. Returns resolved * child targets plus cheap metadata only; never reads file contents. * @param target - the resolved directory target. * @param signal - aborts the listing. * @returns one entry per direct child, in stable name order. */ abstract listDir(target: FsTarget, signal?: AbortSignal): Promise /** * Atomically create or replace UTF-8 text. `expected` guards intent and * staleness; omission allows unconditional overwrite. * @param target - the resolved target to write. * @param content - the full new file content. * @param expected - the write intent guarding the write; omit for unconditional. * @param signal - aborts before the atomic rename takes effect. * @param sandboxPolicy - the per-call mode and workspace root this write * runs under; a sandboxing backend fences the write by it, the bare backend * ignores it. Omit to leave the backend its own default. * @returns the outcome, including the version the write produced. */ abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise /** * Atomically edit literal text. When supplied, the version guard is checked * before matching so stale content reports `FS_STALE_VERSION`; omission edits * the current content without a freshness precondition. * @param target - the resolved target to edit. * @param edit - the literal search/replace request. * @param expected - the version guard; omit for an unconditional edit. * @param signal - aborts before the atomic rename takes effect. * @param sandboxPolicy - the per-call mode and workspace root this edit runs * under; a sandboxing backend fences the edit by it, the bare backend * ignores it. Omit to leave the backend its own default. * @returns the outcome, including the version the edit produced. */ abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise ``` Types: [FsDirEntry](../core-data-structures/filesystem.md) · [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsPathInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) · [SandboxExecutionPolicy](../core-data-structures/sandbox.md) Source: [`packages/fs/fs/src/index.ts:81`](../../packages/fs/fs/src/index.ts) ## `ctx.goals` — `GoalService` Goal service (`ctx.goals`) backed exclusively by the owning session log. ```ts cordis-catalog /** * Read the current goal for one exact live agent. * @param agent - owning live agent. * @returns a fresh view or `undefined` when no goal is current. * @throws {@link GoalError} when the agent is not the registry's live instance. */ get(agent: Agent): GoalView | undefined /** * Remove process-local continuation authority without changing durable goal * phase or revision. Lifecycle owners use this before unloading a driver; * a later human-authorized {@link resume} records the new activation edge. * @param agent - owning live agent. * @returns a fresh disarmed view, or `undefined` when no goal is current. */ disarm(agent: Agent): GoalView | undefined /** * Create and arm a goal. A completed goal may be replaced; every other * current phase must be cleared or resumed instead. * @param agent - owning live agent. * @param request - objective and optional round cap. * @returns the created live view. */ create(agent: Agent, request: CreateGoalRequest): GoalView /** * Edit objective and/or round cap without changing phase. * @param agent - owning live agent. * @param ref - expected current revision. * @param request - at least one replacement field. * @returns the edited view. */ edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView /** * Pause an active goal and disarm automatic continuation. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the paused view. */ pause(agent: Agent, ref: GoalRef): GoalView /** * Resume and arm a stopped goal, or rearm an active goal after a * session-start edge, while its round budget still has capacity. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the active view. */ resume(agent: Agent, ref: GoalRef): GoalView /** * Mark a current non-complete goal complete and disarm it. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the completed view. */ complete(agent: Agent, ref: GoalRef): GoalView /** * Mark an active goal blocked and disarm it. * @param agent - owning live agent. * @param ref - expected current revision. * @param reason - policy-owned stable code and human-readable explanation. * @returns the blocked view with its durable reason. */ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView /** * Clear the current goal while retaining a durable tombstone and history. * @param agent - owning live agent. * @param ref - expected current revision. * @returns the tombstone ref whose revision is one past the cleared snapshot. */ clear(agent: Agent, ref: GoalRef): GoalRef ``` Types: [Agent](../core-data-structures/core.md) · [CreateGoalRequest](../core-data-structures/goal.md) · [EditGoalRequest](../core-data-structures/goal.md) · [GoalBlockReason](../core-data-structures/goal.md) · [GoalRef](../core-data-structures/goal.md) · [GoalView](../core-data-structures/goal.md) Source: [`packages/goal/goal/src/index.ts:197`](../../packages/goal/goal/src/index.ts) ## `ctx.httpServer` — `HttpServerService` The web-shape HTTP carrier service. Activation listens immediately (route registration order carries no request-facing semantics: named routes are composed to be disjoint, and the static dist fallback answers anything not yet claimed during the boot window). A listen failure throws out of init — a FAILED fiber the boot's fail-loud sweep reports. ```ts cordis-catalog /** * Register a named route. Duplicate (kind, path) throws — route patterns are * a composition-level contract, so a collision is a misconfiguration. * @param route - kind, path, and the owning handler. * @returns the disposer removing the route. */ register(route: WebRoute): () => void /** * Register an exact-path HTTP upgrade route. Duplicate paths throw because * one socket can have only one protocol owner. * @param route - pathname and handler owning negotiation plus socket use. * @returns the disposer removing the route. */ registerUpgrade(route: WebUpgradeRoute): () => void /** * Register an index.html transform, applied to every index response in * registration order. * @param transform - pure html-to-html function. * @returns the disposer removing the transform. */ tapIndex(transform: (html: string) => string): () => void ``` Source: [`packages/host/webserver/src/index.ts:63`](../../packages/host/webserver/src/index.ts) ## `ctx.invariants` — `InvariantService` Package-owned invariant registry with global and regex-based selection. ```ts cordis-catalog /** * Register one package's invariant installer. The package name is reserved * even when filtering disables its checks. Enabled installers run in a child * fiber; failure disposes that fiber and releases the reservation. * @param packageName - full npm package name that owns the contribution. * @param installer - listener or startup-check installer for the child context. * @returns an effect-scoped disposer for the registration. */ register(packageName: string, installer: InvariantInstaller): () => void ``` Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts) ## `ctx.llm` — `LlmService` The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. ```ts cordis-catalog /** * Register an adapter for the given provider routes. Throws `LlmError` with code * `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing). * Disposed with the fiber. * @param providers - every provider route this adapter should serve. * @param adapter - the adapter that streams calls for those providers. * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}. */ registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle /** * Describe provider routes with a registered adapter. * @returns detached provider metadata in registration order. */ listProviders(): LlmProviderInfo[] /** * Declare provider routes an adapter plugin can activate through * configuration. Registration is all-or-nothing: an empty list, invalid * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. * @returns the disposer that withdraws all of them. */ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void /** * List every declared configurable provider, registered or dormant. * @returns detached directory entries in declaration order. */ listConfigurableProviders(): LlmConfigurableProvider[] /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. * @returns the provider-owned policy, with normal defaults already resolved. */ providerRetryPolicy(provider: string): ResolvedRetryPolicy /** * Discover models advertised by one registered provider. Catalog membership * is advisory and never changes routing or request validation. * @param provider - registered provider route to inspect. * @returns detached model metadata in adapter-preferred order. */ async listModels(provider: string): Promise /** * Resolve and validate all metadata from the adapter that owns one exact * route. The result is detached from adapter-owned objects; catalog * membership remains advisory and does not control request routing. * @param provider - registered provider route to inspect. * @param model - exact model id passed to the adapter. * @param signal - optional cancellation for adapter-owned asynchronous lookup. * @returns exact model identity plus available context and reasoning metadata. */ async resolveModelInfo( provider: string, model: string, signal?: AbortSignal, ): Promise /** * Validate a conversation call config against its exact model capability and * materialize adapter-configured defaults. Unsupported explicit efforts * reject before provider I/O; no clamping or aliasing is performed. This * standalone query does not bind a later dispatch; use {@link prepareCall} * when logging and streaming must share one adapter registration. * @param config - provider/model route and optional request controls. * @param signal - optional cancellation for adapter-owned capability lookup. * @returns a detached config only when a default must be materialized. */ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** * Resolve one call under its current adapter registration. The returned * one-shot handle keeps that registration across header logging and dispatch, * so HMR cannot combine one adapter's capability result with another adapter. * @param config - provider/model route and optional request controls. * @param signal - optional cancellation for adapter-owned capability lookup. * @returns a prepared config and its registration-bound stream entry point. */ async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise /** * Stream one model call as raw chunks (token-level deltas). Throws * `LlmError` with code `NO_ADAPTER` if no adapter is registered for * `options.provider`. Replay state is retained only when the same adapter * instance owns its historical provider and the target provider. Final * adapter selection remains fixed through asynchronous exact-model resolution * and dispatch. Selection, dispatch, and iteration failures retain their * original Error identity and are tagged in a call-local scope for narrow * agent-loop request recovery; middleware and nested-call failures remain * untagged for the outer call. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` Types: [AdapterRegistrationHandle](../core-data-structures/core.md) · [GenerateOptions](../core-data-structures/core.md) · [LlmAdapter](../core-data-structures/llm-streaming.md) · [LlmCallConfig](../core-data-structures/core.md) · [LlmConfigurableProvider](../core-data-structures/core.md) · [LlmModelInfo](../core-data-structures/core.md) · [LlmProviderInfo](../core-data-structures/core.md) · [LlmResolvedModelInfo](../core-data-structures/core.md) · [PreparedLlmCall](../core-data-structures/llm-streaming.md) · [ResolvedRetryPolicy](../core-data-structures/llm-streaming.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) ## `ctx.permission` — `PermissionService` Owns the deployment's permission presets and their write path. Requires a confining `ctx.bash` executor and `ctx.approval`; unmatched knob values are reported as CUSTOM_PRESET, not an error. ```ts cordis-catalog /** * Resolve the preset matching the effective knob values. A still-matching * last selection wins shared-bundle ties; otherwise the first table match * wins, or {@link CUSTOM_PRESET} when no entry matches. * @param events - the session's events in log order. * @returns the effective preset name, or `custom` when nothing matches. */ current(events: readonly SessionEvent[]): string /** * Build the whole select value for one folded knob state: every table * option in declaration order, `custom` appended exactly while derived. * @param state - the folded knob overrides. * @returns the `permissions` projection payload. */ selectFor(state: KnobState): PermissionSelect /** * Resolve a preset's knob bundle. * @param name - the preset name to resolve. * @returns the configured bundle. * @throws when `name` is not in the table. */ resolve(name: string): PresetSpec /** * Build the client option for a table entry or {@link CUSTOM_PRESET}. A * missing label falls back to the table key. * @param name - a table key, or `custom`. * @returns the option a client renders. * @throws when `name` is neither a table key nor `custom`. */ optionOf(name: string): PresetOption /** * Record a changed preset, then update each changed knob through its own * setter. Selecting the effective preset again appends nothing. * @param session - the session the switch belongs to. * @param name - the preset to switch to; unknown names throw. */ set(session: Session, name: string): void ``` Types: [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) Source: [`packages/ui/permission/src/index.ts:159`](../../packages/ui/permission/src/index.ts) ## `ctx.planMode` — `PlanModeService` `ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror. ```ts cordis-catalog /** * Read the logged plan state and any selected state awaiting a boundary. * * @param agent The agent to read. * @returns Current logged state plus a pending selection, when present. */ get(agent: Agent): { active: boolean; pending?: boolean } /** * Select whether plan mode should be active. Between turns the change * commits immediately — no request boundary would arrive until the next * prompt, so a queued intent would hang (the open-turn fold is the idle * signal: agent status stays `running` through post-turn checkpointing, * where a boundary equally never comes). During an open turn the * selection is held as pending intent for the next in-turn request * boundary. Repeated selection of the current or already-pending state is * a no-op. * * @param agent The agent to switch. * @param active Whether plan mode should be active. * @returns what happened: `committed` (logged now), `queued` (awaiting the * next boundary), `cancelled` (an opposite pending selection was cleared; * the logged state already matches), or `noop` (already in that state). */ set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/plan/plan-mode/src/index.ts:182`](../../packages/plan/plan-mode/src/index.ts) ## `ctx.pty` — `PtyService` In-process registry for replaceable PTY backends and exact-Agent sessions. ```ts cordis-catalog /** * Register one backend type for this effect scope. * @param backend - provider with a non-empty unique type. * @returns disposer that removes exactly this contribution. */ registerBackend(backend: PtyBackend): () => void /** * List registered backend types in registration order. * @returns fresh backend type names. */ listBackends(): string[] /** * Create and publish one owner-scoped session after backend setup succeeds. * @param owner - exact registered Agent that owns access and cleanup. * @param request - backend type plus optional owner-local name and cwd. * @param signal - cancellation of unpublished setup. * @returns published identity, metadata, status, and MOTD. */ async spawn(owner: Agent, request: PtySpawnRequest, signal?: AbortSignal): Promise /** * Test whether an exact owner has a published session or unpublished spawn. * @param owner - exact live owner to inspect. * @returns true across the entire spawn-to-close interval, with no publication gap. */ hasOwnerActivity(owner: Agent): boolean /** * Start one exclusive interactive send. * @param owner - exact session owner. * @param id - target PTY identity. * @param request - explicit text, submit behavior, and cancellation. * @returns live operation handle for foreground await or task registration. */ startSend(owner: Agent, id: PtySessionId, request: PtySendRequest): PtySendOperation /** * Read one bounded scrollback page from an owned session. * @param owner - exact session owner. * @param id - target PTY identity. * @param request - optional newest-relative offset and line count. * @returns bounded retained text and pagination metadata. */ read(owner: Agent, id: PtySessionId, request: PtyReadRequest = {}): PtyReadResult /** * Deliver an allowed signal through an owned backend session. * @param owner - exact session owner. * @param id - target PTY identity. * @param signal - allowed POSIX signal name. * @returns delivered foreground process-group identity. */ signal(owner: Agent, id: PtySessionId, signal: PtySignal): Promise /** * Close one owned session and remove it only after quiescent backend cleanup. * @param owner - exact session owner. * @param id - target PTY identity. * @param reason - diagnostic cleanup reason. * @returns true for a newly closed session, false when the same close is already in flight. */ async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise /** * List fresh snapshots for exactly one owner. * @param owner - exact owner whose sessions are visible. * @returns owner-visible snapshots in publication order. */ list(owner: Agent): PtySessionSnapshot[] ``` Types: [Agent](../core-data-structures/core.md) · [PtyBackend](../core-data-structures/pty.md) · [PtyReadRequest](../core-data-structures/pty.md) · [PtyReadResult](../core-data-structures/pty.md) · [PtySendOperation](../core-data-structures/pty.md) · [PtySendRequest](../core-data-structures/pty.md) · [PtySessionId](../core-data-structures/pty.md) · [PtySessionSnapshot](../core-data-structures/pty.md) · [PtySignal](../core-data-structures/pty.md) · [PtySignalResult](../core-data-structures/pty.md) · [PtySpawnRequest](../core-data-structures/pty.md) · [PtySpawnResult](../core-data-structures/pty.md) Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts) ## `ctx.sandbox` — `SandboxProvider` (abstract seam) Abstract process-sandbox service. confine must return enforcing argv or fail closed at wrap or runner-execution time; silent unconfined passthrough is forbidden. Functional probes arbitrate multi-runner chains and may be skipped for a sole candidate, whose own refusal remains the fail-closed end. ```ts cordis-catalog /** * Wrap `argv` so it executes confined under `policy` on this host; the * caller spawns the returned argv in place of its own. * @param argv - the exact argv the caller is about to spawn (program plus * arguments), NOT a shell string — a shell-shaped consumer passes * `['bash', '-c', command]`. * @param policy - the file-effect policy this execution runs under, * carried per call (see {@link SandboxPolicy}). * @returns the argv to spawn instead, plus the enforcement completeness * the selected backend achieves for it. */ abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv ``` Types: [ConfinedArgv](../core-data-structures/sandbox.md) · [SandboxPolicy](../core-data-structures/sandbox.md) Source: [`packages/sandbox/sandbox/src/index.ts:148`](../../packages/sandbox/sandbox/src/index.ts) ## `ctx.sandboxPolicy` — `SandboxPolicyService` The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. ```ts cordis-catalog /** * Resolve the complete policy for one capability call. An approved explicit * mode outranks the session's last `sandbox/mode` event, which outranks the * deployment default. A session cwd is its workspace-write boundary; the * configured root is the fallback for agentless calls and sessions without a * cwd. * @param request - optional session and approved mode override. * @returns the fully resolved per-call mode and absolute workspace root. */ resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy /** * Read the session override without applying the deployment default. * @param session - session whose log supplies the override. * @returns the last logged mode, or `undefined` without one. */ overrideOf(session: Session): SandboxMode | undefined ``` Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxMode](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md) · [Session](../core-data-structures/session.md) Source: [`packages/sandbox/sandbox-policy/src/index.ts:91`](../../packages/sandbox/sandbox-policy/src/index.ts) ## `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events. ```ts cordis-catalog /** * Resolve this backend's independent local artifact for a session without * reading, creating, flushing, or otherwise materializing it. Backends such * as SQLite that do not own one artifact per session return `undefined`. * @param meta - the immutable session header whose artifact is requested. * @returns the backend-specific absolute location, when one exists. */ abstract locate(meta: SessionHeader): SessionLocation | undefined /** * Register a new session's metadata. A backend MAY defer the physical write * until the first {@link append} (lazy materialization), in which case a * created-but-never-appended session is absent from {@link list} * — abandoned sessions leave nothing behind. * @param meta - the immutable header (id, version, cwd, lineage) to record. */ abstract create(meta: SessionHeader): Promise /** * Durably persist a batch of events. Honors the append-only and contiguous- * seq contracts: the first event's `seq` MUST equal the stored next-seq * (after `load` has durably closed any interrupted turn). Rejects non-JSON- * serializable `event.data` with an error naming the offending event type. * @param id - the session the batch belongs to. * @param events - the contiguous batch to persist, in seq order. */ abstract append(id: SessionId, events: readonly SessionEvent[]): Promise /** * Load a header and balanced contiguous log. A complete interrupted final * turn is preserved and durably closed with missing tool errors plus any open * step and turn boundaries; only a torn final record is discarded. Unknown * versions and corruption in the committed prefix reject. Implementations * MUST NOT crash-repair an identity still bound to a live Session: a balanced * live log may return with its stored header as a durable snapshot, while an * open live turn rejects. * A coordinator-backed cold load reserves the identity across storage awaits, * so concurrent publication of a same-id live Session rejects. * Returned events are detached, and every identified message is deeply * frozen. Coordinator-backed implementations upgrade supported pre-identity * message events before validation; other malformed messages reject before * any stored event is returned. * @param id - the persisted session to reload. * @returns the header and a log ending on a balanced `turn/end`. */ abstract load(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> /** * Inspect a header and its valid contiguous stored prefix without repairing * a torn tail, closing an interrupted turn, or publishing coordinator state. * This read is serialized with writes for the same id and returns detached * values with upgraded, deeply frozen identified messages, so observers * cannot mutate message identity/content or backend-owned state. Other * malformed messages reject. * @param id - the persisted session to inspect. * @param signal - optional cancellation for queued and backend read work. * @returns the header and valid stored event prefix exactly as observed. */ abstract inspect(id: SessionId, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> /** * Read the stored events from `fromSeq` onward — the read-from-seq * primitive for read models that resume from a watermark (e.g. a persisted * projection cache folding only the tail past its checkpoint). Like * {@link inspect} it is non-mutating and detached: no torn-tail truncation, * no synthetic closers, no coordinator-state publication; only events from * the valid contiguous stored prefix are returned, so a torn fragment never * reaches the caller. `fromSeq` at or beyond the stored prefix returns an * empty event list (never an error). Backends whose medium can seek by seq * (SQLite) read only the suffix; sequential media (JSONL, both encodings) * still parse the whole artifact and skip forward — the primitive bounds * what is RETURNED and refolded, not every backend's physical read. * @param id - the persisted session to read. * @param fromSeq - first event seq to include; a non-negative safe integer. * @param signal - optional cancellation for queued and backend read work. * @returns the header and the stored events with `seq >= fromSeq`. */ abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> /** * Lightweight listing from metadata, without a full-log parse. * @param signal - optional cancellation for backend listing work. * @returns one header per materialized session. */ abstract list(signal?: AbortSignal): Promise /** * List materialized sessions with cheap per-log change tokens. * * Repeated observations of an unchanged log return the same revision. A * successful mutating {@link load} repair changes the next listed revision. * Revisions also distinguish independently backed stores so backend-local * counters cannot compare equal across different persistence sources. * @param signal - optional cancellation for backend snapshot-listing work. * @returns one header and opaque revision per materialized session without loading full logs. */ abstract listSnapshots(signal?: AbortSignal): Promise ``` Types: [SessionEvent](../core-data-structures/core.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) · [SessionLocation](../core-data-structures/persistence.md) · [SessionPersistenceSnapshot](../core-data-structures/persistence.md) Source: [`packages/session-persistence/session-persistence/src/index.ts:52`](../../packages/session-persistence/session-persistence/src/index.ts) ## `ctx.sessionProjectionCache` — `SessionProjectionCache` The persisted projection cache service. Opens the `session_projcache` domain at init, checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached row, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. ```ts cordis-catalog /** * The zero-I/O listing read: whole values viewed straight from the stored * rows (version-matching keys only), each cut carried with its watermark * so a client value store can seed under its higher-seq-wins rule — as * stale as the last durable checkpoint but never wrong, and never from an * unrelated log (the caller's header is the identity witness). Fresher * paths (the history tail baseline, {@link coldSnapshot}) supersede these * values whenever a session is actually opened. * @param meta - the listed session's header (identity witness; no log read). * @returns the cut (`asOfSeq` = lowest served-row watermark), or * `undefined` when no usable row exists for this lifecycle. */ cachedSnapshot(meta: SessionHeader): ProjectionSnapshot | undefined /** * Durably checkpoint one live session NOW (both mandatory points call * this; tests and carriers may too). The registry cut is snapshotted at * this boundary (states are live references), then the whole record is * replaced. NOT fail-soft — callers on the fail-soft paths contain it. * @param session - the live session to checkpoint. * @returns resolution after durability and event emission. */ async write(session: Session): Promise /** * Cold-read one persisted session's projections with zero full-log load: * cached rows + a persistence `readFrom` tail from the registry's restore * floor, refolded by the registry and written back (fail-soft) so the next * cold read starts closer. A cache row invalidated by a shrunk log * (crash-repair truncation) triggers one full re-read from seq 0 — the * ladder's slow rung, still no crash. Rejects when the session has no * persisted log (`not found` from the persistence seam). * @param id - the persisted session to read. * @param signal - optional cancellation for the persistence reads. * @returns the snapshot cut at the stored log end. */ async coldSnapshot(id: SessionId, signal?: AbortSignal): Promise ``` Types: [Session](../core-data-structures/session.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) Source: [`packages/session-projection/session-projection-cache/src/index.ts:71`](../../packages/session-projection/session-projection-cache/src/index.ts) ## `ctx.sessionProjections` — `SessionProjectionRegistry` `ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. ```ts cordis-catalog /** * Register one domain's unit. The registration is an effect on the calling * context's fiber: disposing the fiber (or calling the returned disposer) * removes the key — and the unit's cached cells — from subsequent drives * and snapshots. * @param definition - key, boundary schema, pure unit functions, and stateVersion. * @returns the exact disposer that unregisters this unit. */ register(definition: ProjectionDefinition): () => void /** * Subscribe to the change feed. The registration is an effect on the * calling context's fiber. * @param listener - called once per unit whose state reference changed, per committed event. * @returns the exact disposer that unsubscribes. */ onChanged(listener: ProjectionChangeListener): () => void /** * One consistent cut over every registered unit for one session, read from * the watermark cache (missing cells fold lazily over the in-memory log). * Fully synchronous — every value and `asOfSeq` reflect the same log * position. Each value passes its unit's schema before leaving. * @param session - the session whose projection values are read. * @returns the snapshot; `values` is empty when no unit is registered. */ snapshot(session: Session): ProjectionSnapshot /** * State-level checkpoint of every registered unit for one session, read * from the watermark cache (missing cells fold lazily over the in-memory * log). This is the write side of the persisted projection cache: the * returned rows are the `(key → {ver, seq, val})` part of the durable * `(sessionId, key, ver, seq, val)` * rows. Every `val` is a DETACHED structured clone — never the live * cell reference: the watermark cache is this registry's authoritative * mutable state, and a caller reaching the live reference could corrupt * every subsequent snapshot and frame through it (plain JSON by the unit * contract, so the clone is total). * @param session - the session whose unit states are checkpointed. * @returns one row per registered key; empty when no unit is registered. */ checkpoint(session: Session): ProjectionCheckpoint /** * The stored seq a {@link restore} tail read over `checkpoint` must start * at: one event BELOW the lowest usable watermark (a row is usable when * its `ver` matches the live unit's `stateVersion`; an absent or mismatched row * pulls the floor to `0` — that key must refold the full log). The * one-below anchor is load-bearing: the tail then proves how far the * stored log still extends, so {@link restore} can detect a log that * shrank below a row's watermark (crash-repair truncation) instead of * serving the stale row as current — an empty tail read from the anchor * yields an end below every watermark and the restore rejects for a full * re-read. * @param checkpoint - persisted rows for one session (possibly stale or empty). * @returns the seq to hand the persistence `readFrom`, or `undefined` * when no unit is registered (no read needed — {@link restore} would * serve empty values regardless). */ restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined /** * View a checkpoint's rows without any log read: for every registered * unit whose row's `ver` matches, serve the schema-validated * `view` of the stored state; mismatched or absent rows leave their key * absent (a cold or listing consumer treats it as not-yet-available and a * fuller read path refolds it). The zero-I/O rung of the read ladder — * values are as stale as their rows, never wrong. * @param checkpoint - persisted rows for one session (possibly stale or empty). * @returns whole values per key with a usable row; empty when none. */ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial /** * Cold read: fold every registered unit over a stored log suffix, seeding * each from its checkpoint row when usable — the one read recipe (cached * state + forward tail replay + `view`) applied without a live `Session`. * Call with the events returned by a persistence * `readFrom(id, restoreFloor(checkpoint))` and that same floor as * `baseSeq`; the floor's one-below anchor makes the supplied end honest, * so a shrunk log is detected here. A row is usable iff its * `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq` * (`seq >= baseSeq - 1`), and it does not claim events past the * supplied end (`seq <= endSeq`); an unusable row is discarded * and its key refolds from `init` — which is only sound over the full * log, so a discarded row with `baseSeq > 0` throws (the caller re-reads * from seq 0, e.g. after a crash-repair truncation shrank the log below * a row's watermark). * @param checkpoint - persisted rows for one session (possibly stale or empty). * @param events - the stored events with `seq >= baseSeq`, in seq order. * @param baseSeq - the seq `events` starts at (its first event's seq when non-empty). * @returns the snapshot cut at the supplied log end (`asOfSeq` is the last * supplied event's seq, `baseSeq - 1` for an empty tail) plus the * refreshed checkpoint rows at that cut, ready for a durable write-back. */ restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` Types: [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) Source: [`packages/session-projection/session-projection/src/index.ts:156`](../../packages/session-projection/session-projection/src/index.ts) ## `ctx.sessionQuery` — `SessionQueryService` (abstract seam) Unified live-preferred session query service. Exact reads, filters, and traces are backend-independent concrete behavior. A backend implements full-text observation, reconciliation, ranking, cursor generations, and query execution on the same `ctx.sessionQuery` service. ```ts cordis-catalog /** * Search the live-preferred logical corpus and group by session. * @param request - query text, metadata filters, page size, and cursor. * @param exec - optional cancellation control. * @returns session hits ranked by their strongest matching event. */ abstract searchSessions( request: SessionSearchRequest, exec?: SessionSearchExecContext, ): Promise> /** * Search events within one live-preferred logical session. * @param request - target session, query text, filters, page size, and cursor. * @param exec - optional cancellation control. * @returns matching event hits and their target header from one indexed generation. */ abstract searchEvents( request: SessionEventSearchRequest, exec?: SessionSearchExecContext, ): Promise /** * List the complete logical corpus using live-preferred records. * @param signal - optional cancellation for persistence listing. * @returns deterministic newest-first cloned session records. */ listSessions(signal?: AbortSignal): Promise /** * Read and replay-validate one complete logical session log without making it live. * @param sessionId - live or persisted session id to read. * @returns cloned header and complete raw event log from one observation. * @throws when persistence, header compatibility, or replay validation fails. */ async readSession(sessionId: SessionId): Promise /** * Filter the complete logical corpus with provider-independent predicates. * @param filters - ANDed session metadata and availability clauses. * @param signal - optional cancellation for persistence listing. * @returns matching cloned records in deterministic newest-first order. */ async filterSessions( filters: readonly SessionResultFilter[], signal?: AbortSignal, ): Promise /** * Fold the latest log-backed title from one live-preferred logical session. * @param sessionId - live or persisted session id to read. * @param signal - optional cancellation for source resolution and title folding. * @returns latest title snapshot, or `undefined` when the log has no title event. */ async readTitle( sessionId: SessionId, signal?: AbortSignal, ): Promise /** * Fold the latest title and return its source header from one corpus observation. * @param sessionId - live or persisted session id to read. * @param signal - optional cancellation for source resolution and title folding. * @returns cloned source header and optional latest title snapshot. */ async readTitleSnapshot( sessionId: SessionId, signal?: AbortSignal, ): Promise /** * Fold titles for unique sessions from one cancellable corpus observation. * * Results preserve first-occurrence input order. Operational failures stay * isolated per session, while cancellation rejects the complete operation. * @param sessionIds - live or persisted session ids to observe. * @param signal - optional cancellation shared by all source reads. * @returns one fulfilled or rejected result per unique requested id. */ async readTitleSnapshots( sessionIds: readonly SessionId[], signal?: AbortSignal, ): Promise /** * List lightweight raw-log event records for one logical session. * @param sessionId - live-preferred session id to read. * @returns event records in ascending seq order. */ async listEvents(sessionId: SessionId): Promise /** * Scan first-party semantic event documents with provider-independent filters. * @param sessionId - live-preferred session id to scan. * @param filters - ANDed metadata and literal-text predicates. * @returns matching semantic documents in ascending seq order. */ async filterEvents( sessionId: SessionId, filters: readonly SessionEventResultFilter[], ): Promise /** * Read one session's complete current model surface from one corpus observation. * @param sessionId - live-preferred session id to read. * @returns cloned header, current surface, and raw-log capture boundary. * @throws when source resolution fails or the session surface is invalid. */ async readSurface(sessionId: SessionId): Promise /** * Trace known ancestry and descendants from one corpus observation. * @param sessionId - logical session id to trace. * @param signal - optional cancellation for persistence listing. * @returns a complete lineage or an explicit unresolved parent boundary. * @throws when corpus resolution fails, the target is absent, or its known ancestry cycles. */ async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise /** * Trace one event's direct positional and provenance relationships. * @param request - target session id and event seq. * @param signal - optional cancellation for persisted source resolution. * @returns source header, direct links, and the target's positional replacement chain. * @throws when source resolution fails, the target is absent, or surface/provenance validation fails. */ async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise /** * Read one full event plus a bounded raw-log context window. * @param request - target session/seq and context sizes. * @param signal - optional cancellation for persisted source resolution. * @returns cloned target and neighboring events. */ async readEvent(request: SessionEventReadRequest, signal?: AbortSignal): Promise ``` Types: [SessionEventReadRequest](../core-data-structures/session-query.md) · [SessionEventRecord](../core-data-structures/session-query.md) · [SessionEventResultFilter](../core-data-structures/session-query.md) · [SessionEventSearchDocument](../core-data-structures/session-query.md) · [SessionEventSearchPage](../core-data-structures/session-query.md) · [SessionEventSearchRequest](../core-data-structures/session-query.md) · [SessionEventTraceObservation](../core-data-structures/session-query.md) · [SessionEventTraceRequest](../core-data-structures/session-query.md) · [SessionEventWindow](../core-data-structures/session-query.md) · [SessionId](../core-data-structures/core.md) · [SessionLineageTrace](../core-data-structures/session-query.md) · [SessionLogSnapshot](../core-data-structures/session-query.md) · [SessionRecord](../core-data-structures/session-query.md) · [SessionResultFilter](../core-data-structures/session-query.md) · [SessionSearchExecContext](../core-data-structures/session-query.md) · [SessionSearchHit](../core-data-structures/session-query.md) · [SessionSearchPage](../core-data-structures/session-query.md) · [SessionSearchRequest](../core-data-structures/session-query.md) · [SessionSurfaceSnapshot](../core-data-structures/session-query.md) · [SessionTitleObservation](../core-data-structures/session-query.md) · [SessionTitleObservationResult](../core-data-structures/session-query.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md) Source: [`packages/session-query/session-query/src/index.ts:81`](../../packages/session-query/session-query/src/index.ts) ## `ctx.sessionReferences` — `SessionReferenceService` Exact-read consumer that prepares immutable cross-session message context. ```ts cordis-catalog /** * List reference candidates, ranked by working-directory affinity. * @param agent - target agent; self is excluded and its cwd drives ranking. * @param query - optional case-insensitive session-id/cwd/title substring. * @param limit - optional positive result cap. * @param signal - optional cancellation boundary for host autocomplete teardown. * @returns candidates labeled by latest title or, when absent, session id. */ async listCandidates( agent: Agent, query: string = '', limit: number = this.config.candidateLimit, signal?: AbortSignal, ): Promise /** * Snapshot all references before enqueue and return one aggregated durable context. * @param agent - target agent; references to it are rejected. * @param content - already host-normalized readable message content. * @param references - structured source sessions in mention order. * @param signal - optional cancellation boundary for host request teardown. * @returns detached content and optional referenced-session context. */ async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [PreparedReferencedMessage](../core-data-structures/session-reference.md) · [SessionReferenceCandidate](../core-data-structures/session-reference.md) · [SessionReferenceInput](../core-data-structures/session-reference.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) ## `ctx.sessions` — `SessionStore` In-memory session store (`ctx.sessions`). Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. ```ts cordis-catalog /** * Create a session owned by the calling fiber: disposing that fiber stops * event notification and removes the session from the store. `options.seed` * populates the session with a copy of those events (replay/fork); * `options.meta` attaches creation metadata (validated absolute `cwd`, seed * and parent lineage, and delegation depth) as the immutable * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`). * * For an agent whose session must be torn down IN ORDER with its loop (so the * loop's final flush is captured before the store attachment ends), do NOT use this * — fold the session lifecycle into the agent's own effect via * {@link prepare} + {@link enter} + {@link announce} (see * `dsh-agent-loop`'s creation transaction). * * @param id - the session id; omitted, the store mints `session-`. * @param options - seed events and/or creation metadata for the header. * @returns the live session, already entered and announced. * @throws if a session with `id` already exists, metadata is not a plain * lossless-JSON record with valid scalar fields, or `meta.cwd` is a * non-absolute path (storage backends key directories off it). */ create(id?: SessionId, options?: CreateSessionOptions): Session /** * Build a session WITHOUT entering it into the store — validate the id/cwd and * construct the {@link Session} (with its immutable {@link SessionHeader}). * Pairs with {@link enter} + {@link announce}: a caller that owns a composite * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE * effect so a fiber unload tears the session + agent down as a single ORDERED * chain rather than as racing sibling effects — which would remove the publication hooks * before the loop's closing `session/flush`, dropping the closing events. * * @param id - the session id; omitted, the store mints `session-`. * @param options - seed events and/or creation metadata for the header. * @returns the constructed session, NOT yet in the store. * @throws if a session with `id` already exists, metadata is not a plain * lossless-JSON record with valid scalar fields, or `meta.cwd` is a * non-absolute path. */ prepare(id?: SessionId, options?: CreateSessionOptions): Session /** * Enter a {@link prepare}d session into the store: install the module-private * append publication hooks and add it to the store. Returns the DETACH * disposer (hooks + store removal). Does NOT emit `session/created` — * the caller yields this disposer inside its effect and THEN calls * {@link announce}, so a throwing `session/created` listener rolls the attach * back instead of leaking it. * * Re-checks the id for a duplicate: `prepare` and `enter` are public * cross-package primitives and a caller may interleave arbitrary work (or * another create) between them, so a stale prepared session must NOT overwrite * a live store entry of the same id — its detach disposer would later delete * the REAL session. The {@link create} convenience and the agent factory call * the two back-to-back so they never trip this, but the public seam cannot * assume that. * * @param session - a {@link prepare}d session not yet in the store. * @returns the detach disposer (publication hooks + store removal). When called from * a synchronous `session/created` listener, removal and disposal wait until * that creation dispatch unwinds. * @throws if a session with this id is already in the store. */ enter(session: Session): () => void /** Emit `session/created` exactly once for an {@link enter}ed session (with * the carrier {@link enter} captured). Separate from {@link enter} so the * caller can yield the detach disposer first (rollback safety — see * {@link enter}). * @param session - the entered session to announce to listeners. * @throws if the session is not live or its announcement already began, * including a reentrant call from a creation listener. */ announce(session: Session): void /** * Dispatch the awaited `session/flush` durability checkpoint for `session`, * with the carrier captured at {@link enter}. THE flush entry point: the * store owns the carrier, so callers (the loop's turn-end checkpoint, idle * injection, teardown drains) must come through here rather than dispatch a * raw `ctx.parallel('session/flush', …)` — one owner, one spelling, and the * scoped-dispatch invariant can pin it. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. * @throws the first registered listener failure after every listener settles. */ async flush(session: Session): Promise /** * Look up a live session. * @param id - the session id to look up. * @returns the session, or undefined when no live session has that id. */ get(id: SessionId): Session | undefined /** * All live sessions, in creation order. * @returns a fresh array; mutating it does not affect the store. */ list(): Session[] /** * Create a live child session from a stable prefix of a live source. * `boundary` is an inclusive source event seq; omitted means the source's * current last event. The selected slice may end with a between-turn event * but must not end inside an open turn. * * @param source - Live source session object or id. * @param boundary - Inclusive source event seq to fork through; omitted means * the source's current last event, and omitted on an empty source forks an * empty child. * @param childSessionId - Optional child session id; omitted delegates to * `SessionStore`'s id policy. * @returns The created live child session. */ fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session ``` Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [Session](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md) Source: [`packages/core/session/src/index.ts:796`](../../packages/core/session/src/index.ts) ## `ctx.sessionTitle` — `SessionTitleService` Log-backed title fold plus asynchronous fallback generation. ```ts cordis-catalog /** * Read the latest folded title from one live or replayed session. * @param session - session whose log is the title source of truth. * @returns latest title snapshot, or `undefined` before eligible input. */ get(session: Session): SessionTitleSnapshot | undefined /** * Accept an explicit user title. Appends a `session/title` event with the * `user` source, which pins the title: in-flight automatic generation is * superseded and later user messages schedule none (an explicit * {@link SessionTitleService.refresh} remains the deliberate unpin). * @param session - exact live session to rename. * @param title - raw user input; normalized before acceptance. * @returns the accepted title snapshot. * @throws {SessionTitleInvalidError} when the title normalizes to empty. * @throws {Error} when the session is not live or the service is disposed. */ rename(session: Session, title: string): SessionTitleSnapshot /** * Explicitly retry the registered provider, or materialize the built-in * fallback when no provider is registered. * @param session - exact live session to refresh. * @param signal - optional caller cancellation. * @returns latest accepted title, or `undefined` when no eligible text exists. */ async refresh(session: Session, signal?: AbortSignal): Promise /** * Register the sole optional title provider. Disposal aborts its pending and * active work before another provider may register. * @param provider - provider identity, cadence, and generation function. * @returns exact Cordis effect disposer, which settles after active calls quiesce. */ register(provider: SessionTitleProvider): () => Promise ``` Types: [Session](../core-data-structures/session.md) · [SessionTitleProvider](../core-data-structures/session-title.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md) Source: [`packages/session-title/session-title/src/index.ts:261`](../../packages/session-title/session-title/src/index.ts) ## `ctx.settings` — `Settings` (abstract seam) Abstract settings service. Providers implement raw-document storage (`load`/`persist`) and push external changes through Settings.publish; the base class owns namespace registration, resolution, validation, change detection, and the `settings/updated` commit event. ```ts cordis-catalog /** * Prepare the provider's user-editable document for a native editor. File * providers may materialize an absent document before returning its path; * non-file providers return undefined. * @returns the absolute local document path, or undefined for non-file storage. */ prepareDocument(): Promise /** * Register a namespace schema and receive its owner scope. The registration * is an effect on the calling plugin's fiber: disposing that fiber removes * the namespace and its observers. An invalid stored section fails the * registration itself — the earliest point where the schema can judge it. * @param ns - unique namespace; duplicate registration fails loud. * @param schema - schemastery schema resolving this namespace's value. * @param options - composition `base` layer and effect timing. * @returns the owner scope for reads, observation, and updates. */ register(ns: SettingsNamespace, schema: z, options?: SettingsRegisterOptions): SettingsScope /** * Describe every registered namespace for configuration surfaces, including * the composition `base` and raw user layers so a form can mark which fields * the user overrode (presence in `user`) and what a reset returns to. * @param options - redaction switch; wire surfaces must redact. * @returns one descriptor per registered namespace, in registration order. */ describe(options?: SettingsDescribeOptions): SettingsDescriptor[] /** * Read one registered namespace's resolved value. * @param ns - the namespace to read. * @returns the resolved value, or `undefined` while unregistered. */ get(ns: SettingsNamespace): unknown /** * Merge a patch into one registered namespace's user layer, validate the * resolved candidate, persist through the provider, then commit and emit. * A validation failure rejects before anything is persisted. Writes to one * namespace are serialized: concurrent updates apply in call order, each * merging over the previous write's committed section. * @param ns - the registered namespace to update. * @param patch - plain-object patch over the user section. * @param expectedRevision - the descriptor `revision` the caller read; a * namespace that moved past it rejects with {@link SettingsConflictError}. */ async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise /** * Replace one registered namespace's user section wholesale, validate, * persist, then commit and emit. Keys absent from `section` fall back to the * composition `base` and schema defaults — this is the removal/reset path a * merge-only patch cannot express (`replace({})` re-inherits everything). * @param ns - the registered namespace to replace. * @param section - the complete next user section. * @param expectedRevision - the descriptor `revision` the caller read; a * namespace that moved past it rejects with {@link SettingsConflictError}. */ async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise /** * Apply path-addressed edits to one registered namespace's user section, * validate, persist, then commit and emit. The ops are applied to the * section as it stands when the write reaches the front of the queue, so a * caller never has to restate fields it did not touch — and, crucially, * cannot delete fields it never saw. This is the write path for any caller * holding a redacted view; `replace` remains the wholesale reset. * @param ns - the registered namespace to edit. * @param ops - ordered path edits; later ops observe earlier ones. * @param expectedRevision - the descriptor `revision` the caller read; a * namespace that moved past it rejects with {@link SettingsConflictError}. */ async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise ``` Types: [SettingsDescribeOptions](../core-data-structures/settings.md) · [SettingsDescriptor](../core-data-structures/settings.md) · [SettingsNamespace](../core-data-structures/settings.md) · [SettingsPathOp](../core-data-structures/settings.md) · [SettingsRegisterOptions](../core-data-structures/settings.md) · [SettingsScope](../core-data-structures/settings.md) Source: [`packages/settings/settings/src/index.ts:365`](../../packages/settings/settings/src/index.ts) ## `ctx.skills` — `SkillService` Registry of skill providers. It merges provider catalogs with stable first-wins duplicate handling, exposes sorted invocation-neutral summaries, and loads full skill bodies on demand. ```ts cordis-catalog /** * Register a borrowed same-process provider synchronously during plugin apply. Duplicate and * reserved names throw; remote initialization belongs in `list()`. Fiber disposal unregisters * the provider and invalidates catalog caches. * @param create - synchronous factory receiving this registration's lifecycle and invalidation control. * @returns the exact Cordis effect disposer that unregisters this provider; * composite effects may yield it directly to preserve teardown ordering. */ registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void /** * Register a borrowed readonly runtime skill. Project entries outrank runtime entries, which * outrank user entries. Same-name runtime entries are first-wins; a duplicate logs a warning and * receives a no-op disposer so it cannot remove the winner. * @param skill - the skill definition input; omitted invocation and provider fields receive defaults. * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches. */ register(skill: SkillRegistration): () => void /** * List invocation-neutral skill summaries for a workspace. Consumers apply * model or user invocation policy at their operational boundary. Lookup * options and provider candidates are readonly same-process values borrowed * throughout discovery. * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. * @returns all sorted winning summaries. */ async list(options: SkillLookupOptions = {}): Promise /** * Observe the current invocation-neutral catalog and whether discovery completed within a stable revision. * Incomplete observations are never cached, allowing consumers to retain last-good state and * retry on their next request boundary. * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. * @returns sorted summaries plus discovery-completeness state. */ async snapshot(options: SkillLookupOptions = {}): Promise /** * Load and validate the winning candidate, passing its opaque discovery locator back to the * provider. Cancellation is rechecked after selection, including cache hits, and raced against * loading so an uncooperative provider cannot hang the caller. * @param name - kebab-case skill name. * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work. * @returns the full skill, including body content, or `undefined`. */ async get(name: string, options: SkillLookupOptions = {}): Promise ``` Types: [SkillCatalogSnapshot](../core-data-structures/skills.md) · [SkillDefinition](../core-data-structures/skills.md) · [SkillLookupOptions](../core-data-structures/skills.md) · [SkillProvider](../core-data-structures/skills.md) · [SkillProviderControl](../core-data-structures/skills.md) · [SkillRegistration](../core-data-structures/skills.md) · [SkillSummary](../core-data-structures/skills.md) Source: [`packages/skill/skill/src/index.ts:209`](../../packages/skill/skill/src/index.ts) ## `ctx.spillStore` — `SpillStore` (abstract seam) Abstract spill storage service. Subclass, implement saveText, and load the subclass as a plugin — it registers as `ctx.spillStore` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Semantics every implementation must honor: - saveText persists the FULL `content` verbatim and returns an opaque locator, exact byte length, and model-facing retrieval guidance. - Storage is scoped by the request's SaveTextSpill.owner session; the backend chooses a private (not world-readable) location and a collision-free name derived from — never equal to — the caller's `suggestedName`. - `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable); the caller decides how to degrade (the spill policy treats a rejection as best-effort and keeps the inline result). ```ts cordis-catalog /** * Persist `input.content` to a session-scoped spill artifact. * @param input - the owner, provenance, suggested name, and full text to save. * @returns the saved artifact's {@link SpillRef}; rejects on a storage failure. */ abstract saveText(input: SaveTextSpill): Promise ``` Types: [SaveTextSpill](../core-data-structures/spill.md) · [SpillRef](../core-data-structures/spill.md) Source: [`packages/spill/spill/src/index.ts:45`](../../packages/spill/spill/src/index.ts) ## `ctx.storage` — `Storage` The storage hub service. Backends register under `backend`; data forms mount under their `StorageForms` key and are reached as `ctx.storage.
`. ```ts cordis-catalog /** * Mount a data-form facility on the hub. Mounting is an effect: the * returned disposer unmounts the form. * @param form - Form key declared in {@link StorageForms}. * @param facility - The facility instance to expose. * @returns the disposer that unmounts the form. */ mount(form: K, facility: StorageForms[K]): () => void /** * Resolve a mounted data form. * @param form - Form key declared in {@link StorageForms}. * @returns the mounted facility. */ form(form: K): StorageForms[K] ``` Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts) ## `ctx.storageDomain` — `DomainFacility` The mounted domain facility. Opens declared domains over routed backends; one facility instance owns the open-domain table and enforces single-open per domain name. ```ts cordis-catalog /** * Open one declared domain. Steps, each failing the whole call: reject a * name that is already open (`already-open`); resolve the backend route * (`backend-not-found` passes through from the hub); require its `kv` facet * (`facet-unsupported`); open the unit projected from the spec (backend * `version-mismatch`/`malformed-medium` pass through); load and validate * every stored record against the spec's zod schemas (`invalid-record` * with the offending table and key); construct the domain. * * Lifecycle: the CALLER owns the returned handle and closes it via * `Domain.close()` (typically as its own `ctx.effect` disposer) — the * facility does not tie the domain to any consumer fiber. Domains still * open when the facility unmounts are closed by the plugin disposer. * @param spec - The domain declaration, typically from `defineDomain`. * @returns the opened domain handle, typed by the spec. */ async open(spec: S): Promise> /** * Look up an open domain by name, untyped. Diagnostic surface (the package * invariant cross-checks change events against live domain state); typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. */ get(name: string): DomainImpl | undefined /** * Close every domain still open on this facility. The unmount path for * consumers that never called `Domain.close()` themselves; closing is * idempotent, so double-closing an already-closed domain is harmless. * @returns resolution after every unit is released. */ async closeAll(): Promise ``` Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts) ## `ctx.subagents` — `SubagentService` Named provider registry with one-shot runs, durable discovery, and continuable-child operations. ```ts cordis-catalog /** * Establish one durable continuable child and deliver its initial prompt. * Resolves when the child's inbox accepts that prompt, without waiting for the * turn to start or for the message to reach the Session log; any earlier * failure rejects with no ids and rolls back the child entirely. * @param spec - provider, delegation request, and caller cancellation. * @returns the durable child id and the accepted prompt's message id. * @throws when continuation services are unavailable or materialization fails. */ async startContinuable(spec: ContinuableStartSpec): Promise /** * Deliver one later message to a continuable child as its next FIFO turn. A * resident child's Agent inbox accepts it directly (waking a `waiting` * Activation), while an absent one is cold-resumed from its persisted * Session. The Agent inbox is the only queue, so every accepted message has * one observable order. * @param parent - the exact live direct parent authorizing this delivery. * @param childId - durable child session id. * @param content - user-role content to deliver. * @param options - durable provenance and caller cancellation, which stops the * operation only before inbox acceptance. * @returns the accepted message's inbox id. * @throws when continuation services are unavailable, parent authority is * rejected, or the message was not admitted. */ async followup( parent: Agent, childId: SessionId, content: ContentBlock[], options: SubagentFollowupOptions, ): Promise /** * Deliver selected content from one live continuable child to its durable * direct parent. The child is the authority credential; callers cannot name a * recipient. Reporting does not conclude the child's turn or Activation. * @param child - exact live reporting child. * @param content - selected model-facing content. * @param options - parent scheduling and pre-acceptance cancellation. * @returns the stable identity of the parent-accepted message. * @throws when continuation services are unavailable, sender authorization * fails, or the direct parent is not live. */ async reportFrom( child: Agent, content: ContentBlock[], options: SubagentReportOptions, ): Promise /** * Compose one deployment capability into every continuable child's * unpublished creation context on fresh creation and cold resume. Grants wait * for the next Activation; removing the contribution revokes every resident * installation immediately. * @param contribution - synchronous child-scope installer. * @returns the exact Cordis effect disposer. */ registerContinuableSetup(contribution: ContinuableSetupContribution): () => void /** * Close continuable admission below exact live parent Agents, stop only their * visible descendant Activations synchronously, then await admitted scoped * materializations and release those forests child-first. The scoped cutoff * lasts until each exact parent leaves the registry; unrelated parent trees * remain live. * @param parents - exact host-owned parent Agents entering teardown. * @returns once every retained descendant Activation released its `AgentHandle`. * @throws an aggregate error after all branches settle when any failed. */ async drainContinuableDescendants(parents: readonly Agent[]): Promise /** * Enumerate the parent's direct session-backed subagents from the * live-preferred session corpus without loading or resuming an Agent. Session * query supplies lineage, candidate order, event reads, and live state; this * service interprets descriptor mode, activity, and per-child diagnostics * without consulting Agent registrations, Activations, or providers. * * The trace and exact descriptor read receive `signal`; the full event-list * read has no signal parameter, so the scan rechecks cancellation around * every await and between candidates. Query rejections that settle after an * abort become a stable `SubagentError` with code `CANCELLED`. * @param parentSessionId - parent session whose direct children are listed. * @param signal - caller-owned cancellation forwarded where supported and * observed around every query await. * @returns children and per-child diagnostics in stable trace order. * @throws {@link SubagentError} when session query is unavailable or the * caller cancels the scan. */ listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise /** * Register a provider under its name. Registration is effect-scoped and HMR * safe; removing a provider blocks new starts but does not revoke runs that * were already returned to their holders. * @param provider - the trusted provider implementation. * @returns the exact Cordis effect disposer. */ registerProvider(provider: SubagentProvider): () => void /** * Look up a provider by name. * @param name - the provider name. * @returns the provider, or undefined when absent. */ getProvider(name: string): SubagentProvider | undefined /** * List registered provider names in insertion order. * @returns the registered names. */ list(): string[] /** * Establish a published child on the named provider. Capability and semantic * checks run before delegation. Provider ownership lasts until its promise * fulfills; a rejection therefore has no run for the caller to dispose and * emits no run lifecycle events. Post-publication turn and infrastructure * failures settle through the returned run. * @param name - the provider to use. * @param request - child label, prompt, parent, signal, and optional capabilities. * @returns the published holder-owned run. */ async start(name: string, request: SubagentStartRequest): Promise ``` Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableSetupContribution](../core-data-structures/subagent.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [MessageId](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) · [SubagentFollowupOptions](../core-data-structures/subagent.md) · [SubagentListEntry](../core-data-structures/subagent.md) · [SubagentProvider](../core-data-structures/subagent.md) · [SubagentReportOptions](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:165`](../../packages/subagent/subagent/src/index.ts) ## `ctx.subprocess` — `SubprocessService` (abstract seam) Abstract subprocess service. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.subprocess` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). Implementations must honor these semantics: - spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures. - Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists. Piped streams are handed to the caller raw and never buffered here. - SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence. - Disposal of the service terminates all still-running managed processes and awaits their exit. ```ts cordis-catalog /** * Start one managed child process from a fully-specified spec; this seam * applies no defaults. * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment. * @returns the live process handle (streams/readers, signalling, outcome promise). */ abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle ``` Types: [SubprocessHandle](../core-data-structures/subprocess.md) · [SubprocessSpawnSpec](../core-data-structures/subprocess.md) Source: [`packages/subprocess/subprocess/src/index.ts:91`](../../packages/subprocess/subprocess/src/index.ts) ## `ctx.systemPrompt` — `SystemPrompt` Registry service for the prompt inputs assembled before each model step. ```ts cordis-catalog /** * Register an ordered prompt section in the calling context's scope. A scoped * section shadows a global section with the same name; duplicates within one * layer and non-finite orders throw. Registration and disposal emit * `system-prompt/change`. * @param section - the section to register. * @returns the exact Cordis effect disposer. */ section(section: PromptSection): () => void /** * Register ordered cache-safe dynamic context in the calling context's scope. * A scoped context shadows a global context with the same name; duplicates * within one layer and non-finite orders throw. Registration and disposal * emit `system-prompt/change`. * @param context - the context contribution to register. * @returns the exact Cordis effect disposer. */ context(context: PromptContext): () => void /** * Register a tool-schema provider in the calling context's scope. Global and * matching scoped providers both contribute; returning the reserved * {@link TOOL_ORDER_REST} name makes assembly fail. * @param provider - evaluated for each assembly with its context. * @returns the exact Cordis effect disposer. */ tools(provider: (context: AssembleContext) => ToolProviderResult): () => void /** * Register a prompt variable in the calling context's scope. Scoped values * shadow globals; invalid or duplicate names throw. A provider may return * `undefined`, but rendering a section that references that value then fails. * @param name - the `[a-z][a-z0-9_]*` reference name. * @param provider - evaluated for each assembly. * @returns the exact Cordis effect disposer. */ variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void /** * Assemble global and scoped providers, detach tool parameters, apply * canonical ordering, then run the assembly waterfall. Scoped sections and * variables shadow globals; the returned waterfall value is authoritative. * @param context - the optional scope and plugin-defined assembly fields. * @returns the authoritative post-waterfall assembly. */ async assemble(context: AssembleContext = {}): Promise ``` Types: [AssembleContext](../core-data-structures/system-prompt.md) · [PromptContext](../core-data-structures/system-prompt.md) · [PromptSection](../core-data-structures/system-prompt.md) · [ToolProviderResult](../core-data-structures/system-prompt.md) Source: [`packages/core/system-prompt/src/index.ts:298`](../../packages/core/system-prompt/src/index.ts) ## `ctx.tasks` — `TaskService` (abstract seam) Abstract background task registry. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.tasks` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). Implementations must honor these semantics: - Registrations outlive producer and control-surface fibers. Owner and service disposal cancel live work and await compliant producers; a throwing teardown cancel force-fails only the record. - Owned-task access is fenced by the owner's session id. Ids are predictable, so authorization — not secrecy — is the boundary. - Settlement is first-wins: one terminal record, one round of contained listener notification, and released waiters, even against a late producer outcome. - start refuses work while no control surface is attached, so a producer cannot start work that callers cannot collect or stop. ```ts cordis-catalog /** * Preflight access, validation, and owner cleanup before starting and * atomically registering work. A throwing starter leaves nothing registered; * after it returns, registration cannot fail. Settlement records the outcome, * notifies listeners, and releases waiters. * @param spec - task identity, owner, and synchronous starter. * @returns the registry-issued `-N` id. */ abstract start(spec: TaskStart): TaskId /** * List caller-owned and unowned tasks in registration order without exposing * another session's labels. * @param caller - reading agent; a non-agent caller sees only unowned tasks. * @returns fresh snapshots. */ abstract list(caller?: Agent): TaskSnapshot[] /** * Return a non-consuming snapshot without changing its read cursor or notice * state. Throws for an unknown or foreign task. * @param id - task to look up. * @param caller - reading agent checked against the owner. * @returns a fresh snapshot. */ abstract get(id: TaskId, caller?: Agent): TaskSnapshot /** * Read the next stream delta, or the idempotent final output after settlement. * A terminal read marks the task reported. Throws for an unknown or foreign * task. * @param id - task to read. * @param caller - reading agent checked against the owner. * @returns output text and the post-read snapshot. */ abstract read(id: TaskId, caller?: Agent): TaskRead /** * Request cancellation, then mark the task stopping and reported. A producer * throw propagates without changing task state. Throws for an unknown or * foreign task. * @param id - task to cancel. * @param caller - killing agent checked against the owner. * @param reason - logged reason forwarded to the producer. * @returns `requested` for live work, otherwise `already-finished`. */ abstract kill(id: TaskId, caller?: Agent, reason?: string): 'requested' | 'already-finished' /** * Wait for settlement or timeout without cancelling the task. Caller abort * rejects only while the task is live; after settlement the terminal * snapshot wins so a notice suppressed for this waiter is still delivered. * Throws for invalid, unknown, or foreign input. * @param id - task to wait for. * @param timeoutMs - positive finite wait bound in milliseconds. * @param caller - waiting agent checked against the owner. * @param signal - optional cancellation of the wait itself. * @returns snapshot at settlement or timeout. */ abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise /** * Register an effect-scoped completion listener. Each listener is contained; * returned promises are observed but not awaited. No listener runs after * service disposal. * @param listener - receives each terminal snapshot and its exact owner. * @returns disposer that unregisters the listener. */ abstract onTaskDone(listener: TaskDoneListener): () => void /** * Attach an effect-scoped surface that can read and stop tasks. {@link start} * refuses work while none is attached. * @param name - diagnostic label; duplicate names remain independent. * @returns disposer that detaches this surface. */ abstract attachSurface(name: string): () => void ``` Types: [Agent](../core-data-structures/core.md) · [TaskDoneListener](../core-data-structures/tasks.md) · [TaskId](../core-data-structures/tasks.md) · [TaskRead](../core-data-structures/tasks.md) · [TaskSnapshot](../core-data-structures/tasks.md) · [TaskStart](../core-data-structures/tasks.md) Source: [`packages/tasks/tasks/src/index.ts:50`](../../packages/tasks/tasks/src/index.ts) ## `ctx.telemetry` — `Telemetry` (abstract seam) The backend contract in its loadable form: one implementation per context — the cordis `Service` registration under the `telemetry` key throws on a duplicate, cordis' standard behavior. A backend composes a TelemetryCoordinator in its constructor to install the capture side. ```ts cordis-catalog /** * See {@link TelemetryBackend.emit} — the seam declaration is the contract's one home. * @param record - the logical record to report; owned by the backend after the call. */ abstract emit(record: TelemetryRecord): void /** See {@link TelemetryBackend.flush}. */ flush?(): void /** * See {@link TelemetryBackend.shutdown}. * @returns resolves when the backend's pipeline has quiesced. */ abstract shutdown(): Promise ``` Source: [`packages/telemetry/session-telemetry/src/index.ts:135`](../../packages/telemetry/session-telemetry/src/index.ts) ## `ctx.tokenMeter` — `TokenMeterService` Replay owner for one service-wide estimator and isolated per-session folds. ```ts cordis-catalog /** * Measure current request pressure and surface through the durable tail. * * Provider usage is reused only when the latest successful call's canonical * request envelope matches `requestHeader` and its total is no lower than * that call's full heuristic anchor; otherwise the complete envelope and * surface are heuristically repriced. * * `requestHeader` affects request pressure only; surface fields always * describe the current session surface. Every call clones those positional * nodes, so measurement is O(surface). * * @param session - session to replay through its current durable tail. * @param requestHeader - optional effective request envelope replacing the latest logged header. * @returns a detached deeply immutable pressure and surface measurement. */ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement /** * Heuristically price one model-visible message. * @param message - message to price without mutation. * @returns content and role-framing tokens under the fixed service heuristic. */ estimateMessage(message: Message): number ``` Types: [EpochHeader](../core-data-structures/session.md) · [Message](../core-data-structures/core.md) · [Session](../core-data-structures/session.md) · [TokenMeasurement](../core-data-structures/token-meter.md) Source: [`packages/llm/token-meter/src/index.ts:85`](../../packages/llm/token-meter/src/index.ts) ## `ctx.toolResultPrune` — `ToolResultPruneService` Deterministic head/middle/tail pruning for current tool-result surface nodes. ```ts cordis-catalog /** * Measure text content in Unicode code points; non-text blocks cost zero. * @param blocks - tool-result content to measure. * @returns total Unicode code points across text blocks. */ measureContent(blocks: readonly ContentBlock[]): number /** * Replace an over-budget text middle while retaining rich-block order. * Text slicing is by Unicode code point, not UTF-16 code unit, so a retained * boundary cannot split a surrogate pair. Grapheme clusters may still split. * @param blocks - original tool-result content. * @returns pruned content, or `null` when the text is within budget. */ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null /** * Prune every over-budget tool result from one stable current-surface snapshot. * Each replacement preserves the complete event data except for `content`, * and points at the shadowed node for durable provenance and replay. * @param session - session whose current surface is rewritten. * @returns landed replacements and aggregate Unicode-code-point savings. * @throws when the session rejects a replacement; replacements committed * earlier in the pass remain durable. */ pruneSession(session: Session): PruneResult ``` Types: [ContentBlock](../core-data-structures/core.md) · [PruneResult](../core-data-structures/compaction.md) · [Session](../core-data-structures/session.md) Source: [`packages/compact/compact-tool-result-prune/src/index.ts:40`](../../packages/compact/compact-tool-result-prune/src/index.ts) ## `ctx.tools` — `ToolRegistry` Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch. ```ts cordis-catalog /** * Register globally or in the calling agent scope. Scoped tools shadow * globals; duplicates within one layer and the reserved `run_code` name fail. * @param definition - tool schema, execution, and optional finalization/presentation callbacks. * @returns the exact disposer that unregisters the tool. */ register(definition: ToolDefinition): () => void /** * Restrict global tools for the calling agent scope. Empty filters, unknown * names, scope-local names, and reserved transport names fail. Restrictions * intersect; scoped registrations remain visible. * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove). * @returns the exact disposer that lifts this restriction. */ restrict(filter: ToolRestriction): () => void /** * Register a monotonic guard after the extensible `tools/pre-execute` * waterfall. A plain-context guard applies globally; one registered through * `agent.ctx` applies only to that agent. Any matching guard may deny by * returning a reason, while no guard can force-allow a call another guard * denied. The exact effect disposer is returned for ordered ownership and * HMR cleanup. * @param guard - synchronous check; a returned string denies the execution. * @returns the exact disposer that unregisters the guard. */ guard(guard: ToolGuard): () => void /** * Look up a tool as one scope sees it (scoped * shadows global; a restricted-away global reads as absent). Presenters pass * the calling agent so the rendered card matches the definition that * actually executed. * @param name - the tool name as registered. * @param scope - the viewing scope (the agent); omitted = the global view. * @returns the definition the scope resolves, or undefined when none is visible. */ get(name: string, scope?: ScopeKey): ToolDefinition | undefined /** * Project visible definitions onto the allowlisted model-facing schema fields, * excluding execution and presentation callbacks. * @param scope - the viewing scope (the agent); omitted = the global view. * @returns one deep-cloned schema per visible tool. */ schemas(scope?: ScopeKey): ToolSchema[] /** * Classify a pending call through the caller's visible tool definition. Only * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or * throwing classifiers are exclusive. * @param exec - call name, parsed arguments, and optional agent scope. * @returns the fail-closed scheduling mode. */ executionMode(exec: ToolExecutionInput): ToolExecutionMode /** * Execute through pre-policy, guards, around-dispatch, post-policy, * definition-owned content finalization, and final notification. Tool and * listener failures resolve as materialized error results; an invisible tool * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen * snapshot final observers receive. Cancellation * arriving after entry and before final result materialization skips a * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a * successful started outcome with `ABORTED`; already-started work is still * drained and may retain a tool-owned structured error. * @param exec - the typed same-process call input. The registry assigns its * correlation token before policy begins. * @returns the materialized final result. */ async execute(exec: ToolExecutionInput): Promise ``` Types: [ScopeKey](../core-data-structures/scope.md) · [ToolDefinition](../core-data-structures/tools.md) · [ToolExecutionInput](../core-data-structures/tools.md) · [ToolExecutionMode](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolGuard](../core-data-structures/tools.md) · [ToolRestriction](../core-data-structures/tools.md) · [ToolSchema](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:731`](../../packages/core/tools/src/index.ts) ## `ctx.typert` — `TypertRegistry` Registry of generated schemas and package reflection. ```ts cordis-catalog /** * Register one generated contribution atomically for the calling fiber. * Duplicate package-face identities or schema keys reject the whole batch. * @param contribution - generated schemas and package metadata. * @returns the exact effect disposer that removes this contribution. */ register(contribution: TypertContribution): () => void /** * Look up one schema by `#`. * @param key - global schema key. * @returns the live schema record, or `undefined` when absent. */ get(key: string): TypertSchemaRecord | undefined /** * Resolve one required schema. * @param key - global schema key. * @returns the live schema record. * @throws when the key is malformed, the package face is absent, or the schema is not contributed. */ resolve(key: string): TypertSchemaRecord /** * Enumerate live schemas in registration order. * @param filter - optional package and face restriction. * @returns matching schema records. */ list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] /** * Look up generated reflection for one package face. * @param packageName - exact npm package name. * @param face - face to query; defaults to the host runtime. * @returns the live package record, or `undefined` when absent. */ getPackage(packageName: string, face: TypertFace = 'host'): TypertPackageRecord | undefined /** * Enumerate generated package reflection in registration order. * @param filter - optional package and face restriction. * @returns matching package records. */ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] /** * Project a live Zod schema to JSON Schema without caching the result. * @param key - global schema key. * @param params - Zod projection parameters. * @returns a fresh JSON Schema document. */ toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema ``` Source: [`packages/typert/registry/src/index.ts:67`](../../packages/typert/registry/src/index.ts) ## `ctx.userInteraction` — `UserInteractionService` `ctx.userInteraction`: one active UI provider plus an `ask()` surface. ```ts cordis-catalog /** * Register the UI provider. Only one provider may be active in a context. * * @param provider UI-side implementation that collects answers. * @returns Disposer that unregisters this provider. */ registerProvider(provider: UserInteractionProvider): () => void /** * Ask the active UI provider and wait for the user's answer. * * @param request Questions, owner agent, and abort signal. * @returns The answer chosen or typed by the human. */ async ask(request: AskUserQuestionRequest): Promise ``` Types: [AskUserQuestionAnswer](../core-data-structures/user-interaction.md) · [AskUserQuestionRequest](../core-data-structures/user-interaction.md) · [UserInteractionProvider](../core-data-structures/user-interaction.md) Source: [`packages/ui/user-interaction/src/index.ts:51`](../../packages/ui/user-interaction/src/index.ts) ## `ctx.web` — `WebService` The web access service. Registered as `ctx.web` (one instance per context). Selection semantics (resolved at execution time, never order-dependent): - A configured id that is registered and `available()` → that provider. - A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. - A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. - No id configured, exactly one registered usable provider → that provider. - No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. - No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. ```ts cordis-catalog /** * Register a search provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` * if its id is already registered for search. Returns a disposer; disposed * with the calling fiber. * @param provider - the provider; its `id` is the registry key. * @returns the disposer that unregisters the provider. */ registerSearchProvider(provider: WebSearchProvider): () => void /** * Register a fetch provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` * if its id is already registered for fetch. Returns a disposer; disposed * with the calling fiber. * @param provider - the provider; its `id` is the registry key. * @returns the disposer that unregisters the provider. */ registerFetchProvider(provider: WebFetchProvider): () => void /** * Run one search through the selected provider. Resolves the provider at call * time with the selection rules above; throws {@link WebError} when the * capability cannot run. The seam enforces `request.maxResults` on the result: * if the provider over-returns, `sources[]` is truncated and `truncated` set. * @param request - the query plus result-shaping options. * @param signal - optional cancellation signal forwarded to the provider. * @returns the provider's results, capped to `request.maxResults`. */ async search(request: WebSearchRequest, signal?: AbortSignal): Promise /** * Retrieve one URL through the selected provider. Resolves the provider at * call time with the selection rules above; throws {@link WebError} when the * capability cannot run. A non-2xx response is a result, not a throw. * @param request - the URL plus retrieval options. * @param signal - optional cancellation signal forwarded to the provider. * @returns the retrieval outcome; non-2xx responses resolve descriptively. */ async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise ``` Types: [WebFetchProvider](../core-data-structures/web.md) · [WebFetchRequest](../core-data-structures/web.md) · [WebFetchResult](../core-data-structures/web.md) · [WebSearchProvider](../core-data-structures/web.md) · [WebSearchRequest](../core-data-structures/web.md) · [WebSearchResult](../core-data-structures/web.md) Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts) ## `ctx.workflows` — `WorkflowService` (abstract seam) Workflow execution seam. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles. ```ts cordis-catalog /** * Parse and execute a workflow script. * @param request - the script, its `args`, the parent agent, and an * optional cancel signal. * @returns the live run; its `result` resolves when the script settles. */ abstract start(request: WorkflowStartRequest): WorkflowRun ``` Types: [WorkflowRun](../core-data-structures/workflow.md) · [WorkflowStartRequest](../core-data-structures/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:159`](../../packages/workflow/workflow/src/index.ts) ## `ctx.workspace` — `WorkspaceRegistry` Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker. ```ts cordis-catalog /** * Create or reuse a workspace for an existing directory. The path is * canonicalized through `fs.realpath`; a nonexistent path rejects with the * original error and a non-directory rejects. Repeated calls for the same * canonical path return the existing entity without changing its title. * A newly created workspace is prepended to the durable registry order. * Different canonical paths may share a display title. * @param path - Existing directory to own, in any path spelling. * @param title - Display title used only when a new record is created. * @returns the existing or newly durable workspace. */ async create(path: string, title?: string): Promise /** * Look up a workspace by id. * @param id - Workspace id. * @returns the workspace, or `undefined` when unknown. */ get(id: WorkspaceId): Workspace | undefined /** * Synchronous workspace projection in durable registry order. Every * entity's `sessionIds` getter is already filtered by the startup/live * canonical-cwd header index; this method performs no persistence reads. * @returns a fresh ordered array of workspace entities. */ list(): Workspace[] /** * Delete one workspace registration while retaining its directory and every * session log. The durable order is updated before the table deletion; a * failed table write restores the prior order and keeps the entity * published. Unknown ids are an idempotent no-op for domain callers. * @param id - Workspace registration to remove. * @returns `true` when a record was deleted, `false` when it was unknown. */ delete(id: WorkspaceId): Promise /** * Archive one session durably. The session must exist (live or in session * persistence); its workspace accounting — or lack of one — is irrelevant. * An already archived id resolves without writing. * @param sessionId - The session to archive. * @returns resolution after durability. */ archiveSession(sessionId: SessionId): Promise /** * Resolve by canonical directory path without creating or mutating a * workspace. A missing path rejects during `realpath`; an existing unowned * directory returns `undefined`. * @param path - Existing directory path in any spelling. * @returns the workspace owning the canonical path, when one exists. */ async resolveByPath(path: string): Promise ``` Types: [SessionId](../core-data-structures/core.md) Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts) ## Inherited `ctx` members (cordis core + loader/hmr/timer) The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier's prominence. - `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) - `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) - `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:164`](../../vendor/cordis/src/registry.ts)) - `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) - `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) - `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts)) - `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) - `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) - `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) - `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts))