# 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 transaction. * @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:398`](../../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:225`](../../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 prompt and pre-step notices. ```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 ``` Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) Source: [`packages/ui/user-approval/src/index.ts:213`](../../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. - Disposal kills all running background processes and awaits their exit. ```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:48`](../../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 bash 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 bash 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/bash.md) · [ToolExecution](../core-data-structures/tools.md) Source: [`packages/bash/tool-bash/src/index.ts:104`](../../packages/bash/tool-bash/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 sweep 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:143`](../../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. * @param agent - exact receiving agent. * @param line - complete slash-command line. * @param signal - cancellation signal owned by the UI request. * @returns a detached result, 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) · [CommandResult](../core-data-structures/commands.md) Source: [`packages/ui/commands/src/index.ts:227`](../../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 /** * 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:54`](../../packages/compact/compact/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:135`](../../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 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:55`](../../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 that unregisters all of them. */ registerAdapter(providers: string[], adapter: LlmAdapter): () => void /** * Describe provider routes with a registered adapter. * @returns detached provider metadata in registration order. */ listProviders(): LlmProviderInfo[] /** * 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 context capacity from the adapter that owns one exact route. * This query is independent of the advisory model catalog: an unlisted model * may return metadata, while `undefined` never rejects later routing. * @param provider - registered provider route to inspect. * @param model - exact model id passed to the adapter. * @returns detached context metadata, or `undefined` when the adapter has none. */ async resolveModelContext( provider: string, model: string, ): 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, 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: [GenerateOptions](../core-data-structures/core.md) · [LlmAdapter](../core-data-structures/llm-streaming.md) · [LlmModelContext](../core-data-structures/core.md) · [LlmModelInfo](../core-data-structures/core.md) · [LlmProviderInfo](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:159`](../../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 /** * 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:97`](../../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 from the next turn 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. */ set(agent: Agent, active: boolean): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/plan/plan-mode/src/index.ts:141`](../../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 = '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:131`](../../packages/sandbox/sandbox/src/index.ts) ## `ctx.sandboxPolicy` — `SandboxPolicyService` The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode and fallback workspace root. 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 ``` Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md) Source: [`packages/sandbox/sandbox-policy/src/index.ts:68`](../../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. * @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, so observers cannot mutate backend-owned state. * @param id - the persisted session to inspect. * @returns the header and valid stored event prefix exactly as observed. */ abstract inspect(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> /** * Lightweight listing from metadata, without a full-log parse. * @returns one header per materialized session. */ abstract list(): 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. * @returns one header and opaque revision per materialized session without loading full logs. */ abstract listSnapshots(): 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.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 in deterministic relevance order. */ abstract searchEvents( request: SessionEventSearchRequest, exec?: SessionSearchExecContext, ): Promise> /** * List the complete logical corpus using live-preferred records. * @returns deterministic newest-first cloned session records. */ listSessions(): 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. * @returns matching cloned records in deterministic newest-first order. */ async filterSessions(filters: readonly SessionResultFilter[]): Promise /** * Fold the latest log-backed title from one live-preferred logical session. * @param sessionId - live or persisted session id to read. * @returns latest title snapshot, or `undefined` when the log has no title event. */ async readTitle(sessionId: SessionId): 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. * @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): Promise /** * Trace one event's direct positional and provenance relationships. * @param request - target session id and event seq. * @returns direct links plus the target's positional replacement chain. * @throws when source resolution fails, the target is absent, or surface/provenance validation fails. */ async traceEvent(request: SessionEventTraceRequest): Promise /** * Read one full event plus a bounded raw-log context window. * @param request - target session/seq and context sizes. * @returns cloned target and neighboring events. */ async readEvent(request: SessionEventReadRequest): 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) · [SessionEventSearchHit](../core-data-structures/session-query.md) · [SessionEventSearchRequest](../core-data-structures/session-query.md) · [SessionEventTrace](../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) · [SessionTitleSnapshot](../core-data-structures/session-title.md) Source: [`packages/session-query/session-query/src/index.ts:74`](../../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 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 = '', limit = 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 zero or one prepared contexts. */ 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:69`](../../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 resolves when every flush listener has settled; after all settle, * rejects with the first registered listener failure if any listener failed. */ async flush(session: Session): Promise /** * Append one plugin-declared log-only event without borrowing the agent * loop's lifecycle. An open turn receives the event directly and remains * responsible for its ordinary checkpoint. A closed log receives one * zero-step turn around the event, followed by an awaited flush. * * Once the synthetic `turn/start` commits, this method always attempts its * matching `turn/end` and flush, including when the target append fails. * Detachment requested by an event or flush listener is deferred until that * sequence settles, so publication cannot switch from a live scoped session * to an unobserved bare `Session` halfway through the update. * * @param session - exact live session that owns the target log. * @param type - event type opted into {@link OutOfBandSessionEventMap} by its owner. * @param data - typed JSON payload for the target event. * @param trigger - plugin-owned turn trigger used only when the log is closed. * @returns the accepted target event with its assigned sequence and timestamp. * @throws when the session is detached, another out-of-band append is active, * event acceptance fails, the synthetic turn cannot close, or flushing fails. */ async appendOutOfBand( session: Session, type: T, data: SessionEventMap[T], trigger: TurnTrigger, ): 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 turn-enclosed prefix of a live source. * `boundary` is an inclusive source event seq; omitted means the source's * current last event. A non-empty selected slice must end at `turn/end`. * * @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) · [OutOfBandSessionEventType](../core-data-structures/session.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) · [SessionEventMap](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md) · [TurnTrigger](../core-data-structures/session.md) Source: [`packages/core/session/src/index.ts:606`](../../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 /** * 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; an in-progress fallback append may finish durably before rejection. * @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:284`](../../packages/session-title/session-title/src/index.ts) ## `ctx.skills` — `SkillService` Registry of skill providers. It merges provider catalogs with stable first-wins duplicate handling, exposes sorted model-visible 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 provider - the provider to register by `provider.name`. * @returns the exact Cordis effect disposer that unregisters this provider; * composite effects may yield it directly to preserve teardown ordering. */ registerProvider(provider: 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 complete skill definition to expose for discovery. * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches. */ register(skill: SkillRegistration): () => void /** * List model-invocable skill summaries for a workspace. 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 sorted summaries, excluding skills disabled for model invocation. */ async list(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: [SkillDefinition](../core-data-structures/skills.md) · [SkillLookupOptions](../core-data-structures/skills.md) · [SkillProvider](../core-data-structures/skills.md) · [SkillRegistration](../core-data-structures/skills.md) · [SkillSummary](../core-data-structures/skills.md) Source: [`packages/skill/skill/src/index.ts:141`](../../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:35`](../../packages/storage/storage/src/index.ts) ## `ctx.subagents` — `SubagentService` Named provider registry and capability-checked start surface. ```ts cordis-catalog /** * 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 ready 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. * @param name - the provider to use. * @param request - child prompt, parent, signal, and optional capabilities. * @returns the ready holder-owned run. */ async start(name: string, request: SubagentStartRequest): Promise ``` Types: [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:180`](../../packages/subagent/subagent/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 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) · [PromptSection](../core-data-structures/system-prompt.md) · [ToolProviderResult](../core-data-structures/system-prompt.md) Source: [`packages/core/system-prompt/src/index.ts:246`](../../packages/core/system-prompt/src/index.ts) ## `ctx.tasks` — `TaskService` The `tasks` service: the runtime-global background task registry. See the module doc for the ownership, isolation, and lifecycle contracts. ```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. */ 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. */ 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. */ 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. */ 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`. */ 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 it returns the * terminal snapshot so a notice suppressed for this waiter is still delivered. * Timed-out and aborted waits detach their resolvers. 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. */ async 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. */ 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. */ 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:77`](../../packages/tasks/tasks/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:82`](../../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:39`](../../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:634`](../../packages/core/tools/src/index.ts) ## `ctx.tui` — `TuiExtensionService` (abstract seam) Optional terminal-local interaction service provided by one mounted TUI. The concrete provider retains pi-tui, focus, and terminal lifecycle state. Plugins receive only effect-owned overlay sessions. ```ts cordis-catalog /** * Queue an interactive overlay owned by the calling plugin fiber. * * The TUI displays one overlay at a time in FIFO order. Disposing the caller * removes a queued overlay or closes an active one before plugin teardown * settles. This live presentation is neither logged nor replayed. * * @param request - component factory, layout constraints, and cancellation. * @returns the effect-owned overlay session. * @throws when the TUI has begun shutting down. */ abstract openOverlay(request: TuiOverlayRequest): TuiOverlaySession ``` Source: [`packages/ui/tui/src/index.ts:150`](../../packages/ui/tui/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:50`](../../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` The workspace registry service. Opens the `workspace` domain at startup, rebuilds one entity per stored record, and serves entities from an in-memory cache keyed by id. Session persistence is an OPTIONAL peer (resolved via `ctx.get`, never injected): while it is absent, session attachment rejects (what cannot be validated is not recorded) and `sessionIds` projections serve the account unfiltered. There is deliberately no delete entry point in this phase: workspace deletion ships as one complete semantic together with the session-cascade primitives (future work in the owning Agent Note). ```ts cordis-catalog /** * Create a workspace over an existing directory. The path is canonicalized * through `fs.realpath` first — a nonexistent path rejects with the * original `ENOENT`, a path resolving to anything but a directory rejects, * and a canonical path already owned by another workspace (including a * symlink resolving to it) rejects. * @param path - Directory the workspace points at; canonicalized before storing. * @param title - Display title; defaults to `basename` of the canonical path. * @returns the created workspace after durability. */ async create(path: string, title?: string): Promise /** * Look up a workspace by id. * @param id - The workspace id. * @returns the workspace, or `undefined` when unknown. */ get(id: WorkspaceId): Workspace | undefined /** * Snapshot of all workspaces, in load-then-creation order. * @returns a fresh array of the cached entities. */ list(): Workspace[] /** * Resolve a workspace by directory path, through the same `fs.realpath` * canon as {@link create} (hence async). A path that does not exist rejects * with the original error — a missing directory has no canonical form to * compare (a workspace whose recorded directory vanished is only reachable * by id; see `Workspace.status`). * @param path - Directory path in any spelling (symlinks, `..`, trailing slash). * @returns the owning workspace, or `undefined` when none matches. */ async resolveByPath(path: string): Promise ``` Source: [`packages/workspace/workspace/src/index.ts:60`](../../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))