# Prime Agent Documentation

> Technical reference for the Prime Agent monorepo: RLM control plane, continual harness, sessions, skills, daemon and ACP modes, multi-provider auth, and the coding-agent SDK surface.

## Context Links

- [Agent index](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/llms.txt)
- [Human interactive docs](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1)
- [GitHub repository](https://github.com/PrimeIntellect-ai/prime-agent)

## Repository Metadata

- Repository: PrimeIntellect-ai/prime-agent

- Generated: 2026-08-06T19:56:30.584Z
- Updated: 2026-08-06T19:56:44.564Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 25

## Page Index

- 01. [Overview](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/01-overview.md) - What Prime Agent exposes, primary entry points (CLI, SDK, modes), runtime assumptions, and the first docs routes to follow.
- 02. [Installation](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/02-installation.md) - Stable install command, release checksum flow, prime-agent binary placement, IPython runtime prep, and first-launch success signals.
- 03. [Quickstart](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/03-quickstart.md) - Start prime-agent in a project directory, complete onboarding, run a first task, and verify interactive session health.
- 04. [Authentication and providers](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/04-authentication-and-providers.md) - Login and API-key paths, multi-provider selection, OAuth examples, auth error recovery, and BYOK provider boundaries.
- 05. [RLM control plane](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/05-rlm-control-plane.md) - Recursive Language Model model: persistent IPython as the control tool, prompt-as-variable context, and rlm(...) subagent calls.
- 06. [Continual Harness](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/06-continual-harness.md) - Durable harness state (prompts, memories, skill specs, subagent specs), refine boundaries, immutable base prompt, and rollback snapshots.
- 07. [Sessions and runtime](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/07-sessions-and-runtime.md) - Agent session lifecycle, session services, runtime events, queueing, tree navigation, and session-scoped vs durable state.
- 08. [Skills model](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/08-skills-model.md) - Skills as importable Python packages, SKILL.md frontmatter constraints, collision precedence, and project vs personal skill scope.
- 09. [Subagents and messaging](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/09-subagents-and-messaging.md) - rlm-spawned child agents, agent-message skill surface, direct agent-to-agent communication, and multi-agent orchestration constraints.
- 10. [Run daemon-backed sessions](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/10-run-daemon-backed-sessions.md) - Background daemon client modes, active session state, detach and reattach, resume selectors, and worker recovery verification.
- 11. [Create and install skills](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/11-create-and-install-skills.md) - Author a skill package with skill-creator, required SKILL.md fields, Python package layout, and load-path verification.
- 12. [Refine harness state](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/12-refine-harness-state.md) - Run /refine against the current trajectory, apply evidence-backed harness updates, serialize refine, and use snapshots for rollback.
- 13. [Long-running tasks](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/13-long-running-tasks.md) - Goals, compaction, heartbeats, autonomous mode, and retained subagents that keep multi-turn work progressing across disconnects.
- 14. [Use ACP mode](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/14-use-acp-mode.md) - Run the Agent Client Protocol mode, map cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations.
- 15. [Extensions and custom tools](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/15-extensions-and-custom-tools.md) - Register extensions, custom tools, plan-mode and subagent extension samples, allowlists, and dynamic resource patterns.
- 16. [Session configuration reference](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/16-session-configuration-reference.md) - Agent session config keys, defaults, reload behavior, in-memory settings, and config surfaces used by runtime services.
- 17. [Settings and provider keys](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/17-settings-and-provider-keys.md) - Provider registration, model selection, API key and OAuth wiring, dynamic provider updates, and 401 stale-provider recovery.
- 18. [Built-in skills reference](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/18-built-in-skills-reference.md) - Catalog of shipped skills (goal, refine, compact, heartbeat, observe, message, edit, integrations) with entry modules and invocation roles.
- 19. [Agent connection modes](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/19-agent-connection-modes.md) - Daemon, in-process, and snapshot connection paths; agents-view mode; active session state and transfer idempotency constraints.
- 20. [ACP protocol reference](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/20-acp-protocol-reference.md) - ACP mode entrypoints, event types, meta fields, stop reasons, kernel features, and protocol-facing constraints from source and tests.
- 21. [Minimal SDK agent](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/21-minimal-sdk-agent.md) - Copy-paste minimal SDK bootstrap, custom prompt and model wiring, and expected first-run output from examples.
- 22. [Skills, tools, and extensions](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/22-skills-tools-and-extensions.md) - SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring.
- 23. [Sessions and full control](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/23-sessions-and-full-control.md) - SDK session management, settings injection, session runtime hooks, and the full-control composition example.
- 24. [Troubleshooting](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/24-troubleshooting.md) - Auth failures, invalid resume selectors, provider 401s, network retry, worker recovery, and connection-mode failure probes.
- 25. [Develop from source](https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/25-develop-from-source.md) - Monorepo workspaces, build and check scripts, package boundaries (ai, agent, coding-agent, tui), and contributor conventions from AGENTS.md.

## Source File Index

- `AGENTS.md`
- `package.json`
- `packages/agent/README.md`
- `packages/agent/src/agent-loop.ts`
- `packages/agent/src/agent.ts`
- `packages/ai/README.md`
- `packages/ai/src/providers/amazon-bedrock.ts`
- `packages/coding-agent/docs/acp.md`
- `packages/coding-agent/docs/agent-connection.md`
- `packages/coding-agent/examples/extensions/dynamic-resources/SKILL.md`
- `packages/coding-agent/examples/extensions/plan-mode/README.md`
- `packages/coding-agent/examples/extensions/README.md`
- `packages/coding-agent/examples/extensions/subagent/agents.ts`
- `packages/coding-agent/examples/extensions/subagent/README.md`
- `packages/coding-agent/examples/README.md`
- `packages/coding-agent/examples/sdk/01-minimal.ts`
- `packages/coding-agent/examples/sdk/02-custom-model.ts`
- `packages/coding-agent/examples/sdk/03-custom-prompt.ts`
- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/examples/sdk/05-tools.ts`
- `packages/coding-agent/examples/sdk/06-extensions.ts`
- `packages/coding-agent/examples/sdk/07-context-files.ts`
- `packages/coding-agent/examples/sdk/08-prompt-templates.ts`
- `packages/coding-agent/examples/sdk/09-api-keys-and-oauth.ts`
- `packages/coding-agent/examples/sdk/10-settings.ts`
- `packages/coding-agent/examples/sdk/11-sessions.ts`
- `packages/coding-agent/examples/sdk/12-full-control.ts`
- `packages/coding-agent/examples/sdk/13-session-runtime.ts`
- `packages/coding-agent/examples/sdk/README.md`
- `packages/coding-agent/README.md`
- `packages/coding-agent/skills/agent-message/SKILL.md`
- `packages/coding-agent/skills/agent-observe/SKILL.md`
- `packages/coding-agent/skills/compact/SKILL.md`
- `packages/coding-agent/skills/edit/SKILL.md`
- `packages/coding-agent/skills/edit/src/edit/__init__.py`
- `packages/coding-agent/skills/goal/SKILL.md`
- `packages/coding-agent/skills/refine/SKILL.md`
- `packages/coding-agent/skills/refine/src/refine/__init__.py`
- `packages/coding-agent/skills/rlm-heartbeat/SKILL.md`
- `packages/coding-agent/skills/skill-creator/SKILL.md`
- `packages/coding-agent/src/core/agent-messages.ts`
- `packages/coding-agent/src/core/agent-session-config.ts`
- `packages/coding-agent/src/core/agent-session-runtime.ts`
- `packages/coding-agent/src/core/agent-session-services.ts`
- `packages/coding-agent/src/core/agent-session.ts`
- `packages/coding-agent/src/modes/acp/acp-events.ts`
- `packages/coding-agent/src/modes/acp/acp-meta.ts`
- `packages/coding-agent/src/modes/acp/acp-mode.ts`
- `packages/coding-agent/src/modes/acp/acp-stop-reason.ts`
- `packages/coding-agent/src/modes/agents-view/agents-view-mode.ts`
- `packages/coding-agent/src/modes/daemon/active-session-state.ts`
- `packages/coding-agent/test/acp-cold-cli.test.ts`
- `packages/coding-agent/test/acp-events.test.ts`
- `packages/coding-agent/test/acp-rlm-subagents.test.ts`
- `packages/coding-agent/test/agent-connection-daemon.test.ts`
- `packages/coding-agent/test/agent-connection-in-process.test.ts`
- `packages/coding-agent/test/agent-connection-snapshot.test.ts`
- `packages/coding-agent/test/agent-session-config.test.ts`
- `packages/coding-agent/test/agent-session-dynamic-provider.test.ts`
- `packages/coding-agent/test/fixtures/skills/missing-description/SKILL.md`
- `packages/coding-agent/test/fixtures/skills/python-skill/SKILL.md`
- `packages/coding-agent/test/fixtures/skills/python-skill/src/python_skill/__init__.py`
- `packages/coding-agent/test/fixtures/skills/valid-skill/SKILL.md`
- `packages/coding-agent/test/suite/acp-mode.test.ts`
- `packages/coding-agent/test/suite/agent-session-compaction.test.ts`
- `packages/coding-agent/test/suite/agent-session-goal.test.ts`
- `packages/coding-agent/test/suite/agent-session-model-extension.test.ts`
- `packages/coding-agent/test/suite/agent-session-refine-skill.test.ts`
- `packages/coding-agent/test/suite/agent-session-runtime.test.ts`
- `packages/coding-agent/test/suite/agent-session-serialized-refine.test.ts`
- `packages/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts`
- `packages/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts`
- `packages/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts`
- `packages/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts`
- `packages/coding-agent/test/suite/regressions/3616-settings-inmemory-reload.test.ts`
- `packages/coding-agent/test/suite/regressions/4435-auth-error-login-guidance.test.ts`
- `packages/coding-agent/test/suite/regressions/4482-heartbeat-injected-prompt.test.ts`
- `packages/coding-agent/test/suite/regressions/4491-provider-stale-after-401.test.ts`
- `packages/coding-agent/test/suite/regressions/4537-nonblocking-onboarding.test.ts`
- `packages/coding-agent/test/suite/regressions/4573-prime-inference-login-default.test.ts`
- `packages/coding-agent/test/suite/regressions/4575-model-auth-selection.test.ts`
- `packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts`
- `packages/coding-agent/test/suite/regressions/4656-resume-active-session.test.ts`
- `packages/coding-agent/test/suite/regressions/4658-onboarding-transitions.test.ts`
- `packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts`
- `packages/coding-agent/test/suite/regressions/4722-invalid-resume-selector.test.ts`
- `packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts`
- `packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts`
- `packages/tui/README.md`
- `prime-agent-runtime/src/rlm/__init__.py`
- `README.md`

---

## 01. Overview

> What Prime Agent exposes, primary entry points (CLI, SDK, modes), runtime assumptions, and the first docs routes to follow.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/01-overview.md
- Generated: 2026-08-06T19:41:19.573Z

### Source Files

- `README.md`
- `package.json`
- `packages/coding-agent/README.md`
- `packages/agent/src/agent-loop.ts`
- `AGENTS.md`

---
title: "Overview"
description: "What Prime Agent exposes, primary entry points (CLI, SDK, modes), runtime assumptions, and the first docs routes to follow."
---

Prime Agent (`prime-agent`) is an open-source coding and research agent monorepo that exposes a terminal harness, multi-provider model access, a persistent IPython control plane, durable harness state, and a programmatic agent loop. Public install installs the `prime-agent` binary; the workspace packages are `packages/ai`, `packages/agent`, `packages/coding-agent`, and `packages/tui` (Node `>=22.8.0`).

## What Prime Agent exposes

| Surface | Role |
|---------|------|
| Interactive CLI / TUI | Default entry: `prime-agent` in a project directory |
| Session lifecycle CLI | Browse, attach, resume, status, doctor, update, shutdown |
| Persistent IPython kernel | Built-in model tool for files, shell, edits, and inspection |
| Continual harness | Durable supplemental prompts, memories, skill descriptions, subagent specs |
| Skills | Importable Python packages; `/skill:name` and skill creator |
| Extensions / prompt templates | Custom commands, tools, UI, and `/templatename` expansion |
| Programmatic agent loop | `agentLoop` / `agentLoopContinue` in `packages/agent` |
| Headless modes | JSON mode and RPC mode (documented under coding-agent docs) |

Prime Agent began as a hard fork of pi-mono and is now distributed independently. Public releases are versioned tarball artifacts installed by the install script; release packaging rewrites the application package and command to `prime-agent`. Do not use inherited npm package identifiers as the public install path.

## Core abstractions

### Recursive Language Model (RLM)

The RLM model treats context as variables (*prompt-as-a-variable*) and tools and recursive subagents as function calls inside a persistent REPL.

| Capability | Behavior |
|------------|----------|
| Control tool | Persistent IPython is the built-in model tool |
| Programmatic surface | File ops, shell, tools, subagents, and context management run as code |
| Subagents | `rlm(...)` spawns real child agents for parallel or background work and returns results programmatically |
| Agent messaging | Running agents can exchange messages and orchestrate one another without routing everything through the user |

### Continual Harness

The Continual Harness stores supplemental prompts, memories, skill descriptions, and reusable subagent specifications as durable state, local to the session by default.

| Constraint | Behavior |
|------------|----------|
| `/refine` | Reviews the current trajectory and may apply small, evidence-backed updates to supplemental harness state |
| Base system prompt | Immutable; refine never rewrites it |
| Snapshots | Recorded refinement history supports rollback |
| Skills boundary | Refine does not replace packaging and reviewing new executable skills |

```text
┌─────────────────────────────────────────────────────────────┐
│  User / editor / automation                                 │
│    prime-agent  |  attach/resume  |  agentLoop(...)         │
└────────────────────────────┬────────────────────────────────┘
                             │
┌────────────────────────────▼────────────────────────────────┐
│  Session runtime (coding-agent TUI / daemon client)         │
│  messages · queue · tree/fork · compact · goals · autonomous│
└────────────────────────────┬────────────────────────────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
┌───────────────┐   ┌─────────────────┐   ┌──────────────────┐
│ RLM control   │   │ Continual       │   │ packages/agent   │
│ IPython tool  │   │ Harness state   │   │ agentLoop        │
│ rlm(...)      │   │ /refine         │   │ EventStream      │
└───────────────┘   └─────────────────┘   └──────────────────┘
        │
        ▼
┌───────────────────────────────────────┐
│ packages/ai — providers, models,      │
│ streamSimple / tool-capable models    │
└───────────────────────────────────────┘
```

## Primary entry points

### Install and first launch

Stable install (macOS or Linux):

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```

Beta from latest `main`:

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh -s -- beta
```

The installer downloads a versioned release, verifies its SHA-256 checksum, installs the `prime-agent` command, and can prepare the IPython runtime. The Python kernel runtime is also set up automatically on first invocation. Set `PRIME_AGENT_KERNEL_PYTHON` to point at an existing Python environment that provides `ipykernel`.

Start in the directory the agent should work in:

```bash
cd /path/to/project
prime-agent
```

On first launch, run `/login` to choose a subscription or API-key provider, or export a provider key (for example `ANTHROPIC_API_KEY`) before starting.

### Session and service commands

| Command | Purpose |
|---------|---------|
| `prime-agent` | Interactive session in the current directory |
| `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 |

### Interactive commands (selected)

Type `/` in the editor. Extensions can register custom commands; skills appear as `/skill:name`; prompt templates expand via `/templatename`.

| Command | Description |
|---------|-------------|
| `/login`, `/logout` | OAuth authentication |
| `/model` | Switch models (also Ctrl+L) |
| `/effort` | Set reasoning / thinking level |
| `/settings` | Thinking level, theme, message delivery, transport |
| `/resume` | Searchable session view |
| `/new`, `/clear` | Start a new session |
| `/tree` | Jump to any point in the session and continue |
| `/fork` | New session from a previous user message |
| `/clone` | Duplicate the active branch into a new session |
| `/compact [prompt]` | Manual context compaction |
| `/reload` | Reload keybindings, extensions, skills, prompts, context files |
| `/quit` | Quit Prime Agent |

Long-running work surfaces (TUI and autonomous runs) also include `/goal`, `/heartbeat` / `rlm_heartbeat`, `prime-agent schedule`, and `/autonomous` with configured turn, token, and time budgets.

### Message queue while the agent works

| Input | Delivery |
|-------|----------|
| Enter | *Steering* message after the current assistant turn finishes its tool calls |
| Alt+Enter | *Follow-up* message after the agent finishes all work |
| Ctrl+C | Interrupt active work; restore queued messages to the editor |
| Escape | Clear input without interrupting active work |
| Alt+Up | Pull queued messages back into the editor |

### Programmatic agent loop

`packages/agent/src/agent-loop.ts` exports a stream-based loop that keeps `AgentMessage` values through the turn and transforms to LLM `Message[]` only at the provider call boundary.

| Export | Use |
|--------|-----|
| `agentLoop(prompts, context, config, signal?, streamFn?)` | Start a loop with new prompt messages |
| `agentLoopContinue(context, config, signal?, streamFn?)` | Continue without adding a prompt (retries); last context message must convert to `user` or `toolResult` |
| `runAgentLoop` / `runAgentLoopContinue` | Async emit-sink variants returning `AgentMessage[]` |

```ts
// packages/agent — stream API shape
export function agentLoop(
  prompts: AgentMessage[],
  context: AgentContext,
  config: AgentLoopConfig,
  signal?: AbortSignal,
  streamFn?: StreamFn,
): EventStream<AgentEvent, AgentMessage[]>;

export function agentLoopContinue(
  context: AgentContext,
  config: AgentLoopConfig,
  signal?: AbortSignal,
  streamFn?: StreamFn,
): EventStream<AgentEvent, AgentMessage[]>;
```

| Constraint | Detail |
|------------|--------|
| Empty context | `agentLoopContinue` throws `"Cannot continue: no messages in context"` |
| Last role assistant | Throws `"Cannot continue from message role: assistant"` |
| Abort | Abort produces assistant messages with `stopReason: "aborted"` and error `"Request was aborted"` |
| Stream completion | Stream ends on `agent_end`; failure ends with `[]` |
| Emitted events (start path) | `agent_start`, `turn_start`, `message_start` / `message_end` per prompt, then the shared turn loop |

Monorepo root also exposes profiling helpers for TUI and RPC modes (`profile:tui`, `profile:rpc`).

## Runtime assumptions

| Assumption | Requirement / default |
|------------|------------------------|
| Host platforms | Installer targets macOS and Linux; coding-agent docs also cover Windows, Termux, tmux, and terminal setup |
| Node (from source / workspaces) | `engines.node`: `>=22.8.0` |
| Working directory | Agent operates in the current directory; can run commands and modify files there |
| Model tool | Default tool is `ipython` (persistent kernel) |
| Kernel Python | Auto-prepared on first use, or `PRIME_AGENT_KERNEL_PYTHON` with `ipykernel` |
| Auth | Subscription OAuth via `/login` or provider API keys (BYOK); multi-provider selection via `/model` |
| User config paths | e.g. `~/.prime/agent/keybindings.json`, `~/.prime/agent/models.json` |
| Continuity | Daemon-backed sessions keep IPython state, schedules, and subagents running across detach/reattach |
| Long tasks | Compaction, goals, heartbeats, schedules, autonomous budgets, retained subagents |

<Warning>
Prime Agent executes model-generated Python and project commands with your user permissions. Worker and kernel processes improve lifecycle isolation and recovery; they are **not** a security sandbox. Review changes and use trusted repositories, instructions, skills, and extensions only. Run untrusted code or instructions in an external sandbox or restricted environment. Prefer a disposable clone, clean worktree, or another checkpoint you can inspect and restore.
</Warning>

## Providers (BYOK / multi-provider)

Authentication is provider-neutral: use subscription OAuth (`/login`) or API keys. Built-in providers maintain lists of tool-capable models updated each release. Custom providers that speak supported APIs (OpenAI, Anthropic, Google) can be added via `~/.prime/agent/models.json`; custom APIs or OAuth use extensions.

**Subscriptions (examples):** Anthropic Claude Pro/Max, OpenAI ChatGPT Plus/Pro (Codex), GitHub Copilot.

**API-key providers (examples):** Anthropic, OpenAI, Prime Inference, Azure OpenAI, DeepSeek, Google Gemini, Google Vertex, Amazon Bedrock, Mistral, Groq, Cerebras, Cloudflare AI Gateway / Workers AI, xAI, OpenRouter, Vercel AI Gateway, ZAI, OpenCode Zen / Go, Hugging Face, Fireworks, Kimi For Coding, MiniMax, Xiaomi MiMo and regional MiMo Token Plan endpoints.

## Monorepo map

| Package / path | Responsibility |
|----------------|----------------|
| `packages/coding-agent` | CLI, TUI harness, docs, examples (extensions, sandbox) |
| `packages/agent` | Agent loop, context, tools, events |
| `packages/ai` | Providers, models, streaming, credential detection |
| `packages/tui` | Terminal UI primitives |
| Root `npm run build` | Builds `tui` → `ai` → `agent` → `coding-agent` |
| Root `npm run check` | Biome, typecheck, installer and browser smoke checks (does not run tests) |

Issue labels used for package ownership: `pkg:agent`, `pkg:ai`, `pkg:coding-agent`, `pkg:tui`.

## First route map

Use this order for a first successful session, then deepen by surface.

```text
Install ──► Auth ──► Interactive session ──► RLM / harness
   │            │              │
   │            │              ├── daemon attach / resume
   │            │              ├── skills / extensions
   │            │              └── agentLoop / JSON·RPC
   └── doctor / update when background services misbehave
```

| Goal | Docs route |
|------|------------|
| Install binary, checksum, IPython prep | [Installation](/installation) |
| First project session | [Quickstart](/quickstart) |
| Login, API keys, provider boundaries | [Authentication and providers](/authentication-providers) |
| IPython control plane and `rlm(...)` | [RLM control plane](/rlm-control-plane) |
| Durable harness and refine | [Continual Harness](/continual-harness) |
| Session lifecycle and state | [Sessions and runtime](/sessions-runtime) |
| Background detach / reattach | [Run daemon-backed sessions](/daemon-sessions) |
| Long-running goals and autonomous budgets | [Long-running tasks](/long-running-tasks) |
| Minimal programmatic agent | [Minimal SDK agent](/sdk-minimal) |
| Build from source | [Develop from source](/develop-from-source) |

## Next

<CardGroup>
  <Card title="Installation" href="/installation">
    Stable install command, release checksum flow, binary placement, IPython runtime prep, first-launch signals.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    Start in a project directory, complete onboarding, run a first task, verify interactive health.
  </Card>
  <Card title="Authentication and providers" href="/authentication-providers">
    Login and API-key paths, multi-provider selection, OAuth, auth recovery, BYOK boundaries.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython, prompt-as-variable context, and `rlm(...)` subagent calls.
  </Card>
  <Card title="Continual Harness" href="/continual-harness">
    Durable prompts, memories, skill and subagent specs; refine boundaries and rollback snapshots.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Copy-paste SDK bootstrap, prompt and model wiring, expected first-run output.
  </Card>
</CardGroup>

---

## 02. Installation

> Stable install command, release checksum flow, prime-agent binary placement, IPython runtime prep, and first-launch success signals.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/02-installation.md
- Generated: 2026-08-06T19:41:14.360Z

### Source Files

- `README.md`
- `package.json`
- `packages/coding-agent/README.md`
- `packages/coding-agent/test/suite/regressions/4537-nonblocking-onboarding.test.ts`
- `packages/coding-agent/test/suite/regressions/4573-prime-inference-login-default.test.ts`

---
title: "Installation"
description: "Stable install command, release checksum flow, prime-agent binary placement, IPython runtime prep, and first-launch success signals."
---

Prime Agent ships as **versioned tarball releases** installed by `https://app.primeintellect.ai/prime-agent/install.sh`. The installer downloads a release artifact, verifies its **SHA-256** checksum, places the public `prime-agent` command on your machine, and can prepare the **IPython** runtime the agent uses as its default control tool. Public packaging rewrites the application package and command name to `prime-agent`; do not install the inherited `@earendil-works/pi-*` / source-package `pi` bin as the product install path.

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Platform for install script | **macOS** or **Linux** (documented stable install path) |
| Working directory | Launch from the project directory the agent should operate in |
| Auth (first interactive use) | Subscription via `/login`, or an API key in the environment |
| Python kernel (optional override) | Existing environment with `ipykernel` via `PRIME_AGENT_KERNEL_PYTHON` |

<Warning>
Prime Agent runs model-generated Python and project commands with **your user permissions**. Worker and kernel processes improve lifecycle isolation and recovery; they are **not** a security sandbox. Use trusted repositories, instructions, skills, and extensions only. Run untrusted work in an external sandbox or restricted environment.
</Warning>

## Stable install

Install the latest stable release:

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```

### What the installer does

| Step | Behavior |
|------|----------|
| Fetch | Downloads a **versioned** release tarball artifact |
| Verify | Checks the artifact **SHA-256** checksum |
| Install | Installs the public `prime-agent` command |
| Runtime | Can prepare the **IPython** runtime used by the agent |

Release packaging rewrites the application package and CLI entry to `prime-agent`. Treat the curl installer (or `prime-agent update`) as the supported end-user path—not the monorepo’s inherited npm/`pi` identifiers.

### Beta install

Install the beta built from the latest commit on `main`:

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh -s -- beta
```

## `prime-agent` command placement

After a successful install, the public binary name is **`prime-agent`**. Confirm it resolves on `PATH`:

```bash
command -v prime-agent
prime-agent
```

| Surface | Role |
|---------|------|
| `prime-agent` | Public CLI entry after release packaging |
| Inherited `pi` / `@earendil-works/pi-*` | Source-workspace compatibility only—**not** the product install path |

### Update and lifecycle commands

```bash
prime-agent update [--force]         # Update Prime Agent
prime-agent status                   # Inspect background service state
prime-agent doctor [--fix]           # Inspect or repair background services
prime-agent shutdown [--force]       # Stop every agent, worker, and background service
```

Session and attach helpers available after install:

```bash
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
```

## IPython runtime prep

By default the model has one tool: **`ipython`**. The model uses a persistent kernel to read files, run commands, edit code, and inspect data.

| Behavior | Detail |
|----------|--------|
| Automatic setup | Python kernel runtime is set up **automatically on first invocation** |
| Installer assist | Install script **can prepare** the IPython runtime during install |
| Override | Set `PRIME_AGENT_KERNEL_PYTHON` to an existing Python environment that has **`ipykernel`** |

```bash
# Optional: point the kernel at a pre-provisioned Python with ipykernel
export PRIME_AGENT_KERNEL_PYTHON=/path/to/python
prime-agent
```

Capabilities beyond the default kernel (skills, prompt templates, extensions, packages) are added after install; they are not required for the install step itself.

## First launch

<Steps>
  <Step title="Install stable or beta">
    Run the stable or beta install command above, then confirm `prime-agent` is on `PATH`.
  </Step>
  <Step title="Enter the target project directory">
    Prime Agent works in the **current directory** and can run commands and modify files there. Prefer a disposable clone, clean worktree, or other checkpoint you can inspect and restore.

    ```bash
    cd /path/to/project
    prime-agent
    ```
  </Step>
  <Step title="Authenticate">
    **Subscription / OAuth**

    ```bash
    prime-agent
    /login   # Select provider
    ```

    **API key (example: Anthropic)**

    ```bash
    export ANTHROPIC_API_KEY=sk-ant-...
    prime-agent
    ```

    Built-in providers include subscription paths (Anthropic Claude Pro/Max, OpenAI ChatGPT Plus/Pro Codex, GitHub Copilot) and many API-key providers (Anthropic, OpenAI, Prime Inference, Azure OpenAI, and others). Custom providers can be registered under `~/.prime/agent/models.json` when they speak a supported API (OpenAI, Anthropic, Google).
  </Step>
  <Step title="Select a model if needed">
    Use `/model` or **Ctrl+L** after authentication. Built-in tool-capable model lists are maintained per provider and updated with each release.
  </Step>
</Steps>

### Auth persistence after login

After a successful provider login, the interactive flow **refreshes** available models, **selects** the provider default, and **persists** settings so the next process starts with a valid model. For Prime Inference (`api_key`), that default is **GLM 5.2** (`PRIME_INFERENCE_DEFAULT_MODEL_ID`), with API base `https://api.pinference.ai/api/v1` in the connection model shape used by the runtime.

Order of operations after login success: **refresh → select → persist**.

### Onboarding behavior

Once onboarding has been shown (`onboardingShown: true` in settings), client-local auth mismatch or a missing daemon session model must **not** reopen onboarding. Treat a quiet re-entry (no forced onboarding loop) as expected when onboarding already completed.

## First-launch success signals

Use these checks after install:

| Signal | Expected |
|--------|----------|
| Binary resolves | `command -v prime-agent` prints a path; shell can execute `prime-agent` |
| Interactive UI | **Startup header** with compact brand and runtime summary; messages area; editor; footer (empty by default) |
| Verbose load list | `prime-agent --verbose` lists loaded AGENTS.md files, prompt templates, skills, and extensions |
| Auth | `/login` completes, or API key env is accepted; `/model` / Ctrl+L can switch models |
| Default tool | Session is usable with the built-in **`ipython`** tool |
| Kernel | First invocation completes kernel setup without requiring a manual install step (unless you override via `PRIME_AGENT_KERNEL_PYTHON`) |
| Defaults persisted | After login, default provider/model survive process restart (settings flush) |
| Onboarding | Already-shown onboarding does not reopen for client-local auth mismatch or missing model |
| Update path | `prime-agent update [--force]` is available for later upgrades |
| Services (optional) | `prime-agent status` / `prime-agent doctor` inspect background service state |

<Check>
Healthy first run: install succeeds, `prime-agent` launches in the project directory, auth or API key is configured, the startup header appears, and you can talk to the agent while it uses the IPython kernel.
</Check>

## Config and data locations (post-install)

| Path | Use |
|------|-----|
| `~/.prime/agent/models.json` | Custom providers and models (supported API shapes) |
| `~/.prime/agent/keybindings.json` | Custom keybindings |

Session settings (including default provider/model and `onboardingShown`) are managed by the agent settings layer and flushed so restarts keep valid defaults.

## Release and monorepo notes

For end users, install via the curl script above. The monorepo (`package.json` name `prime-agent`, version **0.7.0**, Node **`>=22.8.0`**) exposes packaging and installer checks used by maintainers:

| Script | Role |
|--------|------|
| `npm run check:installer` | `node scripts/check-installer-render.mjs` |
| `npm run release:pack` | `node scripts/pack-prime-agent-release.mjs` |
| `npm run release:patch` / `release:minor` / `release:major` | Release automation via `scripts/release.mjs` |

CI includes a **Build Binaries** workflow badge; public distribution remains the versioned tarball + install script path described above.

## Common failure probes

| Symptom | What to check |
|---------|----------------|
| `prime-agent` not found | Installer did not complete; shell `PATH` not refreshed; re-run stable install |
| Installer exits mid-download | Network to `app.primeintellect.ai`; re-run so SHA-256 verification can complete on a full artifact |
| Kernel / IPython errors | Let automatic first-invocation setup finish, or set `PRIME_AGENT_KERNEL_PYTHON` to a Python with `ipykernel` |
| Auth / empty model | Run `/login` or export a valid API key; confirm default provider/model after login (refresh → select → persist) |
| Unexpected onboarding again | Confirm `onboardingShown` already true; reopening for client-local auth mismatch or missing model is a regression |
| Stale install | `prime-agent update [--force]`; use beta only when you intentionally want `main` builds |
| Background services | `prime-agent doctor [--fix]`, `prime-agent status`, `prime-agent shutdown [--force]` |

## Related pages

<CardGroup>
  <Card title="Quickstart" href="/quickstart">
    Start in a project directory, complete onboarding, run a first task, and verify interactive session health.
  </Card>
  <Card title="Authentication and providers" href="/authentication-providers">
    Login and API-key paths, multi-provider selection, OAuth examples, and BYOK boundaries.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython as the control tool, prompt-as-variable context, and `rlm(...)` subagent calls.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, API key and OAuth wiring, and 401 recovery.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, network retry, worker recovery, and connection-mode probes.
  </Card>
  <Card title="Develop from source" href="/develop-from-source">
    Monorepo workspaces, build and check scripts, package boundaries, and contributor conventions.
  </Card>
</CardGroup>

---

## 03. Quickstart

> Start prime-agent in a project directory, complete onboarding, run a first task, and verify interactive session health.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/03-quickstart.md
- Generated: 2026-08-06T19:43:57.028Z

### Source Files

- `README.md`
- `packages/coding-agent/README.md`
- `packages/coding-agent/src/core/agent-session.ts`
- `packages/coding-agent/test/suite/regressions/4658-onboarding-transitions.test.ts`
- `packages/coding-agent/examples/sdk/01-minimal.ts`

---
title: "Quickstart"
description: "Start prime-agent in a project directory, complete onboarding, run a first task, and verify interactive session health."
---

`prime-agent` is the interactive CLI entrypoint for Prime Agent. Launched from a project directory, it opens a new interactive session against that working directory, loads credentials and models from `~/.prime/agent/`, and gives the model a single built-in tool: the persistent `ipython` kernel. First launch runs onboarding when no ready model is configured; sessions auto-save under `~/.prime/agent/sessions/` and run as daemon-backed workers so detaching the TUI does not necessarily stop the agent.

<Warning>
Prime Agent executes model-generated Python and project commands with your user permissions. Worker and kernel processes improve lifecycle isolation and recovery; they are **not** a security sandbox. Use trusted repositories, instructions, skills, and extensions only. Prefer a disposable clone or clean worktree for first runs.
</Warning>

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Platform | macOS or Linux for the stable installer path |
| Binary | `prime-agent` on `PATH` (installer), or a source checkout with `./prime-agent.sh` |
| Auth | At least one subscription OAuth login via `/login`, or an API key via env/`auth.json` |
| Project cwd | The directory you want the agent to read and modify |
| Python kernel (optional override) | Auto-bootstrapped on first `ipython` use; set `PRIME_AGENT_KERNEL_PYTHON` to a Python that already has `ipykernel` |

Install is covered in [Installation](/installation). This page assumes you can run `prime-agent` (or the source launcher) from a shell.

## Start path

```text
project cwd
    │
    ▼
prime-agent  ──► interactive TUI + session worker
    │
    ├─ no ready model / first launch ──► onboarding (auth + model)
    │
    └─ model ready ──► editor prompt
                           │
                           ▼
                    user message → provider → ipython tool calls
                           │
                           ▼
              ~/.prime/agent/sessions/*.jsonl  +  optional daemon worker
```

## First session

<Steps>
<Step title="Install or confirm the binary">
Stable release:

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```

Beta built from `main`:

```bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh -s -- beta
```

From a source checkout (Node.js 22.8.0+):

```bash
git clone https://github.com/PrimeIntellect-ai/prime-agent
cd prime-agent
npm ci
./prime-agent.sh
```

`./prime-agent.sh` preserves the directory from which it is invoked, so you can call the absolute path from another project. Use `./prime-agent.sh --dist` after a build to run the bundled CLI.

Verify:

```bash
prime-agent --version
prime-agent --help
```

Success: version prints; help shows usage for `prime-agent [options] [@files...] [message...]` and top-level commands such as `agents`, `attach`, `status`, and `doctor`.
</Step>

<Step title="Enter the project directory and launch">
```bash
cd /path/to/project
prime-agent
```

Defaults that matter on cold start:

| Behavior | Default |
|----------|---------|
| Client mode | Interactive when stdin is a TTY and `--print` is not set |
| Working directory | Process cwd (or `--cwd <dir>`) |
| Session persistence | On (JSONL under `~/.prime/agent/sessions/`); use `--no-session` for ephemeral |
| Startup surface | New chat (not the agents browser); use `prime-agent agents` or left-arrow for session catalog |
| Built-in tool | `ipython` only |

Optional launch shapes:

```bash
prime-agent --provider anthropic --model claude-opus-4-5
prime-agent @README.md "Summarize this repository"
prime-agent -p "List the top-level layout and how to run checks"
```
</Step>

<Step title="Complete onboarding">
On first interactive launch, onboarding runs when global settings do not yet mark onboarding as shown **and** there is no ready model with configured auth. Completing or dismissing onboarding sets `onboardingShown` in `~/.prime/agent/settings.json` so the flow does not reopen on every start.

What you will see:

1. **Splash** (when no models are available, or when continuing from Prime CLI auth) with progress such as `Signing in to Prime Intellect...` and `Preparing models...`
2. **Auth** via Prime Inference login when no candidates exist, or the configuration menu when models are already discoverable
3. **Model selection** (`models` tab of the configuration menu); the splash stays mounted until that menu closes

If a model is already selected and authenticated (for example after exporting an API key), onboarding is skipped.

You can always re-auth later with `/login` and pick models with `/model` (or Ctrl+L).
</Step>

<Step title="Authenticate if needed">
Use one path. Credentials are provider-neutral: subscription OAuth or your own API keys (BYOK).

<Tabs>
<Tab title="Subscription / OAuth">
In the interactive editor:

```text
/login
```

Built-in subscription logins include Claude Pro/Max, ChatGPT Plus/Pro (Codex), and GitHub Copilot. Tokens land in `~/.prime/agent/auth.json` with auto-refresh.
</Tab>

<Tab title="API key (environment)">
```bash
export ANTHROPIC_API_KEY=sk-ant-...
# or OPENAI_API_KEY, PRIME_API_KEY, GEMINI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY, ...
prime-agent
```

Env var names are provider-specific; full tables live under authentication and providers docs.
</Tab>

<Tab title="API key (auth file)">
Store keys in `~/.prime/agent/auth.json` (created mode `0600`). Auth-file credentials take priority over environment variables.

```json
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "prime-inference": { "type": "api_key", "key": "..." }
}
```

You can also run `/login`, choose an API-key provider, and paste a key so it is written for you.
</Tab>
</Tabs>

If no models resolve, the CLI surfaces:

```text
No models available. Use /login to log into a provider via OAuth or API key.
```

For a selected model without credentials:

```text
No API key found for <provider>.
```

Recover with `/login`, then `/model` if needed.
</Step>

<Step title="Run a first task">
In the editor, type a request and press Enter:

```text
Summarize this repository and tell me how to run its checks.
```

Expected runtime behavior:

- The assistant streams a response in the messages pane.
- Tool use appears as `ipython` cells (Python control plane: files, shell magics, skills, subagents).
- The long-lived kernel bootstraps on first tool use; override Python with `PRIME_AGENT_KERNEL_PYTHON` or a managed venv via `PRIME_AGENT_KERNEL_VENV` when you need a fixed environment.
- Session transcript appends under `~/.prime/agent/sessions/`.

Useful first prompts:

```text
Review authentication and test coverage as independent subtasks. Run them in parallel, then synthesize the findings.
```

```text
!npm run check
```

Notes:

- `@` fuzzy-searches project files; `@path` can also be passed on the CLI.
- `!command` runs a shell command and sends output to the model; `!!command` runs without adding output to context.
- During agent work, project commands usually run inside the IPython control environment (for example a `%%bash` cell), not as separate built-in tools.
- Multi-line input: Shift+Enter (Ctrl+Enter on Windows Terminal).
</Step>

<Step title="Verify interactive session health">
Confirm the session is usable with these signals:

| Check | How | Healthy signal |
|-------|-----|----------------|
| UI ready | After onboarding | Startup header + empty editor; no hard auth error banner |
| Model bound | `/session` or footer via `/usage` | Model/provider present; `/usage` shows context/token fields |
| Auth valid | Send a short prompt | Streaming text or tool cells; not `401` / “Authentication failed” |
| Session file | `/session` | Session ID and path under `~/.prime/agent/sessions/` |
| Background service | `prime-agent status` | Supervisor/worker status reports without fatal socket errors |
| Detach safety | Close TUI, then reattach | Worker still listed; `prime-agent attach <agent>` restores the UI |
| Diagnostics | `prime-agent doctor` | Clean report, or `--fix` clears stale sockets/orphans |

```bash
prime-agent status
prime-agent list
prime-agent doctor
prime-agent agents
prime-agent attach <agent>
```

Continue or resume later:

```bash
prime-agent -c                  # most recent session
prime-agent -r <path|id>        # resume a saved session
prime-agent --resume <path|id>  # same selector form
```

Inside the TUI: `/resume`, `/new`, `/tree`, `/fork`, `/clone`, `/reload`.
</Step>
</Steps>

## Interactive surface

From top to bottom:

| Region | Role |
|--------|------|
| Startup header | Brand + runtime summary; `--verbose` lists loaded `AGENTS.md`, prompts, skills, extensions |
| Messages | User text, assistant output, `ipython` tool calls/results, notifications, errors |
| Editor | Prompts, `/` commands, `@` files, `!` shell |
| Footer | Empty by default; `/usage` for tokens, cost, context |

While the agent is working:

| Input | Effect |
|-------|--------|
| Enter | Queue a *steering* message (after current tool batch) |
| Alt+Enter | Queue a *follow-up* (after the agent finishes all work) |
| Ctrl+C | Interrupt; twice exits while the exit hint is visible |
| Escape | Clear input without interrupting |

## Project instructions

Prime Agent loads context files at startup:

| Location | Scope |
|----------|-------|
| `~/.prime/agent/AGENTS.md` | Global |
| `AGENTS.md` or `CLAUDE.md` walking from parents into cwd | Project and ancestors |

Example `AGENTS.md`:

```markdown
# Project Instructions

- Run `npm run check` after code changes.
- Do not run production migrations locally.
- Keep responses concise.
```

System prompt overrides (optional):

| File | Effect |
|------|--------|
| `.prime/agent/SYSTEM.md` or `~/.prime/agent/SYSTEM.md` | Replace default system prompt |
| `APPEND_SYSTEM.md` in either location | Append without replacing |

After edits, restart or run `/reload`. Disable discovery with `--no-context-files` / `-nc`.

## Config and state paths

Default agent directory: `~/.prime/agent/` (override with `PRIME_AGENT_CODING_AGENT_DIR` when the package is configured as `prime-agent`).

| Path | Purpose |
|------|---------|
| `auth.json` | OAuth tokens and API keys |
| `settings.json` | Defaults, including `onboardingShown` |
| `sessions/` | JSONL session transcripts |
| `logs/` | Client, daemon, and agent diagnostics |
| `models.json` | Custom models / providers |

## Non-interactive and SDK smoke tests

One-shot print mode (also merges piped stdin into the prompt):

```bash
prime-agent -p "Summarize this codebase"
cat README.md | prime-agent -p "Summarize this text"
```

Headless event modes: `--mode json`, `--mode rpc`. Editor integrations: `--mode acp` (see ACP docs).

Minimal programmatic session (package identifiers in source retain the `@earendil-works/pi-coding-agent` name):

```typescript
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?");
```

`createAgentSession()` discovers skills, extensions, tools, and context from cwd and the agent dir; model comes from settings or the first available authenticated model.

## Common failure signals

| Symptom | Likely cause | Recovery |
|---------|--------------|----------|
| `No models available` | No OAuth/API key configured | `/login` or export provider env var; see [Authentication and providers](/authentication-providers) |
| `No API key found for <provider>` | Model selected without credentials | `/login` or set the provider env key / `auth.json` entry |
| `Authentication failed for "..."` | Expired token, bad key, or network | `/login`; message guidance includes `Run /login to update credentials.` |
| Onboarding never returns | Auth cancelled mid-flow | Exit and relaunch; use `/login` then `/model` |
| Kernel bootstrap errors | Missing Python/`ipykernel` | Set `PRIME_AGENT_KERNEL_PYTHON` to a working interpreter with `ipykernel` |
| `session_already_active` / lease errors | Another worker owns the session | `prime-agent list`, attach the active agent, or stop the conflicting worker |
| Stale sockets / orphaned services | Unclean shutdown | `prime-agent doctor --fix`; escalate with `prime-agent shutdown` if needed |
| Detach loses the UI but work continues | Expected resident worker behavior | `prime-agent agents` / `attach <agent>` rather than starting a duplicate session |

## Useful commands after first success

```bash
prime-agent agents                   # Browse running, idle, and saved sessions
prime-agent attach <agent>           # Reattach interactive UI
prime-agent --resume <path|id>       # Resume a saved session
prime-agent status                   # Background service state
prime-agent doctor [--fix]           # Inspect or repair services
prime-agent update [--force]         # Update the installed binary
prime-agent shutdown [--force]       # Stop agents, workers, and supervisor
```

In-session commands worth knowing early: `/model`, `/effort`, `/settings`, `/usage`, `/session`, `/compact`, `/refine`, `/goal`, `/hotkeys`, `/quit`.

## Next

<CardGroup>
<Card title="Authentication and providers" href="/authentication-providers">
Login, API keys, multi-provider selection, and auth recovery.
</Card>
<Card title="RLM control plane" href="/rlm-control-plane">
Persistent IPython as the control tool, prompt-as-variable context, and `rlm(...)` subagents.
</Card>
<Card title="Sessions and runtime" href="/sessions-runtime">
Session lifecycle, queueing, tree navigation, and durable vs session-scoped state.
</Card>
<Card title="Run daemon-backed sessions" href="/daemon-sessions">
Detach, reattach, resume selectors, and worker recovery.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Auth failures, resume selectors, provider 401s, and connection probes.
</Card>
<Card title="Minimal SDK agent" href="/sdk-minimal">
Copy-paste SDK bootstrap and first-run output.
</Card>
</CardGroup>

---

## 04. Authentication and providers

> Login and API-key paths, multi-provider selection, OAuth examples, auth error recovery, and BYOK provider boundaries.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/04-authentication-and-providers.md
- Generated: 2026-08-06T19:44:08.370Z

### Source Files

- `packages/coding-agent/examples/sdk/09-api-keys-and-oauth.ts`
- `packages/coding-agent/test/suite/regressions/4435-auth-error-login-guidance.test.ts`
- `packages/coding-agent/test/suite/regressions/4573-prime-inference-login-default.test.ts`
- `packages/coding-agent/test/suite/regressions/4575-model-auth-selection.test.ts`
- `packages/ai/src/providers/amazon-bedrock.ts`
- `packages/coding-agent/test/agent-session-dynamic-provider.test.ts`

---
title: "Authentication and providers"
description: "Login and API-key paths, multi-provider selection, OAuth examples, auth error recovery, and BYOK provider boundaries."
---

Prime Agent resolves model credentials through `AuthStorage` and `ModelRegistry`. Interactive sessions use `/login` and `/logout`; the CLI accepts `--api-key` as a non-persisted runtime override; the SDK constructs the same stack with `AuthStorage.create()` and optional custom paths. Credentials live under `~/.prime/agent/auth.json` by default (override with `PRIME_AGENT_CODING_AGENT_DIR`), file mode `0600`. Provider catalog and keys stay BYOK/BYOC: each provider supplies its own key, OAuth token, or ambient cloud credentials; nothing requires a single hosted model vendor.

## Credential surfaces

| Surface | Path / flag | Persisted | Notes |
|---------|-------------|-----------|--------|
| Auth file | `~/.prime/agent/auth.json` | Yes | API keys (`type: "api_key"`) and OAuth tokens (`type: "oauth"`) |
| Models file | `~/.prime/agent/models.json` | Yes | Custom providers; optional `apiKey` fallback |
| Prime CLI config | `~/.prime/config.json` | Yes | Prime Inference key, team selection, base URLs |
| Environment | Provider-specific vars | No | See table below |
| Runtime | `--api-key` / `setRuntimeApiKey` | No | Highest priority; process-scoped |
| Ambient cloud | AWS / GCP ADC | No | Bedrock, Vertex; not plain API-key env vars |

:::files
~/.prime/
├── config.json          # Prime CLI / Inference (api_key, team_id, urls)
└── agent/
    ├── auth.json        # Provider API keys + OAuth credentials
    ├── models.json      # Custom / local providers (optional keys)
    └── settings.json    # Default provider/model after login
:::

## Credential resolution order

`AuthStorage.getApiKey()` walks sources in order and skips sources marked **stale** after repeated auth failures.

### Most providers

1. Runtime override (`--api-key` / `setRuntimeApiKey`)
2. Stored credential in `auth.json` (API key or OAuth, with locked refresh when expired)
3. Environment variable(s) from `env-api-keys.ts`
4. Fallback resolver (custom provider keys from `models.json`)

### Prime Inference (`prime-inference`)

1. Runtime override
2. `PRIME_API_KEY` environment variable
3. Prime CLI config (`~/.prime/config.json` `api_key`)
4. Stored `auth.json` entry
5. Fallback resolver

<Note>
For non-Prime providers, a stored `auth.json` key wins over the matching environment variable. For Prime Inference, env and Prime CLI config win over `auth.json`.
</Note>

### `auth.json` key field formats

| Form | Example | Behavior |
|------|---------|----------|
| Literal | `"sk-ant-..."` | Used as-is |
| Env name | `"MY_ANTHROPIC_KEY"` | Reads `process.env[name]`, else treats string as literal |
| Shell command | `"!op read 'op://...'"` | Executes command, uses stdout; cached for process lifetime |

## Interactive login and logout

### `/login`

Runs `ProviderAuthFlows.runLogin()`:

1. Provider selector lists OAuth subscriptions, API-key model providers, and service credentials (Serper, MCP `mcp:<server>`).
2. Selection routes to the matching flow:
   - **OAuth** → browser / device flow via registered OAuth provider
   - **`prime-inference`** → browser challenge + optional pasted key + team picker
   - **`amazon-bedrock`** → setup guidance only (external AWS credentials)
   - **Other API-key providers** → paste key into login dialog
3. On success: credentials saved, model registry refreshed, host `onAuthChanged` / `onLoginCompleted` run.

After a successful **Prime Inference** login, the session refreshes available models, selects the default `z-ai/glm-5.2`, and persists provider/model so the next process starts with a valid model.

### `/logout`

Removes credentials **saved by `/login`** only. Environment variables and `models.json` keys are unchanged. If nothing is stored, status explains that limitation.

### Auth status labels

`getAuthStatus(provider)` returns non-secret metadata:

| `source` | Meaning |
|----------|---------|
| `runtime` | `--api-key` / SDK runtime override |
| `stored` | Entry in `auth.json` |
| `environment` | Known env var (or ambient cloud marker) |
| `prime_cli` | `~/.prime/config.json` for Prime Inference |
| `fallback` | Custom provider config (`models.json`) |
| `stale` | Prior source invalidated after auth failure (`label: "expired"`) |

## OAuth (subscription) providers

Built-in OAuth providers (registry in `@earendil-works/pi-ai` OAuth module):

| Provider ID | Display name | Notes |
|-------------|--------------|--------|
| `openai-codex` | ChatGPT Plus/Pro (Codex Subscription) | ChatGPT subscription OAuth |
| `anthropic` | Anthropic (Claude Pro/Max) | Subscription tokens; third-party harness usage is billed as extra usage per token |
| `github-copilot` | GitHub Copilot | github.com or Enterprise Server domain |

Tokens land in `auth.json` as `type: "oauth"`. Expired tokens refresh under a file lock so concurrent Prime Agent processes do not race. Failed refresh returns no key for that provider until `/login` succeeds; credentials remain on disk for retry.

Extensions and MCP can call `registerOAuthProvider()` (MCP IDs use `mcp:<server>`). Those appear as **service** credentials in the login selector and do not drive model selection.

### Anthropic subscription warning

When the active model is `anthropic` and credentials are OAuth or an `sk-ant-oat…` key, the UI can surface:

> Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits.

## API-key providers

Use `/login` → API key provider, or set environment variables before launch:

```bash
export ANTHROPIC_API_KEY=sk-ant-...
prime-agent
```

| Provider ID | Environment variable(s) |
|-------------|-------------------------|
| `anthropic` | `ANTHROPIC_OAUTH_TOKEN`, then `ANTHROPIC_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| `azure-openai-responses` | `AZURE_OPENAI_API_KEY` |
| `prime-inference` | `PRIME_API_KEY` |
| `deepseek` | `DEEPSEEK_API_KEY` |
| `google` | `GEMINI_API_KEY` |
| `google-vertex` | `GOOGLE_CLOUD_API_KEY` or ADC + project + location |
| `mistral` | `MISTRAL_API_KEY` |
| `groq` | `GROQ_API_KEY` |
| `cerebras` | `CEREBRAS_API_KEY` |
| `xai` | `XAI_API_KEY` |
| `openrouter` | `OPENROUTER_API_KEY` |
| `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` |
| `zai` | `ZAI_API_KEY` |
| `opencode` / `opencode-go` | `OPENCODE_API_KEY` |
| `huggingface` | `HF_TOKEN` |
| `fireworks` | `FIREWORKS_API_KEY` |
| `kimi-coding` | `KIMI_API_KEY` |
| `minimax` / `minimax-cn` | `MINIMAX_API_KEY` / `MINIMAX_CN_API_KEY` |
| `moonshotai` / `moonshotai-cn` | `MOONSHOT_API_KEY` |
| `xiaomi` | `XIAOMI_API_KEY` |
| `xiaomi-token-plan-cn` / `-ams` / `-sgp` | matching `XIAOMI_TOKEN_PLAN_*` |
| `cloudflare-ai-gateway` / `cloudflare-workers-ai` | `CLOUDFLARE_API_KEY` (+ account/gateway env) |
| `github-copilot` | `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN` |

Example stored shape:

```json
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "prime-inference": { "type": "api_key", "key": "..." }
}
```

## Prime Inference login path

Provider ID: `prime-inference`. Default inference URL: `https://api.pinference.ai/api/v1`.

<Steps>
  <Step title="Reuse or browser login">
    `loginPrimeInference` first checks an existing Prime CLI key for inference write scope via `/api/v1/user/whoami`. If missing or insufficient, it runs an RSA challenge: open `frontendUrl/dashboard/tokens/challenge?code=…`, poll encrypted result, decrypt API key.
  </Step>
  <Step title="Manual paste fallback">
    If browser login fails, the dialog accepts a pasted API key and re-checks inference access.
  </Step>
  <Step title="Persist and team">
    Key is written via Prime CLI config when enabled (default for normal CLI), team selection optional (`PRIME_TEAM_ID` freezes team from env). Team header: `X-Prime-Team-ID` when a team is active.
  </Step>
  <Step title="Model default">
    Post-login selects and persists `prime-inference` / `z-ai/glm-5.2` when that model is available after refresh.
  </Step>
</Steps>

Related service credential: `prime-agent-traces` for trace upload scope (`agent_traces` write permission).

## Cloud and ambient providers

### Amazon Bedrock (`amazon-bedrock`)

Not a single API-key login. `/login` shows setup info; detection uses ambient AWS credentials:

- `AWS_PROFILE`
- `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`
- `AWS_BEARER_TOKEN_BEDROCK`
- ECS task roles (`AWS_CONTAINER_CREDENTIALS_*`)
- IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`)

```bash
export AWS_REGION=us-west-2   # default us-east-1 if unset
prime-agent --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0
```

Proxy / edge knobs: `AWS_ENDPOINT_URL_BEDROCK_RUNTIME`, `AWS_BEDROCK_SKIP_AUTH`, `AWS_BEDROCK_FORCE_HTTP1`, `AWS_BEDROCK_FORCE_CACHE`.

### Google Vertex AI (`google-vertex`)

ADC via `gcloud auth application-default login` or `GOOGLE_APPLICATION_CREDENTIALS`, plus `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. Successful ambient setup surfaces as configured with a non-secret `<authenticated>` marker.

### Azure OpenAI Responses

`AZURE_OPENAI_API_KEY` plus base URL / resource name env vars (see in-repo providers docs for deployment maps).

### Cloudflare AI Gateway (BYOK modes)

| Mode | Request auth | Upstream auth |
|------|--------------|---------------|
| Workers AI | Cloudflare token only | Cloudflare-native |
| Unified billing | Cloudflare token only | Cloudflare deducts credits |
| Stored BYOK | Cloudflare token only | Keys stored in AI Gateway dashboard |
| Inline BYOK | Cloudflare token + upstream `Authorization` | Request supplies provider key (e.g. via `models.json` header override) |

Prefer unified billing or stored BYOK for normal CLI use. Inline BYOK is an explicit multi-credential boundary: Cloudflare token plus upstream provider key.

## Custom and local providers (BYOC)

**`models.json`** — register Ollama, vLLM, LM Studio, or any supported API (`openai-completions`, OpenAI Responses, Anthropic Messages, Google Generative AI):

```json
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [{ "id": "llama3.1:8b" }]
    }
  }
}
```

`apiKey` is required by schema; local servers often ignore it. Keys resolve through the AuthStorage fallback path (`source: "fallback"`).

**Extensions** — `pi.registerProvider(...)` overrides `baseUrl` / headers / models, or registers a new provider. Overrides apply at factory load, `session_start`, or command time without requiring process restart. Dynamic registration is provider-neutral: the extension is a portable skill/source file, not a hard dependency on one vendor.

## Multi-provider selection

- **`/model`** lists available models. Authenticated providers sort above unauthenticated ones; unauthenticated public rows can show `sign in` while remaining selectable.
- After auth changes, connection model catalog refreshes (`refreshConnectionModelsAfterAuthChange`) so configured-provider sets match live credentials.
- Scoped sessions may expose both scoped-only models and the full public catalog in autocomplete.
- CLI: `prime-agent --provider <id> --model <id>`; optional `--api-key <key>` requires an explicit model (or `--models`) and is bound to that model’s provider only.

## CLI and SDK wiring

### CLI

```bash
# Env BYOK
export OPENAI_API_KEY=sk-...
prime-agent --provider openai --model gpt-5.4

# Runtime key (not written to disk)
prime-agent --provider anthropic --model claude-opus-4-7 --api-key sk-ant-...
```

`--api-key` without a resolvable model is an error: *`--api-key requires a model to be specified via --model, --provider/--model, or --models`*.

### SDK

```typescript
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";

const authStorage = AuthStorage.create(); // ~/.prime/agent/auth.json
const modelRegistry = ModelRegistry.create(authStorage);

// Optional process-scoped override
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");

// Custom paths (app-owned storage)
const customAuth = AuthStorage.create("/tmp/my-app/auth.json");
const customRegistry = ModelRegistry.create(customAuth, "/tmp/my-app/models.json");

// Built-in models only (no models.json)
const simpleRegistry = ModelRegistry.inMemory(authStorage);

await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  authStorage,
  modelRegistry,
});
```

## Auth errors and recovery

### Detection

`isLikelyAuthenticationError` treats messages as auth failures when they match `401`/`403`, unauthorized/forbidden, invalid API key, expired/invalid token, access denied, and related phrases.

### User guidance

| Condition | Message fragment |
|-----------|------------------|
| Auth failure | `Run /login to update credentials.` (`LOGIN_RECOVERY_MESSAGE`) |
| No models | Points at `/login` and docs `providers.md` / `models.md` |
| No API key for provider | `No API key found for <provider>.` + login help |
| No model selected | Login help + `/model` |

Guidance is appended once; messages that already mention `/login` are left unchanged.

### Stale credentials after repeated 401

On structured provider auth failures (`diagnostics.kind: "auth"`, status 401) or bare `401` status errors:

1. Auto-retry may attempt once (when retry is enabled).
2. Current auth source token is marked stale (`markAuthSourceStale`).
3. Session emits `auth_stale` (includes `sourceTokens` for daemon clients).
4. `hasAuth(provider)` becomes false; `getAuthStatus` reports `{ configured: false, source: "stale", label: "expired" }`.
5. Final assistant error includes `/login` recovery text.

Setting a new key (`set`, `setRuntimeApiKey`, Prime CLI save, etc.) clears the matching stale source so the new credential is usable.

```text
request → 401 auth failure → optional single auto-retry
         → mark current source token stale
         → emit auth_stale
         → surface "Run /login to update credentials."
```

Non-auth errors (for example 500) do not mark auth stale.

## Failure modes

| Symptom | Likely cause | Recovery |
|---------|--------------|----------|
| No models available | No resolved credentials for any provider | `/login` or set env keys |
| Authentication failed for `"provider"` | Expired OAuth / invalid key / network | `/login`; check network |
| Provider stuck after 401 | Source marked stale | Update credentials; do not reuse the same failed value |
| Bedrock “not detected” | No AWS ambient credentials | Configure profile, IAM keys, bearer token, or role env |
| Prime Inference access denied | Token lacks `inference` write scope | Browser login with inference-capable token |
| Logout did nothing | Only env / `models.json` credentials present | Unset env or edit `models.json` |
| `--api-key` rejected | No model specified | Pass `--model` / `--provider` |

## Boundaries (BYOK / BYOC)

- **Provider-neutral core**: `AuthStorage` + `ModelRegistry` do not assume a single paid gateway; each provider ID maps to its own credential channel.
- **Logout boundary**: stored login credentials only; ambient and file-based custom keys remain.
- **Prime Inference vs other providers**: different resolution order and optional team header; still an API-key-shaped provider ID in the catalog.
- **Service vs model credentials**: Serper and MCP OAuth do not change the selected model after login (`kind: "service"`).
- **Custom providers**: local/self-hosted endpoints via `models.json` or extensions keep keys and base URLs in user-owned config.

## Related pages

<CardGroup cols={2}>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, API key and OAuth wiring, dynamic updates, and 401 stale-provider recovery.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    First interactive session, onboarding, and health checks after credentials are configured.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Session config keys and defaults that interact with provider and model choice.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap `createAgentSession` with auth and model wiring.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, and related runtime recovery probes.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    `registerProvider` and custom OAuth/API implementations for private endpoints.
  </Card>
</CardGroup>

---

## 05. RLM control plane

> Recursive Language Model model: persistent IPython as the control tool, prompt-as-variable context, and rlm(...) subagent calls.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/05-rlm-control-plane.md
- Generated: 2026-08-06T19:43:31.210Z

### Source Files

- `prime-agent-runtime/src/rlm/__init__.py`
- `packages/agent/src/agent-loop.ts`
- `packages/agent/src/agent.ts`
- `packages/coding-agent/test/acp-rlm-subagents.test.ts`
- `packages/coding-agent/README.md`

---
title: "RLM control plane"
description: "Recursive Language Model model: persistent IPython as the control tool, prompt-as-variable context, and rlm(...) subagent calls."
---

Prime Agent’s RLM control plane is the model-facing surface where work runs as Python in a persistent IPython kernel, not as a fixed set of built-in tools. The TypeScript host owns provider streams, session transcripts, child `AgentSession` lifecycles, and credentials; the only default model tool is `ipython`. Context that the model needs again is held as named Python variables and kernel state; recursive work is admitted with `await rlm(...)` over a Jupyter comm bridge (`host.request`), which returns an admission handle immediately and never the child’s answer.

## Control plane shape

| Layer | Component | Owns |
| --- | --- | --- |
| Model loop | `Agent` / agent loop (`packages/agent`) | Streaming turns, tool calls, steering/follow-up queues |
| Session host | `AgentSession` | Policy, host request handlers, child registry, usage attribution |
| Single tool | `ipython` | Lazy kernel provision, bootstrap, cell execute, output shaping |
| Transport | `KernelManager` | Jupyter ZeroMQ channels, comm dispatch, interrupt/shutdown |
| Python surface | `prime-agent-runtime` (`rlm`) | Callable `rlm`, `host_request`, harness proxy, handle types |

```mermaid
flowchart TD
  subgraph host["TypeScript host"]
    loop["Agent loop · stream + tool calls"]
    session["AgentSession"]
    km["KernelManager"]
    child["Child AgentSession"]
    providers["Model providers · BYOK"]
  end

  subgraph kernel["IPython kernel process"]
    ipy["ipython tool cells"]
    rlm["rlm · host_request"]
    skills["Python skills · variables"]
  end

  loop -->|"tool: ipython"| session
  session --> km
  km <-->|"shell · iopub · control"| ipy
  ipy --> rlm
  ipy --> skills
  rlm -->|"comm host.request"| km
  km -->|"typed dispatch"| session
  session --> child
  loop --> providers
  child --> providers
```

Provider choice stays BYOK: spawn can request an exact `provider/model` selector from credentials already configured for the session; the kernel never holds the full auth store.

## Single built-in tool: `ipython`

Default active tools are `["ipython"]`. The tool schema is one field:

<ParamField body="code" type="string" required>
Python scratchpad or a `%%bash` shell cell executed in the agent kernel. Prefer the project’s own environment for project imports, tests, CLIs, and dependency checks.
</ParamField>

On first use the session provisions a kernel (optionally prewarmed). Bootstrap injects:

- callable `rlm` in the user namespace (or a stub that raises if `prime-agent-runtime` is missing);
- pre-imported Python skill modules when installed;
- `nest_asyncio` when available so `await` works inside cells.

Kernel Python resolution order:

1. `PRIME_AGENT_KERNEL_PYTHON` (must import `ipykernel` and a current `prime-agent-runtime` when recursion is used);
2. `~/.prime/agent/kernel-venv` (uv-managed Python 3.11 bootstrap);
3. XDG data path when `~/.prime` is not writable.

Managed bootstrap also installs `prime-agent-runtime`, `dill` (namespace snapshots), and default data packages (`requests`, `httpx`, `pandas`, `numpy`, …). Additional packages in the kernel use `uv pip install <pkg>` (no system `pip` module in that venv).

<Warning>
The kernel runs with the worker’s OS permissions. It is a durable control environment, not a security sandbox. Untrusted repos or third-party skills need an external sandbox or restricted environment.
</Warning>

## Prompt-as-variable context

RLM doctrine in the system prompt treats the kernel as long-lived working memory:

- Assign reads/searches/results to **named variables** so later cells can slice, filter, and act without re-reading.
- **Python state persists** across cells and turns: variables, imports, helpers, parsed outputs, and `rlm` handles.
- **`%%bash` cells** are throw-away subshells: shell `cd` / `export` do not carry over. Persist cwd with `%cd` and env with `os.environ[...]` or `%env`.
- When `%%bash` is used, it must be the **first line** of the cell (no comments or Python before it).
- Do not install project deps into the kernel just to import a repo; run project commands through the project’s own environment.

Ordinary model context remains the host transcript. Compaction can summarize older messages while the kernel (and optional dill snapshot under the session artifact dir) retains intermediate state. Compaction may also restart the kernel to release memory; restore notices then tell the model to recreate lost variables.

```python
from pathlib import Path

configs = list(Path(".").rglob("*.toml"))
large = [p for p in configs if p.stat().st_size > 10_000]
```

```bash
%%bash
npm run check
```

## Recursive calls: `rlm(...)`

### API surface

`prime-agent-runtime` exposes a module-level callable and mirror methods:

| Symbol | Behavior |
| --- | --- |
| `await rlm(prompt, **kwargs)` | Same as `rlm.run` |
| `await rlm.run(prompt, **kwargs)` | Admit a child; return `RLMSpawnHandle` |
| `await rlm.find_models(query="", limit=8)` | Bounded catalog of models with active credentials |
| `await rlm.list_subagents()` | Direct children of this parent session |
| `await rlm.delete_subagent(target)` | Cancel/close and tombstone one direct child |
| `await rlm.host_request(type, payload?)` | Generic typed host bridge |
| `rlm.harness` | Continual harness ledger (session-local by default) |

`RLMSpawnHandle` fields: `rlm_child_id`, `name`, `session_dir`, `model`.

`rlm.run` kwargs (unknown keys fail):

| Kwarg | Type | Rules |
| --- | --- | --- |
| `name` | `str` | Unique among siblings; max 64 chars; optional (host generates `subagent-…` default) |
| `model` | `str` | Exact `provider/id` from `find_models`; unavailable or failed auth preflight aborts spawn (no silent fallback) |

Default recursion: root `RLM_DEPTH` is 0 (or env); `RLM_MAX_DEPTH` defaults to **1** (root may create children; children may not recurse further unless raised via env, settings `rlmMaxDepth`, session inheritance, or the rlm-max-depth APIs).

### Admission contract

1. Model executes `await rlm("…")` inside an `ipython` cell.
2. Kernel opens a `host.request` comm with type `rlm.run`.
3. Host checks depth, name uniqueness, model selection, then creates `sub-xxxxxxxx` under the parent artifact directory.
4. Reply returns the handle **immediately after admission** (status moves through queued/running independently).
5. Child runs as a full `AgentSession` with its own context, incremented `RLM_DEPTH`, and inherited tools/skills/providers unless model was overridden.
6. Child answers only via `agent_message` (or files), never as the `rlm()` return value.

```python
handle = await rlm("Review the authentication flow", name="auth-reviewer")
print(handle.rlm_child_id, handle.name, handle.session_dir, handle.model)

# Parallel independent work — end the turn; do not await completion
api = await rlm("Review the public API", name="api-reviewer")
tests = await rlm("Review test coverage", name="test-reviewer")
```

```python
# Child side (when agent_message is installed)
await agent_message.send(message, receiver_role="parent")

# Parent follow-up
await agent_message.send(
    "Check the new regression test.",
    receiver_role="child",
    receiver_name=api.name,
)

children = await rlm.list_subagents()
await rlm.delete_subagent(children[0])  # when context is no longer needed
```

`list_subagents` status values: `running` | `completed` | `error`. Registry is parent-scoped and survives compaction, kernel restart, and parent restore for daemon-backed completed children; unrelated new parent sessions do not inherit it.

### Host request types (kernel → session)

| Type | Role |
| --- | --- |
| `rlm.run` | Spawn child |
| `rlm.find_models` | Model search (`limit` 1–20; default 8) |
| `rlm.list_subagents` | Registry read |
| `rlm.delete_subagent` | Delete by id / active-session id / session id / unique name |
| `model.info` | Current parent model metadata |
| `goal.*` | Goals (when enabled) |
| `compact.*` | Compaction skill bridge |
| `refine.*` | Harness refine bridge |
| `rlm_heartbeat.*` | Heartbeat scheduling |
| `agent_message.*` | Cross-agent messaging |

Request type is applied last on the comm payload so a payload `"type"` key cannot reroute the call.

## Control channel and deadlock avoidance

IPython processes shell messages serially. If `rlm.run` replied on the **shell** channel while a cell was blocked on that reply, the kernel would deadlock.

The Python shim therefore registers comm handlers on the **control** channel; the host sends admission replies there. Futures complete with `loop.call_soon_threadsafe` because control handlers may run on another thread. Ordinary cell I/O stays on shell/IOPub.

```text
shell    execute_request / execute_reply / kernel_info
iopub    stdout, stderr, results, errors, status, comm_open
control  interrupt, shutdown, host-request replies during execute
```

`KernelManager.execute()` serializes ordinary cells on one shared namespace. Concurrent RLM children still run as separate sessions (and optional kernels), not as concurrent cells in the parent kernel.

## Usage attribution

The admission handle has no usage or completion fields. Child assistant usage/cost is folded asynchronously into the parent assistant turn that launched the child. Transcripts may record `child_usage_attributed` entries for reload. Context-tree reporting separates a node’s own usage from attributed child totals so aggregate cost remains reconcilable.

ACP clients can observe subagent lifecycle as namespaced session-update metadata (`rlm_child_update` → ACP meta `subagents`) for the session lifetime, including fire-and-forget spawns outside a prompt turn.

## Failure modes

| Condition | Result |
| --- | --- |
| Missing `prime-agent-runtime` in kernel | Clear `RuntimeError` on `rlm.*` |
| `RLM_DEPTH >= RLM_MAX_DEPTH` | Host raises recursion limit error (Python may check first) |
| Unsupported `rlm.run` kwargs | Host rejects with listed keys |
| Unknown / unauthenticated `model` | Spawn fails; no substitution |
| Duplicate child `name` | Name unavailable error |
| Parent disposed during spawn | Spawn rejected |
| Busy kernel after interrupt | Wait (preserve state) or kill (lose variables) |
| Parent teardown | Active descendants cancelled; runtimes closed |

## Verification signals

<Check>
After first `ipython` use: kernel starts without import errors; `rlm` is callable in a cell (`await rlm.find_models()` returns a list, possibly empty).
</Check>

<Check>
`handle = await rlm("ping", name="probe")` returns four fields immediately; parent UI/ACP shows a running child update; completion arrives only via message or files.
</Check>

<Check>
At max depth 1, a child call to `rlm(...)` fails with the depth limit message.
</Check>

## Related pages

<CardGroup>
  <Card title="Continual Harness" href="/continual-harness">
    Durable prompts, memories, skill specs, subagent specs, and refine boundaries layered on the RLM runtime.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    Child agents, agent-message skill surface, and multi-agent constraints.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Python-backed skills imported into the same kernel as first-class callables.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, autonomous mode, and retained children across disconnects.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, events, queueing, and session-scoped vs durable state.
  </Card>
  <Card title="ACP mode" href="/acp-mode">
    How RLM subagent updates surface to editor clients.
  </Card>
</CardGroup>

---

## 06. Continual Harness

> Durable harness state (prompts, memories, skill specs, subagent specs), refine boundaries, immutable base prompt, and rollback snapshots.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/06-continual-harness.md
- Generated: 2026-08-06T19:44:07.311Z

### Source Files

- `packages/coding-agent/skills/refine/SKILL.md`
- `packages/coding-agent/skills/refine/src/refine/__init__.py`
- `packages/coding-agent/test/suite/agent-session-refine-skill.test.ts`
- `packages/coding-agent/test/suite/agent-session-serialized-refine.test.ts`
- `packages/coding-agent/README.md`

---
title: "Continual Harness"
description: "Durable harness state (prompts, memories, skill specs, subagent specs), refine boundaries, immutable base prompt, and rollback snapshots."
---

The continual harness is Prime Agent’s persisted, editable ledger for reusable behavior outside the token history: prompt notes, memories, skill call contracts, subagent specs, and refinement events. It lives as `rlm.harness` / `get_harness_state()` in the IPython kernel and as `harness_state.json` on disk; host `/refine`, the `refine` skill, and kernel CRUD all read and write the same store. It is not a second execution engine—runtime still runs through the TypeScript host, IPython, and `rlm(...)` recursion.

## What the harness stores

| Kind | Role | Typical content |
|------|------|-----------------|
| `prompt` | Supplemental prompt notes only | Narrow behavioral policy addendums |
| `memory` | Durable facts and preferences | Decisions, failures, outcomes, user preferences |
| `skill` | Reusable Python call description | `reference` + `arguments` contract for an installed or planned callable |
| `subagent` | Reusable delegation role | Purpose, instructions, when to spawn via `await rlm("…")` |

Each entry is a `HarnessEntry` with stable fields: `id`, `kind`, `title`, `content`, `path` (grouping, default `general`), `scope` (`local` \| `global`), `reference`, `arguments`, `metadata`, `source`, `created_at`, `updated_at`, `version`.

Skill create/update edits require a Python `reference` (`type: "python"`, import, and `callable` or `call_pattern`) and an `arguments` object (use `{}` only when the callable needs no inputs). Subagent specs document the native spawn form: admission via `handle = await rlm("sub-task")` returns `rlm_child_id`, `name`, `session_dir`, and `model`—not the child’s answer; results come from `agent_message` or files.

```text
HarnessState (schema: 1)
├── entries
│   ├── prompt:   { id → HarnessEntry }
│   ├── memory:   { id → HarnessEntry }
│   ├── skill:    { id → HarnessEntry }
│   └── subagent: { id → HarnessEntry }
└── refinements[]  (id, trigger, changes, evidence, outcome, created_at)
```

## Local vs global scope

| Scope | Default store | Blast radius |
|-------|---------------|--------------|
| `local` | Session artifact dir: `…/session-artifacts/<session-id>/harness/harness_state.json` | Current session only |
| `global` | `~/.prime/agent/harness/` (or `PRIME_AGENT_CODING_AGENT_DIR` / `PI_CODING_AGENT_DIR`) | Cross-session |

- Local is the default for refinement and for session-specific progress, blockers, and current-run coordination.
- Global is for stable cross-session lessons, durable preferences, reusable skills/subagents, or explicitly project-qualified facts.
- One refinement applies only to the requested scope’s store. During a local refine, global entries are read-only context (no update/delete of global ids; create a local entry for overrides).
- Merged overview ids may show display prefixes `local:` / `global:`; edit payloads always use bare ids.

Local refinement requires a **persisted** session (local harness directory). Non-persistent sessions must use global refinement or cannot refine locally.

:::files
~/.prime/agent/
  harness/
    harness_state.json
    refinements.jsonl          # global refinement history for rollback
  session-artifacts/
    <root-session-id>/
      harness/
        harness_state.json     # local session harness
  sessions/
    <root-session-id>.jsonl    # session custom entries include prime-agent.refinement
:::

Python path resolution also honors `RLM_HARNESS_STATE_DIR` / `RLM_GLOBAL_HARNESS_STATE_DIR` and, for local, `RLM_SESSION_DIR/harness/` when those env vars are set by the host.

## Immutable base system prompt

The base RLM system prompt is **not** editable through the harness. Prompt-kind entries are supplemental notes only; refine guidance explicitly forbids rewriting the base prompt. After a successful apply, the session rebuilds `_baseSystemPrompt` and reassigns `agent.state.systemPrompt` so the next turn sees the updated compact harness overview without changing the immutable base template.

Custom `customPrompt` options still compose with harness injection when `harnessState` is passed into `buildSystemPrompt`.

## How state reaches the model

`formatHarnessStateForPrompt` injects a compact **Continual Harness State** section into the system prompt: counts and short summaries per kind (default 6 entries per kind, 180-character content clips), recent refinements, and call-contract guidance for IPython vs shell-only sessions. Full entry bodies stay on disk; the overview is a routing/context hint.

Kernel and host both degrade corrupt `harness_state.json` to empty state rather than failing the session; the next successful save rewrites a clean file.

## Mutation surfaces

### User and host: `/refine`

Session slash command (also available in Agents View and RPC):

| Form | Effect |
|------|--------|
| `/refine` | Plan and apply evidence-backed create/update/delete edits (local by default) |
| `/refine <instructions>` | Focus the planner with free-text instructions |
| `/refine --global …` | Target the global harness store |
| `/refine rollback <refinement-id>` | Invert a prior refinement from recorded before/after entry snapshots |
| `/refine rollback <id> --global` | Rollback routing with global flag (path/scope still resolved from history) |

RPC wire shape: `{ type: "refine", instructions?, rollbackId?, global? }`.

### Agent: `refine` skill (IPython)

The bundled `refine` skill is a thin host bridge over the same host implementation as `/refine`. It never runs mid-cell.

```python
await refine.status()
# → {"pending": bool, "in_flight": bool}

await refine.run()
await refine.run("create a memory about always checking git status before committing")
await refine.run("promote the error-handling pattern to a global skill", global_=True)
# → {"scheduled": True, "note": "..."}  or  {"scheduled": False, "reason": "..."}
```

| Constraint | Behavior |
|------------|----------|
| Active turn required | `refine.run` while not streaming returns `scheduled: false` (`no active turn…`) |
| Schedule timing | Runs when the current turn ends; system prompt rebuilds; agent resumes |
| One request per turn | A second `run` before turn end **updates** instructions (and keeps prior `global` if not re-specified) |
| Serialized mode | A new request can abort/invalidate an in-flight background plan and replace pending options |

### Kernel: `rlm.harness` CRUD

The runtime exposes explicit methods (host bootstrap asserts these exist):

- `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`
- `record_refinement`
- `get_harness_state(global_=…)` / `overview(…)`

The Python store reloads when on-disk mtime changes so host `/refine` writes are not overwritten by a stale in-kernel cache.

### Harness skill entry vs installed skill package

| Surface | What it is |
|---------|------------|
| Installed skill | Real package under project/personal skills paths with `SKILL.md` (and usually Python sources) |
| Continual harness `skill` entry | Persisted description of a reusable Python call (`reference` + `arguments`) |

`/refine` can create or update harness skill **descriptions** after a repeated procedure emerges. Packaging new executable functionality still uses skill creation (`skill-creator` / install paths), not refine alone.

## Refine lifecycle

```mermaid
sequenceDiagram
  participant User as User or refine.run
  participant Session as AgentSession
  participant Planner as planRefinement
  participant Disk as harness_state.json
  participant Prompt as system prompt

  User->>Session: /refine or scheduled refine
  Note over Session: Planning is backgrounded<br/>(does not block new turns)
  Session->>Disk: load state + history
  Session->>Planner: trajectory + harness overview
  Planner-->>Session: RefinementPlan (or rollback proposal)
  Note over Session: Apply waits for agent idle,<br/>event queue, compaction
  Session->>Disk: re-read target store
  Session->>Disk: apply create/update/delete
  Session->>Disk: saveHarnessState (atomic tmp+rename)
  Session->>Session: append prime-agent.refinement
  Session->>Prompt: rebuild base+harness overview
  Session-->>User: refine_complete (or refine_failed)
```

**Plan vs apply**

1. **Plan** (`planRefinement`): LLM emits JSON `{ summary, rationale, expectedOutcome, edits[] }`, or builds a rollback proposal from history. Conversation slice is truncated (~80k chars). Output budget is `min(model.maxTokens, 32000)`.
2. **Apply** (`applyRefinementProposal`): Re-reads the target store immediately before mutation so concurrent kernel writes during the long LLM pass are not clobbered. Baseline-aware conflict check rejects edits when an entry changed under the planner (`entry changed during refinement planning`).
3. **Persist**: Atomic write (`*.tmp` → rename, mode preserved or `0o600`). Global refinements also append to `refinements.jsonl`. Every result is stored as a session custom entry `prime-agent.refinement`.

**Serialization**

- Concurrent public `refine()` calls wait on in-flight plan/apply.
- Only the apply critical section blocks new turn entry (`_waitForRefineIdle`); planning does not.
- Headless/print/json/rpc paths can enable **serialized refine**: at most one primary+refine model request at a time; checkpoint may run refine once per interval inside the agent loop.

## Rollback snapshots

Rollback is not a full-file restore of arbitrary history; it inverts a recorded `RefinementResult`:

1. Locate refinement by id in merged history (global `refinements.jsonl` + session custom entries; session wins on id conflict).
2. Build reverse edits from each **applied** edit’s `before` / `after` snapshots (delete ↔ recreate, update ↔ restore fields).
3. Apply into the store resolved from `harnessStatePath` / scope (legacy records without scope honor the recorded path).
4. Missing local state file for a local rollback raises a clear error naming the path.

Unapplied edits (validation failures) are skipped in the reverse pass.

## Auto-refine

Settings (`autoRefine` on session settings; resolved by `getAutoRefineSettings()`):

| Key | Default | Meaning |
|-----|---------|---------|
| `enabled` | `true` | Auto review/refine loop on (set `false` to opt out) |
| `turnInterval` | `25` | Assistant turns between auto-refine reviews (min 1; non-numeric → default) |
| `compact` | `true` | Also consider refine after compaction checkpoints |
| `cooldownMs` | `20 * 60_000` | Minimum ms between auto-refine reviews (min 0) |

Auto-refine uses a separate reviewer JSON gate (`shouldRefine`, `rationale`, optional `instructions`) and **defaults to local** harness edits. Instructions push empty edits over speculative memories and avoid global promotion unless explicitly requested. Provider credentials still come from the host’s BYOK/BYOC auth path—no fixed model vendor is required.

## Events and ACP

Session events include `refine_complete` (with `RefinementResult`) and `refine_failed`. In ACP mode, refinement status is not a native ACP field; it travels in the namespaced `_meta` envelope (`ai.primeintellect.prime-agent`) as refinement status metadata that vanilla ACP clients ignore.

## Common errors and constraints

| Situation | Result |
|-----------|--------|
| Local refine without persisted session | Error: local harness requires persisted session; use global |
| `refine.run` outside active turn | `{ scheduled: false, reason: "no active turn…" }` |
| Invalid `instructions` / `global` types | Host throws type errors |
| Rollback id missing | `Refinement ${id} not found` |
| Entry race during plan→apply | Edit fails: `entry changed during refinement planning` |
| Skill missing `reference` / `arguments` | Validation error on apply |
| Truncated refine JSON | Explicit budget-exhausted style error (not opaque parse-only failure) |
| Corrupt harness file | Load as empty; next save rewrites |

## When to refine

Use refine after: repeated failures, reusable tactics, repeated delegation roles → subagent specs, repeated procedures → skill entries, durable facts/preferences → memory, narrow policies → prompt notes, user corrections that should persist, or validation that shows a harness entry is wrong. Prefer the smallest component and evidence-backed edits; empty `edits` is better than speculative state.

## Related pages

<CardGroup>
  <Card title="Refine harness state" href="/refine-harness">
    Run /refine, apply evidence-backed updates, serialize refine, and roll back with snapshots.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython control tool, prompt-as-variable context, and rlm(...) subagents.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Installed Python skill packages vs harness skill entries, collision rules, and scopes.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, session-scoped vs durable state, and artifact layout.
  </Card>
  <Card title="Built-in skills" href="/builtin-skills">
    Catalog including refine, goal, compact, heartbeat, and related host bridges.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    autoRefine and other session config keys, defaults, and reload behavior.
  </Card>
</CardGroup>

---

## 07. Sessions and runtime

> Agent session lifecycle, session services, runtime events, queueing, tree navigation, and session-scoped vs durable state.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/07-sessions-and-runtime.md
- Generated: 2026-08-06T19:44:36.714Z

### Source Files

- `packages/coding-agent/src/core/agent-session.ts`
- `packages/coding-agent/src/core/agent-session-runtime.ts`
- `packages/coding-agent/src/core/agent-session-services.ts`
- `packages/coding-agent/src/core/agent-session-config.ts`
- `packages/coding-agent/examples/sdk/13-session-runtime.ts`
- `packages/coding-agent/test/suite/agent-session-runtime.test.ts`

---
title: "Sessions and runtime"
description: "Agent session lifecycle, session services, runtime events, queueing, tree navigation, and session-scoped vs durable state."
---

`AgentSessionRuntime` owns one active `AgentSession` plus its cwd-bound `AgentSessionServices`. Modes (interactive TUI, print/JSON/RPC, SDK, daemon worker) sit above this pair: they rebind UI/extension subscriptions when the runtime replaces the session, and they do not own provider calls, queues, transcript writes, or child RLM runtimes.

```mermaid
flowchart TB
  subgraph clients["Clients"]
    tui["Interactive TUI"]
    headless["Print · JSON · RPC"]
    sdk["SDK createAgentSession / createAgentSessionRuntime"]
  end

  subgraph runtime["AgentSessionRuntime"]
    session["AgentSession"]
    services["AgentSessionServices"]
    children["RLM subagent runtimes"]
    store["ActionStore<br/>steer · followUp"]
    kernel["IPython kernel"]
  end

  subgraph durable["Durable storage"]
    jsonl["Session JSONL tree<br/>~/.prime/agent/sessions/"]
    auth["auth.json · models.json · settings"]
    harness["Harness / refine snapshots"]
  end

  providers["Model providers BYOK"]

  tui --> runtime
  headless --> runtime
  sdk --> runtime
  runtime --> session
  session --> services
  session --> store
  session --> kernel
  session --> children
  session --> jsonl
  session <-->|"stream"| providers
  services --> auth
```

## Runtime stack

| Layer | Type / factory | Owns |
|-------|----------------|------|
| Runtime | `AgentSessionRuntime` / `createAgentSessionRuntime` | Active session, services, diagnostics, session lease, hosted RLM children, replace/dispose |
| Services | `AgentSessionServices` / `createAgentSessionServices` | cwd-bound infrastructure: auth, settings, models, resource loader, MCP |
| Session | `AgentSession` / `createAgentSession` or `createAgentSessionFromServices` | Agent loop, queues, events, compaction, goals, tree navigation, tools, kernels |
| Transcript | `SessionManager` | Append-only JSONL tree, leaf pointer, branch/fork files |
| Config snapshot | `AgentSessionRuntimeConfig` | Fixed process-level options reused across session replacements |

Services are recreated when the effective session cwd changes. Session options (model, tools, RLM depth, autonomous flags) resolve against those services before `AgentSession` construction.

### AgentSessionServices

```ts
interface AgentSessionServices {
  cwd: string;
  agentDir: string;
  authStorage: AuthStorage;
  settingsManager: SettingsManager;
  modelRegistry: ModelRegistry;
  resourceLoader: ResourceLoader;
  mcpManager: McpManager;
  diagnostics: AgentSessionRuntimeDiagnostic[];
}
```

`createAgentSessionServices` returns diagnostics instead of printing or exiting. Callers decide whether warnings are shown and whether errors abort startup. CLI resource paths should be absolute before they reach service creation so cwd switches do not reinterpret them.

### AgentSessionRuntimeConfig

Runtime config is a mergeable snapshot of provider/model, tools, skills, extensions, themes, autonomous gates, `serializedRefine`, and `initialGoal`. It is not the settings file: settings live under `SettingsManager`; this object is what a host (CLI flags, JSON client, daemon worker) passes into the runtime factory for every rebuild.

| Field | Role |
|-------|------|
| `cwd` / `agentDir` / `sessionDir` | Paths for discovery and session storage |
| `provider` / `model` / `apiKey` / `thinking` | Initial model selection |
| `tools` / `noTools` / `noBuiltinTools` | Tool surface |
| `extensions` / `skills` / `promptTemplates` / `themes` + `no*` flags | Resource discovery gates |
| `autonomous` | Continuation policy |
| `serializedRefine` | Auto-refine between turns (print/headless), survives `appMode="daemon"` handoff |
| `initialGoal` | Seeds a top-level goal once when the branch is still seedable |

## Session lifecycle

### Create

```ts
const createRuntime: CreateAgentSessionRuntimeFactory = async ({
  cwd, sessionManager, sessionStartEvent,
}) => {
  const services = await createAgentSessionServices({ cwd });
  return {
    ...(await createAgentSessionFromServices({
      services,
      sessionManager,
      sessionStartEvent,
    })),
    services,
    diagnostics: services.diagnostics,
  };
};

const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});
```

`createAgentSessionRuntime` acquires a session lease (when enabled), asserts the session cwd exists, runs the factory, and stores the factory for later `/new`, resume, `/fork`, and import.

### Replace

`AgentSessionRuntime` replacement methods share one teardown → build → rebind sequence:

1. Emit cancellable `session_before_switch` or `session_before_fork` to extensions.
2. Acquire a replacement lease on the target session path.
3. Emit `session_shutdown`, flush agent-trace upload, run `beforeSessionInvalidate`, `disposeAsync()` (kernel snapshot flush), dispose hosted subagent runtimes.
4. Build a new runtime with the stored factory and commit the lease.
5. Call `rebindSession` / `onSessionReplaced` listeners, then optional `withSession`.

| Method | Purpose | `session_start.reason` | Shutdown reason |
|--------|---------|------------------------|-----------------|
| `newSession()` | New JSONL (optional `parentSession`) | `new` | `new` |
| `switchSession(path)` | Open existing session file | `resume` | `resume` |
| `fork(entryId)` | New file (or in-memory branch) from a tree position | `fork` | `fork` |
| `importFromJsonl(path)` | Copy import into session dir and open | `resume` | `resume` |
| `dispose()` | Final teardown | — | `quit` |

All return `{ cancelled: boolean }` when an extension cancels the before-event. Dispose is idempotent: concurrent `dispose()` calls share one promise; subagent dispose errors still tear down remaining children and surface the first error.

### Rebind after replacement

After any replacement, rebind session-local subscriptions to `runtime.session`. Holding a stale `session` reference after `newSession` / `switchSession` is a common integration bug.

```ts
async function bindSession(runtime: AgentSessionRuntime) {
  unsubscribe?.();
  const session = runtime.session;
  await session.bindExtensions({});
  unsubscribe = session.subscribe((event) => {
    if (event.type === "session_action_update") {
      // steering + followUps previews
    }
  });
  return session;
}
```

## Durable storage

Sessions auto-save as JSONL under `~/.prime/agent/sessions/` (override agent dir via `PRIME_AGENT_CODING_AGENT_DIR`). Current layout is a flat directory of `<session-id>.jsonl`; older per-project dirs migrate on load.

```bash
prime-agent --continue          # most recent session for cwd
prime-agent --resume [path|id]  # picker or direct resume
prime-agent --no-session        # ephemeral; do not save
prime-agent --fork <path|id>    # fork into a new session file
```

`SessionManager` factories:

| Factory | Persistence |
|---------|-------------|
| `SessionManager.create(cwd, sessionDir?)` | New file under default or given session dir |
| `SessionManager.open(path, sessionDir?, cwdOverride?)` | Existing file |
| `SessionManager.inMemory(cwd?)` | No disk file (`sessionFile` undefined) |

Header shape (first line):

```ts
interface SessionHeader {
  type: "session";
  version?: number; // CURRENT_SESSION_VERSION = 3
  id: string;
  timestamp: string;
  cwd: string;
  parentSession?: string;
  rlmDepth?: number;
  git?: GitContext;
}
```

Entries form a tree via `id` / `parentId`. The leaf pointer is the active conversation tip. `buildSessionContext()` walks root → leaf, applying compaction and branch summaries for the LLM context.

### Interactive session commands

| Command | Effect |
|---------|--------|
| `/session` | Current file, id, message count |
| `/resume` | Session picker (search, rename, delete) |
| `/new` | Runtime `newSession()` |
| `/name <name>` | Display name on the active session |
| `/tree` | In-file leaf navigation |
| `/fork` | New session file from a prior user message |
| `/clone` | Duplicate active branch into a new session file |
| `/compact` | Manual compaction |
| `/usage` | Tokens, cost, context |

## Prompt admission and queueing

All turn work flows through a session-owned `ActionStore`, not `Agent.steer` / `Agent.followUp` directly. Delivery policies:

| Schedule | Delivery policy | When it runs |
|----------|-----------------|--------------|
| `steer` | `next_turn_boundary` | After the current assistant finishes its tool calls, before the next LLM call |
| `followUp` | `when_run_idle` | Only when the run is idle (no tools, no steering) |

### Prompt APIs

```ts
await session.prompt(text, options?);
await session.promptUntilAccepted(text, options?); // ownership accepted; may still be queued
await session.promptAndWait(text, options?);       // accepted + completion
await session.steer(text, images?, options?);
await session.followUp(text, images?, options?);   // returns boolean (coalesce may drop)
```

Important `PromptOptions`:

| Option | Default / constraint |
|--------|----------------------|
| `expandPromptTemplates` | `true` |
| `streamingBehavior` | Required when already streaming or busy with queued work: `"steer"` \| `"followUp"` |
| `followUpQueueKey` | Coalesce so only one pending follow-up exists for that key |
| `queueIfBusy` | Queue when idle but unfinished actions remain |
| `source` | Extension input source; default `"interactive"` |
| `preflightResult` | RPC hook: `(success, queued?) => void` |

Action lifecycle (legal transitions enforced):

```text
queued → selected → preparing → committing → running → completed
                                      ↘ failed / cancelled
```

Visible queue state is projected as `SessionActionSnapshot` and emitted as `session_action_update`:

```ts
interface SessionActionSnapshot {
  queuedCount: number;
  steering: readonly string[];
  followUps: readonly string[];
  active?: {
    kind: "turn" | "session_command";
    phase: "preparing" | "committing" | "running";
    label?: string;
  };
}
```

Daemon recovery can restore unfinished actions via `SessionActionRecoverySnapshot` (`SESSION_ACTION_RECOVERY_FORMAT_VERSION = 1`). Restored delivery records start with `durable: false` until re-committed to the transcript.

Branch mutations and turn dispatch share commit fences: `navigateTree` acquires a queued-work pause and a session-action commit fence so the leaf and action store stay consistent.

## Tree navigation vs fork

| Operation | File | API | Typical use |
|-----------|------|-----|-------------|
| `/tree` / `navigateTree` | Same session | `AgentSession.navigateTree(targetId, options)` | Explore alternatives in place |
| `/fork` / `runtime.fork` | New session file (when persisted) | `AgentSessionRuntime.fork(entryId)` | Separate lineage from a user message or position |
| `/clone` | New session file | Branch at current leaf | Snapshot active work before continuing |

### navigateTree selection rules

- **User or custom message:** leaf moves to the message's parent; message text returns as `editorText` for edit-and-resubmit (new branch).
- **Assistant / tool / compaction / other:** leaf moves to that entry; editor empty; continue from that point.
- **Root user message:** leaf resets; original prompt placed in the editor.

Optional branch summary when leaving a path: summarize abandoned entries into a `branch_summary` attached at the new leaf. Extensions can cancel or supply the summary via `session_before_tree` / `session_tree`.

`fork` with `position: "before"` (default) requires a user message entry and returns selected user text. `position: "at"` forks including the selected entry. Persisted sessions call `SessionManager.createBranchedSession(leafId)` to write a new JSONL containing only the root→leaf path; in-memory sessions branch in place.

## Events

### AgentSessionEvent (session.subscribe)

Extends core `AgentEvent` (message start/update/end, tool execution, turn/agent boundaries) with session-specific types:

| `type` | Payload highlights |
|--------|--------------------|
| `session_action_update` | `actions: SessionActionSnapshot` |
| `session_info_changed` | `name` |
| `thinking_level_changed` / `service_tier_changed` | Level / tier |
| `compaction_start` / `compaction_end` | reason, result, aborted, severity |
| `auto_retry_start` / `auto_retry_end` | attempt, delay, success |
| `auth_stale` | provider, sourceTokens |
| `rlm_child_update` | `RlmChildAgentSnapshot` |
| `recap_update` / `goal_update` | recap string / `GoalState` |
| `bash_start` / `bash_output` / `bash_end` | user bash runs |
| `refine_complete` / `refine_failed` | refinement result / error |
| `ipython_sent_agent_message` | kernel-sent agent message |

Session persistence of messages is internal (on message end). Multiple listeners are supported; each `subscribe` returns its own unsubscribe.

### Extension session events

| Event | Cancellable | When |
|-------|-------------|------|
| `session_start` | no | `startup` \| `reload` \| `new` \| `resume` \| `fork` |
| `session_before_switch` | yes | Before new/resume |
| `session_before_fork` | yes | Before fork |
| `session_before_tree` | yes | Before leaf change; can supply summary |
| `session_tree` | no | After leaf change |
| `session_before_compact` / `session_compact` | yes / no | Compaction |
| `session_shutdown` | no | `quit` \| `reload` \| `new` \| `resume` \| `fork` |

## Session-scoped vs durable state

| State | Lifetime | Storage |
|-------|----------|---------|
| Action queue (steer/followUp), pump epoch, commit fences | Process / until drain | Memory (`ActionStore`) |
| Agent in-memory messages | Until rebuild or navigate | `agent.state.messages` (rebuilt from JSONL) |
| Active IPython kernel + namespace | Until dispose/rebuild | Process + optional snapshot dir on dispose |
| Extension runner, tool registry, base system prompt | Session instance | Memory; rebuilt on bind/reload |
| RLM child run maps, retained child sessions | Parent session lifetime | Memory (+ child JSONL if persisted) |
| Transcript entries (messages, model/thinking changes, labels, compactions, branch summaries, custom) | Durable | Session JSONL tree |
| Goals (`thread_goal_state`) | Durable on branch | JSONL custom/goal entries |
| Auth / models / user settings | Cross-session | `~/.prime/agent/auth.json`, `models.json`, settings |
| Harness prompts, memories, skill/subagent specs | Cross-session (refine) | Continual harness store (separate from session tree) |
| Session leases | While runtime holds path | `agentDir/session-leases/` when `PRIME_AGENT_INTERNAL_SESSION_LEASES` enabled |

```text
Session-scoped (dies with AgentSession instance)
  queues · kernel · extension bindings · active child maps · in-memory agent state

Durable (survives restart / reattach)
  JSONL tree + artifacts · auth · settings · harness snapshots · optional kernel namespace snapshot
```

Ephemeral mode (`SessionManager.inMemory()` or CLI `--no-session`) keeps the same runtime APIs without writing a session file. Daemon-backed long-running work rehydrates from JSONL and optional action-recovery snapshots; see daemon and long-running docs for worker recovery.

## Session metadata and activity

| Getter / property | Meaning |
|-------------------|---------|
| `session.sessionId` | Stable session id from header |
| `session.sessionFile` | Absolute JSONL path, or undefined if in-memory |
| `session.sessionName` | Optional display name |
| `session.rlmDepth` | RLM spawn depth (0 = root) |
| `session.model` | Active model |
| `session.isStreaming` | Agent turn streaming |
| `session.isSessionActive` | Streaming, compacting, retrying, bash, refine, branch summary, or unfinished actions |
| `session.queuedActionCount` | Visible queued steer + follow-up count |
| `runtime.metadata` | `kind: "top-level" \| "subagent"`, parent ids, spawn prompt/code |
| `runtime.diagnostics` | Non-fatal create-time issues |
| `runtime.modelFallbackMessage` | Startup model warning; suppressed once a model is selected |

## Constraints and failure modes

| Condition | Behavior |
|-----------|----------|
| Prompt while streaming without `streamingBehavior` | Throws: specify `'steer'` or `'followUp'` |
| Extension command as steer/followUp text | Rejected |
| Fork invalid entry / non-user with `position: "before"` | Throws `Invalid entry ID for forking` |
| `navigateTree` unknown id | Throws `Entry … not found` |
| Summarize without model | Throws `No model available for summarization` |
| Import missing path | `SessionImportFileNotFoundError` |
| Missing/unresolvable session cwd | `MissingSessionCwdError` / assert failure before open |
| Session already leased by another process | `SessionAlreadyActiveError` (when leases enabled) |
| Replacement build fails after teardown | Error propagates; uncommitted lease released; caller must handle empty runtime |
| Concurrent dispose | Single teardown; later callers await the same promise |

Provider keys are BYOK: `ModelRegistry` + `AuthStorage` resolve API keys/OAuth per provider. Session runtime does not embed a hosted model service.

## Minimal runtime example

From `packages/coding-agent/examples/sdk/13-session-runtime.ts`:

```ts
const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});

let session = await bindSession(runtime);
const original = session.sessionFile;

await runtime.newSession();
session = await bindSession(runtime);

if (original) {
  await runtime.switchSession(original);
  session = await bindSession(runtime);
}

await runtime.dispose();
```

Persistence-only recipe (`11-sessions.ts`): `SessionManager.inMemory()`, `SessionManager.create(cwd)`, or open a path—then `createAgentSession({ sessionManager })` without a multi-session runtime when you never replace the session.

## Related pages

<CardGroup>
  <Card title="Session configuration reference" href="/session-configuration">
    Config keys, defaults, reload, and settings surfaces used by runtime services.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    Daemon, in-process, and snapshot paths; active session state and transfer constraints.
  </Card>
  <Card title="Run daemon-backed sessions" href="/daemon-sessions">
    Detach/reattach, resume selectors, and worker recovery.
  </Card>
  <Card title="Sessions and full control (SDK)" href="/sdk-sessions-control">
    SDK session management, settings injection, and full-control composition.
  </Card>
  <Card title="Continual Harness" href="/continual-harness">
    Durable harness state versus session-scoped transcript state.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and autonomous continuation across disconnects.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    RLM child runtimes, agent-message surface, and multi-agent constraints.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Invalid resume selectors, auth failures, and connection-mode probes.
  </Card>
</CardGroup>

---

## 08. Skills model

> Skills as importable Python packages, SKILL.md frontmatter constraints, collision precedence, and project vs personal skill scope.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/08-skills-model.md
- Generated: 2026-08-06T19:41:32.870Z

### Source Files

- `packages/coding-agent/skills/skill-creator/SKILL.md`
- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/test/fixtures/skills/valid-skill/SKILL.md`
- `packages/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts`
- `packages/coding-agent/skills/edit/SKILL.md`
- `packages/coding-agent/skills/edit/src/edit/__init__.py`

---
title: "Skills model"
description: "Skills as importable Python packages, SKILL.md frontmatter constraints, collision precedence, and project vs personal skill scope."
---

Skills in Prime Agent are directories with a required `SKILL.md` (YAML frontmatter plus markdown instructions). At startup the runtime loads only each skill’s `name` and `description` into the system prompt; the full body loads on demand when a task matches. Discovery and merge run through `DefaultResourceLoader` (`reload()` / `getSkills()`), which returns resolved `Skill` records and collision or load diagnostics. Prime Agent follows the [Agent Skills standard](https://agentskills.io/specification) and extends it with Python-backed skills installed into the agent’s persistent IPython kernel.

## Skill kinds

| Kind | Shape | Use when |
|---|---|---|
| **markdown** | `SKILL.md` plus optional `scripts/`, `references/`, `assets/` | Workflows, CLI recipes, domain knowledge, multi-step instructions |
| **python** | Same markdown skill **plus** a Python package installed into the persistent IPython kernel | Capabilities that are naturally one call: `await my_skill(...)` — API wrappers, fetchers, converters, computations |

Default to markdown. Use Python only when the agent should **call** the capability from the kernel rather than follow instructions.

## Scope and install locations

| Scope | Path | Typical use |
|---|---|---|
| **Project** | `.prime/agent/skills/<name>/` | Shared via the repo |
| **Personal (global)** | `~/.prime/agent/skills/<name>/` | User-wide skills |
| **Package** | Package `skills/` directory, or paths listed under `pi.skills` in that package’s `package.json` | Skills shipped with an npm package |

Package skills are wired through settings: a `settings.json` under the user agent dir or project `.prime/agent/` may list package roots in a `packages` array. The package’s `package.json` can declare skill paths with `pi: { skills: ["skills/<name>"] }`.

## Layout

```
my-skill/
├── SKILL.md              # Required: frontmatter + instructions
├── scripts/              # Optional helper scripts referenced by instructions
├── references/           # Optional detail docs (load only when needed)
└── assets/               # Optional templates and data files
```

Only `SKILL.md` is required; everything else is freeform. Paths in the body are relative to the skill directory (the directory that contains `SKILL.md`).

## SKILL.md frontmatter

```markdown
---
name: my-skill
description: What this skill does and when to use it. Be specific.
---
```

| Field | Required | Rules |
|---|---|---|
| `name` | Yes | Max 64 chars. Lowercase `a-z`, `0-9`, hyphens. No leading, trailing, or consecutive hyphens. Must match the parent directory name. |
| `description` | Yes | Max 1024 chars. **Missing or empty description → skill is silently not loaded.** |
| `disable-model-invocation` | No | `true` hides the skill from the system prompt; only explicit `/skill:<name>` invokes it. |
| `license` | No | License name or reference to a bundled file. |
| `compatibility` | No | Max 500 chars. Environment requirements. |
| `metadata` | No | Arbitrary key-value mapping. |
| `allowed-tools` | No | Space-delimited pre-approved tools (experimental). |

Unknown fields are ignored. Name rule violations produce **warnings** but the skill still loads. Description emptiness is the hard load gate.

### Description as routing surface

The description is the only text the model sees before deciding to load the skill. State **what** the skill does **and** **when** to use it (trigger conditions, concrete tasks, tools, phrases).

- Good: `Extracts text and tables from PDF files, fills PDF forms, and merges PDFs. Use when working with PDF documents.`
- Poor: `Helps with PDFs.`

### Body and progressive disclosure

Keep `SKILL.md` short: decision flow, common commands, contract. Put exhaustive detail (schemas, long option lists, large examples) in `references/*.md` and link them so they enter context only when needed. State setup (installs, env vars, credentials) early and explicitly.

## Collision precedence

On a name collision, **the first skill found wins**. Effective precedence (highest first):

1. Explicit `--skill` paths and `skills` settings entries  
2. **Project** skills (`.prime/agent/skills/`)  
3. **User / global** skills (`agentDir/skills/`, typically `~/.prime/agent/skills/`)  
4. **Package** skills (via `packages` + package `pi.skills`)  
5. **Built-in** skills  

Regression coverage for this order (`DefaultResourceLoader`):

| Scenario | Winner |
|---|---|
| Same name on user auto-discovered skill and package skill | User skill |
| Same name on project skill and package skill | Project skill |
| Same name on project, user, and package | Project skill |

When a collision occurs, `loader.getSkills().diagnostics` can include a diagnostic with `type: "collision"` and `collision.name` / `collision.loserPath` (for example the package skill path loses when a user skill wins).

```text
  load order / precedence (winner = first match for a name)
  ─────────────────────────────────────────────────────────
  explicit --skill / settings.skills
            │
            ▼
  project  .prime/agent/skills/<name>/
            │
            ▼
  user     <agentDir>/skills/<name>/
            │
            ▼
  package  packages[] + package.json pi.skills
            │
            ▼
  built-in
```

## Runtime and SDK surface

### Discovery and override

`DefaultResourceLoader` is constructed with `cwd` and `agentDir`, then `await loader.reload()`. `loader.getSkills()` returns `{ skills, diagnostics }`.

SDK configuration can filter or replace the discovered set via `skillsOverride`:

```ts
const customSkill: Skill = {
  name: "my-skill",
  description: "Custom project instructions",
  filePath: "/virtual/SKILL.md",
  baseDir: "/virtual",
  sourceInfo: createSyntheticSourceInfo("/virtual/SKILL.md", { source: "sdk" }),
  disableModelInvocation: false,
  kind: "markdown",
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  skillsOverride: (current) => {
    const filteredSkills = current.skills.filter(
      (s) => s.name.includes("browser") || s.name.includes("search"),
    );
    return {
      skills: [...filteredSkills, customSkill],
      diagnostics: current.diagnostics,
    };
  },
});
await loader.reload();
const { skills: allSkills, diagnostics } = loader.getSkills();
```

Pass the loader into `createAgentSession({ resourceLoader: loader, ... })` so the session uses the merged skill set.

### Skill record fields (SDK)

| Field | Role |
|---|---|
| `name` | Skill id (matches frontmatter / directory) |
| `description` | Routing text in the system prompt |
| `filePath` | Path to `SKILL.md` (or synthetic path for inline skills) |
| `baseDir` | Skill root directory |
| `sourceInfo` | Provenance (`createSyntheticSourceInfo` for SDK-defined skills) |
| `disableModelInvocation` | Mirrors frontmatter `disable-model-invocation` |
| `kind` | e.g. `"markdown"` |

## Python-backed skill contract (example: `edit`)

Shipped python skills keep the same `SKILL.md` surface and add a package entry that the kernel can call. Example: `packages/coding-agent/skills/edit/`.

**Frontmatter (`edit/SKILL.md`):**

```yaml
---
name: edit
description: Replace an exact, unique string in an existing file. Use for targeted single-occurrence edits to files from the IPython kernel instead of rewriting the whole file.
---
```

**Callable API** (`edit/src/edit/__init__.py`):

```python
async def run(path: str, old_str: str, new_str: str) -> str:
    ...
```

| Input / output | Behavior |
|---|---|
| `path` | Relative, absolute, or `~`-prefixed (home expanded) |
| `old_str` | Must occur **exactly once**; else `ValueError` |
| `new_str` | Replacement text |
| Success | Returns short confirmation (e.g. `Edited <resolved_path>`); may stream a diff via MIME `application/vnd.prime-agent.diff+json` |
| Missing file | `FileNotFoundError` |
| 0 or &gt;1 matches | `ValueError` |

Invocation from the kernel (documented on the skill):

```python
await edit(path="pkg/file.py", old_str=old, new_str=new)
```

Or shell-style: `!edit --path pkg/file.py --old-str "..." --new-str "..."`.

## Load, reload, and verification

| Signal | Meaning |
|---|---|
| Startup | Name + description only in system prompt; body on demand |
| Interactive `/reload` | Picks up new or changed skills without full process restart |
| Other sessions | Pick up skills on start |
| `/skill:<name>` | Explicit invoke (required when `disable-model-invocation: true`) |
| Empty / missing description | Skill **not loaded** (silent) |
| Name rule break | Warning; skill still loads |
| Name collision | Winner per precedence; diagnostics may record loser path |
| `getSkills().diagnostics` | Load warnings and collisions for tooling or user inspection |

Verification checklist after authoring:

1. Frontmatter rules, especially `name` ≡ directory name and non-empty `description`.  
2. Reload or new session; inspect warnings / diagnostics for bad names, missing descriptions, collisions.  
3. Optional direct invoke: `/skill:<name>`.  
4. For Python-backed skills, also satisfy the package install / callable checks described in skill-creator’s `references/python-skills.md` (create flow).

## Settings touchpoints

| Surface | Role |
|---|---|
| User `settings.json` under `agentDir` | e.g. `{ "packages": ["<pkgDir>"] }` to attach package skill roots |
| Project `.prime/agent/settings.json` | Same `packages` pattern at project scope |
| Package `package.json` → `pi.skills` | Relative skill paths inside the package (e.g. `skills/web-fetch`) |
| Explicit `skills` settings / `--skill` | Highest precedence sources |

Skill packs remain **file, repository, or catalog sources**—not tied to a particular model provider. Provider auth and model selection are separate from skill discovery and collision.

## Failure modes

| Symptom | Likely cause | What to check |
|---|---|---|
| Skill never appears | Empty/missing `description` | Frontmatter; silent skip |
| Unexpected skill body | Name collision | Project vs user vs package; diagnostics `type: "collision"` |
| Model never loads skill | `disable-model-invocation: true` | Invoke with `/skill:<name>` |
| Name warnings | Frontmatter `name` ≠ dir or invalid charset | Fix name; skill may still load |
| Package skill ignored | Missing `packages` entry or wrong `pi.skills` path | User/project `settings.json` and package `package.json` |

## Related pages

<CardGroup>
  <Card title="Create and install skills" href="/create-skills">
    Author a skill package with skill-creator, required SKILL.md fields, Python package layout, and load-path verification.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog of shipped skills (goal, refine, compact, heartbeat, observe, message, edit, integrations) with entry modules and invocation roles.
  </Card>
  <Card title="Skills, tools, and extensions (SDK)" href="/sdk-skills-tools-extensions">
    SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring.
  </Card>
  <Card title="Continual Harness" href="/continual-harness">
    Durable harness state including skill specs, refine boundaries, and rollback snapshots.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython as the control tool and how kernel-callable Python skills fit the control plane.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Register extensions and custom tools alongside skills for broader capability surfaces.
  </Card>
</CardGroup>

---

## 09. Subagents and messaging

> rlm-spawned child agents, agent-message skill surface, direct agent-to-agent communication, and multi-agent orchestration constraints.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/09-subagents-and-messaging.md
- Generated: 2026-08-06T19:45:50.820Z

### Source Files

- `packages/coding-agent/examples/extensions/subagent/agents.ts`
- `packages/coding-agent/examples/extensions/subagent/README.md`
- `packages/coding-agent/src/core/agent-messages.ts`
- `packages/coding-agent/skills/agent-message/SKILL.md`
- `packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts`
- `packages/coding-agent/test/acp-rlm-subagents.test.ts`

---
title: "Subagents and messaging"
description: "rlm-spawned child agents, agent-message skill surface, direct agent-to-agent communication, and multi-agent orchestration constraints."
---

Prime Agent runs multi-agent work through two complementary surfaces: native **RLM child sessions** admitted with `await rlm(...)` from the persistent IPython kernel, and **nuclear-family agent messaging** through the built-in `agent_message` skill and daemon routing. Child answers never return from `rlm()`; they arrive later as attributed `agent_message` turns, terminal notices, or files the parent inspects.

## Architecture

```mermaid
flowchart TB
  subgraph parent["Parent AgentSession"]
    kernel["IPython kernel\nrlm / agent_message"]
    host["Host bridge\nrlm.run · agent_message.*"]
    registry["Parent-scoped child registry"]
    session["AgentSession.runRlmChild"]
  end

  subgraph children["Child runtimes"]
    c1["Child AgentSession\nindependent context + session_dir"]
    c2["Sibling / retained child"]
  end

  subgraph daemon["Daemon supervisor"]
    route["send_message routing\nsender identity + safety limits"]
  end

  kernel -->|"host_request"| host
  host --> session
  session --> registry
  session --> c1
  c1 -->|"agent_message.send parent"| route
  c2 -->|"agent_message.send sibling/child"| route
  route -->|"steer into target context"| session
  route -->|"steer into target context"| c1
```

| Surface | What it is | Result path |
| --- | --- | --- |
| `await rlm(prompt, name=..., model=...)` | Admit a recursive child `AgentSession` | Admission handle only (`rlm_child_id`, `name`, `session_dir`, `model`) |
| `await agent_message.send(...)` | Direct message to parent / sibling / child | Receipt (`delivered` or `queued`); body is a custom `agent_message` in the target session |
| `prime-agent send <agent> <message>` | CLI entry into the same daemon path | Printed delivery status / JSON receipt |
| Extension `subagent` tool | Optional file-defined subprocess agents | Tool result from a separate process (not the native RLM registry) |

<Note>
Native RLM children share the TypeScript agent runtime, providers, skills, tools, and session machinery with the parent. The example extension under `packages/coding-agent/examples/extensions/subagent/` is a separate subprocess workflow for markdown agent profiles; prefer `rlm(...)` for built-in recursive orchestration.
</Note>

## Spawn RLM children

### Admission API

The callable `rlm` object is preloaded in the kernel. These forms are equivalent:

```python
handle = await rlm("Review the authentication flow", name="auth-reviewer")
handle = await rlm.run("Review the authentication flow", name="auth-reviewer")
print(handle.rlm_child_id, handle.name, handle.session_dir, handle.model)
```

Supported kwargs:

| Kwarg | Type | Required | Notes |
| --- | --- | --- | --- |
| `name` | string | no | Unique among siblings under the same parent; max 64 chars after trim |
| `model` | string | no | Exact `provider/id` from `await rlm.find_models(...)`; unavailable models fail spawn (no silent fallback) |

Any other kwarg fails with `Unsupported rlm.run kwargs: ...`.

Default names are generated as readable selectors (`subagent-<prompt-slug>-<id-suffix>`) when `name` is omitted. Reserved selectors such as `all` / `*` / `broadcast` are rejected for names because they collide with messaging policy.

### What admission returns

```python
# RLMSpawnHandle — admission only, never the child answer
{
  "rlm_child_id": "sub-…",
  "name": "auth-reviewer",
  "session_dir": "…/artifacts/…/sub-…",
  "model": "provider/model-id",
}
```

Spawn is **fire-and-forget at the call site**: the host creates the child runtime and runs the prompt in detached work. End the parent turn instead of awaiting completion.

```python
api = await rlm("Review the public API", name="api-reviewer")
tests = await rlm("Review test coverage", name="test-reviewer")
# parent turn ends; replies arrive later via agent_message or files
```

### Lifecycle and registry

```python
children = await rlm.list_subagents()
for child in children:
    print(child.session_name, child.status, child.active_session_id, child.rlm_child_id)

await rlm.delete_subagent(children[0])  # id, active session id, session id, or unique name
```

| Status | Meaning |
| --- | --- |
| `running` | Child task still in flight |
| `completed` | Initial task finished; daemon-backed children can remain addressable for follow-ups |
| `error` | Startup or run failed |

Registry scope is **parent-session-local**. It survives compaction, kernel restart, and parent restore. An unrelated new root session does not inherit children. Deletion cancels/closes the runtime and tombstones the registry entry; it does **not** erase transcript or artifact files.

### Inheritance and depth

| Property | Behavior |
| --- | --- |
| Model | Child inherits parent model unless `model=` selects an authenticated executable model |
| Tools / skills / retry / resource loader | Reused from parent runtime configuration |
| Depth | Child receives `RLM_DEPTH + 1`; spawn blocked when `RLM_DEPTH >= RLM_MAX_DEPTH` |
| Default max depth | `1` (root may spawn children; children may not recurse further unless raised) |

Depth resolution order: chat-persisted value → configured/inherited option → global settings `rlmMaxDepth` → env `RLM_MAX_DEPTH` → default `1`.

```text
RLM recursion depth limit reached (RLM_DEPTH=1, RLM_MAX_DEPTH=1)
```

### Child reply doctrine

When `agent_message` is installed, child system doctrine tells the model:

- Task prompts are labeled `[task from parent]`.
- When an answer is required, reply with `await agent_message.send(message, receiver_role="parent")`.
- Not every task needs a reply; cleanup and idle after sending.

If a child finishes without any parent reply, the parent still learns via an attributed agent message when messaging is available, or a fallback `rlm_child_terminal_notice` injection (`completed without sending a reply`). Silent drop is not allowed.

## Agent messaging surface

### Built-in skill

Skill package: `packages/coding-agent/skills/agent-message/`  
Import name: `agent_message`  
Host requests: `agent_message.list_agents`, `agent_message.send`

```python
roster = await agent_message.list_agents()
# roster["current"] → name, id, depth
# roster["entries"] → relationship, name, id, depth, status (+ repliedSinceTask for children)

receipt = await agent_message.send(
    "Please inspect the latest result.",
    receiver_role="child",
    receiver_name="api-reviewer",
)
print(receipt["deliveryStatus"], receipt.get("deliveredAt") or receipt.get("queuedAt"))
```

### Send contract

| Argument | Constraint |
| --- | --- |
| `message` | Non-empty string after trim |
| `receiver_role` | `"parent"` \| `"sibling"` \| `"child"` |
| `receiver_name` | **Omitted** for parent; **required** for sibling and child (name or id) |
| Broadcast | `await agent_message.send("all", "status update")` → family roster only |

Positional session-id targets are rejected. Sender identity is daemon-derived; Python cannot supply a spoofable `from` field.

### Receipt shape

| Field | Values / meaning |
| --- | --- |
| `id` | `agentmsg_<uuid>` |
| `source` | `agent_message` |
| `deliveryStatus` | `delivered` (idle target context accepted) or `queued` (steer accepted while busy) |
| `deliveredAt` / `queuedAt` | ISO timestamps |
| `deliveryMode` | Skill path always steers; receipt records `steer` |
| Broadcast result | `{ "receipts": [ receipt \| { target, error } ] }` — one failure does not cancel others |

`send` does **not** block until a queued message is processed. Do not delete a child immediately after send; wait until observation shows idle and the context is no longer needed.

### Safety limits (daemon)

| Limit | Default |
| --- | --- |
| Max message characters | `16384` |
| Max pending messages per target session | `20` |
| Rate limit capacity | `3` tokens |
| Rate limit refill | `1` token / `1000` ms |

Daemon admin surfaces:

```bash
prime-agent daemon agent-messages status
prime-agent daemon agent-messages pause
prime-agent daemon agent-messages resume
prime-agent daemon agent-messages clear <session>
```

RPC equivalents: `agent_messages_status`, `agent_messages_pause`, `agent_messages_resume`, `agent_messages_clear`, plus `send_message`.

### Nuclear-family reach

Messaging is limited to **parent, siblings, and direct children**. Roots at depth 0 are siblings of each other. Grandchildren and cousins are out of reach:

```text
Agent reach is limited to parent, siblings, and children
```

Relay through an intermediate child for deeper trees. Session names are unique per sibling group (same parent + depth); conflicts raise:

```text
Agent name "api-reviewer" is unavailable: an agent of that name already exists at depth N under this parent
```

### Delivered prompt shape

Target sessions receive a custom message (`customType: "agent_message"`) whose text content looks like:

```text
[from child:api-reviewer]
Agent-to-agent message received.
Source: agent_message
From: api-reviewer, active …, session …
To: …, active …, session …
Message id: agentmsg_…

Please inspect the latest result.
```

## CLI and external routing

```bash
prime-agent send <agent> "Please verify the latest migration"
prime-agent send --from <source-agent> <target-agent> --message "…"
```

From a parent that already holds child handles:

```python
children = await rlm.list_subagents()
child = next(c for c in children if c.session_name == "api-reviewer")
await agent_message.send(
    "Continue with the updated diff",
    receiver_role="child",
    receiver_name=child.session_name,
)
```

Sending to an idle completed daemon-backed subagent starts an ordinary follow-up turn in that same child session/context. The child remains available only until the parent session closes (unless deleted earlier).

## ACP surface for RLM children

In ACP mode, RLM subagent lifecycle is streamed to the client as namespaced metadata under the Prime Agent meta namespace (`subagents` entries with statuses such as `running` and `done`). Fire-and-forget children still surface without requiring a concurrent user prompt turn when the ACP session is subscribed for the session lifetime.

## Optional extension: file-defined subprocess agents

The example extension at `packages/coding-agent/examples/extensions/subagent/` is **not** the native RLM path. It:

- Discovers markdown agents from `~/.prime/agent/agents/*.md` (user) and optionally `.prime/agent/agents/*.md` (project)
- Spawns a separate Prime Agent process per invocation with isolated context
- Supports single, parallel (max 8 tasks, 4 concurrent), and chain modes

Agent definition frontmatter:

```markdown
---
name: scout
description: Fast codebase recon
tools: bash
model: claude-haiku-4-5
---

System prompt body…
```

| Scope | Load path | Default |
| --- | --- | --- |
| `user` | `~/.prime/agent/agents` | Default (safe) |
| `project` | nearest `.prime/agent/agents` | Off unless `agentScope: "project"` or `"both"` |
| `both` | project overrides same-name user agents | Only for trusted repos |

Project-local agents are repo-controlled prompts that can instruct tools including shell/IPython. Interactive confirmation applies before project agents unless disabled.

## Multi-agent orchestration constraints

| Constraint | Rule |
| --- | --- |
| Result channel | Never treat `rlm()` return as the answer; use `agent_message` or files |
| Reach | Parent / sibling / child only; relay for deeper graphs |
| Naming | Sibling names unique under parent; reserved `all`/`broadcast`/`*` blocked |
| Depth | Default max depth `1`; raise via settings/env/chat before nested recursion |
| Broadcast | Family roster only; not a global daemon broadcast |
| Identity | Daemon-derived sender; cannot spoof `from` from Python |
| Queues | Busy targets get steered/queued messages; capacity and rate limited |
| Child deletion | Wait for idle after follow-ups; deletion removes messaging reach, not disk artifacts |
| Usage | Child token/cost folds into the parent assistant turn that spawned it |
| Trust | Kernel and workers use OS permissions of the client; not a security sandbox |

## Common workflows

### Parallel research, fan-in later

```python
a = await rlm("Find auth entry points", name="auth-scout")
b = await rlm("Find provider config paths", name="provider-scout")
# end turn — collect agent_message replies or files children write
```

### Explicit parent follow-up

```python
children = await rlm.list_subagents()
await agent_message.send(
    "Re-check after the new regression landed.",
    receiver_role="child",
    receiver_name="auth-scout",
)
# keep child until idle before delete
# await rlm.delete_subagent("auth-scout")
```

### Child answer path

```python
await agent_message.send(
    "Findings: …",
    receiver_role="parent",
)
```

### Family broadcast status

```python
result = await agent_message.send("all", "stand down — root is compacting")
for item in result["receipts"]:
    print(item)
```

## Failure modes

| Symptom | Likely cause | Action |
| --- | --- | --- |
| `RLM recursion depth limit reached` | Depth at max | Raise `rlmMaxDepth` / `RLM_MAX_DEPTH` if nested spawn is intentional |
| `Requested subagent model "…" is unavailable…` | Bad or unauthenticated selector | `await rlm.find_models(query)` and use an exact returned selector, or omit `model` |
| `Agent name "…" is unavailable…` | Sibling name collision | Rename, delete old child, or pick another name |
| `Broadcast agent messaging is not supported` | Target was `*` / `all` / `broadcast` as a direct target without the `send("all", msg)` form | Use role addressing or `send("all", message)` |
| `Agent reach is limited to parent, siblings, and children` | Target outside nuclear family | Relay through intermediate child |
| `Target session has too many pending messages` | Queue at `20` unfinished | Wait for target idle; avoid message storms |
| Rate-limit reject | Sender bucket exhausted | Back off ~1s per refill token |
| `agent messaging is not available in this session` | No controller (e.g. non-daemon / skill filtered) | Use daemon-backed session; ensure skill not filtered out |
| Child finished, parent saw nothing | Delivery failed | Parent still gets fallback terminal notice; check messaging controller health |
| Extension `Unknown agent: "…"` | Markdown agent not discovered | Install under user agents path or enable project scope deliberately |

## Verification signals

| Check | Expected |
| --- | --- |
| Spawn | Immediate handle with non-empty `rlm_child_id` and `name` |
| List | New entry appears in `rlm.list_subagents()` with `status` `running` then `completed`/`error` |
| Reply | Parent transcript shows custom `agent_message` with `[from child:…]` (or parent relationship label) |
| Terminal silence | Parent still receives completion notice text containing `completed without sending a reply` |
| ACP | Client `session/update` meta includes subagent lifecycle entries |
| CLI send | `Sent to <name>` or `Queued for <name>` (or JSON receipt with `deliveryStatus`) |

## Related pages

<CardGroup>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython control tool, prompt-as-variable context, and rlm(...) admission model.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, autonomous mode, and retained subagents across disconnects.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog entry for agent-message and related orchestration skills.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, queueing, tree navigation, and session-scoped vs durable state.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Extension registration and the sample subagent tool patterns.
  </Card>
  <Card title="Run daemon-backed sessions" href="/daemon-sessions">
    Detach/reattach, resume selectors, and worker recovery for multi-agent work.
  </Card>
</CardGroup>

---

## 10. Run daemon-backed sessions

> Background daemon client modes, active session state, detach and reattach, resume selectors, and worker recovery verification.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/10-run-daemon-backed-sessions.md
- Generated: 2026-08-06T19:46:55.957Z

### Source Files

- `packages/coding-agent/src/modes/daemon/active-session-state.ts`
- `packages/coding-agent/docs/agent-connection.md`
- `packages/coding-agent/test/agent-connection-daemon.test.ts`
- `packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts`
- `packages/coding-agent/test/suite/regressions/4656-resume-active-session.test.ts`
- `packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts`

---
title: "Run daemon-backed sessions"
description: "Background daemon client modes, active session state, detach and reattach, resume selectors, and worker recovery verification."
---

Daemon-backed sessions run agent execution in resident worker processes under a local supervisor. Terminal UIs, print/JSON/RPC clients, and CLI lifecycle commands attach over the daemon protocol (`prime-agent.daemon` protocol v7) while the worker owns the session, queue, kernel, and persisted transcript.

## Architecture

```mermaid
flowchart LR
  subgraph clients["Clients"]
    tui["Interactive TUI"]
    print["print / json / rpc"]
    cli["list · attach · stop · send"]
  end

  subgraph supervisor["Daemon supervisor"]
    socket["Unix socket / named pipe"]
    route["attach · detach · reattach · list"]
    journal["command journal + ownership"]
  end

  subgraph worker["Resident worker"]
    runtime["AgentSessionRuntime"]
    session["AgentSession + JSONL"]
    kernel["IPython kernel"]
  end

  tui --> socket
  print --> socket
  cli --> socket
  socket --> route
  route --> runtime
  runtime --> session
  runtime --> kernel
  journal -.-> route
```

| Layer | Responsibility |
|---|---|
| Client | Presentation, input, local preferences; talks through `AgentConnection` / `DaemonClient` |
| Supervisor | Socket accept, session routing, multi-client attach sets, ownership, recovery coordination |
| Worker | Provider calls, tools, queues, compaction, schedules, RLM descendants, persistence |

Workers are process-isolated for lifecycle and failure containment. They normally run with the same OS permissions as the client (not a security sandbox). Provider credentials stay BYOK: the worker uses the same auth surfaces as interactive sessions.

Default socket path:

| Platform | Path |
|---|---|
| Unix | `$TMPDIR/prime-agent-<uid>/daemon.sock` |
| Windows | `\\.\pipe\prime-agent-daemon` |

Override with `--daemon-socket <path>` (also accepted as `--socket` on internal daemon subcommands).

## Client modes

Interactive, print, JSON, RPC, piped-stdin, and `--no-session` clients share the daemon-owned worker runtime. Output protocols and exit semantics stay mode-specific; execution ownership does not.

| Mode | Entry | Execution owner | Client lifecycle |
|---|---|---|---|
| Interactive TUI | `prime-agent` (TTY) | Resident worker | Detach on UI exit; worker keeps running |
| Print | `--print` / `-p` | Resident worker | One-shot prompt; worker may remain for other clients |
| JSON | `--mode json` | Resident worker | JSONL events; daemon-backed |
| RPC | `--mode rpc` | Resident worker | Stdio JSON RPC; drains accepted commands before EOF |
| Piped stdin | non-TTY stdin | Resident worker | Same path as other headless clients |
| No session | `--no-session` | Resident worker (`noSession`) | Ephemeral session file behavior |
| Legacy rollback | `PRIME_AGENT_INTERNAL_LEGACY_OWNED_WORKER_FRONTEND=1` | In-process (not daemon) | Escape hatch only; does not create the daemon socket |

Headless runtime services (extensions that load agent tools, kernels, and related worker-side wiring) load in the worker process, not in the thin client frontend.

Client-owned (short-lived) workers advertise the `client_owned_sessions` capability and use `lifecycle: "client_owned"` on `create`. Resident sessions omit client ownership and survive client exit. Promotion from owned to resident commits durable worker state before best-effort peer sync; persistence failure rolls the promotion back.

## Active session state

Each live agent has an **active session id** (`activeSessionId`) distinct from the durable `sessionId` / session file. Supervisor state tracks:

| Field | Role |
|---|---|
| `activeSessionId` | Public attach target (12-char display form of a UUID by default) |
| `runtime` | `AgentSessionRuntime` for the worker-hosted session |
| `clients` | Set of attached `DaemonSocketClient` sockets |
| `pendingAttaches` | In-flight attach snapshot reservations (counts toward busyness) |
| `eventGeneration` / `lastEventSequence` | Event cursor generation + monotonic sequence |
| `clientEnv` | Allowlisted client env (e.g. Herdr pane identity), bound once at create/adopt |
| `summaryState` | Background status for the agents view |

Socket clients also track per-socket attach sets, optional catch-up after backpressure, snapshot streaming handles, and capability negotiation (`attach_snapshot`, `event_sequence`, `extension_ui`, `slim_attach`, `chunked_snapshot`, `client_owned_sessions`).

Session list rows (`SessionSummary`) expose:

| Field | Values / notes |
|---|---|
| `lifecycle` | `draft` (no message yet) · `live` (agents view) · `archived` (resume-only) |
| `activity` | `working` · `idle` |
| `workerState` | `starting` · `ready` · `recovering` · `failed` |
| `workerPid` | Diagnostic only — not a stable session id |
| `attachedClients` | Count of attached sockets |
| `sessionName` | Optional human name for selectors |

## Lifecycle commands

Public agent lifecycle surface (preferred):

```bash
prime-agent list [--all] [--json]
prime-agent attach <agent>
prime-agent stop <agent> [--json]
prime-agent rename <agent> <name> [--json]
prime-agent send [--from <agent>] <agent> <message>
prime-agent agents
prime-agent status [--json]
prime-agent doctor [--fix] [--json]
prime-agent shutdown [--force] [--json]
```

| Command | Behavior |
|---|---|
| `list` | Live agents by default; `--all` includes saved/non-live rows |
| `attach <agent>` | Rewrites to interactive `--resume <agent>` (cannot combine with `--resume` / `--continue` / `--fork`) |
| `stop <agent>` | Daemon `kill` for that active session |
| `rename` | Sets durable `sessionName` |
| `send` | Direct inter-agent message (`--steer` / `--follow-up` modes) |
| `agents` | Opens the agents view |
| `status` | Background service / process inventory |
| `doctor` | Inspect (and optionally `--fix`) stale sockets / idle orphans |
| `shutdown` | Stop all agents and services; `--force` skips confirm and kills unresponsive workers |

Internal `prime-agent daemon …` remains available for development; public `daemon` as a top-level command is rejected in favor of the agent commands above.

### Start and open

`daemon start` (or `daemon open` when no supervisor is listening) spawns:

```bash
prime-agent --mode daemon --daemon-socket <path> [session flags…]
```

as a detached child, then waits until the socket accepts connections (10s timeout). `open` then `create`s a session (auto name `1`, `2`, … when unnamed) and attaches a lightweight readline terminal.

### Create, attach, detach (protocol)

| Command | Purpose |
|---|---|
| `create` | Spawn/register a session; optional `sessionPath`, `continueRecent`, `name`, `config`, `lifecycle` |
| `attach` | Bind a client to `activeSessionId`; optional `resumeCursor`, capabilities, adopt-only client env |
| `detach` | Unbind this client (`activeSessionId` optional for detach-all-on-socket) |
| `reattach` | Move one client from `activeSessionId` to `targetActiveSessionId` without stopping peers |
| `kill` | Stop the worker/session |
| `retry_worker` | CLI `daemon retry <session>` — request worker restart/retry |
| `promote_owned_session` / `complete_owned_session` | Client-owned lifecycle transitions |

Attach returns a coherent `DaemonAttachResult`: protocol info, snapshot (state + messages + last event cursor), replay status (`complete` | `partial` | `unavailable`), optional chunked `snapshotStream`, and negotiated client id/capabilities.

Large transcripts stream as `session_snapshot_begin` / `session_snapshot_chunk` / `session_snapshot_end`. Live events carry generation-aware cursors `{ generation, sequence }`. Bare sequence numbers are not comparable across worker generations.

## Detach and reattach

Closing the TUI or ending a headless attach **detaches the client**. It does not stop the resident worker. Queue, schedules, goals, heartbeats, kernel, and RLM descendants keep running under the supervisor.

```bash
prime-agent list
prime-agent attach <activeSessionId|sessionId|name|suffix>
```

From a lightweight daemon attach terminal, `/detach`, `/quit`, and `/exit` send `detach` and print `Detached.`.

### Multi-client attach

Multiple clients may attach independently to the same active session. Detaching one client does not detach peers.

### `/resume` onto an already-live worker

When `switch_session` targets a session path already resident under another active id, the supervisor returns `session_already_active` with `activeSessionId`. Non-owned clients then issue `reattach` from the source active id to the target, apply a replacement snapshot (inline or streamed), and emit `session_replaced`. Peer clients on source and target stay attached.

Client-owned headless workers do **not** silently reattach across ownership; `ownedSession` surfaces rethrow `session_already_active` instead of reattaching.

### Socket loss

After a transient socket drop, `DaemonAgentConnection` reconnects with the same client identity and last cursor, reattaches, and emits `session_resynced`. If incremental replay is unavailable, the attach snapshot is the recovery baseline. Optional `recoverDaemon` hooks run during reconnect for supervisor recovery waits.

## Resume selectors

Selectors resolve against live sessions (CLI and supervisor) in this order:

1. Exact `activeSessionId` map key
2. Exact `sessionId` or `sessionName`
3. Unambiguous hex **suffix** match on `activeSessionId` or `sessionId` (normalized hex; display ids are 12 chars)

| Outcome | Error / result |
|---|---|
| One match | That session’s `activeSessionId` |
| Multiple matches | `Ambiguous active session "<selector>": matches …` / CLI `Ambiguous active session "<selector>"` |
| No match | `Unknown active session: <selector>` |

Saved-session resume (disk picker) uses path or id forms:

```bash
prime-agent --resume
prime-agent --resume <path|id>
prime-agent --resume -- "continue this work"
prime-agent --continue
prime-agent attach <live-agent>   # live daemon target → interactive resume
```

| Selector context | Accepts |
|---|---|
| Live daemon (`list` / `attach` / `stop` / messaging) | Active id, session id, session name, unambiguous suffix |
| Saved resume (`--resume` / `/resume`) | Session file path, session id (prefix/suffix heuristics for saved ids), interactive picker |
| Archived lifecycle | Not shown in default live list; reachable via saved resume |

`attach` cannot be combined with `--resume`, `--continue`, or `--fork`.

## Worker recovery

Resident workers are replaceable under the **current supervisor generation**. Recovery properties enforced by protocol and tests:

| Mechanism | Behavior |
|---|---|
| Supervisor generation | Only the current owner generation may replace a crashed worker; stale supervisors are displaced |
| Worker auth + commands | Stale generation commands fail with `supervisor_generation_stale` |
| Public command journal | Mutating commands journaled by `clientId + commandId`; completed retries return recorded results; uncertain in-flight results are not blindly replayed |
| Ownership lease | Displaced owner cannot insert new public journal entries (`no longer owns`) |
| Worker recovery journal | Append-only busy/idle operation records per `activeSessionId` (`version: 1`, fsync, compact when all idle) |
| `workerState` | Surfaces `recovering` / `failed` on list rows while replacement runs |
| `daemon retry <session>` | Explicit retry/restart request for a live session |
| Update restart | Daemon update path can restore/resume interrupted sessions with counts for total/restored/resumed/failed |

After replacement, clients reattach by `activeSessionId`, take a fresh snapshot, and continue prompts. Event generation changes; clients must treat post-recovery cursors as a new generation.

Worker recovery journal record shape:

```json
{
  "version": 1,
  "activeSessionId": "…",
  "sessionId": "…",
  "sessionFile": "…",
  "busy": true,
  "operation": "…",
  "recordedAt": "2026-…"
}
```

## Protocol notes

| Constant | Value |
|---|---|
| Protocol name | `prime-agent.daemon` |
| Protocol version | `7` |
| Schema revision | `13` |
| Framing | JSONL (public); private framed transport for worker links |
| Mutating commands | Journaled; stable `clientId` + `commandId` for idempotency |
| Client env allowlist | `HERDR_ENV`, `HERDR_PANE_ID`, `HERDR_SOCKET_PATH`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID` (create/adopt only; attach does not rebind identity) |

Classify wire changes as backward-compatible, capability-gated, or incompatible. Capability-gated features require clients to check server capabilities before sending (e.g. `client_owned_sessions`, extension UI, chunked snapshots).

## Verification

<Steps>
  <Step title="Start or reuse a daemon-backed session">
    From a project directory with providers configured:

```bash
prime-agent --print --model <provider/model> "ping"
```

    Expect exit code 0 and no `Timed out waiting for daemon worker` on stderr. The default daemon socket should exist under the platform path above (or the path passed via `--daemon-socket`).
  </Step>
  <Step title="List live agents">
```bash
prime-agent list
prime-agent list --json
```

    Expect a row with `lifecycle: live`, an `activeSessionId`, and `workerState: ready` when healthy.
  </Step>
  <Step title="Detach and reattach">
    Start an interactive session, exit the UI (detach), then:

```bash
prime-agent list
prime-agent attach <activeSessionId-or-name>
```

    Expect the interactive UI to reconnect to the same worker without recreating the session file from scratch.
  </Step>
  <Step title="Confirm multi-mode headless path">
```bash
prime-agent --mode rpc --daemon-socket <path> …
# send: {"id":"state","type":"get_state"}
```

    Expect a successful `get_state` response. EOF should drain already-accepted RPC commands before the connection releases.
  </Step>
  <Step title="Service health">
```bash
prime-agent status
prime-agent doctor
```

    Use `doctor --fix` only when intentional cleanup of stale sockets/orphans is desired. Use `shutdown --force` to stop the whole local daemon fabric.
  </Step>
</Steps>

## Failure modes

| Symptom | Likely cause | Action |
|---|---|---|
| `Unknown active session: …` | Bad selector or session not live | `list`; use exact id/name or longer suffix |
| `Ambiguous active session "…"` | Suffix matches multiple lives | Use full `activeSessionId` or unique `sessionName` |
| `Session is already active in …` | Target already resident | Non-owned UI should reattach; owned workers must not steal |
| `supervisor_generation_stale` | Worker still bound to displaced supervisor | Wait for current generation replacement; reattach |
| `Timed out waiting for daemon worker` | Worker launch failed | Check model/auth, extensions, socket path, `status` / `doctor` |
| Daemon socket already in use | Another supervisor owns the path | Reuse it, or `shutdown` then restart |
| `no longer owns` on mutate | Client talking to displaced supervisor | Reconnect; do not retry against stale owner |
| Attach works but model missing in UI | Client lacks credentials the worker has | Prefer daemon summary `modelFallbackMessage`; fix worker env/auth |
| SIGINT in print mode leaves work running | Detach ≠ kill | `stop <agent>` or `shutdown` if the session should end |

## Related pages

<CardGroup>
  <Card title="Agent connection modes" href="/agent-connection">
    Daemon vs in-process vs snapshot paths, capabilities, and transfer constraints.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, services, events, queueing, and tree navigation.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and work that continues across detach.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Invalid resume selectors, worker recovery probes, and connection failures.
  </Card>
</CardGroup>

---

## 11. Create and install skills

> Author a skill package with skill-creator, required SKILL.md fields, Python package layout, and load-path verification.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/11-create-and-install-skills.md
- Generated: 2026-08-06T19:42:56.731Z

### Source Files

- `packages/coding-agent/skills/skill-creator/SKILL.md`
- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/test/fixtures/skills/python-skill/SKILL.md`
- `packages/coding-agent/test/fixtures/skills/python-skill/src/python_skill/__init__.py`
- `packages/coding-agent/test/fixtures/skills/missing-description/SKILL.md`
- `packages/coding-agent/skills/goal/SKILL.md`

---
title: "Create and install skills"
description: "Author a skill package with skill-creator, required SKILL.md fields, Python package layout, and load-path verification."
---

Prime Agent skills are directories that contain a required `SKILL.md` (YAML frontmatter plus markdown instructions). At startup the agent loads only each skill’s `name` and `description` into the system prompt; the full body loads on demand when a task matches. Prime Agent follows the [Agent Skills standard](https://agentskills.io/specification) and extends it with Python-backed skills callable from the persistent IPython kernel. The built-in `skill-creator` skill is the authoring contract for both kinds.

## Skill kinds

| Kind | Surface | Use when |
|---|---|---|
| `markdown` | `SKILL.md` plus optional `scripts/`, `references/`, and `assets/` | Workflows, CLI recipes, domain knowledge, multi-step instructions |
| `python` | A markdown skill that also ships a Python package installed into the agent’s persistent IPython kernel | Capabilities that are naturally one Python call (`await my_skill(...)`): API wrappers, fetchers, converters, computations |

Default to markdown. Choose Python only when the agent should call a capability from IPython instead of following instruction text.

## Install locations

Pick one install root when the scope is not already fixed by the repo or product surface:

| Scope | Path | Shared how |
|---|---|---|
| Project | `.prime/agent/skills/<name>/` | Checked into the repository |
| Personal (global) | `~/.prime/agent/skills/<name>/` | Local to the user agent directory |
| npm package | package `skills/` directory, or `pi.skills` paths in that package’s `package.json` | Shipped with a package |

`<name>` must match the skill frontmatter `name` and the parent directory name.

The SDK resource loader discovers skills from project and agent skill trees (example discovery comment: `cwd/.pi/skills`, `~/.pi/agent/skills`, and related roots). Prefer the `.prime/agent/skills` locations when authoring or installing for Prime Agent via `skill-creator`.

### Name collision precedence

On a name collision, the first skill found wins:

1. Explicit `--skill` paths and `skills` settings entries
2. Project skills
3. Global skills
4. Package skills
5. Built-in skills

## Create a skill

<Steps>
<Step title="Choose kind and location">
Use markdown unless the agent must call the capability as Python (`await my_skill(...)`). Place the directory under project, personal, or package skill roots from the table above.
</Step>
<Step title="Scaffold the directory">
Create `<name>/SKILL.md`. Optionally add `scripts/`, `references/`, and `assets/`. Everything except `SKILL.md` is freeform.
</Step>
<Step title="Write frontmatter and body">
Set required `name` and `description`. Keep the body short (decision flow, common commands, contract). Put long reference material in `references/*.md` and link it.
</Step>
<Step title="Verify load">
Confirm frontmatter rules, reload the session, check diagnostics/warnings, and invoke `/skill:<name>`. For Python-backed skills, also follow the package checks documented in `skill-creator`’s `references/python-skills.md`.
</Step>
</Steps>

## Directory layout

:::files
my-skill/
├── SKILL.md              # Required: frontmatter + instructions
├── scripts/              # Optional helper scripts the instructions reference
├── references/           # Optional detailed docs, loaded only when needed
└── assets/               # Optional templates and data files
:::

Reference files with paths relative to the skill directory. The agent resolves them against the directory that contains `SKILL.md`.

### Python package fixture layout

A Python-backed skill keeps the same `SKILL.md` root and ships importable code under `src/`:

:::files
python-skill/
├── SKILL.md
└── src/
    └── python_skill/
        └── __init__.py
:::

The test fixture exports an async entrypoint:

```python
# src/python_skill/__init__.py
async def run(value: str = "ok") -> str:
    """Return the provided value."""
    return value
```

Its `SKILL.md` points callers at the import name:

```markdown
---
name: python-skill
description: A Python-backed skill for testing.
---

# Python Skill

Use the `python_skill` import.
```

<Note>
`skill-creator` requires reading `references/python-skills.md` for the full Python package contract before authoring production Python skills. That package-contract detail is not duplicated here.
</Note>

## SKILL.md frontmatter

```markdown
---
name: my-skill
description: What this skill does and when to use it. Be specific.
---
```

| Field | Required | Rules |
|---|---|---|
| `name` | Yes | Max 64 chars. Lowercase `a-z`, `0-9`, hyphens. No leading, trailing, or consecutive hyphens. Must match the parent directory name. |
| `description` | Yes | Max 1024 chars. Missing or empty description → skill is **silently not loaded**. |
| `disable-model-invocation` | No | `true` hides the skill from the system prompt; only explicit `/skill:<name>` invokes it. |
| `license` | No | License name or reference to a bundled file. |
| `compatibility` | No | Max 500 chars. Environment requirements. |
| `metadata` | No | Arbitrary key-value mapping. |
| `allowed-tools` | No | Space-delimited pre-approved tools (experimental). |

Unknown fields are ignored. Name rule violations produce warnings; the skill still loads (when `description` is present and non-empty).

### Description routing

The description is the only text the model sees before deciding to load the skill. State what the skill does and the trigger conditions (`Use when ...`), naming concrete tasks, tools, and request phrases.

| Quality | Example |
|---|---|
| Good | `Extracts text and tables from PDF files, fills PDF forms, and merges PDFs. Use when working with PDF documents.` |
| Poor | `Helps with PDFs.` |

Built-in examples of routing-ready descriptions:

- `skill-creator`: create, validate, and install markdown and Python skills; use when the user asks to create a skill, turn a workflow/script/prompt into a skill, add a Python skill, or asks how to write `SKILL.md` and where skills live.
- `goal`: manage the persistent thread goal from IPython; use to read status/budget, start a goal when explicitly requested, or mark the active goal complete.

### Body progressive disclosure

Keep `SKILL.md` short: decision flow, common commands, and contract. Push API schemas, full option lists, and long examples into `references/*.md` and link them so they enter context only when needed. State setup steps (installs, env vars, credentials) explicitly and early.

## Load path and session verification

After writing the skill:

1. Re-read frontmatter against the field table, especially `name` matching the directory and a non-empty `description`.
2. In an interactive session, run `/reload` to pick up new skills without restart. Other sessions load skills on start.
3. Loading problems (bad name, missing description, name collisions) surface as warnings/diagnostics.
4. Invoke the skill with `/skill:<name>`.
5. For Python-backed skills, run the additional checks from `skill-creator` → `references/python-skills.md`.

<Check>
Success signals: skill appears in discovery, no fatal diagnostics for that skill, and `/skill:<name>` invokes the body. Missing/empty `description` does not load at all (silent skip).
</Check>

### Missing description (silent skip)

This fixture is not loaded because `description` is absent:

```markdown
---
name: missing-description
---

# Missing Description

This skill has no description field.
```

## SDK: discover, filter, and inject skills

Programmatic sessions use `DefaultResourceLoader` from `@earendil-works/pi-coding-agent`. Skills can be discovered, filtered, merged, or replaced through `skillsOverride`.

```ts
import {
  createAgentSession,
  createSyntheticSourceInfo,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
  type Skill,
} from "@earendil-works/pi-coding-agent";

const customSkill: Skill = {
  name: "my-skill",
  description: "Custom project instructions",
  filePath: "/virtual/SKILL.md",
  baseDir: "/virtual",
  sourceInfo: createSyntheticSourceInfo("/virtual/SKILL.md", { source: "sdk" }),
  disableModelInvocation: false,
  kind: "markdown",
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  skillsOverride: (current) => {
    const filteredSkills = current.skills.filter(
      (s) => s.name.includes("browser") || s.name.includes("search"),
    );
    return {
      skills: [...filteredSkills, customSkill],
      diagnostics: current.diagnostics,
    };
  },
});

await loader.reload();
const { skills: allSkills, diagnostics } = loader.getSkills();

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

### `Skill` object fields (SDK)

| Field | Role |
|---|---|
| `name` | Skill identifier |
| `description` | Routing text |
| `filePath` | Path to `SKILL.md` (may be virtual for synthetic skills) |
| `baseDir` | Skill base directory for relative resolution |
| `sourceInfo` | Provenance via `createSyntheticSourceInfo(...)` (example uses `{ source: "sdk" }`) |
| `disableModelInvocation` | When true, hide from model-driven invocation |
| `kind` | e.g. `"markdown"` |

### Loader APIs

| API | Behavior |
|---|---|
| `new DefaultResourceLoader({ cwd, agentDir, skillsOverride })` | Construct loader with optional skill transform |
| `await loader.reload()` | Reload resources, including skills |
| `loader.getSkills()` | Returns `{ skills, diagnostics }` |

Inspect `diagnostics` after reload for load warnings (name issues, collisions, and related problems).

## Troubleshooting

| Symptom | Cause | Action |
|---|---|---|
| Skill never appears | Missing or empty `description` | Add a non-empty `description` (max 1024 chars). Silent non-load. |
| Warning on load, skill still present | `name` rule violation | Fix casing, length, hyphens, or directory match. Name violations warn but still load. |
| Unexpected skill content | Name collision | First match wins; check `--skill` / settings, then project → global → package → built-in. |
| New skill not visible in open session | Session started before install | Run `/reload`, or start a new session. |
| Python skill docs incomplete locally | Package contract not followed | Use `skill-creator` and complete checks in `references/python-skills.md`. |
| SDK session missing expected skills | Override filtered them out | Inspect `skillsOverride` and `diagnostics` from `getSkills()`. |

## Related pages

<CardGroup>
<Card title="Skills model" href="/skills-model">
Skills as importable packages, frontmatter constraints, collision precedence, and project vs personal scope.
</Card>
<Card title="Built-in skills reference" href="/builtin-skills">
Catalog of shipped skills (`goal`, `refine`, `compact`, and others) with entry modules and roles.
</Card>
<Card title="Skills, tools, and extensions (SDK)" href="/sdk-skills-tools-extensions">
SDK recipes for loading skills, registering tools, extensions, and related session wiring.
</Card>
<Card title="RLM control plane" href="/rlm-control-plane">
Persistent IPython as the control tool and how Python-callable skills fit agent execution.
</Card>
<Card title="Continual Harness" href="/continual-harness">
Durable harness state including skill specs, refine boundaries, and rollback snapshots.
</Card>
</CardGroup>

---

## 12. Refine harness state

> Run /refine against the current trajectory, apply evidence-backed harness updates, serialize refine, and use snapshots for rollback.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/12-refine-harness-state.md
- Generated: 2026-08-06T19:47:26.748Z

### Source Files

- `packages/coding-agent/skills/refine/SKILL.md`
- `packages/coding-agent/skills/refine/src/refine/__init__.py`
- `packages/coding-agent/test/suite/agent-session-refine-skill.test.ts`
- `packages/coding-agent/test/suite/agent-session-serialized-refine.test.ts`
- `packages/coding-agent/src/core/agent-session.ts`

---
title: "Refine harness state"
description: "Run /refine against the current trajectory, apply evidence-backed harness updates, serialize refine, and use snapshots for rollback."
---

`/refine` and the bundled `refine` skill plan small create/update/delete edits against the continual harness (prompt notes, memories, skill specs, subagent specs) from the current conversation trajectory, then apply them only after the turn is idle. The base system prompt is never rewritten. Local edits land in the session artifact store; global edits land under the agent harness directory and are logged for cross-session rollback.

## What refine does

Refinement is a host-side subsystem in `AgentSession` and `packages/coding-agent/src/core/refinement/`. It:

1. Reads the trajectory, merged harness overview, and prior refinement history.
2. Emits a JSON proposal of focused harness edits (or an empty `edits` array).
3. Re-loads the target harness file, applies edits with before/after snapshots, persists state, appends a refinement record, and rebuilds the system prompt.

It does **not** edit source files, package new skill packages on disk, or change the trained base system prompt. Harness skill entries are description/reference contracts for Python calls; packaging new code still uses skill authoring tools.

```mermaid
sequenceDiagram
  participant User as User or IPython
  participant Session as AgentSession
  participant Plan as planRefinement
  participant Disk as harness_state.json

  User->>Session: /refine or refine.run
  Note over Session: Schedule only while a turn is active
  Session->>Plan: Background plan (LLM or rollback)
  Plan-->>Session: RefinementPlan + baselineState
  Session->>Session: Wait for agent idle
  Session->>Disk: Re-load target store
  Session->>Disk: apply + saveHarnessState
  Session->>Session: append refinement record, rebuild system prompt
  Session-->>User: refine_complete or refine_failed
```

## Surfaces

| Surface | Entry | Behavior |
| --- | --- | --- |
| Slash command | `/refine [instructions]` | Session command; optional focus text |
| Global scope | `/refine --global [instructions]` | Target global harness store |
| Rollback | `/refine rollback <refinement-id>` | Rebuild inverse edits from recorded snapshots |
| Global rollback | `/refine rollback <id> --global` or `--global` prefix | Same parser; scope resolved from history when possible |
| IPython skill | `await refine.run(...)` / `await refine.status()` | Host bridge; schedules, does not run mid-cell |
| Connection API | `connection.refine({ instructions?, rollbackId?, global? })` | Daemon / in-process / RPC clients |
| Session method | `session.refine(options)` | Plan in background; apply when idle |
| Events | `refine_complete`, `refine_failed` | Session listeners and extension hook on success |

### Slash command

```text
/refine
/refine capture git status before every commit
/refine --global promote error-handling pattern to a reusable skill
/refine rollback refine_20260806123045000
/refine rollback refine_20260806123045000 --global
```

Parser rules (`parseRefineCommandOptions`):

- Optional leading `--global` sets global scope.
- `rollback` without an id errors: `Usage: /refine rollback <refinement-id>`.
- Trailing `--global` after the rollback id is also accepted.
- Any other non-empty remainder is treated as free-text `instructions`.

### Kernel skill (`refine`)

```python
await refine.status()
await refine.run()
await refine.run("create a memory about always checking git status before committing")
await refine.run("promote the error-handling pattern to a global skill", global_=True)
```

| Call | Returns | Notes |
| --- | --- | --- |
| `status()` | `{ pending, in_flight }` | `pending` = request queued for this turn; `in_flight` = plan or apply active (including serialized background plan) |
| `run(instructions=None, global_=False)` | `{ scheduled: True, note: ... }` or `{ scheduled: False, reason: ... }` | Python keyword is `global_` because `global` is reserved |

Host request types: `refine.status`, `refine.run`. Unknown types throw. Handlers register only when auto-refine is allowed for the session (`rlmDepth === 0` and a local harness directory exists from a persisted session).

## Prerequisites and constraints

- **Root session only for skill/auto-refine:** `rlmDepth === 0`. Subagents do not register `refine.*` host handlers.
- **Persisted session for local scope:** Local refine needs a session artifact (or RLM session) dir. Without it, planning fails with: local harness refinement requires a persisted session; use global refinement instead.
- **Active turn for `refine.run`:** Scheduling while not streaming returns `scheduled: false` with reason `no active turn; refine can only be requested while a turn is running`.
- **One coalesced request per turn:** A second `run` before the turn ends updates instructions/global flags; it does not queue two refinements.
- **Never mid-cell:** Application waits until the turn ends and the agent is idle, then rebuilds the system prompt and continues.
- **Base prompt immutable:** Edits targeting id `base_system_prompt` are rejected.
- **Scope isolation:** One refinement writes only the requested scope store. During local refine, global entries are read-only context; propose a local override instead of updating global ids.

## Workflow

<Steps>
  <Step title="Observe a durable lesson">
    Trigger refine after a repeated failure, reusable tactic, delegation role, or behavior policy. Prefer a focused memory, skill, prompt note, or subagent spec over rewriting large harness sections.
  </Step>
  <Step title="Schedule refinement">
    From chat: `/refine …` or `/refine --global …`. From IPython during a turn: `await refine.run(...)`. Check `await refine.status()` if you need `pending` / `in_flight`.
  </Step>
  <Step title="Plan against trajectory">
    Host runs `planRefinement`: conversation slice (up to 80k chars), harness overview, refinement history, and scope policy. Auto-refine may run a cheaper review gate first (`shouldRefine` + optional instructions). Explicit `/refine` and `refine.run` skip that gate.
  </Step>
  <Step title="Apply when idle">
    Apply re-reads the target `harness_state.json`, strips display prefixes `local:` / `global:` from edit ids, runs `applyRefinementProposal` with a planning baseline (rejects edits whose entries changed during planning), saves state, records history, rebuilds the system prompt, and emits `refine_complete`.
  </Step>
  <Step title="Validate and roll back if needed">
    Confirm harness entries and next-turn behavior. If a refinement is harmful: `/refine rollback <refinement-id>` (or the connection/API equivalent). Rollback rebuilds inverse create/update/delete edits from each applied edit's `before` / `after` snapshot.
  </Step>
</Steps>

## Edit model

### Kinds

| Kind | Purpose | Create/update requirements |
| --- | --- | --- |
| `prompt` | Supplemental behavioral notes only | `title`, `content` |
| `memory` | Durable facts, decisions, failures, preferences | `title`, `content` |
| `skill` | Python REPL skill contract | `title`, `content`, `arguments`, `reference` with `type: "python"`, import, and callable or `call_pattern` |
| `subagent` | Reusable delegation role/spec | `title`, `content` |

Actions: `create` | `update` | `delete`. Delete requires `id`. Create may omit `id` (slug from title). Source on written entries is `"refine"`.

### Proposal JSON shape

The planner must return JSON only:

```json
{
  "summary": "one sentence",
  "rationale": "why these edits are justified by trajectory evidence",
  "expectedOutcome": "what should improve and how to validate it",
  "edits": [
    {
      "action": "create",
      "kind": "memory",
      "title": "Check git status before commit",
      "content": "Always run git status before committing.",
      "path": "git",
      "reason": "Agent committed without checking dirty tree twice"
    }
  ]
}
```

Empty `edits` with a rationale is valid when nothing should change.

### Scope policy

| Scope | Store | Use for |
| --- | --- | --- |
| `local` (default) | `session-artifacts/<session-id>/harness/harness_state.json` | Session progress, temporary blockers, current-run coordination, non-reusable project facts |
| `global` | `~/.prime/agent/harness/harness_state.json` (via `getAgentDir()/harness`) | Stable cross-session lessons, durable preferences, reusable skills/subagents, explicitly project-qualified facts |

Merged overview for prompts overlays local on global; colliding local ids are shown with a `local:` prefix for display only—edits must use bare ids.

## Persistence and snapshots

:::files
~/.prime/agent/
  harness/
    harness_state.json          # global store
    refinements.jsonl           # global refinement results (rollback log)
  sessions/
    <session-id>.jsonl          # custom type prime-agent.refinement
  session-artifacts/
    <session-id>/
      harness/
        harness_state.json      # local store
:::

| Artifact | Contents |
| --- | --- |
| `harness_state.json` | `schema`, `entries.{prompt,memory,skill,subagent}`, `refinements[]` event log |
| Applied edit snapshots | Each applied edit records `before` / `after` harness entry clones |
| Session custom entry | `prime-agent.refinement` → full `RefinementResult` |
| Global history | `refinements.jsonl` append-only for global results |

`saveHarnessState` writes via temp file + rename with mode `0o600` (or existing mode). Corrupt state files load as empty rather than crashing the session.

### Rollback mechanics

Given a target refinement id:

1. History is the merge of global `refinements.jsonl` and session custom entries.
2. `rollbackProposal` walks applied edits in reverse: restore `before` via create/update, or delete if there was only `after`.
3. Apply targets the scope inferred from the result (or `harnessStatePath` for legacy local records pointing at global).
4. Missing local state file fails with a clear path error.
5. Result sets `rollbackOf` to the original id.

## Serialized refine vs interactive refine

| Mode | When | Timing |
| --- | --- | --- |
| Interactive (`serializedRefine: false`) | Default TUI / interactive; daemon worker default | Explicit `refine.run` schedules and runs after turn end; auto-refine often after `agent_end` |
| Serialized (`serializedRefine: true`) | Print/JSON/headless (`appMode !== "interactive" && !== "daemon"`) | Plan may start at assistant `message_end` (overlaps tools); apply runs at `shouldStopAfterTurn` so refine never overlaps the primary model call |

Runtime config field:

```ts
// AgentSessionRuntimeConfig
serializedRefine?: boolean;
```

Serialized checkpoint guarantees (from tests and session code):

- At most one concurrent primary/refine model path at the boundary.
- Explicit `refine.run` takes priority over interval auto-refine and skips the review gate.
- A replacement `refine.run` aborts/invalidates an in-flight serialized plan.
- Failure stamps cooldown and does not synchronously retry the same interval review.
- Pending refine is drained before dispose when possible.
- Prompt/model state from the refined harness is visible on the resumed turn.

## Auto-refine

Settings object `autoRefine` (defaults applied in `SettingsManager.getAutoRefineSettings()`):

| Key | Default | Meaning |
| --- | --- | --- |
| `enabled` | `true` | Master switch |
| `turnInterval` | `25` | Assistant turns between interval reviews (min 1) |
| `compact` | `true` | Allow post-compaction auto-refine |
| `cooldownMs` | `20 * 60_000` | Cooldown after review/failure (min 0) |

Triggers: `turn_interval` and `compact`. Auto-refine review returns:

```json
{
  "shouldRefine": true,
  "rationale": "short reason",
  "instructions": "optional instructions for /refine"
}
```

Default auto scope is **local**. Review should only ask for global when lessons are durable and cross-session. Auto-refine is skipped when `rlmDepth > 0` or there is no local harness dir.

## Result and events

### `RefinementResult`

| Field | Type | Meaning |
| --- | --- | --- |
| `id` | `string` | e.g. `refine_<timestamp>` |
| `summary` | `string` | One-line summary |
| `rationale` | `string` | Evidence justification |
| `expectedOutcome` | `string` | Validation expectation |
| `appliedEdits` | array | Each edit with `applied`, optional `error`, `before`/`after` |
| `harnessStatePath` | `string` | Path written |
| `rollbackOf` | `string?` | Set when this result is a rollback |
| `scope` | `"local" \| "global"?` | Effective store |

### Session events

```ts
{ type: "refine_complete"; result: RefinementResult }
{ type: "refine_failed"; error: string }
```

Extension emit on success: `{ type: "refine_complete", id, summary, appliedEdits, scope }`. Listener/extension failures after a successful persist do not convert success into failure.

### Host schedule responses

```json
// success
{ "scheduled": true, "note": "Refinement runs when the current turn ends; ..." }

// no active turn
{ "scheduled": false, "reason": "no active turn; refine can only be requested while a turn is running" }

// status
{ "pending": true, "in_flight": false }
```

## Failure modes

| Symptom | Likely cause | What to do |
| --- | --- | --- |
| `scheduled: false` / no active turn | `refine.run` outside a streaming turn | Call during tool/IPython work inside a turn, or use `/refine` between turns |
| Local refine requires persisted session | Ephemeral / non-persisted session | Persist the session, or use `--global` / `global_=True` |
| Handlers missing | `rlmDepth > 0` or no local harness dir | Refine from the root session only |
| `Refinement <id> not found` | Unknown rollback id | Check session custom entries / global `refinements.jsonl` |
| Local state file not found on rollback | Artifact deleted or path moved | Restore artifact or accept that local rollback is unavailable |
| Entry changed during planning | Concurrent harness write (e.g. `rlm.harness`) | Retry refine; apply re-reads disk and uses baseline conflict checks |
| Planning JSON truncated / parse error | Model hit output budget | Narrow instructions; retry; refine forces non-reasoning complete for JSON |
| `refine_failed` event | Plan/apply error | Inspect `error` string; interval auto-refine will cool down rather than tight-loop |
| Speculative auto-refine noise | Auto-refine too aggressive | Set `autoRefine.enabled: false` or raise `turnInterval` / `cooldownMs` |

## Configuration and SDK notes

- **BYOK/BYOC:** Refine uses the session's currently selected model and provider credentials (`_getRequiredRequestAuth`). No hosted refine service is required.
- **SDK:** Pass `serializedRefine` through session runtime config when building headless agents that must keep refine off the primary model call path.
- **Model output:** Refine and auto-review use `completeSimple` with capped max tokens (`min(model.maxTokens, 32000)` for refine, `4096` for review) and intentionally non-reasoning completion so final text remains parseable JSON.

## Related pages

<CardGroup>
  <Card title="Continual Harness" href="/continual-harness">
    Durable harness entries, merge rules, immutable base prompt, and store layout.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    IPython as control tool, host bridges, and how skills call into the host.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog entry for the refine skill and related host-bridge skills.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and autonomous loops that interact with refine boundaries.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, turn boundaries, and where pending refine is consumed.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Runtime config keys including `serializedRefine` and related session options.
  </Card>
</CardGroup>

---

## 13. Long-running tasks

> Goals, compaction, heartbeats, autonomous mode, and retained subagents that keep multi-turn work progressing across disconnects.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/13-long-running-tasks.md
- Generated: 2026-08-06T19:48:18.209Z

### Source Files

- `packages/coding-agent/skills/goal/SKILL.md`
- `packages/coding-agent/skills/compact/SKILL.md`
- `packages/coding-agent/skills/rlm-heartbeat/SKILL.md`
- `packages/coding-agent/test/suite/agent-session-goal.test.ts`
- `packages/coding-agent/test/suite/agent-session-compaction.test.ts`
- `packages/coding-agent/test/suite/regressions/4482-heartbeat-injected-prompt.test.ts`

---
title: "Long-running tasks"
description: "Goals, compaction, heartbeats, autonomous mode, and retained subagents that keep multi-turn work progressing across disconnects."
---

Prime Agent keeps multi-turn work alive through the resident session worker: durable goal state, context compaction, user and agent heartbeats, general schedules, bounded autonomous continuations, and retained RLM child sessions. Detaching the TUI or CLI client does not stop the worker; the session queue, IPython kernel, schedules, and descendants stay owned by the daemon-backed process.

## Runtime model

Long-running surfaces share one worker-owned session tree rather than a client-owned chat process.

```mermaid
flowchart TD
  client["TUI / CLI / attach client"]
  peer["Peer agent or retained subagent"]
  supervisor["Daemon supervisor<br/>routing + attachments"]

  subgraph worker["Resident session worker"]
    heartbeats["User + RLM heartbeats"]
    schedules["schedule add / cron / once"]
    goals["Persistent goal"]
    autonomous["Autonomous mode"]
    policy["Continuation policy"]
    queue["Session prompt queue"]
    session["AgentSession"]
    kernel["Persistent IPython kernel"]
    children["RLM child sessions"]

    heartbeats --> queue
    schedules --> queue
    goals --> policy
    autonomous --> policy
    policy --> queue
    queue --> session
    session --> kernel
    session <--> children
  end

  artifacts["JSONL transcript + session artifacts"]

  client <-->|"attach · detach · commands"| supervisor
  peer -->|"agent_message / send"| supervisor
  supervisor --> queue
  session --> artifacts
  artifacts -. "restore after restart" .-> session
```

| Surface | Owner | What it does |
|---|---|---|
| Persistent goal | User + host; model completes via `goal` skill | Re-prompts the objective until `await goal.complete()` or user/host pause/clear/budget limit |
| Autonomous mode | Host policy | Injects follow-up turns until gates pass or limits are hit |
| `/heartbeat` | User | One visible recurring instruction per session |
| `rlm_heartbeat` | Agent (IPython) | Multiple internal recurring instructions for the same session |
| `prime-agent schedule` | User or automation | One-time or cron prompts targeted at an addressable agent |
| Compaction | Host auto + `/compact` + `compact` skill | Summarizes older transcript while keeping the kernel and long-running policies |
| Retained RLM children | Parent session registry | Keep completed daemon-backed children addressable for follow-ups |

<Note>
Daemon workers are process-isolated for lifecycle and recovery, not security sandboxes. They normally run with the same OS permissions as the client.
</Note>

## Prerequisites for background progress

Long-running work survives disconnect when the session runs as a daemon-backed worker:

```bash
prime-agent list
prime-agent attach <agent>
prime-agent agents
prime-agent status
prime-agent doctor [--fix]
```

Closing the UI detaches the client. Use `prime-agent stop <agent>` to stop a worker, or `prime-agent shutdown [--force]` to stop all agents and services.

Workers persist transcripts as JSONL and store feature-specific state under the session artifact directory. Supervisor or worker restart can recover session state, schedules, and retained completed RLM children without treating the terminal as the owner of the work.

## Persistent goals

A goal is a durable objective the harness keeps presenting across turns until it is complete, paused, budget-limited, errored, or cleared. Goal status, token budget, usage, and timestamps live in the TypeScript host; the IPython `goal` skill is the kernel-side interface.

### Goal statuses

| Status | Meaning |
|---|---|
| `idle` | No goal |
| `active` | Host continues the objective after ordinary assistant turns |
| `paused` | User paused; host does not continue |
| `budget_limited` | Token budget reached; host injects a wrap-up prompt and does not start new substantive goal work |
| `complete` | Model marked success with `await goal.complete()` |
| `error` | Host recorded an error state |

Only completion is exposed to the model through the skill. Pause, resume, clear, and budget-limit transitions are user/host controlled.

### User controls

```text
/goal Ship the release and verify every published artifact
/goal --budget 200000 Complete the repository migration
/goal status
/goal pause
/goal resume
/goal clear
```

Usage:

- `/goal [--budget <tokens>] <objective>` or `/goal --token-budget <tokens> <objective>`
- Objective max length: `4000` characters
- Token budget must be a positive integer when set

Starting a slash goal activates `ipython` if it is not already in the active tool set so the model can call `goal.complete()`.

### Kernel API

```python
await goal.get()
await goal.create("ship the release notes", token_budget=200000)
await goal.complete()
```

Host responses use snake_case fields:

| Field | Type | Notes |
|---|---|---|
| `goal` | object or `null` | `objective`, `status`, `token_budget`, `tokens_used`, `time_used_seconds`, timestamps |
| `remaining_tokens` | int or `null` | `null` when no budget |
| `completion_budget_report` | string or `null` | Present after successful completion when usage is reportable |

Constraints:

- Do not create a goal unless the user or system/developer instructions explicitly request a persistent long-running goal.
- `goal.create` fails while a goal is active, paused, or budget-limited. Completed or errored goals can be replaced.
- The harness keeps continuing an active goal until `await goal.complete()` arrives. Saying the work is done is not enough.
- Do not call `goal.complete()` only because the budget is nearly exhausted.
- Post-completion assistant turns are not billed against the finished goal's token usage.

Active goals inject `customType: "goal_context"` messages (`<goal_context>...</goal_context>`) for continuation, budget-limit wrap-up, and user objective updates. Events include `goal_update` with the current `GoalState`.

## Heartbeats and schedules

Prime Agent has three related scheduling surfaces.

| Surface | Source id | Cardinality | Default interval | Default delivery |
|---|---|---|---|---|
| `/heartbeat` | `heartbeat` | One user-visible job per session (new create cancels previous active/paused) | `every 5m` | `steer` |
| `rlm_heartbeat` | `rlm_heartbeat` | Many concurrent internal jobs | `every 5m` | `steer` |
| `prime-agent schedule` | general schedule jobs | Many one-time or cron jobs per agent | n/a | n/a |

Minimum recurring interval is 10 seconds.

### User heartbeat

```text
/heartbeat every 10m Check the deployment and report meaningful changes
/heartbeat status
/heartbeat pause
/heartbeat resume
/heartbeat clear
```

Add `--follow-up` when the prompt should wait until the current turn finishes. Default delivery steers (interrupts) active work. Use `/heartbeats` to inspect both user and agent-created heartbeats.

### Agent RLM heartbeats

Use when the agent should own recurring checks without replacing the user's `/heartbeat`:

```python
await rlm_heartbeat.create("check test progress", interval="5m", label="tests")
await rlm_heartbeat.create("watch build", delivery_mode="follow_up")
await rlm_heartbeat.list()
await rlm_heartbeat.update("job-id", status="pause")
await rlm_heartbeat.delete("job-id")
```

| API | Behavior |
|---|---|
| `list(include_inactive=False)` | Active and paused by default |
| `create(instruction, interval=None, label=None, delivery_mode=None)` | Recurring only; multiple allowed |
| `update(id, ...)` | Patch instruction, interval, label, status (`pause`/`resume`), delivery mode |
| `delete(id)` | Cancel one job |

Delivery modes:

- `steer` (default): interrupt the current turn so the heartbeat runs promptly
- `follow_up`: wait until the current turn finishes

The `rlm_heartbeat` skill cannot read, replace, pause, resume, or clear the user-level `/heartbeat`.

Heartbeat prompts are stored as transcript custom messages (`customType: "heartbeat_prompt"`, displayable) while provider input still receives them as user content. Delivery still runs `before_agent_start` extension handlers.

### General schedules

```bash
prime-agent schedule add worker "in 30m" -- "Check the benchmark result"
prime-agent schedule add worker "0 9 * * 1-5" -- "Review open work"
prime-agent schedule list --all
prime-agent schedule cancel <job-id>
```

Jobs are persisted per session and continue while the UI is detached. Due ticks are claimed and advanced before prompt delivery so a crash does not replay an uncertain prompt. Missed ticks coalesce rather than building an unbounded backlog. Different target sessions dispatch independently.

## Autonomous mode

Autonomous mode is a bounded host policy for runs where no human input is expected. After assistant turns, the host may inject a continuation user message until quality gates pass or a limit is reached.

### Enable

Interactive:

```text
/autonomous on
/autonomous status
/autonomous off
```

CLI:

```bash
prime-agent \
  --autonomous \
  --autonomous-gate "npm run check" \
  --autonomous-max-turns 20 \
  "Implement and verify the requested change"
```

| Flag / config | Default | Role |
|---|---|---|
| `--autonomous` | off | Enable policy |
| `--autonomous-gate <command>` | none (repeatable) | Shell gate run before finish is allowed |
| `--autonomous-gate-retries <n>` | `3` | Retries per failed gate |
| `--autonomous-gate-timeout-ms <n>` | `300000` (5m) | Per-gate timeout |
| `--autonomous-max-continuations <n>` | `3` | Host-injected follow-up limit |
| `--autonomous-max-turns <n>` | `12` | Assistant-turn limit |
| `--autonomous-max-tokens <n>` | `80000` | Host token budget (input + output + cache write; cache reads excluded) |
| `--autonomous-timeout-ms <n>` | `1800000` (30m) | Wall-clock limit |

`/autonomous status` reports enabled state plus `continuations/turns/tokens` usage against limits.

### Decision policy

When enabled and the last assistant message is not `error`/`aborted`:

1. If gates are configured, run them in the session cwd.
2. Gate **passed** → stop (no continuation).
3. Gate **failed** within retries → continue with a gate-failure prompt that includes bounded command output (max 6000 chars of gate output).
4. Gate **retry exhausted** or any limit hit → stop.
5. No gates configured → continue with reason `missing_terminal_evidence` until a limit stops the run.

Unchanged-workspace optimization: if a gate failed and the git worktree snapshot (status, diff, untracked hash) is unchanged, the host does not rerun the command. It records `not rerun: workspace unchanged since previous failed gate` and still consumes a retry attempt. Edit source, tests, or a blocker artifact before finishing again.

Default continuation prompt tells the model no human input is available, to keep working within budget, and not to end the session itself; configured gates and host limits decide completion.

### Goals vs autonomous mode

| Concern | Goal | Autonomous mode |
|---|---|---|
| Stores objective | Yes (`objective`, usage, status) | No |
| Completion signal | `await goal.complete()` | Gates pass and/or host limits |
| Continuation content | Goal-context custom message with objective | Fixed or gate-failure continuation prompt |
| Typical use | Explicit multi-turn objective the user wants pursued | Unattended verifier/evaluator loops |

They can run together: the goal holds the objective; autonomous mode decides whether another host-driven turn is injected when the assistant would otherwise stop.

## Compaction for continuity

Compaction frees model context so long-running work can continue. It is not a completion signal and does not stop goals, autonomous continuations, heartbeats, schedules, or existing child sessions.

### Triggers

| Trigger | Mechanism |
|---|---|
| Auto-compaction | `contextTokens > contextWindow - reserveTokens` |
| User | `/compact [instructions]` |
| Agent | `await compact.run(instructions=None)` |

Defaults (`settings.compaction`):

| Key | Default |
|---|---|
| `enabled` | `true` |
| `reserveTokens` | `16384` |
| `keepRecentTokens` | `20000` |

Settings live in `~/.prime/agent/settings.json` or `<project-dir>/.prime/agent/settings.json`.

### Kernel API

```python
await compact.status()
await compact.run()
await compact.run("keep the failing test names and the migration checklist")
```

| Call | Result |
|---|---|
| `status()` | `tokens`, `context_window`, `percent` (`None` right after compaction until the next model response), `scheduled` |
| `run(instructions?)` | `{"scheduled": True}` or `{"scheduled": False, "reason": ...}` |

Rules:

- Compaction never runs mid-cell. A scheduled compaction runs when the current turn ends; the harness then resumes with the summary plus recent messages.
- One `run` per turn is enough; later calls before the turn ends only update instructions.
- The IPython kernel persists through compaction: variables, imports, and helpers remain available.
- Default details track cumulative `readFiles` / `modifiedFiles` across repeated compactions.

Transcript shape: a `compaction` session entry with `summary`, `firstKeptEntryId`, `tokensBefore`, optional `customInstructions`, and optional extension `details`. The model sees system prompt + summary + messages from `firstKeptEntryId` onward.

## Retained subagents

RLM children admitted with `handle = await rlm("task", name="worker")` return at admission, not completion. Results arrive only through `agent_message` replies or files.

Daemon-backed children that finish successfully remain in the parent-scoped registry while the parent session is open:

```python
children = await rlm.list_subagents()
for child in children:
    print(child.session_name, child.status, child.active_session_id)

await agent_message.send(
    "Continue with the updated diff",
    receiver_role="child",
    receiver_name=children[0].session_name,
)

await rlm.delete_subagent(children[0])  # when context is no longer needed
```

Registry properties that matter for long runs:

- Survives compaction, kernel restart, and parent restore
- Successfully completed daemon-backed children rehydrate from parent artifacts
- Inline children stay inspectable in-process but have no active-session id
- Deletion cancels/closes the runtime and writes a durable tombstone; it does not erase transcript or artifacts on disk
- Parent can still project retained-child follow-up activity into parent child-update events

Cross-session messaging also works from the shell:

```bash
prime-agent send <agent> "Please verify the latest migration"
```

Delivery modes for `agent_message` / send: `auto` (steer if busy, deliver if idle), `steer`, `follow_up`. Receipts are `delivered` or `queued`.

## Typical long-running workflow

<Steps>
  <Step title="Start durable work in a project session">
    Launch `prime-agent` in the project directory so a daemon-backed worker owns the session. Optionally set a goal and/or enable autonomous mode before or during the task.
  </Step>
  <Step title="Set continuation policy">
    Use `/goal <objective>` when the user wants a durable objective with explicit completion via `goal.complete()`. Use `/autonomous on` or `--autonomous` with gates when the run should proceed without human turns until verifiers pass or limits hit.
  </Step>
  <Step title="Add recurring checks">
    Configure `/heartbeat every <interval> <instruction>` for user-visible rechecks, or have the agent create labeled `rlm_heartbeat` jobs for internal coordination. Use `prime-agent schedule add` for one-shot or cron prompts against a named agent.
  </Step>
  <Step title="Delegate and retain children">
    Spawn RLM children for independent work. Keep handles or recover them with `rlm.list_subagents()`, then follow up with `agent_message` instead of redoing context from scratch.
  </Step>
  <Step title="Manage context growth">
    Let auto-compaction run, or call `/compact` / `await compact.run(...)` at natural boundaries when context is high and work remains. Expect the kernel state to survive; re-read goal and child registry if needed.
  </Step>
  <Step title="Detach safely">
    Close or detach the client. Reattach with `prime-agent attach <agent>` or inspect with `prime-agent list` / `prime-agent agents`. Stop only when the worker should end.
  </Step>
</Steps>

## Configuration reference

### Compaction settings

```json
{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  }
}
```

Disable auto-compaction with `"enabled": false`. Manual `/compact` and the `compact` skill still work.

### Autonomous runtime limits

Defaults from `DEFAULT_AUTONOMOUS_LIMITS` / `DEFAULT_AUTONOMOUS_GATES`:

| Field | Default |
|---|---|
| `maxContinuations` | `3` |
| `maxTurns` | `12` |
| `maxTokens` | `80000` |
| `timeoutMs` | `1800000` |
| `gates.commands` | `[]` |
| `gates.maxRetries` | `3` |
| `gates.timeoutMs` | `300000` |

### Heartbeat defaults

| Constant | Value |
|---|---|
| `DEFAULT_HEARTBEAT_SCHEDULE` | `every 5m` |
| `DEFAULT_HEARTBEAT_DELIVERY_MODE` | `steer` |
| Minimum interval | 10 seconds |

## Failure modes and verification

| Symptom | Likely cause | Check |
|---|---|---|
| Goal keeps continuing after "done" | Model never called `await goal.complete()` | Inspect `goal_update` / `goalState.status`; require completion cell |
| Cannot create a second goal | Prior goal still `active`, `paused`, or `budget_limited` | `/goal status`; complete, resume, or clear |
| Autonomous run stops early | Limit hit or gate retry exhausted | `/autonomous status`; gate output in continuation prompts |
| Gate not rerun | Workspace snapshot unchanged after failure | Edit tracked files/tests/blocker artifacts, then retry |
| Heartbeat not firing after disconnect | Worker stopped or schedule cancelled | `prime-agent list`, `prime-agent status`, `/heartbeats` |
| Duplicate schedule delivery expected after crash | Tick was already claimed | Design assumes no uncertain replay; next future tick only |
| Child missing after compaction | Deleted or never daemon-backed | `await rlm.list_subagents()`; only completed daemon children rehydrate as addressable workers |
| Context full / early stop | Compaction disabled or not scheduled | `await compact.status()`; enable auto-compaction or `await compact.run(...)` |

Verification signals for healthy long-running sessions:

- Active goal: `goalState.status === "active"` and recurring `goal_context` messages until completion
- Heartbeat: custom `heartbeat_prompt` entries appear on schedule; user text history does not treat them as ordinary user prompts
- Compaction: `compaction` entries in the session file and `compactionSummary` at the head of model context
- Autonomous: status custom message shows rising continuation/turn/token counters while still enabled
- Retained children: `list_subagents()` returns stable names/ids after parent restore

## Related pages

<CardGroup cols={2}>
  <Card title="Run daemon-backed sessions" href="/daemon-sessions">
    Attach, detach, resume selectors, and worker recovery for background sessions.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython control tool, prompt-as-variable context, and `rlm(...)` admission.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    Child agents, `agent_message`, delivery modes, and multi-agent constraints.
  </Card>
  <Card title="Continual Harness" href="/continual-harness">
    Durable prompts, memories, skill and subagent specs that complement long-running policies.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, queueing, events, and session-scoped vs durable state.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog entries for `goal`, `compact`, `rlm-heartbeat`, and related skills.
  </Card>
  <Card title="Session configuration reference" href="/session-configuration">
    Config keys and defaults used by compaction, goals, and autonomous services.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Worker recovery, invalid resume selectors, and connection-mode probes.
  </Card>
</CardGroup>

---

## 14. Use ACP mode

> Run the Agent Client Protocol mode, map cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/14-use-acp-mode.md
- Generated: 2026-08-06T19:45:52.998Z

### Source Files

- `packages/coding-agent/docs/acp.md`
- `packages/coding-agent/src/modes/acp/acp-mode.ts`
- `packages/coding-agent/src/modes/acp/acp-events.ts`
- `packages/coding-agent/test/suite/acp-mode.test.ts`
- `packages/coding-agent/test/acp-cold-cli.test.ts`
- `packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts`

---
title: "Use ACP mode"
description: "Run the Agent Client Protocol mode, map cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations."
---

ACP mode starts with `prime-agent --mode acp` and runs Prime Agent as an [Agent Client Protocol](https://agentclientprotocol.com) agent: JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout. Implementation lives under `packages/coding-agent/src/modes/acp/`, driven in-process through `InProcessAgentConnection` and `@agentclientprotocol/sdk` rather than by translating RPC mode.

## When to use ACP

| Mode | Fit |
|---|---|
| **ACP** (`--mode acp`) | External client *drives* a session interactively: prompt, stream tool calls, cancel a turn (editors such as Zed/VS Code, evaluation harnesses). |
| JSON event stream | Batch runs that dump every event and exit with a status code. |
| RPC | Prime Agent’s richer, non-ACP command surface. |

Any standard ACP client can drive the agent without Prime Agent–specific knowledge. Prime-only capabilities travel in reverse-domain `_meta` and are ignored by vanilla clients.

## Launch

```bash
prime-agent --mode acp
```

Cold CLI coverage in tests also wires provider and isolation flags when spawning a real process:

```bash
prime-agent \
  --mode acp \
  --provider <provider-id> \
  --model <model-id> \
  --no-session \
  --offline \
  --daemon-socket /path/to/d.sock
```

| Flag / env | Role in cold ACP launch |
|---|---|
| `--mode acp` | Select ACP agent mode. |
| `--provider` / `--model` | Bind the session model (tests use a local intercept provider). |
| `--no-session` | Avoid durable session file coupling for the cold path. |
| `--offline` | Offline operation for isolated runs. |
| `--daemon-socket` | Socket path used by the cold-CLI harness. |
| `ENV_AGENT_DIR` | Agent data directory (e.g. `models.json` with provider `baseUrl`, `api`, `apiKey`, models). |

Provider credentials remain BYOK: point providers at your own endpoints and keys; ACP does not require a hosted Prime-specific model service.

### Transport rules

- One JSON-RPC message per line on **stdout**; requests are read from **stdin**.
- stdin stays open for the life of the connection; the agent exits when stdin closes (EOF).
- Diagnostics go to **stderr**. Never write non-protocol traffic to stdout.
- Startup non-interactive modes call `takeOverStdout()`, which redirects normal `process.stdout.write` to stderr. ACP frames must use the raw stdout escape hatch (`writeRawStdout`) so the protocol is not published on stderr.

```text
  Client (editor / harness)              prime-agent --mode acp
  ┌───────────────────────┐              ┌────────────────────────────┐
  │ JSON-RPC requests     │── stdin ──▶  │ ACP agent + InProcess      │
  │                       │              │ AgentConnection            │
  │ session/update +      │◀─ stdout ──│ NDJSON frames (raw stdout) │
  │ JSON-RPC responses    │              │ diagnostics → stderr       │
  └───────────────────────┘              └────────────────────────────┘
```

## Supported methods

| Method | Kind | Behavior |
|---|---|---|
| `initialize` | request | Returns protocol version, capabilities, and agent info (`agentInfo.name` is `"prime-agent"`). |
| `session/new` | request | Creates the session. **One session per connection.** |
| `session/prompt` | request | Runs one turn; resolves with a stop reason. |
| `session/cancel` | notification | Aborts the addressed session’s turn. |
| `session/close` | request | Releases the session and frees the connection for a new one. |

### Connection and turn constraints

- **One session per connection.** The underlying session is fixed at process startup. A second concurrent session would silently share conversation, working directory, and model, so a second `session/new` is **refused**. Start another process for another session.
- **`session/prompt` refuses a concurrent turn** while one is already running.
- **Working directory cannot change after startup.** A client-supplied `cwd` that does not match the agent’s real cwd is reported in `_meta` (not applied, not silently ignored).

### Minimal handshake

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}
```

```json
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/path/to/project","mcpServers":[]}}
```

```json
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"<id>","prompt":[{"type":"text","text":"Say hello"}]}}
```

Expected success path for a clean turn:

- `initialize` → `protocolVersion` matches `acp.PROTOCOL_VERSION`, `agentInfo.name === "prime-agent"`, and `_meta` includes the Prime Agent namespace.
- `session/new` → `sessionId` string.
- `session/prompt` → `{ "stopReason": "end_turn" }` with streamed `session/update` notifications containing `agent_message_chunk` text.

### Prompt content blocks

`session/prompt` blocks are split into text and images the agent accepts:

| Block `type` | Handling |
|---|---|
| `text` | Concatenated into the turn text. |
| `image` | Passed as image content when `data` and `mimeType` are strings (advertised in `initialize`; not dropped silently). |
| `resource` | Embedded `resource.text` becomes model context; optional `resource.uri` is prefixed. |
| `resource_link` | `uri` is appended as text. |

## Streamed updates

Session activity arrives as `session/update` notifications. Mapping is pure (`acpUpdatesForSessionEvent`) so one Prime Agent event can fan out to zero or more ACP updates.

| Prime Agent activity | ACP `sessionUpdate` |
|---|---|
| Assistant text (`text_delta`) | `agent_message_chunk` |
| Reasoning (`thinking_delta`) | `agent_thought_chunk` |
| Tool starts | `tool_call` with `status: "in_progress"` |
| Tool finishes | `tool_call_update` with `completed` or `failed` |
| Shell output | Synthetic `tool_call` plus incremental `tool_call_update` |

### Tool kinds and titles

`acpToolKind` maps tool names to ACP kinds:

| Tool name | ACP `kind` |
|---|---|
| `ipython`, `bash` | `execute` |
| `read` | `read` |
| `edit`, `write` | `edit` |
| other | `other` |

Supported kind union: `read` \| `edit` \| `delete` \| `move` \| `search` \| `execute` \| `think` \| `fetch` \| `other`.

Statuses: `pending` \| `in_progress` \| `completed` \| `failed`.

IPython is the model-facing tool (`IPYTHON_TOOL_NAME = "ipython"`):

- Start: `tool_call` titled `"IPython cell"`, `kind: "execute"`, `rawInput: { code: <cell source> }` when `args.code` is a string.
- End: `tool_call_update` with optional text content and optional `_meta` rich IPython fields.

Bash runs outside the normal tool-call lifecycle. Mapping tracks `activeBashRunId` from `bash_start` so incremental `bash_output` attaches to a synthetic tool call id derived from the run id (not an orphan fallback id).

### Rich IPython `_meta` (tool end)

When the IPython tool returns `details.attachments` / `details.diffs`, the update may include:

```json
{
  "sessionUpdate": "tool_call_update",
  "toolCallId": "...",
  "status": "completed",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "ipython": {
        "attachments": [{ "mimeType": "...", "path": "...", "bytes": 1234 }],
        "diffCount": 1
      }
    }
  }
}
```

Attachment payloads are **not** inlined in `_meta` (decoded base64 length only as `bytes`); images already ride as ACP image content blocks.

## Prime Agent `_meta` extensions

Namespace: reverse-domain key **`ai.primeintellect.prime-agent`** (`PRIME_AGENT_META_NAMESPACE`). Nothing non-standard is placed on ACP object roots (reserved for future protocol fields).

Capabilities without native ACP fields—subagents, autonomous quality gates, goals, heartbeats, continual-harness refinement, compaction, rich IPython—use this envelope. Example subagent tree update:

```json
{
  "sessionUpdate": "session_info_update",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "subagents": [
        { "id": "sub-1", "sessionName": "reviewer", "status": "running" }
      ]
    }
  }
}
```

When autonomous mode is enabled, status may also be mirrored under `_meta` with fields such as `enabled`, `continuationsUsed`, `turnsUsed`, `tokensUsed`, `gateAttempt`, and `gateFailure`.

## Stop reasons

`session/prompt` resolves with one of:

| `stopReason` | Meaning |
|---|---|
| `end_turn` | Turn finished normally. |
| `cancelled` | `session/cancel` aborted the turn. |
| `max_tokens` | Autonomous token budget exhausted. |
| `max_turn_requests` | Autonomous turn, continuation, or wall-clock limit stopped the run. |

Autonomous quality gates run **inside** a single prompt turn. A failing gate is a **continuation**, not a stop reason; the turn resolves only when the gate loop settles. Gate attempts remain visible in `_meta` during that loop.

### Provider failure must not look like success

A real cold CLI process talking to a provider that returns **401** must **not** answer `session/prompt` with `{ "stopReason": "end_turn" }` and zero `session/update` notifications (that pattern reads as a successful empty turn). The response must either carry a JSON-RPC `error` or a non-`end_turn` stop reason.

## Working directory (`cwd`) constraints

Client `session/new` supplies `cwd`. The agent does not chdir for a mismatched path; comparison is canonical:

1. `resolve` the path.
2. `realpathSync` when possible; on missing/inaccessible paths, fall back to the lexical resolved path.
3. On Windows, normalize the drive letter to lowercase.
4. If canonical strings differ, compare filesystem identity (`statSync` `dev` + `ino` as bigints). If either side has `dev === 0n` or `ino === 0n`, identity is untrusted and comparison fails (Windows path-based stat can report `dev` 0 with a real `ino`; inode alone is volume-local).

| Client `cwd` situation | `session/new` result | `_meta` under Prime namespace |
|---|---|---|
| Symlink or alias that resolves to the agent process cwd | Session created | No `cwd` mismatch field |
| Case-only spelling of the same directory (same `dev`/`ino`) | Session created | No mismatch field |
| Path that cannot be canonicalized to the real cwd (e.g. missing path) | Session still created | `cwd: { requested, actual }` where `actual` is `process.cwd()` |

```text
session/new { cwd }
        │
        ▼
  sameCwd(requested, process.cwd()) ?
     │ yes                    │ no
     ▼                        ▼
  sessionId only      sessionId + _meta
                      ai.primeintellect.prime-agent.cwd
                      = { requested, actual }
```

Editors should pass a path that resolves to the process working directory, or tolerate `cwd` mismatch metadata without assuming the agent switched directories.

## Turn boundary and compaction

Turn failure detection uses a **TurnBoundary** (message object identities + content keys), not a pre-turn message count. Auto-compaction can rebuild `state.messages` mid-turn so this turn’s messages may sit at lower indices than a pre-prompt count. Content keys also cover transports that re-parse JSON (fresh object identity) and compaction paths that re-materialize kept messages.

## Clean shutdown

Prefer **closing stdin** so the agent sees EOF and unwinds children (daemon supervisor, Python kernel) over hard-killing the process. Cold-CLI tests end stdin, wait for exit, then escalate `SIGTERM` / `SIGKILL` only if the process does not exit.

## Integration checklist

<Steps>
  <Step title="Start the agent">
    Run `prime-agent --mode acp` with cwd set to the project root the editor intends. Wire provider/model via settings or CLI as needed.
  </Step>
  <Step title="Handshake">
    Call `initialize`, then a single `session/new` with `{ cwd, mcpServers: [] }`. Assert `agentInfo.name === "prime-agent"` and inspect optional `_meta` / `cwd` mismatch.
  </Step>
  <Step title="Drive turns">
    Call `session/prompt` with text and optional image/resource blocks. Subscribe to `session/update` for chunks and tool calls. Cancel with `session/cancel` if needed; close with `session/close` before a new session on the same connection.
  </Step>
  <Step title="Verify">
    Successful text turn: `stopReason: "end_turn"` and non-empty `agent_message_chunk` stream. Failed provider: not silent `end_turn` with zero updates. Protocol frames only on stdout; logs on stderr.
  </Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause | What to check |
|---|---|---|
| No frames on stdout / protocol on stderr | Stdout taken over without raw write path | Frames must go through the raw stdout sink; diagnostics only on stderr. |
| Second `session/new` fails | One-session-per-connection limit | Start a new process; or `session/close` then open a new session on the same connection if free. |
| Concurrent prompt rejected | Turn already in progress | Wait for the prior `session/prompt` result or send `session/cancel`. |
| `_meta.cwd` present after `session/new` | Client path ≠ agent process cwd after canonical/`dev`+`ino` compare | Align editor cwd with process cwd; treat mismatch as advisory, not a chdir. |
| Empty successful-looking turn | Historical provider-failure bug path | Require non-`end_turn` or JSON-RPC `error` when the provider rejects (e.g. 401). |
| Orphan shell chunks | Bash mapping without `runId` correlation | Mapping must retain last `bash_start` run id for subsequent output. |
| Process hangs after client exit | stdin left open | Close stdin for clean EOF unwind. |

## Related pages

<CardGroup cols={2}>
  <Card title="ACP protocol reference" href="/acp-reference">
    Entrypoints, event types, meta fields, stop reasons, and protocol-facing constraints.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    In-process, daemon, and snapshot connection paths used under ACP.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, runtime events, and session-scoped state.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Autonomous mode, gates, heartbeats, and multi-turn progression.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, and connection-mode probes.
  </Card>
  <Card title="Overview" href="/overview">
    CLI, SDK, and mode entry points across Prime Agent.
  </Card>
</CardGroup>

---

## 15. Extensions and custom tools

> Register extensions, custom tools, plan-mode and subagent extension samples, allowlists, and dynamic resource patterns.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/15-extensions-and-custom-tools.md
- Generated: 2026-08-06T19:46:02.135Z

### Source Files

- `packages/coding-agent/examples/extensions/README.md`
- `packages/coding-agent/examples/sdk/06-extensions.ts`
- `packages/coding-agent/examples/sdk/05-tools.ts`
- `packages/coding-agent/examples/extensions/plan-mode/README.md`
- `packages/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts`
- `packages/coding-agent/examples/extensions/dynamic-resources/SKILL.md`

---
title: "Extensions and custom tools"
description: "Register extensions, custom tools, plan-mode and subagent extension samples, allowlists, and dynamic resource patterns."
---

Extensions are TypeScript modules that receive an `ExtensionAPI` (`pi`) and can intercept agent events, register tools and commands, and extend UI and resources. Prime Agent loads them from the CLI (`--extension` or an auto-discovery directory), from project/agent discovery paths used by the SDK resource loader, or from inline `extensionFactories` on `DefaultResourceLoader`.

## Load an extension

### CLI

From the repository root:

```bash
./prime-agent.sh --extension packages/coding-agent/examples/extensions/permission-gate.ts
```

Copy an example into the agent extensions directory for auto-discovery:

```bash
cp permission-gate.ts ~/.prime/agent/extensions/
```

Example extensions live under `packages/coding-agent/examples/extensions/`.

### SDK discovery paths

`createAgentSession` with `DefaultResourceLoader` discovers extension files from:

| Source | Path / key |
|--------|------------|
| Agent dir | `~/.pi/agent/extensions/` |
| Project cwd | `<cwd>/.pi/extensions/` |
| Settings | `settings.json` `"extensions"` array |
| Loader option | `additionalExtensionPaths: string[]` |
| Loader option | `extensionFactories: ((pi) => void)[]` |

<Note>
CLI example docs use `~/.prime/agent/extensions/` for copy-based auto-discovery. SDK comments document `~/.pi/agent/extensions/` and `<cwd>/.pi/extensions/`. Use the path your entrypoint documents for that surface.
</Note>

### Loader and session bind

```ts
import {
  createAgentSession,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
} from "@earendil-works/pi-coding-agent";

const resourceLoader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
  extensionFactories: [
    (pi) => {
      pi.on("agent_start", () => {
        console.log("[Inline Extension] Agent starting");
      });
    },
  ],
});
await resourceLoader.reload();

const { session } = await createAgentSession({
  resourceLoader,
  sessionManager: SessionManager.inMemory(),
});
```

Regression coverage also calls `await session.bindExtensions({})` after `createAgentSession` so factory-registered tools attach to the session.

## Extension module contract

An extension file exports a default function:

```ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // hooks, tools, commands
}
```

Documented hook and registration surfaces from the SDK sample:

| Surface | Role |
|---------|------|
| `pi.on("agent_start", …)` | Agent start |
| `pi.on("tool_call", …)` | Observe or block a tool call |
| `pi.on("agent_end", …)` | End of agent turn; receives `event.messages` |
| `pi.on("session_start", …)` | Session start (used for dynamic tool registration) |
| `pi.registerTool({ … })` | Custom tool |
| `pi.registerCommand(name, { description, handler })` | Slash/command handler |

`tool_call` may return `{ block: true, reason: "..." }` to block execution, or `undefined` to allow.

## Register a custom tool

Custom tools are not passed as ad-hoc objects on `createAgentSession`. Register them through the extensions system with `pi.registerTool()`.

```ts
pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "Does something useful",
  parameters: Type.Object({
    input: Type.String(),
  }),
  execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => ({
    content: [{ type: "text", text: `Processed: ${params.input}` }],
    details: {},
  }),
});
```

### Tool definition fields (sample)

| Field | Purpose |
|-------|---------|
| `name` | Tool id used by allowlists and activation |
| `label` | Display label |
| `description` | Tool description |
| `parameters` | TypeBox schema (`Type.Object`, …) |
| `promptSnippet` | Optional prompt text (used in dynamic/regression samples) |
| `execute` | Async runner; returns `{ content, details }` |

`content` items use `{ type: "text", text: string }`.

### Register a command

```ts
pi.registerCommand("mycommand", {
  description: "Do something",
  handler: async (args, ctx) => {
    ctx.ui.notify(`Command executed with: ${args}`);
  },
});
```

## Tool allowlists

`createAgentSession({ tools: string[] })` is an allowlist over **built-in, extension, and custom** tool names. Names match whatever tools are available after extensions bind.

```ts
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";

await createAgentSession({
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(),
});
```

With a custom project root, pass the same `cwd` into the session and the in-memory session manager so built-ins resolve against that directory:

```ts
const customCwd = "/path/to/project";
await createAgentSession({
  cwd: customCwd,
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(customCwd),
});
```

### Verified allowlist behavior

From regression `#2835` (`tools` + extension `session_start` registration of `dynamic_tool`):

| `tools` value | Active tools | System prompt notes |
|---------------|--------------|---------------------|
| `["ipython", "dynamic_tool"]` | Only `ipython` and `dynamic_tool` (`getAllTools` / `getActiveToolNames`) | Non-listed tools (e.g. `bash`, `edit`) do not appear as tool bullets |
| `[]` | None | No `Available tools:` section; dynamic tool name absent |

<Warning>
An empty allowlist disables **all** tools, including extension tools registered on `session_start`. List both built-in and extension tool names you need.
</Warning>

## Dynamic tools and resources

### Register after startup

`dynamic-tools.ts` (catalog) and the regression factory register tools on `session_start`:

```ts
pi.on("session_start", () => {
  pi.registerTool({
    name: "dynamic_tool",
    label: "Dynamic Tool",
    description: "Tool registered from session_start",
    promptSnippet: "Run dynamic test behavior",
    parameters: Type.Object({}),
    execute: async () => ({
      content: [{ type: "text", text: "ok" }],
      details: {},
    }),
  });
});
```

Catalog notes for `dynamic-tools.ts`: register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines.

### `resources_discover`

`dynamic-resources/` loads skills, prompts, and themes via `resources_discover`. Example skill frontmatter shipped with that extension:

```yaml
---
name: dynamic-resources
description: Example skill loaded from resources_discover
---
```

Body: skill content is provided by the dynamic-resources extension.

## Plan-mode sample

`plan-mode/` is a Claude Code-style read-only exploration mode with `/plan` and step tracking.

### Controls

| Input | Effect |
|-------|--------|
| `/plan` | Toggle plan mode |
| `/todos` | Show plan progress |
| `Ctrl+Alt+P` | Toggle plan mode |
| `--plan` | Enable plan mode at launch |

### Workflow

<Steps>
  <Step title="Enable plan mode">
    Use `/plan` or `--plan`.
  </Step>
  <Step title="Explore and draft a plan">
    Ask the agent to analyze code and emit a numbered plan under a `Plan:` header:

    ```
    Plan:
    1. First step description
    2. Second step description
    3. Third step description
    ```
  </Step>
  <Step title="Execute">
    Choose "Execute the plan" when prompted. Full tool access is restored. The agent marks steps with `[DONE:n]`; a progress widget shows completion. State persists across session resume.
  </Step>
</Steps>

### Modes

| Mode | Tools | Behavior |
|------|-------|----------|
| Plan (read-only) | `bash` and `questionnaire` only; bash filtered by allowlist | Plan without edits |
| Execution | Full tool access | Ordered steps, `[DONE:n]`, progress widget |

### Bash allowlist (plan mode)

**Allowed (read-only):** `cat`, `head`, `tail`, `less`, `more`, `grep`, `find`, `rg`, `fd`, `ls`, `pwd`, `tree`, `git status`, `git log`, `git diff`, `git branch`, `npm list`, `npm outdated`, `yarn info`, `uname`, `whoami`, `date`, `uptime`.

**Blocked:** `rm`, `mv`, `cp`, `mkdir`, `touch`, `git add`, `git commit`, `git push`, `npm install`, `yarn add`, `pip install`, `sudo`, `kill`, `reboot`, `vim`, `nano`, `code`.

## Subagent extension sample

Catalog entry `subagent/`: delegate tasks to specialized subagents with isolated context windows. Wire multi-agent messaging and orchestration through the dedicated subagents docs rather than redefining protocol here.

## Extension catalog (examples)

All paths relative to `packages/coding-agent/examples/extensions/`.

### Lifecycle and safety

| Extension | Description |
|-----------|-------------|
| `permission-gate.ts` | Confirm before dangerous bash (`rm -rf`, `sudo`, …) |
| `protected-paths.ts` | Block writes to `.env`, `.git/`, `node_modules/` |
| `confirm-destructive.ts` | Confirm destructive session actions (clear, switch, fork) |
| `dirty-repo-guard.ts` | Block session changes with uncommitted git changes |
| `sandbox/` | OS-level sandboxing via `@anthropic-ai/sandbox-runtime` with per-project config |

### Custom tools

| Extension | Description |
|-----------|-------------|
| `todo.ts` | Todo tool + `/todos`, custom rendering, state persistence |
| `hello.ts` | Minimal custom tool |
| `question.ts` | `ctx.ui.select()` questions |
| `questionnaire.ts` | Multi-question input with tab bar |
| `tool-override.ts` | Override tools (logging/access control) |
| `dynamic-tools.ts` | Tools on `session_start` / command; prompt snippets and guidelines |
| `structured-output.ts` | Final structured-output tool with `terminate: true` |
| `built-in-tool-renderer.ts` | Compact rendering for bash/edit, original behavior |
| `minimal-mode.ts` | Minimal built-in tool rendering (calls only when collapsed) |
| `truncated-tool.ts` | ripgrep wrapper; truncate at 50KB / 2000 lines |
| `ssh.ts` | Remote bash/edit over SSH via pluggable operations |
| `subagent/` | Specialized subagents, isolated context windows |

### Commands and UI (selected)

| Extension | Description |
|-----------|-------------|
| `preset.ts` | Presets for model, thinking, tools, instructions (`--preset`, `/preset`) |
| `plan-mode/` | Read-only plan mode (`/plan`, step tracking) |
| `tools.ts` | Interactive `/tools` enable/disable with session persistence |
| `handoff.ts` | `/handoff <goal>` focused session transfer |
| `send-user-message.ts` | `pi.sendUserMessage()` |
| `rpc-demo.ts` | RPC-supported extension UI methods (pair with `examples/rpc-extension-ui.ts`) |
| `reload-runtime.ts` | `/reload-runtime` and `reload_runtime` tool |
| `shutdown-command.ts` | `/quit` via `ctx.shutdown()` |

Additional UI samples cover status/header/footer widgets, overlays, autocomplete, modal/rainbow editors, desktop notifications (OSC 777), and games (`snake.ts`, `tic-tac-toe.ts` with `executionMode: "sequential"`, `doom-overlay/`).

### System prompt, compaction, resources, messaging

| Extension | Description |
|-----------|-------------|
| `pirate.ts` | `systemPromptAppend` |
| `claude-rules.ts` | Scan `.claude/rules/` into system prompt |
| `custom-compaction.ts` | Custom full-conversation compaction |
| `trigger-compact.ts` | Compact above 100k tokens + `/trigger-compact` |
| `dynamic-resources/` | Skills, prompts, themes via `resources_discover` |
| `message-renderer.ts` | `registerMessageRenderer` |
| `event-bus.ts` | Inter-extension bus via `pi.events` |
| `session-name.ts` | `setSessionName` for session selector |
| `bookmark.ts` | `setLabel` for `/tree` navigation |

### Custom providers (examples)

| Extension | Description |
|-----------|-------------|
| `custom-provider-anthropic/` | Custom Anthropic provider with OAuth and custom streaming |
| `custom-provider-gitlab-duo/` | GitLab Duo via `@earendil-works/pi-ai` streaming API through a proxy |

Providers remain BYOK/BYOC: register your own provider implementation; no single hosted model is required.

## Architecture (registration path)

```mermaid
flowchart TB
  subgraph sources [Extension sources]
    CLI["CLI --extension path"]
    AD["Auto-discovery dirs"]
    SET["settings.json extensions"]
    PATHS["additionalExtensionPaths"]
    FACT["extensionFactories"]
  end

  subgraph loader [DefaultResourceLoader]
    REL["reload()"]
  end

  subgraph session [Agent session]
    CAS["createAgentSession tools allowlist"]
    BIND["bindExtensions"]
    API["ExtensionAPI pi"]
    TOOLS["registerTool / getAllTools"]
    CMDS["registerCommand"]
    HOOKS["on agent_start tool_call session_start agent_end"]
  end

  CLI --> REL
  AD --> REL
  SET --> REL
  PATHS --> REL
  FACT --> REL
  REL --> CAS
  CAS --> BIND
  BIND --> API
  API --> TOOLS
  API --> CMDS
  API --> HOOKS
```

## Verification checklist

| Check | Signal |
|-------|--------|
| CLI load | Extension path accepted via `--extension` or present under auto-discovery dir |
| SDK load | `resourceLoader.reload()` then session runs without loader errors |
| Custom tool visible | Tool name in `session.getAllTools()` / `getActiveToolNames()` after `bindExtensions` |
| Allowlist filter | Only listed names active; `tools: []` → empty tools and no `Available tools:` prompt section |
| Plan mode | `/plan` or `--plan`; tools limited to bash + questionnaire; bash allowlist enforced |
| Dynamic skill | Skill frontmatter `name: dynamic-resources` available when dynamic-resources extension loads via `resources_discover` |

## Related pages

<CardGroup>
  <Card title="Skills, tools, and extensions (SDK)" href="/sdk-skills-tools-extensions">
    SDK recipes for skills, tools, extensions, context files, and subagent extension wiring.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Minimal bootstrap, custom prompt/model wiring, first-run expectations.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Skills as packages, SKILL.md constraints, and load-scope precedence.
  </Card>
  <Card title="Create and install skills" href="/create-skills">
    Author skill packages and verify load paths.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    Child agents, agent-message surface, and multi-agent constraints.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Session config keys, defaults, and reload behavior.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Shipped skill catalog and entry modules.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration and API key / OAuth wiring for custom providers.
  </Card>
</CardGroup>

---

## 16. Session configuration reference

> Agent session config keys, defaults, reload behavior, in-memory settings, and config surfaces used by runtime services.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/16-session-configuration-reference.md
- Generated: 2026-08-06T19:48:52.680Z

### Source Files

- `packages/coding-agent/src/core/agent-session-config.ts`
- `packages/coding-agent/test/agent-session-config.test.ts`
- `packages/coding-agent/examples/sdk/10-settings.ts`
- `packages/coding-agent/src/core/agent-session.ts`
- `packages/coding-agent/test/suite/regressions/3616-settings-inmemory-reload.test.ts`
- `packages/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts`

---
title: "Session configuration reference"
description: "Agent session config keys, defaults, reload behavior, in-memory settings, and config surfaces used by runtime services."
---

Prime Agent splits session configuration across three surfaces that runtime services combine at create time and again on reload: durable `settings.json` via `SettingsManager`, per-session bootstrap via `AgentSessionRuntimeConfig`, and construction-time `createAgentSession` / `AgentSessionCreationOptions`. Project settings deep-merge over global settings; CLI and SDK options override resolved defaults for a single session without always writing them back to disk.

## Config surfaces

| Surface | Owner type / API | Persistence | Typical producers |
|---------|------------------|-------------|-------------------|
| Durable settings | `Settings` + `SettingsManager` | Global and project `settings.json` (or in-memory storage) | CLI `/settings`, package manager, SDK `SettingsManager.create` / `inMemory` |
| Runtime session config | `AgentSessionRuntimeConfig` | Session bootstrap only; merged with `mergeAgentSessionRuntimeConfig` | CLI args → `runtimeConfigFromArgs`, daemon worker create requests |
| Session construction | `CreateAgentSessionOptions` / `AgentSessionCreationOptions` | Session instance fields | `createAgentSession`, `createAgentSessionFromServices`, RLM subagent spawn |

```mermaid
flowchart TB
  subgraph durable["Durable settings"]
    GS["~/.prime/agent/settings.json"]
    PS[".prime/agent/settings.json"]
    SM["SettingsManager\ndeepMerge global + project"]
    GS --> SM
    PS --> SM
  end

  subgraph bootstrap["Session bootstrap"]
    CLI["CLI args / daemon create"]
    RC["AgentSessionRuntimeConfig"]
    CLI --> RC
  end

  subgraph services["AgentSessionServices"]
    AS["authStorage + modelRegistry"]
    RL["DefaultResourceLoader"]
    MCP["McpManager"]
    SM --> RL
    SM --> MCP
    AS --> MCP
  end

  subgraph session["AgentSession"]
    CA["createAgentSession /\ncreateAgentSessionFromServices"]
    AS2["AgentSession runtime"]
    CA --> AS2
  end

  SM --> CA
  RC --> CA
  RL --> CA
  MCP --> CA
  AS --> CA
  AS2 -->|"reload()"| SM
  AS2 -->|"reload()"| RL
```

## Settings files and merge rules

| Scope | Path | Role |
|-------|------|------|
| Global | `~/.prime/agent/settings.json` (`getAgentDir()` + `settings.json`) | Defaults for all projects |
| Project | `.prime/agent/settings.json` (`CONFIG_DIR_NAME` under cwd) | Overrides for the current project |

Merge behavior (`deepMergeSettings`):

- Project values win over global for the same key.
- Nested objects (for example `compaction`, `retry`, `terminal`) merge field-by-field.
- Arrays and primitives are replaced, not concatenated.
- `undefined` override values are skipped.

Global-only keys read from `globalSettings` (not the merged project overlay):

- `rlmMaxDepth`
- `idleEvictionMinutes` (daemon idle eviction policy)

`sessionDir` precedence when multiple sources set a directory:

1. CLI `--session-dir`
2. `PRIME_AGENT_SESSION_DIR`
3. Legacy `PRIME_AGENT_CODING_AGENT_SESSION_DIR`
4. `sessionDir` in settings

## `Settings` keys and defaults

Defaults below are the values getters apply when a key is omitted. Unset `defaultThinkingLevel` falls through to runtime `DEFAULT_THINKING_LEVEL` (`"medium"`), not a baked-in settings default of `"xhigh"`.

### Model and transport

| Key | Type | Default when unset | Notes |
|-----|------|--------------------|-------|
| `defaultProvider` | string | — | Saved default provider |
| `defaultModel` | string | — | Saved default model id |
| `recentModels` | string[] | `[]` | `"provider/id"`, most recent first, capped at 20 |
| `defaultThinkingLevel` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "max"` | runtime `"medium"` | Session uses settings value if set |
| `defaultServiceTier` | service tier | `"default"` | Fast/priority tiers when supported |
| `thinkingBudgets` | object | — | Optional per-level token budgets |
| `enabledModels` | string[] | — | Model cycle patterns (same shape as `--models`) |
| `transport` | `"sse" \| "websocket" \| "auto"` | `"auto"` | Legacy `websockets` boolean migrates into this |
| `hideThinkingBlock` | boolean | `false` | UI hide for thinking blocks |

### Compaction, refine, branch summary, retry

| Key | Type | Default when unset |
|-----|------|--------------------|
| `compaction.enabled` | boolean | `true` |
| `compaction.reserveTokens` | number | `16384` |
| `compaction.keepRecentTokens` | number | `20000` |
| `compaction.agentCallable` | boolean | `true` (exposes compact skill) |
| `autoRefine.enabled` | boolean | `true` |
| `autoRefine.turnInterval` | number | `25` (min 1) |
| `autoRefine.compact` | boolean | `true` |
| `autoRefine.cooldownMs` | number | `1200000` (20 minutes) |
| `branchSummary.reserveTokens` | number | `16384` |
| `branchSummary.skipPrompt` | boolean | `false` |
| `retry.enabled` | boolean | `true` |
| `retry.maxRetries` | number | `3` |
| `retry.baseDelayMs` | number | `2000` |
| `retry.provider.timeoutMs` | number | SDK default |
| `retry.provider.maxRetries` | number | SDK default |
| `retry.provider.maxRetryDelayMs` | number | `60000` |

Legacy `retry.maxDelayMs` migrates to `retry.provider.maxRetryDelayMs`.

### Message delivery and daemon

| Key | Type | Default when unset | Notes |
|-----|------|--------------------|-------|
| `steeringMode` | `"all" \| "one-at-a-time"` | `"one-at-a-time"` | Legacy `queueMode` migrates here |
| `followUpMode` | `"all" \| "one-at-a-time"` | `"one-at-a-time"` | |
| `idleEvictionMinutes` | number \| `"off"` | `90` | Global only; `"none"` treated as `"off"` |
| `rlmMaxDepth` | number | env/runtime default | Global only |
| `sessionDir` | string | — | `~` expanded; see precedence above |

### Terminal, images, UI

| Key | Type | Default when unset | Notes |
|-----|------|--------------------|-------|
| `theme` | string | theme system fallback | Often resolved as `"dark"` / `"prime"` in UI |
| `quietStartup` | boolean | `false` | |
| `treeFilterMode` | enum | `"user-only"` | `default`, `no-tools`, `user-only`, `labeled-only`, `all` |
| `editorPaddingX` | number | `0` | Clamped 0–3 on set |
| `autocompleteMaxVisible` | number | `5` | Clamped 3–20 on set |
| `showHardwareCursor` | boolean | `false` unless `PI_HARDWARE_CURSOR=1` | |
| `terminal.showImages` | boolean | `true` | |
| `terminal.clearOnShrink` | boolean | `false` | |
| `terminal.showTerminalProgress` | boolean | `false` | |
| `terminal.fullscreen` | boolean | `true` | Overridden by `PI_FULLSCREEN=0/1` |
| `terminal.fullscreenMouse` | boolean | `true` | |
| `images.autoResize` | boolean | `true` | |
| `images.blockImages` | boolean | `false` | Blocks images to providers |
| `markdown.codeBlockIndent` | string | `"  "` | |
| `warnings.anthropicExtraUsage` | boolean | `true` (when read as warning flag) | |

### Shell, packages, resources, MCP

| Key | Type | Default when unset | Notes |
|-----|------|--------------------|-------|
| `shellPath` | string | — | Custom shell (for example Cygwin) |
| `shellCommandPrefix` | string | — | Prepended to every bash command |
| `npmCommand` | string[] | — | Argv for npm package operations |
| `packages` | `PackageSource[]` | `[]` | String or filtered object form |
| `extensions` | string[] | `[]` | Local paths; supports globs / `+` / `-` / `!` |
| `skills` | string[] | `[]` | Same path pattern rules |
| `prompts` | string[] | `[]` | Prompt templates |
| `themes` | string[] | `[]` | Theme paths |
| `enableSkillCommands` | boolean | `true` | `/skill:name` registration |
| `enableBuiltinSkills` | boolean | `true` | Built-in skill load |
| `bundledSkills.websearch` | boolean | `true` | Built-in websearch skill |
| `mcpServers` | record | — | User-declared HTTP/stdio MCP servers |
| `agentTraces.enabled` | boolean | `false` | |
| `onboardingShown` / `onboardingCompleted` | boolean | — | Onboarding completion flags |

Paths in global settings resolve relative to `~/.prime/agent`. Paths in project settings resolve relative to `.prime/agent`. Absolute paths and `~` are supported.

### Example `settings.json`

```json
{
  "defaultProvider": "anthropic",
  "defaultModel": "claude-sonnet-4-20250514",
  "defaultThinkingLevel": "high",
  "transport": "auto",
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000,
    "agentCallable": true
  },
  "autoRefine": {
    "enabled": true,
    "turnInterval": 25,
    "cooldownMs": 1200000
  },
  "retry": {
    "enabled": true,
    "maxRetries": 3,
    "baseDelayMs": 2000,
    "provider": { "maxRetryDelayMs": 60000 }
  },
  "enabledModels": ["claude-*", "gpt-4o"],
  "packages": ["pi-skills"]
}
```

## `SettingsManager` API

<ParamField body="SettingsManager.create" type="(cwd, agentDir?) => SettingsManager">
Loads global and project settings from files under `agentDir` and `cwd`. Default `agentDir` is `getAgentDir()` (`~/.prime/agent`).
</ParamField>

<ParamField body="SettingsManager.inMemory" type="(settings?: Partial<Settings>) => SettingsManager">
No file I/O. Seeds global storage with the provided settings after migration. Used by tests and headless SDK runs.
</ParamField>

<ParamField body="SettingsManager.fromStorage" type="(storage: SettingsStorage) => SettingsManager">
Custom backend implementing lock-scoped read/write of JSON text for `"global"` and `"project"`.
</ParamField>

| Method | Behavior |
|--------|----------|
| `getGlobalSettings()` / `getProjectSettings()` | Structured clones of scoped objects |
| `applyOverrides(partial)` | Deep-merges into the **in-memory merged** view only; does not mark fields modified or write storage |
| `reload()` | Awaits write queue, reloads both scopes from storage, clears modified-field tracking, recomputes merge |
| `flush()` | Awaits queued persistence writes |
| `drainErrors(scope?)` | Returns and clears recorded load/save errors (`global` / `project`) |
| Typed getters/setters | Most setters update **global** settings, mark modified keys (including nested keys), and enqueue a locked merge-write |

Persistence rules:

- Setters queue writes; call `flush()` at durability boundaries.
- Writes only include fields marked modified in that session; concurrent external edits to other keys are preserved via lock + merge.
- If a scope failed to parse on load, subsequent saves for that scope are blocked and recorded as errors instead of overwriting a corrupt file.
- Project resource path setters (`setProjectPackages`, `setProjectExtensionPaths`, …) write the project file; global counterparts write the agent-dir file.

### In-memory settings and reload

`SettingsManager.inMemory` stores the initial snapshot in memory-backed global storage. Direct `reload()` and `DefaultResourceLoader.reload()` re-read that storage, so the initial keys survive reload. Setters (for example `setTheme`) update storage; after `flush()` + `reload()`, both the new and original keys remain. `applyOverrides` alone does **not** write storage and is dropped on reload.

```ts
import { createAgentSession, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";

const settingsManager = SettingsManager.create(process.cwd());
settingsManager.applyOverrides({
  compaction: { enabled: false },
  retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
});

await createAgentSession({
  settingsManager,
  sessionManager: SessionManager.inMemory(),
});

settingsManager.setDefaultThinkingLevel("low");
await settingsManager.flush();

for (const { scope, error } of settingsManager.drainErrors()) {
  console.warn(`Warning (${scope} settings): ${error.message}`);
}

// Tests without disk:
const inMemorySettings = SettingsManager.inMemory({
  compaction: { enabled: false },
  retry: { enabled: false },
});
```

## `AgentSessionRuntimeConfig`

Runtime bootstrap for a session (CLI, daemon create, worker rehydrate). Merge with `mergeAgentSessionRuntimeConfig(base, override)`: override wins per field when defined; arrays are cloned; `extensionFlagValues` shallow-merges; `autonomous` deep-merges including gate `commands`.

| Field | Type | Role |
|-------|------|------|
| `cwd` | string | Effective working directory |
| `agentDir` | string | Global agent config directory |
| `sessionDir` | string | Session storage directory override |
| `provider` / `model` / `apiKey` | string | Model selection / one-shot API key |
| `systemPrompt` | string | Replace system prompt |
| `appendSystemPrompt` | string[] | Append prompt segments |
| `thinking` | `ThinkingLevel` | Initial thinking level |
| `models` | string[] | Scoped cycle models (else settings `enabledModels`) |
| `tools` | string[] | Tool allowlist |
| `noTools` / `noBuiltinTools` | boolean | Suppress all / built-in tools |
| `extensions` / `skills` / `promptTemplates` / `themes` | string[] | Extra resource paths (CLI paths resolved absolute at parse time) |
| `noExtensions` / `noSkills` / `noPromptTemplates` / `noThemes` / `noContextFiles` | boolean | Disable discovery classes |
| `autonomous` | `AgentAutonomousConfig` | Host autonomous continuation policy |
| `extensionFlagValues` | record | Extension CLI flags |
| `serializedRefine` | boolean | Sync refine between turns (print/json/rpc → daemon worker) |
| `initialGoal` | `{ objective; tokenBudget? }` | Seed top-level goal (rlmDepth 0 only; ignored if goal already persisted) |

CLI mapping (`runtimeConfigFromArgs`): non-interactive modes set `serializedRefine: true` for print/json/rpc; interactive and daemon app modes do not. Daemon server default config strips `initialGoal` so only explicit create requests seed goals.

### Autonomous defaults

| Field | Default |
|-------|---------|
| `enabled` | only when explicitly `true` |
| `maxContinuations` | `3` |
| `maxTurns` | `12` |
| `maxTokens` | `80000` |
| `timeoutMs` | `30 * 60 * 1000` |
| `gates.commands` | `[]` |
| `gates.maxRetries` | `3` |
| `gates.timeoutMs` | `5 * 60 * 1000` |
| `continuationPrompt` | built-in autonomous continuation text |

Subagent sessions (`rlmDepth > 0`) force autonomous `enabled: false` when resolving runtime session options, while still merging other autonomous fields.

## Services vs session construction

`createAgentSessionServices` builds cwd-bound infrastructure:

- `SettingsManager` (file or injected)
- `AuthStorage` / `ModelRegistry`
- `DefaultResourceLoader` (reads settings for packages, paths, built-in skill gates)
- `McpManager` (`getUserServers` ← `settingsManager.getMcpServers()`)
- Diagnostics for extension provider registration and unknown flags

`createAgentSession` / `createAgentSessionFromServices` then construct `AgentSession` with model, thinking, tools, autonomous, serialized refine, initial goal, and RLM metadata. Default thinking when neither option nor settings provide a level: `DEFAULT_THINKING_LEVEL` (`"medium"`), clamped to model capabilities.

| `CreateAgentSessionOptions` field | Default |
|-----------------------------------|---------|
| `cwd` | `process.cwd()` or session manager cwd |
| `agentDir` | `getAgentDir()` |
| `settingsManager` | `SettingsManager.create(cwd, agentDir)` |
| `sessionManager` | `SessionManager.create(cwd, …)` |
| `model` | settings default, else first available |
| `thinkingLevel` | settings, else `"medium"` |
| `tools` / `noTools` | built-in ipython + extension tools unless suppressed |
| `autonomous` / `serializedRefine` / `initialGoal` | optional host policy |

## Session `reload()` behavior

`AgentSession.reload()`:

1. Emits `session_shutdown` with reason `"reload"`.
2. `settingsManager.reload()` — re-reads global/project storage; **in-memory-only `applyOverrides` are discarded**.
3. Reloads `auth.json` so daemon client logins become visible.
4. Resets API providers; refreshes MCP user servers from reloaded settings.
5. `resourceLoader.reload()` — reapplies settings resource lists (packages, prompts, skills, extensions, themes).
6. Rebuilds runtime tools/extensions; may emit `session_start` reason `"reload"` and re-extend resources.

Regression coverage:

- Changing global `settings.json` `prompts` after startup, then `session.reload()`, updates both `settingsManager.getGlobalSettings().prompts` and the session’s loaded prompt templates (stale resource lists are not kept).
- In-memory settings keep their initial values across `reload()` and resource-loader reload.

Extension contexts captured before reload are stale: do not reuse a pre-reload `pi`/command `ctx` after `await ctx.reload()`.

## Config consumption map

| Runtime concern | Settings / config source |
|-----------------|--------------------------|
| Default model / thinking / service tier | `defaultProvider`, `defaultModel`, `defaultThinkingLevel`, `defaultServiceTier` |
| Compaction | `compaction.*` via `getCompactionSettings()` |
| Auto-refine cadence | `autoRefine.*` via `getAutoRefineSettings()` |
| Agent-level retry | `retry.*` / `getRetrySettings()` / `getProviderRetrySettings()` |
| Steering / follow-up queues | `steeringMode`, `followUpMode` |
| Transport + thinking budgets | `transport`, `thinkingBudgets` |
| Resource discovery | `packages`, `extensions`, `skills`, `prompts`, `themes`, enable flags |
| MCP | `mcpServers` |
| Shell tool | `shellPath`, `shellCommandPrefix` |
| Image pipeline | `images.*` |
| Daemon idle policy | `idleEvictionMinutes` (global) |
| RLM depth default | `rlmMaxDepth` (global) + env |
| Autonomous host policy | `AgentSessionRuntimeConfig.autonomous` / session options |
| Serialized refine / initial goal | runtime config → session construction |

## Troubleshooting

| Symptom | Likely cause | Check |
|---------|--------------|-------|
| Setting change ignored after edit | Edited project vs global scope, or nested merge left other fields | Inspect both `settings.json` files; call `session.reload()` or restart |
| Override lost after reload | Used `applyOverrides` without setters/storage write | Prefer setters + `flush()`, or re-apply after reload |
| Corrupt settings not updating | Parse error blocked saves | `drainErrors()`; fix JSON, then reload |
| Prompt/skill list stale | Resource settings updated on disk but session not reloaded | `await runtime.session.reload()` |
| Daemon idle eviction wrong | Set only in project settings | Set `idleEvictionMinutes` in global `~/.prime/agent/settings.json` |
| Thinking not `"xhigh"` | Unset settings use runtime default `"medium"` | Set `defaultThinkingLevel` explicitly |
| Transport not SSE | Default is `"auto"` | Set `transport` explicitly |
| In-memory test “forgets” keys | Relied on `applyOverrides` only | Seed `SettingsManager.inMemory({...})` or use setters |

## Related pages

<CardGroup>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, services, events, and session-scoped vs durable state.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, API keys, OAuth, and 401 recovery.
  </Card>
  <Card title="Sessions and full control" href="/sdk-sessions-control">
    SDK session management, settings injection, and full-control composition.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, autonomous mode, and retained subagents.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, resume selectors, provider 401s, and connection probes.
  </Card>
</CardGroup>

---

## 17. Settings and provider keys

> Provider registration, model selection, API key and OAuth wiring, dynamic provider updates, and 401 stale-provider recovery.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/17-settings-and-provider-keys.md
- Generated: 2026-08-06T19:49:11.809Z

### Source Files

- `packages/coding-agent/examples/sdk/09-api-keys-and-oauth.ts`
- `packages/coding-agent/examples/sdk/02-custom-model.ts`
- `packages/ai/src/providers/amazon-bedrock.ts`
- `packages/coding-agent/test/agent-session-dynamic-provider.test.ts`
- `packages/coding-agent/test/suite/agent-session-model-extension.test.ts`
- `packages/coding-agent/test/suite/regressions/4491-provider-stale-after-401.test.ts`

---
title: "Settings and provider keys"
description: "Provider registration, model selection, API key and OAuth wiring, dynamic provider updates, and 401 stale-provider recovery."
---

Prime Agent resolves models and credentials through `AuthStorage`, `ModelRegistry`, and session settings. Credentials live in `~/.prime/agent/auth.json` (override with `PRIME_AGENT_CODING_AGENT_DIR` / `PI_CODING_AGENT_DIR`), custom providers and model overrides in `models.json`, and defaults such as `defaultProvider` / `defaultModel` in `settings.json`. Runtime paths include CLI flags (`--provider`, `--model`, `--api-key`, `--models`), interactive `/login` and `/model`, extension `pi.registerProvider()`, and the SDK `createAgentSession({ authStorage, modelRegistry, model })` surface.

Architecture is BYOK/BYOC: any provider that speaks a supported API can be wired with your own keys, OAuth, ambient cloud credentials, or a proxy. No hosted model vendor is required.

## Config surfaces

| Surface | Default path | Role |
|---------|--------------|------|
| Agent dir | `~/.prime/agent/` | Root for auth, models, global settings |
| Auth store | `~/.prime/agent/auth.json` | API keys and OAuth tokens (`0600`) |
| Models | `~/.prime/agent/models.json` | Custom providers, overrides, local endpoints |
| Global settings | `~/.prime/agent/settings.json` | Defaults including model/thinking/retry |
| Project settings | `.prime/agent/settings.json` | Project overrides (wins over global) |

Override the agent directory with `PRIME_AGENT_CODING_AGENT_DIR` or `PI_CODING_AGENT_DIR` (tilde expansion supported).

### Model-related settings

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `defaultProvider` | string | — | Provider id used when resolving the default model |
| `defaultModel` | string | — | Model id paired with `defaultProvider` |
| `defaultThinkingLevel` | string | `"xhigh"` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
| `retry.enabled` | boolean | `true` | Agent-level auto-retry on transient errors |
| `retry.maxRetries` | number | `3` | Max agent-level retry attempts |
| `retry.baseDelayMs` | number | `2000` | Exponential backoff base (2s, 4s, 8s, …) |

`setModel()` persists the selection via `settingsManager.setDefaultModelAndProvider()` and appends a `model_change` session entry.

## Credential resolution

`AuthStorage.getApiKeyWithSourceToken()` and `ModelRegistry.getApiKeyAndHeaders()` combine several sources. Documented product order:

1. CLI `--api-key` (runtime override; not written to disk)
2. `auth.json` entry (API key or OAuth token)
3. Environment variable for that provider
4. Custom provider keys from `models.json` / registered providers (fallback)

Implementation details that matter in practice:

- **Runtime override** (`setRuntimeApiKey` / `--api-key`) always wins when present and not marked stale.
- **Prime Inference** (`prime-inference`) prefers env `PRIME_API_KEY`, then Prime CLI config (`prime_cli`), then `auth.json`.
- **Other providers** prefer `auth.json` over env vars.
- **Fallback resolver** supplies `models.json` / extension `apiKey` values after AuthStorage sources.
- Ambient cloud auth (Bedrock IAM/profile/bearer, Vertex ADC) is detected by `getEnvApiKey()` and reported as configured without exposing secret material.

```text
Request for provider P
  │
  ├─ runtime override (--api-key / setRuntimeApiKey)
  ├─ [prime-inference only] PRIME_API_KEY → Prime CLI config → auth.json
  ├─ [other providers] auth.json → env API key
  ├─ models.json / registerProvider apiKey fallback
  └─ ambient markers (Bedrock / Vertex) when applicable
```

### Auth status sources

`AuthStatus.source` values used by `getAuthStatus()` / `getProviderAuthStatus()`:

| Source | Meaning |
|--------|---------|
| `runtime` | In-process `--api-key` / `setRuntimeApiKey` |
| `stored` | Entry in `auth.json` |
| `environment` | Provider env var (or ambient credential presence) |
| `prime_cli` | Prime Inference via Prime CLI config |
| `fallback` | Resolver-backed key (typically `models.json`) |
| `models_json_key` / `models_json_command` | Request auth configured on the provider in models/registry |
| `stale` | Previously used credentials marked expired after auth failures |

`configured: true` means a durable or resolvable source exists without refreshing OAuth. `source: "stale", label: "expired"` means the active credential set failed auth and is blocked until replaced.

## API keys

### CLI

```bash
export ANTHROPIC_API_KEY=sk-ant-...
prime-agent --provider anthropic --model claude-sonnet-4-5

# One-shot runtime key (not persisted); requires a resolved model
prime-agent --provider anthropic --model claude-sonnet-4-5 --api-key sk-ant-...
```

`--api-key` without a model resolution path errors: it requires `--model`, `--provider`/`--model`, or `--models`.

### Interactive and auth file

- `/login` stores API keys or OAuth credentials in `auth.json`.
- `/logout` clears the provider entry (and Prime CLI credentials for `prime-inference` when enabled).

```json
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "prime-inference": { "type": "api_key", "key": "..." }
}
```

The `key` field accepts:

| Form | Example |
|------|---------|
| Literal | `"sk-ant-..."` |
| Env var name | `"MY_ANTHROPIC_KEY"` |
| Shell command | `"!op read 'op://vault/item/credential'"` |

Command-backed keys in `auth.json` resolve via `resolveConfigValue` (process-lifetime caching for ordinary resolution; uncached re-resolve after a stale match).

### Representative env → provider map

| Provider id | Environment variable |
|-------------|----------------------|
| `anthropic` | `ANTHROPIC_OAUTH_TOKEN`, then `ANTHROPIC_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| `prime-inference` | `PRIME_API_KEY` |
| `google` | `GEMINI_API_KEY` |
| `openrouter` | `OPENROUTER_API_KEY` |
| `xai` | `XAI_API_KEY` |
| `azure-openai-responses` | `AZURE_OPENAI_API_KEY` |
| `github-copilot` | `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN` |

Full mapping lives in `packages/ai/src/env-api-keys.ts`.

### SDK

```typescript
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";

// Default paths: ~/.prime/agent/auth.json + models.json
const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);

// Custom paths
const customAuth = AuthStorage.create("/tmp/my-app/auth.json");
const customRegistry = ModelRegistry.create(customAuth, "/tmp/my-app/models.json");

// Runtime key (not persisted)
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");

// Built-ins only (no models.json)
const simpleRegistry = ModelRegistry.inMemory(authStorage);

await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  authStorage,
  modelRegistry: simpleRegistry,
});
```

## OAuth

Subscription OAuth providers (ChatGPT Codex, Claude Pro/Max, GitHub Copilot, and extension-registered OAuth providers) use `/login` → browser or device-code flow → `auth.json` entry of type `oauth`.

- Tokens auto-refresh under a file lock so concurrent processes do not clobber each other.
- Failed refresh returns no key (provider skipped for discovery) while preserving credentials for a later `/login`.
- Extension OAuth registers via `pi.registerProvider(name, { oauth: { name, login, refreshToken, getApiKey, modifyModels? } })`. The OAuth provider `id` is forced to the registration name.

## Model selection

### Built-in catalog vs available models

| API | Behavior |
|-----|----------|
| `getModel(provider, id)` (`@earendil-works/pi-ai`) | Look up a built-in catalog entry |
| `modelRegistry.find(provider, id)` | Built-in + custom + extension models |
| `modelRegistry.getAvailable()` | Models with configured auth (no OAuth refresh) |
| `modelRegistry.getAll()` | Full registry regardless of auth |

### CLI and interactive

```bash
prime-agent --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0
prime-agent --models "anthropic/*,openai/gpt-*"
```

- `/model` reloads `models.json` and lists models by `id` (display `name` used for matching and status text).
- Session `setModel(model)` requires configured auth and `canUseModel()` (Prime team gating for private Prime Inference models).
- Emits extension event `model_select` with `previousModel`, `model`, and `source` (`set` / cycle).
- `setModel(model, { waitForExtensions: false })` returns after state is saved without waiting for slow handlers; handlers still serialize across quick switches.
- `cycleModel()` walks scoped models (`--models` / enabled models) or all available models.

### SDK model pick

```typescript
import { getModel } from "@earendil-works/pi-ai";
import { AuthStorage, createAgentSession, ModelRegistry } from "@earendil-works/pi-coding-agent";

const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);

const opus = getModel("anthropic", "claude-opus-4-5");
const available = await modelRegistry.getAvailable();

const { session } = await createAgentSession({
  model: available[0],
  thinkingLevel: "medium", // off | low | medium | high (and model-supported levels)
  authStorage,
  modelRegistry,
});
```

## Provider registration

Two complementary paths; both are BYOK-friendly.

### 1. `models.json` (file-based)

```json
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false
      },
      "models": [{ "id": "llama3.1:8b" }]
    }
  }
}
```

Supported `api` values for file config include `openai-completions`, `openai-responses`, `anthropic-messages`, and `google-generative-ai`. Extension registration additionally supports `azure-openai-responses`, `openai-codex-responses`, `mistral-conversations`, `google-vertex`, and `bedrock-converse-stream`.

Provider fields: `baseUrl`, `api`, `apiKey`, `headers`, `authHeader`, `models`, `modelOverrides`. `apiKey` / header values support literal, env-var name, or `!command` forms. Shell commands in `models.json` resolve at request time (no built-in TTL); wrap slow or rate-limited commands yourself. `/model` availability checks do **not** execute shell commands.

### 2. Extensions: `pi.registerProvider()`

```typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // Override-only: keep built-in models, change endpoint
  pi.registerProvider("anthropic", {
    baseUrl: "https://proxy.example.com",
  });

  // Full provider with models (replaces that provider's model list)
  pi.registerProvider("my-provider", {
    name: "My Provider",
    baseUrl: "https://api.example.com",
    apiKey: "MY_API_KEY",
    api: "openai-completions",
    authHeader: true,
    models: [
      {
        id: "my-model",
        name: "My Model",
        reasoning: false,
        input: ["text", "image"],
        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
        contextWindow: 128000,
        maxTokens: 4096,
      },
    ],
  });
}
```

Validation rules when `models` is non-empty:

- `baseUrl` required
- `apiKey` or `oauth` required
- each model must resolve an `api` (provider- or model-level)
- `streamSimple` requires `api`

`pi.unregisterProvider(name)` removes dynamic models and restores built-ins overridden by that registration.

## Dynamic provider updates

`registerProvider` is safe after initial load: no `/reload` required. Overrides apply to the **active** session model immediately.

| Call site | Effect |
|-----------|--------|
| Top-level extension factory | Applied during resource load / session create |
| `session_start` handler | Applied when extensions bind; updates active model |
| Custom command handler | Applied on command run without reload |

Verified behaviors: `baseUrl` overrides appear on `session.model` and on the model object passed into the stream function for the next prompt.

`ModelRegistry.refresh()` reloads disk models, resets API/OAuth registries, re-applies still-registered dynamic providers, and reloads `auth.json` so credentials written by another process (for example a UI login while a daemon holds the session) become visible.

## Cloud ambient providers

### Amazon Bedrock

Auth markers (any one is enough for “configured”):

- `AWS_PROFILE`
- `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`
- `AWS_BEARER_TOKEN_BEDROCK`
- ECS task role URIs / `AWS_WEB_IDENTITY_TOKEN_FILE` (IRSA)

Stream options support `region`, `profile`, `bearerToken`, thinking controls, and `requestMetadata`. Region resolution: explicit option → env → profile chain → default `us-east-1`. Bearer tokens skip SigV4 when set (unless `AWS_BEDROCK_SKIP_AUTH=1` for unauthenticated proxies). Optional: `AWS_ENDPOINT_URL_BEDROCK_RUNTIME`, `AWS_BEDROCK_FORCE_HTTP1`, `AWS_BEDROCK_FORCE_CACHE`.

### Google Vertex

ADC via `gcloud auth application-default login` or `GOOGLE_APPLICATION_CREDENTIALS`, plus `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`.

## 401 stale-provider recovery

When a provider returns a concrete auth failure, the session marks the **credential source that was used** stale so the same dead key is not reused indefinitely.

### What counts as a concrete auth failure

- Structured diagnostic `provider_stream_failure` with `details.kind === "auth"` and status `401` or `403`
- Or error text matching `401`/`403` plus auth-related wording (including bare `"401 status code (no body)"`)

### Recovery loop

```mermaid
stateDiagram-v2
  [*] --> Streaming
  Streaming --> Retry: concrete 401/403 and retries remain
  Retry --> Streaming: backoff then re-prompt
  Retry --> Stale: max retries / cancel / retry disabled
  Streaming --> Stale: final non-auth error after prior auth failure
  Stale --> Guidance: errorMessage + "Run /login to update credentials."
  Guidance --> [*]
```

| Condition | Behavior |
|-----------|----------|
| Retry enabled, first auth failure | Capture auth source token; one (or more) auto-retries per `retry.maxRetries` |
| Repeated auth failure | Mark captured sources stale; emit `auth_stale`; append login guidance |
| Retry disabled | Mark stale immediately; no `auto_retry_start` |
| Retry cancelled mid-backoff | Mark captured sources stale; `auto_retry_end` with `"Retry cancelled"` |
| Credentials rotated during backoff | Mark each failed source token stale; new runtime key is a new identity |
| Auth failure then 500 on later attempt | Still marks prior auth sources stale; final error may be the 500 text plus login guidance |

Stale fingerprinting matches `source` + `identityFingerprint` + `valueFingerprint`. Updating credentials (new `/login`, new env value, new `setRuntimeApiKey`) produces a new fingerprint and becomes usable again. Stale status surfaces as:

```ts
authStorage.getAuthStatus(provider)
// { configured: false, source: "stale", label: "expired" }
```

Daemon and connection clients receive:

```ts
{ type: "auth_stale", provider: string, sourceTokens?: AuthSourceToken[] }
```

Recovery action: run `/login` (or supply a new key) for that provider, then continue the session.

## Error and guidance strings

| Situation | Message pattern |
|-----------|-----------------|
| No models with auth | `No models available.` + login help pointing at packaged `providers.md` / `models.md` |
| No model selected | `No model selected.` + login help + `/model` |
| Missing key for provider | `No API key found for <provider>.` |
| Auth failure after stale mark | Original error + `Run /login to update credentials.` |
| `setModel` without auth | throws `No API key for <provider>/<id>` |

## Verification checklist

<Steps>
  <Step title="Confirm credentials resolve">
    Set an env key or complete `/login`, then run `prime-agent model list` (or SDK `modelRegistry.getAvailable()`). Expect at least one model for the configured provider.
  </Step>
  <Step title="Confirm model selection persists">
    Select a model with `/model` or `session.setModel()`. Expect a `model_change` session entry and updated `defaultProvider` / `defaultModel` in settings.
  </Step>
  <Step title="Confirm dynamic overrides">
    From an extension, call `pi.registerProvider(provider, { baseUrl })` at load, `session_start`, or command time. The active model’s `baseUrl` should update for the next stream without restart.
  </Step>
  <Step title="Confirm stale recovery">
    Force a 401 (invalid key). With retries enabled, expect a single retry class of attempts, then `auth_stale`, `hasAuth === false` for that source, and login guidance on the assistant error. After `/login` or a new runtime key, `getAuthStatus` should leave `stale`.
  </Step>
</Steps>

## Related pages

<CardGroup cols={2}>
  <Card title="Authentication and providers" href="/authentication-providers">
    Login paths, multi-provider selection, OAuth examples, and BYOK boundaries.
  </Card>
  <Card title="Session configuration reference" href="/session-configuration">
    Full settings schema, defaults, reload behavior, and runtime config surfaces.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Extension load path, allowlists, and provider registration from extensions.
  </Card>
  <Card title="Sessions and full control (SDK)" href="/sdk-sessions-control">
    SDK settings injection, session hooks, and full-control composition.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, network retry, and recovery probes.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap session creation with model and auth wiring.
  </Card>
</CardGroup>

---

## 18. Built-in skills reference

> Catalog of shipped skills (goal, refine, compact, heartbeat, observe, message, edit, integrations) with entry modules and invocation roles.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/18-built-in-skills-reference.md
- Generated: 2026-08-06T19:47:43.046Z

### Source Files

- `packages/coding-agent/skills/goal/SKILL.md`
- `packages/coding-agent/skills/refine/SKILL.md`
- `packages/coding-agent/skills/compact/SKILL.md`
- `packages/coding-agent/skills/rlm-heartbeat/SKILL.md`
- `packages/coding-agent/skills/agent-observe/SKILL.md`
- `packages/coding-agent/skills/agent-message/SKILL.md`

---
title: "Built-in skills reference"
description: "Catalog of shipped skills (goal, refine, compact, heartbeat, observe, message, edit, integrations) with entry modules and invocation roles."
---

Prime Agent ships kernel-callable skills as packages under `packages/coding-agent/skills/`. Each skill exposes an async Python surface invoked from the persistent IPython control plane (or kernel), with host-backed behavior for harness, context, goals, and family messaging. Documented here from shipped `SKILL.md` packages: `goal`, `refine`, `compact`, `rlm-heartbeat`, `agent-observe`, and `agent-message`.

<Note>
User-facing slash commands such as `/compact`, `/refine`, and `/heartbeat` are host surfaces. The skills below are the agent-side IPython interfaces to related behavior (or, for RLM heartbeats, a separate agent-owned surface that does not control the user's `/heartbeat`).
</Note>

## Catalog

| Skill name | Package path | Kernel entry | Role |
|---|---|---|---|
| `goal` | `packages/coding-agent/skills/goal/` | `goal` | Persistent thread objective: read status/budget, create when explicitly requested, complete when achieved |
| `refine` | `packages/coding-agent/skills/refine/` | `refine` | Schedule continual-harness refinement from trajectory (session-local or global) |
| `compact` | `packages/coding-agent/skills/compact/` | `compact` | Report context usage; schedule conversation compaction |
| `rlm-heartbeat` | `packages/coding-agent/skills/rlm-heartbeat/` | `rlm_heartbeat` | Agent-owned recurring prompts for the current session |
| `agent-observe` | `packages/coding-agent/skills/agent-observe/` | `agent_observe` | Read-only nuclear-family session inspection |
| `agent-message` | `packages/coding-agent/skills/agent-message/` | `agent_message` | Direct messages to parent, siblings, or children via the daemon |

```text
packages/coding-agent/skills/
├── goal/SKILL.md              → await goal.*
├── refine/SKILL.md            → await refine.*
├── compact/SKILL.md           → await compact.*
├── rlm-heartbeat/SKILL.md     → await rlm_heartbeat.*
├── agent-observe/SKILL.md     → await agent_observe.*
└── agent-message/SKILL.md     → await agent_message.*
```

## Invocation model

| Concern | Behavior |
|---|---|
| Call site | IPython / kernel: `await <entry>.<method>(...)` |
| Host-backed skills | `goal`, `refine`, `compact` — implementation lives in the host; skill is the kernel-side interface |
| Deferred apply | `refine.run` and `compact.run` schedule work; it runs when the **current turn ends**, not mid-cell |
| Immediate return | Scheduling APIs return `{"scheduled": True}` or `{"scheduled": False, "reason": ...}` |
| One request per turn | Re-calling `run` before the turn ends updates instructions only |
| Family graph | `agent_observe` / `agent_message` operate on the nuclear family (self, parent, siblings, direct children) through the local daemon |
| Related RLM APIs | Subagent lifecycle uses `rlm.list_subagents()`, `rlm.delete_subagent(...)` (parent-owned); not part of observe |

```mermaid
flowchart TB
  subgraph kernel["IPython / kernel"]
    G["goal"]
    R["refine"]
    C["compact"]
    H["rlm_heartbeat"]
    O["agent_observe"]
    M["agent_message"]
  end
  subgraph host["Host"]
    HG["Goal state"]
    HR["/refine + harness apply"]
    HC["/compact + summary"]
  end
  subgraph daemon["Local daemon"]
    FAM["Nuclear family sessions"]
    MSG["Message delivery"]
  end
  G --> HG
  R --> HR
  C --> HC
  H --> FAM
  O --> FAM
  M --> MSG
  MSG --> FAM
```

---

## `goal` — thread goal

**Frontmatter:** `name: goal`  
**Description:** Manage the persistent thread goal from IPython. Use to read goal status and budget usage, to start a goal when the user explicitly asks for one, or to mark the active goal complete once its objective is fully achieved.

The harness keeps re-prompting the agent toward the objective across turns until completion. Goal state (status, token budget, usage) lives in the host.

### API

| Call | Returns / effect |
|---|---|
| `await goal.get()` | Dict: `goal` (`None` if unset), `remaining_tokens`, `completion_budget_report` |
| `await goal.create(objective, token_budget=None)` | Start active goal |
| `await goal.complete()` | Mark existing goal achieved |

**`goal` object fields (when set):** `objective`, `status`, `token_budget`, `tokens_used`, `time_used_seconds`, timestamps.

### Constraints

- `create` fails while a goal is still pending (active, paused, or budget-limited). Completed or errored goals are replaced.
- Create only when the user or system/developer instructions **explicitly** request a persistent long-running goal; do not infer goals from ordinary tasks.
- Set `token_budget` only when an explicit budget is requested.
- Pause, resume, clear, and budget-limiting are host/user-controlled — not exposed on this skill.
- Completion must call `await goal.complete()`; saying the work is done does not stop harness continuation.
- When `complete` returns a `completion_budget_report`, report final usage to the user.

### Example

```python
await goal.get()
await goal.create("ship the release notes", token_budget=200000)
await goal.complete()
```

---

## `refine` — continual harness refinement

**Frontmatter:** `name: refine`  
**Description:** Trigger continual harness refinement from IPython. Use when you notice a repeated failure, reusable tactic, delegation role, or behavior policy that should be persisted as a harness entry. Returns immediately; refinement runs when the current turn ends.

Refinement analyzes the conversation trajectory and applies small, evidence-backed updates to the continual harness (prompts, memories, skills, subagent specs). Same host path as the user's `/refine` command.

### API

| Call | Returns / effect |
|---|---|
| `await refine.status()` | `pending` (queued this turn), `in_flight` (planning or applying) |
| `await refine.run(instructions=None, global_=False)` | `{"scheduled": True}` or `{"scheduled": False, "reason": ...}` |

### Parameters

<ParamField body="instructions" type="string | None">
Focus refinement on a specific observation. Optional.
</ParamField>

<ParamField body="global_" type="bool" default="False">
`True` targets the global harness store (cross-session). Omit/`False` for session-scoped (local) refinement.
</ParamField>

### Constraints

- Never runs mid-cell; applies at turn end, rebuilds the system prompt, then resumes the agent.
- Prefer focused memories, skills, prompt notes, or subagent specs over rewriting the whole harness.
- Use after repeated failures, reusable tactics, repeated delegation roles, or persistable behavior policies.

### Example

```python
await refine.status()
await refine.run()
await refine.run("create a memory about always checking git status before committing")
await refine.run("promote the error-handling pattern to a global skill", global_=True)
```

---

## `compact` — context compaction

**Frontmatter:** `name: compact`  
**Description:** Check context usage and compact the conversation from IPython. Use when context is filling up and substantial work remains, so the session is summarized and you keep working instead of stopping early.

Compaction replaces older history with a dense summary. Host implementation matches the user's `/compact` command.

### API

| Call | Returns / effect |
|---|---|
| `await compact.status()` | `tokens`, `context_window`, `percent` (`None` right after compaction until the next model response), `scheduled` |
| `await compact.run(instructions=None)` | `{"scheduled": True}` or `{"scheduled": False, "reason": ...}` when nothing to compact yet |

### Constraints

- Never runs mid-cell; runs when the turn ends; harness resumes with summary plus recent messages.
- IPython kernel **persists** through compaction: variables, imports, and helpers remain available.
- Compact at a natural boundary when usage is high and substantial work remains.
- One `run` per turn; later calls only update instructions.

### Example

```python
await compact.status()
await compact.run()
await compact.run("keep the failing test names and the migration checklist")
```

---

## `rlm-heartbeat` — agent-owned heartbeats

**Frontmatter:** `name: rlm-heartbeat`  
**Kernel entry:** `rlm_heartbeat`  
**Description:** Manage agent-owned RLM heartbeats from IPython. Use when the user asks the agent to start, create, schedule, or manage a heartbeat, unless they explicitly request the user's `/heartbeat`.

RLM heartbeats are **internal** recurring prompts for the current agent session. They are separate from the user's visible `/heartbeat`. This skill cannot read, replace, pause, resume, or clear the user-level heartbeat.

### API

| Call | Behavior |
|---|---|
| `await rlm_heartbeat.list(include_inactive=False)` | List this session's internal RLM heartbeats (default: active + paused) |
| `await rlm_heartbeat.create(instruction, interval=None, label=None, delivery_mode=None)` | Create recurring heartbeat |
| `await rlm_heartbeat.update(id, instruction=None, interval=None, label=None, status=None, delivery_mode=None)` | Update by id |
| `await rlm_heartbeat.delete(id)` | Cancel by id |

### Defaults and enums

| Field | Values / default |
|---|---|
| `interval` | Default every **5 minutes** when omitted |
| `label` | Optional; distinguish concurrent heartbeats |
| `delivery_mode` | `"steer"` (default) or `"follow_up"` |
| `status` (update) | `"pause"` or `"resume"` |

### Delivery mode

| Mode | Behavior when session is busy |
|---|---|
| `steer` (default) | Interrupt the current turn so the heartbeat runs promptly |
| `follow_up` | Wait for the current turn to finish before running |

### Constraints

- Use for agent-internal recurring checks and long-running task coordination.
- Do **not** use this skill to configure the user's `/heartbeat`.
- Keep instructions specific and actionable per tick.
- Multiple RLM heartbeats may run at once.

### Example

```python
await rlm_heartbeat.create("check test progress", interval="5m", label="tests")
await rlm_heartbeat.create("watch build", delivery_mode="follow_up")
await rlm_heartbeat.list()
await rlm_heartbeat.update("job-id", status="pause")
await rlm_heartbeat.delete("job-id")
```

---

## `agent-observe` — family observation (read-only)

**Frontmatter:** `name: agent-observe`  
**Kernel entry:** `agent_observe`  
**Description:** Read-only observation of an agent's parent, siblings, and direct children. Use to inspect family status and bounded recent-message previews without mutating sessions.

Observes the nuclear family through the local daemon: parent, siblings, direct children, and self. Limited to family members in the **same worker**; root siblings in other workers are not observable yet.

**Not allowed:** prompt, steer, clear, kill, rename, or any other mutation of another session. Deletion of subagents is parent-owned RLM (`await rlm.delete_subagent(...)`), not observe.

### API

| Call | Returns |
|---|---|
| `await agent_observe.list_agents()` | `current` and `agents` |
| `await agent_observe.get_agent(target)` | `{ "agent": <summary> }` |
| `await agent_observe.recent_messages(target, limit=8, max_chars=800)` | Bounded recent message previews |

### Agent summary fields

Active session id, session id, optional name, runtime kind, cwd, status, streaming state, message count, pending count, latest message preview.

### Selectors and bounds

| Item | Rule |
|---|---|
| `target` | Active id, session id/name, or unambiguous suffix |
| `limit` | 1–50 (default 8) |
| `max_chars` | 80–2000 (default 800) |
| Scope | Self, parent, siblings, direct children only; outside family rejected |

### Example

```python
children = await rlm.list_subagents()
child = next((item for item in children if item.active_session_id), None)
if child is not None:
    worker = await agent_observe.get_agent(child.session_name)
    recent = await agent_observe.recent_messages(child.session_name, limit=6)
    # Deletion is parent-owned RLM, not observe:
    await rlm.delete_subagent(child)
```

---

## `agent-message` — family messaging

**Frontmatter:** `name: agent-message`  
**Kernel entry:** `agent_message`  
**Description:** Message an agent's parent, siblings, or direct children through the daemon. Use the family roster to discover reachable agents and send direct text without spoofing sender identity.

Send direct messages within the current agent's nuclear family: parent, siblings, and direct children only. Roots are siblings. Sender identity is **daemon-derived** from the current session — do not include a `from` field.

### API

| Call | Behavior |
|---|---|
| `await agent_message.list_agents()` | `current` (`name`, `id`, `depth`) and family-scoped `entries` (`relationship`, `name`, `id`, `depth`, `status`) |
| `await agent_message.send(message, receiver_role=..., receiver_name=None)` | One direct text message to an active session |
| `send("all", message)` | Broadcast to family roster only; returns `{receipts: [...]}` |

### `list_agents` details

- Includes inactive family members.
- Sort order: parent, then siblings by name, then children by name.
- Does **not** expose a global daemon session list.

### `send` details

| Item | Rule |
|---|---|
| `receiver_role` | `"parent"` \| `"sibling"` \| `"child"` (resolved within current family) |
| `receiver_name` | Required for siblings and children; omit for the unique parent |
| Idle completed subagent | Starts an ordinary follow-up turn in that same child session/context |
| Child lifetime | Child remains available only until its parent session closes |
| Delivery | Always **steering** so a busy target sees messages during its active run |
| Broadcast failures | One failed delivery does not reject successful deliveries; failed entries include target id and short `error` |

### Receipts

| `deliveryStatus` | Meaning |
|---|---|
| `"delivered"` | Message reached an idle target's context (`deliveredAt`) |
| `"queued"` | Steering accepted; delivers when target's current work allows (`queuedAt`). `send` does not block waiting |

### Safety

- Do not delete a child immediately after `send`; wait until observation shows idle and context is no longer needed before `await rlm.delete_subagent(child)`.
- Reach is parent / siblings / direct children only; relay via an intermediate for grandchildren or cousins.
- Sender identity cannot be spoofed from Python.
- Daemon enforces message size, rate, and pending-queue limits before accepting delivery.

### Example

```python
children = await rlm.list_subagents()
child = next((item for item in children if item.active_session_id), None)
if child is not None:
    receipt = await agent_message.send(
        "Please inspect the latest result.",
        receiver_role="child",
        receiver_name=child.session_name,
    )
    # Keep the child until this follow-up finishes so its result remains observable.
```

---

## Cross-skill coordination patterns

### Long-running work

| Need | Skill |
|---|---|
| Explicit multi-turn objective + budget | `goal.create` / `goal.complete` |
| Context pressure mid-task | `compact.status` → `compact.run` |
| Periodic agent-owned checks | `rlm_heartbeat.create` with `steer` or `follow_up` |
| Persist a learned policy | `refine.run` (local or `global_=True`) |

### Multi-agent family

| Need | Skill |
|---|---|
| Discover / inspect family | `agent_observe.list_agents` / `get_agent` / `recent_messages` |
| Parent-owned child handles | `rlm.list_subagents()` |
| Direct instruction to family member | `agent_message.send` |
| Broadcast to roster | `agent_message.send("all", message)` |
| Remove child after work | `rlm.delete_subagent` only when idle and no longer needed |

```text
                  parent session
                       │
         ┌─────────────┼─────────────┐
         │             │             │
      sibling       (self)        sibling
                       │
              ┌────────┴────────┐
           child A           child B
              ▲                 │
   agent_message.send      agent_observe.*
   (steer delivery)        (read-only)
```

---

## User-level vs agent-level surfaces

| Concern | User / host | Agent skill |
|---|---|---|
| Compaction | `/compact` | `compact` |
| Harness refine | `/refine` | `refine` |
| Heartbeat | `/heartbeat` (user-visible) | `rlm_heartbeat` (agent-internal only; does not control user heartbeat) |
| Goal lifecycle (pause/resume/clear) | User + host | `goal` only get/create/complete |
| Family message delivery limits | Daemon enforcement | `agent_message` subject to those limits |

---

## Error and boundary signals

| Signal | Meaning |
|---|---|
| `{"scheduled": False, "reason": ...}` | `refine.run` or `compact.run` could not start |
| `goal.create` fails while pending | Active, paused, or budget-limited goal still exists |
| `percent: None` on `compact.status` | Immediately after compaction, until next model response |
| Observe/message outside family | Rejected; transcript reads follow the same family rule |
| Message receipt `"queued"` | Accepted for later delivery; not yet in idle context |
| Broadcast partial failure | Successful receipts still delivered; failed entries carry `error` |

---

## Related pages

<CardGroup>
  <Card title="Skills model" href="/skills-model">
    Skills as importable packages, SKILL.md frontmatter, collision precedence, and scope.
  </Card>
  <Card title="Create and install skills" href="/create-skills">
    Author packages with skill-creator, required fields, layout, and load-path checks.
  </Card>
  <Card title="Refine harness state" href="/refine-harness">
    Run refine against the trajectory, apply harness updates, and use snapshots for rollback.
  </Card>
  <Card title="Continual Harness" href="/continual-harness">
    Durable prompts, memories, skill specs, subagent specs, and refine boundaries.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and retained subagents across disconnects.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    rlm-spawned children, agent-message surface, and multi-agent constraints.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython as control tool, prompt-as-variable context, and rlm(...) calls.
  </Card>
</CardGroup>

---

## 19. Agent connection modes

> Daemon, in-process, and snapshot connection paths; agents-view mode; active session state and transfer idempotency constraints.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/19-agent-connection-modes.md
- Generated: 2026-08-06T19:50:13.143Z

### Source Files

- `packages/coding-agent/docs/agent-connection.md`
- `packages/coding-agent/test/agent-connection-daemon.test.ts`
- `packages/coding-agent/test/agent-connection-in-process.test.ts`
- `packages/coding-agent/test/agent-connection-snapshot.test.ts`
- `packages/coding-agent/src/modes/daemon/active-session-state.ts`
- `packages/coding-agent/src/modes/agents-view/agents-view-mode.ts`

---
title: "Agent connection modes"
description: "Daemon, in-process, and snapshot connection paths; agents-view mode; active session state and transfer idempotency constraints."
---

`AgentConnection` is the client boundary between UI surfaces and the process that owns agent execution. Interactive, agents-view, print, RPC, ACP, and SDK paths talk to the same TypeScript interface; only the adapter changes.

It is **not** the daemon wire protocol and not a hosted gateway API. Adapters own framing, versioning, reconnect, and DTO translation. The UI must not hold `AgentSessionRuntime`, `AgentSession`, `SessionManager`, daemon sockets, or executable callbacks through this boundary.

## Architecture

```mermaid
flowchart LR
  ui["InteractiveMode / agents-view / print / RPC / ACP"]
  iface["AgentConnection"]
  daemonAdapter["DaemonAgentConnection"]
  inprocAdapter["InProcessAgentConnection"]
  protocol["prime-agent.daemon protocol"]
  worker["Session worker"]
  runtime["AgentSessionRuntime"]

  ui --> iface
  iface --> daemonAdapter
  iface --> inprocAdapter
  daemonAdapter --> protocol --> worker --> runtime
  inprocAdapter --> runtime
```

| Path | Adapter | When it is used | Execution owner |
| --- | --- | --- | --- |
| Daemon (default interactive) | `DaemonAgentConnection` | Normal local interactive, agents view, most client modes when the daemon is available | Session worker under the daemon supervisor |
| In-process | `InProcessAgentConnection` | SDK/local fallback, process-local extension factories, owned-session worker, ACP/RPC/print when not on the daemon client path | Same process as the client |
| Snapshot transfer | Shared snapshot types + chunked stream | Attach, replacement, and resync of coherent session state (including large transcripts) | Produced by the execution owner; assembled by the client adapter |

Startup composition lives in `main.ts`. `InteractiveMode` only depends on `AgentConnection`.

## Startup routing

Client modes prefer the daemon unless startup is excluded (daemon process itself, startup benchmark, help, list-models, owned-session worker, or process-local extension factories).

| Decision helper | Effect |
| --- | --- |
| `shouldUseDaemonClient` | Client modes use the daemon transport when not the daemon process, not benchmarking, not help, not list-models |
| `shouldUseDaemonClientRuntime` | Same as above, but also false for owned-session workers and process-local extension factories |
| `shouldUseDaemonInteractive` | Interactive daemon path requires interactive mode, not `--no-session`, not list-models |
| `shouldOpenAgentsViewForDaemonInteractive` | Opens agents view only for explicit `agents`, without onboarding and without resume/continue/fork selectors |

Default interactive launch opens a chat session. The unified agents view is reached with `prime-agent agents` or by returning from a session (for example left-arrow when `returnToAgentsView` is set).

When the daemon interactive path cannot run (for example process-local extensions), interactive mode falls back to `InProcessAgentConnection` with `InteractiveModeLocalSessionHost` so callback-bearing local extensions still work.

## `AgentConnection` surface

Core operations exposed to clients:

- Prompt, steer, follow-up, abort, idle wait, bash
- Model / thinking / service tier / transport / queue settings
- Compaction, refine, retry abort, reload
- Session replace ops: new, switch, fork, import/export, tree navigation
- State, transcript, tree, context, stats, resources, model catalog
- Saved-session list/rename/delete
- Heartbeats and cron jobs
- Extension UI request/response (serializable only)
- RLM child cancel, agent messaging, optional `watchSession` for another live session

Selected event types:

| Event | Meaning |
| --- | --- |
| `session_event` | Live session activity (stream, tools, bash, refine, RLM children, …) |
| `session_replaced` | Runtime behind the connection was replaced; apply new state + messages |
| `session_resynced` | Full coherent snapshot after attach/reconnect/catch-up |
| `extension_ui_request` | Serializable extension UI op (select, confirm, input, editor, …) |
| `connection_status` | `reconnecting` / `connected` |
| `closed` | Terminal connection loss |

## Daemon connection path

### Adapter responsibilities

`DaemonAgentConnection` owns:

- A `DaemonClient` and stable client id (`daemon-agent-connection:<uuid>`)
- Active session id and latest snapshot cache
- Last event cursor / sequence and retired generations
- Streamed snapshot assembly (`session_snapshot_begin` / `chunk` / `end` / `failed`)
- Reconnect with the same identity and resume cursor when `recoverDaemon` is provided

Attach advertises client capabilities, including:

- `attach_snapshot`
- `event_sequence`
- `extension_ui` (interactive clients)
- `slim_attach`
- `chunked_snapshot`
- `client_owned_sessions` when the session is client-owned

### Protocol constants

| Constant | Current value / role |
| --- | --- |
| Protocol name | `prime-agent.daemon` |
| `DAEMON_PROTOCOL_VERSION` | `7` |
| `DAEMON_SCHEMA_REVISION` | `13` |
| Framing | JSONL (optional private-framed transport for some clients) |
| Command envelopes | Protocol metadata + optional `clientId` + command id (envelope min protocol 7) |

Wire changes must be classified as backward-compatible, capability-gated, or incompatible, with schema/protocol metadata and old/new client coverage updated together.

### Session lifecycle on create

| Lifecycle | Behavior |
| --- | --- |
| `resident` | Default interactive fleet sessions; survive detach; listed in agents view while live |
| `client_owned` | Headless/print/RPC-style owned workers; dispose can complete the worker instead of only detaching; require server capability `client_owned_sessions` |

Client env allowlist (`HERDR_*` keys) is sent on **create**, and on attach only for the primary interactive connection with `sendClientEnv`. Attach is adopt-if-absent: watchers must not rebind session identity.

### Reconnect and replay

1. Client keeps last cursor `{ generation, sequence }`.
2. On transient socket loss with recovery enabled, emit `connection_status: reconnecting`, probe/restart supervisor (`recoverDaemon`), reconnect, reattach with `resumeCursor`.
3. Default reconnect budget: `DAEMON_RECONNECT_TIMEOUT_MS` (60s).
4. Successful recovery emits `session_resynced` then `connection_status: connected`.
5. Replay status is `complete` | `partial` | `unavailable`. Generation changes make bare sequences non-comparable; `event_generation_changed` forces snapshot-as-truth.
6. The protocol does **not** guarantee infinite event history. Durable state + a fresh snapshot are the recovery baseline; replay is an optimization.

Duplicate or retired-generation events are dropped. Sequence numbers are only meaningful inside their generation.

## In-process connection path

`InProcessAgentConnection` wraps an `AgentSessionRuntime` in the same process.

Use cases:

- Interactive fallback when daemon client runtime is disabled
- SDK embedding
- ACP, RPC, and print when constructed with a local runtime
- Local extension binding via `InteractiveModeLocalSessionHost` / `bindHeadlessExtensions`

Behavior notes:

- Snapshots are built directly with `createAgentConnectionSnapshot` / `createAgentConnectionState`
- Session rebind emits `session_replaced` and rebinds session event listeners
- Tool definitions are serializable metadata only (no execute/renderer callbacks)
- Resource snapshots include remote-friendly `AgentConnectionArtifactReference` ids
- There is no daemon command journal or chunked socket transfer; process lifetime is the reliability domain

Executable callbacks and renderers never cross the generic `AgentConnection` boundary; they stay on local service hosts when needed.

## Snapshot path

An `AgentConnectionSnapshot` is the coherent UI view of execution state:

| Field | Role |
| --- | --- |
| `state` | Cached `AgentConnectionState` (model, streaming, queues, goal, recap, …) |
| `messages` | Finalized transcript |
| `streamingMessage` | In-progress assistant message (separate from finalized messages) |
| `sessionContext` / `sessionTree` | Context and branch tree |
| `parent` / `children` | Parent metadata and live RLM child snapshots |
| `lastEventSequence` / `lastEventCursor` | Resume markers |
| `replay` | Attach-time replay coverage |

### Chunked transfer

Clients advertising `chunked_snapshot` receive large transcripts as:

1. `session_snapshot_begin` — snapshot without messages, `snapshotId`, counts, purpose (`attach` \| `replacement` \| `resync`)
2. `session_snapshot_chunk` — ordered message batches
3. `session_snapshot_end` — chunk count + final cursor
4. or `session_snapshot_failed`

`slim_attach` omits redundant top-level attach fields; clients read `snapshot.summary` / `snapshot.messages` instead.

### Transfer identity and idempotency

Supervisor snapshot cache rules:

- Transfer identity is the stable stream envelope (`snapshotId`, message count, target chunk bytes, last event sequence/cursor)
- Live summary fields (activity, attached client counts) can change without advancing transcript sequence and must not alone invalidate a completed transfer
- Duplicate chunks/end records for a completed matching transfer are accepted; mismatched completed transfers fail the cache
- Failed transfers free the client to request a fresh snapshot
- Worker disconnect during transfer fails the in-flight generation; clients recover via resync/reattach

Client assemblies time out when a stream is incomplete; dispose rejects open assemblies.

## Active session state

Daemon-side `ActiveSessionState` is the supervisor’s record for a live session:

| Field | Role |
| --- | --- |
| `activeSessionId` | Display-oriented runtime id (unique among active sessions) |
| `runtime` | `AgentSessionRuntime` owner |
| `clients` | Attached socket clients |
| `pendingAttaches` | In-flight attaches (busyness without event fan-out yet) |
| `extensionUiRequests` | Outstanding serializable UI request resolvers |
| `eventGeneration` / `lastEventSequence` | Event cursor domain |
| `summaryState` | Latest background status for agents-view labels |
| `clientEnv` | Adopted allowlisted client env for `pi.exec` subprocesses; never rebound after set |

### Selector resolution

`resolveActiveSessionState(sessions, selector)` resolves:

1. Direct `activeSessionId` map hit
2. Exact `sessionId` or `sessionName` (unique only)
3. Suffix match on active session id or session id (unique only)

Ambiguous matches throw `AmbiguousActiveSessionError`. Unknown selectors throw `Unknown active session: …`.

Socket clients track attach/catch-up/backpressure:

- Events can be dropped while backpressured and replaced with one catch-up snapshot on drain
- Replacement catch-up takes precedence over ordinary resync for the same client/session
- Snapshot streaming can be aborted per session

### Multi-client attach

Multiple clients may attach to the same active session (primary TUI, agents-view open, subagent watchers). Switching to a session already owned by another resident worker reattaches; client-owned headless workers do not silently transfer ownership.

## Agents-view mode

`runAgentsViewMode` is the fleet browser for daemon-backed sessions.

Characteristics:

- Requires a daemon socket (`socketPath`)
- Lists live + passive + saved sessions with search, scopes, and slash commands (`name`, `kill`, …)
- Opening a row attaches with `DaemonAgentConnection.attach` when `activeSessionId` exists
- If the active runtime is gone but a session file remains, `create` with `sessionPath` resumes; create-with-path is **idempotent for that client** (already-resident sessions are reused)
- Missing original `cwd` falls back to the agents-view config cwd with a notice
- Opened chats set `returnToAgentsView: true` and `agentsViewOwnsStartupNotices: true`
- Scope frames support parent/child navigation (scoped agents view / scope back with optional return chat)
- Session crash tears down the chat TUI cleanly, then returns to the list with a status message

Without the daemon, `prime-agent agents` cannot run the full view; interactive fallback warns and opens a normal chat.

## Command lifecycle and mutation idempotency

Mutating daemon commands use a durable supervisor journal keyed by `clientId + commandId`:

| Journal status | Client result |
| --- | --- |
| `new` | Command is dispatched |
| `complete` | Recorded response is returned (safe retry) |
| `pending` | Response is **not** replayed; client gets `command_result_uncertain` |

Rules:

- Received is journaled **before** dispatch
- Missing result after crash is treated as uncertain, never blindly re-executed
- Clients acknowledge durable results so old journal rows can compact
- `AgentConnection` method promises are client convenience, not a general remote workflow state machine
- Attach is intentionally read-only and not fence-gated against idle eviction races; if the worker is gone, attach fails cleanly and the client can take the saved-session path

Prompt admission has its own cancellation statuses (`cancelled` \| `owned` \| `unknown` \| `unsupported`). Only confirmed `cancelled` is safely retryable as a fresh admission (`AgentConnectionPromptAdmissionError`).

## Extension and UI boundaries

Daemon-owned extensions may request serializable UI only (select, confirm, input, editor, notification, status, widget, title, editor-text). Clients validate and return serializable responses.

Never cross the connection:

- Tool `execute`, argument prep, custom renderers
- Extension runner callbacks
- Local completion functions
- Runtime / session-manager objects

Terminal rendering, keybindings, themes, clipboard, and local credential UI stay in `InteractiveModeUiServices` (or equivalent). If an action changes execution or persisted session state, it goes through `AgentConnection`.

Saved-session and import/export paths intentionally keep local filesystem shapes. Hosted transports should use opaque ids, string timestamps, and explicit artifact handles instead.

## Capability gating examples

| Capability | Client requirement |
| --- | --- |
| `side_question_transcript` | Required before multi-turn side-question previous turns |
| `transient_bash` | Required for unrecorded side-conversation bash + `runId` correlation |
| `prompt_admission_cancellation` | Required for signal-backed prompt cancel semantics |
| `heartbeat_catalog` / `heartbeat_management` | Heartbeat list/manage; degrade when absent |
| `model_catalog` | Full model catalog fetch |
| `client_owned_sessions` | Create/dispose owned workers |
| `delete_rlm_subagent` | Delete RLM subagent sessions |

Always check negotiated server capabilities before sending optional commands.

## Failure modes and verification

| Symptom | Likely cause | What to check |
| --- | --- | --- |
| `Unknown active session` | Stale id after worker passivation | Resume via session file / create-with-path |
| `Ambiguous active session` | Selector matches multiple live sessions | Use full active session id or unique name |
| `command_result_uncertain` | Mutation received then lost before durable result | Inspect external side effects; do not assume no-op or full success |
| Snapshot transfer failure | Worker disconnect or mismatched stream | Reattach; expect `session_resynced` |
| Reconnect timeout | Supervisor not recoverably starting | Daemon logs, `recoverDaemon` path, socket path |
| Agents view unavailable | No daemon / non-daemon interactive fallback | Start daemon path; avoid process-local-only startup if fleet UI is required |
| Extension UI missing | Client without `extension_ui` | Attach with interactive supportsExtensionUi |

Verification signals of a healthy daemon attach:

- Attach returns a snapshot (inline or completed stream)
- `getState()` / `getInitialSnapshot()` reflect session id and model
- Live events advance cursor within the same generation
- After forced socket loss with recovery, UI receives `connection_status` then `session_resynced`

## Related pages

<CardGroup cols={2}>
  <Card title="Run daemon-backed sessions" href="/daemon-sessions">
    Detach, reattach, resume selectors, and worker recovery.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, events, queues, and tree navigation.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and retained subagents across disconnects.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    RLM children, agent messaging, and multi-agent constraints.
  </Card>
  <Card title="ACP mode" href="/acp-mode">
    Editor-facing protocol mode and connection composition.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Resume selectors, worker recovery, and connection-mode failure probes.
  </Card>
</CardGroup>

---

## 20. ACP protocol reference

> ACP mode entrypoints, event types, meta fields, stop reasons, kernel features, and protocol-facing constraints from source and tests.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/20-acp-protocol-reference.md
- Generated: 2026-08-06T19:50:17.528Z

### Source Files

- `packages/coding-agent/docs/acp.md`
- `packages/coding-agent/src/modes/acp/acp-mode.ts`
- `packages/coding-agent/src/modes/acp/acp-events.ts`
- `packages/coding-agent/src/modes/acp/acp-meta.ts`
- `packages/coding-agent/src/modes/acp/acp-stop-reason.ts`
- `packages/coding-agent/test/acp-events.test.ts`

---
title: "ACP protocol reference"
description: "ACP mode entrypoints, event types, meta fields, stop reasons, kernel features, and protocol-facing constraints from source and tests."
---

ACP mode (`prime-agent --mode acp`) runs Prime Agent as an [Agent Client Protocol](https://agentclientprotocol.com) agent: JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout, implemented with `@agentclientprotocol/sdk` and an in-process `AgentConnection`. It does not shell out to RPC mode; IPython, subagents, autonomous gates, and related signals are mapped into standard `session/update` payloads or the reverse-domain `_meta` envelope `ai.primeintellect.prime-agent`.

## Entrypoints

| Surface | Path / symbol | Role |
|---|---|---|
| CLI | `prime-agent --mode acp` | Selects app mode `acp` via `--mode` (`text \| json \| rpc \| acp \| daemon`) |
| Main routing | `resolveAppMode` → `runAcpMode` / `runAcpModeWithConnection` | Cold process uses `runAcpMode(runtime)`; attach/connection path uses `runAcpModeWithConnection(connection)` |
| Module exports | `packages/coding-agent/src/modes/acp/` | `runAcpMode`, `runAcpModeWithConnection`, event mappers, meta helpers, stop-reason mapper |
| SDK dependency | `@agentclientprotocol/sdk` (^1.3.0) | NDJSON stream, `agent()` handler chain, `PROTOCOL_VERSION` |

```bash
prime-agent --mode acp
```

Optional CLI context used by cold-process tests: `--provider`, `--model`, `--no-session`, `--offline`, `--daemon-socket`. Stdin is **not** treated as piped prompt content in ACP mode; stdin is the protocol transport.

```ts
// packages/coding-agent/src/modes/acp/acp-mode.ts
export async function runAcpMode(runtimeHost: AgentSessionRuntime): Promise<never>
export async function runAcpModeWithConnection(
  connection: AgentConnection,
  options?: AcpModeOptions,
): Promise<never>
```

<ParamField body="bindHeadlessExtensions" type="() => Promise<void>">
Bind headless extensions once the connection is live (in-process). Invoked on the first successful `session/new` bind path.
</ParamField>

<ParamField body="stream" type="ReturnType of acp.ndJsonStream">
Transport override. Defaults to NDJSON over stdio. Tests inject an in-memory duplex pair.
</ParamField>

<ParamField body="ownStdout" type="boolean">
When `false` or when `stream` is supplied, the mode skips claiming process stdout. Production stdio path calls `takeOverStdout()` so stray writes go to stderr; protocol frames use `writeRawStdout`.
</ParamField>

## Transport

- **Framing:** one JSON-RPC message per line (NDJSON).
- **Direction:** client requests/notifications on stdin; agent responses and `session/update` notifications on stdout.
- **Lifetime:** stdin stays open for the connection; agent exits when the transport closes (stdin EOF). With an injected `stream`, the mode returns instead of calling `process.exit`.
- **Diagnostics:** stderr only. Protocol traffic must never share stdout with logs.

```mermaid
sequenceDiagram
  participant Client as ACP client
  participant Stdio as NDJSON stdio
  participant Acp as runAcpModeWithConnection
  participant Conn as AgentConnection

  Client->>Stdio: initialize
  Stdio->>Acp: request
  Acp-->>Client: protocolVersion, agentCapabilities, agentInfo, _meta

  Client->>Acp: session/new
  Acp->>Conn: subscribe + bind extensions
  Acp-->>Client: sessionId (+ optional cwd _meta)

  Client->>Acp: session/prompt
  Acp->>Conn: promptAndWait + waitForHeadlessCompletion
  Conn-->>Acp: session_event / heartbeats_changed
  Acp-->>Client: session/update (chunks, tools, _meta)
  Acp-->>Client: stopReason

  Client->>Acp: session/cancel (notification)
  Acp->>Conn: abort

  Client->>Acp: session/close
  Acp->>Conn: unsubscribe + abort if needed
  Acp-->>Client: {}
```

## Supported methods

| Method | Kind | Behavior |
|---|---|---|
| `initialize` | request | Returns protocol version, capabilities, agent info, empty namespaced `_meta` |
| `session/new` | request | Creates the single session; binds headless extensions once; subscribes for session lifetime |
| `session/prompt` | request | Runs one turn; streams updates; resolves with `stopReason` or JSON-RPC error on failure |
| `session/cancel` | notification | Aborts only the addressed session’s in-flight turn |
| `session/close` | request | Unsubscribes, aborts in-flight work, frees the single-session slot |

### `initialize`

Returns:

| Field | Value / notes |
|---|---|
| `protocolVersion` | `acp.PROTOCOL_VERSION` from the SDK |
| `agentCapabilities.loadSession` | `false` |
| `agentCapabilities.promptCapabilities` | `{ image: true, embeddedContext: true }` |
| `agentCapabilities.sessionCapabilities` | `{ close: {} }` — client may release the session slot |
| `agentInfo` | `{ name: "prime-agent", title: "Prime Agent", version: VERSION }` |
| `_meta` | `{ "ai.primeintellect.prime-agent": {} }` — namespace advertised; no non-standard keys on the ACP object root |

### `session/new`

<ParamField body="cwd" type="string">
Client-requested working directory. Prime Agent’s cwd is fixed at process startup; a differing `cwd` does not change the agent directory. When the requested path is not the same canonical directory as the real agent cwd, the response includes `_meta.ai.primeintellect.prime-agent.cwd` with `requested` and `actual`.
</ParamField>

<ParamField body="mcpServers" type="array">
Accepted by clients in tests; not a separate ACP session-isolation surface in this mode.
</ParamField>

**Response:** `{ sessionId: string, _meta?: { "ai.primeintellect.prime-agent": { cwd?: { requested, actual } } } }`.

**Cwd comparison:** resolve + `realpathSync` when possible; Windows drive-letter normalization; optional `dev`/`ino` identity when both stats are trustworthy (non-zero). Symlink aliases that resolve to the same directory are not treated as a mismatch.

### `session/prompt`

<ParamField body="sessionId" type="string" required>
Must match the live ACP session.
</ParamField>

<ParamField body="prompt" type="array" required>
Content blocks. Supported types:

| Block `type` | Handling |
|---|---|
| `text` | Joined into the user text (newline-separated) |
| `image` | Requires `data` + `mimeType`; passed as model images |
| `resource` | Embedded `resource.text` (optional `uri` prefix) becomes text context |
| `resource_link` | `uri` string appended as text |

Image and embedded-context blocks are advertised in `initialize` and must reach the model (not dropped silently).
</ParamField>

**Success response:** `{ stopReason: AcpStopReason }`.

**Failure:** JSON-RPC error. Model/provider failures use `prime-agent turn failed: <errorMessage>` rather than a clean `end_turn` with zero updates. Cancellation maps to `{ stopReason: "cancelled" }` (not an error).

Turn outcome uses a pre-turn message boundary (object identity + content key of `role`/`timestamp`/`stopReason`/`errorMessage`) so auto-compaction rebuilds of `state.messages` cannot misattribute an earlier failure or hide this turn’s failure.

### `session/cancel`

Notification. Only cancels when `sessionId` matches **and** a turn abort controller is active. Stray cancels for other session IDs are ignored. Calls `connection.abort()` after marking the local controller aborted.

### `session/close`

Releases subscription, clears the single-session slot, and aborts the underlying agent if a turn is in flight (same abort path as cancel). Post-close activity must not produce further `session/update` notifications. A new `session/new` is then allowed on the same connection. Unknown `sessionId` or a second close of a released id is an error.

## Protocol constraints

| Constraint | Enforcement |
|---|---|
| One ACP session per connection | Second `session/new` throws: *“prime-agent ACP mode hosts one session per connection; start another prime-agent process for a second session”* |
| One prompt turn at a time | Concurrent `session/prompt` throws: *“A prompt turn is already running for this ACP session”* |
| Fixed cwd | Client `cwd` cannot rebind the agent; mismatch reported in `_meta` |
| No concurrent multi-session isolation | Underlying `AgentConnection` would share conversation, cwd, model, and queues if multiple ACP sessions were faked |
| Subscription lifetime | Subscribed for the session (not per turn) so fire-and-forget subagents still stream after the spawning turn |
| Stdout ownership | Non-interactive modes redirect `process.stdout.write` to stderr; ACP frames use the raw stdout escape hatch |
| Client disconnect | Abort in-flight turn, unsubscribe, dispose connection; stdio entrypoint exits `0` |
| Unknown session | Prompt/close for unknown id throws `Unknown ACP session: …` |

Autonomous quality gates run **inside** a single `session/prompt` turn. A failing gate is a continuation, not a stop reason; the request resolves only after `waitForHeadlessCompletion` settles. Gate attempts appear in `_meta` while that runs.

Unsolicited turns (heartbeat/cron, inbound agent messages, out-of-band `session.prompt`) still stream as `session/update` because the subscription is session-scoped.

## Streamed updates (`session/update`)

Agent → client notifications: `{ sessionId, update }`. Mapping is pure in `acpUpdatesForSessionEvent` (one session event → zero or more updates).

### Standard ACP update kinds

| Prime Agent source | `sessionUpdate` | Notes |
|---|---|---|
| Assistant `text_delta` | `agent_message_chunk` | `{ type: "text", text }` content |
| Assistant `thinking_delta` | `agent_thought_chunk` | Separate from visible text |
| `tool_execution_start` | `tool_call` | `status: "in_progress"` |
| `tool_execution_end` | `tool_call_update` | `completed` or `failed`; optional text content |
| `bash_start` | `tool_call` | Synthetic id `prime-agent-bash-<runId>` |
| `bash_output` | `tool_call_update` | Incremental chunk; correlates via mapping state |
| `bash_end` | `tool_call_update` | Failed on non-zero exit or cancel |
| Compaction, subagents, goals, refine, agent messages, heartbeats | `session_info_update` | Namespaced `_meta` only |

Empty deltas and non-assistant `message_update` events emit nothing. Events with no ACP place (for example `agent_start`, `recap_update`) emit nothing.

### Tool kind mapping (`acpToolKind`)

| Tool name | ACP `kind` |
|---|---|
| `ipython` | `execute` (title `"IPython cell"`, `rawInput: { code }`) |
| `bash` | `execute` |
| `read` | `read` |
| `edit`, `write` | `edit` |
| other | `other` |

Tool statuses: `pending` \| `in_progress` \| `completed` \| `failed`.

IPython is the model-facing tool. Cell source is the ACP `rawInput`. Rich kernel output (attachments, diffs under tool `details`) is **not** invented as a MIME bundle: text goes in standard content when present; attachment mime/path/decoded `bytes` and `diffCount` go under `_meta…ipython`. Base64 payloads are not duplicated into `_meta`.

Bash is outside the normal tool-call lifecycle. Mapping state tracks `activeBashRunId` so `bash_output` (no `runId`) attaches to the correct synthetic tool call.

### Heartbeats

Connection-level `heartbeats_changed` (not a session event) becomes:

```json
{
  "sessionUpdate": "session_info_update",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "heartbeatsChanged": true
    }
  }
}
```

## `_meta` namespace

**Namespace constant:** `PRIME_AGENT_META_NAMESPACE = "ai.primeintellect.prime-agent"`.

**Rule:** never put non-standard fields on ACP object roots; only under `_meta[namespace]`. Vanilla clients ignore `_meta`; Prime Agent-aware clients and harnesses read it.

```json
{
  "sessionUpdate": "session_info_update",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "subagents": [
        { "id": "sub-1", "sessionName": "reviewer", "status": "running" }
      ]
    }
  }
}
```

### Session meta fields

| Field | Shape | Emitted when |
|---|---|---|
| `cwd` | `{ requested, actual }` | `session/new` cwd mismatch |
| `heartbeatsChanged` | `boolean` | Heartbeat/cron schedule change |
| `goal` | `{ status, objective?, tokenBudget?, tokensUsed? }` | `goal_update` |
| `refinement` | `{ status: "complete"\|"failed", summary?, changes?, error? }` | `refine_complete` / `refine_failed` (`changes` lists applied edits only as `"action kind:id"`) |
| `agentMessage` | `{ toolCallId, target?, deliveryStatus? }` | Kernel sent agent-to-agent message |
| `sessionId` | `string` | Available on meta type (session-level use) |
| `rlmDepth` / `rlmMaxDepth` | numbers | Available on meta type |
| `compaction` | `{ tokensBefore?, summary? }` | `compaction_end` |
| `subagents` | array of `{ id, sessionName?, status, model?, depth?, tokenCount?, error? }` | `rlm_child_update` |
| `autonomous` | `{ enabled, continuationsUsed, turnsUsed, tokensUsed, gateAttempt?, gateFailure?, limitReason? }` | After headless completion of a prompt when autonomous mode is enabled |
| `ipython` | `{ attachments?: [{ mimeType?, path?, bytes? }], diffCount? }` | IPython tool end with rich `details` |

Helper: `primeAgentMeta(payload)` → `{ [namespace]: payload }`.

## Stop reasons

Type: `"end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled"`.

Mapper: `acpStopReason({ cancelled, autonomous? })`.

| Condition | `stopReason` |
|---|---|
| Local abort / `session/cancel` (and cancelled catch path) | `cancelled` (wins over autonomous state) |
| Autonomous disabled or no limit hit | `end_turn` |
| Autonomous `maxTokens` exhausted | `max_tokens` |
| Autonomous `maxContinuations`, `maxTurns`, or `timeoutMs` | `max_turn_requests` |
| (Type includes `refusal`; current mapper does not emit it) | — |

Autonomous default limits (core defaults, used when autonomous mode is configured): `maxContinuations: 3`, `maxTurns: 12`, `maxTokens: 80_000`, plus wall-clock `timeoutMs`. A stopped autonomous run must not be reported as a clean `end_turn` when a limit was hit.

Provider/model failures are **not** stop reasons: they reject the prompt request with an error payload that should include the underlying message (for example provider `401` text in cold-CLI coverage).

## Kernel and feature surface over ACP

ACP is a front end over the same session/kernel stack. Capabilities verified to remain representable:

| Capability | How it appears over ACP |
|---|---|
| IPython state across cells | Each cell → `tool_call` kind `execute` + `tool_call_update` with stdout text |
| Continual harness CRUD (`rlm.harness.*`) | Kernel still runs create/list/get/delete; refine outcomes → `_meta.refinement` |
| RLM depth / subagent list APIs | Kernel env and host handlers; lifecycle → `_meta.subagents` |
| Agent-to-agent messaging (`agent_message` skill) | Send receipt → `_meta.agentMessage`; inbound prompts still stream as message chunks |
| Autonomous gates | Loop inside one prompt; state → `_meta.autonomous` |
| Compaction | `_meta.compaction` (`session_info_update`) |
| Goals | `_meta.goal` |
| Out-of-band turns | Streamed without a client-initiated prompt |

## Error and recovery matrix

| Situation | Client observation |
|---|---|
| Unknown `sessionId` on prompt/close | JSON-RPC error |
| Second concurrent prompt | JSON-RPC error; first turn remains cancellable |
| Second `session/new` without close | JSON-RPC error |
| Cwd differs from agent cwd | Session still created; `_meta…cwd` present |
| Provider/auth/model failure this turn | Prompt rejects; not `end_turn` |
| Earlier failed turn still in transcript | Later turns use turn-boundary membership; slash/handled turns without a new assistant message still `end_turn` |
| Mid-turn compaction rebuild | Failure still detected via identity/key boundary, not pre-turn message count |
| Cancel wrong session | No-op; live turn continues |
| Close mid-turn | Underlying `requestAbort` / connection abort; slot freed |
| Client disconnect (stdin EOF) | Mode tears down; no further updates |

## Minimal client handshake

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/path/to/project","mcpServers":[]}}
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"<uuid>","prompt":[{"type":"text","text":"say hi"}]}}
```

Expected successful prompt result shape:

```json
{ "stopReason": "end_turn" }
```

Streamed text appears as intermediate `session/update` notifications with `update.sessionUpdate === "agent_message_chunk"`.

## When to use ACP vs other modes

| Mode | Fit |
|---|---|
| **ACP** | External client drives interactive turns (editors, evaluation harnesses); streaming tools + cancel |
| **JSON event stream** | Batch run: dump events and exit code |
| **RPC** | Prime Agent’s richer native command surface (not ACP-compatible) |

## Related pages

<CardGroup>
  <Card title="Use ACP mode" href="/acp-mode">
    Cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    In-process connection used by ACP, plus daemon and snapshot paths.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, runtime events, and session-scoped state that ACP maps outward.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    IPython control tool and `rlm(...)` subagents that surface through ACP `_meta`.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Provider 401s, auth recovery, and connection-mode failure probes that show up as prompt errors.
  </Card>
</CardGroup>

---

## 21. Minimal SDK agent

> Copy-paste minimal SDK bootstrap, custom prompt and model wiring, and expected first-run output from examples.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/21-minimal-sdk-agent.md
- Generated: 2026-08-06T19:50:59.064Z

### Source Files

- `packages/coding-agent/examples/sdk/01-minimal.ts`
- `packages/coding-agent/examples/sdk/README.md`
- `packages/coding-agent/examples/sdk/02-custom-model.ts`
- `packages/coding-agent/examples/sdk/03-custom-prompt.ts`
- `packages/coding-agent/examples/README.md`

---
title: "Minimal SDK agent"
description: "Copy-paste minimal SDK bootstrap, custom prompt and model wiring, and expected first-run output from examples."
---

`createAgentSession()` in `@earendil-works/pi-coding-agent` is the programmatic factory for a single `AgentSession`. With no options it discovers resources from `process.cwd()` and `~/.prime/agent`, resolves a model from settings or the first available credentialed model, enables the default built-in tool `ipython`, and returns `{ session, extensionsResult, modelFallbackMessage? }`.

The published TypeScript packages still use `@earendil-works/pi-*` package names. Those are API identifiers, not a hard dependency on an external monorepo layout. Provider credentials are BYOK: keys come from `AuthStorage` (runtime overrides, `auth.json`, env vars, or custom resolvers), not from a fixed hosted endpoint.

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Package | `npm install @earendil-works/pi-coding-agent` (SDK ships in the main package) |
| Runtime | Node with ESM; examples run via `npx tsx` |
| Auth | At least one provider credential so a model can be resolved |
| Working directory | Defaults to `process.cwd()` for project discovery and sessions |
| Agent config dir | Defaults to `~/.prime/agent` via `getAgentDir()`; override with `PRIME_AGENT_CODING_AGENT_DIR` |

Without a resolvable model, `createAgentSession()` still returns a session, but `modelFallbackMessage` is set to a “No models available” guidance string and `thinkingLevel` is forced to `"off"`. Prompting then fails at stream time when no API key/model is usable.

## Minimal bootstrap

Matches `packages/coding-agent/examples/sdk/01-minimal.ts`: all defaults, stream assistant text, then inspect message history.

```typescript
/**
 * Minimal SDK Usage
 *
 * Uses all defaults: discovers skills, extensions, tools, context files
 * from cwd and ~/.pi/agent. Model chosen from settings or first available.
 */

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);
});
console.log();
```

<Note>
Comment text in the example still mentions `~/.pi/agent`. Runtime config uses package `piConfig.configDir` (`.prime/agent`), so the effective default directory is `~/.prime/agent` unless `PRIME_AGENT_CODING_AGENT_DIR` overrides it.
</Note>

### What defaults do

When options are omitted, `createAgentSession()` wires:

| Surface | Default |
|---------|---------|
| `cwd` | `process.cwd()` (or session manager cwd if provided) |
| `agentDir` | `getAgentDir()` → `~/.prime/agent` or `PRIME_AGENT_CODING_AGENT_DIR` |
| `authStorage` | `AuthStorage.create()` under `agentDir/auth.json` when `agentDir` is set |
| `modelRegistry` | `ModelRegistry.create(authStorage, agentDir/models.json)` |
| `resourceLoader` | `DefaultResourceLoader` + `reload()` (skills, extensions, prompts, themes, context files) |
| `sessionManager` | `SessionManager.create(cwd, …)` (persistent under the session dir) |
| `settingsManager` | `SettingsManager.create(cwd, agentDir)` |
| `model` | Restored session model → settings default → first available |
| `thinkingLevel` | Restored session / settings / `DEFAULT_THINKING_LEVEL` (`"medium"`), then clamped to model capabilities |
| Default tools | Active tool name `ipython` when `tools` and `noTools` are unset |
| MCP | `McpManager` registered so user MCP providers and skill gates apply on the bare SDK path |

### Run the example

```bash
cd packages/coding-agent
npx tsx examples/sdk/01-minimal.ts
```

From an app that depends on the published package, import the same API and run your script with `tsx` or a built ESM entry.

## Expected first-run output

Output is provider- and model-dependent. Structure is stable:

1. **Streaming phase** — `message_update` events with `assistantMessageEvent.type === "text_delta"` write partial assistant text to stdout (no automatic newline after the stream).
2. **Completion** — `session.prompt()` resolves only after the accepted turn finishes (including tool rounds and retries).
3. **History dump** — `session.state.messages` (same history as `session.agent.state.messages` / `session.messages`) prints full message objects: user prompt, assistant message(s), and any tool-result messages if the model called `ipython`.

Typical successful terminal shape:

```text
<streamed assistant text about cwd contents>
{ role: 'user', content: '...', ... }
{ role: 'assistant', content: [...], ... }
# optional tool messages if tools ran
```

<Check>
Success signals: streaming text appears, `prompt()` resolves without throw, and `session.state.messages` contains at least one user and one assistant message. If `modelFallbackMessage` is set after create, fix credentials before treating a blank stream as success.
</Check>

### Failure modes on first run

| Symptom | Cause | Action |
|---------|--------|--------|
| `modelFallbackMessage` about no models | No credentialed model in registry/settings | Configure API key/OAuth under agent dir or env; see authentication docs |
| Auth / 401-style throw during stream | Invalid or missing key for the selected model | Update `auth.json` or `authStorage.setRuntimeApiKey(provider, key)` |
| Empty stdout, then dumped messages | No `text_delta` events (tool-only turn, or error path) | Inspect `session.state.messages` and subscribe to `tool_execution_*` / `agent_end` |
| Hang on tool | Default `ipython` tool needs a working kernel/runtime | Ensure IPython runtime prep for the environment, or restrict `tools` |

## Custom model wiring

Matches `packages/coding-agent/examples/sdk/02-custom-model.ts`.

```typescript
import { getModel } from "@earendil-works/pi-ai";
import { AuthStorage, createAgentSession, ModelRegistry } from "@earendil-works/pi-coding-agent";

const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);

// Built-in catalog lookup (does not require a key to exist)
const opus = getModel("anthropic", "claude-opus-4-5");
if (opus) {
  console.log(`Found model: ${opus.provider}/${opus.id}`);
}

// Custom / models.json entry
const customModel = modelRegistry.find("my-provider", "my-model");

// Only models with configured credentials
const available = await modelRegistry.getAvailable();
console.log(
  "Available models:",
  available.map((m) => `${m.provider}/${m.id}`),
);

if (available.length > 0) {
  const { session } = await createAgentSession({
    model: available[0],
    thinkingLevel: "medium", // off | minimal | low | medium | high | xhigh | max (clamped)
    authStorage,
    modelRegistry,
  });

  session.subscribe((event) => {
    if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
      process.stdout.write(event.assistantMessageEvent.delta);
    }
  });

  await session.prompt("Say hello in one sentence.");
  console.log();
}
```

### Model selection paths

| API | Role |
|-----|------|
| `getModel(provider, id)` from `@earendil-works/pi-ai` | Built-in model catalog entry |
| `modelRegistry.find(provider, id)` | Built-in + custom models from `models.json` |
| `await modelRegistry.getAvailable()` | Subset with valid API keys / OAuth |
| `createAgentSession({ model, thinkingLevel })` | Pin model and thinking for the session |

Thinking levels used by the session: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`. Values are clamped to what the selected model supports.

### Credential resolution (BYOK)

`AuthStorage` resolves keys in order:

1. Runtime overrides (`setRuntimeApiKey`) — not persisted  
2. Stored credentials in `auth.json` (API keys or OAuth)  
3. Provider environment variables  
4. Fallback resolvers for custom providers in `models.json`

Pass the same `authStorage` and `modelRegistry` into `createAgentSession` so stream auth matches model discovery.

## Custom system prompt

Matches `packages/coding-agent/examples/sdk/03-custom-prompt.ts`. Prompt text is owned by `DefaultResourceLoader`, not by a top-level `createAgentSession` string option.

### Replace the system prompt

```typescript
import {
  createAgentSession,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
} from "@earendil-works/pi-coding-agent";

const cwd = process.cwd();
const agentDir = getAgentDir();

const loader1 = new DefaultResourceLoader({
  cwd,
  agentDir,
  systemPromptOverride: () =>
    `You are a helpful assistant that speaks like a pirate.
Always end responses with "Arrr!"`,
  // Avoid appending APPEND_SYSTEM.md from agent/project dirs
  appendSystemPromptOverride: () => [],
});
await loader1.reload();

const { session: session1 } = await createAgentSession({
  resourceLoader: loader1,
  sessionManager: SessionManager.inMemory(),
});
```

### Append to the default prompt

```typescript
const loader2 = new DefaultResourceLoader({
  cwd,
  agentDir,
  appendSystemPromptOverride: (base) => [
    ...base,
    "## Additional Instructions\n- Always be concise\n- Use bullet points when listing things",
  ],
});
await loader2.reload();

const { session: session2 } = await createAgentSession({
  resourceLoader: loader2,
  sessionManager: SessionManager.inMemory(),
});
```

| Override | Behavior |
|----------|----------|
| `systemPromptOverride(base)` | Replace or transform the resolved base system prompt |
| `appendSystemPromptOverride(base)` | Replace or transform append segments (including discovered `APPEND_SYSTEM` sources) |
| `await loader.reload()` | **Required** before passing the loader into `createAgentSession` |
| `SessionManager.inMemory()` | Avoids writing a persistent session file while experimenting |

<Warning>
If you fully replace the system prompt but leave the default append discovery in place, project/user append files can still be merged. The replace example clears that with `appendSystemPromptOverride: () => []`.
</Warning>

## Core options used by minimal setups

<ParamField body="cwd" type="string">
Working directory for project-local discovery. Default: `process.cwd()`.
</ParamField>

<ParamField body="agentDir" type="string">
Global config directory. Default: `getAgentDir()` (`~/.prime/agent`).
</ParamField>

<ParamField body="authStorage" type="AuthStorage">
Credential store. Default: `AuthStorage.create(...)`.
</ParamField>

<ParamField body="modelRegistry" type="ModelRegistry">
Model catalog + availability. Default: `ModelRegistry.create(authStorage, ...)`.
</ParamField>

<ParamField body="model" type="Model">
Explicit model. Default: settings / first available / restored session model.
</ParamField>

<ParamField body="thinkingLevel" type="ThinkingLevel">
Default from settings or `"medium"`, then clamped to the model.
</ParamField>

<ParamField body="tools" type="string[]">
Allowlist of tool names. When omitted, default built-in is `ipython` unless `noTools` changes that.
</ParamField>

<ParamField body="resourceLoader" type="ResourceLoader">
Skills, extensions, prompts, themes, context. Default: `DefaultResourceLoader` with `reload()`.
</ParamField>

<ParamField body="sessionManager" type="SessionManager">
Persistence. Default: disk-backed under the session directory; use `SessionManager.inMemory()` for ephemeral runs.
</ParamField>

### Return value

<ResponseField name="session" type="AgentSession">
Live session: `prompt()`, `subscribe()`, model controls, `state` / `messages`, `dispose()` / `disposeAsync()`.
</ResponseField>

<ResponseField name="extensionsResult" type="LoadExtensionsResult">
Loaded extension metadata for UI or host wiring.
</ResponseField>

<ResponseField name="modelFallbackMessage" type="string | undefined">
Set when the preferred model could not be restored or no model is available.
</ResponseField>

## Event subscription (streaming)

Minimal scripts usually only handle `text_delta`. Full event surface for debugging first runs:

```typescript
session.subscribe((event) => {
  switch (event.type) {
    case "message_update":
      if (event.assistantMessageEvent.type === "text_delta") {
        process.stdout.write(event.assistantMessageEvent.delta);
      }
      break;
    case "tool_execution_start":
      console.log(`Tool: ${event.toolName}`);
      break;
    case "tool_execution_end":
      console.log(`Result: ${event.result}`);
      break;
    case "agent_end":
      console.log("Done");
      break;
  }
});
```

`subscribe` returns an unsubscribe function. Prefer `await session.disposeAsync()` when the process should tear down IPython/kernel state cleanly.

## Prompt entry point

```typescript
await session.prompt(text, options?);
```

Useful `PromptOptions` for embedders:

| Option | Default / notes |
|--------|-----------------|
| `expandPromptTemplates` | `true` — expand file-based prompt templates |
| `images` | Optional image attachments |
| `streamingBehavior` | Required if calling `prompt` while already streaming: `"steer"` or `"followUp"` |
| `source` | Input source for extension handlers; defaults to interactive |

`prompt()` resolves after the accepted run completes. Preflight rejection and stream-time failures surface as thrown errors or message/event stream content depending on stage.

## Verification checklist

<Steps>
  <Step title="Install and import">
    Depend on `@earendil-works/pi-coding-agent` and import `createAgentSession` (plus `AuthStorage` / `ModelRegistry` when wiring credentials explicitly).
  </Step>
  <Step title="Confirm credentials">
    Run with a configured provider, or call `await modelRegistry.getAvailable()` and assert `length > 0` before creating the session.
  </Step>
  <Step title="Run minimal prompt">
    `cd packages/coding-agent && npx tsx examples/sdk/01-minimal.ts` (or your app script).
  </Step>
  <Step title="Observe stream + history">
    Expect stdout text deltas, resolved `prompt()`, and non-empty `session.state.messages`.
  </Step>
  <Step title="Optional: pin model and prompt">
    Port patterns from `02-custom-model.ts` and `03-custom-prompt.ts`; always `await loader.reload()` for resource overrides.
  </Step>
</Steps>

## Scope of this page

This page covers the minimal path: bootstrap, model pin, system-prompt overrides, run command, and first-run signals. Skills, tools, extensions, context files, session persistence strategies, and full-control composition are separate SDK pages.

## Next

<CardGroup>
  <Card title="Skills, tools, and extensions" href="/sdk-skills-tools-extensions">
    Load skills, register tools and extensions, context files, and prompt templates from the SDK.
  </Card>
  <Card title="Sessions and full control" href="/sdk-sessions-control">
    Session managers, settings injection, runtime hooks, and the full-control composition example.
  </Card>
  <Card title="Authentication and providers" href="/authentication-providers">
    Login, API keys, OAuth, multi-provider selection, and BYOK boundaries.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, and dynamic provider updates.
  </Card>
  <Card title="Overview" href="/overview">
    CLI, SDK, and mode entry points for Prime Agent.
  </Card>
</CardGroup>

---

## 22. Skills, tools, and extensions

> SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/22-skills-tools-and-extensions.md
- Generated: 2026-08-06T19:49:20.776Z

### Source Files

- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/examples/sdk/05-tools.ts`
- `packages/coding-agent/examples/sdk/06-extensions.ts`
- `packages/coding-agent/examples/sdk/07-context-files.ts`
- `packages/coding-agent/examples/sdk/08-prompt-templates.ts`
- `packages/coding-agent/examples/extensions/subagent/agents.ts`

---
title: "Skills, tools, and extensions"
description: "SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring."
---

SDK composition for agent sessions centers on `createAgentSession` from `@earendil-works/pi-coding-agent`, with `DefaultResourceLoader` as the override surface for skills, extensions, AGENTS.md context files, and prompt templates. Tool allowlists pass as the `tools` option on `createAgentSession`; custom tools and commands register through the extension API (`pi.registerTool`, `pi.registerCommand`). Subagent discovery in the subagent extension sample loads markdown agent specs from user and project agent directories.

## Composition surface

| Surface | Primary API | Session attach path |
| --- | --- | --- |
| Skills | `DefaultResourceLoader` + `skillsOverride` | `createAgentSession({ resourceLoader })` |
| Tools (built-in / selected) | `createAgentSession({ tools })` | Same call; names match available tools |
| Custom tools / commands | Extension default export + `pi.registerTool` / `pi.registerCommand` | Via `resourceLoader` discovery or `extensionFactories` |
| Context files (`AGENTS.md`) | `agentsFilesOverride` + `getAgentsFiles()` | `createAgentSession({ resourceLoader })` |
| Prompt templates (`/name`) | `promptsOverride` + `getPrompts()` | `createAgentSession({ resourceLoader })` |
| Subagent specs | `discoverAgents(cwd, scope)` | Extension / multi-agent wiring (sample) |

Common imports across the SDK examples:

```ts
import {
  createAgentSession,
  createSyntheticSourceInfo,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
  type Skill,
  type PromptTemplate,
} from "@earendil-works/pi-coding-agent";
```

`SessionManager.inMemory()` (optionally with a custom cwd) is the in-process session backend used in these recipes. Call `await loader.reload()` after constructing a `DefaultResourceLoader` with overrides before reading discovered resources or creating the session.

```text
createAgentSession
├── tools: string[]                    # e.g. ["ipython"]
├── cwd?: string                       # applied when building built-in tools
├── resourceLoader?: DefaultResourceLoader
│     ├── skillsOverride
│     ├── agentsFilesOverride
│     ├── promptsOverride
│     ├── additionalExtensionPaths
│     └── extensionFactories
└── sessionManager: SessionManager.inMemory([cwd])
```

## Skills

Skills supply specialized instructions loaded into the system prompt. Discover, filter, merge, or replace them through `DefaultResourceLoader`.

### Discovery

After `reload()`, inspect:

```ts
const { skills: allSkills, diagnostics } = loader.getSkills();
```

The skills example discovers skills from paths including:

- `cwd/.pi/skills`
- `~/.pi/agent/skills`
- additional locations covered by the loader (“etc.” in the sample comment)

Non-empty `diagnostics` are logged as warnings in the sample.

### `Skill` shape (inline / synthetic)

| Field | Type / value in sample | Role |
| --- | --- | --- |
| `name` | string | Skill identifier |
| `description` | string | Human-readable summary |
| `filePath` | string (e.g. `/virtual/SKILL.md`) | Path identity |
| `baseDir` | string | Skill base directory |
| `sourceInfo` | from `createSyntheticSourceInfo(path, { source: "sdk" })` | Provenance for synthetic skills |
| `disableModelInvocation` | boolean | Model-invocation gate |
| `kind` | `"markdown"` | Skill kind in the sample |

### Override pattern

`skillsOverride` receives the current skill set and returns `{ skills, diagnostics }`:

```ts
const customSkill: Skill = {
  name: "my-skill",
  description: "Custom project instructions",
  filePath: "/virtual/SKILL.md",
  baseDir: "/virtual",
  sourceInfo: createSyntheticSourceInfo("/virtual/SKILL.md", { source: "sdk" }),
  disableModelInvocation: false,
  kind: "markdown",
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  skillsOverride: (current) => {
    const filteredSkills = current.skills.filter(
      (s) => s.name.includes("browser") || s.name.includes("search"),
    );
    return {
      skills: [...filteredSkills, customSkill],
      diagnostics: current.diagnostics,
    };
  },
});
await loader.reload();

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

<Note>
The sample filters discovered skills by name substrings (`browser`, `search`) then appends an inline skill. Preserve `current.diagnostics` unless you intentionally rewrite diagnostics.
</Note>

## Tools

Pass tool **names** to choose which built-in, extension, or custom tools are enabled. Names match against all available tools. Custom tools are not listed as static arrays here; they register via extensions with `pi.registerTool()` (see [Extensions](#extensions)).

### Session options

<ParamField body="tools" type="string[]" required>
Tool name allowlist. SDK samples use `["ipython"]` for the default and explicit IPython surfaces.
</ParamField>

<ParamField body="cwd" type="string">
When set, `createAgentSession()` applies that cwd when it builds the actual built-in tools. Pair with `SessionManager.inMemory(customCwd)` when the sample uses a custom project path.
</ParamField>

<ParamField body="sessionManager" type="SessionManager" required>
In the tools samples: `SessionManager.inMemory()` or `SessionManager.inMemory(customCwd)`.
</ParamField>

### Recipes

```ts
// Default / explicit IPython tool surface
await createAgentSession({
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(),
});

// Custom cwd
const customCwd = "/path/to/project";
await createAgentSession({
  cwd: customCwd,
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(customCwd),
});
```

<Info>
For custom tools, use the extensions system (`06-extensions.ts` pattern): register with `pi.registerTool()`, then enable by name through the same `tools` selection model once the tool is available to the session.
</Info>

## Extensions

Extensions intercept agent events and can register custom tools and commands. They are the unified path for extensions, custom tools, commands, and related hooks.

### Discovery defaults

By default, extension files are discovered from:

| Location | Notes |
| --- | --- |
| `~/.pi/agent/extensions/` | User agent dir |
| `<cwd>/.pi/extensions/` | Project-local |
| `settings.json` `"extensions"` array | Additional paths |

An extension is a TypeScript file that exports a default function:

```ts
export default function (pi: ExtensionAPI) { ... }
```

### SDK loader options

```ts
const resourceLoader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
  extensionFactories: [
    (pi) => {
      pi.on("agent_start", () => {
        console.log("[Inline Extension] Agent starting");
      });
    },
  ],
});
await resourceLoader.reload();

const { session } = await createAgentSession({
  resourceLoader,
  sessionManager: SessionManager.inMemory(),
});
```

| Option | Role |
| --- | --- |
| `additionalExtensionPaths` | Extra extension file paths |
| `extensionFactories` | Inline `(pi) => void` factories without a separate file |

### ExtensionAPI surfaces (sample)

#### Events

| Event | Handler signature in sample | Notes |
| --- | --- | --- |
| `agent_start` | `async () => { ... }` | Agent lifecycle start |
| `tool_call` | `async (event) => { ... }` | Logs `event.toolName`; may return `{ block: true, reason: "..." }` to block execution, or `undefined` to allow |
| `agent_end` | `async (event) => { ... }` | Receives `event.messages` (sample logs `event.messages.length`) |

#### `pi.registerTool`

```ts
pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "Does something useful",
  parameters: Type.Object({
    input: Type.String(),
  }),
  execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => ({
    content: [{ type: "text", text: `Processed: ${params.input}` }],
    details: {},
  }),
});
```

| Field | Role |
| --- | --- |
| `name` | Tool name used for selection / matching |
| `label` | Display label |
| `description` | Tool description |
| `parameters` | Schema object (`Type.Object` in the sample) |
| `execute` | Async runner; returns `{ content: [{ type: "text", text }], details }` |

#### `pi.registerCommand`

```ts
pi.registerCommand("mycommand", {
  description: "Do something",
  handler: async (args, ctx) => {
    ctx.ui.notify(`Command executed with: ${args}`);
  },
});
```

### Session stream hooks (consumer side)

Outside the extension file, the session returned by `createAgentSession` supports subscribe + prompt:

```ts
session.subscribe((event) => {
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});

await session.prompt("List files in the current directory.");
```

## Context files (`AGENTS.md`)

Context files inject project-specific instructions into the system prompt. Discovery walks up from `cwd` for `AGENTS.md` files. Override or extend with `agentsFilesOverride`.

### Override and discovery

```ts
const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  agentsFilesOverride: (current) => ({
    agentsFiles: [
      ...current.agentsFiles,
      {
        path: "/virtual/AGENTS.md",
        content: `# Project Guidelines

## Code Style
- Use TypeScript strict mode
- No any types
- Prefer const over let`,
      },
    ],
  }),
});
await loader.reload();

const discovered = loader.getAgentsFiles().agentsFiles;
// each entry: { path, content }

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

| API | Returns / accepts |
| --- | --- |
| `agentsFilesOverride(current)` | `{ agentsFiles: Array<{ path, content }> }` |
| `getAgentsFiles()` | `{ agentsFiles }` with `path` and `content` (length used for logging) |

To disable context files entirely, return an empty list from `agentsFilesOverride` (commented intent in the sample).

## Prompt templates

File-based templates inject content when invoked as `/templatename`. Discover from:

- `cwd/.pi/prompts/`
- `~/.pi/agent/prompts/`

### `PromptTemplate` shape

| Field | Sample value | Role |
| --- | --- | --- |
| `name` | `"deploy"` | Slash command name → `/deploy` |
| `description` | string | Listing text |
| `filePath` | e.g. `/virtual/prompts/deploy.md` | Path identity |
| `sourceInfo` | `createSyntheticSourceInfo(..., { source: "sdk" })` | Synthetic provenance |
| `content` | markdown string | Injected body |

### Override pattern

```ts
const deployTemplate: PromptTemplate = {
  name: "deploy",
  description: "Deploy the application",
  filePath: "/virtual/prompts/deploy.md",
  sourceInfo: createSyntheticSourceInfo("/virtual/prompts/deploy.md", { source: "sdk" }),
  content: `# Deploy Instructions

1. Build: npm run build
2. Test: npm test
3. Deploy: npm run deploy`,
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  promptsOverride: (current) => ({
    prompts: [...current.prompts, deployTemplate],
    diagnostics: current.diagnostics,
  }),
});
await loader.reload();

const discovered = loader.getPrompts().prompts;
// log: `/${template.name}: ${template.description}`

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

## Subagent extension wiring

The subagent extension sample (`packages/coding-agent/examples/extensions/subagent/agents.ts`) discovers markdown agent configs for multi-agent use. It depends on `getAgentDir` and `parseFrontmatter` from `@earendil-works/pi-coding-agent`.

### Types

```ts
export type AgentScope = "user" | "project" | "both";

export interface AgentConfig {
  name: string;
  description: string;
  tools?: string[];
  model?: string;
  systemPrompt: string;
  source: "user" | "project";
  filePath: string;
}

export interface AgentDiscoveryResult {
  agents: AgentConfig[];
  projectAgentsDir: string | null;
}
```

### Directory layout

| Source | Path resolution |
| --- | --- |
| User agents | `path.join(getAgentDir(), "agents")` |
| Project agents | Walk upward from `cwd` until a directory `.prime/agent/agents` exists; nearest wins |

Only `.md` files that are regular files or symbolic links are loaded. Missing dirs and unreadable files are skipped silently.

### Frontmatter contract

`parseFrontmatter` is applied to each file. Required frontmatter fields:

- `name`
- `description`

Optional:

- `tools` — comma-separated string, trimmed, empty tokens dropped → `tools?: string[]`
- `model` — string

The markdown body becomes `systemPrompt`. Files missing `name` or `description` are skipped.

### `discoverAgents(cwd, scope)`

```ts
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult
```

| `scope` | Behavior |
| --- | --- |
| `"user"` | Only user agents |
| `"project"` | Only project agents (if project dir found) |
| `"both"` | User first, then project; **same name is overwritten by project** (Map set order) |

Returns `{ agents, projectAgentsDir }` where `projectAgentsDir` is the discovered project path or `null`.

### Listing helper

```ts
formatAgentList(agents, maxItems): { text: string; remaining: number }
```

- Empty list → `{ text: "none", remaining: 0 }`
- Otherwise formats up to `maxItems` as `name (source): description` joined by `"; "`, with `remaining` count for overflow.

### Extension package layout (sample)

```text
packages/coding-agent/examples/extensions/subagent/
└── agents.ts    # discovery + AgentConfig + formatAgentList
```

Wire discovered agents into your extension’s tool or spawn surface using the returned `AgentConfig` fields (`tools`, `model`, `systemPrompt`, `source`, `filePath`). The sample file itself only implements discovery and listing.

## ResourceLoader checklist

Use this order for every override-based recipe:

<Steps>
  <Step title="Construct loader">
    `new DefaultResourceLoader({ cwd, agentDir: getAgentDir(), ...overrides })`
  </Step>
  <Step title="Reload">
    `await loader.reload()`
  </Step>
  <Step title="Inspect (optional)">
    `getSkills()`, `getAgentsFiles()`, or `getPrompts()` and handle `diagnostics` where returned
  </Step>
  <Step title="Create session">
    `await createAgentSession({ resourceLoader: loader, sessionManager: SessionManager.inMemory() })` — add `tools` / `cwd` as needed
  </Step>
</Steps>

### Override callback return shapes

| Override | Return object |
| --- | --- |
| `skillsOverride` | `{ skills, diagnostics }` |
| `agentsFilesOverride` | `{ agentsFiles }` |
| `promptsOverride` | `{ prompts, diagnostics }` |

Synthetic resources should use `createSyntheticSourceInfo(path, { source: "sdk" })` for `sourceInfo` when defining inline skills or prompt templates.

## Example index

| Example path | Topic |
| --- | --- |
| `packages/coding-agent/examples/sdk/04-skills.ts` | Skills filter/merge + synthetic skill |
| `packages/coding-agent/examples/sdk/05-tools.ts` | `tools: ["ipython"]`, custom cwd |
| `packages/coding-agent/examples/sdk/06-extensions.ts` | Extension paths, factories, events, registerTool/Command |
| `packages/coding-agent/examples/sdk/07-context-files.ts` | AGENTS.md discovery + override |
| `packages/coding-agent/examples/sdk/08-prompt-templates.ts` | `/name` prompt templates |
| `packages/coding-agent/examples/extensions/subagent/agents.ts` | Subagent markdown discovery and scope |

## Constraints and failure modes

| Area | Constraint from samples |
| --- | --- |
| Tool selection | Names must match available tools; custom tools come from extensions, not a separate `createAgentSession` custom-tool field |
| Extension `tool_call` | Return `{ block: true, reason }` to block; `undefined` allows |
| Context disable | Empty `agentsFiles` via `agentsFilesOverride` |
| Subagent frontmatter | No `name` or `description` → file ignored |
| Subagent tools frontmatter | Comma-separated; empty after trim → `tools` omitted |
| Scope `"both"` | Project agent **wins** on name collision |
| Project agents path | `.prime/agent/agents` (not `.pi/...`) while skills/prompts/extensions samples use `.pi` under cwd and agent dir |
| Diagnostics | Skills and prompts overrides preserve `current.diagnostics` in samples |

## Related pages

<CardGroup>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap `createAgentSession`, custom prompt and model wiring, first-run shape.
  </Card>
  <Card title="Sessions and full control" href="/sdk-sessions-control">
    Session management, settings injection, runtime hooks, full-control composition.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Skills as packages, SKILL.md constraints, collision precedence, project vs personal scope.
  </Card>
  <Card title="Create and install skills" href="/create-skills">
    Author skill packages, required frontmatter, load-path verification.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog of shipped skills and invocation roles.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Register extensions, custom tools, plan-mode and subagent samples, allowlists.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    Child agents, agent-message surface, multi-agent orchestration constraints.
  </Card>
</CardGroup>

---

## 23. Sessions and full control

> SDK session management, settings injection, session runtime hooks, and the full-control composition example.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/23-sessions-and-full-control.md
- Generated: 2026-08-06T19:51:29.924Z

### Source Files

- `packages/coding-agent/examples/sdk/11-sessions.ts`
- `packages/coding-agent/examples/sdk/12-full-control.ts`
- `packages/coding-agent/examples/sdk/13-session-runtime.ts`
- `packages/coding-agent/examples/sdk/10-settings.ts`
- `packages/coding-agent/src/core/agent-session-runtime.ts`

---
title: "Sessions and full control"
description: "SDK session management, settings injection, session runtime hooks, and the full-control composition example."
---

The SDK surface for programmatic agents is `createAgentSession()` for a single session, and `createAgentSessionRuntime()` when the host must replace the active session (`newSession`, `switchSession`, `fork`, `importFromJsonl`). Session persistence is owned by `SessionManager`; settings by `SettingsManager`; credentials and models by `AuthStorage` / `ModelRegistry`; discovery by `ResourceLoader`. Package imports use `@earendil-works/pi-coding-agent` and `@earendil-works/pi-ai` (API identifiers for the published packages).

## Two entry points

| Entry | Use when | Returns |
|-------|----------|---------|
| `createAgentSession(options?)` | One session for the process lifetime | `{ session, extensionsResult, modelFallbackMessage? }` |
| `createAgentSessionRuntime(factory, options)` | Host needs session replacement without restarting | `AgentSessionRuntime` with `.session`, `.services`, `.diagnostics` |

`createAgentSession` defaults:

| Option | Default |
|--------|---------|
| `cwd` | `process.cwd()` (or `sessionManager.getCwd()`) |
| `agentDir` | `getAgentDir()` → `~/.prime/agent` (overridable via env) |
| `authStorage` | `AuthStorage.create(agentDir/auth.json)` |
| `modelRegistry` | `ModelRegistry.create(authStorage, agentDir/models.json)` |
| `sessionManager` | `SessionManager.create(cwd)` |
| `settingsManager` | `SettingsManager.create(cwd, agentDir)` |
| `resourceLoader` | `DefaultResourceLoader` + `reload()` |
| `tools` | built-in `ipython` unless `tools` / `noTools` override |

For replaceable sessions, split **cwd-bound services** from the session:

```typescript
const createRuntime: CreateAgentSessionRuntimeFactory = async ({
  cwd, sessionManager, sessionStartEvent,
}) => {
  const services = await createAgentSessionServices({ cwd });
  return {
    ...(await createAgentSessionFromServices({
      services,
      sessionManager,
      sessionStartEvent,
    })),
    services,
    diagnostics: services.diagnostics,
  };
};

const runtime = await createAgentSessionRuntime(createRuntime, {
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  sessionManager: SessionManager.create(process.cwd()),
});
```

`createAgentSessionServices` rebuilds auth, settings, model registry, resource loader, and MCP manager for the effective cwd. `createAgentSessionFromServices` builds the `AgentSession` against those services.

## SessionManager factories

`SessionManager` controls whether history is persisted and which file is active.

| Factory | Persistence | Behavior |
|---------|-------------|----------|
| `SessionManager.inMemory(cwd?)` | None | Ephemeral; `session.sessionFile` is unset |
| `SessionManager.create(cwd, sessionDir?)` | New file under session dir | Fresh session; optional custom `sessionDir` (no cwd encoding required) |
| `SessionManager.continueRecent(cwd, sessionDir?)` | Most recent for cwd, else new | Resume path for “continue last” |
| `SessionManager.open(path, sessionDir?, cwdOverride?)` | Existing path | Load header cwd (or override) |
| `SessionManager.openAsync(path, ...)` | Same as `open` | Non-blocking parse for daemon/large files |
| `SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)` | New file in target | Copy history into another project cwd |

List sessions for a cwd:

```typescript
const sessions = await SessionManager.list(process.cwd());
// SessionInfo: path, id, cwd, name?, state?, parentSessionPath?,
// rlmDepth, created, modified, messageCount, firstMessage, ...
```

`SessionManager.listAll()` scans the global sessions root. Cross-project fork uses `forkFrom`.

### Example: persistence modes

From `packages/coding-agent/examples/sdk/11-sessions.ts`:

```typescript
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";

// In-memory
const { session: inMemory } = await createAgentSession({
  sessionManager: SessionManager.inMemory(),
});

// New persistent file
const { session: newSession } = await createAgentSession({
  sessionManager: SessionManager.create(process.cwd()),
});

// Continue most recent (or create)
const { session: continued, modelFallbackMessage } = await createAgentSession({
  sessionManager: SessionManager.continueRecent(process.cwd()),
});
if (modelFallbackMessage) console.log("Note:", modelFallbackMessage);

// Open listed session
const sessions = await SessionManager.list(process.cwd());
if (sessions.length > 0) {
  const { session: opened } = await createAgentSession({
    sessionManager: SessionManager.open(sessions[0].path),
  });
}
```

<Note>
Restored sessions may surface `modelFallbackMessage` when the saved provider/model is unavailable or unauthenticated. Treat it as a host warning, not a hard failure.
</Note>

## Settings injection

`SettingsManager` merges **global** (`agentDir`) and **project** (cwd) settings, then optional runtime overrides.

| Factory | I/O | Use |
|---------|-----|-----|
| `SettingsManager.create(cwd, agentDir?)` | File-backed | Production / CLI-aligned |
| `SettingsManager.inMemory(partial?)` | None | Tests and hermetic hosts |
| `SettingsManager.fromStorage(storage)` | Custom backend | Alternate storage |

Common methods:

| Method | Effect |
|--------|--------|
| `applyOverrides(partial)` | Deep-merge into in-memory effective settings (not automatically a durable write of every key) |
| `setDefaultThinkingLevel(level)` | Updates memory and queues persistence |
| `flush()` | Awaits the write queue (durability boundary) |
| `drainErrors(scope?)` | Returns `{ scope, error }[]` then clears |
| `getGlobalSettings()` / `getProjectSettings()` | Cloned snapshots |
| `reload()` | Re-read storage; clears modification tracking |

Settings keys used in SDK examples include:

| Key | Nested fields (examples) | Defaults (when unset) |
|-----|--------------------------|------------------------|
| `compaction` | `enabled`, `reserveTokens`, `keepRecentTokens`, `agentCallable` | compaction enabled by default |
| `retry` | `enabled`, `maxRetries`, `baseDelayMs`, `provider.*` | retry enabled; `maxRetries` 3; `baseDelayMs` 2000 |
| `defaultThinkingLevel` | — | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "max"` |

### Example: override + flush

From `packages/coding-agent/examples/sdk/10-settings.ts`:

```typescript
const settingsManager = SettingsManager.create(cwd);
settingsManager.applyOverrides({
  compaction: { enabled: false },
  retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
});

await createAgentSession({
  settingsManager,
  sessionManager: SessionManager.inMemory(),
});

settingsManager.setDefaultThinkingLevel("low");
await settingsManager.flush();

const settingsErrors = settingsManager.drainErrors();
for (const { scope, error } of settingsErrors) {
  console.warn(`Warning (${scope} settings): ${error.message}`);
}
```

<Warning>
Setters update memory immediately and queue writes. Call `flush()` before process exit when durability matters. Surface `drainErrors()` at the app layer; load failures do not throw from constructors.
</Warning>

## Full-control composition

Full control means **no filesystem discovery**: supply auth, models, settings, tools, system prompt, and a custom `ResourceLoader` so extensions, skills, prompts, themes, and Agents files are empty or explicit.

From `packages/coding-agent/examples/sdk/12-full-control.ts`:

```typescript
import { getModel } from "@earendil-works/pi-ai";
import {
  AuthStorage,
  createAgentSession,
  createExtensionRuntime,
  ModelRegistry,
  type ResourceLoader,
  SessionManager,
  SettingsManager,
} from "@earendil-works/pi-coding-agent";

const authStorage = AuthStorage.create("/tmp/my-agent/auth.json");
if (process.env.MY_ANTHROPIC_KEY) {
  authStorage.setRuntimeApiKey("anthropic", process.env.MY_ANTHROPIC_KEY); // not persisted
}

const modelRegistry = ModelRegistry.inMemory(authStorage);
const model = getModel("anthropic", "claude-sonnet-5");
if (!model) throw new Error("Model not found");

const settingsManager = SettingsManager.inMemory({
  compaction: { enabled: false },
  retry: { enabled: true, maxRetries: 2 },
});

const resourceLoader: ResourceLoader = {
  getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
  getSkills: () => ({ skills: [], diagnostics: [] }),
  getPrompts: () => ({ prompts: [], diagnostics: [] }),
  getThemes: () => ({ themes: [], diagnostics: [] }),
  getAgentsFiles: () => ({ agentsFiles: [] }),
  getSystemPrompt: () => `You are a minimal assistant.\nAvailable: ipython. Be concise.`,
  getAppendSystemPrompt: () => [],
  extendResources: () => {},
  reload: async () => {},
};

const { session } = await createAgentSession({
  cwd: process.cwd(),
  agentDir: "/tmp/my-agent",
  model,
  thinkingLevel: "off",
  authStorage,
  modelRegistry,
  resourceLoader,
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(process.cwd()),
  settingsManager,
});
```

`ResourceLoader` contract:

| Method | Role |
|--------|------|
| `getExtensions()` | Extensions + `createExtensionRuntime()` |
| `getSkills()` / `getPrompts()` / `getThemes()` | Discovered resources + diagnostics |
| `getAgentsFiles()` | Context files (e.g. AGENTS.md) |
| `getSystemPrompt()` / `getAppendSystemPrompt()` | System prompt text |
| `extendResources(paths)` | Runtime path injection |
| `reload()` | Refresh after disk changes |

Auth notes for BYOK hosts:

- `AuthStorage.create(path?)` — file-backed credentials (default `~/.prime/agent/auth.json`).
- `AuthStorage.inMemory(data?)` — no disk.
- `setRuntimeApiKey(provider, key)` — process-local override; **not** written to disk.
- Prefer explicit provider keys from env or your secret store; do not hardcode a hosted gateway.

## Session runtime replacement

`AgentSessionRuntime` owns the current `AgentSession` plus cwd-bound `AgentSessionServices`. Replacement methods tear down the current runtime, create the next one via the stored factory, then rebind. If creation fails after teardown starts, the error propagates; hosts own user-facing recovery.

### Runtime methods

| Method | Shutdown reason | Start reason | Notes |
|--------|-----------------|--------------|-------|
| `newSession({ parentSession?, setup?, withSession? })` | `new` | `new` | Optional parent linkage; `setup` mutates the new manager before finish |
| `switchSession(path, { cwdOverride?, withSession? })` | `resume` | `resume` | Opens path; asserts session cwd exists |
| `fork(entryId, { position?, withSession? })` | `fork` | `fork` | `position`: `"before"` (default) or `"at"`; may return `selectedText` |
| `importFromJsonl(inputPath, cwdOverride?)` | `resume` | `resume` | Copies into session dir if needed; throws `SessionImportFileNotFoundError` if missing |
| `dispose()` | `quit` | — | Idempotent; flushes traces, disposes subagent runtimes, releases lease |

Return shape for switch/new/import: `{ cancelled: boolean }`. Fork also returns `{ selectedText?: string }`.

Cancellation: extensions can cancel via `session_before_switch` or `session_before_fork` (`result.cancel === true`).

### Lifecycle events

| Event | When | Cancellable |
|-------|------|-------------|
| `session_before_switch` | Before new/resume | Yes |
| `session_before_fork` | Before fork | Yes |
| `session_shutdown` | Before invalidating current session | No |
| `session_start` | After new session is applied | No |

`session_start.reason`: `"startup" | "reload" | "new" | "resume" | "fork"`.  
`session_shutdown.reason`: `"quit" | "reload" | "new" | "resume" | "fork"`.

Replacement also:

1. Emits `session_shutdown` to the extension runner  
2. Flushes agent trace upload (best-effort on teardown)  
3. Runs optional `setBeforeSessionInvalidate` (sync UI teardown)  
4. Awaits `session.disposeAsync()` (kernel snapshot flush)  
5. Disposes hosted RLM subagent runtimes  
6. Applies the new session; commits session lease  
7. Calls `setRebindSession` / `onSessionReplaced` listeners / `withSession`

Metadata on the runtime (`AgentSessionRuntimeMetadata`) includes `kind: "top-level" | "subagent"`, parent ids, RLM child id, spawn prompt, and `rehydratedCompleted`.

### Required rebind pattern

After any replacement, **do not keep stale subscriptions** against the old `AgentSession`. Rebind to `runtime.session`:

```typescript
// packages/coding-agent/examples/sdk/13-session-runtime.ts
let unsubscribe: (() => void) | undefined;

async function bindSession() {
  unsubscribe?.();
  const session = runtime.session;
  await session.bindExtensions({});
  unsubscribe = session.subscribe((event) => {
    if (event.type === "session_action_update") {
      console.log(
        "Queued:",
        event.actions.steering.length + event.actions.followUps.length,
      );
    }
  });
  return session;
}

let session = await bindSession();
await runtime.newSession();
session = await bindSession();

if (originalSessionFile) {
  await runtime.switchSession(originalSessionFile);
  session = await bindSession();
}

unsubscribe?.();
await runtime.dispose();
```

<Tip>
Hosts can also use `runtime.setRebindSession(fn)` or `runtime.onSessionReplaced(listener)` so every replacement path rebinds automatically.
</Tip>

### Architecture

```mermaid
flowchart TB
  subgraph Host["Host process"]
    Factory["CreateAgentSessionRuntimeFactory"]
    RT["AgentSessionRuntime"]
  end

  subgraph Services["AgentSessionServices cwd-bound"]
    Auth["AuthStorage"]
    SM["SettingsManager"]
    MR["ModelRegistry"]
    RL["ResourceLoader"]
    MCP["McpManager"]
  end

  subgraph Session["Active AgentSession"]
    SessMgr["SessionManager"]
    Ext["ExtensionRunner"]
    Sub["RLM subagent runtimes"]
  end

  Factory --> RT
  RT --> Services
  RT --> Session
  Factory -->|"new / switch / fork / import"| Services
  RT -->|"teardown + apply"| Session
```

## Session events for hosts

Subscribe on `AgentSession` (not only runtime):

```typescript
session.subscribe((event) => {
  switch (event.type) {
    case "message_update":
      if (event.assistantMessageEvent.type === "text_delta") {
        process.stdout.write(event.assistantMessageEvent.delta);
      }
      break;
    case "session_action_update":
      // steering + followUps queues
      break;
    case "tool_execution_start":
    case "tool_execution_end":
    case "agent_end":
      break;
  }
});

await session.prompt("List files in the current directory.");
```

## Run the examples

From the monorepo package:

```bash
cd packages/coding-agent
npx tsx examples/sdk/10-settings.ts
npx tsx examples/sdk/11-sessions.ts
npx tsx examples/sdk/12-full-control.ts
npx tsx examples/sdk/13-session-runtime.ts
```

| File | Focus |
|------|--------|
| `10-settings.ts` | Disk + in-memory settings, `applyOverrides`, `flush`, `drainErrors` |
| `11-sessions.ts` | `inMemory` / `create` / `continueRecent` / `list` / `open` |
| `12-full-control.ts` | Explicit auth, model, empty discovery loader, tool allowlist |
| `13-session-runtime.ts` | Factory, `newSession`, `switchSession`, rebind, `dispose` |

## Constraints and failure modes

| Case | Behavior |
|------|----------|
| Missing import path | `importFromJsonl` throws `SessionImportFileNotFoundError` |
| Unresolvable session cwd | `assertSessionCwdExists` fails on open/switch/import |
| Invalid fork entry | `Error("Invalid entry ID for forking")` |
| Extension cancels switch/fork | Method returns `{ cancelled: true }`; session unchanged |
| Session creation fails mid-replace | Error propagates; uncommitted lease released |
| No models configured | `modelFallbackMessage` may describe missing models; clears once `session.model` is set |
| Settings parse errors | Collected via `drainErrors()`; empty settings used for that scope |
| Concurrent writers | Session leases (`acquireSessionLease`) protect replacement paths |

## Verification checklist

<Check>
After `SessionManager.create`, `session.sessionFile` is a real path; after `inMemory`, it is unset.
</Check>
<Check>
After `runtime.newSession()`, `runtime.session` is a new object; re-subscribe and `bindExtensions` again.
</Check>
<Check>
Full-control loaders return empty skills/extensions; only listed `tools` are available.
</Check>
<Check>
`settingsManager.flush()` completes without errors, or `drainErrors()` reports scoped failures.
</Check>
<Check>
`await runtime.dispose()` ends cleanly and is safe to call twice.
</Check>

## Related pages

<CardGroup cols={2}>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap `createAgentSession` with defaults and first prompt.
  </Card>
  <Card title="Skills, tools, and extensions" href="/sdk-skills-tools-extensions">
    Resource loaders, custom tools, and extension factories.
  </Card>
  <Card title="Session configuration reference" href="/session-configuration">
    Full settings keys, defaults, and reload behavior.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Lifecycle, queueing, tree navigation, session-scoped vs durable state.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, OAuth, and 401 recovery.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    Daemon, in-process, and snapshot connection paths.
  </Card>
</CardGroup>

---

## 24. Troubleshooting

> Auth failures, invalid resume selectors, provider 401s, network retry, worker recovery, and connection-mode failure probes.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/24-troubleshooting.md
- Generated: 2026-08-06T19:52:50.771Z

### Source Files

- `packages/coding-agent/test/suite/regressions/4435-auth-error-login-guidance.test.ts`
- `packages/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts`
- `packages/coding-agent/test/suite/regressions/4722-invalid-resume-selector.test.ts`
- `packages/coding-agent/test/suite/regressions/4491-provider-stale-after-401.test.ts`
- `packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts`
- `packages/coding-agent/docs/agent-connection.md`

---
title: "Troubleshooting"
description: "Auth failures, invalid resume selectors, provider 401s, network retry, worker recovery, and connection-mode failure probes."
---

Runtime failures in Prime Agent surface on three boundaries: provider auth and stream errors on the agent session, saved-session resume selectors at CLI startup, and local daemon/worker transport under `AgentConnection`. Most user-facing recovery paths are `/login`, retry settings, `--resume` / Agents View, and `prime-agent doctor` / `shutdown --force`.

## Failure map

| Symptom | Boundary | Primary signals | First recovery |
|---------|----------|-----------------|----------------|
| `401` / `403`, invalid API key, expired token | Provider stream → `AgentSession` | `stopReason: "error"`, optional `provider_stream_failure` diagnostic, `auth_stale` | `/login`, then re-prompt |
| `Network connection lost.` or other transient provider errors | Auto-retry | `auto_retry_start` / `auto_retry_end` | Wait for backoff; adjust `retry.*` if exhausted |
| `No session found matching '…'` / ambiguous resume | CLI session resolver | `SessionSelectorNotFoundError`, `SessionSelectorAmbiguousError` | Use suggested ID, suffix, or left-arrow session browser |
| Lost daemon socket, session closed, stale supervisor | `DaemonAgentConnection` | `connection_status`, `closed`, session closed reasons | Reopen from Agents View; `list` / `attach`; `doctor` / `shutdown --force` |
| Worker crash / supervisor generation fence | Resident worker recovery | New worker descriptor, `supervisor_generation_stale`, `session_resynced` | Let recovery finish; reattach; do not replay uncertain mutations |

## Auth failures and login guidance

When an assistant message ends with `stopReason: "error"` and the error text looks like authentication failure, the session appends:

```text
Run /login to update credentials.
```

Classification treats a message as authentication-related when it matches any of:

- status tokens `401` or `403`
- phrases such as `unauthorized`, `forbidden`, `invalid API key`, `authentication failed`, `expired` / `invalid token`, `access denied`, `permission denied`

If the error already contains `/login`, guidance is not duplicated.

### Startup / credential-missing messages

| Situation | Message pattern | Action |
|-----------|-----------------|--------|
| No models resolve | `No models available.` + provider login help | `/login` or set a provider API key |
| Model selected but no key | `No API key found for <provider>.` | `/login` or env / `auth.json` for that provider |
| Explicit auth failure | `Authentication failed for "<provider>"…` | `/login` |

Provider login help points at the local docs under the install’s `providers.md` and `models.md`, and at interactive `/login` (OAuth or API key). Credentials live in `~/.prime/agent/auth.json` (mode `0600`); auth-file entries take priority over environment variables.

```bash
# Interactive recovery
/login
/model

# Or BYOK via environment (provider-specific)
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export PRIME_API_KEY=...
prime-agent
```

<Check>
After a successful `/login`, re-send the prompt. Auth status for the provider should no longer report `source: "stale"` / `label: "expired"`.
</Check>

## Provider 401s and stale credentials

Concrete provider auth failures are detected from:

1. Structured diagnostics: `diagnostics[]` entry with `type: "provider_stream_failure"` and `details.kind: "auth"` with status `401` or `403`
2. Bare error text patterns such as `401 status code (no body)` or `401 Unauthorized` combined with auth-related wording

Stream failures are classified into kinds including `auth`, `rate_limit`, `overloaded`, `server_error`, `invalid_request`, `refusal`, `safety`, and `unknown`. Permanent structured kinds (`auth`, `invalid_request`, `refusal`) are not retried indefinitely after the first retry attempt is already in progress.

### Auth stale lifecycle

```text
provider returns 401/403
        │
        ▼
capture AuthSourceToken for current provider source
        │
        ├─ retry.enabled ──► one auto-retry (auth) then mark stale if still failing
        │
        └─ retry disabled / cancelled / exhausted ──► mark auth stale immediately
        │
        ▼
auth_stale event
authStorage: configured=false, source="stale", label="expired"
API key lookup returns undefined for that source
errorMessage includes "Run /login to update credentials."
```

Behavior under repeated 401s (with default-style retry):

| Condition | Observed behavior |
|-----------|-------------------|
| Structured `auth` 401 with retries enabled | At most one auto-retry for that auth failure class; then `auth_stale` and credentials cleared for the failed source |
| Bare `401 status code (no body)` (e.g. `prime-inference`) | Still classified as auth; emits `auth_stale` with `sourceTokens` (often `source: "runtime"`) for daemon clients |
| Retry cancelled during backoff (`abortRetry`) | `auto_retry_end` with `finalError: "Retry cancelled"`; captured auth sources still marked stale |
| Credentials replaced mid-backoff | Prior failed source tokens are marked stale; fresh key is not left as the dead source |
| Final error after 401 then non-auth (e.g. 500) | Captured auth sources still go stale; final assistant error may be the non-auth text but still gets login guidance when stale-mark runs |
| `retry.enabled: false` | No `auto_retry_start`; single request; concrete auth failure still marks stale |

<Warning>
Stale means the runtime will not keep using the failed credential source. Update credentials with `/login` (or a new env / auth-file key) before expecting further provider calls for that provider.
</Warning>

## Network and auto-retry

Agent-level auto-retry runs on `agent_end` when the last assistant message is retryable (or a still-eligible concrete auth failure). Transient text such as `Network connection lost.` is retryable: the session emits `auto_retry_start`, strips the failed assistant message from live agent state (history retains it), waits with exponential backoff, then calls `agent.continue()`.

### Retry settings

Configure under global or project settings (`~/.prime/agent/settings.json` or `.prime/agent/settings.json`):

| Key | Type | Default | Role |
|-----|------|---------|------|
| `retry.enabled` | boolean | `true` | Master switch for agent-level auto-retry |
| `retry.maxRetries` | number | `3` | Maximum agent-level attempts after the first failure |
| `retry.baseDelayMs` | number | `2000` | Backoff base: delay = `baseDelayMs * 2^(attempt-1)` (2s, 4s, 8s, …) |
| `retry.provider.timeoutMs` | number | SDK default | Provider/SDK request timeout |
| `retry.provider.maxRetries` | number | SDK default | Provider/SDK-level retries (separate from agent-level) |
| `retry.provider.maxRetryDelayMs` | number | `60000` | Cap on server-requested retry delay; longer delays fail immediately. `0` disables the cap |

```json
{
  "retry": {
    "enabled": true,
    "maxRetries": 3,
    "baseDelayMs": 2000,
    "provider": {
      "timeoutMs": 3600000,
      "maxRetries": 0,
      "maxRetryDelayMs": 60000
    }
  }
}
```

### What is not auto-retried

| Case | Reason |
|------|--------|
| Context overflow | Handled by compaction, not retry |
| Faux provider queue exhausted (`No more faux responses queued`) | Terminal fixture state |
| `agent_lifecycle_failure` diagnostics | Not a provider transient error |
| Structured permanent provider failure after a retry already started | Avoids spinning on `auth` / `invalid_request` / `refusal` |
| Successful assistant message mid-turn | Retry counter resets on non-error assistant messages |

### Events (interactive, JSON, RPC, SDK)

```json
{
  "type": "auto_retry_start",
  "attempt": 1,
  "maxAttempts": 3,
  "delayMs": 2000,
  "errorMessage": "Network connection lost."
}
```

```json
{
  "type": "auto_retry_end",
  "success": true,
  "attempt": 1
}
```

On final failure, `success` is `false` and `finalError` carries the last error text (or `Retry cancelled` when aborted). RPC exposes `set_auto_retry` to toggle the agent-level switch at runtime.

## Invalid resume selectors

`prime-agent --resume <selector>` and related session open paths resolve selectors through the session resolver (not the daemon attach name path).

### Selector resolution order

1. Path-like selector (`/`, `\`, or `.jsonl` suffix) → open as path  
2. Exact normalized ID match in local project sessions  
3. Exact match in all sessions  
4. Unique prefix/suffix partial match (local, then all)  
5. Otherwise `SessionSelectorNotFoundError`, optionally with a closest-ID suggestion  

Normalization strips hyphens and lowercases IDs. Hex session list UIs often show a 12-character suffix; that suffix is accepted when it uniquely matches.

### Errors and CLI output

| Error | When | User-facing recovery |
|-------|------|----------------------|
| `SessionSelectorNotFoundError` | No match | Message `No session found matching '<selector>'`. If a single closest ID is within edit distance ≤ `max(1, floor(len/5))` and not tied, CLI adds `Did you mean '<id>'?` |
| `SessionSelectorAmbiguousError` | Multiple prefix/suffix matches | Lists matching IDs (and names when present); no auto-pick |
| Missing `--resume` value misuse | Flag without selector where required | Browse with left-arrow / session picker |

```bash
# Mistyped ID → exit 1 with suggestion when confident
prime-agent --resume <almost-right-id>

# Accept the 12-char suffix shown in session lists when unique
prime-agent --resume aaaaaaaaaaaa

# Open picker then send a prompt after selection
prime-agent --resume -- "continue this work"

# Continue most recent without a selector
prime-agent --continue
```

Exact normalized ID wins over partial prefix/suffix matches. Suggestions are suppressed for short selectors (`< 4` normalized chars), ties, or low-confidence distances.

Interactive fallback: open Prime Agent and use left-arrow (or `/resume`) to browse sessions. Daemon Agents View requires the daemon path (not `--no-daemon`).

## Worker recovery and daemon process faults

Resident interactive sessions run in isolated workers under a detached supervisor. Closing the TUI detaches the client; it does not stop the worker.

### Worker crash recovery

- A worker crash is scoped to one root session tree.  
- Recovery retries after **250 ms**, **1 s**, and **5 s**; three failures mark that root failed.  
- Recovery reaps the old process group, appends a visible recovery marker to the transcript, restores the root under the same active-session ID, and does **not** replay uncertain side effects.  
- Only the **current supervisor generation** may replace a crashed resident worker; obsolete generations are fenced.

### Generation fencing

Workers authenticate to the supervisor with a per-worker token and the current supervisor generation. Commands from a displaced generation fail with `supervisor_generation_stale` and must not insert public journal entries for the old owner.

### Client recovery commands

```bash
prime-agent list
prime-agent attach <agent>
prime-agent agents
prime-agent status
prime-agent doctor
prime-agent doctor --fix
prime-agent stop <agent>
prime-agent shutdown
prime-agent shutdown --force
```

| Command | Use when |
|---------|----------|
| `list` / `attach` | Reattach to a live resident worker after client disconnect |
| `status` | Inspect background services |
| `doctor` / `doctor --fix` | Diagnose or repair service / socket state (including orphan sockets) |
| `shutdown --force` | Stop supervisor + workers; required for incompatible/stale daemons that refuse idle replacement, and for unresponsive process groups |

`shutdown --force` serializes shutdown admission and reclaims unrenewed live leases so concurrent force-shutdowns do not race destructively.

## Connection-mode failure probes

`AgentConnection` is the UI/SDK client boundary. Interactive mode normally uses `DaemonAgentConnection`; SDK and explicit fallbacks may use `InProcessAgentConnection`. Failure behavior differs by transport.

### Daemon version probe

Startup/connect probes the local socket (`probeDaemonVersion`):

| Status | Meaning |
|--------|---------|
| `absent` | No daemon accepted a connect within the short probe timeouts |
| `current` | Hello matches client protocol version, schema id, and app version |
| `stale` | Connected but version/schema/app mismatch, or no recognizable hello |

A stale daemon that cannot be auto-replaced (busy sessions) raises `StaleDaemonError` with both daemon and client identity lines and instructs:

```bash
prime-agent shutdown --force
```

then retry the original command.

### Daemon process inventory statuses

`daemon ps` / related discovery classifies sockets as:

| Status | Meaning |
|--------|---------|
| `current` | Compatible live daemon |
| `stale` | Live but version-incompatible |
| `unreachable` | Socket present but not healthy; force shutdown may be required |
| `orphan-file` | Socket file without a live daemon; safe to remove with doctor/fix paths |

### Connection closed / session closed messages

`DaemonAgentConnection` formats fatal messages with session ID, session file (when known), and a diagnostic log path under `~/.prime/agent/logs/` (or the default agent log).

| Situation | Message intent |
|-----------|----------------|
| Socket loss without recovery hook | `Lost connection to the Prime Agent daemon…` transcript remains; restart / Agents View |
| Session `killed` / `shutdown` / `completed` / `replaced` / `update` | Reason-specific closed text + reopen guidance |
| Update reconnect timeout | Daemon restarted for update; window failed to restore before timeout |
| Snapshot transfer failure | Failed attach/resync snapshot recovery with snapshot and recovery causes |

With `recoverDaemon` configured (normal interactive path), transient socket loss triggers bounded reconnect: stable client identity + last event cursor `{ generation, sequence }`, reattach, then `session_resynced` / replacement snapshot. Generation changes invalidate bare sequence comparison; missing replay is non-fatal—the attach snapshot is the durable baseline.

### Mutation idempotency on reconnect

Mutating daemon commands are journaled by `clientId + commandId`:

- Completed command replay returns the stored result  
- Received-but-not-durable results report **uncertain** and are not blindly re-executed  
- Clients acknowledge durable results so journals can compact  

Do not assume a client method promise is a general remote workflow API; treat uncertain results as requiring operator inspection of session state.

### In-process vs daemon

| Mode | Failure surface |
|------|-----------------|
| `DaemonAgentConnection` | Socket, supervisor, worker generation, snapshot streaming, journal |
| `InProcessAgentConnection` | Same process as runtime; no daemon reconnect/journal—errors are local runtime/provider failures |

## Diagnostic artifacts

| Artifact | Location / command | When useful |
|----------|--------------------|-------------|
| Daemon / worker / client / provider logs | `~/.prime/agent/logs/` | Connection closed messages cite the path |
| Agent default log | `getAgentLogPath()` under agent dir | Message-handler and recovery noise |
| TUI layout debug | Hidden `/debug` → `~/.prime/agent/prime-agent-debug.log` | Rendering / width issues (not provider auth) |
| Session JSONL | `~/.prime/agent/sessions/` | Resume path, transcript after worker recovery |
| Settings | `~/.prime/agent/settings.json`, project `.prime/agent/settings.json` | Retry and related knobs |
| Auth store | `~/.prime/agent/auth.json` | Credential presence after `/login` |

## Quick recovery procedures

<Steps>
  <Step title="Provider 401 or auth_stale">
    Confirm the assistant error includes `Run /login to update credentials.` Run `/login` for the provider (OAuth or API key), select a model with `/model` if needed, and re-prompt. For BYOK, update the provider env var or `auth.json` entry and restart if the process still holds a stale runtime key.
  </Step>
  <Step title="Network / transient stream errors">
    Leave `retry.enabled` true and wait for `auto_retry_*` cycles. If retries exhaust, raise `retry.maxRetries` or lower `retry.baseDelayMs` only after confirming the provider is healthy. Cap long server-requested delays with `retry.provider.maxRetryDelayMs`.
  </Step>
  <Step title="Resume selector rejected">
    Copy the suggested session ID from the error, use the list’s 12-character suffix when unique, or run `prime-agent --resume` / left-arrow browse. For ambiguous prefixes, lengthen the selector until one session matches.
  </Step>
  <Step title="Lost daemon connection or stuck workers">
    Run `prime-agent list` and `prime-agent attach <agent>`. If services are unhealthy, `prime-agent doctor` then `doctor --fix`. For incompatible or stuck daemons, `prime-agent shutdown --force`, then start again. Transcripts remain on disk under the sessions directory.
  </Step>
</Steps>

## Related pages

<CardGroup>
  <Card title="Authentication and providers" href="/authentication-providers">
    Login paths, API keys, OAuth, and BYOK boundaries.
  </Card>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, and 401 stale-provider recovery.
  </Card>
  <Card title="Run daemon-backed sessions" href="/daemon-sessions">
    Detach/reattach, resume selectors, and worker recovery verification.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    Daemon vs in-process adapters, snapshots, and reconnect semantics.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, events, and session-scoped vs durable state.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Config keys including retry-related session surfaces.
  </Card>
</CardGroup>

---

## 25. Develop from source

> Monorepo workspaces, build and check scripts, package boundaries (ai, agent, coding-agent, tui), and contributor conventions from AGENTS.md.

- Page Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/pages/25-develop-from-source.md
- Generated: 2026-08-06T19:56:30.568Z

### Source Files

- `package.json`
- `AGENTS.md`
- `packages/coding-agent/README.md`
- `packages/agent/README.md`
- `packages/ai/README.md`
- `packages/tui/README.md`

---
title: "Develop from source"
description: "Monorepo workspaces, build and check scripts, package boundaries (ai, agent, coding-agent, tui), and contributor conventions from AGENTS.md."
---

Prime Agent is an npm workspaces monorepo rooted at `package.json`. Source packages live under `packages/*` with inherited npm names `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui`. The public product, CLI, and release artifact are `prime-agent`. Local development runs through `./prime-agent.sh` (tsx on `packages/coding-agent/src/cli.ts`) or `./prime-agent.sh --dist` after a full build.

<Note>
Release docs and install scripts use Prime Agent product names. Workspace manifests still keep inherited `@earendil-works/pi-*` identifiers, a source `pi` bin entry, and some `PI_*` compatibility env vars until the namespace migration completes. Do not treat those inherited names as the public install path.
</Note>

## Prerequisites

| Requirement | Constraint |
|-------------|------------|
| Node.js | `>=22.8.0` (root `engines`; coding-agent enforces this at CLI entry) |
| npm | Prefer npm `>=11.10` so `.npmrc` `min-release-age=7` is enforced |
| Git | Required for clone and `PRIME_AGENT_BUILD_ID` from `git describe` |
| Python | Kernel runtime expects Python `>=3.10` with `ipykernel` (auto-bootstrap or `PRIME_AGENT_KERNEL_PYTHON`) |

Dependency updates resolve only to packages published at least 7 days ago (`min-release-age=7` in `.npmrc`). For an urgent security patch younger than 7 days:

```bash
npm install --min-release-age=0 <pkg>
```

## Clone and install

<Steps>
  <Step title="Clone and install workspaces">
```bash
git clone https://github.com/PrimeIntellect-ai/prime-agent
cd prime-agent
npm ci
```

Workspaces include `packages/*` plus selected coding-agent extension examples:

- `packages/coding-agent/examples/extensions/with-deps`
- `packages/coding-agent/examples/extensions/custom-provider-anthropic`
- `packages/coding-agent/examples/extensions/custom-provider-gitlab-duo`
- `packages/coding-agent/examples/extensions/sandbox`
  </Step>
  <Step title="Run from source against a project cwd">
```bash
/path/to/prime-agent/prime-agent.sh
```

The launcher can be invoked from any directory and preserves the caller’s working directory, so a source checkout can target a separate test project.
  </Step>
  <Step title="Verify the toolchain">
```bash
npm run check
```

`npm run check` runs Biome (format/lint with `--write --error-on-warnings`), `tsgo --noEmit`, installer render checks, and browser smoke checks. It does **not** run the test suite.
  </Step>
</Steps>

## Run from source

`prime-agent.sh` is the primary local entrypoint.

| Mode | Behavior |
|------|----------|
| Default | `tsx packages/coding-agent/src/cli.ts` |
| `--dist` | `node packages/coding-agent/dist/bundle/cli.js` (bundled build; fails if bundle missing) |
| `--no-env` | Unsets common provider API key / OAuth env vars for auth-path testing |

```bash
# Dev path (tsx)
./prime-agent.sh

# Release-like path after build
npm run build
./prime-agent.sh --dist

# Isolated config (avoids colliding with daily sessions)
PRIME_AGENT_CODING_AGENT_DIR=/tmp/prime-agent-dev ./prime-agent.sh
```

Success signals:

- Interactive TUI starts in the current cwd (or daemon-backed session attaches).
- Missing deps fail fast: `tsx not found … Run npm install from the repo root first` or `Bundle not found … Run npm run build first`.

### Config directories

| Scope | Default path | Override |
|-------|--------------|----------|
| User config | `~/.prime/agent/` | `PRIME_AGENT_CODING_AGENT_DIR` (compat: `PI_CODING_AGENT_DIR`) |
| Sessions | under agent dir | `PRIME_AGENT_SESSION_DIR` |
| Project config | `.prime/agent/` in project root | — |
| Package assets | resolved via `getPackageDir()` in `packages/coding-agent/src/config.ts` | `PI_PACKAGE_DIR` |

Use `getPackageDir()`, `getThemeDir()`, and related helpers for package assets. Do not resolve themes, skills, or docs from raw `__dirname` when packaging must work for source, Node dist, and standalone binaries.

## Monorepo layout

:::files
prime-agent/
├── package.json              # workspaces, root scripts, engines
├── AGENTS.md                 # contributor rules (source of truth for agents)
├── prime-agent.sh            # source launcher
├── prime-agent-runtime/      # Python kernel shim (ipykernel, rlm)
├── scripts/                  # release, pack, check, kernel bootstrap helpers
├── packages/
│   ├── ai/                   # @earendil-works/pi-ai — LLM providers & models
│   ├── agent/                # @earendil-works/pi-agent-core — agent loop runtime
│   ├── tui/                  # @earendil-works/pi-tui — terminal UI primitives
│   └── coding-agent/         # @earendil-works/pi-coding-agent — CLI, daemon, RLM
│       ├── src/              # CLI, modes, core services
│       ├── skills/           # built-in Python skills
│       ├── examples/sdk/     # SDK samples
│       ├── test/             # vitest suites + suite harness
│       └── docs/             # in-repo product docs
└── .npmrc                    # min-release-age=7
:::

## Package boundaries

```mermaid
flowchart TB
  subgraph app["Application"]
    CA["coding-agent<br/>@earendil-works/pi-coding-agent<br/>CLI · daemon · sessions · RLM"]
  end
  subgraph libs["Libraries"]
    AG["agent<br/>@earendil-works/pi-agent-core<br/>Agent loop · state · events"]
    AI["ai<br/>@earendil-works/pi-ai<br/>Providers · stream · models"]
    TUI["tui<br/>@earendil-works/pi-tui<br/>TUI components · editor"]
  end
  subgraph py["Python"]
    RT["prime-agent-runtime<br/>kernel rlm shim"]
  end
  CA --> AG
  CA --> AI
  CA --> TUI
  AG --> AI
  CA --> RT
```

| Package | Workspace name | Role | Depends on |
|---------|----------------|------|------------|
| `packages/ai` | `@earendil-works/pi-ai` | Unified LLM API, provider streams, model catalog, OAuth helpers | Provider SDKs |
| `packages/agent` | `@earendil-works/pi-agent-core` | Stateful agent runtime (`Agent`, loop, events, tool handling) | `pi-ai` |
| `packages/tui` | `@earendil-works/pi-tui` | Differential-render TUI primitives (Editor, Markdown, overlays) | chalk, marked, optional koffi |
| `packages/coding-agent` | `@earendil-works/pi-coding-agent` | Product CLI, interactive/RPC/JSON/ACP modes, daemon/worker, skills, harness | `pi-ai`, `pi-agent-core`, `pi-tui` |
| `prime-agent-runtime` | Python project | Kernel-side recursion shim (`rlm`, skills, harness helpers) | ipykernel, nest-asyncio, tyro |

Release packaging rewrites coding-agent name/bin/config for distribution (`scripts/pack-prime-agent-release.mjs`). In-tree, `piConfig` sets product name `prime-agent` and config dir `.prime/agent`, while the source `bin` entry remains `pi` pointing at `dist/bundle/cli.js`.

Root TypeScript path aliases in `tsconfig.json` map workspace packages to `packages/*/src` for typecheck without requiring a prior emit.

## Root scripts

| Script | What it does |
|--------|----------------|
| `npm run build` | Ordered: `tui` → `ai` → `agent` → `coding-agent` |
| `npm run clean` | `clean` in each workspace |
| `npm run dev` | Concurrent watch builds for all four packages via `concurrently` |
| `npm run check` | Biome + `tsgo --noEmit` + installer + browser-smoke |
| `npm run test` | `npm run test --workspaces --if-present` |
| `npm run release:patch` / `release:minor` / `release:major` | Lockstep release via `scripts/release.mjs` |
| `npm run release:pack` | Pack versioned release artifact |

Coding-agent package scripts of note:

| Script | Purpose |
|--------|---------|
| `build` | `tsgo` build, asset copy (themes, skills, `prime-agent-runtime`), esbuild bundle |
| `test` | vitest full package |
| `test:ci` | Kernel bootstrap + vitest excluding process supervisor stress |
| `test:process` | Daemon supervisor process tests |
| `test:kernel` | Kernel-heavy ACP/state tests (serialized) |

CI (`.github/workflows/ci.yml`) runs `npm ci` → `npm run build` → `npm run check`, then matrix tests per package (coding-agent sharded `test:ci` with uv for Python).

## Validation workflow

Contributor rule from `AGENTS.md`: after code changes (not pure docs), run `npm run check` and fix all errors, warnings, and infos before committing. Husky pre-commit runs `npm run check` and restages formatter-touched files.

<Warning>
Root agent rules intentionally restrict automated agents from running `npm run dev`, `npm run build`, and full `npm test` unless the user instructs otherwise. Humans developing locally still use those scripts as needed. Prefer focused package tests during iteration.
</Warning>

### Focused tests

Run tests from the **package root**, not the monorepo root:

```bash
cd packages/coding-agent
npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts
```

Rules:

- If you create or modify a test file, run that file until it passes.
- Coding-agent suite tests under `test/suite/` use `test/suite/harness.ts` and the **faux** provider — not real provider APIs, keys, or paid tokens.
- Issue regressions: `packages/coding-agent/test/suite/regressions/<issue-number>-<short-slug>.test.ts`.

Optional full offline suite driver: root `test.sh` moves `~/.prime/agent/auth.json` aside, unsets provider env keys, and runs workspace tests without live credentials.

## Contributor conventions

`AGENTS.md` is the binding contributor contract for this repository.

### Code quality

- No `any` unless unavoidable; prefer real types from installed packages.
- No inline/dynamic imports for modules or types — use top-level static imports.
- Do not downgrade code to silence type errors from outdated deps; upgrade the dependency.
- Do not hardcode key matches (`matchesKey(keyData, "ctrl+x")`). Keybindings must be configurable with defaults in `DEFAULT_EDITOR_KEYBINDINGS` or `DEFAULT_APP_KEYBINDINGS`.
- Never edit `packages/ai/src/models.generated.ts` by hand. Change `packages/ai/scripts/generate-models.ts` and regenerate via the ai package build/`generate-models` script.
- Do not preserve backward compatibility unless explicitly requested.
- Ask before removing intentional behavior.

### Parallel-agent git safety

Multiple agents may share a worktree. Commit only files you changed in the current session:

```bash
git status
git add packages/ai/src/providers/transform-messages.ts packages/ai/CHANGELOG.md
git commit -m "fix(ai): description"
```

Forbidden without extreme care: `git add -A` / `git add .`, `git reset --hard`, `git checkout .`, `git clean -fd`, `git stash`, `git commit --no-verify`.

Include `fixes #<n>` / `closes #<n>` when closing issues via commit.

### Changelogs

Each package has `packages/*/CHANGELOG.md`. New bullets go only under `## [Unreleased]` as past-tense one-liners (Added/Changed/Fixed/Removed). Do not edit released version sections.

### Issue labels

Use `pkg:agent`, `pkg:ai`, `pkg:coding-agent`, and/or `pkg:tui` on GitHub issues.

### Daemon protocol changes

Wire changes must be classified as backward-compatible, capability-gated, or incompatible:

- Optional features: negotiate server capability; clients must check before use.
- Incompatible or startup-required behavior: bump `DAEMON_PROTOCOL_VERSION`.
- Every wire change: update `DAEMON_SCHEMA_REVISION`, command/event compatibility maps, and both new-client/old-daemon and old-client/new-daemon tests.
- Optional metadata/UI must degrade locally; never block agent start or attachment.
- Do not require a new daemon command at startup without a protocol or capability gate.

### Adding an LLM provider

High-level surface (details in `AGENTS.md`):

1. Types in `packages/ai/src/types.ts` (`Api`, options, `KnownProvider`).
2. Provider implementation under `packages/ai/src/providers/`.
3. Subpath export in `packages/ai/package.json`, type re-exports, lazy registration in `register-builtins.ts`, credentials in `env-api-keys.ts`.
4. Model generation in `packages/ai/scripts/generate-models.ts`.
5. Provider matrix tests under `packages/ai/test/`.
6. Coding-agent wiring: `model-resolver.ts`, `provider-display-names.ts`, CLI env docs, providers docs.
7. `packages/ai` README + CHANGELOG unreleased entry.

Providers are BYOK/BYOC: callers supply API keys or OAuth; the monorepo does not hard-require a hosted Prime-only model.

### Versioning and release

All packages share one version (lockstep). Semantics used by the project:

| Bump | Meaning |
|------|---------|
| `patch` | Bug fixes and new features |
| `minor` | API breaking changes |

Release scripts finalize changelogs, commit, tag, and publish. Prefer updating `[Unreleased]` as you land work.

## Local debugging surfaces

| Surface | Location / command |
|---------|-------------------|
| Hidden `/debug` log | `~/.prime/agent/prime-agent-debug.log` |
| Daemon/client/provider logs | `~/.prime/agent/logs/` |
| Service status | `prime-agent status` (or `./prime-agent.sh status`) |
| Repair | `prime-agent doctor` / `doctor --fix` |
| Shutdown | `prime-agent shutdown` |

Interactive TUI smoke via tmux (from `AGENTS.md`): create a fixed-size session, send `./prime-agent.sh`, capture the pane, send keys — avoid killing unrelated tmux sessions on shared hosts.

## Format and typecheck toolchain

| Tool | Config | Role |
|------|--------|------|
| Biome | `biome.json` | Lint + format (tabs, line width 120) on `packages/*/src|test` and coding-agent examples |
| TypeScript | `tsconfig.base.json` + root `tsconfig.json` | Strict ES2022 / Node16 modules; root is `noEmit` check |
| Package emit | `packages/*/tsconfig.build.json` | `tsgo` production builds to `dist/` |
| Vitest | package `vitest.config.ts` | Unit/integration tests |

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `tsx not found` | Root install missing | `npm ci` from repo root |
| `Bundle not found` with `--dist` | Dist not built | `npm run build` |
| Node version error at CLI start | Node &lt; 22.8.0 | Upgrade Node |
| Check fails on format only | Biome `--write` expected | Re-run `npm run check`, restage |
| Live provider tests fail offline | Real keys required | Use faux provider / suite harness; keep env keys unset for unit paths |
| Daemon attach collides with daily sessions | Shared `~/.prime/agent` | Set `PRIME_AGENT_CODING_AGENT_DIR` to a temp dir |
| New dependency resolves oddly | Old npm ignoring `min-release-age` | Use npm ≥ 11.10 |

## Next

<CardGroup>
  <Card title="Installation" href="/installation">
    Stable install command, checksum verification, and binary placement for non-source installs.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    First interactive session after install or source launch.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Extension registration patterns used by monorepo examples.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Programmatic bootstrap using workspace packages from `examples/sdk`.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth, resume, provider 401, and connection-mode failure probes.
  </Card>
  <Card title="Overview" href="/overview">
    Product entry points, modes, and runtime assumptions.
  </Card>
</CardGroup>

---
