Documentation Index Fetch the complete documentation index at: https://mintlify.com/earendil-works/pi/llms.txt
Use this file to discover all available pages before exploring further.
The SDK gives you programmatic access to Pi’s agent capabilities from within a Node.js application. Use it to build custom UIs, integrate agent reasoning into existing workflows, create automated pipelines, or spawn sub-agents from your own tools.
Quick start
Install the package:
npm install @earendil-works/pi-coding-agent
Then create a session and subscribe to events:
import { AuthStorage , createAgentSession , ModelRegistry , SessionManager } from "@earendil-works/pi-coding-agent" ;
const authStorage = AuthStorage . create ();
const modelRegistry = ModelRegistry . create ( authStorage );
const { session } = await createAgentSession ({
sessionManager: SessionManager . inMemory (),
authStorage ,
modelRegistry ,
});
session . subscribe (( event ) => {
if ( event . type === "message_update" && event . assistantMessageEvent . type === "text_delta" ) {
process . stdout . write ( event . assistantMessageEvent . delta );
}
});
await session . prompt ( "What files are in the current directory?" );
Core concepts
createAgentSession()
createAgentSession() is the main factory function. It creates a single AgentSession using a ResourceLoader to supply extensions, skills, prompt templates, themes, and context files. When you omit the resourceLoader option, it uses DefaultResourceLoader with standard discovery.
import { createAgentSession } from "@earendil-works/pi-coding-agent" ;
// Minimal: defaults with DefaultResourceLoader
const { session } = await createAgentSession ();
// With overrides
const { session } = await createAgentSession ({
model: myModel ,
tools: [ readTool , bashTool ],
sessionManager: SessionManager . inMemory (),
});
It returns a CreateAgentSessionResult:
interface CreateAgentSessionResult {
session : AgentSession ;
extensionsResult : LoadExtensionsResult ;
modelFallbackMessage ?: string ; // set when session model couldn't be restored
}
AgentSession interface
AgentSession manages agent lifecycle, message history, model state, compaction, and event streaming.
interface AgentSession {
// Send a prompt and wait for completion
prompt ( text : string , options ?: PromptOptions ) : Promise < void >;
// Queue messages during streaming
steer ( text : string ) : Promise < void >;
followUp ( text : string ) : Promise < void >;
// Subscribe to events (returns unsubscribe function)
subscribe ( listener : ( event : AgentSessionEvent ) => void ) : () => void ;
// Session info
sessionFile : string | undefined ;
sessionId : string ;
// Model control
setModel ( model : Model ) : Promise < void >;
setThinkingLevel ( level : ThinkingLevel ) : void ;
cycleModel () : Promise < ModelCycleResult | undefined >;
cycleThinkingLevel () : ThinkingLevel | undefined ;
// State access
agent : Agent ;
model : Model | undefined ;
thinkingLevel : ThinkingLevel ;
messages : AgentMessage [];
isStreaming : boolean ;
// In-place tree navigation
navigateTree ( targetId : string , options ?: {
summarize ?: boolean ;
customInstructions ?: string ;
replaceInstructions ?: boolean ;
label ?: string ;
}) : Promise <{ editorText ?: string ; cancelled : boolean }>;
// Compaction
compact ( customInstructions ?: string ) : Promise < CompactionResult >;
abortCompaction () : void ;
// Abort current operation
abort () : Promise < void >;
// Cleanup
dispose () : void ;
}
Session replacement operations — new session, resume, fork, and import — live on AgentSessionRuntime, not on AgentSession.
createAgentSessionRuntime()
Use createAgentSessionRuntime() when you need to replace the active session and rebuild cwd-bound runtime state. This is the layer used by interactive, print, and RPC modes.
import {
type CreateAgentSessionRuntimeFactory ,
createAgentSessionFromServices ,
createAgentSessionRuntime ,
createAgentSessionServices ,
getAgentDir ,
SessionManager ,
} from "@earendil-works/pi-coding-agent" ;
const createRuntime : CreateAgentSessionRuntimeFactory = async ({ cwd , sessionManager , sessionStartEvent }) => {
const services = await createAgentSessionServices ({ cwd });
return {
... ( await createAgentSessionFromServices ({ services , sessionManager , sessionStartEvent })),
services ,
diagnostics: services . diagnostics ,
};
};
const runtime = await createAgentSessionRuntime ( createRuntime , {
cwd: process . cwd (),
agentDir: getAgentDir (),
sessionManager: SessionManager . create ( process . cwd ()),
});
AgentSessionRuntime owns session replacement across newSession(), switchSession(), fork(), clone flows, and importFromJsonl(). After any replacement, runtime.session points to the new session — re-subscribe to events on the new instance.
let session = runtime . session ;
let unsubscribe = session . subscribe (() => {});
await runtime . newSession ();
unsubscribe ();
session = runtime . session ;
unsubscribe = session . subscribe (() => {});
Session management
import { SessionManager } from "@earendil-works/pi-coding-agent" ;
// No persistence
SessionManager . inMemory ()
// New persistent session
SessionManager . create ( process . cwd ())
// Continue most recent session
SessionManager . continueRecent ( process . cwd ())
// Open a specific file
SessionManager . open ( "/path/to/session.jsonl" )
// List sessions
const current = await SessionManager . list ( process . cwd ());
const all = await SessionManager . listAll ( process . cwd ());
Sessions use a tree structure with id/parentId linking, enabling in-place branching. const sm = SessionManager . open ( "/path/to/session.jsonl" );
// Tree traversal
const entries = sm . getEntries ();
const tree = sm . getTree ();
const path = sm . getPath ();
const leaf = sm . getLeafEntry ();
const entry = sm . getEntry ( id );
const children = sm . getChildren ( id );
// Labels
const label = sm . getLabel ( id );
sm . appendLabelChange ( id , "checkpoint" );
// Branching
sm . branch ( entryId );
sm . branchWithSummary ( id , "Summary..." );
sm . createBranchedSession ( leafId );
Runtime session replacement
// Start a fresh session
await runtime . newSession ();
// Switch to a saved session
await runtime . switchSession ( "/path/to/session.jsonl" );
// Fork from a specific user entry
await runtime . fork ( "entry-id" );
// Clone the active path at a specific entry
await runtime . fork ( "entry-id" , { position: "at" });
Model configuration
import { getModel } from "@earendil-works/pi-ai" ;
import { AuthStorage , ModelRegistry } from "@earendil-works/pi-coding-agent" ;
const authStorage = AuthStorage . create ();
const modelRegistry = ModelRegistry . create ( authStorage );
// Find a built-in model by provider and id
const opus = getModel ( "anthropic" , "claude-opus-4-5" );
// Find any model including custom models from models.json
const custom = modelRegistry . find ( "my-provider" , "my-model" );
// Get only models with valid API keys configured
const available = await modelRegistry . getAvailable ();
const { session } = await createAgentSession ({
model: opus ,
thinkingLevel: "medium" , // off, minimal, low, medium, high, xhigh
// Models available for cycling (e.g. Ctrl+P in interactive mode)
scopedModels: [
{ model: opus , thinkingLevel: "high" },
{ model: haiku , thinkingLevel: "off" },
],
authStorage ,
modelRegistry ,
});
When no model is provided, Pi tries to restore from session, falls back to the settings default, then falls back to the first available model.
API keys and OAuth
API key resolution priority (handled by AuthStorage):
Runtime overrides via setRuntimeApiKey() — not persisted to disk
Stored credentials in auth.json (API keys or OAuth tokens)
Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
Fallback resolver for custom provider keys from models.json
import { AuthStorage , ModelRegistry } from "@earendil-works/pi-coding-agent" ;
// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
const authStorage = AuthStorage . create ();
const modelRegistry = ModelRegistry . create ( authStorage );
// Runtime override — not persisted
authStorage . setRuntimeApiKey ( "anthropic" , "sk-my-temp-key" );
// Custom storage location
const customAuth = AuthStorage . create ( "/my/app/auth.json" );
const customRegistry = ModelRegistry . create ( customAuth , "/my/app/models.json" );
// Built-in models only, no models.json
const simpleRegistry = ModelRegistry . inMemory ( authStorage );
import {
codingTools , // [read, bash, edit, write] — default
readOnlyTools , // [read, grep, find, ls]
readTool , bashTool , editTool , writeTool ,
grepTool , findTool , lsTool ,
} from "@earendil-works/pi-coding-agent" ;
// Use a built-in tool set
const { session } = await createAgentSession ({ tools: readOnlyTools });
// Pick individual tools
const { session } = await createAgentSession ({ tools: [ readTool , bashTool , grepTool ] });
The pre-built tool instances (readTool, bashTool, etc.) use process.cwd() for path resolution. When you specify a custom cwd and provide explicit tools, use the factory functions instead:
import {
createCodingTools ,
createReadOnlyTools ,
createReadTool , createBashTool , createEditTool , createWriteTool ,
createGrepTool , createFindTool , createLsTool ,
} from "@earendil-works/pi-coding-agent" ;
const cwd = "/path/to/project" ;
const { session } = await createAgentSession ({
cwd ,
tools: createCodingTools ( cwd ),
});
// Or pick specific tools
const { session } = await createAgentSession ({
cwd ,
tools: [ createReadTool ( cwd ), createBashTool ( cwd ), createGrepTool ( cwd )],
});
If you omit tools, Pi automatically creates them with the correct cwd. You only need factory functions when you specify both a custom cwd and explicit tools.
import { Type } from "typebox" ;
import { createAgentSession , defineTool } from "@earendil-works/pi-coding-agent" ;
const myTool = defineTool ({
name: "my_tool" ,
label: "My Tool" ,
description: "Does something useful" ,
parameters: Type . Object ({
input: Type . String ({ description: "Input value" }),
}),
execute : async ( _toolCallId , params ) => ({
content: [{ type: "text" , text: `Result: ${ params . input } ` }],
details: {},
}),
});
const { session } = await createAgentSession ({
customTools: [ myTool ],
});
Custom tools passed via customTools are combined with tools registered by extensions via pi.registerTool().
Extensions
Extensions are loaded by the ResourceLoader. DefaultResourceLoader discovers extensions from ~/.pi/agent/extensions/, .pi/extensions/, and any extension sources in settings.json.
import { createAgentSession , DefaultResourceLoader } from "@earendil-works/pi-coding-agent" ;
const loader = new DefaultResourceLoader ({
additionalExtensionPaths: [ "/path/to/my-extension.ts" ],
extensionFactories: [
( pi ) => {
pi . on ( "agent_start" , () => {
console . log ( "[Extension] Agent starting" );
});
},
],
});
await loader . reload ();
const { session } = await createAgentSession ({ resourceLoader: loader });
To communicate with extensions from outside, pass a shared event bus:
import { createEventBus , DefaultResourceLoader } from "@earendil-works/pi-coding-agent" ;
const eventBus = createEventBus ();
const loader = new DefaultResourceLoader ({ eventBus });
await loader . reload ();
eventBus . on ( "my-extension:status" , ( data ) => console . log ( data ));
See extensions for the full extension API.
System prompt override
Use DefaultResourceLoader with systemPromptOverride to replace the system prompt:
import { createAgentSession , DefaultResourceLoader } from "@earendil-works/pi-coding-agent" ;
const loader = new DefaultResourceLoader ({
systemPromptOverride : () => "You are a helpful assistant." ,
});
await loader . reload ();
const { session } = await createAgentSession ({ resourceLoader: loader });
Events
Subscribe to AgentSession events to receive streaming output and lifecycle notifications.
session . subscribe (( event ) => {
switch ( event . type ) {
// Streaming text delta from the assistant
case "message_update" :
if ( event . assistantMessageEvent . type === "text_delta" ) {
process . stdout . write ( event . assistantMessageEvent . delta );
}
if ( event . assistantMessageEvent . type === "thinking_delta" ) {
// Thinking output (when thinking is enabled)
}
break ;
// Tool execution
case "tool_execution_start" :
console . log ( `Tool started: ${ event . toolName } ` );
break ;
case "tool_execution_update" :
// Streaming tool output
break ;
case "tool_execution_end" :
console . log ( `Tool finished: ${ event . isError ? "error" : "success" } ` );
break ;
// Message lifecycle
case "message_start" :
case "message_end" :
break ;
// Agent lifecycle
case "agent_start" :
break ;
case "agent_end" :
// event.messages contains new messages
break ;
// Turn lifecycle (one LLM response + tool calls)
case "turn_start" :
break ;
case "turn_end" :
// event.message: assistant response
// event.toolResults: tool results from this turn
break ;
// Queue, compaction, and retry
case "queue_update" :
console . log ( event . steering , event . followUp );
break ;
case "compaction_start" :
case "compaction_end" :
case "auto_retry_start" :
case "auto_retry_end" :
break ;
}
});
Full list of message_update delta types:
Delta type Description startMessage generation started text_startText content block started text_deltaText content chunk text_endText content block ended thinking_startThinking block started thinking_deltaThinking content chunk thinking_endThinking block ended toolcall_startTool call started toolcall_deltaTool call arguments chunk toolcall_endTool call ended (includes full toolCall object) doneMessage complete ("stop", "length", or "toolUse") errorError occurred ("aborted" or "error")
Run modes
The SDK exports three run mode utilities for building custom interfaces on top of createAgentSession().
InteractiveMode
runPrintMode
runRpcMode
Full TUI with editor, chat history, and all built-in commands: import {
type CreateAgentSessionRuntimeFactory ,
createAgentSessionFromServices ,
createAgentSessionRuntime ,
createAgentSessionServices ,
getAgentDir ,
InteractiveMode ,
SessionManager ,
} from "@earendil-works/pi-coding-agent" ;
const createRuntime : CreateAgentSessionRuntimeFactory = async ({ cwd , sessionManager , sessionStartEvent }) => {
const services = await createAgentSessionServices ({ cwd });
return {
... ( await createAgentSessionFromServices ({ services , sessionManager , sessionStartEvent })),
services ,
diagnostics: services . diagnostics ,
};
};
const runtime = await createAgentSessionRuntime ( createRuntime , {
cwd: process . cwd (),
agentDir: getAgentDir (),
sessionManager: SessionManager . create ( process . cwd ()),
});
const mode = new InteractiveMode ( runtime , {
migratedProviders: [],
modelFallbackMessage: undefined ,
initialMessage: "Hello" ,
initialImages: [],
initialMessages: [],
});
await mode . run ();
Single-shot mode: send prompts, output result, then exit: import {
type CreateAgentSessionRuntimeFactory ,
createAgentSessionFromServices ,
createAgentSessionRuntime ,
createAgentSessionServices ,
getAgentDir ,
runPrintMode ,
SessionManager ,
} from "@earendil-works/pi-coding-agent" ;
const createRuntime : CreateAgentSessionRuntimeFactory = async ({ cwd , sessionManager , sessionStartEvent }) => {
const services = await createAgentSessionServices ({ cwd });
return {
... ( await createAgentSessionFromServices ({ services , sessionManager , sessionStartEvent })),
services ,
diagnostics: services . diagnostics ,
};
};
const runtime = await createAgentSessionRuntime ( createRuntime , {
cwd: process . cwd (),
agentDir: getAgentDir (),
sessionManager: SessionManager . create ( process . cwd ()),
});
await runPrintMode ( runtime , {
mode: "text" ,
initialMessage: "Hello" ,
initialImages: [],
messages: [ "Follow up" ],
});
JSON-RPC mode for subprocess integration: import {
type CreateAgentSessionRuntimeFactory ,
createAgentSessionFromServices ,
createAgentSessionRuntime ,
createAgentSessionServices ,
getAgentDir ,
runRpcMode ,
SessionManager ,
} from "@earendil-works/pi-coding-agent" ;
const createRuntime : CreateAgentSessionRuntimeFactory = async ({ cwd , sessionManager , sessionStartEvent }) => {
const services = await createAgentSessionServices ({ cwd });
return {
... ( await createAgentSessionFromServices ({ services , sessionManager , sessionStartEvent })),
services ,
diagnostics: services . diagnostics ,
};
};
const runtime = await createAgentSessionRuntime ( createRuntime , {
cwd: process . cwd (),
agentDir: getAgentDir (),
sessionManager: SessionManager . create ( process . cwd ()),
});
await runRpcMode ( runtime );
See RPC mode for the JSON protocol.
SDK vs RPC mode
Use the SDK when
You want type safety and IDE autocomplete
You’re in the same Node.js process
You need direct access to agent state and messages
You want to customize tools or extensions programmatically
Use RPC mode when
You’re integrating from another language
You want process isolation
You’re building a language-agnostic client
Full exports
// Factory
createAgentSession
createAgentSessionRuntime
AgentSessionRuntime
// Auth and models
AuthStorage
ModelRegistry
// Resource loading
DefaultResourceLoader
type ResourceLoader
createEventBus
// Helpers
defineTool
// Session management
SessionManager
SettingsManager
// Built-in tools (use process.cwd())
codingTools
readOnlyTools
readTool, bashTool, editTool, writeTool
grepTool, findTool, lsTool
// Tool factories (for custom cwd)
createCodingTools
createReadOnlyTools
createReadTool, createBashTool, createEditTool, createWriteTool
createGrepTool, createFindTool, createLsTool
// Types
type CreateAgentSessionOptions
type CreateAgentSessionResult
type ExtensionFactory
type ExtensionAPI
type ToolDefinition
type Skill
type PromptTemplate
type Tool