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.

Structured decisions

Use generateStructuredOutput() for one schema-validated semantic decision without starting an agent session. Pass optional exact model and fallbackModels IDs, currentModel, modelRegistry, instructions, state, and schema. Omit model to start with the current chat model; the current chat model is also the terminal fallback. Atomic resolves each ID through the supplied registry: a chat model uses chat inference, and a registered classifier uses the generic classify operation. A catalog entry does not prove that the provider can serve the request. Classifiers run only when the schema is a finite Choice and are otherwise skipped. General calls derive choice questions from that schema and never read routerModel. Workflow-stage and subagent model: "auto" selection use routerModel internally; no separate routing-decision API is exported. See Structured decisions.

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.
  • builtins selects shipped packages independently of tools: keys are workflows, subagents, mcp, web-access, and intercom. Omitted, {}, and omitted keys enable packages. A false value removes that package’s extensions and resources, including on reload; true enables it.
  • tools accepts an allowlist of coding, extension, and custom tool names or * patterns. tools: [] exposes none, including Intercom. A list containing only +name/-name entries instead changes the resolved defaults, for example tools: ["+codemode", "-write"]. Modifiers take exact names, cannot be mixed with plain entries, and apply to reloaded defaults too. Invalid lists throw. Extension/custom tools and MCP exposure retain their default behavior with modifiers.
  • excludedTools removes matching names from the registry and active selection. Exclusions win over tools; unknown names are ignored.
  • noTools: "all" exposes no tools, even with a nonempty tools allowlist or modifiers. It does not remove package resources or authorize services.
  • noTools: "builtin" suppresses coding-tool defaults when tools is omitted, keeping extension/custom tools except exclusions. An explicit tools list still wins over this mode.
  • Configured defaultTools selects initial coding tools when tools and noTools are omitted. Plain names replace the defaults; modifier-only lists such as ["-bash", "+ls"] change the defaults, and project modifier-only lists layer over the global selection. Extension/custom tools retain their own activation behavior. See defaultTools.
Workflow and subagent children inherit these restrictions. Child allowlists intersect with the parent selection, including during model fallback. A child cannot restore a parent-disabled package or excluded tool. See child configuration for model/auth, host callback and working-directory inheritance.

Checking tool status

session.getToolStatus() explains a session’s tools: where the tool selection came from, every registered tool with its exposure, owner, and active state, why each inactive tool is off and how to turn it on, selected names that an exclusion removed, and selected names that nothing registered, with a suggestion when a name looks like a typo. formatToolStatus() renders the report as plain text.
Each entry in tools has name, active, exposure, source, sourcePath, summary, and, for inactive tools, inactiveReason. missing and excluded list { name, reason } entries; a missing name usually means a typo or an extension that failed to load. Pass { heading, dim, warning } functions as the second argument to formatToolStatus() to add terminal colors. Interactive mode shows the same report with /tools, and RPC clients request it with get_tools.

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. Completed bash commands also return structuredContent shaped as { output, truncated, full_output_path?, exit_code, wall_time_seconds }, including nonzero exits. Programmatic callers receive up to 1 MiB of captured output, plus an omission marker when truncated; larger output keeps its head and tail and supplies a path to the full output. This is a captured-output limit, not a strict serialized JSON size limit. Model-facing text keeps its ordinary truncation limits. Running/background observations keep their existing task lifecycle and result format.

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. 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 a plain-name tools allowlist, include each custom or extension tool name you want enabled, for example tools: ["read", "bash", "my_tool"]. Modifier-only lists retain default activation and can enable inactive registered tools with +name. 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. Register the factory when a caller needs a schema-validated decision inferred by a selected model:
The calling model does not supply the decision fields. It calls structured_output with:
  • instructions: the judgment to make.
  • state: a nonempty named object containing the task and relevant context. Do not put secrets here.
  • model: optional exact provider/model ID of a chat model or a registered classifier. Omit it to use the current stage or session chat model.
  • fallbackModels: optional ordered exact IDs tried after the primary.
The tool infers the result through the session model registry. A successful call returns the schema-validated value as pretty-printed JSON, stores that value in details, and sets terminate: true. It does not capture the calling model’s arguments as the decision. A classifier is used only when the registered schema is a finite Choice; otherwise that candidate is skipped and the next fallback, then the current chat model, is tried. A classifier failure such as missing credentials, an unavailable provider, an unsupported classify operation, a refusal, or a malformed answer advances the same chain. A chat provider refusal advances immediately when reported as an error; plain text without a result gets up to three repairs before advancing. Cancellation stops the call. Saved /login credentials are used through the session model registry; a listed model does not prove live access. Each call must finish within a wall-clock deadline that covers every repair attempt, retry, and model fallback. It defaults to the session’s httpIdleTimeoutMs setting; pass timeoutMs (a positive integer in milliseconds) to createStructuredOutputTool() to change it. The calling model cannot. See Structured decisions. A custom tool name still works. Include that name in any explicit tools allowlist:
Custom tool names are supported, and the prompt metadata follows the configured name. The examples above use final_decision and the default structured_output name. Include the name you register in any explicit tools allowlist.
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 the resources from a supplied loader and adds Atomic’s shipped builtin extensions and resources. Repeated references to a shipped builtin load it once. Your loader’s arrays and discovery options are not rewritten. Existing tool selection and collision rules still apply. Named extensionFactories descriptors may set builtin: true to expose the factory as a synthetic builtin:<name> resource, and replaceable: true to omit it when another extension claims the same tool, command, or flag. Use additionalExtensionPaths: ["builtin:codemode"] for explicit built-in loading, including with noExtensions: true. These inline resource controls are separate from the companion-package builtins map. See built-in extension resources. Every DefaultResourceLoader includes Atomic’s inline built-in extensions, codemode, tool-search, and llama.cpp, so defaultTools: ["+codemode"] or tools: ["+tool_search"] works in SDK sessions without extra factories. loader.getExtensions() lists them with builtin:<name> paths. Registering them does not activate codemode or tool_search; both stay opt-in. A descriptor you pass with the same name replaces the default one, and noExtensions: true, disabledBuiltinExtensions: ["codemode"], or an extensions setting such as "-builtin:codemode" turns one off. Pass extensionBindings to createAgentSession() to install uiContext, mode, commandContextActions, shutdownHandler or onError before startup hooks run. Creation awaits startup and resource discovery. A failed startup shuts down the partially created session before rejecting. Rebinding updates the host without replaying session_start; reload emits one start for its new generation. Remove manual post-creation startup calls from integrations that only used them to initialize extensions. 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. Use await session.dispose() rather than fire-and-forget cleanup. It seals admission immediately and waits for owned work, extension shutdown, persistence and lease release. Concurrent disposal calls return the same promise. Catch ShutdownFailed to inspect its component errors; a rejected close is not successful cleanup, and the session stays closed. Do not add a manual MCP, Intercom or workflow teardown sequence. Caller-supplied settings, storage and model runtimes remain caller-owned. AgentSessionRuntime uses the same awaited close when replacing sessions and transfers configured host bindings before the replacement starts. Reload invalidates old input requests and extension subscriptions while retaining host configuration. Live durable workflows retain their existing runtime ownership across reload and session replacement; final owner disposal waits for their shutdown. A late answer from an old generation cannot authorize replacement work. Settings persistence failures can make disposal reject. Repair storage and repeat the write before closing; disposal does not consume the caller’s SettingsManager.drainErrors() channel. Duplicate executeBash() IDs remain correlation IDs, and abortBash(id) cancels every active call with that ID. Ensure callbacks settle independently of disposal. For reload, replacement, and resource ownership guidance, see SDK lifecycle. 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.create(), open(), continueRecent(), forkFrom(), and mutations are asynchronous. SessionManager.inMemory() returns an explicitly memory-backed manager synchronously. Await a mutation before reading its result or starting dependent work. A resolved write is committed; a failed write rejects with the store’s typed error and leaves the failed entry unpublished. New persistent sessions store their header during creation and commit setup entries immediately. There is no flush method. Reads remain synchronous. 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.
Package workflows returned by DefaultResourceLoader are available through the SDK session’s workflow tool, even when the factory adds the shipped workflows extension. Use the tool’s reload action to pick up added or removed package workflows without recreating the session. Project packages still require project trust; workflow reload does not grant it. Custom ResourceLoader implementations can optionally provide getWorkflowResources() returning ResolvedResource[] and refreshWorkflowResources() returning Promise<ResolvedResource[]>. Refresh should return the current trust-filtered resources. If refresh is omitted, workflow reload uses the getter’s current snapshot. If both are omitted, shipped workflows and normal workflow-directory discovery remain available. Package MCP servers from DefaultResourceLoader remain available through the session’s mcp tool when the factory adds the shipped MCP extension. Custom loaders can provide getMcpServerContributions() returning McpServerContribution[]; return only contributions allowed by your loader’s trust policy. Normal MCP configuration overrides still apply. See MCP servers for connection and tool invocation.

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)

For trusted custom hosts, the package exports AgentTaskHost and its integration types, not the raw native supervisor. Bind the actual host scope and a mandatory authorizeLaunch guard. Never accept ownership or authorization from model-supplied input or reconstruct a lease from a task ID or saved history. startAgentTask(intent, operation, runnerFactory) returns a Result containing {taskId, lease} after setup. The factory receives the original AbortSignal, reference, and reportActivity context. Return separate result and cleanup promises; report cleanup only when it is confirmed. Replaying the same operation reuses execution without calling another factory. Use observeAgentLaunch(taskId, policy?), waitForTask, resolveTask, cancelTask, watchOwnerTasks, and close within the bound owner. Observation returns Result/WaitOutcome. Yielding or cancelling an observation does not stop execution. Cancellation and cleanup failures remain observable; owner close may remain pending until your runner acknowledges cleanup.

Supervised command SDK

For shell integration, use the exported Bash and PowerShell factories with a trusted taskOwner binding. Execution timeout and observation budget are separate. The automatic command wait is normally 10000 ms; until-settled waits for completion. A yielded command remains owned, and later waits can read retained output with explicit omission markers. Without a supported owner, explicit background execution is refused. Native Windows legacy WSL bash.exe is not supported for owned commands; run Atomic inside WSL instead. See waiting for shell tasks and Background tasks for caller usage.

Task transcript references

Task inspectors show references to the bound child conversation, excluding thinking and non-conversation entries. An unbound or empty history reports Transcript unavailable. Saved history is not authority to restart or control a task.