Search for a command to run...
Compiled from 38 nodes · est. 84 min read
Updated
Prime Agent is an open-source, terminal-native AI coding and research agent built by PrimeIntellect-ai for general and long-running work, released under the MIT License. It was forked from pi-mono on 2026-05-08 and is now developed and distributed independently as the prime-agent CLI, installable via a custom R2-hosted tarball installer. Two architectural facts explain almost everything else: a persistent IPython kernel is the sole built-in tool substrate (with %%bash cells and Python skills replacing the legacy bash and edit built-ins), and a background daemon hosts multiple concurrent agent sessions in isolated worker processes that survive terminal disconnects.
Setup and installation covers first-run orientation, the monorepo build, the self-update protocol, and version-by-version upgrade notes — start here if you're installing or upgrading. Session runtime is the core reference for programmatic use: createAgentSession, AgentSessionRuntime, AgentSessionServices, kernel bootstrap, system prompt assembly, and the Settings schema. Tools, Skills and compaction, and Extensions document what the agent can do and how to extend it — the IPython tool, Bash tool, and Edit tool implementations; the Skills and Compaction subsystems; and the extension loader, UI context, and lifecycle API. Daemon architecture, Autonomous and ACP modes, and TUI and interactive use cover the run-time surfaces — the JSONL daemon protocol and mode implementation, autonomous mode with RLM subagents and long-running agents, ACP mode over NDJSON stdio, and the interactive TUI's sessions, navigation, and message queue. Providers and authentication and Testing and harness round out the tree with model-provider setup and the Test harness plus Continual Harness and refine machinery used for evaluation and self-improvement.
If you came here to install and run prime-agent for the first time, read Orientation and installation in the Setup and installation section, then Providers and authentication to log in to a model. If you came here to understand the architecture end-to-end, read Daemon architecture first for the client/supervisor/worker split, then Session runtime for what runs inside a worker, then Autonomous and ACP modes for how long-running and external-harness sessions are driven. If you came here to add a feature — a new tool, skill, or UI affordance — read Extensions for the loader and lifecycle, then the relevant page under Tools or Skills and compaction for the surface you're extending. If you came here to debug behavior or write tests, read Test harness and Continual Harness and refine under Testing and harness, and consult Upgrading under Setup and installation when old code or session artifacts reference removed names like RLMResult, the bare /resume flag, or the .pi/ config directory.
Updated
Pages in this section:
Updated
Prime Agent is an open-source coding and research agent for general and long-running work, released under the MIT License.[1] Prime Agent began as a hard fork of pi-mono (https://github.com/badlogic/pi-mono) but is now developed and distributed independently.[2]
Prime Agent is installable on macOS or Linux via a curl-based installer: curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh.[1] The installer downloads a versioned release, verifies its SHA-256 checksum, installs the prime-agent command, and can prepare the IPython runtime used by the agent — details live on Installation and self-update.[1] After npm install, packages/coding-agent runs node postinstall.cjs via the postinstall lifecycle hook.[3]
Prime Agent must be started from the repository or directory it should work in; it runs commands and modifies files in the current working directory.[1] Prime Agent executes model-generated Python and project commands with the user's own permissions; its worker and kernel processes improve lifecycle isolation and recovery but are not a security sandbox.[1]
packages/coding-agent/package.json declares piConfig.name = "prime-agent" and piConfig.configDir = ".prime/agent", setting the agent's display name and configuration directory.[3] The packages/coding-agent binary is registered as pi, pointing to the bundled CLI entry point at dist/bundle/cli.js.[3] By default, Prime Agent gives the model one built-in tool — ipython — which uses a persistent kernel to read files, run commands, edit code, and inspect data; the tool is covered in depth on IPython tool.[2] Sessions are stored as JSONL files with a tree structure; each entry carries an id and parentId, enabling in-place branching without creating new files — session management is detailed on Sessions and navigation.[2] Prime Agent stores sessions as JSONL (JSON Lines) files, where each line is a self-contained JSON object, enabling individual entries to be appended or read without parsing the entire file. Prime Agent's persistent IPython kernel preserves Python environment state — variables, imports, and context — across tool calls within a session, eliminating the need to re-execute earlier steps.
Sources
Updated
Prime Agent is a monorepo with a root build pipeline (tui → ai → agent → coding-agent), a published npm package under @earendil-works/pi-coding-agent that releases as prime-agent, and a complete CI/test harness spanning type-check, linting, sharded vitest runs, and kernel integration tests. The workspace retains legacy @earendil-works/pi-* identifiers and pi bin entry for internal compatibility; packages/coding-agent's build produces both TypeScript-compiled output and a bundled CLI, with optional clipboard access and runtime assets bundled into dist/.
The prime-agent monorepo workspace root is a private npm module-type package named prime-agent, with workspaces spanning all packages/* directories plus four specific extension example packages under packages/coding-agent/examples/extensions/.[1] Both the root and packages/coding-agent require Node.js >=22.8.0 as the minimum engine version.[1][2] packages/coding-agent is published under the npm scope @earendil-works/pi-coding-agent, retaining the legacy earendil-works scope from the upstream pi-mono fork; public releases rewrite the package and command to prime-agent.[2][3] The workspace retains legacy @earendil-works/pi-* source package identifiers, the pi package manifest key, and a pi bin entry for internal compatibility — the inherited npm package should NOT be used as the install path.[3]
The root build script compiles packages in strict dependency order: tui → ai → agent → coding-agent.[1] The dev script runs the ai, agent, coding-agent, and tui packages concurrently via concurrently, each in watch mode.[1] packages/coding-agent's build produces both a TypeScript-compiled dist/ (via tsgo) and a bundled CLI (via scripts/bundle.mjs); the build:binary variant additionally compiles a self-contained Bun binary at dist/pi.[2] The copy-assets step in packages/coding-agent bundles interactive theme JSON, PNG assets, export-HTML templates, the prime-agent-runtime directory, and the skills/ directory into dist/.[2] tsgo is a high-performance TypeScript compiler toolchain used in the Prime Agent build pipeline for both compiling source to dist/ and type-checking via --noEmit.
The root check script runs Biome linting (with --write --error-on-warnings), TypeScript type-checking via tsgo --noEmit, and two custom smoke checks (check:installer and check:browser-smoke).[1] Versioning scripts (version:patch, version:minor, version:major) bump versions across all workspaces with npm version -ws, then run scripts/sync-versions.js, and perform a full node_modules and lock-file clean reinstall.[1] Release scripts (release:patch, release:minor, release:major) delegate to scripts/release.mjs with the bump level; release:pack delegates to scripts/pack-prime-agent-release.mjs.[1] shell-quote is pinned to ^1.10.0 in the root package.json via an override, likely as a security constraint.[1] Clipboard access is declared as an optional dependency (@mariozechner/clipboard) in packages/coding-agent, so it degrades gracefully when unavailable.[2] Prime Agent uses a changelog-fragment workflow: each PR must include a Markdown file under packages/<pkg>/.changes/ (e.g., packages/coding-agent/.changes/<id>.md); at release time, scripts/release.mjs and scripts/lib/changelog-fragments.mjs assemble these fragments into the final CHANGELOG.md.
CI runs on ubuntu-latest with Node.js 22 and cancels concurrent runs for the same branch via the concurrency group ci-${{ github.ref }} with cancel-in-progress: true.[4] System dependencies installed for CI builds include libcairo2-dev, libpango1.0-dev, libjpeg-dev, libgif-dev, librsvg2-dev, fd-find, and ripgrep (with fdfind symlinked to fd).[4] The build-check CI job runs npm run build followed by npm run check (type-check and lint) with a 15-minute timeout.[4] The coding-agent test suite is split into three shards (--shard=1/3, 2/3, 3/3) plus a process smoke test and a kernel test, all requiring uv to be installed.[4] CI installs uv via python3 -m pip install --user uv and appends $HOME/.local/bin to PATH for test jobs that require it.[4] A gate job, build-check-test, always runs after build-check and test and verifies both succeeded, providing a single required-status check for branch protection.[4]
CI tests in packages/coding-agent run with vitest --run; the test:ci variant first bootstraps the kernel via tsx src/core/kernel/bootstrap-cli.ts and excludes test/daemon-supervisor-process.test.ts — see Test harness for harness internals.[2] Heavy kernel integration tests are scoped with --tagsFilter kernel-heavy and target acp-kernel-features.test.ts, acp-cold-cli.test.ts, kernel-goal-skill.test.ts, and kernel-state-roundtrip.test.ts.[2]
Sources
Updated
Prime Agent's installation and self-update system detects how the package was installed — whether via Bun binary, Homebrew, or a package manager — by inspecting runtime signatures and filesystem paths, then routes self-update requests appropriately or reports when updates are unavailable. Self-update detection checks path writability and global package-manager scope; Bun binaries and Homebrew installs signal unavailability and return a manual GitHub download URL, while package-manager installs route through npm, yarn, or pnpm update commands.
packages/coding-agent/src/config.ts detects whether the process is running as a Bun compiled binary by checking if import.meta.url contains "$bunfs", "~BUN", or "%7EBUN" — Bun's virtual filesystem path indicators.[1] Whether Bun is the runtime at all (compiled binary or bun run) is detected separately via !!process.versions.bun, exported as isBunRuntime.[1] The InstallMethod type enumerates seven values: "bun-binary", "homebrew", "npm", "pnpm", "yarn", "bun", and "unknown".[1] detectInstallMethod() resolves the active method in priority order: "bun-binary" first (when isBunBinary is true), then Homebrew, then inspection of __dirname and process.execPath for package-manager path fragments (/pnpm/, /yarn/, /npm/, etc.).[1] A Homebrew install is identified by the package directory containing both "/cellar/" and "/libexec/lib/node_modules/" in its path.[1] install.sh and package.json comply with npm 12's remote dependency policy, which rejects installation patterns that earlier npm versions permitted. scripts/check-installer.mjs validates install.sh for npm 12 compatibility and is integrated into the build-binaries.yml CI workflow; changes to install.sh that fail this check will break binary builds.
getSelfUpdateCommand() returns undefined — signalling that self-update is unavailable — when any of three conditions hold: the detected install method carries no update command, the package is not under the global package manager's root, or the install path is not writable.[1] Both "bun-binary" and "homebrew" install methods return undefined from getSelfUpdateCommandForMethod(), as neither supports a programmatic self-update via a package manager command.[1] For a "bun-binary" install, getSelfUpdateUnavailableInstruction() returns a manual download URL: https://github.com/PrimeIntellect-ai/prime-agent/releases/latest.[1] On Windows, isManagedByGlobalPackageManager() does not infer the npm global prefix from path shape alone, because Windows global npm uses <prefix>\node_modules — indistinguishable from a local project install without npm root -g evidence.[1] When bun is configured as the npm command, the global package root is derived from bun pm bin -g output and the default path ~/.bun/install/global/node_modules.[1] When getSelfUpdateCommand() returns undefined, getSelfUpdateUnavailableInstruction() provides a human-readable fallback message containing a manual download URL so users can update without a package manager command.
The environment variable PRIME_AGENT_INTERACTIVE_SELF_UPDATE (exported as SELF_UPDATE_INTERACTIVE_CHILD_ENV) is the sentinel used to mark an interactive self-update child process.[1] Exit code 75 (SELF_UPDATE_NOT_ATTEMPTED_EXIT_CODE) signals that a self-update was not attempted.[1] When the update spec is a direct artifact — a URL, file: path, .tgz, or .tar.gz — the self-update logic installs the new package first and then uninstalls the old one (uninstallAfterInstall: true), reversing the usual order to avoid a gap in availability.[1]
Sources
Updated
Prime Agent has undergone major breaking changes across versions: v0.7.0 standardized message delivery, v0.6.0 restructured agent messaging to role-addressed delivery and narrowed agent reach to the nuclear family, and earlier versions removed legacy APIs like positional send() and built-in bash tools. Each release bumped daemon schema versions and protocol numbers, cleanly rejecting older clients and daemons at connect; upgrading requires adapting code to the new API surfaces for messaging, spawning subagents, and session resumption.
In v0.7.0, agent messages were changed to always use steering delivery and all delivery-mode options were removed from the Python, CLI, RPC, and connection APIs. Code passing mode to agent_message.send, or a delivery mode over the CLI/RPC, must drop it.[1]
In v0.6.0, rlm(...) was changed to return at task admission instead of waiting for the child to finish. It now yields a spawn handle (rlm_child_id, name, session_dir, model); RLMResult and its final answer, usage, and model-fallback warning are removed. A child now reports back with agent_message.send(..., receiver_role="parent"). Code that read result.answer, or used asyncio.gather(...) over rlm(...) as fan-in, must be updated.[2] Also in v0.6.0, agent_message.send was changed to role-addressed delivery: callers must pass receiver_role ("parent", "sibling", "child") plus receiver_name for siblings and children. The old positional send(target, message) form no longer works, and the separate roster() call is now agent_message.list_agents().[2] Agent reach was narrowed in v0.6.0 to the nuclear family: an agent may message or observe only its parent, siblings, and direct children. Top-level sessions are siblings of one another; grandchildren and cousins must be reached by relaying through the intermediate child.[2] Requesting an unavailable subagent model now fails the spawn in v0.6.0 instead of silently falling back to the parent's model with a warning.[2] The daemon schema revision was bumped to 13 in v0.6.0 for parent-edge, depth, naming, and passivation wire changes; older clients and daemons are rejected cleanly at connect.[2]
In v0.5.0, session input scheduling was reworked into a single session action lifecycle and store (daemon protocol 7, schema revision 8); older clients and daemons are rejected cleanly at connect.[3]
In v0.4.0, the recursive daemon get_session_tree response was replaced with flat nodes linked by parentId (protocol 6); clients must support the new response shape.[4] Also in v0.4.0, /resume and bare --resume were removed. Sessions can be browsed with left-arrow from a daemon chat, or resumed directly with --resume <session-id|path>.[4]
In v0.2.6, the legacy pi-mono bash and edit built-in tools were removed; IPython %%bash cells and the Python edit skill replace them.[5] In v0.1.0, the interactive ! and !! bash shortcuts were removed from interactive mode; IPython should be used for shell commands instead.[6]
Sources
github.com…llect-ai/prime-agent/releases/tag/v0.7.0github.com…llect-ai/prime-agent/releases/tag/v0.6.0github.com…llect-ai/prime-agent/releases/tag/v0.5.0github.com…llect-ai/prime-agent/releases/tag/v0.4.0github.com…llect-ai/prime-agent/releases/tag/v0.2.6github.com…llect-ai/prime-agent/releases/tag/v0.1.0Updated
Pages in this section:
Updated
AgentSessionRuntime owns an AgentSession and its cwd-scoped services; the createAgentSession() SDK entry point from @earendil-works/pi-coding-agent orchestrates resource loading, model selection with fallback, tool setup, and thinking-level clamping to build a ready-to-use session. Session replacement methods rebuild the runtime from scratch when working directory or configuration changes; callers must re-subscribe to runtime.session after any replacement, and streaming sessions require explicit streamingBehavior to accept new prompts.
packages/coding-agent/src/core/index.ts is the barrel export for all core shared modules used across run modes, including AgentSession, AgentSessionRuntime, AgentSessionServices, extension types, EventBus, bash executor, compaction, RLM subagent runtime, and session utilities.[1] packages/coding-agent/src/core/sdk.ts is the primary programmatic entry point for creating agent sessions, exporting createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, and related types.[2] The packages/coding-agent package exposes two public exports: the root . (index) and ./hooks (hooks module), both shipping types and ES module builds from the dist/ directory.[3] The SDK is distributed under the npm package name @earendil-works/pi-coding-agent, installed via npm install @earendil-works/pi-coding-agent.[4]
createAgentSession() is the primary SDK entry point in @earendil-works/pi-coding-agent; with no arguments it discovers skills, extensions, tools, and context files from cwd and ~/.pi/agent, and chooses the model from settings or the first available provider.[5] createAgentSession() uses a ResourceLoader to supply extensions, skills, prompt templates, themes, and context files; if none is provided it defaults to DefaultResourceLoader with standard discovery, calling reload() immediately so resources are available before the session is built.[4][2] createAgentSession attempts to restore the model from an existing session's stored provider/modelId; if that model is unavailable or unconfigured, it sets modelFallbackMessage and falls back to findInitialModel.[2] The thinkingLevel option defaults to the value stored in the existing session (if any), then to the settings default, then to the DEFAULT_THINKING_LEVEL constant; it is clamped to the chosen model's actual capabilities via clampThinkingLevel, so requesting 'high' on a model that supports only 'medium' silently downgrades.[2] When no model is available at all, createAgentSession sets thinkingLevel to "off" rather than throwing.[2] The serviceTier option automatically downgrades from 'priority' to 'default' when the selected model does not support fast mode (checked via supportsFastMode).[2] The noTools option accepts "all" (no tools enabled) or "builtin" (disable only the default built-in ipython tool, keeping extension/custom tools); when neither tools nor noTools is specified, initialActiveToolNames defaults to ["ipython"], making IPython the sole initially active tool.[2] When settingsManager.getBlockImages() is enabled, createAgentSession replaces all ImageContent items in messages with a { type: 'text', text: 'Image reading is disabled.' } placeholder at the convertToLlm wrapper level — checked dynamically so mid-session setting changes take effect.[2]
CreateAgentSessionResult carries three fields: session (the AgentSession), extensionsResult (for UI context setup), and an optional modelFallbackMessage warning string emitted when the session was restored with a different model than saved.[2] packages/coding-agent/src/core/sdk.ts re-exports the tool factory functions createBashTool, createEditTool, createIpythonTool, and withFileMutationQueue for callers who need custom cwd-scoped tool instances.[2]
AgentSessionRuntime owns the current AgentSession plus its cwd-bound services; session replacement methods tear down the current runtime first, then create and apply the next — if creation fails, the error is propagated to the caller, which is responsible for user-facing error handling.[6] CreateAgentSessionRuntimeFactory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and finally creates the AgentSession. The factory pattern enables the runtime to rebuild state when the working directory changes.[6][4] CreateAgentSessionRuntimeResult extends CreateAgentSessionResult and adds services: AgentSessionServices and diagnostics: AgentSessionRuntimeDiagnostic[].[6] AgentSessionRuntimeMetadata records the runtime's kind ("top-level" or "subagent"), creation timestamp, parent session references, RLM child/parent node IDs, rehydration state, initial prompt, spawn code, and session directory.[6]
The teardownCurrent method flushes agent trace uploads, runs beforeSessionInvalidate, awaits the kernel's final snapshot flush via session.disposeAsync(), and disposes all hosted subagent runtimes — in that order.[6] If building a replacement AgentSession runtime throws, AgentSessionRuntime releases the uncommitted session lease before re-throwing, preventing lease leaks on failed transitions.[6] AgentSessionRuntime.setRuntimeEnvScope installs a host-provided async scope that wraps every runtime rebuild (new/switch/fork/import and subagent creation), during which extensions re-load; the daemon uses it to apply the session's client env for load-time captures.[6] AgentSessionRuntime.setBeforeSessionInvalidate installs a synchronous callback that runs after session_shutdown handlers finish but before the current session is invalidated — used for host-owned TUI teardown that must not yield to the event loop.[6] A session lease is a lock that prevents two runtimes from writing to the same session file simultaneously.
createAgentSessionRuntime() creation failures throw; the caller is responsible for handling them, and diagnostics are exposed on runtime.diagnostics.[4]
Calling session.prompt() during streaming without specifying streamingBehavior throws an error; callers must use steer(), followUp(), or pass streamingBehavior: "steer" | "followUp" in PromptOptions.[4] PromptOptions.preflightResult is a callback called once per prompt() invocation — true when the prompt was accepted, queued, or handled; false when rejected before acceptance. Failures after acceptance are reported through the normal event and message stream, not through preflightResult(false).[4] AgentSession.steer() queues a steering message for delivery after the current assistant turn finishes its tool calls; followUp() queues a message that is only delivered when the agent fully stops.[4] AgentSession.navigateTree() performs in-place tree navigation within the current session file, accepting a targetId and options for summarize, customInstructions, replaceInstructions, and label; it returns { editorText?, cancelled }. Session navigation is covered further on the Sessions and navigation page.[4] AgentSession.agent.state.messages and agent.state.tools support direct array replacement (copies the top-level array), enabling use cases like branching or tool set swapping.[4] Agent.continue in packages/agent/src/agent.ts returns typed result codes instead of throwing or returning generic errors when preconditions are unmet (e.g., compaction required, session not continuable), letting call sites branch on the specific failure reason.
After calling runtime.newSession() (or any session-replacing operation), callers must unsubscribe from the old AgentSession, retrieve runtime.session again, and re-subscribe to the new one.[4]
packages/coding-agent/src/index.ts is the package's top-level barrel file, re-exporting the entire public API surface: session management, auth, compaction, extension system, skills, tools, daemon protocol, interactive mode, UI components, and theme utilities.[7] AgentSession and its associated event/config types (AgentSessionConfig, AgentSessionEvent, AgentSessionEventListener, ModelCycleResult, PromptOptions) are exported from ./core/agent-session.js, forming the core session lifecycle surface.[7] The compaction API — compact, shouldCompact, generateBranchSummary, findCutPoint, calculateContextTokens, estimateTokens, and related types — is exported from ./core/compaction/index.js. Compaction behaviour is covered on the Compaction page.[7] AuthStorage and related credential types (ApiKeyCredential, OAuthCredential, AuthStatus) plus storage backend classes (FileAuthStorageBackend, InMemoryAuthStorageBackend) are exported from ./core/auth-storage.js. Authentication configuration is covered on the Providers and authentication page.[7] getAgentDir and VERSION are exported from ./config.js, providing the config-path and package-version entry points for the coding-agent package.[7] The extension system surface includes createExtensionRuntime, discoverAndLoadExtensions, ExtensionRunner, defineTool, wrapRegisteredTool, and type-guard helpers isBashToolResult, isEditToolResult, isIpythonToolResult, isToolCallEventType, all exported from ./core/extensions/index.js.[7]
Minimal SDK usage: session.subscribe(callback) streams events; session.prompt(text) runs a turn; session.state.messages holds the conversation history after each turn:
import { createAgentSession } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession();
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?");
session.state.messages.forEach((msg) => console.log(msg));
Example: continuing a previous session with createAgentSession and handling a model fallback warning.
// Continue previous session
const { session, modelFallbackMessage } = await createAgentSession({
continueSession: true,
});
Example: full-control createAgentSession usage with an explicit DefaultResourceLoader, restricted tool list, and in-memory session manager.
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
settingsManager: SettingsManager.create(),
});
await loader.reload();
const { session } = await createAgentSession({
model: myModel,
tools: ["ipython"],
resourceLoader: loader,
sessionManager: SessionManager.inMemory(),
});
The canonical minimal SDK setup — creating AuthStorage and ModelRegistry, calling createAgentSession() with an in-memory SessionManager, subscribing to message_update events, and calling session.prompt():
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?");
Sources
Updated
AgentSessionServices (in packages/coding-agent/src/core/agent-session-services.ts) is the coherent set of cwd-bound runtime services — auth, settings, model registry, resource loader, and MCP manager — created for one effective session working directory; it is infrastructure only, and the AgentSession itself is created separately.[1] The two-phase split — createAgentSessionServices followed by createAgentSessionFromServices — is intentional: it lets callers resolve model, thinking, tools, and other session inputs against the services before committing to session creation.[1] rlmDepth is the nesting depth of the current agent within a recursive agent chain; a value of 0 indicates the agent is the top-level, non-subagent session.
AgentSessionRuntimeDiagnostic carries non-fatal issues with type "info" | "warning" | "error" and a message string; diagnostics are returned to the caller rather than printed or causing process exit, leaving the app layer to decide whether to show warnings or abort startup.[1] When telemetry is enabled and the telemetry notice has not yet been shown, createAgentSessionServices pushes an "info" diagnostic explaining what is collected and how to disable it (via telemetry.enabled=false, PRIME_AGENT_TELEMETRY=0, DO_NOT_TRACK=1, or offline mode), then marks the notice as shown.[1] Passing an unknown extension flag name produces an "error" diagnostic listing the unknown --flag names; passing a string-typed flag without a string value produces a separate "error" diagnostic stating the flag requires a value.[1] Extension provider registrations stored in extensionsResult.runtime.pendingProviderRegistrations are applied at service-creation time by calling modelRegistry.registerProvider; any registration error is surfaced as an "error" diagnostic, and the pending list is cleared afterwards.[1]
createAgentSessionServices defaults agentDir to getAgentDir(), authStorage to AuthStorage.create(join(agentDir, "auth.json")), and modelRegistry to ModelRegistry.create(authStorage, join(agentDir, "models.json")) when those options are omitted.[1] The McpManager is initialized with authStorage and a getter for user MCP servers from settingsManager; when modelRegistry's OAuth providers are reset, it automatically re-registers user MCP providers via mcpManager.registerUserProviders().[1]
The built-in Herdr reporter is suppressed when noBuiltinHerdrReporter is set or resourceLoaderOptions.noExtensions is true; this covers RLM subagent runtimes, which inherit the parent's HERDR_* pane identity — two reporters on the same pane would race each other, and a subagent quit would release the pane while the parent is still running (see RLM subagents).[1] When the built-in Herdr reporter is active, it defers to Herdr's file-based integration if that file was actually loaded; deferral is late-bound to the loader's loaded extension paths so that a file which exists but is disabled or never discovered does not silence the built-in reporter.[1]
AgentSessionCreationOptions.noTools accepts "all" to suppress all tools or "builtin" to suppress only built-in tools for a session.[1] AgentSessionCreationOptions.initialGoal seeds a goal at session creation; it is applied only at rlmDepth 0 and the operation is idempotent.[1] AgentSessionCreationOptions.serviceTier sets the provider service tier; fast mode uses "priority".[1] createAgentSessionFromServices installs agent trace upload via installAgentTraceUpload before delegating to createAgentSession from sdk.js.[1]
agent-traces.ts maintains a disk-cursor outbox: trace payloads are appended to a local log at emission time, and a background cursor advances through the log as uploads succeed — ensuring traces survive session close, crashes, and daemon restarts. The agent trace upload outbox is wired into the session teardown path via agent-session-runtime.ts, agent-session.ts, in-process-agent-connection.ts, daemon-mode.ts, and interactive-mode.ts. Telemetry that must survive crashes and daemon restarts should extend the outbox pattern in agent-traces.ts rather than implementing ad-hoc upload scheduling.
Sources
Updated
System prompt assembly in Prime Agent is orchestrated by a single buildSystemPrompt function that merges a base prompt (either the RLM template or a custom one) with injected context—subagent guidance, active tools, skills, harness state, project files, and guidelines—in a strict order that ensures the model reads delegation rules before concrete specs and receives consistent cross-platform paths. The assembly process adapts its output based on what tools are enabled: IPython defaults when unspecified, skills inject only when file access exists, subagent guidance only when recursion is allowed, and each section's formatting is normalized to prevent duplication and embedding errors.
buildSystemPrompt in packages/coding-agent/src/core/system-prompt.ts is the single function that assembles the full system prompt, merging the base RLM prompt, subagent guidance, harness state, project context files, skills, additional guidelines, and an optional append section.[1] When no customPrompt is provided, buildSystemPrompt calls buildRlmPrompt as the base and then appends subagent guidance, harness state, additional guidelines, project context files, and the skills section in that order.[1] When a customPrompt is provided, buildSystemPrompt uses it as the base and still appends context files, skills, current date, working directory, child agent doctrine, harness state, and the append section — preserving full context injection.[1] The RLM prompt in packages/coding-agent/src/core/prompts/rlm.ts is the canonical source for RLM subagent constraints; it instructs the model to perform long-running tasks in a nonblocking manner, surface progress visibly, and clarify the scope of each task. The RLM prompt in packages/coding-agent/src/core/prompts/rlm.ts omits any reference to an async bash() kernel helper, as that function is not available in the kernel runtime; including it would cause the model to attempt calls that silently fail. The RLM prompt in packages/coding-agent/src/core/prompts/rlm.ts explicitly discourages use of subprocess.run, directing the model toward the agent's bash() tool instead, to prevent Python subprocess calls that bypass tool-call traceability and sandboxing. Tool-preference guidance in rlm.ts is order- and phrasing-sensitive; reordering sections or rewording tool instructions risks re-introducing model drift toward uncontrolled execution patterns.
buildSystemPrompt defaults selectedTools to ["ipython"] when none are provided, making IPython the only assumed tool.[1] All backslashes in cwd and messagesPath are normalized to forward slashes before being embedded in the system prompt, ensuring cross-platform consistency; messagesPath defaults to the string "not persisted" when the option is absent.[1] buildSystemPrompt embeds the current date (formatted YYYY-MM-DD) into custom prompts explicitly; the base buildRlmPrompt handles date injection for the default path.[1]
Subagent guidance (buildSubagentGuidance) is injected into the default prompt only when both allowRecursion is true (default) and ipython is an active tool.[1] Subagent guidance is positioned after the base RLM prompt prefix and before the harness-state menu, so the model reads when and why to delegate before seeing the concrete subagent specs it can match against — the same ordering convention used in Claude Code's Agent tool.[1] The skills section is appended (on both the custom and default paths) only when the model has file access — i.e., when at least one of ipython or bash is in the active tool list.[1] Skills tagged with disableModelInvocation are filtered out before the visible skills list is built and are never referenced in the system prompt.[1] BuildSystemPromptOptions.harnessState accepts a HarnessState that is serialized via formatHarnessStateForPrompt and appended as compact persistent context; IPython, bash, and refine example variants are included based on which tools and skills are active.[1]
BuildSystemPromptOptions exposes rlmDepth (fixed recursive-agent depth), rlmParentAgent (human-readable parent name for child doctrine), and allowRecursion (whether to include RLM recursion guidance) as first-class fields.[1] formatPromptGuidelines deduplicates guidelines by normalized (trimmed) text and emits each unique line as a bullet (- guideline), preventing duplicate instructions when callers supply overlapping guidelines.[1]
Sources
Updated
The kernel bootstrapper in bootstrap.ts manages the IPython kernel's Python environment—pinning Python 3.11, validating cached state via a schema version file, and pre-installing mandatory packages (ipykernel, prime-agent-runtime, dill) plus 12 optional RLM packages. Bootstrap operations are guarded by file-level locking and in-flight deduplication to prevent concurrent races, with optional forkserver acceleration (v0.2.4+) that spawns subagent kernels from a pre-imported template rather than cold booting each time.
The kernel bootstrapper lives in packages/coding-agent/src/core/kernel/bootstrap.ts and manages the IPython kernel's Python environment, pinning the managed Python version to 3.11 via the PYTHON_VERSION constant.[1] A BOOTSTRAP_SCHEMA = 8 constant tracks the bootstrap schema version; any change that invalidates a cached bootstrap environment must bump this constant.[1] The bootstrap version is persisted in a .bootstrap-version file alongside the environment, allowing the bootstrapper to detect and invalidate stale environments without running the full install.[1] The BootstrapVersion interface records schema number, ipykernel version, runtime version, snapshot (dill) version, extraUvArgs, and pythonSkills with their pyprojectHash; a hash mismatch forces a reinstall.[1]
Three Python packages are mandatory in every kernel environment: ipykernel, prime-agent-runtime, and dill.[1] dill is used exclusively for serializing the kernel's user namespace so it can be revived across session resume; it is intentionally not surfaced to the model as an importable package.[1]
The RLM kernel environment pre-installs 12 Python packages by default, defined in DEFAULT_RLM_EXTRA_PACKAGES: requests, httpx, pyyaml (imported as yaml), tomli, python-dotenv (imported as dotenv), pandas, numpy, scipy, beautifulsoup4 (imported as bs4), lxml, pydantic, and tyro.[1] Three derived arrays are exported from the same file — DEFAULT_RLM_EXTRA_UV_ARGS, DEFAULT_RLM_EXTRA_IMPORT_NAMES, and DEFAULT_RLM_EXTRA_IMPORT_LABELS — used for uv installation, import validation, and prompt generation respectively.[1]
uv is the Python package manager used by the bootstrapper; if it is absent, the fallback install command is curl -LsSf https://astral.sh/uv/install.sh | sh.[1]
The runtime readiness check asserts that rlm.harness and rlm.rlm.harness each expose all 13 required harness methods: create_memory, update_memory, delete_memory, create_skill, update_skill, delete_skill, create_subagent, update_subagent, delete_subagent, create_prompt_note, update_prompt_note, delete_prompt_note, and record_refinement.[1] The same check (RUNTIME_READY_CHECK) asserts that rlm does NOT have a background attribute (assert not hasattr(rlm, 'background')), enforcing that the removed background API is absent.[1] Additionally, the readiness check validates that HarnessEntry has reference and scope dataclass fields, that create_skill and update_skill accept a reference parameter, and that create_memory and get_harness_state accept a global_ parameter.[1]
Bootstrap concurrency is guarded by a file-level lock (BOOTSTRAP_LOCK_NAME = ".bootstrap.lock") with a 100 ms retry interval and a 30-second stale-lock timeout when the owning PID is absent.[1] An in-flight deduplication guard (inFlightEnsureKernelPython) ensures that concurrent callers sharing the same cache key share a single bootstrap promise rather than racing.[1] In v0.2.4, concurrent kernel boots during large subagent fan-outs were bounded by a process-wide semaphore with a default of min(16, 2*cores), overridable via PRIME_AGENT_MAX_CONCURRENT_KERNEL_BOOTS.[2] A parent-watchdog mechanism — spanning fork-server-script.ts, fork-server.ts, and kernel/index.ts — monitors the owning process and self-terminates the kernel when that owner dies, preventing orphaned kernel processes.
The EnsureKernelPythonOptions interface accepts an optional pythonSkills array (of KernelPythonSkill) and an optional onProgress callback of type KernelBootstrapProgressHandler for reporting bootstrap progress messages.[1] A host request contract layer in packages/coding-agent/src/core/kernel/index.ts establishes typed contracts governing how requests from the host environment flow into the kernel, validated via host-request-contract.test.ts and host-request-context.ts. In kernel/index.ts, ZMQ EAGAIN errors raised when the kernel socket is accessed before the kernel has fully started are suppressed, preventing those raw errors from leaking to callers. Corrupt REPL protocol frames are detected and repaired in repl-manager.ts and repl.py rather than propagated, preventing silent data corruption during IPython tool execution. The host-reply envelope in kernel communication is owned exclusively by the dispatcher; reply routing through repl-manager.ts, bootstrap.ts, agent-session.ts, and repl.py must use the dispatcher-owned envelope path.
In v0.2.3, the IPython kernel was changed to stay alive across compaction: variables, imports, and helpers the agent defined are no longer wiped, and the model is instead told which names remain defined.[3] In v0.2.4, a Python forkserver was added (on by default on Linux; opt out with PRIME_AGENT_KERNEL_FORKSERVER=0) that forks subagent kernels from one pre-imported template process instead of a full cold boot each time, with automatic fallback to direct spawn on any failure.[2]
Sources
Updated
Settings in Prime Agent is a hierarchical schema defined by the Settings interface in settings-manager.ts, stored as JSON at global (<agentDir>/settings.json) and project (<cwd>/.prime-agent/settings.json) levels, covering provider defaults, compaction, retry logic, terminal rendering, image handling, MCP servers, and package loading. SettingsManager provides a file-backed storage abstraction (SettingsStorage) with lazy lock-only-on-write semantics, tracks dirty fields per session for selective flush, and supplies defaults for compaction (enabled, 16k reserve tokens), retry (3 max retries, 2s base delay), and terminal behavior (fullscreen mode, image display).
settings-manager.ts defines the Settings interface, which is the central configuration schema for prime-agent; its fields cover provider/model defaults, compaction, retry, terminal rendering, image handling, MCP servers, skill/extension loading, and more.[1] Global settings are stored at <agentDir>/settings.json and project settings at <cwd>/.prime-agent/settings.json, as established by FileSettingsStorage in settings-manager.ts.[1] FileSettingsStorage.withLock() only creates the settings directory and acquires the lock when a write is actually needed; reading a non-existent file returns undefined without touching the filesystem.[1]
The SettingsStorage interface exposes a single withLock(scope, fn) method; fn receives the current raw JSON string (or undefined if absent) and returns the new JSON string to write, or undefined to skip writing.[1] InMemorySettingsStorage provides a lock-free in-memory implementation of SettingsStorage, intended for tests that do not need file I/O.[1] SettingsManager tracks which global settings fields and nested subfields were modified during the current session using a modifiedFields: Set<keyof Settings> and modifiedNestedFields: Map<keyof Settings, Set<string>>, enabling selective dirty-flushing.[1]
recentModels in Settings stores at most 20 "provider/id" keys, most-recently-used first, controlled by RECENT_MODELS_LIMIT = 20 in settings-manager.ts.[1] DEFAULT_IDLE_EVICTION_MINUTES in settings-manager.ts is 90; this is the global daemon default for how long a session tree can be idle before the supervisor stops its worker process, overridable via idleEvictionMinutes in settings (or set to "off").[1] Settings.treeFilterMode controls which messages appear in the session tree; the default is "user-only", with other values being "default", "no-tools", "labeled-only", and "all".[1]
The CompactionSettings interface defaults: enabled to true, reserveTokens to 16384, keepRecentTokens to 20000, and agentCallable to true (which exposes the compact skill so the model can request compaction itself).[1] The AutoRefineSettings interface defaults: enabled to true, turnInterval to 25 assistant turns, compact to true, and cooldownMs to 20 minutes.[1] The BranchSummarySettings interface defaults skipPrompt to false (when true, the "Summarize branch?" prompt is skipped and no summary is produced by default) and reserveTokens to 16384.[1] Compaction reduces a session's token count by summarizing or pruning older messages to keep the session within a model's context window; without it, long sessions eventually exceed the limit and fail.
The RetrySettings interface defaults: enabled to true, maxRetries to 3, and baseDelayMs to 2000 (exponential backoff produces 2 s, 4 s, and 8 s delays); the nested ProviderRetrySettings.maxRetryDelayMs defaults to 60000 ms, which is the maximum server-requested delay before failing.[1]
The TerminalSettings interface defaults: showImages to true, clearOnShrink to false, showTerminalProgress to false, fullscreen to true (alternate-screen rendering), and fullscreenMouse to true (wheel scrolling; disable if it breaks text selection).[1] The ImageSettings interface defaults autoResize to true (images are resized to a 2000×2000 maximum for model compatibility) and blockImages to false; setting blockImages: true prevents all images from being sent to LLM providers.[1]
The PackageSource type allows either a plain string (load all resources from a package) or an object with a source field plus optional extensions, skills, prompts, and themes arrays to filter which resources are loaded.[1] Settings.npmCommand lets users override the npm command used for package lookup and install operations as an argv-style array (e.g. ["mise", "exec", "node@20", "--", "npm"]), which is useful for Cygwin or version-manager setups.[1] Settings.shellCommandPrefix is prepended to every bash command executed by the agent (e.g. "shopt -s expand_aliases" to enable alias support inside the kernel shell).[1]
Sources
Updated
Pages in this section:
Updated
In v0.6.0, --mode acp was added, running prime-agent as an Agent Client Protocol agent over NDJSON on stdio, driving an AgentConnection in-process rather than shelling out to RPC mode.[1] ACP mode intentionally avoids a translating adapter because prime-agent's differentiators — IPython-only tools, subagents, and autonomous gates — would be flattened away by such an adapter; instead, these appear as first-class ACP events.[2] Capabilities ACP has no native concept for (e.g. autonomous-gate state, subagents, heartbeats, compaction, goals, rich IPython output) travel in a reverse-domain _meta envelope keyed "ai.primeintellect.prime-agent", which vanilla ACP clients safely ignore; nothing non-standard is added to an ACP object root.[2][3] NDJSON (Newline-Delimited JSON) is a transport format in which each JSON value occupies exactly one newline-terminated line, allowing a stream reader to parse complete messages incrementally without buffering the entire stream. A kernel-owned MCP runtime (packages/coding-agent/src/core/kernel/) runs MCP servers inside the kernel process, with server lifecycles tied to that kernel.
One JSON-RPC message is written per line on stdout; requests are read from stdin, and diagnostics go to stderr — nothing else may be written to stdout, which belongs entirely to the protocol.[3] At startup, ACP mode calls takeOverStdout(), which redirects process.stdout.write to stderr so stray logging cannot corrupt the NDJSON stream; ACP frames are then written via writeRawStdout(), the same raw escape hatch used by RPC mode.[2] The rawStdoutSink() function returns a WritableStream<Uint8Array> that routes all bytes through writeRawStdout, bypassing the stdout takeover that would otherwise misdirect the ACP stream to stderr.[2] The default transport is acp.ndJsonStream(rawStdoutSink(), Readable.toWeb(process.stdin)) — NDJSON framing over stdin/stdout — used when no stream override is provided in AcpModeOptions.[2]
runAcpMode is the top-level entry point; it instantiates an InProcessAgentConnection around the provided AgentSessionRuntime and delegates to runAcpModeWithConnection.[2] The AcpModeOptions interface accepts a stream override (replaces the default NDJSON-over-stdio transport) and an ownStdout flag (skips claiming stdout when the caller supplies its own transport), both used by tests to run the protocol in-memory without a subprocess — see Testing and harness.[2] agent-connection/snapshot.ts supports capturing resident session state as a snapshot of an active AgentConnection.
ACP mode supports five methods: initialize (returns protocol version, capabilities, and agent info), session/new (creates the session), session/prompt (runs one turn and resolves with a stop reason), session/cancel (notification; aborts the addressed session's turn), and session/close (releases the session and frees the connection for a new one).[3] ACP mode advertises image: true and embeddedContext: true prompt capabilities and sessionCapabilities: { close: {} } in its initialize response, so clients can release the single-session slot by closing rather than dropping the connection.[2] session/prompt resolves with one of four stop reasons: end_turn (normal), cancelled (session/cancel called), max_tokens (autonomous token budget exhausted), or max_turn_requests (autonomous turn, continuation, or wall-clock limit hit). Autonomous quality gates run inside a single prompt turn and do not produce an intermediate stop reason.[3] In ACP mode, IPython is the model-facing tool: a cell execution appears as a tool_call of kind execute whose rawInput carries the cell source.[3] ACP mode supports declaring MCP programs in its configuration; the agent resolves them through the MCP manager and surfaces the resulting tools to the system prompt. ACP mode gates prompt dispatch on a turn-admission signal from agent-session.ts, coordinated through daemon-protocol.ts, preventing a race condition where a prompt could be answered before the session was ready to accept the next turn. openai-completions.ts preserves the reasoning_details field on Chat-format responses during replay, maintaining the model's reasoning chain across multi-turn conversations. acp-events.ts preserves assistant message boundaries when converting ACP events into the internal message representation, preventing consecutive assistant messages from being collapsed or merged incorrectly.
ACP mode maintains a single-session invariant: one ACP connection drives exactly one AgentConnection, and a second session/new request is refused rather than silently sharing conversation state, cwd, and queues. For a second concurrent session, start another process.[2][3] session/prompt refuses a concurrent turn while one is already running, and the working directory cannot be changed after startup — a client-supplied cwd that differs from the agent's real one is reported back in _meta rather than silently ignored.[3]
promptContent() splits ACP prompt blocks into plain text and images: text blocks are joined with newlines, image blocks become ImageContent objects, embedded resource blocks with .text are prepended with their URI and appended to the text corpus, and resource_link blocks contribute only their URI string.[2] Image and embedded-resource blocks must not be silently dropped: the client has been told via initialize that they are supported, so dropping them would let a client believe a pasted screenshot was accepted when it was not.[2]
The TurnBoundary mechanism tracks pre-turn transcript state using both object identity (a WeakSet) and a content key (a Set<string>) to survive auto-compaction, which can rebuild state.messages and re-materialize entries at lower indices, making a simple pre-turn message count unreliable — see Compaction.[2] The messageKey() function produces a stable identity string from [role, timestamp, stopReason, errorMessage]; because compaction drops messages without rewriting them, these fields are preserved on kept messages and the key survives compaction.[2] turnFailure() detects a failed turn by scanning the transcript in reverse for the newest assistant message added after the TurnBoundary; if that message has stopReason === "error" it returns errorMessage (falling back to "the model request failed"). Only post-turn messages are examined to avoid reporting stale errors from earlier turns.[2] In v0.6.1, ACP mode was fixed to report a failed turn as an error rather than a clean end_turn. Previously, a provider error, expired auth, or unusable model left session/prompt resolving with no updates at all, which appeared to clients as a successful but empty turn.[4] acp-mode.ts awaits terminal quiescence before signalling turn completion, preventing the next agent step from observing stale tool output.
sameCwd() compares two working-directory paths by canonicalizing them with realpathSync and then cross-checking dev+ino inode identity via statSync; the inode check is skipped when either dev or ino is zero, because a zero dev on Windows can produce false inode matches across different volumes.[2] normalizeWindowsDriveLetter() lowercases the drive letter of Windows paths (e.g. C: → c:) before path comparison, preventing false mismatches from mixed-case drive letters that Windows APIs can return inconsistently.[2] canonicalCwd() falls back to a lexical path when realpathSync throws (missing or inaccessible path), preserving the previous comparison behavior rather than crashing.[2]
autonomousMeta() returns undefined when autonomous mode is not enabled, and otherwise wraps autonomous status — continuations used, turns used, tokens used, gate attempt, and gate-failure exit text — in a primeAgentMeta envelope for the ACP _meta field.[2]
src/core/semantic-edges.ts contains a provenance producer that emits semantic-edges-v1 records during agent-session runtime, capturing compaction events, RLM calls, side-questions, and SDK interactions. agent-traces.ts is the single delivery path for both span traces and semantic-edges records; it owns the disk-cursor outbox and schedules uploads durably rather than fire-and-forget. Late compaction slices arriving after a session closes are settled before the outbox flushes; daemon-spawned subagents forward their parent lineage so the provenance graph remains fully connected. Provenance tracing is the mechanism by which agent-session runtime events — compaction, model calls, subagent interactions — are recorded as linked records, enabling a fully connected audit graph across sessions and subagents.
Sources
Updated
rlm(...) spawns real child agents for parallel or background work and returns their results programmatically from within the persistent IPython environment.[1] As of v0.2.3, subagents became first-class sessions: opening a subagent attaches to its own session and renders through the same rich chat UI as the main conversation instead of a laggy parent-rebuilt transcript; finished subagents stay viewable in the session list and sort below running ones.[2]
RlmRunRequest in packages/coding-agent/src/core/rlm-runtime.ts carries prompt, kwargs, and an optional cellSourceCode (the IPython cell that issued the rlm.run call) for display purposes.[3] RlmSpawnHandle — the value returned when a child agent is spawned — carries rlm_child_id, name, session_dir, and model, not a blocking result, reflecting fire-and-forget spawn semantics introduced in v0.6.0.[3] Subagent registry status in packages/coding-agent/src/core/rlm-runtime.ts is one of three string literals: "running", "completed", or "error".[3] RlmDeleteSubagentResult includes an optional outcome field with values "deleted" or "skipped_running", allowing callers to distinguish a successful deletion from a no-op due to the subagent still running.[3]
SubagentRuntimeHost in packages/coding-agent/src/core/rlm-runtime.ts is the interface the daemon or supervisor implements; only createRlmSubagentRuntime and deleteRlmSubagentRuntime are required — completeRlmSubagentRuntime, releaseRlmSubagentRuntime, and disposeRlmSubagentRuntimes are optional lifecycle hooks.[3] CreateRlmSubagentRuntimeOptions includes rlmDepth, rlmMaxDepth, and rlmParentNodeId, allowing the host to enforce recursion depth limits when spawning child agents.[3] CreateRlmSubagentRuntimeOptions.onSessionPublished is a callback that fires before the host makes the runtime addressable, allowing the parent session to be informed of the child session immediately after creation.[3] Live RLM child sessions (including grandchildren) are tracked under the children field of DaemonSessionSnapshot in packages/coding-agent/src/modes/daemon/daemon-protocol.ts, typed as AgentConnectionRlmChildAgentSnapshot[] — see Daemon protocol for the broader snapshot schema.[4] rlm-ledger.ts in packages/coding-agent/src/modes/daemon/ centralizes RLM spawn ledger ownership at the daemon supervisor layer (daemon-supervisor.ts), making it the single authoritative source of truth for tracking which RLM subagents belong to a given agent family. Centralizing the RLM spawn ledger to daemon-supervisor.ts via rlm-ledger.ts prevents races and split-brain state between sibling subagents that arose when ledger state was managed per-session in daemon-mode.ts. The consolidation of RLM subagent metadata onto the spawn ledger touched daemon-catalog-process.ts, daemon-mode.ts, rlm-ledger.ts, rlm-subagent-display.ts, test/rlm-ledger.test.ts, and test/daemon-mode.test.ts. When an RLM child agent is deleted, the daemon cleans up stale kernel state in agent-traces.ts and session-manager.ts and deduplicates artifact paths in session-file-actions.ts at write time. Invariants for RLM child-lifecycle teardown — kernel state cleanup and artifact path deduplication — are covered by session-artifacts-delete.test.ts and session-manager/artifact-paths.test.ts. The agents view (agents-view-mode.ts and agents-view-state.ts) surfaces the model identifier and effort setting for each RLM subagent alongside session info; agents-view-state.ts carries model and effort fields per agent entry. 502-unified-session-view.test.ts covers rendering of model and effort fields in the agents view. CreateRlmSubagentRuntimeOptions includes a thinkingLevel field that specifies the reasoning level for a spawned subagent, overriding the parent's reasoning level for that child. Reasoning level selection for RLM subagents is tested in 4649-subagent-model-selection.test.ts. Goal-continuation logic in agent-session.ts includes a quiescence check that defers goal continuations while any subagent work item remains in an unsettled state, preventing the orchestrator from racing ahead of outstanding subagent tasks. goal-continuation-quiescence.test.ts covers the hold-and-release behavior of goal continuations when subagent work items are unsettled. rlmMaxDepth defaults to 2, enforced in agent-session.ts and settings-manager.ts; deeper RLM recursion requires an explicit rlmMaxDepth override passed through CreateRlmSubagentRuntimeOptions. The prime-agent-runtime package provides a minimal CPython REPL implementation (rlm.repl) as the default execution host for RLM subagent sessions; the IPython tool layer remains available but is no longer the primary REPL host. RLM idle-detection in agent-session.ts is driven entirely by the activity-change event bus rather than a polling loop, eliminating periodic busy-waiting when no subagents are running; any new activity source must emit on that bus to be visible to the idle detector. Switching to activity-change event-based quiescence detection in agent-session.ts also fixes a post-compaction idle regression where the session could miss the quiescence signal after a compaction event. RLM child session snapshot projection logic is centralized in a single code path; agent-session.ts, daemon-mode.ts, and daemon-session-list.ts derive their views from the same source to prevent divergence bugs. daemon-session-list.test.ts and agent-session-recursion.test.ts cover the RLM child snapshot projection contract. RLM task-tree cancellation in agent-session.ts uses an iterative traversal with a visited set, guaranteeing each subagent node is cancelled exactly once and avoiding stack overflow or double-visits in deep or cyclic spawn graphs. agent-session-recursion.test.ts validates the cancellation contract for multi-level spawn graphs, asserting each subagent node is cancelled exactly once.
createRlmRunHostHandler validates that payload.prompt is a string and coerces a missing or non-object payload.kwargs to {}; it is the adapter between the kernel host bridge wire format and RlmRunHandler.[3] createRlmDeleteSubagentHostHandler requires payload.target to be a non-empty string and trims whitespace before delegating; a missing or blank value throws immediately.[3]
findRlmModelMatches in packages/coding-agent/src/core/rlm-runtime.ts scores models against a query using exact match (lowest score), prefix match, and partial match across the provider/id, id, and name fields; unmatched models are excluded entirely.[3] Passing an empty query to findRlmModelMatches returns all models (up to limit) with score 0, functioning as an unfiltered catalog listing.[3]
All prompt-building logic for RLM subagents lives in packages/coding-agent/src/core/prompts/rlm.js; packages/coding-agent/src/core/prompts/index.ts is a pure re-export barrel that surfaces buildChildAgentDoctrine, buildRlmPrompt, buildSubagentGuidance, ChildAgentDoctrineOptions, and RlmPromptOptions with no logic of its own.[5] buildSubagentGuidance receives hasAgentMessage and hasAgentObserve flags derived from whether the agent_message and agent_observe Python skills are installed, allowing the guidance to tailor inter-agent communication instructions — see Skills for how those skills are defined.[6]
In v0.5.0, subagent guidance was changed to retain reusable children and delete completed direct children once they are no longer needed.[7]
Sources
README.mdgithub.com…llect-ai/prime-agent/releases/tag/v0.2.3packages/coding-agent/src/core/rlm-runtime.tspackages/coding-agent/src/modes/daemon/daemon-protocol.tspackages/coding-agent/src/core/prompts/index.tspackages/coding-agent/src/core/system-prompt.tsgithub.com…llect-ai/prime-agent/releases/tag/v0.5.0Updated
For long-running tasks, automatic compaction, persistent goals, heartbeats, schedules, autonomous mode, and retained subagents preserve progress across turns and terminal sessions.[1] Daemon-backed sessions keep running when the terminal disconnects and can be reattached later using prime-agent attach <agent> — see Daemon architecture for the underlying implementation.[1]
/goal keeps an objective and its progress active across turns until it is completed, paused, or cleared.[1]
As of v0.2.9, scheduled heartbeat prompts steer by default — interrupting the current turn — with a steer/follow_up delivery mode selectable via /heartbeat --steer|--follow-up and the rlm_heartbeat skill's delivery_mode argument.[2] In v0.3.3, the bundled orchestration heartbeat skill was removed from the model system prompt.[3] A heartbeat is a scheduled prompt automatically injected into a running agent's turn cycle, enabling self-correction or progress reporting without requiring a human message. Heartbeat delivery timing and mode determine when and how Prime Agent receives steering feedback during long-running task execution.
deleteRlmSubagentRuntime in SubagentRuntimeHost accepts an optional session parameter; the session is absent when the target child is still in a passive (snapshotted but not resident) state.[4] A passive subagent has its state saved to disk but is not actively consuming memory or compute; it can be restored to a resident (active, in-memory) state on demand.
Sources
Updated
Autonomous mode (/autonomous) continues within configured turn, token, and time budgets and can run user-defined quality gates; a passed gate checks only what that gate verifies, and reaching a budget limit does not imply task success.[1]
In v0.1.5, goals were moved into a bundled goal Python skill backed by a typed host bridge, leaving IPython as the only built-in tool.[2] Autonomous mode was introduced in v0.3.0 with host-side continuations, configurable limits, and quality gates for evaluator-controlled runs.[3] A --goal seeding bug was fixed in v0.5.0: CLI --goal sessions now show the objective to the model, which had previously been invisible to first turns and continuations because the goal-context message was never injected.[4]
HarnessOptions exposes an autonomous field of type AgentAutonomousConfig and an autoRefineReviewer field, both passed to AgentSession to enable autonomous mode with quality gates in tests — see Test harness for full harness API coverage.[5]
Sources
Updated
Pages in this section:
Updated
Skills in Prime Agent are importable Python packages or markdown files managed by a built-in discovery system; the Skill type unifies both kinds (distinguished by a kind field), and the loader exports functions to find, format, and extract runtime metadata for installation. Skill discovery scans directories for SKILL.md files or loose .md files, respects gitignore patterns and hidden entries, tolerates read errors gracefully, and normalizes Python dependencies automatically — allowing skills to be organized as project, personal, or package-level workflows.
Skills in prime-agent are importable Python packages; a built-in skill creator can turn recurring workflows into project or personal skills.[1] The Skill type in packages/coding-agent/src/core/skills.ts is a discriminated union of MarkdownSkill and PythonSkill, distinguished by a kind field ("markdown" | "python"); Python skills carry a python: SkillPythonMetadata field, while markdown skills do not.[2]
packages/coding-agent/src/index.ts exports the skills system — loadSkills, loadSkillsFromDir, formatSkillsForPrompt, getPythonSkillRuntimeInfo, and related types — from ./core/skills.js.[3] getPythonSkillRuntimeInfo filters a skill list to only Python skills and returns each skill's name, importName, packagePath, and pyprojectPath — the data needed to install them into the IPython kernel.[2]
loadSkillsFromDir follows these discovery rules: a directory containing SKILL.md is treated as a skill root and recursion stops there; otherwise, direct .md children of the scan root are loaded, and subdirectories are recursed to find nested SKILL.md files.[2] During skill directory scanning, node_modules directories and hidden entries (names starting with .) are always skipped.[2] Skill discovery honors .gitignore, .ignore, and .fdignore files, applying their patterns relative to the scan root so that nested ignore files are scoped correctly.[2] Unreadable ignore files encountered during skill discovery are silently skipped rather than causing an error.[2] A scan failure of an entire skill directory is logged as a warning rather than a thrown error, allowing the agent to continue loading other skills.[2]
Skill names must match their parent directory name, consist only of lowercase a–z, digits, and hyphens, be no longer than 64 characters, and must not start, end with, or contain consecutive hyphens — all enforced by validation in packages/coding-agent/src/core/skills.ts.[2]
normalizePythonSkills in packages/coding-agent/src/core/kernel/bootstrap.ts automatically resolves sibling directory dependencies of a Python skill by scanning adjacent directories for pyproject.toml files and matching project names, ensuring transitive local packages are included.[4] Python skill deduplication uses the composite key importName + '\0' + packagePath, so the same package found at two different paths is treated as two distinct skills.[4]
The prime-agent-runtime skill.py cli() function is a console-script entry point that imports the skill module by name (matching sys.argv[0] stem), locates its run callable, and runs it via tyro; the console-script name must exactly match the skill's Python import name, using underscores instead of dashes.[5] run_cli(func, prog) in prime-agent-runtime/src/rlm/skill.py parses CLI arguments for a skill function using tyro, awaits the result if it is a coroutine, and prints a non-None result to stdout.[5]
Since v0.2.2, a bundled websearch skill (Google search via the Serper API) loads by default; a Serper key is added via /login ("Serper (web search)") and stored with other credentials. The skill can be disabled with bundledSkills.websearch: false and overridden by a same-named skill in any user, project, package, or --skill location.[6] Since v0.2.3, built-in Linear and Notion integrations ship as bundled Python skills that talk to each service's official MCP server; they are disabled by default and activate after signing in via the Services tab in /login or /mcp login, with credentials stored in the existing auth.json.[7]
Sources
Updated
Compaction in Prime Agent is a pure-function module that summarizes old conversation history when context tokens approach the window limit; SessionManager handles all I/O and reloads the session after the compaction logic completes. The compaction system estimates context tokens from actual usage data and previous messages, determines valid cut points (user/assistant/custom/execution/summary messages—never tool results), and produces a CompactionResult carrying the summary and tracking metadata without session identifiers. An active goal (a long-running autonomous objective) is persisted independently across the compaction boundary in agent-session.ts, preventing daemon sessions from silently dropping the goal when context is trimmed. After compaction completes and session context is restored, agent-session.ts automatically requeues any continuation prompt that was interrupted by compaction, resuming in-flight work. When a post-compaction continuation fails to start (e.g., due to a context error or resource exhaustion), agent-session.ts propagates a rejection to all registered idle waiters rather than leaving them hanging indefinitely.
The compaction module at packages/coding-agent/src/core/compaction/compaction.ts contains pure functions only; the SessionManager is responsible for all I/O, and the session is reloaded after compaction completes.[1] The COMPACT_SKILL_NAME constant is "compact", identifying the compaction skill by name.[1]
shouldCompact triggers compaction when contextTokens > contextWindow - settings.reserveTokens, and returns false if compaction is disabled or contextWindow <= 0.[1]
estimateContextTokens anchors its estimate on the last assistant message's actual usage data and adds a chars/4 estimate only for messages after that anchor point; without any usage data, it estimates all messages with chars/4.[1] getAssistantUsage skips messages with a stopReason of "aborted" or "error" because those messages do not carry valid usage data.[1]
Valid compaction cut points are user, assistant, custom, bashExecution, branchSummary, and compactionSummary messages; toolResult messages are never valid cut points because they must immediately follow their tool call.[1] getMessageFromEntryForCompaction deliberately excludes compaction entries (returning undefined for them), unlike getMessageFromEntry which converts them to summary messages — this prevents prior compaction summaries from being re-summarized.[1]
The CompactionResult interface carries summary, firstKeptEntryId, tokensBefore, and an optional details field for extension-specific data; uuid and parentUuid are not included — the SessionManager adds those fields when persisting the result.[1] File operations accumulated in a CompactionDetails from a previous pi-generated compaction entry are carried forward into the next compaction's file-op tracking; hook-generated compaction entries are excluded from this carry-forward (checked via !prevCompaction.fromHook).[1]
agent-session-compaction-continuation.test.ts is the canonical regression guard for compaction continuity, covering both generic continuation-state and goal-specific state persistence across the compaction boundary.
Sources
Updated
Pages in this section:
Updated
The /refine endpoint reviews a harness's current trajectory and applies small, evidence-backed updates to supplemental state — prompts, memories, skill descriptions, subagent specs — while preserving the immutable base system prompt and maintaining rollback-safe snapshots. Refine operates both as an IPython-callable skill (scheduling updates without blocking the current turn) and as an endpoint with output budgets derived from the selected model, preventing silent truncation of large proposals.
/refine reviews the current trajectory and can apply small, evidence-backed updates to supplemental harness state — supplemental prompts, memories, skill descriptions, and subagent specs — with recorded snapshots that support rollback; it never rewrites the immutable base system prompt.[1] As of v0.3.3, an agent-callable refine skill lets the model schedule continual harness refinement from IPython via await refine.run() without blocking the current turn.[2] In v0.5.1, /refine was fixed to derive output budgets from the selected model rather than a fixed 4096-token cap, so large multi-edit proposals are no longer truncated mid-string with an opaque JSON parse error; a truncated reply now reports the exhausted budget directly.[3] Refinement pass status and final outcomes are propagated through the message queue and rendered in both interactive and headless modes via structured result messages. Refinement outcome messaging is implemented across packages/coding-agent/src/core/messages.ts, agent-session.ts, components/refinement-outcome-message.ts, modes/headless-completion.ts, and components/conversation-components.ts; test coverage lives in test/refinement-outcome-message.test.ts.
Sources
Updated
createHarness creates a unique temporary directory for each harness instance using tmpdir() + timestamp + random suffix, providing test isolation for file-system operations.[1] When persistSession is false (the default), createHarness uses SessionManager.inMemory(); when true, it creates a file-backed SessionManager rooted at a temporary directory under sessions/.[1] The default system prompt used when creating an Agent in createHarness is "You are a test assistant.", overridable via HarnessOptions.systemPrompt.[1] FauxProviderRegistration is a test-only stand-in for a real AI model provider that supplies scripted responses instead of making live network calls, keeping tests fast, deterministic, and offline-capable.
HarnessOptions supports rlmDepth and rlmMaxDepth fields that are passed directly to AgentSession, enabling tests to configure subagent recursion depth limits.[1] The Agent constructed in createHarness wires three lifecycle hooks — onPayload (before provider request), onResponse (after provider response), and transformContext (message context transformation) — all delegated through the ExtensionRunner if handlers are registered.[1]
The Harness interface exposes setResponses, appendResponses, and getPendingResponseCount methods, all delegated directly to the underlying FauxProviderRegistration, for controlling faux model responses in tests.[1] The Harness interface exposes eventsOfType<T>(type), a type-narrowed helper that returns only events of the specified AgentSessionEvent discriminant type from the session's event log.[1]
The getMessageText helper in packages/coding-agent/test/suite/harness.ts handles both string content and structured content arrays, joining all type: "text" parts with newlines.[1] getUserTexts(harness) and getAssistantTexts(harness) are test utility functions in packages/coding-agent/test/suite/harness.ts that filter harness.session.messages by role and extract plain text from each message via getMessageText.[1]
Sources
Updated
Prime Agent's tool system is now ipython-only at the core; bash and edit were removed as first-class tools in v0.2.6, leaving ToolName as a single constant value. The pi runtime API lets you query all registered tools (built-in and extension) and switch the active subset dynamically via setActiveTools, getActiveTools, and getAllTools. The ipython tool executes Python code in an embedded IPython kernel, providing a persistent, stateful execution environment across invocations.
The only ToolName recognized by packages/coding-agent/src/core/tools/index.ts is "ipython"; bash and edit have been removed as first-class tool names, consistent with their removal in v0.2.6.[1] createAllToolDefinitions and createAllTools in packages/coding-agent/src/core/tools/index.ts return a Record<ToolName, ToolDef> / Record<ToolName, Tool> containing only an ipython entry, each accepting an optional ToolsOptions for per-tool configuration.[1] The ToolsOptions interface has a single optional property ipython?: IpythonToolOptions, which is threaded through to all createAll* helpers.[1] ACP-provided MCP tools are registered as first-class callable tools in Prime Agent's native tool-dispatch path via a CPython-proxy adapter in src/core/tools/acp-mcp.ts, making them invocable without a full MCP round-trip. src/core/mcp/mcp-manager.ts routes ACP-origin MCP tools to the CPython-proxy adapter in src/core/tools/acp-mcp.ts rather than the generic MCP handler. New ACP tool categories should follow the src/core/tools/acp-mcp.ts pattern to ensure registration in the native dispatch table.
pi.setActiveTools(toolNames: string[]) changes the active tool set at runtime; pi.getActiveTools() returns the currently active tool names; and pi.getAllTools() returns all registered ToolInfo objects, covering both built-in and extension tools — see Extension API and lifecycle for extension tool registration.[2]
Sources
Updated
The IPython tool, implemented in packages/coding-agent/src/core/tools/ipython.ts, accepts a single code string field described as 'Python scratchpad code or %%bash shell cells to execute in the agent kernel', with guidance to use the target project's own environment for project imports, tests, scripts, CLIs, and dependency checks instead of direct kernel imports.[1] The Python kernel runtime is set up automatically on first invocation; the PRIME_AGENT_KERNEL_PYTHON environment variable can be set to point to an existing Python environment with ipykernel instead.[2] A code comment marks the persistent kernel with a TODO to reconsider whether it is needed once RLM-1 weights land, indicating it may not be required long-term.[1] A kernel namespace snapshot captures the names and serialized values of kernel variables at a point in time, enabling their restoration in a future session without re-running the original code. The kernel namespace snapshot/restore contract is defined in state-snapshot.ts and is covered by the test suites kernel-state-roundtrip.test.ts and kernel-state-snapshot.test.ts. After each execution, the IPython kernel captures a namespace snapshot and prunes oversized kernel state before injecting it into subsequent turns; this cycle is implemented across kernel/index.ts, kernel/state-snapshot.ts, tools/ipython.ts, and the RLM system prompt. agent-session.ts coordinates the kernel state trim/restore cycle to prevent unbounded namespace growth, which would otherwise inflate context length and risk inference slowdowns or context-window overflows in long coding sessions.
buildRlmBootstrapCode generates IPython kernel bootstrap code that sets NO_COLOR=1, disables IPython color output, applies nest_asyncio, imports rlm (or installs the missing-rlm stub), and — when Python skills are provided — imports each skill module, wrapping it in _PrimeAgentCallableSkillModule so the module itself is directly awaitable.[1] When rlm cannot be imported into the IPython kernel, a _PrimeAgentMissingRlm stub is placed in the rlm global; calling .run(), .find_models(), .list_subagents(), .delete_subagent(), or the callable form raises a RuntimeError advising the user to remove ~/.prime/agent/kernel-venv so prime-agent can rebuild it.[1] When a Python skill module fails to import during kernel bootstrap, a _PrimeAgentUnavailableSkill stub is placed in the global namespace under the skill's name; calling it raises a RuntimeError with the import error message.[1]
IpythonToolOptions.provisioner is a shared IpythonKernelProvisioner that owns the kernel lifecycle; when provided, all other kernel-configuration options are ignored.[1] IpythonToolOptions.readyGate is a Promise<unknown> that resolves before the kernel starts, preventing a /reload's old-kernel snapshot flush from racing the new kernel's restore.[1] IpythonToolOptions.snapshotDir specifies the per-session artifact directory for kernel namespace snapshots; omitting it disables snapshots entirely.[1] IpythonToolOptions.onRestore fires once per kernel start when a previous session's namespace was revived (some names restored or some failed), enabling the session to inform the model about restored state.[1] %%bash cells can have a commandPrefix prepended to every cell body and/or a shellPath substituted for the %%bash magic (converting it to %%script <shellPath>) when IpythonToolOptions.commandPrefix or shellPath are set.[1]
IpythonToolDetails captures per-execution metadata including status ("ok" | "error" | "aborted" | "starting"), durationMs, stdout/stderr, result text, diff displays, kernel attachments, sent agent messages, and a kernelRestarted boolean.[1] The raceWithAbort helper wraps any promise with an AbortSignal; if the signal is already aborted at call time, the promise is immediately rejected without subscribing, and an optional onAbort callback fires synchronously when the signal fires.[1]
The IPython cell component in packages/coding-agent/src/modes/interactive/components/ipython-cell.ts applies syntax highlighting to the full cell content as a single unit before splitting for display, so multi-line string literals and other newline-spanning tokens retain correct color across line boundaries.
Sources
Updated
The bash tool lives in packages/coding-agent/src/core/tools/bash.ts and is still a full implementation exported via createBashToolDefinition and createLocalBashOperations, even though it is no longer registered as a built-in ToolName in the tools index — it remains available for extensions that import it directly.[1] createLocalBashOperations is the standard local-shell backend, intended for extensions that intercept user_bash but still want prime-agent's standard shell behavior while wrapping or rewriting commands.[1]
The BashToolInput schema accepts a required command: string and an optional timeout: number (in seconds; no default timeout is applied).[1] The tool description exposed to the model states that output is truncated to the last DEFAULT_MAX_LINES lines or DEFAULT_MAX_BYTES / 1024 KB — whichever is hit first — and that the full output is saved to a temp file when truncated.[1]
The BashToolOptions interface supports four extension points: operations (pluggable exec backend), commandPrefix (prepended to every command), shellPath (explicit shell binary), and spawnHook (mutate command, cwd, or env before execution).[1] BashSpawnHook is typed as (context: BashSpawnContext) => BashSpawnContext, where BashSpawnContext carries command, cwd, and env; the hook can rewrite any of these fields before the process is spawned.[1]
createLocalBashOperations rejects command execution with an error if the working directory does not exist at spawn time.[1] Local bash processes are spawned with detached: process.platform !== 'win32', and their PIDs are tracked via trackDetachedChildPid / untrackDetachedChildPid to prevent the parent from hanging on stdio handles inherited by detached descendants.[1] Abort signals sent to the local bash backend kill the entire process tree via killProcessTree, not just the immediate child process.[1] The bash tool's kernel-side implementation is asynchronous, executing shell commands through a Python-side backend so that long-running commands do not block the event loop, enabling non-blocking REPL operations. Asynchronous bash execution and orphan-process tracking required updates across src/core/kernel/bootstrap.ts, src/core/kernel/state-snapshot.ts, src/modes/daemon/daemon-supervisor.ts, src/cli/owned-session-worker.ts, and orphan-process-journal.ts. The Python-side bash tool in prime-agent-runtime/src/rlm/bash.py emits an ordered completion sentinel at the end of command output; without it, output lines could arrive out of order relative to the completion signal, causing apparent truncation of results. In packages/coding-agent/src/core/agent-session.ts, each executeBash call receives a dedicated AbortController instance, so aborting one command does not cancel sibling or subsequent commands in the same session.
Sources
Updated
The edit tool, implemented in packages/coding-agent/src/core/tools/edit.ts, edits a single file using exact text replacement, routing all file mutations through withFileMutationQueue (from file-mutation-queue.js) to serialize concurrent writes to the same file.[1] The EditOperations interface allows pluggable file I/O — with readFile, writeFile, and access methods — making it possible to delegate editing to remote systems such as SSH.[1] The edit tool renders with renderShell: 'self', meaning it manages its own shell rendering instead of delegating to a container.[1]
prepareEditArguments normalizes tool input before validation: models that send edits as a JSON string instead of an array — specifically noted for Opus 4.6 and GLM-5.1 — have their input parsed and converted to an array.[1] prepareEditArguments also handles a legacy single-edit form where oldText and newText appear as top-level keys, converting them into an edits array entry for backward compatibility.[1] Similarly, the path field accepts file_path as a legacy alias in render helpers, supporting older model outputs that used the old field name.[1]
validateEditInput throws if edits is not a non-empty array, enforcing at least one replacement per call.[1] Each edit's oldText must be unique in the file and non-overlapping with other edits in the same call; edits that affect the same block or nearby lines must be merged into one, and large unchanged regions must not be included merely to connect distant changes.[1]
EditToolDetails carries a unified diff string (diff) and an optional firstChangedLine — the line number of the first change in the new file — for editor navigation.[1] The Ctrl+J keybinding — registered in keybindings.ts, wired through runner.ts, and handled in interactive-mode.ts — toggles the inline diff view for edits independently of the tool-output collapse state. The edit tool's interactive TUI rendering is split across edit-summary.ts, tool-execution.ts, and conversation-components.ts; edit-summary.ts provides an always-visible edit-summary component, replacing the previous behavior of burying diffs inside collapsible tool output.
Sources
Updated
Extensions are TypeScript modules that can subscribe to agent lifecycle events, register LLM-callable tools, register commands/keyboard shortcuts/CLI flags, and interact with the user via UI primitives — capabilities defined in packages/coding-agent/src/core/extensions/types.ts.[1] Extensions are loaded via jiti, so TypeScript works without compilation.[2] packages/coding-agent/src/core/extensions/index.ts is the single public barrel for the extension system, re-exporting all event types, context types, tool helpers, loader functions, and the ExtensionRunner from their respective implementation files — see Extension API and lifecycle for the full public API surface.[3]
Place extensions in ~/.prime/agent/extensions/ (global) or .prime/agent/extensions/ (project-local) for auto-discovery; only extensions in these locations can be hot-reloaded with /reload.[2] The -e ./path.ts flag loads an extension directly and is intended for quick tests only.[2]
For extensions with npm dependencies, place a package.json with a "pi": { "extensions": ["./src/index.ts"] } field next to the extension and run npm install; imports from node_modules/ are resolved automatically.[2] For extensions distributed as Prime Agent packages (installed via prime-agent package install), runtime dependencies must be in dependencies rather than devDependencies, because installation uses npm install --omit=dev by default.[2] Available imports for extensions include @earendil-works/pi-coding-agent (types: ExtensionAPI, ExtensionContext, and events), typebox (tool parameter schemas), @earendil-works/pi-ai (StringEnum for Google-compatible enums), @earendil-works/pi-tui (TUI components for custom rendering), and Node.js built-ins such as node:fs and node:path.[2]
Extensions run with the user's full system permissions and can execute arbitrary code — only install extensions from sources you trust.[2]
Sources
Updated
Extensions in Prime Agent are factory functions that receive an ExtensionAPI to register tools, providers, commands, and lifecycle handlers; async factories can initialize remote data before startup, making it available to the session and CLI. Prime Agent's lifecycle runs in strict order across session setup (session_start → resources_discover) and per-prompt execution (input → before_agent_start → agent/message events → per-tool-call events → agent_end), allowing handlers to observe and intercept at each stage.
The extension loader API in packages/coding-agent/src/core/extensions/index.ts exports four functions from ./loader.js: createExtensionRuntime, discoverAndLoadExtensions, loadExtensionFromFactory, and loadExtensions.[1] From ./types.js, the same module exports the defineTool helper and the type-guard functions isBashToolResult, isEditToolResult, isIpythonToolResult, and isToolCallEventType.[1] Tool-decoration helpers wrapRegisteredTool and wrapRegisteredTools are exported from ./wrapper.js, and the built-in Herdr integration — createHerdrAgentStateExtension, hasFileBasedHerdrIntegration, and herdrAgentStateExtension — is exported from ./builtin/herdr-agent-state.js.[1]
Extensions export a default factory function that receives ExtensionAPI; the factory may be synchronous or asynchronous. When it returns a Promise, Prime Agent awaits it before continuing startup — async initialization completes before session_start, before resources_discover, and before provider registrations queued via pi.registerProvider() are flushed.[2] The full lifecycle event order is: session_start → resources_discover → (per-prompt) input → before_agent_start → agent_start → message_start/update/end → and, per tool call: turn_start, context, before_provider_request, after_provider_response, tool_execution_start, tool_call, tool_execution_update, tool_result, tool_execution_end, turn_end → agent_end.[2] An async factory can fetch remote data — for example, a local model list — before calling pi.registerProvider(), making those models available at startup and to prime-agent model list.[2] The session_before_refine lifecycle hook fires immediately before each refinement cycle; a handler receives the agent session and may return a modified session or metadata that shapes refinement behavior. session_before_refine is wired through packages/coding-agent/src/core/extensions/types.ts, extensions/runner.ts, extensions/index.ts, and core/refinement/refinement.ts.
pi.registerTool() registers a custom tool callable by the LLM. The definition requires name, label, description, parameters (a TypeBox schema), and an async execute(toolCallId, params, signal, onUpdate, ctx) function returning { content, details }.[2] The defineTool() helper from @earendil-works/pi-coding-agent is the canonical way to build a typed tool definition before passing it to pi.registerTool() — see Extensions for broader extension how-to knowledge.[3] pi.registerCommand(name, { description, handler }) registers a slash-command (e.g. /hello) accessible in the TUI; the handler receives (args, ctx).[2] pi.appendEntry<T>(type, data) stores state that survives session restarts. Entries are retrieved by iterating ctx.sessionManager.getBranch() and filtering on entry.type === "custom" && entry.customType === type.[2] A tool_call event handler can block a tool call by returning { block: true, reason: string }; returning nothing allows the call to proceed.[2] TypeBox is a JSON Schema builder for TypeScript that defines tool parameters schemas in Prime Agent extensions, enabling runtime validation of LLM-supplied arguments and automatic type inference in the execute function.
Canonical async factory pattern for dynamically discovering and registering a local OpenAI-compatible provider at startup:
export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = await response.json();
pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "LOCAL_OPENAI_API_KEY",
api: "openai-completions",
models: payload.data.map((model) => ({
id: model.id,
name: model.name ?? model.id,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: model.context_window ?? 128000,
maxTokens: model.max_tokens ?? 4096,
})),
});
}
Minimal custom tool extension using defineTool and pi.registerTool():
import { Type } from "@earendil-works/pi-ai";
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
const helloTool = defineTool({
name: "hello",
label: "Hello",
description: "A simple greeting tool",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: { greeted: params.name },
};
},
});
export default function (pi: ExtensionAPI) {
pi.registerTool(helloTool);
}
The tools.ts extension example demonstrates building a /tools command that uses ctx.ui.custom() with SettingsList from @earendil-works/pi-tui to render an interactive TUI dialog for toggling tools on/off, with changes applied immediately via pi.setActiveTools() and persisted via pi.appendEntry().[4] Tool selection state stored via pi.appendEntry() is restored per-branch: the tools.ts extension finds the last tools-config custom entry in ctx.sessionManager.getBranch() and filters saved tool names against currently-existing tools before restoring.[4] A reference implementation of the session_before_refine hook is provided in examples/extensions/custom-refinement.ts and documented in docs/extensions.md.
Sources
Updated
The ExtensionUIContext interface in packages/coding-agent/src/core/extensions/types.ts is mode-specific: interactive, RPC, and print modes each provide their own implementation.[1] ExtensionContext exposes hasUI: boolean to let extensions detect whether UI interaction is available — it is false in print/RPC mode.[1] The assistant-message component in packages/coding-agent/src/modes/interactive/components/assistant-message.ts includes a null guard on content blocks, tolerating null entries in an assistant turn and preventing render-time failures when a model returns null blocks. A feature hint in feature-hints.ts surfaces the opt-in trace-sharing prompt during interactive sessions; the hint participates in hint-rotation logic and displays only to users who have not yet opted in.
ctx.ui exposes confirm(title, message), notify(message, level), setStatus(id, text) (footer status), setWidget(id, lines) (widget above editor), and custom(callback) for full TUI components with keyboard input — see Extensions for a broader orientation.[2]
The WidgetPlacement type accepts exactly two values: "aboveEditor" and "belowEditor".[1] ExtensionUIContext.setWidget defaults widget placement to "aboveEditor" when placement is not specified.[1]
ExtensionUIContext.setWorkingIndicator restores the default animated spinner when called with no argument; frames: ["●"] produces a static indicator; frames: [] hides the indicator entirely; custom frames are rendered verbatim and must supply their own color codes.[1] ExtensionUIContext.setWorkingMessage restores the default working/loading message shown during streaming when called with no argument.[1]
ExtensionUIContext.setFooter receives a ReadonlyFooterDataProvider giving access to git branch and extension statuses from setStatus(); token stats, model info, and similar data are available via ctx.sessionManager and ctx.model.[1]
ExtensionUIContext.setEditorComponent restores the default editor when called with undefined.[1] To build a custom editor with full app keybinding support (escape, ctrl+d, model switching, etc.), extensions should extend CustomEditor from @earendil-works/pi-coding-agent and call super.handleInput(data) for any keys not handled by the custom editor.[1] ExtensionUIContext.pasteToEditor triggers paste handling, including automatic collapse for large content.[1]
The AutocompleteProviderFactory type is a function (current: AutocompleteProvider) => AutocompleteProvider that wraps the current autocomplete provider to add behavior, following a middleware/decorator pattern.[1]
ExtensionUIContext.onTerminalInput is available in interactive mode only and returns an unsubscribe function.[1]
ExtensionUIContext.setHiddenThinkingLabel restores the default label for hidden thinking blocks when called with no argument.[1]
ExtensionUIDialogOptions supports an AbortSignal to programmatically dismiss dialogs and a timeout in milliseconds that auto-dismisses the dialog with a live countdown display.[1]
The ContextUsage interface reports tokens as null (not zero) when the context token count is unknown — for example, immediately after compaction before the next LLM response.[1] ContextUsage.percent is null whenever tokens is null.[1]
Sources
Updated
Loader and runner internals manage extension initialization, environment setup, and action dispatch: the loader builds the extension runtime with stale-context detection and queued registration, while the runner wires those to core implementations, handles keybinding conflicts, and provides type-safe event emission. Prime Agent's loader uses compile-time bundling flags and module aliasing to ensure extensions see unified typebox instances and correctly resolve paths, then protects against stale contexts through assertActive() checks and invalidation markers.
The __PI_BUNDLED__ compile-time constant in loader.ts is replaced with true by the esbuild CLI bundle (scripts/bundle.mjs); in unbundled dist/ and under tsx it remains undefined. This flag controls whether virtual modules or jiti aliases are used for resolving pi packages inside extensions.[1] loader.ts aliases typebox, typebox/compile, typebox/value, @sinclair/typebox, @sinclair/typebox/compile, and @sinclair/typebox/value so that extensions importing either the scoped or unscoped typebox package resolve to the same instance bundled with the agent.[1] expandPath() in loader.ts normalizes Unicode spaces (non-breaking, em-space, thin-space, etc.) before expanding ~/ or ~ prefixes to the OS home directory, ensuring extension paths pasted from rich-text editors resolve correctly.[1]
createExtensionRuntime() in loader.ts returns an ExtensionRuntime whose action methods (sendMessage, appendEntry, etc.) all throw "Extension runtime not initialized. Action methods cannot be called during extension loading." until Runner.bindCore() replaces them with real implementations.[1] createExtensionRuntime() pre-initializes refreshTools as a no-op rather than a throwing stub, because registerTool() is valid during extension loading — refreshTools only needs a real implementation after bindCore() wires it up.[1] Before bindCore() is called, registerProvider in createExtensionRuntime() queues registrations into runtime.pendingProviderRegistrations instead of calling the model registry directly; bindCore() flushes this queue and then replaces registerProvider with a direct call.[1]
runtime.invalidate(message?) in loader.ts marks the runtime as stale, causing all subsequent assertActive() calls to throw. The default stale message explicitly lists the operations that invalidate a context: ctx.newSession(), ctx.fork(), ctx.switchSession(), and ctx.reload().[1] Every action method on the ExtensionAPI created by createExtensionAPI() in loader.ts calls runtime.assertActive() first, so using a stale context after session replacement or reload throws immediately rather than silently doing nothing.[1]
registerFlag() in createExtensionAPI() sets the flag's default value in runtime.flagValues only if no value is already stored, preventing a re-register from overriding a user-supplied value.[1] getFlag() in createExtensionAPI() returns undefined if the calling extension has not registered the named flag — guarded with !extension.flags.has(name) — even if another extension registered a flag with the same name.[1] The exec() method on ExtensionAPI merges the session-supplied environment (runtime.getExecEnv?.()) with any caller-supplied options.env, with the caller's values winning. The session env is read at call time rather than at registration time, so per-session variables are always current.[1]
runner.ts defines RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS, enumerating the global TUI actions that extensions cannot override: app.interrupt, app.clear, app.exit, app.suspend, app.model.select, app.tools.expand, app.messages.expand, app.thinking.toggle, app.subagents.focus, app.editor.external, app.message.followUp, tui.input.submit, tui.select.confirm, tui.select.cancel, tui.input.copy, and tui.editor.deleteToLineEnd.[2] buildBuiltinKeybindings() in runner.ts normalizes key IDs to lowercase when building the built-in keybinding map, and if the same physical key is bound to both a reserved and a non-reserved action, the reserved action always wins regardless of iteration order.[2]
runner.ts defines RunnerEmitEvent as all ExtensionEvent types except ToolCallEvent, ToolResultEvent, UserBashEvent, ContextEvent, BeforeProviderRequestEvent, BeforeAgentStartEvent, MessageEndEvent, ResourcesDiscoverEvent, and InputEvent, which have dedicated typed emitXxx() methods for stronger type safety.[2] emitSessionShutdownEvent() in runner.ts only calls extensionRunner.emit() when at least one handler for "session_shutdown" is registered, returning true if emitted and false if skipped.[2]
ExtensionRunner in runner.ts initializes all its handler fields (newSessionHandler, forkHandler, navigateTreeHandler, switchSessionHandler, reloadHandler, shutdownHandler, etc.) to safe no-op defaults so extensions can be constructed and events emitted before the full application context is wired.[2] ExtensionRunner.bindCore() in runner.ts flushes the pendingProviderRegistrations queue accumulated during extension loading, calling either the injected providerActions.registerProvider or this.modelRegistry.registerProvider for each entry. Errors are forwarded to emitError() rather than thrown.[2]
The NewSessionHandler type exported from runner.ts accepts an optional parentSession string, a setup callback receiving a SessionManager, and a withSession callback receiving a ReplacedSessionContext, and resolves to { cancelled: boolean }.[2] The ForkHandler type exported from runner.ts takes an entryId string and optional position ("before" | "at") plus a withSession callback, resolving to { cancelled: boolean }. The NavigateTreeHandler additionally supports summarize, customInstructions, replaceInstructions, and label options.[2]
Sources
Updated
Prime Agent uses a three-layer daemon architecture where the client owns only rendering and local UI preferences, a supervisor daemon handles routing and worker health, and independent session workers each own an AgentSessionRuntime with its kernels, scheduler, and all descendant sessions. The same execution and persistence path flows through the session queue regardless of prompt origin—user attachment, schedule, goal, or autonomous mode—so all input types use unified queuing, backpressure, and recovery. In the daemon architecture, backpressure is the mechanism by which a session worker signals callers that its queue is full, causing new inputs to be held or rejected until capacity becomes available.
In the prime-agent daemon architecture, three distinct layers each own a bounded set of concerns: the client (TUI or headless) owns rendering, keyboard input, and local UI preferences only — execution is owned by the session worker.[1] The daemon supervisor owns session discovery, routing, attachments, worker health, and cross-agent message delivery — not individual session logic.[1] Each session worker owns exactly one root AgentSessionRuntime, its scheduler, all kernels, and all descendant sessions below that root.[1] AgentSession owns provider calls, queues, tools, compaction, goals, child lifecycles, and transcript writes.[1] AgentSession serves as the single authority for agent-message admission logic, rather than distributing that responsibility across the daemon supervisor and worker periphery.
Workers and kernels run as separate processes for lifecycle and failure containment, not as security sandboxes — they normally run with the same OS permissions as the client.[1] From the session queue onward, the same execution and persistence path is used whether a prompt originates from a user attachment, heartbeat, cron schedule, goal continuation, autonomous mode, or another agent.[1] Peer subagent connections in the daemon are established on demand rather than eagerly, simplifying message-ordering and backpressure reasoning across concurrent sessions.
Four sub-documents elaborate the architecture: agent-connection.md (client/runtime boundary, snapshots, replay, reconnect), daemon.md (process ownership, leases, scheduling, backpressure, crash recovery), rlm-runtime.md (IPython host requests and recursive child execution), and long-running-agents.md (detached sessions, messages, goals, scheduled work).[1]
In v0.1.8, the long-lived daemon was changed to upgrade automatically when Prime Agent self-updates, so a new TUI no longer silently attaches to a stale daemon.[2] In v0.3.0, daemon and headless execution was changed to isolate each root session tree in a recoverable worker process, introducing protocol-v2 chunked snapshots, compact streaming, attachment-local backpressure, and session leases — while leaving print, JSON, and RPC interfaces unchanged.[3] In v0.3.2, all client modes — interactive, print, JSON, RPC, piped-stdin, and no-session — were changed to use the same daemon-owned runtime while preserving their existing commands, output protocols, and lifecycle behavior.[4] In v0.5.0, large daemon session loads were changed to stream JSONL history and avoid retaining a second full-file copy in memory.[5] In v0.6.1, the global idleEvictionMinutes daemon setting was documented, including its default, valid values, and eviction/passivation behavior.[6] In v0.7.1, retry_worker was fixed to clear saved stop markers so retried workers recover correctly instead of being stuck at "Session worker is not connected".[7]
Sources
packages/coding-agent/docs/architecture.mdgithub.com…llect-ai/prime-agent/releases/tag/v0.1.8github.com…llect-ai/prime-agent/releases/tag/v0.3.0github.com…llect-ai/prime-agent/releases/tag/v0.3.2github.com…llect-ai/prime-agent/releases/tag/v0.5.0github.com…llect-ai/prime-agent/releases/tag/v0.6.1github.com…llect-ai/prime-agent/releases/tag/v0.7.1Updated
The daemon protocol is defined in packages/coding-agent/src/modes/daemon/daemon-protocol.ts and uses JSONL transport over a local socket — it is the transport used by DaemonAgentConnection today, explicitly described as not the final remote gateway protocol.[1] The protocol name is "prime-agent.daemon" (constant DAEMON_PROTOCOL_NAME) and the current protocol version is 7 (constant DAEMON_PROTOCOL_VERSION).[1] Schema revision 16 — identified as "protocol-7-schema-16-1bcb9e7f1a49" and exported as DAEMON_SCHEMA_REVISION / DAEMON_SCHEMA_ID — adds the "stopping" workerState and stops reporting disconnected workers as "ready".[1] The minimum protocol version for the command envelope format is 7, exported as DAEMON_COMMAND_ENVELOPE_MIN_PROTOCOL_VERSION.[1] The RLM ledger loader in rlm-ledger.ts and daemon-mode.ts uses a unified parser that handles both legacy and current registry formats consistently, replacing two previously divergent parsing branches. Engineers extending the RLM ledger schema in rlm-ledger.ts or daemon-mode.ts must update the unified parser; adding a parallel parsing branch re-introduces divergence between legacy and current format handling.
DAEMON_SUPPORTED_CLIENT_CAPABILITIES lists all capabilities a client may declare: "attach_snapshot", "event_sequence", "extension_ui", "slim_attach", "chunked_snapshot", and "client_owned_sessions".[1] DAEMON_DEFAULT_CLIENT_CAPABILITIES includes only "attach_snapshot" and "event_sequence"; extended capabilities such as "slim_attach" and "chunked_snapshot" must be explicitly declared by the client.[1] DAEMON_DEFAULT_SERVER_CAPABILITIES includes all supported client capabilities plus "delete_rlm_subagent", "heartbeat_catalog", "heartbeat_management", "model_catalog", "side_question_transcript", "transient_bash", "session_input_admission", "prompt_admission_cancellation", and "queue_message_mutation".[1] Clients must verify the "transient_bash" server capability before sending execute_bash commands with the transient or runId fields; bash_start/bash_end events carry these markers so clients can correlate runs by identity.[1] The "side_question_transcript" server capability signals that the daemon honors previousTurns on start_side_question for multi-turn side conversations; clients must check for this capability before sending follow-up transcripts.[1]
Clients using the "slim_attach" capability receive a DaemonAttachResult where the top-level state and messages fields are omitted; they must read from snapshot.summary and snapshot.messages instead.[1] DaemonAttachClientMetadata supports a telemetryDisabled opt-out flag; per the opt-out-only policy, a telemetry-enabled worker must reject an attach that carries this flag.[1] DaemonSessionLifecycle is either "resident" (daemon-owned) or "client_owned", distinguishing who controls the session lifetime.[1]
Client env vars are forwarded to the daemon on create only — not on attach — because attaching must not rebind a session's identity, since watchers such as the agents view and subagent viewers may also attach. This is enforced by the DaemonClientEnv contract.[1] The allowlisted client env vars that may be forwarded over the daemon socket are HERDR_ENV, HERDR_PANE_ID, HERDR_SOCKET_PATH, HERDR_TAB_ID, and HERDR_WORKSPACE_ID, defined as DAEMON_CLIENT_ENV_KEYS; both client and server filter against this list.[1] collectDaemonClientEnv reads from process.env by default and returns undefined — not an empty object — when none of the allowlisted keys are present.[1] collectDaemonLaunchEnv forwards the entire process.env except keys prefixed with "PRIME_AGENT_INTERNAL_".[1]
The DaemonUpdateRestartManifest format version is 1 (exported as DAEMON_UPDATE_RESTART_FORMAT_VERSION); the manifest carries per-session state for graceful update-restart, including queued actions, streaming state, compaction state, and bash state.[1] DaemonUpdateRestartSession captures per-session restart state via the flags wasStreaming, wasCompacting, wasBashRunning, and hadRunningRlmChildren, so the new daemon process can accurately resume interrupted work.[1]
The schema revision changelog tracks: revision 9 publishes persisted RLM spawn depth on passive session rows; revision 10 extends this to all session catalog rows; revision 11 adds immediate get/set commands for active-session RLM max depth; revision 12 adds idle-residency metadata on session summary rows; revision 13 narrows agent-origin reach and roster wire shapes to the nuclear family; revision 14 carries the client's monotonic telemetry opt-out on attach and reattach; revision 15 adds mutate_queued_message and the queue_message_mutation capability.[1]
Sources
Updated
The daemon-mode file packages/coding-agent/src/modes/daemon/daemon-mode.ts owns live AgentSessionRuntime instances and exposes a JSONL protocol over a local socket, allowing clients to attach and detach from sessions without disposing the underlying agent loop.[1] daemon-mode.ts re-exports DaemonCommand, DaemonOutbound, and DaemonResponse from ./daemon-protocol.js; SessionActivity, SessionLifecycle, and SessionSummary from ./daemon-session-list.js; and defaultDaemonSocketPath from ./daemon-socket.js — protocol and wire types are covered in detail on Daemon protocol.[1] The agents-view and session-list do not surface workers in the stopping state; the daemon protocol reports worker lifecycle transitions accurately. The daemon root depth counter is reset on each new daemon context initialization, preventing depth-tracking drift across restarts. Without socket-path normalisation, symlinks or trailing slashes could produce different identity strings for the same physical socket, causing daemon-supervisor.ts to spawn a duplicate daemon or fail to claim ownership of an existing one. A centralized agent-status classifier in agents-view-state.ts, agent-roster.ts, and daemon-session-list.ts consolidates worker status derivation into one authoritative path, ensuring the TUI's subagent summary and daemon session list report consistent status values for the same worker. Extensions to daemon modes or new agent states must update the shared agent-status classifier in agents-view-state.ts, agent-roster.ts, and daemon-session-list.ts rather than patching individual UI components, to maintain consistency across all status-reporting surfaces. packages/coding-agent/src/core/session-lease.ts is the canonical location where daemon process identity is computed; the derivation must be timezone-independent and invariant to locale settings, ensuring consistent lease keys across system clock offset changes. test/suite/regressions/879-timezone-stable-process-identity.test.ts validates that the daemon process identity computed in session-lease.ts remains stable across timezone changes. Heartbeat-only sessions are classified as normal sessions in idle state rather than a distinct residency class, ensuring consistent display in agents-view and aligned eviction scheduling with other idle sessions. session-action-store.ts records a durable wake token for idle heartbeat sessions, enabling incoming tasks to restart them without a cold spawn. daemon-session-summarizer.ts re-publishes a roster-row update when the temporal currency of the idle verdict changes — even if other summary fields are structurally identical — ensuring agents-view subscribers display current idle/busy status rather than stale verdicts. The publication predicate for roster-row currency is defined there.
The DaemonModeOptions interface accepts an optional socketPath, a required defaultSessionConfig of type AgentSessionRuntimeConfig, a required createRuntime factory of type CreateAgentSessionRuntimeFactory, and an optional worker block containing a required authenticationToken and an optional restoreActiveSessionId.[1]
The complete set of daemon commands recognized by daemon-mode.ts spans session lifecycle (create, attach, detach, kill, rename, new_session, switch_session, fork), prompting (prompt, prompt_and_wait, steer, follow_up), agent messages (send_message, agent_messages_status, agent_messages_pause, agent_messages_resume, agent_messages_clear), cron and heartbeats (cron_list, cron_add, cron_cancel, heartbeat_get, heartbeat_set, heartbeat_update, heartbeats_list, heartbeat_manage), and update/restart operations (prepare_update_restart, retry_worker, restart, shutdown).[1] daemon-mode.ts tracks delivery state for remote agent messages and skips re-sending already-delivered messages during retry attempts, preventing duplicate message processing in long-running or multi-agent daemon sessions. In daemon-mode.ts, worker-mode sessions accept session renames that have been explicitly approved by the supervisor; unapproved rename requests are still rejected. daemon-supervisor.ts enforces session ownership validation when a new open request arrives while a previous one is in flight, preventing a different session from attaching to an in-flight result. test/daemon-supervisor-lazy-subagents.test.ts covers the race scenario where concurrent open requests could attach to shared in-flight results.
Three compile-time constants govern snapshot and update timing: WORKER_SNAPSHOT_TERMINAL_DRAIN_TIMEOUT_MS is 1,000 ms, UPDATE_RESTART_PREPARE_TIMEOUT_MS is 90,000 ms, and MAX_SESSION_SNAPSHOT_STABILIZATION_RETRIES is 3.[1] When a worker stop times out, daemon-supervisor.ts finalizes the registration instead of leaving it stranded, preventing agent-count drift and stop-command hangs. daemon-supervisor.ts retains the root process's kill-cleanup handler across all lifecycle transitions, preventing child processes from being orphaned during supervisor teardown. daemon-supervisor.ts filters out workers in terminal failure states when constructing the heartbeat catalog sent to the orchestrator, ensuring the catalog reflects only alive or pending workers. Failed workers are reported through a separate failure-reporting path, not the heartbeat catalog. The daemon shutdown path in daemon-mode.ts and active-session-state.ts explicitly awaits completion of any bash command still executing at close time, preventing close signals from abandoning running subprocesses and leaving output undelivered to the client. Any new teardown hooks must be inserted after this bash-drain await to avoid truncating in-flight bash output. When worker spawn fails in daemon-supervisor.ts (e.g., EMFILE: too many open files), the underlying OS error propagates through daemon-errors.ts and main.ts to the caller instead of an opaque fallback, enabling root-cause diagnosis. daemon-supervisor-monitor.test.ts and daemon-errors.test.ts validate this propagation path.
The RECOVERY_CHECKPOINT_EVENTS set defines the lifecycle moments at which the daemon records a recovery checkpoint: agent_start, agent_end, turn_start, turn_end, message_start, message_end, tool_execution_start, tool_execution_end, compaction_start, compaction_end, and auto_retry_start.[1] daemon-supervisor.ts detects and cleans up stale worker registrations on session resume, preventing ghost entries from blocking new workers. daemon-supervisor-ownership.ts stores supervisor ownership records in a persistent location rather than $TMPDIR, preventing loss of worker-ownership state across OS purges and reboots. daemon-supervisor.ts blocks session-reuse requests until a worker's restart sequence completes, preventing attachment to incompletely-recovered workers and ensuring reliable session state. Tests in test/daemon-supervisor-monitor.test.ts and test/daemon-supervisor-lazy-subagents.test.ts assert this ordering guarantee.
When an update-restart interrupts a session, daemon-mode.ts injects the fixed UPDATE_RESTART_MARKER message into the transcript: "<prime_agent_update_interrupted>\nPrime Agent was updated and intentionally interrupted this session. Continue from the saved transcript and restored tool/kernel state. Any running model, tool, bash, or child-agent work may have been partially completed.\n</prime_agent_update_interrupted>".[1]
Sources
Updated
Prime Agent's interactive mode is a fullscreen TUI with a scrollable transcript, pinned prompt bar, and mouse support; keyboard shortcuts and session control are customizable and hot-reloadable. OSC 8 is a terminal escape sequence standard that embeds clickable hyperlinks in terminal output, associating a URI with a text span so supporting terminals can open the link on interaction.
As of v0.2.5, fullscreen TUI rendering — a scrollable transcript with a pinned prompt bar and mouse selection — is enabled by default.[1] The fullscreen TUI renderer supports OSC 8 terminal hyperlinks; clicking a hyperlink in fullscreen mode opens it. Hyperlink hit-testing in fullscreen mode is implemented via a utility in packages/tui/src/utils.ts, with coverage provided by fullscreen.test.ts and hyperlink-at-column.test.ts. Bare URLs (not wrapped in OSC 8 escape sequences) in agent output are rendered as clickable links in fullscreen TUI mode, implemented via utilities in packages/tui/src/fullscreen.ts, tui.ts, and utils.ts, with gating logic in packages/coding-agent/src/core/settings-manager.ts. Collapse state for tool-execution blocks, IPython cells, and agent messages is unified as a single toggle in fullscreen TUI mode; state is coordinated in interactive-mode.ts with corresponding updates to conversation-components.ts, ipython-cell.ts, and tool-execution.ts. Mouse capture state in fullscreen TUI mode is managed in packages/coding-agent/src/core/settings-manager.ts rather than the TUI layer directly, making it the authoritative location for terminal-compatibility guards, including capability flag negotiation at startup and on mode transitions. The working-status elapsed-timer is implemented across packages/coding-agent/src/core/agent-messages.ts, agent-session.ts, and modes/interactive/interactive-mode.ts, with test coverage in test/interactive-mode-status.test.ts. The working-status elapsed-timer initializes from the session's original start timestamp and persists across session re-entries, giving a cumulative wall-clock total of working time rather than resetting on each re-entry. Subagent summary display in the interactive TUI is rendered as a visually distinct bordered tile, implemented in packages/coding-agent/src/modes/interactive/components/subagent-summary-line.ts, with test coverage in test/subagent-summary-line.test.ts; the bordered presentation improves readability when multiple subagents complete in close succession. The markdown-transform-hook extension point in packages/tui/src/components/markdown.ts is the sanctioned integration point for intercepting and transforming fenced code blocks before markdown display. Mermaid diagram code blocks in the interactive TUI are rendered as terminal diagrams rather than raw markup; the feature is user-facing and controlled via packages/coding-agent/src/core/settings-manager.ts and settings-selector.ts.
Keyboard shortcuts are customizable via ~/.prime/agent/keybindings.json; the full list is shown by the /hotkeys command.[2] In v0.4.0, alt+enter was added to queue a reply as a follow-up while Enter steers a streaming session, and ctrl+n starts a new session from Agents View.[3] The Ctrl+P keybinding toggles visibility of tool-execution blocks, IPython cells, and sent agent messages in the conversation view.
The /reload command hot-reloads keybindings, extensions, skills, prompts, and context files; themes hot-reload automatically without needing /reload.[2] The startup header's --verbose flag lists all loaded AGENTS.md files, prompt templates, skills, and extensions.[2]
Sources
Updated
Prime Agent sessions are persistent workspaces that can be resumed, forked, or navigated as a tree of branches — controlled via CLI flags at startup (-c, -r, --fork) or commands within a session (/tree, /fork, /clone). The daemon maintains only active sessions in memory; saved sessions load on-demand, and the CLI provides status, attachment, and management commands (agents, attach, status, doctor, shutdown) for controlling the background service. A session tree is the branching history of a Prime Agent session; each /fork, /clone, or resume-from-an-earlier-point creates a new branch, letting users explore alternative paths while preserving all prior work.
Prime Agent sessions can be managed from the CLI: -c continues the most recent session, -r [path|id] browses or resumes past sessions, --no-session runs in ephemeral mode (no save), and --fork <path|id> forks a specific session into a new one.[1] As of v0.2.4, the daemon no longer auto-restores on-disk sessions on startup; sessions return only via /resume or --resume, and the agents view lists only sessions the daemon is actively holding.[2] The --resume CLI flag is implemented in main.ts with routing logic also in interactive-mode.ts; the /resume slash command is implemented in command-registry.ts and slash-commands.ts. Both paths are covered by regression tests. The agents view (agents-view-state.ts) orders sessions by most-recent message timestamp, so the most recently active session surfaces at the top of the list automatically. The interactive agents view (interactive-mode.ts) displays the hint text "type to search sessions" when no session is selected, guiding users to filter existing sessions via the input field rather than create new ones. Empty sessions (those that have never received a prompt) report an accurate idle status rather than being misclassified; this behavior is implemented in daemon-session-list.ts and daemon-supervisor.ts. The daemon supervisor evicts empty sessions (those that have never received a prompt) from memory when the client detaches, freeing roster slots; eviction logic lives in daemon-supervisor.ts. Subagent token usage and cost roll up to the parent session view; new agent capabilities should report usage through src/core/usage.ts to ensure costs appear in the agents view. Usage data is aggregated in src/core/usage.ts, propagated through src/core/context-tree.ts and src/core/compaction/, and persisted via daemon-protocol.ts, daemon-session-list.ts, daemon-supervisor.ts, rlm-ledger.ts, and saved-session-info.ts so that cost data survives session restarts. The agents view (agents-view-state.ts, agents-view-mode.ts) displays per-session token counts and accumulated cost on each agent row. The agent roster (agent-roster.ts) exposes a descendant-busy count so that the TUI (agents-view-state.ts, agents-view-mode.ts) and daemon-protocol consumers share a consistent busy status across all session-tree levels, including multi-level RLM hierarchies.
Prime Agent CLI reference: key subcommands for managing sessions and background services.
prime-agent agents # Browse running, idle, and saved sessions
prime-agent attach <agent> # Reattach to a running session
prime-agent --resume <path|id> # Resume a saved session
prime-agent status # Inspect background service state
prime-agent doctor [--fix] # Inspect or repair background services
prime-agent update [--force] # Update Prime Agent
prime-agent shutdown [--force] # Stop every agent, worker, and background service
The /tree command lets users navigate the session tree in-place, select any previous point, continue from there, and switch between branches — all history preserved in a single session file. Pressing Escape twice also opens /tree (see TUI and interactive use for keyboard shortcut details).[1] /fork creates a new session file from a previous user message on the active branch; /clone duplicates the current active branch into a new session file at the current position.[1] Added in v0.2.9, /btw and /side support one-turn inline side questions that use the current context without changing the main session.[4]
The /traces command supports sub-commands status, on, off, preview, upload-current, upload-all, and login; upload is an alias for upload-current.[1]
Sources
Updated
The message queue separates steering (executed during agent work) from follow-up messages (executed after), with each delivery mode offering one-at-a-time or bulk queueing; messages are browsable and editable before dispatch. The editor supports file search via @, image paste via Ctrl+V, and bash integration through !command (output sent to LLM) and !!command (run locally), plus Shift+Enter for newlines.
The message queue supports two delivery modes: Enter queues a steering message (delivered after current tool calls finish), and Alt+Enter queues a follow-up message (delivered only after all agent work finishes).[1] steeringMode and followUpMode settings can be "one-at-a-time" (default — waits for response before delivering the next) or "all" (delivers all queued messages at once).[1]
Queued messages can be browsed individually with Alt+Up / Alt+Down; while browsing, Enter applies the edit as steering input and Alt+Enter applies it as a follow-up. Submitting an empty edit deletes the item.[1] queue-selection.ts is the single source of truth for the interactive mode's queue state; interactive-mode.ts reads derived state from it rather than maintaining its own copy, preventing sync bugs that caused stale queue counts and incorrect hint placement. 4509-side-questions.test.ts and 4741-hint-placement.test.ts are regression tests guarding against queue-state sync failures in the interactive mode's render pipeline.
The editor's @ prefix triggers fuzzy file search, Tab completes paths, Shift+Enter (or Ctrl+Enter on Windows Terminal) inserts a newline, Ctrl+V pastes images (Alt+V on Windows), and !command sends bash output to the LLM while !!command runs without sending.[1] custom-editor.ts (packages/tui/src/components/editor.ts) overrides the base editor.ts key handler; any key handler in custom-editor.ts must explicitly pass through keys it does not intend to intercept. A bug in custom-editor.ts caused the Down Arrow key to be consumed even when the cursor was not on the last line, blocking downward navigation inside multi-line drafts; the fix restores default cursor-movement behavior for Down Arrow during prompt composition. prompt-stash-state.ts (interactive mode package) saves and restores the editor buffer when transitioning between the editor and agents view, preventing silent data loss when navigating away from in-progress drafts. interactive-mode.ts stashes the draft when opening the agents view and restores it on return; behavior is covered by interactive-mode-prompt-stash.test.ts. On terminals that emit raw \n for Shift+Enter, custom-editor.ts and interactive-mode.ts normalize the key event before the send-or-newline branch, ensuring the editor inserts a newline instead of submitting the prompt.
Sources
Updated
Prime Agent ships with Anthropic, OpenAI, and GitHub Copilot built in; authenticate via /login in interactive mode or configure credentials via environment variables, config files, or auth.json in order of precedence. Custom model providers compatible with OpenAI, Anthropic, or Google APIs can be added to ~/.prime/agent/models.json; custom API protocols or OAuth flows require extensions. MCP (Model Context Protocol) is a protocol Prime Agent uses to register and communicate with external model providers; providers are tracked at runtime and can be added dynamically via mcp-command.ts.
Built-in subscription providers are Anthropic Claude Pro/Max, OpenAI ChatGPT Plus/Pro (Codex), and GitHub Copilot; authenticate via /login in interactive mode.[1] A model-registry.ts bug caused Prime Agent to advertise an unsupported Codex client version during model-discovery requests, breaking model enumeration for Codex-backed providers; the fix reports a version string the Codex endpoint accepts. The regression test 702-codex-client-version.test.ts guards against future misalignment between the model-registry.ts version constant and the client version accepted by the upstream Codex API. In packages/ai/src/providers/anthropic.ts, the hardcoded impersonated Claude Code version string is 2.1.257, required for compatibility with Fable 5.x; version mismatches silently break model routing rather than producing explicit errors.
Custom providers and models can be added via ~/.prime/agent/models.json if they speak a supported API (OpenAI, Anthropic, or Google); custom APIs or OAuth require extensions instead.[1] Prime Inference credential precedence follows this order: the PRIME_API_KEY environment variable first, then the Prime CLI config, then auth.json.[2] Extensions are separately installable plugins that enable Prime Agent to support custom API protocols or OAuth authentication flows not covered by built-in providers. Credentials for non-standard APIs cannot be configured in Prime Agent without an extension. In resolve-config-value.ts, the credential resolver treats environment variables set to empty strings as unset, normalizing them to undefined so the fallback chain continues rather than propagating an empty credential value that would cause downstream auth failures. When an MCP provider is added via mcp-command.ts, Prime Agent triggers a refresh of all registered MCP providers so the agent operates with current provider state rather than the stale list cached at startup. The MCP provider refresh logic is coordinated by agent-session.ts, threaded through interactive-mode-services.ts and interactive-mode.ts; interactive-mode-services.ts owns the post-add refresh contract. Test coverage for MCP provider refresh after a provider is added resides in test/interactive-mode-services.test.ts and test/interactive-mode-status.test.ts. The OAuth implementation in packages/ai/src/mcp/oauth.ts performs protected-resource metadata discovery before token exchange, following the MCP OAuth spec's Resource Indicator flow; the resource parameter is populated from the /.well-known/oauth-protected-resource document. Test coverage for the MCP OAuth protected-resource discovery sequence resides in packages/ai/test/mcp-oauth.test.ts. In interactive-mode.ts, the list of available connection models is derived from live state on each read rather than stored as a separate field, eliminating stale-state bugs where the model list could diverge from underlying reality after auth changes or model-registry refreshes. In interactive-mode.ts, a shadow boolean flag for session-started state has been replaced with a direct read of messageCount, eliminating stale-state bugs where the session indicator could diverge from underlying reality. In packages/ai/src/providers/openai-completions.ts, Anthropic prompt-caching logic advances the cache-control marker through the full message sequence, including tool-result messages, positioning the marker after all results before applying the final cache-control annotation to ensure optimal cache hits. Test coverage for Anthropic cache-control marker placement across tool results in multi-turn conversations resides in openai-completions-cache-control-format.test.ts.
As of v0.2.0, /effort sets the reasoning level, replacing the previous Shift+Tab thinking-level cycle.[3] Reasoning capability metadata for Prime Agent models is defined in packages/ai/src/types.ts; adding a reasoning-capable model no longer requires manual code changes — the capability is sourced from the provider's catalog entry. The generate-models.ts script pulls reasoning capability flags from upstream provider metadata; provider adapters for Anthropic, Google, Google Vertex, Amazon Bedrock, OpenAI-completions, and openrouter-reasoning.ts consume these flags. Reasoning-level support (low/medium/high token budgets for thinking models) is derived from provider metadata in the packages/ai layer rather than maintained as a static per-model list. generate-models.ts generates the Prime Inference model catalog at packages/ai/src/models.generated.ts; the file should not be hand-edited. model-resolver.ts resolves model identifiers against the generated catalog at packages/ai/src/models.generated.ts; drift between the two — or between either and related tests — causes runtime resolution failures. The /fast effort toggle is supported for sessions authenticated via raw OpenAI API keys; model-selection and header logic are implemented in packages/ai/src/models.ts, providers/openai-responses.ts, and providers/openai-codex-responses.ts, with interactive-mode availability checks enforced in interactive-mode.ts. The Prime Inference provider integration contributes models to the generated catalog (packages/ai/src/models.generated.ts); test coverage for Prime Inference model availability resides in test/prime-inference-models.test.ts. The Fireworks provider integration contributes models to the Prime Inference model catalog (packages/ai/src/models.generated.ts); test coverage for Fireworks model availability resides in test/fireworks-models.test.ts.
Sources