Skip to main content
Atomic can help you use the SDK. Ask it to build an integration for your use case.

SDK

The SDK provides programmatic access to atomic’s agent capabilities. Use it to embed atomic in other applications, build custom interfaces, or integrate with automated workflows. Example use cases:
  • Build a custom UI (web, desktop, mobile)
  • Integrate agent capabilities into existing applications
  • Create automated pipelines with agent reasoning
  • Build custom tools that spawn sub-agents
  • Test agent behavior programmatically
See examples/sdk/ for working examples from minimal to full control.

Quick Start

ModelRuntime is the canonical asynchronous provider runtime when an integration wants provider-owned credentials, dynamic catalogs, and native providers in one object:
ModelRuntime.create() accepts custom authPath, modelsPath, credential storage, and runtime auth overrides. ModelRegistry and AuthStorage remain available as Atomic’s synchronous compatibility facades. Use readStoredCredential(provider, authPath?) for a lightweight read of one stored provider credential. Extensions supplied directly to SDK sessions can use the exported InlineExtension type. Extension APIs and event types include native registerProvider(Provider), registerEntryRenderer, entry_appended, before_provider_headers, and agent_settled. The package root also exports buildContextEntries, sessionEntryToContextMessages, and CompactionEntry for converting durable session branches into model context. The equivalent active-session operation is sessionManager.buildContextEntries().

Installation

Install @bastani/atomic as a project dependency with npm, pnpm, or Bun: With npm:
With pnpm:
With Bun:
Atomic does not require package install scripts. If you want to disable dependency lifecycle scripts during the Atomic install, you can add --ignore-scripts to the install command. The SDK is included in the main package. No separate SDK package is needed.

Core Concepts

createAgentSession()

The main factory function for a single AgentSession. createAgentSession() uses a ResourceLoader to supply extensions, skills, prompt templates, themes, and context files. If you do not provide one, it uses DefaultResourceLoader with standard discovery.

AgentSession

The session manages agent lifecycle, message history, model state, compaction, and event streaming.
compact() serializes older context to numbered lines, asks the session model for JSON deleted ranges, validates them, and mechanically reconstructs a durable verbatim transcript string. It appends a compaction entry with details.strategy: "verbatim-lines"; the recent tail remains ordinary messages. The model never authors replacement context text. Session replacement APIs such as new-session, resume, fork, and import live on AgentSessionRuntime, not on AgentSession.

createAgentSessionRuntime() and AgentSessionRuntime

Use the runtime API when you need to replace the active session and rebuild cwd-bound runtime state. This is the same layer used by the built-in interactive, print, and RPC modes. createAgentSessionRuntime() takes a runtime factory plus the initial cwd/session target. The factory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and returns a full runtime result.
AgentSessionRuntime owns replacement of the active runtime across:
  • newSession()
  • switchSession()
  • fork()
  • clone flows via fork(entryId, { position: "at" })
  • importFromJsonl()
Important behavior:
  • runtime.session changes after those operations
  • event subscriptions are attached to a specific AgentSession, so re-subscribe after replacement
  • if you use extensions, call runtime.session.bindExtensions(...) again for the new session
  • creation returns diagnostics on runtime.diagnostics
  • if runtime creation or replacement fails, the method throws and the caller decides how to handle it

Prompting and Message Queueing

PromptOptions controls prompt expansion, queueing behavior while streaming, and prompt preflight notifications:
preflightResult is called once per prompt() invocation:
  • true when the prompt was accepted, queued, or handled immediately
  • false when prompt preflight rejected before acceptance
It fires before prompt() resolves. prompt() still resolves only after the full accepted run finishes, including retries. Failures after acceptance are reported through the normal event and message stream, not through preflightResult(false). The prompt() method handles prompt templates, extension commands, and message sending:
Behavior:
  • Extension commands (e.g., /mycommand): Execute immediately, even during streaming. They manage their own LLM interaction via pi.sendMessage().
  • File-based prompt templates (from .md files): Expanded to their content before sending or queueing.
  • During streaming without streamingBehavior: Throws an error. Use steer() or followUp() directly, or specify the option.
  • preflightResult(true): Means the prompt was accepted, queued, or handled immediately.
  • preflightResult(false): Means preflight rejected before acceptance.
For explicit queueing during streaming:
Both steer() and followUp() expand file-based prompt templates but error on extension commands (extension commands cannot be queued).

Agent and AgentState

The Agent class (from @earendil-works/pi-agent-core) handles the core LLM interaction. Access it via session.agent.

Events

Subscribe to events to receive streaming output and lifecycle notifications.

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.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 file (AGENTS.md)
  • 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 SDK and extension compatibility, while catalog refresh is asynchronous. Await modelRegistry.refresh() before reading getAll(), find(), or getAvailable() when a provider may update its catalog. The refresh result reports aborted and per-provider errors; successful providers publish their new catalogs even if another provider fails, and failed or timed-out 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
Context-window selection is independent from thinkingLevel. contextWindow accepts a raw token count such as 400_000 or 1_000_000; for most providers the value must be present in the model’s supported context windows (model.contextWindowOptions plus the scalar default). GitHub Copilot is the only provider with rounded long-context budget handling: when a tiered Copilot model advertises a long tier below the branded request (for example 936_000 for a 1_000_000 request), Atomic selects the largest advertised Copilot long tier at or below the request instead of falling back to the short tier. Settings lookup first checks the selected model’s defaultContextWindows["provider/modelId"] entry, then the optional global defaultContextWindow fallback; unsupported model-specific settings keep the model default and return contextWindowWarning, while unsupported global fallback values are ignored silently as not applicable to the active model. When you pass contextWindowStrict: true, an unsupported explicit selection is reported as contextWindowError so callers can fail before prompting. A successful explicit contextWindow startup option is journaled as a context_window_change entry even when it equals the scalar model default, so the user’s explicit budget choice survives future settings changes and resume. At runtime, use session.getAvailableContextWindows() to inspect supported values, session.supportsContextWindowSelection() to check whether more than one value is selectable, and session.setContextWindow(tokens, { persistDefault }) to change the active model budget. setContextWindow() journals a context_window_change entry only when the active value changes. Passing { persistDefault: true } also writes the effective selected budget to defaultContextWindows["provider/modelId"] in settings instead of the global fallback, so a Copilot prompt cap such as 936k does not leak into Anthropic, Cursor, or other providers. Tree navigation replays the target branch’s context_window_change state into the active model without adding another journal entry or changing settings. Larger provider context windows may consume more credits/cost, so opt into larger values deliberately. For catalog-advertised GitHub Copilot long-context models (including github-copilot/gpt-5.5, github-copilot/claude-sonnet-5, and github-copilot/gemini-3.1-pro-preview), selecting 1m raises Atomic’s local budget to the model’s advertised 922k/936k tier and sends X-GitHub-Api-Version: 2026-06-01; GitHub applies the long-context tier server-side by prompt token count, consumes more Copilot AI credits, and requires long-context/usage-based billing entitlement. The package root exports the same context-window helpers and types used by the runtime: parseContextWindowValue(), formatContextWindow(), validateContextWindowValue(), normalizeContextWindowOptions(), getModelDefaultContextWindow(), getSupportedContextWindows(), withContextWindowOptions(), selectContextWindow(), ContextWindowParseResult, ContextWindowSelection, ContextWindowSelectionError, and ContextWindowSelectionOptions. Importing from @bastani/atomic also includes the @earendil-works/pi-ai Model<Api> augmentation for contextWindowOptions and defaultContextWindow, so SDK consumers can use the helper types without importing internal source paths.
See examples/sdk/02-custom-model.ts

API Keys and OAuth

AuthStorage and ModelRegistry remain synchronous public SDK entry points. Token refresh is serialized under Atomic’s credential-store lock, and the authStorage and modelRegistry session options remain available. API key resolution priority (handled by AuthStorage):
  1. Runtime overrides (via setRuntimeApiKey, not persisted)
  2. Stored credentials in auth.json (API keys or OAuth tokens)
  3. Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
  4. Fallback resolver (for custom provider keys from models.json)
OAuth credentials may provide an API key, request headers, and a credential-specific baseUrl. Atomic applies all three to the request. In particular, GitHub Copilot enterprise and token-specific endpoints replace the static model URL without dropping retries, attribution headers, fast mode, or extension request hooks.
See examples/sdk/09-api-keys-and-oauth.ts

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, 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.
  • excludedTools is a blocklist: matching built-in, extension, and custom tool names are omitted from the final registry and active tool set. If both are provided, tools is applied first and excludedTools subtracts from it.
  • noTools: "all" disables all tools
  • 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.

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.

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.
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:

Complete Example

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.

RPC Mode Alternative

For subprocess-based integration without building with the SDK, use the CLI directly:
See RPC documentation for the JSON protocol. The SDK is preferred when:
  • You want type safety
  • You’re in the same Node.js process
  • You need direct access to agent state
  • You want to customize tools/extensions programmatically
RPC mode is preferred when:
  • You’re integrating from another language
  • You want process isolation
  • You’re building a language-agnostic client

Exports

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