Skip to main content

SDK API reference

Options Reference

Directories

Atomic reads primary .atomic locations first and legacy .pi locations for compatibility when multiple config directories are supported. Passing an explicit agentDir makes that directory the user override. cwd is used by DefaultResourceLoader for:
  • Project extensions (.atomic/extensions/, then legacy .pi/extensions/)
  • Project skills:
    • .atomic/skills/, then legacy .pi/skills/
    • .agents/skills/ in cwd and ancestor directories (up to git repo root, or filesystem root when not in a repo)
  • Project prompts (.atomic/prompts/, then legacy .pi/prompts/)
  • Context files (AGENTS.override.md, AGENTS.md, or CLAUDE.md walking up from cwd)
  • Session directory naming
agentDir is used by DefaultResourceLoader for:
  • Global extensions (extensions/)
  • Global skills:
    • skills/ under agentDir (for example ~/.atomic/agent/skills/; legacy ~/.pi/agent/skills/ is also considered by default)
    • ~/.agents/skills/
  • Global prompts (prompts/)
  • Global context files (AGENTS.override.md, AGENTS.md, or CLAUDE.md under agentDir)
  • Settings (settings.json)
  • Custom models (models.json)
  • Credentials (auth.json)
  • Sessions (sessions/)
When you pass a custom ResourceLoader, cwd and agentDir no longer control resource discovery. They still influence session naming and tool path resolution.

Model

ModelRegistry keeps synchronous reads for extension compatibility, while catalog refresh is asynchronous. Extensions should await modelRegistry.refresh() before synchronous getAll(), find(), or getAvailable() reads when a provider may update its catalog. New SDK integrations use ModelRuntime; await modelRuntime.refresh() reports aborted and per-provider errors, and failed providers retain their last-known models. If no model is provided:
  1. Tries to restore from session (if continuing)
  2. Uses default from settings
  3. Falls back to first available model
See examples/sdk/02-custom-model.ts

Model catalog persistence and refresh

ModelRuntime.create() restores cached catalogs from local persistence but does not contact pi.dev unless you opt in. allowModelNetwork (default false) enables a create-time network refresh, and modelRefreshTimeoutMs (default 15_000) bounds how long that refresh may run before it is aborted. Pass refreshOnCreate: false to skip the initial catalog and availability refresh entirely; built-in models remain available.
Remote catalogs are persisted locally so later runtimes can restore them without a network request. The default file is models-store.json next to models.json — with the default modelsPath that is ~/.atomic/agent/models-store.json. Set modelsStorePath to choose another location, or inject modelsStore to control persistence entirely; a runtime created with modelsPath: null keeps its store in memory. Network refreshes are throttled to once per provider every four hours unless forced. To force an immediate refresh, call await modelRuntime.refresh({ allowNetwork: true, force: true, signal }). Setting ATOMIC_OFFLINE (legacy alias PI_OFFLINE) disables model network access, and a refresh() call that omits allowNetwork follows that same runtime network policy.

API Keys and OAuth

ModelRuntime is the asynchronous SDK engine for provider composition, credentials, model catalogs, and requests. ModelRegistry remains a thin compatibility facade for extensions; await modelRegistry.complete(model, context, options) routes a request through its runtime with the resolved provider and auth. New SDK integrations should pass modelRuntime to createAgentSession and use modelRuntime.complete() directly when they issue standalone requests. Credential resolution combines runtime API-key overrides, stored auth.json credentials, environment variables, and the active models.json provider configuration. OAuth acquisition is provider-owned and runs through ModelRuntime.login().
See the complete ModelRuntime credential and model configuration example.

System Prompt

Use a ResourceLoader to override the system prompt:
See examples/sdk/03-custom-prompt.ts

Tools

Specify which tools to expose by name:
  • Built-in tool names enabled by default: read, bash, kill, edit, write, find, search, ask_user_question, todo
  • find discovers filesystem paths by glob; search searches file contents with regex patterns across files, directories, globs, and internal URLs.
  • tools is an allowlist: when provided, only the listed built-in, extension, and custom tool names are exposed, plus mandatory ordinary intercom.
  • excludedTools is a blocklist: matching built-in, extension, and custom tool names are omitted from the final registry and active tool set, except mandatory ordinary intercom. If both are provided, tools is applied first and excludedTools subtracts from it.
  • noTools: "all" disables every tool except mandatory ordinary intercom
  • noTools: "builtin" disables default built-ins while keeping extension and custom tools enabled, except names listed in excludedTools

Bash tool behavior

Atomic’s built-in bash tool matches upstream pi: when bash is enabled, commands execute through the configured shell with the Atomic process permissions. Use tools, excludedTools, or noTools to decide whether a session exposes the bash tool at all. Atomic no longer provides a command-level allow/deny option for bash; use an operating-system/container sandbox or a custom tool/extension when you need command allowlisting or stronger isolation.

Waiting for existing shell tasks

Both Bash and PowerShell factories accept { action: "wait", id: taskId, budgetMs: 1000 } with a trusted taskOwner binding. No command is executed. BashToolInput and PowerShellToolInput distinguish command launches from existing-task waits; narrow by action before reading command. budgetMs is optional, finite, and non-negative. Omission uses the owner’s command wait policy and zero polls. Results keep the WaitOutcome in details.observation, available exit information in details.exitCode, and retained output in text content. Failure and cancellation metadata remain in the settled observation. Yielded waits advance through bounded retained-output pages for the same owned task, even when the tool is recreated. Partial UTF-8 characters continue on the next page. Settled waits return all retained output again, subject to labelled gaps and truncation. Aborting the call releases only its observation. A binding from AgentTaskHost.ownerBinding also releases waits for incoming owner messages. Do not mix wait arguments with launch fields. Unknown or foreign IDs and unbound waits are rejected before execution hooks. Custom operations.exec does not provide existing-task ownership. See Background tasks for examples and lifetime rules.

PowerShell tool behavior

createPowerShellTool() and createPowerShellToolDefinition() provide the same tool used by interactive sessions. When their default local operations execute on native Windows, they prefer pwsh.exe, fall back to powershell.exe, and throw a clear error when neither executable is available. createLocalPowerShellOperations() and getPowerShellConfig() are also exported for custom integrations. The PowerShell factories expose the current ATOMIC_* and legacy PI_* session snapshot by default; set exposeSessionEnvironment: false to opt out. PowerShell tools and local operations accept a trusted taskOwner binding and the same wait observation policy as bash. Owned native Windows execution automatically yields after the owner’s command budget (normally 10000 ms); explicit per-call budgets override it, and execution timeout remains separate. Commands use encoded PowerShell transport internally, while task descriptions retain the original command text. Without a supported owner, explicit background requests are refused and foreground execution waits for completion.

Tools with Custom cwd

When you pass a custom cwd, createAgentSession() builds selected built-in tools for that cwd.
See examples/sdk/05-tools.ts

Custom Tools

Use defineTool() for standalone definitions and arrays like customTools: [myTool]. Inline pi.registerTool({ ... }) already infers parameter types correctly. Custom tools passed via customTools are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via pi.registerTool(). If you pass tools, include each custom or extension tool name you want enabled, for example tools: ["read", "bash", "my_tool"]. Use excludedTools to remove a custom or extension tool by name from the final exposed set. ToolDefinition.constrainedSampling is part of the public SDK and survives defineTool(), customTools, tool wrappers, session/staged inspection, and isolated execution. Use { type: "json_schema", strict: "prefer" | "require" }, { type: "grammar", variants: { openai_lark?: string, openai_regex?: string } }, or false. prefer can fall back; require fails when the active model cannot enforce strict JSON Schema. Grammar constraints require one required string parameter and capable model metadata. Public inspection preserves optional-property identity exactly: an omitted key stays absent, an explicitly present undefined stays present, and false or a config object remains unchanged. The exported ConstrainedSamplingConfig type and extension reference define the exact shape. Typed RPC clients receive the four model capability flags through optional ModelInfo.compat; see RPC. Factory-created createBashTool() instances receive the same execution-time ATOMIC_SESSION_*/PI_SESSION_* model and session snapshot as the built-in bash tool. Set exposeSessionEnvironment: false only when the subprocess must not receive it. MessageRenderOptions.outputPad is likewise passed to normal and isolated custom message renderers. Normal sessions also expose kill({ id: taskId }) for their owned bash and PowerShell background tasks. Include kill when using a tools allowlist if the agent should be able to stop those tasks. The exported createKillTool and createKillToolDefinition factories accept KillToolOptions.taskOwner, a trusted execution-time callback returning the same owner binding used by shell launch. Without a binding they reject execution. The result details preserve the supervisor’s CancelReceipt, including its decision, execution outcome, and cleanup state. See Background tasks.

Structured output final results

structured_output is not registered in normal agent sessions by default. Add it only when a caller needs a machine-readable final-answer contract by registering the exported factory as a custom tool:
The tool parameters are exactly the supplied schema: with DecisionSchema, the model calls structured_output({ approved, findings }). Array and primitive schemas are also accepted by the factory when the target provider/tool runtime supports them; the captured value is whatever JSON value matches the schema. A successful call stores the params in capture.value, returns them as pretty-printed JSON tool-result text for text print mode, keeps the flat value in tool details, writes the same JSON to the configured output.outputPath when an output file sink is configured, and sets terminate: true so there is no extra follow-up assistant turn. Atomic relies on the tool schema instead of extra structured-output parsing or sidecar validation. Structured-output tool definitions opt out of oversized-result persistence. Custom tool names are supported, and the prompt metadata follows the configured name. If you use a custom name such as final_decision, include that name in any explicit tools allowlist. If the standard structured_output name is required, register the factory with its default name:
See examples/sdk/05-tools.ts

Extensions

Extensions are loaded by the ResourceLoader. DefaultResourceLoader discovers extensions from ~/.atomic/agent/extensions/ and .atomic/extensions/ first, then legacy ~/.pi/agent/extensions/ and .pi/extensions/, plus settings.json extension sources.
createAgentSession() preserves resources from a supplied loader but restores Atomic’s mandatory bundled Intercom extension after loader overrides, deferred reloads, and same-name extension or customTools collisions. The supplied loader still controls every optional extension. Strict reloads (failOnExtensionErrors: true) require the loader’s transactional prepareReload() support so a failed candidate cannot mutate live state before validation. DefaultResourceLoader provides that support. Custom loaders without it remain compatible with ordinary reloads, but strict reload fails before calling their mutating reload() method. Extensions can register tools, subscribe to events, add commands, and more. See Extensions for the full API. Event Bus: Extensions can communicate via pi.events. Pass a shared eventBus to DefaultResourceLoader if you need to emit or listen from outside:
See examples/sdk/06-extensions.ts and Extensions

Skills

See examples/sdk/04-skills.ts

Context Files

See examples/sdk/07-context-files.ts

Slash Commands

See examples/sdk/08-prompt-templates.ts

Session Management

Sessions use a tree structure with id/parentId linking, enabling in-place branching.
SessionManager tree API:
See examples/sdk/11-sessions.ts and Session Format

Settings Management

Static factories:
  • SettingsManager.create(cwd?, agentDir?) - Load from files
  • SettingsManager.inMemory(settings?) - No file I/O
Project-specific settings: Settings load from Atomic-first locations and merge:
  1. Global: ~/.atomic/agent/settings.json, then legacy ~/.pi/agent/settings.json
  2. Project: <cwd>/.atomic/settings.json, then legacy <cwd>/.pi/settings.json
Project overrides global. Nested objects merge keys. Setters modify global settings by default. Persistence and error handling semantics:
  • Settings getters/setters are synchronous for in-memory state.
  • Setters enqueue persistence writes asynchronously.
  • Call await settingsManager.flush() when you need a durability boundary (for example, before process exit or before asserting file contents in tests).
  • SettingsManager does not print settings I/O errors. Use settingsManager.drainErrors() and report them in your app layer.
See examples/sdk/10-settings.ts

ResourceLoader

Use DefaultResourceLoader to discover extensions, skills, prompts, themes, and context files.

Return Value

createAgentSession() returns:

Run Modes

The SDK exports run mode utilities for building custom interfaces on top of createAgentSession():

InteractiveMode

Full TUI interactive mode with editor, chat history, and all built-in commands:

runPrintMode

Single-shot mode: send prompts, output result, exit:

runRpcMode

JSON-RPC mode for subprocess integration:
See RPC documentation for the JSON protocol.

Exports

The main entry point exports:
For extension types, see Extensions for the full API.

Owner-bound task supervisor (S1)

S1 adds an SDK-only task foundation in src/core/tasks/contracts.ts and src/core/tasks/supervisor.ts, backed by the native TaskSupervisor. It is an internal trusted-host integration surface, not a new CLI command. The package root exports the narrow AgentTaskHost adapter and its integration types, not the raw supervisor. Runtime-created subagent contexts use it; bash/PTY and task UI integration are separate slices. AgentTaskHost binds an actual trusted scope and mandatory authorizeLaunch guard. Its startAgentTask(intent, operation, runnerFactory) returns a Result containing {taskId, lease} after setup. Each launch supplies its own factory receiving the original AbortSignal, reference and reportActivity context. Return separate result and cleanup promises; yielding never replaces either promise, and only confirmed cleanup may report reaped. Exact operation replay never calls another factory. observeAgentLaunch(taskId, policy?) delegates to S1 initial observation; waitForTask, resolveTask, cancelTask, watchOwnerTasks and close remain owner-scoped S1 doors. Observation returns the exact Result/WaitOutcome DTO, not a new model response shape. These APIs are for trusted first-party hosts, never model-supplied ownership or permission. For already-admitted in-process tasks, the optional taskExecution runner hooks retain the original execution and cleanup promises. An exact Intercom commit yields the registered observation. In an explicit foreground group it also yields active sibling observations through the existing group signal, once per child; neither path detaches or completes those executions. Public launches in actual sessions use this bridge by default. Each workflow admission boundary allocates one process-private stage attempt identity. The actual stage session binds its original session/run/stage identity; fallback session replacement keeps that identity and the same lazily bound bindAgentTaskHost owner. Replacement disposal does not close tasks. Boundary sealing fences task admission and starts owner closure; generation close awaits independent cleanup and surfaces failure. Fresh boundaries have fresh identities, including restoration; history is not a restart capability. Public producers, durable callback joins and nonvisual completion intent/admission use this owner binding. When a task completion outbox is created from session history, it immediately retries unacknowledged terminal completion intents through the current admission boundary. It does not wait for another task to settle or recreate execution capabilities. Acknowledged intents are not redelivered. Failed admission keeps the original completion identity pending for retry; a closed boundary prevents admission. Top-level session initialization restores admission keys from persisted custom messages, so a crash after delivery is persisted but before its outbox acknowledgement does not deliver the same completion again. A host binds its actual session or workflow-stage scope with bindHostSession, provides launch authorization and a runner factory, then calls openTaskOwner. Authorization runs before native admission. startAgentTask registers an agent task before runner setup and returns its lease without waiting for completion. Exact operation replay reuses that task and execution; a fresh operation creates a distinct task. Leases are environment-local capabilities, cannot be serialized, and cannot be reconstructed from task IDs or historical records. initialObservation applies launch policy: omitted policy yields default-background, explicit background yields explicit, and foreground registers a wait with its requested budget. A ready terminal result wins. await waitForTask(task, budgetMs?, designation?) and await foregroundTask(task, budgetMs?) return a Result containing a WaitOutcome, not a lease. Native registration and the WaitId registry are populated synchronously before either door awaits. Host lifecycle actions can use findWait(waitId) to yield or dispose a registered observation; ordinary callers need no extra observe call. SDK waits do not replace the host designation unless given a matching HostSession. An elapsed/explicit yield or observer disposal never stops or relaunches execution; a later yield of a disposed wait replays its ObserverCancelled Result. Requested agent waits default to 30000 ms. Supply owner-host settings through bindHostSession({ scope, tasks: { wait: { kind: "automatic", agentBudgetMs: 5000 } }, authorizeLaunch, createRunner }); { kind: "until-settled" } disables timed yielding. Per-call budgets override settings, including zero for immediate yield. These settings apply to explicit foreground-first launch, live foregrounding and task-ID waits, never to a default independent launch. Wide numeric budgets are not narrowed to u32. Accepted NaN budgets (including configured agentBudgetMs) do not panic native scheduling. The implementation leaves such observations pending until explicit yield, settlement, observer disposal or owner closure: the elapsed comparison never reaches NaN. It uses bounded sleep chunks without rewriting the caller’s budget. This is scheduling behavior, not a new finite-only input restriction or an RFC-mandated deadline; other numeric budgets and per-call precedence are unchanged. await cancelTask(task, cause) returns a Result containing a cancellation receipt and preserves the first accepted cause. closeTaskOwner seals admission before draining and succeeds only after independent cleanup acknowledgement. The trusted runner supplies separate result and cleanup promises: confirmed reaping after cancellation can close even if no result arrives. Natural cleanup-first delivery waits for its outcome before acknowledging reaping. External native owner closure also aborts resources attached to already-settled results without rewriting them. Failed cleanup remains observable; absent acknowledgement can leave close pending. User cancellation retains pending input attention until settlement or owner closure; event-reduced and reattached snapshots report the same native facts. Runner result rejections become failed RunnerFailed results; cleanup rejections become diagnostic CleanupFailed resources, never successful reaping. Setup throws retain SpawnFailed and unconfirmed cleanup. Strings and Error messages are preserved verbatim; other JS values use safe string conversion, with Unprintable JavaScript rejection if conversion throws. Cancelled cleanup still does not depend on the result promise settling. This slice exercises fake runners, not force-stop or real-process cleanup guarantees.

Supervised command SDK

startCommandTask(owner, intent, operation) starts an owned Unix pipe/PTY or Windows pipe/ConPTY command. The command intent keeps execution timeout separate from observation: waitForTask defaults to 10000 ms for commands, and expiry returns a yielded observation without terminating the process. On Unix, owner closure sends TERM, allows 250 ms grace, then KILL, reaps the leader and confirms process-group exit and reader drain. A cleanup failure retains diagnostics instead of claiming a closed owner. This is normal owner/host shutdown cleanup, not a guarantee for forced host death or a blocked JavaScript loop. Both native and facade CommandIntent accept optional shell: { program, args }: the executable is launched directly with command appended as one final argv argument. Omitting shell preserves the default native pipe shell. inheritEnv defaults to true; false uses exactly the supplied environment rather than inheriting the host’s. Both fields participate in operation replay identity. taskStdin(task) returns a non-serializable stdin capability. writeTaskInput takes an operation ID and {kind:"bytes", bytes:Uint8Array} or {kind:"eof"}. Empty bytes are a no-op. Input has 65536 byte credits, refuses excess input before admission, and replays recorded receipts without resending bytes. Ambiguous partial delivery returns InputDeliveryUnknown, including operation ID and known accepted-byte count. readTaskOutput(task, {start, maximumBytes}) returns owned byte chunks at decimal offsets, requested bounds, omitted ranges and an optional next offset. Requests are clamped to the 1 MiB live-preview bound before allocating or reading a page; use nextOffset to continue. It does not sanitize or normalize bytes. Retention uses a 1 MiB live head/tail, 8 MiB foreground spill threshold and 5 GiB disk cap. Retained output is not conversation history. File-spool policy uses supervised pipe drains, never inherited direct file writers. Stdout, stderr and descendants share one serialized disk budget; crossing writes retain only the permitted prefix. The file remains within the cap during foreground collection and termination. After foreground collection yields, rejected overflow kills the group and settles OutputLimitExceeded after confirmed cleanup. Spool setup failure refuses launch with SpawnFailed. Drained pipe/PTY output instead keeps running with bounded retained bytes and omissions. Unix PTY resize uses the retained portable-pty master; Windows PTY uses ConPTY. Windows pipe and ConPTY commands start suspended and enter a kill-on-close Job Object before resume. Failed containment refuses execution, with no unsupervised spawn fallback. Cleanup must be confirmed; failures retain diagnostic resources rather than reporting reaping. Native Windows legacy WSL bash.exe stdin transport remains refused for owned launch: Windows jobs cannot supervise the Linux guest process tree. Atomic running inside WSL uses the normal POSIX/Bash path instead. Bash tools and createLocalBashOperations accept a trusted taskOwner binding. On Unix and native Windows, that binding obtains pipe/PTY processes through supervised admission, preserving configured shell arguments, cwd, environment and existing authorization. Foreground collection honors the owner’s command wait configuration, including until-settled; the automatic default is 10000 ms. A yielded process stays owned and its retained output remains readable. Bash output inserts explicit [Output omitted: bytes start-end] markers, with an exclusive end offset, between retained chunks rather than silently joining gaps. Without that binding, existing bash and native PTY execution are unchanged. No UI is added. watchOwnerTasks(owner, cursor?) provides an opaque lease, snapshot, decimal-string cursor and disposable AsyncIterable<NativeEvent>. Each iterator observes one contiguous delivery epoch. On local backlog overflow or native journal reset, the subscription updates its authoritative snapshot and cursor, discards stale queued deltas, and completes the old iterator (next() returns done:true, including an already-pending read). This also works when an oversized final settlement leaves no retained event, without later activity or cleanup. No synthetic reset event is inserted and the NativeEvent and subscription types are unchanged. After any iterator completion, reconcile subscription.snapshot at subscription.cursor. If the owner is still live and observation is still wanted, obtain another iterator from the same subscription.events; the old iterator stays done. Reset does not dispose the subscription or close the owner. Subsequent deltas are authentic and ordered; ignore events at or below an already-applied snapshot cursor. Explicit dispose() (idempotent) or breaking out of a live iterator ends observation, not the owner. Owner closure also ends delivery. Track your own disposal when deciding whether to resume. New subscriptions are refused once owner closing begins; existing subscriptions continue through cleanup/closure. Calling dispose() from onReconcile also stops the active drain from publishing its retained events. Pending and newly created iterators finish without those events; the reconciled snapshot remains available. The optional subscription.onReconcile callback is a convenience, not required for correctness; callback exceptions remain visible as subscription.failure. Raw strings and Error messages are preserved; unprintable values (including hostile conversion or revoked proxies) use Unprintable JavaScript rejection. Diagnostic conversion cannot interrupt event delivery or rearming the fallback poll. Native callbacks are wake hints; journal drains and reset snapshots are authoritative. Each live subscription has one fallback poll, stopped on disposal or observed closure. The native byte journal and facade delivery backlog are bounded. Each task separately retains its most recent 256 accepted activity report IDs, SHA-256 payload hashes and receipts (TASK_REPORT_IDENTITY_WINDOW). Within that window, identical payloads return duplicate with the original cursor; conflicting payloads return ReportConflict. Neither check emits events or refreshes retention order. An evicted ID is fresh: while the task is live it is accepted, applies its activity again and gets a new cursor; existing terminal and owner-close guards still apply. Terminal outcome reports and their recorded receipts are retained separately for the task record’s lifetime and never evicted by activity churn. This bounds identity entry count, not caller ID length, task count, terminal payloads or total task-history memory. S1 adds no persistence layer. Activity IDs have no reserved spellings, including runner-outcome, empty strings and isolated surrogates. The facade submits its own result through private trusted runner support: the actor selects a free terminal identity and accepts the outcome under the same lock. With at most 256 retained activity IDs, at most 257 distinct candidates suffice; selection emits no events and retains no extra ID history. Caller-supplied reports still use the unchanged reportTaskOutcome contract: same-ID cross-kind reports conflict, and terminal replay retains its original receipt. The internal support also reuses an accepted terminal identity, so a different result cannot replace it; cancellation-first still rejects late natural outcomes. Normal, rejected and setup-failure results all use this path without bypassing cleanup evidence. Caller-provided strings retain their exact JavaScript UTF-16 code units, including isolated surrogates, valid pairs and embedded NUL, across scopes, intent, operation/report identity, activity, results and nested output/cleanup metadata. They remain ordinary string fields, not encoded wrappers. Replacing a surrogate with U+FFFD is a changed payload or identity, never an exact replay. Nonempty descriptions supply the title; otherwise the first nonblank task line is copied without rewriting its code units, falling back to the agent name. Absent optional fields, empty strings, known zero metrics and ordered duplicate data remain distinct. The optional elapsedMs, toolCount and tokenCount metrics and completed/failed exitCode preserve JavaScript numbers without narrowing or normalization, including fractional and extreme values. Within the retained activity window (and for terminal reports throughout the task record’s lifetime), exact replay distinguishes omission, zero and negative zero; repeated NaN and infinite values acknowledge once. Changed numeric payloads return ReportConflict without earning another event. OutputRef is metadata, not proof of retained bytes: output storage, readTaskOutput, command input, persistence, completion delivery and real agent/Intercom integration belong to later slices. The credential-free repository fixture test/fixtures/task-s1-demo.ts exercises this real facade and native actor with one fake runner.

Task transcript references

An admitted runner can call context.bindTranscript(sessionManager) with its existing child session history. readTaskTranscript(task, cursor?) in core/tasks/transcript.ts reads that binding through the task capability. It returns message and content-block references, not copied text: id, kind, source, and toolCallId when applicable. Kinds are prompt, assistant, tool-call, tool-result, and response. Thinking blocks and non-conversation entries are excluded. Repeated source IDs are deduplicated; repeated messages with different IDs remain distinct. The first page contains up to 100 recent references in source order. Pass the opaque nextCursor to read earlier references; omittedEarlier identifies remaining older content. Cursors belong to one task and bound session. An unknown task returns UnknownTask, a cursor from another task/session returns ScopeMismatch, and an unbound or empty history returns TranscriptUnavailable with Transcript unavailable. This adapter does not launch work or reconstruct live capabilities from history. Production subagent runners bind their child history, and main and attached workflow chat hosts mount the shared inspector. Command detail reads are scoped to the current selection and view lifetime: late results and errors cannot overwrite another view.