@counterposition/skill-pi 0.84.2 → 0.99.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,11 +7,13 @@ Pi supports subscription-based providers via OAuth, API-key providers via env va
7
7
  Use `/login` in interactive mode, then select a provider. The `/login` selector is fuzzy-searchable and shows where each entry's auth comes from (`--api-key`, env var, custom provider) without leaking the secret.
8
8
 
9
9
  - Anthropic Claude Pro / Max — third-party usage draws from extra usage and is billed per token (suppress the warning via `warnings.anthropicExtraUsage`)
10
- - OpenAI ChatGPT Plus / Pro (Codex) — `/login` defaults to browser auth but can use a device-code flow for headless environments
10
+ - OpenAI with a ChatGPT subscription (Pi 0.99.0) — `/login openai` offers **Sign in with ChatGPT**, an OAuth login that uses the subscription with the OpenAI API on the regular `openai` provider. Pi stores a stable `deviceId` for it in global settings. Subscription usage-limit errors are not retried
11
+ - OpenAI Codex (legacy) — the older `openai-codex` provider, renamed in Pi 0.99.0 and superseded by Sign in with ChatGPT. Its `/login` still offers browser auth or a device-code flow; its default model is `gpt-6.1-sol` (Pi 0.99.1). Prefer Sign in with ChatGPT on `openai` for new setups
11
12
  - GitHub Copilot
12
13
  - OpenRouter (Pi 0.82.0) — `/login openrouter` runs a PKCE flow that mints a user-controlled API key billed from OpenRouter credits; on headless/SSH hosts paste the redirect URL or authorization code into the prompt (Pi 0.83.0). `OPENROUTER_API_KEY` still works via **Use an API key**
13
14
  - xAI (Grok/X subscription, Pi 0.80.8) — `/login xai` then **Use a subscription** (device-code OAuth); `XAI_API_KEY` remains available via **Use an API key**
14
- - Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl`
15
+ - Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl`. Since Pi 0.86.0 a public Radius catalog ships for immediate/offline selection (cached and live gateway catalogs overlay it); custom gateways use their own catalog
16
+ - Meta Muse (Pi 0.86.1) — `/login meta` with automatic Model API key refresh; `META_API_KEY` also works
15
17
 
16
18
  `/login <provider>` with autocomplete works since Pi 0.80.4, and login methods are provider-owned since 0.80.8 (registered pi-ai providers expose their own auth options and status in `/login`). Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. `pi auth check [provider|model]` (Pi 0.84.1) preflights credential readiness; `pi auth print-api-key` / `pi auth print-bearer-token` (Pi 0.83.0) export resolved credentials to external clients, refreshing OAuth as needed.
17
19
 
@@ -42,11 +44,13 @@ Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` re
42
44
  | OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
43
45
  | Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |
44
46
  | Radius | `RADIUS_API_KEY` | `radius` |
47
+ | TypeSafe (classifier models only) | `TYPESAFE_API_KEY` | `typesafe` |
45
48
  | Hugging Face | `HF_TOKEN` | `huggingface` |
46
49
  | Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
47
50
  | Together AI | `TOGETHER_API_KEY` | `together` |
48
51
  | Baseten | `BASETEN_API_KEY` | `baseten` |
49
52
  | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` |
53
+ | Meta (Muse) | `META_API_KEY` | `meta` |
50
54
  | MiniMax | `MINIMAX_API_KEY` | `minimax` |
51
55
  | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |
52
56
  | Qwen Token Plan / Individual | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` / `qwen-token-plan-individual` (Pi 0.81.0 / 0.84.1) |
@@ -84,10 +88,12 @@ Entries may include an `env` object (Pi 0.79.5) for provider-scoped environment
84
88
 
85
89
  ## Credential Resolution Order
86
90
 
87
- 1. `--api-key`
91
+ 1. `--api-key` (runtime override)
88
92
  2. `auth.json`
89
- 3. Environment variables
90
- 4. Custom provider keys from `models.json`
93
+ 3. `apiKey` from `models.json`
94
+ 4. Environment variables or ambient cloud credentials
95
+
96
+ Provider extensions can define their own authentication behavior.
91
97
 
92
98
  ## Cloud Providers
93
99
 
@@ -149,12 +155,11 @@ Use `models.json` for OpenAI-compatible, Anthropic-compatible, Google-compatible
149
155
 
150
156
  ```json
151
157
  {
152
- "providers": [
153
- {
154
- "name": "ollama",
158
+ "providers": {
159
+ "ollama": {
155
160
  "baseUrl": "http://localhost:11434/v1",
156
161
  "api": "openai-completions",
157
- "apiKey": "unused",
162
+ "apiKey": "ollama",
158
163
  "models": [
159
164
  {
160
165
  "id": "llama3.2",
@@ -173,10 +178,12 @@ Use `models.json` for OpenAI-compatible, Anthropic-compatible, Google-compatible
173
178
  }
174
179
  ]
175
180
  }
176
- ]
181
+ }
177
182
  }
178
183
  ```
179
184
 
185
+ `providers` is an object keyed by provider ID, not an array; the optional `name` field is only a display label. A `models` entry adds or replaces a model with the same ID on that provider; use `modelOverrides` to change metadata of an existing built-in or extension model.
186
+
180
187
  `apiKey` is optional (Pi 0.80.0): omit it when auth comes from `/login`, `auth.json`, or CLI `--api-key`. Like `auth.json` keys, `apiKey` and `headers` values support `!command` execution and `$ENV_VAR` interpolation; plain uppercase strings are literals. Keyless local servers (e.g. Ollama) should keep a dummy value so their models appear in `/model`.
181
188
 
182
189
  Common `api` values:
@@ -195,12 +202,16 @@ Common `api` values:
195
202
  - `samplingParams` (Pi 0.84.0) is a free-form object merged verbatim into every request body after pi's own fields (its keys win) — for OpenAI-compatible APIs only (`openai-completions`, `openai-responses`, `azure-openai-responses`). Use it for server-specific knobs like llama.cpp `min_p` or vLLM `top_k`; in `modelOverrides` it merges per key.
196
203
  - `thinkingLevelMap` (Pi 0.72) replaces `compat.reasoningEffortMap`. Map pi levels (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) to provider values; use `null` to hide a level. Maps may contain holes (Pi 0.80.6) — e.g. expose `high` and `max` without `xhigh`. When a key is omitted, standard levels through `high` use the provider default mapping, but the extended `xhigh`/`max` levels are unsupported.
197
204
  - `cost` supports request-wide input pricing tiers (Pi 0.80.6): a `tiers` array where each tier supplies a complete alternate rate set and applies to the whole request when total input usage (`input + cacheRead + cacheWrite`) exceeds `inputTokensAbove`; the highest matching threshold wins. Also usable in `modelOverrides` and extension-registered providers.
198
- - `modelOverrides` applies to built-in and (since Pi 0.80.4) extension-registered provider models; per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `samplingParams`, `headers`, `compat`.
205
+ - `modelOverrides` applies to built-in and (since Pi 0.80.4) extension-registered provider models; per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `samplingParams`, `headers`, `compat`, `inputLimits`, `promptCache`.
206
+ - `inputLimits.images.resize` (Pi 0.87.0) sets per-model cache-safe image encoding — `maxWidth`, `maxHeight`, `maxBytes` (base64 payload), `jpegQuality`; defaults 2000×2000, 4.5 MiB, quality 80 — applied once to attachments, `read` images, and tool-result images (switching models does not re-encode history).
207
+ - `promptCache: { short?, long? }` (Pi 0.86.0) declares best-effort cache lifetimes in seconds per retention tier; models without a lifetime for the active tier are not eligible for cache warming (see `cacheWarming` in `references/settings.md`).
208
+ - `compat.allowedFallbackModels` (Pi 0.86.0) overrides or disables Anthropic server-side fallback models; `compat.supportsMidConvoEffort` (Pi 0.84.4) marks custom Anthropic Messages models that accept per-turn effort changes.
209
+ - OpenAI-compatible local servers: `vllmPriority` and `supportsMaxOutputTokens` (Pi 0.85.0) plus configurable thinking-token budget fields for vLLM, Qwen/SGLang, and llama.cpp (Pi 0.84.3).
199
210
  - `openRouterRouting` is forwarded as-is in the OpenRouter `provider` field (fallbacks, ZDR, ignore lists, throughput/latency).
200
211
  - `compat.thinkingFormat` supports OpenAI-compatible reasoning variants: `openrouter` sends `reasoning: { effort }`, `together` sends `reasoning: { enabled }` plus `reasoning_effort` when supported, `qwen-chat-template` targets local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`, `chat-template` (Pi 0.79.9) sends configurable `chat_template_kwargs` via `compat.chatTemplateKwargs` — e.g. `{ "thinking": { "$var": "thinking.enabled" } }` for DeepSeek models behind vLLM/Hugging Face chat templates — and `baseten` (Pi 0.84.0) sends `chat_template_args` via `compat.chatTemplateArgs`.
201
212
  - **Breaking (Pi 0.80.7):** `compat.sendSessionIdHeader` was removed. Session affinity is now controlled by `compat.sessionAffinityFormat` (`"openai"` sends `session_id`/`x-client-request-id`, `"openai-nosession"` omits the underscore-containing `session_id` header, `"openrouter"` sends `x-session-id`; default auto-detected). Replace `sendSessionIdHeader: false` with `sessionAffinityFormat: "openai-nosession"`. `sendSessionAffinityHeaders` still gates the behavior for `openai-completions`.
202
213
  - `compat.deferredToolsMode: "kimi"` (Pi 0.80.9) enables Kimi's deferred tool serialization; `compat.supportsToolReferences` / `compat.supportsToolSearch` enable native dynamic tool loading on verified custom endpoints, and `compat.supportsStrictTools` / `compat.supportsOpenAIGrammarTools` / per-model `constrainedSampling` gate constrained sampling (see `references/extensions.md`). `compat.supportsFinishReason: false` (Pi 0.84.0) tells pi to infer stop/toolUse for OpenAI-compatible streams that omit `finish_reason`.
203
- - Advanced `compat` flags exist for proxy quirks (`cacheControlFormat` — now also covering tool-result text content, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults.
214
+ - Advanced `compat` flags exist for proxy quirks (`cacheControlFormat` — now also covering tool-result text content, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults.
204
215
 
205
216
  ### Context Overflow Recovery
206
217
 
@@ -208,12 +219,96 @@ Pi can auto-compact and retry when a provider fails with a recognized context-wi
208
219
 
209
220
  ### llama.cpp
210
221
 
211
- Pi supports the llama.cpp router server (Pi 0.81.0): configure with `/login llama.cpp`, search/download Hugging Face models and load/unload with live progress via `/llama`, then select loaded models in `/model`. The model catalog persists across restarts (fixed in Pi 0.82.1). See [Pi `docs/llama-cpp.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/llama-cpp.md).
222
+ Pi supports the llama.cpp router server (Pi 0.81.0): configure with `/login llama.cpp`, search/download Hugging Face models and load/unload with live progress via `/llama`, then select loaded models in `/model`. The model catalog persists across restarts (fixed in Pi 0.82.1). Every llama.cpp chat model is also listed as a classifier model (see [Classifier Models](#classifier-models)). `"extensions": ["-builtin:llama.cpp"]` removes the provider and `/llama`. See [Pi `docs/llama-cpp.md`](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/llama-cpp.md).
223
+
224
+ ## Model Types
225
+
226
+ Since Pi 0.99.0 catalogs hold typed models: `type` is `"chat"` (the default when omitted), `"image"`, or `"classifier"`. Chat-facing reads (`getModels()`, `getAvailable()`, `ctx.modelRegistry.getAll()`, `/model`) stay chat-only. `ModelRuntime`, `ctx.modelRegistry`, and pi-ai `Models` all have `getModelsOfType(type, provider?)`, `getModelOfType(type, provider, id)`, and async `getAvailableOfType(type, provider?)`. Only `ModelRuntime` and `Models` have `getAllModels()` and `getAllAvailable()` for every type; `ctx.modelRegistry` adds `findOfType(type, provider, id)` instead. pi-ai exports `isModelType()` and `getModelType()` for mixed lists. One upstream ID may have separate chat and image entries. Sources: [models.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/models.md#use-classifier-models), [pi-ai types](https://github.com/earendil-works/pi/blob/v0.99.1/packages/ai/src/types.ts).
227
+
228
+ ### Classifier Models
229
+
230
+ Classifiers do not chat. They answer typed questions about JSON state with probabilities, and do not appear in `/model`.
231
+
232
+ | Provider | Model IDs | Auth |
233
+ |----------|-----------|------|
234
+ | `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
235
+ | `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or `/login` |
236
+ | `cloudflare-workers-ai` | `typesafe/jev` | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` |
237
+ | `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
238
+ | `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
239
+ | llama.cpp router | every loaded chat model ID | `/login llama.cpp` |
240
+
241
+ ```typescript
242
+ const jev = ctx.modelRegistry.findOfType("classifier", "typesafe", "jev-latest");
243
+ if (jev) {
244
+ const result = await ctx.modelRegistry.classify(
245
+ jev,
246
+ {
247
+ state: { message: "The change works, thanks." },
248
+ questions: {
249
+ approved: {
250
+ type: "bool",
251
+ instructions: "Does the user approve of the result?",
252
+ criteria: { true: "Approval", false: "No approval" },
253
+ },
254
+ },
255
+ },
256
+ { signal: ctx.signal },
257
+ );
258
+ const answer = result.stopReason === "stop" ? result.answers.approved : undefined;
259
+ if (answer?.type === "bool") console.log(answer.probability);
260
+ }
261
+ ```
262
+
263
+ - Question types: `choice` (`criteria` maps labels to descriptions; answer has `choice`, `probabilities`, `confidence`), `bool` (`criteria: { true, false }`; answer has `probability`), and `score` (`criteria` is an ordered list; answer has `score`, `confidence`). Pi translates `bool` to TypeSafe's wire format.
264
+ - `classify()` never rejects. Failures and aborts return `stopReason: "error"` or `"aborted"` with `errorMessage`. Check `stopReason` and each answer's `type` before use, and validate probabilities yourself if a decision depends on them (for example finite and within `[0, 1]`).
265
+ - `result.usage` carries token counts and catalog cost when the service reports them; TypeSafe's direct `jev-latest` has no catalog price.
266
+ - `jev-latest` moves with the service. Pin a versioned ID such as `openrouter`'s `typesafe/jev-1.13` when thresholds were tuned against one model.
267
+ - Codemode scripts reach classifiers through `models.classify()` (see `references/codemode.md`). The SDK equivalent is `modelRuntime.classify()`. A llama.cpp classifier accepts a per-request `temperature` to soften overconfident label probabilities.
268
+
269
+ A gate that fails closed on classifier errors is in `examples/classifier-gate.ts`.
270
+
271
+ ### Image Generation
272
+
273
+ `ModelRuntime.generateImages(model, { input }, options?)` (Pi 0.99.0) generates images with runtime-resolved auth; list image models with `getModelsOfType("image")`. OpenRouter image models appear under the `openrouter` provider with its credential. Like `classify()`, it returns an error result rather than rejecting.
274
+
275
+ In pi-ai, image models live in the same catalog as chat models: `builtinModels()` includes them, `models.getModelOfType("image", provider, id)` finds one (type `ImageModel`), `models.generateImages()` runs it, and `createProvider({ models, images })` registers image implementations. The separate `*Images*` API was removed in pi-ai 0.99.0.
276
+
277
+ ## Virtual Models
278
+
279
+ A virtual model (experimental, Pi 0.99.0) is a selectable entry that routes each request to a physical model. It is for routing by task, cost, or conversation state, not for permission decisions. Source: [virtual-models.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/virtual-models.md), [`jev-router.ts`](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/examples/extensions/jev-router.ts).
280
+
281
+ ```typescript
282
+ pi.registerVirtualModel({
283
+ provider: "router",
284
+ id: "auto",
285
+ name: "Auto",
286
+ thinkingLevels: ["low", "high"],
287
+ route(request, ctx) {
288
+ const sticky = request.failed ?? request.previous;
289
+ if (request.reason !== "user" && sticky) {
290
+ return { model: sticky.model, thinkingLevel: sticky.thinkingLevel ?? "medium" };
291
+ }
292
+ const id = request.thinkingLevel === "high" ? "claude-sonnet-4-5" : "claude-haiku-4-5";
293
+ const model = ctx.modelRegistry.find("anthropic", id);
294
+ if (!model) throw new Error(`anthropic/${id} is not in the catalog`);
295
+ return { model, thinkingLevel: "medium" };
296
+ },
297
+ });
298
+ ```
299
+
300
+ - `route()` runs before every request with `reason` (`user`, `continuation`, `retry`, `direct`), `previous`, `failed`, `state`, `messages`, and `signal`. Returning `previous` for continuations and `failed` for retries keeps prompt caches and thinking signatures valid.
301
+ - Returned `state` (JSON) is stored on the session branch and comes back as `request.state`; `direct` requests (compaction summaries, `ctx.modelRegistry.streamSimple()`) have none.
302
+ - The route must be a physical model with credentials; throwing or returning a virtual model ends the request with an error.
303
+ - Selection (`ctx.model`, `model_change`) names the virtual model; each assistant message records the physical `provider`/`model`/`thinkingLevel`. The footer shows both; `/session` lists cost per physical model.
304
+ - `pi.unregisterVirtualModel(provider, id)` removes one (`unregisterProvider()` does not). SDK code can call `modelRuntime.registerVirtualModel(definition)`.
212
305
 
213
306
  ## Custom Providers via Extensions
214
307
 
215
308
  Use extensions when you need custom streaming, custom headers, OAuth/device-flow logic, or want to override an existing provider's `baseUrl`/`headers`:
216
309
 
310
+ A config-form `models` list replaces the provider's models across chat, image, and classifier operations; image and classifier entries need an explicit `type` and implementations keyed by `api` through `images` and `classifiers`. Registering only `baseUrl` or `headers` keeps the built-in models. See [custom-provider.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/custom-provider.md).
311
+
217
312
  ```typescript
218
313
  // Full registration
219
314
  pi.registerProvider("my-provider", {
@@ -236,6 +331,10 @@ pi.registerProvider("my-provider", {
236
331
  pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" });
237
332
  ```
238
333
 
334
+ ### Custom Streaming (Breaking, Pi 0.86.0)
335
+
336
+ Custom `streamSimple` implementations receive a normalized `TranscriptContext` instead of `Context`. System prompts and tool declarations live in transcript system messages: read them with `getCurrentSystemPrompt(context.messages)` and `getCurrentTools(context.messages)` (not `context.systemPrompt` / `context.tools`), and call `collapseSystemMessages(context)` if the backend cannot accept mid-conversation system messages. `ToolCall.arguments` and `ToolResultMessage.details` must be JSON-compatible. Pi 0.84.3 also renamed `GoogleThinkingLevel` to `GoogleApiThinkingLevel`.
337
+
239
338
  ## SDK / Extension Auth Lookup
240
339
 
241
340
  If extension code needs auth for a specific model request, use `getApiKeyAndHeaders(model)` rather than the removed `getApiKey(model)`:
@@ -247,4 +346,4 @@ if (!auth.ok) throw new Error(auth.error);
247
346
  const { apiKey, headers } = auth;
248
347
  ```
249
348
 
250
- This matters for providers whose headers or auth values resolve dynamically on every request. In SDK code, use `ModelRuntime.getAuth(providerOrModel)` instead (Pi 0.80.8) — passing a model also resolves built-in, `models.json`, and extension model headers; see `references/sdk.md`.
349
+ This matters for providers whose headers or auth values resolve dynamically on every request. For nested model calls, `ctx.modelRegistry.streamSimple(model, context, options)` (Pi 0.86.0) resolves auth itself. In SDK code, use `ModelRuntime.getAuth(providerOrModel)` instead (Pi 0.80.8) — passing a model also resolves built-in, `models.json`, and extension model headers; see `references/sdk.md`.
@@ -30,10 +30,15 @@ session.subscribe((event) => {
30
30
  }
31
31
  });
32
32
 
33
- await session.prompt("What files are in the current directory?");
33
+ try {
34
+ await session.prompt("What files are in the current directory?");
35
+ console.log(session.getLastAssistantText());
36
+ } finally {
37
+ session.dispose();
38
+ }
34
39
  ```
35
40
 
36
- Run with `npx tsx my-script.ts`.
41
+ Run with `npx tsx my-script.ts` (or Node's built-in type stripping). `prompt()` resolves when the run finishes, including automatic retries. `session.dispose()` aborts active work, invalidates extension contexts, and removes listeners. Checked against Pi v0.99.1: [sdk.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/sdk.md), [SDK examples](https://github.com/earendil-works/pi/tree/v0.99.1/packages/coding-agent/examples/sdk).
37
42
 
38
43
  **Breaking (Pi 0.80.8):** `ModelRuntime` replaced the old `AuthStorage`/`ModelRegistry` pair as the SDK model/auth facade. `CreateAgentSessionOptions.authStorage` and `modelRegistry` are gone — pass the async `modelRuntime` instead. `AuthStorage` is no longer exported; use `ModelRuntime` (or a custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off reads of `auth.json`. `ModelRegistry` still exists only as the synchronous extension-facing compatibility facade, and its `refresh()` is now `Promise<void>`.
39
44
 
@@ -44,6 +49,7 @@ import {
44
49
  createAgentSession,
45
50
  DefaultResourceLoader,
46
51
  defineTool,
52
+ getAgentDir,
47
53
  ModelRuntime,
48
54
  SessionManager,
49
55
  } from "@earendil-works/pi-coding-agent";
@@ -56,11 +62,14 @@ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
56
62
 
57
63
  ```typescript
58
64
  const cwd = "/path/to/project";
65
+ const agentDir = getAgentDir();
59
66
  const modelRuntime = await ModelRuntime.create();
67
+ const resourceLoader = new DefaultResourceLoader({ cwd, agentDir });
68
+ await resourceLoader.reload(); // a loader you pass in is not reloaded for you
60
69
 
61
70
  const { session } = await createAgentSession({
62
71
  cwd,
63
- agentDir: "~/.pi/agent",
72
+ agentDir,
64
73
  modelRuntime,
65
74
  model: getBuiltinModel("anthropic", "claude-opus-4-8"),
66
75
  thinkingLevel: "medium", // off | minimal | low | medium | high | xhigh | max
@@ -69,9 +78,8 @@ const { session } = await createAgentSession({
69
78
  ],
70
79
  tools: ["read", "bash", "edit", "write"],
71
80
  customTools: [/* defineTool(...) entries */],
72
- resourceLoader: new DefaultResourceLoader(),
81
+ resourceLoader,
73
82
  sessionManager: SessionManager.inMemory(),
74
- shouldStopAfterTurn: (state) => state.turnCount >= 5,
75
83
  });
76
84
  ```
77
85
 
@@ -81,12 +89,61 @@ Notes:
81
89
  - Pi 0.68.0 changed the SDK `tools` option from `Tool[]` to a `string[]` allowlist of built-in, extension, and custom tool names. Use `noTools: "builtin"` to disable built-ins while keeping extension/custom tools enabled, or `noTools: "all"` for none.
82
90
  - `customTools` accepts `ToolDefinition[]`. Build them with `defineTool({...})` for full TypeScript inference.
83
91
  - The `create*Tool(cwd)` factories still exist for code that needs explicit `AgentTool` instances (e.g. when wiring tools into pi-agent-core directly), but they are no longer the value passed to `createAgentSession({ tools })`.
84
- - `DefaultResourceLoader` loads extensions, skills, prompt templates, themes, and context files. Replace it to drive resource discovery from custom sources (and it must implement `loadProjectContextFiles()` if you want `AGENTS.md`/`CLAUDE.md` discovery; that helper is also exported standalone).
85
- - Pass `shouldStopAfterTurn(state) => boolean` (Pi 0.72.0) to exit the agent loop gracefully after a completed turn.
92
+ - `DefaultResourceLoader` requires `{ cwd, agentDir }` and loads extensions, skills, prompt templates, themes, and context files. Replace it to drive resource discovery from custom sources (and it must implement `loadProjectContextFiles()` if you want `AGENTS.md`/`CLAUDE.md` discovery; that helper is also exported standalone).
93
+ - **Breaking (Pi 0.87.0):** pi-agent-core's `shouldStopAfterTurn` option was removed (it was never a `createAgentSession()` option). On a low-level `Agent`, set `finishTurn` instead: it runs after the assistant message and tool results are finalized but before `turn_end`, also for error/aborted responses. Return `{ action: "end" }` to stop, `{ action: "continue" }` to ensure one more request, or `undefined` for normal scheduling — guard hard exits first: `if (turn.message.stopReason === "error" || turn.message.stopReason === "aborted") return;`. Inside `createAgentSession()`, use an extension's actionable `turn_end` / `agent_before_settle` boundaries (see `references/extensions.md`).
94
+
95
+ ## Built-in Extensions: Codemode, Tool Search, MCP
96
+
97
+ The CLI loads `codemode`, `tool_search`, MCP, and llama.cpp as built-in extensions. SDK sessions load none of them. Opt in through the resource loader (Pi 0.99.0, [example](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/examples/sdk/14-codemode-mcp.ts)):
98
+
99
+ ```typescript
100
+ import {
101
+ createAgentSession,
102
+ createCodemodeExtension,
103
+ createMcpExtension,
104
+ createToolSearchExtension,
105
+ DefaultResourceLoader,
106
+ getAgentDir,
107
+ SessionManager,
108
+ SettingsManager,
109
+ } from "@earendil-works/pi-coding-agent";
110
+
111
+ const cwd = process.cwd();
112
+ const resourceLoader = new DefaultResourceLoader({
113
+ cwd,
114
+ agentDir: getAgentDir(),
115
+ extensionFactories: [
116
+ createCodemodeExtension({ mode: "on" }), // options override codemode.mode / inlineBudget
117
+ createToolSearchExtension(),
118
+ createMcpExtension(), // reads mcp.json like the CLI (project file only when trusted)
119
+ ],
120
+ });
121
+ await resourceLoader.reload();
122
+
123
+ const settingsManager = SettingsManager.create(cwd);
124
+ settingsManager.applyOverrides({ defaultTools: ["+codemode", "+tool_search"] });
125
+
126
+ const { session } = await createAgentSession({
127
+ resourceLoader,
128
+ settingsManager,
129
+ sessionManager: SessionManager.inMemory(),
130
+ });
131
+
132
+ try {
133
+ await session.bindExtensions({}); // emits session_start; MCP connects here, in the background
134
+ await session.prompt("Use codemode to count the TypeScript files in src/.");
135
+ } finally {
136
+ session.dispose();
137
+ }
138
+ ```
139
+
140
+ - `codemode` and `tool_search` are registered inactive. Enable them with `defaultTools` (`+name` keeps the other defaults), or let MCP activate them for servers that need them. Passing `tools: [...]` instead restricts the session to the named tools, which hides MCP tools.
141
+ - Extension `session_start` handlers run only when the host calls `session.bindExtensions()`. Without it, MCP servers never connect.
142
+ - Inline extensions can be named `{ name, factory, replaceable?: true, builtin?: true }`. `replaceable` drops the extension when another one registers the same tool, command, or flag name. `builtin` supplies the code for `builtin:<name>`: it loads by default, is listed in `pi config`, is disabled by `-builtin:<name>` or `noExtensions`, and loads after project trust (so it cannot handle `project_trust`).
86
143
 
87
144
  ## Models & Auth (`ModelRuntime`)
88
145
 
89
- `ModelRuntime` (Pi 0.80.8) implements the pi-ai `Models` interface and owns credential storage. Auth resolution priority: runtime overrides (`setRuntimeApiKey`, not persisted) → stored credentials in `auth.json` → environment variables → fallback resolver for `models.json` keys.
146
+ `ModelRuntime` (Pi 0.80.8) implements the pi-ai `Models` interface and owns credential storage. Auth resolution priority: runtime overrides (`setRuntimeApiKey`, not persisted) → stored credentials in `auth.json` → `apiKey` from `models.json` or an extension registration → the provider's environment variables or ambient cloud credentials.
90
147
 
91
148
  ```typescript
92
149
  import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
@@ -116,6 +173,8 @@ const inMemory = await ModelRuntime.create({ credentials: new InMemoryCredential
116
173
 
117
174
  Pi 0.84.x made catalog refresh cancellation-aware: `refresh(options?)` accepts `{ providers?, allowNetwork?, force?, signal? }` and returns a `ModelsRefreshResult` (`{ errors, aborted }`) instead of discarding provider errors; `create({ signal })`, `login()`, `logout()`, and the now-async `setRuntimeApiKey()/removeRuntimeApiKey()` accept optional abort signals too (unbounded when omitted — applications own deadline policy). Credential mutations resolve once local state is consistent, without waiting for remote freshness; if that synchronization fails they reject with the exported `CredentialSynchronizationError` (inspect `providerId`, `operation`, `credential`, `cause` rather than retrying blindly). A failed refresh never undoes a committed credential change, and each `refresh()` starts a fresh generation so stale refreshes cannot publish afterward.
118
175
 
176
+ Non-chat models (Pi 0.99.0): `getModelsOfType(type, provider?)`, `getModelOfType(type, provider, id)`, `getAvailableOfType()`, `getAllModels()`, and `getAllAvailable()` cover `"chat"`, `"image"`, and `"classifier"` entries; `getModels()` stays chat-only. `modelRuntime.classify(model, context, options?)` and `modelRuntime.generateImages(model, context, options?)` resolve auth at request time and return error results (`stopReason: "error" | "aborted"`) instead of rejecting. `modelRuntime.registerVirtualModel(definition)` registers a routing model without an extension. See `references/providers.md#model-types`.
177
+
119
178
  To match CLI model parsing, use the exported resolver helpers (Pi 0.80.4):
120
179
 
121
180
  ```typescript
@@ -139,11 +198,11 @@ await session.prompt("Review the current directory");
139
198
  await session.prompt("Stop and do this instead", { streamingBehavior: "steer" });
140
199
  await session.prompt("After you finish, also check tests", { streamingBehavior: "followUp" });
141
200
 
142
- await session.steer("Use a smaller diff");
201
+ const disposition = await session.steer("Use a smaller diff"); // "queued" | "handled"
143
202
  await session.followUp("Summarize the changes afterward");
144
203
  ```
145
204
 
146
- `prompt()` expands file-based prompt templates. During active streaming, calling it without `streamingBehavior` throws.
205
+ `prompt()` expands file-based prompt templates and resolves after an accepted run finishes. During active streaming, calling it without `streamingBehavior` throws. Since Pi 0.99.0 `steer()` and `followUp()` return a disposition: `"queued"` (also after an extension transformed the input) or `"handled"` (an extension consumed it, so nothing was queued). Both throw for extension commands (`/name`); send those through `prompt()`, which runs them immediately, even while streaming. Read state through `session.messages`, `session.model`, `session.thinkingLevel`, `session.systemPrompt`, and `session.getActiveToolNames()`.
147
206
 
148
207
  ## Session Runtime
149
208
 
@@ -215,10 +274,12 @@ Use `runtime.newSession()`, `runtime.switchSession()`, and `runtime.fork()` for
215
274
  Useful state:
216
275
 
217
276
  - `session.sessionFile` / `session.sessionId`
218
- - `session.agent.state.messages`
277
+ - `session.agent.state.messages` — read-only view in practice: since Pi 0.87.0 `SessionManager` is canonical for provider context, so assigning it no longer changes future requests
219
278
  - `session.model`
220
279
  - `session.thinkingLevel`
221
280
 
281
+ To change model context from the SDK (Pi 0.87.0): restore external history with `SessionManager.inMemory(cwd, { id }, entries)` (Pi 0.85.0), navigate with `session.navigateTree()`, or append through `session.sessionManager` and call `session.refreshContext()`. `sessionManager.appendContextEdit(entryId, null)` omits one message from future provider context (a `{ content }` replacement rewrites it) without touching raw history, usage, or UI; `appendCompaction(summary, null, tokensBefore)` records a retain-none compaction. `SessionEntry` now includes `ContextEditEntry` (`type: "context_edit"`), so exhaustive entry switches must handle it.
282
+
222
283
  ## Events
223
284
 
224
285
  ```typescript
@@ -229,14 +290,14 @@ session.subscribe((event) => {
229
290
  process.stdout.write(event.assistantMessageEvent.delta);
230
291
  }
231
292
  break;
232
- case "tool_execution_start":
293
+ case "tool_execution_start": // nested calls (ctx.executeTool, codemode) carry parentToolCallId
233
294
  case "tool_execution_update":
234
295
  case "tool_execution_end":
235
296
  case "agent_start":
236
297
  case "agent_end": // one low-level run; may be followed by retry/compaction/queued follow-ups
237
298
  case "agent_settled": // fully settled — no automatic continuation left (Pi 0.80.4)
238
299
  case "turn_start":
239
- case "turn_end":
300
+ case "turn_end": // Pi 0.87.0: carries turnIndex, messageEntryId, toolResultEntryIds
240
301
  case "queue_update":
241
302
  case "compaction_start":
242
303
  case "compaction_end":
@@ -260,9 +321,11 @@ Pi 0.84.0 added transport-neutral remote-session client APIs: `@earendil-works/p
260
321
 
261
322
  ## RPC Mode
262
323
 
263
- `pi --mode rpc` speaks newline-delimited JSON over stdio. Additions since Pi 0.79:
324
+ `pi --mode rpc` speaks newline-delimited JSON over stdio ([rpc.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/rpc.md), [rpc-commands.md](https://github.com/earendil-works/pi/blob/v0.99.1/packages/coding-agent/docs/rpc-commands.md)). Additions since Pi 0.79:
264
325
 
326
+ - Input dispositions (Pi 0.99.0): a successful `prompt` response carries `data.disposition` — `"started"` (a run started), `"queued"` (queued during a run), or `"handled"` (an extension command or input handler consumed it). `steer` and `follow_up` responses carry `"queued"` or `"handled"`. On `"handled"` no run started for that input: do not wait for `agent_settled`. Otherwise keep consuming events and wait for `agent_settled`, not `agent_end`. Failures after acceptance arrive as events, never as a second response. `RpcClient.prompt(message, images?, streamingBehavior?)`, `steer()`, and `followUp()` return the disposition.
265
327
  - `agent_settled` event (Pi 0.80.4) fires when a run is fully settled — no automatic retry, compaction retry, or queued continuation remains; `agent_end` now carries `willRetry`. `set_thinking_level` accepts `"max"` where the model supports it.
328
+ - `clear_queue` (Pi 0.84.4) returns and removes queued steering and follow-up messages; direct RPC `steer`/`follow_up` commands pass through extension `input` handlers since Pi 0.86.0.
266
329
  - `get_entries` / `get_tree` (Pi 0.80.3) read session entries and tree snapshots over RPC; `get_available_thinking_levels` (Pi 0.81.0) lists the current model's supported thinking levels.
267
330
  - Direct RPC `bash` commands stream `bash_execution_update` chunks correlated by request `id` (Pi 0.82.0); include an `id` on the command to associate them.
268
331
  - `compact` responses include summary `usage`; `get_tokens`/`get_status` totals cover assistant messages, tool-reported usage, and summaries (Pi 0.81.0).
@@ -283,13 +346,18 @@ Pi 0.84.0 added transport-neutral remote-session client APIs: `@earendil-works/p
283
346
  To name an inline factory in the startup Extensions list (instead of `<inline:1>`), wrap it in an `InlineExtension` (Pi 0.80.4):
284
347
 
285
348
  ```typescript
286
- import type { InlineExtension } from "@earendil-works/pi-coding-agent";
349
+ import { DefaultResourceLoader, getAgentDir, type InlineExtension } from "@earendil-works/pi-coding-agent";
287
350
 
288
351
  const myProvider: InlineExtension = {
289
352
  name: "my-provider",
290
353
  factory: (pi) => { /* ... */ },
291
354
  };
292
- const loader = new DefaultResourceLoader({ extensionFactories: [myProvider] });
355
+ const loader = new DefaultResourceLoader({
356
+ cwd: process.cwd(),
357
+ agentDir: getAgentDir(),
358
+ extensionFactories: [myProvider],
359
+ });
360
+ await loader.reload();
293
361
  ```
294
362
 
295
363
  Bare factory functions are still accepted.
@@ -18,6 +18,7 @@ Edit JSON directly or use `/settings` for common interactive options.
18
18
  "defaultThinkingLevel": "medium",
19
19
  "hideThinkingBlock": false,
20
20
  "showCacheMissNotices": false,
21
+ "cacheWarming": "streaming",
21
22
  "thinkingBudgets": {
22
23
  "minimal": 1024,
23
24
  "low": 4096,
@@ -29,13 +30,15 @@ Edit JSON directly or use `/settings` for common interactive options.
29
30
 
30
31
  - Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"` — `max` (Pi 0.80.6) is an opt-in level above `xhigh`, exposed only when the selected model supports it (some models, e.g. GPT-5.6, expose both). Custom themes can define `thinkingMax`; existing themes fall back to `thinkingXhigh`.
31
32
  - `hideThinkingBlock` hides visible thinking output in the UI
32
- - `showCacheMissNotices` (Pi 0.80.4) shows transcript notices for significant prompt-cache misses
33
+ - `showCacheMissNotices` (Pi 0.80.4) shows transcript notices for significant prompt-cache misses; newer releases also report successful cache warming, compaction/branch-summary usage, and provider recovery
34
+ - `cacheWarming` (Pi 0.86.0, global only): `"off"`, `"streaming"` (default — keep eligible caches alive during long tool runs), or `"idle"` (also between runs). Pi warms only when the model declares a `promptCache` lifetime and it estimates at least $0.05 in avoided cache-miss cost; refresh usage counts toward session totals but never enters model context. `/session` shows the next decision; extensions can override via `cache_warming_decision`
35
+ - `/model` and `/thinking` (Pi 0.84.3) selections are session-scoped; press `Ctrl+S` in the selector to save as the global default
33
36
 
34
37
  ## UI & Display
35
38
 
36
39
  ```json
37
40
  {
38
- "theme": "dark",
41
+ "theme": "system",
39
42
  "quietStartup": false,
40
43
  "collapseChangelog": false,
41
44
  "doubleEscapeAction": "tree",
@@ -51,7 +54,7 @@ Edit JSON directly or use `/settings` for common interactive options.
51
54
  - `doubleEscapeAction`: `"tree"`, `"fork"`, or `"none"`
52
55
  - `treeFilterMode`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, or `"all"`
53
56
  - In `/tree`, `Shift+T` toggles timestamps on entry labels
54
- - `theme` also accepts `"light-name/dark-name"` (Pi 0.79.7) to switch themes automatically with the terminal color scheme; `/` is reserved in theme names for this. On first run Pi detects the terminal background and defaults to `dark` or `light`. `--use-theme <name[/name]>` (Pi 0.84.2) sets a per-run initial theme without changing saved settings.
57
+ - `theme` defaults to `"system"` (Pi 0.99.0), which builds Pi's colors from the terminal's reported foreground, background, and ANSI palette and follows light/dark switches; `system` is a reserved theme name. `dark` and `light` are the other built-ins. `"light-name/dark-name"` (Pi 0.79.7) switches themes with the terminal color scheme; `/` is reserved in theme names for this. `--use-theme <name[/name]>` (Pi 0.84.2) sets a per-run initial theme without changing saved settings.
55
58
  - `outputPad` (Pi 0.80.3, `0` or `1`) sets horizontal padding for user messages, assistant messages, and thinking blocks; custom message renderers receive it as `options.outputPad` (Pi 0.82.1).
56
59
  - `externalEditor` (Pi 0.80.3) sets the `Ctrl+G` external editor command and takes precedence over `$VISUAL`/`$EDITOR`.
57
60
 
@@ -63,13 +66,17 @@ Experimental since Pi 0.84.0:
63
66
  {
64
67
  "tuiMode": "regular",
65
68
  "fullscreenExitOutput": "transcript",
66
- "fullscreenScrollbar": "auto"
69
+ "fullscreenScrollbar": "auto",
70
+ "fullscreenCopyOnSelect": true,
71
+ "fullscreenWheelScrollLines": "auto"
67
72
  }
68
73
  ```
69
74
 
70
75
  - `tuiMode`: `"regular"` (default) or `"fullscreen"`; `/settings` switches at runtime, `--tui-mode` overrides per run
71
76
  - `fullscreenExitOutput` (Pi 0.84.2): `"transcript"` prints the final transcript on exit; `"resume-hint"` restores the previous screen and prints only a resume hint
72
77
  - `fullscreenScrollbar`: `"auto"` shows while scrolling, `"always"` reserves the rightmost column, `"hidden"`
78
+ - `fullscreenCopyOnSelect` (Pi 0.84.4, default `true`): when `false`, `Ctrl+X` copies the active selection (falling back to the last assistant message)
79
+ - `fullscreenWheelScrollLines` (Pi 0.99.0): `"auto"` (default) or 1–100 lines per mouse-wheel event. `"auto"` moves one line per event in local macOS terminals and accelerates fast spins elsewhere (up to 6 lines); Alt+wheel moves five times as far
73
80
  - Fullscreen keeps editor/status/widgets/footer docked while the transcript scrolls independently; transcript search (`Ctrl+Shift+F`, Pi 0.84.2), page/half-page/line scrolling, and marked-message navigation are configurable via `tui.altScreen.*` keybindings
74
81
  - Theme tokens `scrollbarThumb`, `searchMatchBg`, `searchMatchText` are optional additions with fallbacks (`selectedBg`, `selectedBg`, `text`)
75
82
 
@@ -80,7 +87,10 @@ Experimental since Pi 0.84.0:
80
87
  "compaction": {
81
88
  "enabled": true,
82
89
  "reserveTokens": 16384,
83
- "keepRecentTokens": 20000
90
+ "keepRecentTokens": 20000,
91
+ "modelOverrides": {
92
+ "anthropic/claude-opus-5-5": { "reserveTokens": 32768 }
93
+ }
84
94
  },
85
95
  "branchSummary": {
86
96
  "reserveTokens": 16384,
@@ -90,6 +100,7 @@ Experimental since Pi 0.84.0:
90
100
  "enabled": true,
91
101
  "maxRetries": 3,
92
102
  "baseDelayMs": 2000,
103
+ "maxAgentDelayMs": 60000,
93
104
  "provider": {
94
105
  "timeoutMs": 3600000,
95
106
  "maxRetries": 0,
@@ -101,7 +112,8 @@ Experimental since Pi 0.84.0:
101
112
 
102
113
  Notes:
103
114
 
104
- - `retry.maxRetries` (default `3`) and `retry.baseDelayMs` (default `2000`) govern Pi's own agent-level retry with exponential backoff.
115
+ - `compaction.modelOverrides` (Pi 0.86.0) sets per-model `reserveTokens`/`keepRecentTokens` keyed by exact `provider/modelId`; each value falls back to the ordinary compaction setting, then the default.
116
+ - `retry.maxRetries` (default `3`) and `retry.baseDelayMs` (default `2000`) govern Pi's own agent-level retry with exponential backoff, capped per attempt by `retry.maxAgentDelayMs` (default `60000`, Pi 0.86.0).
105
117
  - `retry.provider.*` controls the underlying provider SDK's request timeout and retry/backoff — useful for slow local LLMs and flaky proxies. `retry.provider.timeoutMs` sets the SDK request timeout; `retry.provider.maxRetries` defaults to `0` (keep it there unless you need SDK-level retries, since raising it can let provider retries swallow usage-limit errors before Pi handles them).
106
118
  - `retry.provider.maxRetryDelayMs` (default `60000`) caps how long a server-requested retry delay can be before the request fails immediately with an informative error; set to `0` to disable the cap. The old top-level `retry.maxDelayMs` was renamed to this and is auto-migrated.
107
119
  - `branchSummary.skipPrompt` skips the confirmation step when navigating with `/tree`.
@@ -133,7 +145,10 @@ Notes:
133
145
  "showImages": true,
134
146
  "clearOnShrink": false,
135
147
  "showTerminalProgress": false,
136
- "imageWidthCells": 60
148
+ "imageWidthCells": 60,
149
+ "hyperlinks": "auto",
150
+ "images": "auto",
151
+ "trueColor": "auto"
137
152
  },
138
153
  "images": {
139
154
  "autoResize": true,
@@ -149,6 +164,7 @@ Notes:
149
164
  - `npmCommand` is argv-style and is used for npm lookup/install operations, including git-package installs. User-scoped npm packages install under `~/.pi/agent/npm/`; project-scoped npm packages install under `.pi/npm/`.
150
165
  - `terminal.showTerminalProgress` (default `false` since Pi 0.70.0) toggles OSC 9;4 progress reporting in supporting terminals (iTerm2, WezTerm, Windows Terminal, Kitty).
151
166
  - `terminal.imageWidthCells` (Pi 0.68.1) caps inline tool-output image width in terminal cells.
167
+ - `terminal.hyperlinks` / `terminal.images` (`"kitty" | "iterm2" | "auto" | false`) / `terminal.trueColor` (Pi 0.84.4) override capability detection (settings win over `PI_HYPERLINKS`, `PI_IMAGE_PROTOCOL`, `PI_TRUE_COLOR`). Only force what the full terminal path (tmux, SSH) supports.
152
168
 
153
169
  ## Warnings & Telemetry
154
170
 
@@ -167,7 +183,6 @@ Notes:
167
183
  {
168
184
  "sessionDir": ".pi/sessions",
169
185
  "enabledModels": ["claude-*", "gpt-4o", "gemini-2*"],
170
- "defaultTools": ["bash", "read", "edit"],
171
186
  "markdown": {
172
187
  "codeBlockIndent": " ",
173
188
  "mermaid": "streaming"
@@ -177,12 +192,25 @@ Notes:
177
192
 
178
193
  When multiple sources specify a session directory, `--session-dir` takes precedence over `sessionDir` in settings. Pi 0.65.0 removed the old `session_directory` extension/settings hook.
179
194
 
180
- - `defaultTools` (Pi 0.84.2) picks the built-in tools enabled at startup (global or per project; project replaces global). An empty array starts with no built-in tools while keeping extension/SDK custom tools. `--tools` remains a strict allowlist for all tools, `--exclude-tools` filters the resulting list.
181
195
  - `markdown.mermaid` (Pi 0.84.0): `"off"`, `"final"`, or `"streaming"` — themed Unicode rendering of supported Mermaid diagrams in interactive messages. Pi 0.84.0 also renders terminal-friendly Unicode for LaTeX expressions.
182
196
 
197
+ ## Tools & Codemode
198
+
199
+ ```json
200
+ {
201
+ "defaultTools": ["+codemode"],
202
+ "codemode": { "mode": "on", "inlineBudget": 3000 }
203
+ }
204
+ ```
205
+
206
+ - `defaultTools` picks the tools enabled at startup (default `read`, `bash`, `edit`, `write`; also `grep`, `find`, `ls`, `powershell`, and the built-in extension tools `codemode` and `tool_search`). Plain names replace the defaults. A list of only `+name`/`-name` entries (Pi 0.99.0) changes the inherited selection instead; in a mixed list the plain names form the selection and `+`/`-` apply in order. A project list of only `+`/`-` entries applies on top of the user setting; a project list with a plain name replaces it. An empty array disables built-in tools but keeps extension/SDK tools.
207
+ - `--tools` replaces the whole selection for one run and does not accept `+`/`-`; `--exclude-tools` filters the result.
208
+ - `codemode.mode`: `"on"` (default) keeps declared tools declared; `"only"` hides active built-in and extension tools from the model so it reaches them through `codemode`. `codemode.inlineBudget` (default `3000`, estimated tokens) caps tool declarations in the `codemode` description. See `references/codemode.md`.
209
+ - MCP servers are configured in `~/.pi/agent/mcp.json` and the trust-gated `.pi/mcp.json`, not in `settings.json`. See `references/mcp.md`.
210
+
183
211
  ## Project Trust
184
212
 
185
- Since Pi 0.79.0, project-local settings, resources, and packages (`.pi/` settings/extensions/skills/prompts/themes, project packages) only load after the project is trusted. `AGENTS.md` / `CLAUDE.md` context files load regardless of trust. Trust is a loading gate, not a sandbox.
213
+ Since Pi 0.79.0, project-local settings, resources, and packages (`.pi/` settings/extensions/skills/prompts/themes/`mcp.json`, project packages) only load after the project is trusted. `AGENTS.md` / `CLAUDE.md` context files load regardless of trust. Trust is a loading gate, not a sandbox.
186
214
 
187
215
  ```json
188
216
  {
@@ -224,6 +252,7 @@ Notes:
224
252
  - `packages` loads npm/git/local Pi packages.
225
253
  - `extensions`, `skills`, `prompts`, and `themes` are for local files/directories.
226
254
  - `enableSkillCommands` controls `/skill:name` registration.
255
+ - Built-in extensions (Pi 0.99.0) are `builtin:mcp`, `builtin:llama.cpp`, `builtin:codemode`, and `builtin:tool-search`. They load by default; `"extensions": ["-builtin:mcp"]` disables one, and a project `+builtin:<name>`/`-builtin:<name>` entry overrides the user setting. `pi config` lists them under Built-in.
227
256
 
228
257
  ## Include / Exclude Patterns
229
258
 
@@ -252,6 +281,7 @@ Run `pi --help` for the authoritative list. Most commonly:
252
281
  pi [options] [@files...] [messages...]
253
282
 
254
283
  # package commands: install/remove/uninstall/update/list/config (all support -l for project scope)
284
+ # MCP commands: pi mcp add|remove|list|login|logout (see references/mcp.md)
255
285
 
256
286
  # modes
257
287
  -p, --print single-shot text output
@@ -277,15 +307,16 @@ pi [options] [@files...] [messages...]
277
307
  --no-session ephemeral
278
308
 
279
309
  # tools and resources
280
- --tools <list>, -t name allowlist (read,bash,edit,write,grep,find,ls,...)
310
+ --tools <list>, -t name allowlist (read,bash,edit,write,grep,find,ls,codemode,...)
281
311
  --exclude-tools <list>, -xt disable specific built-in/extension/custom tools, keep the rest
282
312
  --no-tools, -nt disable everything
283
313
  --no-builtin-tools, -nbt disable built-ins, keep extension/custom
284
- -e, --extension <source> repeatable; npm/git/path
314
+ -e, --extension <source> repeatable; npm/git/path, or builtin:<name>
285
315
  --skill <path> repeatable
286
316
  --prompt-template <path> repeatable
287
317
  --theme <path> repeatable
288
- --no-extensions / --no-skills / --no-prompt-templates / --no-themes
318
+ --no-extensions, -ne also disables built-in extensions (pi -ne -e builtin:mcp keeps only MCP)
319
+ --no-skills / --no-prompt-templates / --no-themes
289
320
  --no-context-files, -nc skip AGENTS.md / CLAUDE.md discovery
290
321
 
291
322
  # trust and network
@@ -294,13 +325,15 @@ pi [options] [@files...] [messages...]
294
325
  --offline disable startup network operations (same as PI_OFFLINE=1)
295
326
 
296
327
  # prompts and misc
297
- --system-prompt <text> replace default
298
- --append-system-prompt <text> repeatable; appended with double newlines
328
+ --system-prompt <text|path> replace default
329
+ --append-system-prompt <text|path> repeatable; appended with double newlines
299
330
  --tui-mode regular|fullscreen experimental fullscreen TUI (Pi 0.84.0)
300
331
  --use-theme <name[/name]> per-run initial theme, saved settings untouched (Pi 0.84.2)
301
332
  --verbose
302
333
  ```
303
334
 
335
+ Use `/bug [description]` (Pi 0.86.0) to report a problem with redacted diagnostics, optionally the transcript or a model-written summary; it uploads to Radius or exports a ZIP. Crashes are recorded in `~/.pi/agent/crashes.json` and attached to the next report.
336
+
304
337
  ## Environment Variables
305
338
 
306
339
  | Variable | Effect |
@@ -316,7 +349,7 @@ pi [options] [@files...] [messages...]
316
349
  | `PI_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
317
350
  | `PI_HARDWARE_CURSOR` | `1` shows the hardware cursor (see terminal-setup docs) |
318
351
  | `PI_TUI_ESC_TIMEOUT` | Ms to wait after a lone ESC before treating it as Escape (default 100 over SSH, 10 otherwise; Pi 0.84.2) — raise it if Alt-key input is misread |
319
- | `PI_EXPERIMENTAL` | `1` enables experimental features, e.g. strict JSON-schema sampling for built-in tools (Pi 0.84.2) |
352
+ | `PI_EXPERIMENTAL` | `1` enables experimental features (strict JSON-schema sampling for built-in tools no longer needs it since Pi 0.86.0) |
320
353
  | `AI_AGENT=pi` / `PI_CODING_AGENT=true` | Set automatically by CLI/RPC entry points so child processes can detect Pi (generic vs Pi-specific marker) |
321
354
  | `VISUAL`, `EDITOR` | External editor for `Ctrl+G` (the `externalEditor` setting takes precedence) |
322
355
  | Provider API key env vars | See `references/providers.md` |