Skip to content

0.9.71

Latest

Choose a tag to compare

@github-actions github-actions released this 03 Nov 23:17
· 805 commits to main since this release
e32a426

Readable model discovery, faster commands, and broader Markdown customization come with the move to pi 1.5.0.

  • Model identity and discovery. Primary labels show raw model IDs and reasoning levels, and --list-models is tab-separated. Copilot discovers the models your account offers, caches them for offline startup, and shows their advertised display names as secondary metadata without changing request IDs. Run atomic update --models once to refresh older cached names (#4275, #4279, #4303).
  • Faster shell commands. Commands return as soon as their processes are gone instead of waiting out a 250 ms grace period (#4274).
  • Chat and queue correctness. Messages sent as a reply finishes, rewritten by an input hook, or handled by a hook no longer leave duplicate or unwanted chat entries. Alt+Up restores what you typed (#4276, #4282, #4287).
  • Session continuity. Anthropic message threads send only new messages when the server can continue the conversation. Concurrent processes can open a new SQLite store without failing on its journal-mode lock (#4293, #4278).
  • Markdown and extension tools. Transformers can opt into custom messages, skill blocks, branch summaries and changelogs, and Mermaid diagrams render in expanded branch summaries. pi 1.5.0 brings terminal and key fixes, page keys in lists, faster code and Markdown rendering, plain JSON Schema validation alongside TypeBox, MCP JSON reply limits, and a Copilot classifier (#4288, #4290, #4291, #4292, #4300, #4301, #4304).
  • Shutdown cleanup. Idle RPC shutdown requests run without another client command. RPC and print mode finish cleanup when stdin closes or a first signal arrives; a second signal still forces exit (#4288, #4303).

Breaking change: If your extension builds AgentTool directly with Type.Enum, Type.Tuple, Type.Intersect or another unmapped schema kind, narrow params yourself or use a mapped kind. registerTool() and ToolDefinition are unchanged.

Copilot discovery now preserves readable model names alongside exact request IDs.

  • Account model discovery. OAuth and API-key users get the chat models their account offers, with advertised names and exact IDs, without replacing curated catalog metadata (#4279, #4303).
  • Anthropic conversation reuse. Message threads send only new messages when the server can continue a session, with full-request fallback when its context changes (#4293).
  • Tool compatibility. Plain JSON Schema tools gain coercion and validation through pi-schema while TypeBox tools keep their existing checks (#4300, #4301).
Full changelog

Breaking Changes

  • AgentTool from @earendil-works/pi-agent-core 1.5.0 takes any JSON Schema object as parameters and types params through @earendil-works/pi-schema. Extension code that builds an AgentTool directly keeps the static type of TypeBox schemas of the common kinds (String, Number, Integer, Boolean, Null, Literal, Array, Object, Union, Record, Unsafe, Unknown, Any); other kinds such as Type.Enum, Type.Tuple and Type.Intersect now give params: unknown, so narrow params yourself or use one of the mapped kinds. Tools registered through registerTool() and ToolDefinition are unchanged

Added

  • pi.registerMarkdownTransformer() takes an optional { messageTypes } that selects the Markdown surfaces a transformer runs on. Besides user, assistant, and thinking Markdown, transformers can now opt into custom (custom messages without a registered renderer and skill blocks), summary (branch summaries), and changelog (What's New and /changelog). Without options a transformer runs on user, assistant, and thinking Markdown as before. The built-in Mermaid renderer now also draws diagrams in expanded branch summaries (upstream #11756, #4304).
  • Added github-copilot/microsoft-decision-1 for text classification in codemode, using Copilot OAuth or an API key (upstream #11679).
  • GitHub Copilot discovers models from the account's /models catalog, so models the account offers but the built-in catalog lacks appear in /model, --list-models and workflow catalogs. It works with OAuth and API-key credentials, and the list is cached in models-store.json for offline startup. A discovered model keeps the exact advertised ID, is marked discovered: true, and can be adjusted with modelOverrides; built-in entries keep their curated metadata.
  • atomic update lists each updated package with its npm version or git commit change, for example npm:foo 1.2.0 -> 1.3.0, and prints Packages are already up to date only when it checked the packages and nothing warned: after a warning it says no packages were updated, and in offline mode it says package updates were skipped. PackageManager.update() reports each changed package as a new result progress event whose message holds that line (upstream #11707, #11711)
  • Anthropic sessions now use message threads (beta): each request sends only the new messages and the server continues from the previous reply, falling back to the full request when the conversation changed (for example after compaction or a tool list change). On by default for the anthropic provider on api.anthropic.com; set compat.supportsMessageThreads in models.json to enable it for gateways that forward the thread field, or to turn it off. Cache warming does not touch the session's thread (upstream earendil-works/pi#11545)
  • Added the text-only github-copilot/microsoft-decision-1 classifier with OAuth and API-key support (upstream #11679).
  • github-copilot discovers models from the account's /models list. discoverGitHubCopilotModels(catalog, context) reads the list with the effective OAuth or API-key credential and returns the chat models the catalog lacks, each under its exact advertised ID, with its advertised display name or the ID when no name is provided, and marked discovered: true; parseGitHubCopilotDiscoveredModels(response, catalog) applies the same rules to a response. It returns undefined, not an empty list, when the list cannot be read (no usable credential, or a 400, 401, 403 or 404 response), so a caller keeps the models it found before. Entries with a disabled policy, hidden from the model picker, without tool-call support, or already in the catalog are skipped, so curated entries keep their metadata. Every entry, fast entries included, takes its API, limits, image input and reasoning efforts from its own row, and a Messages entry that advertises adaptive thinking gets compat.forceAdaptiveThinking, so the selected effort is sent. A fast entry carries fast route metadata only when its family names another model.
  • Model.discovered marks a model found in the account's own model list rather than in the built-in catalog.
  • RefreshModelsContext.updateCredential lets a provider's network refresh save account data it read, such as the model lists an availability filter checks, to the OAuth login the refresh used. Nothing is written once the store holds a different token, so a renewed login or another account is never changed.
  • Anthropic requests with a sessionId (and cacheRetention not "none") now use message threads (message-threads-2026-08-12 beta): the server continues from the previous reply and only the new messages are sent instead of the whole conversation. When anything earlier in the request changed, or the server can't continue the thread, the full request is sent. Token counts and prompt caching are unchanged. On by default only for the anthropic provider on api.anthropic.com; set compat.supportsMessageThreads to change that, for example for gateways that forward the thread field. Fast-mode requests keep their speed, and changing it starts a new thread; requests made with a caller-supplied client never use threads. cleanupSessionResources(sessionId) frees a session's thread state (upstream earendil-works/pi#11545)
  • StreamOptions.disableContinuation keeps a request from using or replacing server-side continuation state such as Anthropic message threads. Set it on requests that repeat an earlier one, such as cache warming (upstream earendil-works/pi#11545)

Changed

  • Arguments of tools with a plain JSON Schema, such as MCP tools, are coerced and checked by @earendil-works/pi-schema: a single value is wrapped into a one-item array, "1"/"0" become booleans, null for a required string becomes "null", a $ref that names nothing rejects only the argument at its place instead of every call, and the errors the model sees use pi-schema's messages (- path: missing). Tools with TypeBox schemas, the built-in tools and most extension tools, are validated by TypeBox as before. See Tool parameter schemas (upstream 6536f801, adapted to keep TypeBox)
  • GitHub Copilot fast models are the exact IDs the account's model list advertises; Atomic no longer derives Copilot <base>-fast variants from the login's fast list. A configured default model, or a resumed session's Copilot fast model, that is not cached yet stops with a message to run atomic update --models instead of switching to another model, also when interactive startup waits for project trust or extensions. A refresh that cannot read the account's list keeps the models cached before.
  • Atomic now uses a model's raw ID followed by its reasoning level as the primary label rather than replacing it with a display name. The ID is exactly what --model, fallbackModels, enabledModels and workflow model: fields take. This covers /model and its scoped list, the footer, the startup header, the status line after /model or Ctrl+P, the per-model thinking settings, the session tree, fallback notices (Falling back from …, Model fallback: …, Model: ran on …, not the requested …), task rows and heartbeats, and subagent and workflow stage labels. A label reads provider/id level, or id level where the provider is already clear, such as the footer with one provider configured. A model without reasoning shows no level. Split on whitespace, a label gives the provider/id and level that --model provider/id:level takes. Display names stay as secondary metadata: /model shows a name that adds words the ID lacks in a dim column, and typing a display name in /model, its argument completion or --list-models <search> still finds the model (#4269)
  • --list-models output changed for scripts that parse it. Columns are tab-separated. The provider and model columns are now one model column holding provider/id, and the thinking yes/no column is now level: the reasoning level --model provider/id starts that model at, empty for a model without reasoning. The header row is model, level, context, max-out, images (#4269)
  • model_fallback_start session events carry the optional fromThinkingLevel and toThinkingLevel of reasoning models, so a fallback that only changes the level can say so. from and to are unchanged provider/id strings (#4269)
  • Tightened the default system prompt: a shorter preamble, one line for additional tools, one rule for reading the docs before harness changes, and a shorter rule for resolving skill-relative paths (upstream 705d047c)
  • Updated @earendil-works/pi-tui, @earendil-works/pi-agent-core and @earendil-works/pi-codemode to 1.5.0 (see Breaking Changes for the AgentTool typing)
  • The Windows guide recommends a terminal by the console host it uses and lists known conhost problems with remedies, and the tmux guide lists the tmux 3.6 color-reply text that can appear in the editor at startup (upstream #11644, #11645, #11647)
  • validateToolArguments() and validateToolCall() validate a plain JSON Schema, one TypeBox did not build (an MCP tool's, a JSON round-tripped declaration, a schema from @earendil-works/pi-schema's builders), with @earendil-works/pi-schema (compile(schema, { coerce: true }).parse()), a new dependency. Such tools get pi-schema's coercion, the superset of TypeBox's Value.Convert rules and pi-ai's own: "1"/"0" to true/false, "null" to null, a fractional numeric string to an integer ("42.1" to 42), a single value wrapped into an array, and null for a required string is now "null" instead of "". Their validation errors list pi-schema's messages (- value: missing, - limit: expected number, got string), and the url and json-pointer-uri-fragment formats are checked. TypeBox-built schemas keep TypeBox's conversion, checks (refinements and Format.Set() formats included) and messages, and Type, Static, TSchema and the TypeBox StringEnum() stay exported (upstream 6536f801, adapted to keep TypeBox)
  • Tool.parameters accepts any JSON Schema object: Tool<TParameters extends object = TSchema>. The default is still TSchema, so existing Tool and Tool<typeof schema> types are unchanged (upstream 6536f801)
  • github-copilot availability keeps fast-route models for a credential without a fast list, such as an API key, because only the account's own model list adds them. A login that lists its fast IDs offers each listed ID even when it is not a picker entry. A network refresh that reads the account's list also saves its model lists to the OAuth login that read it, so a model the refresh discovers is offered at once, while the login's lists still decide for every model, so models another login discovered are not offered.
  • Subagent rows, status lines and Model: ran on …, not the requested … notices show each model as its raw provider/id followed by its reasoning level, provider/id high, instead of provider/id · thinking high (#4269)
  • Graph node cards show a stage's model as its raw ID followed by its reasoning level, for example claude-sonnet-4-5 high, and stage model notices name the model as provider/id. A gateway model ID that contains a slash, such as OpenRouter's anthropic/claude-sonnet-4-5, now shows in full on the card instead of losing its first segment (#4269)

Fixed

  • /model shows the account's display name for GitHub Copilot models discovered from the account's model list instead of repeating the model ID. Model IDs, and the IDs sent to Copilot, are unchanged. Run atomic update --models once to replace names cached by an earlier version (#4303).
  • Process idle extension ctx.shutdown() requests in RPC mode without waiting for another client command, while preserving command responses before exit. RPC mode no longer cuts off shutdown cleanup, such as a pending MCP token refresh, when stdin closes and SIGTERM arrives together, which process managers like Paseo and systemd do. Once a shutdown has started, a stdin close or a single signal no longer interrupts it; a second signal still exits at once. Print mode likewise no longer dies from a signal that arrives during its final cleanup (upstream #11752, #4303).
  • Tool rows reuse syntax highlighting until their code, theme, or grammars change, and streamed assistant messages reuse rendered Markdown blocks (upstream pi 0f21a331).
  • Shell commands report their result as soon as they exit. Every bash call, through a pipe or a terminal (PTY), waited about 250 ms after its command exited, because the task supervisor always waited out its full termination grace before confirming that the command's processes were gone. It now confirms that as soon as they are, so ten trivial commands take about 0.15 s instead of about 2.7 s, and a stopped command whose processes exit on TERM settles at once too. Processes a command leaves running still get TERM, and KILL once the grace ends (#4268).
  • Several processes opening one SQLite store at once no longer fail with database is locked while the file is switched to the write-ahead log, for example pooled workflow processes starting on a new journal. SQLite does not wait for another connection's lock while it changes a file's journal mode, so the switch failed at once whenever another process was writing a file still in the rollback journal or switching it at the same moment. Opening a store, and wrapping a connection with wrapNodeSqliteDatabase, now retries the switch until the busy timeout has passed, as every other statement waits. Storage adapters can do the same with the new switchToWriteAheadLog from @bastani/atomic/storage (#4270)
  • A message sent with Enter just as the agent's reply finished no longer shows twice in the chat in the default interactive mode, where the session runs in an engine process. When the engine had already finished the turn but the terminal had not yet registered it, the engine started the message as a new turn, and the chat showed it once where it was sent and again below the reply to it. The extra copy also stayed when the chat was redrawn, for example by /reload. The model always received it once. Follow-ups sent with Alt+Enter and sessions that run in-process were not affected (#4272)
  • A message an extension's input hook rewrites now shows once in the chat, as it was sent. A message sent while the agent was idle or just as its reply finished showed both the text that was typed and the rewritten text, in the default interactive mode and in-process alike, and the typed copy came back whenever the chat was redrawn, for example by /reload. Messages typed while the agent works, which the session queues, were not affected. The model always received only the rewritten message (#4277)
  • Alt+Up now restores a queued message as it was typed. When an extension's input hook had rewritten a steering or follow-up message, Alt+Up put the rewritten text back in the editor, so sending it again ran the hook a second time and the model received a twice-rewritten message. The same held for a queued skill or prompt template, which came back expanded. This happened in the default interactive mode and in-process alike. The SDK's clearQueue() and the RPC clear_queue response keep listing the messages as queued under steering and followUp, and now also list them as typed under typed when any of them changed (#4284)
  • A message an extension's input hook handles itself no longer stays in the chat. Nothing was sent to the model, but the chat kept the typed text and showed it again whenever it was redrawn, for example by /reload. This happened for a message sent while the agent was idle, with Enter or Alt+Enter, and for one typed while the previous prompt was still being sent, in the default interactive mode and in-process alike. Anything the extension shows itself, such as a notice, is unaffected (#4285)
  • atomic update no longer stops when git clean cannot remove every untracked file of a git package, for example on Windows while another Atomic session has one of the package's native modules loaded. It warns, still installs the dependencies of the new commit, and retries the clean on the next update (upstream #11597)
  • Branch summaries made from /tree now leave out a reply that a context edit hides from the model, such as a failed attempt that an automatic retry replaced, and use the replaced text of edited entries, as the live context and compaction already do (upstream #11680)
  • Fixed Shift+Enter queueing a follow-up instead of inserting a newline in Apple Terminal setups that send ESC + Return; with native Shift detection available, ESC + Return with Shift held is now Shift+Enter (from pi-tui 1.5.0)
  • Fixed fullscreen double-click selection splitting paths and tokens at ~, ., \, + and _; word characters now match iTerm2's defaults (from pi-tui 1.5.0)
  • Fixed searches in /model, /resume names and other fuzzy pickers ranking a query scattered over an earlier part of the text above a contiguous match later in it, so typing opus lists the Opus models first (from pi-tui 1.5.0)
  • Fixed display math wider than the chat being wrapped row by row, which split fractions and sums apart; it now shows the raw LaTeX source (from pi-tui 1.5.0)
  • Fixed wrapped lines starting with a space when a styled word ended exactly at the wrap width, and strikethrough continuing into the padding of wrapped lines (from pi-tui 1.5.0)
  • Fixed Orca terminals falling back to text for inline images; Orca is recognized as supporting true color, hyperlinks and, when it advertises ORCA_IMAGE_PROTOCOL=kitty, Kitty images (from pi-tui 1.5.0)
  • Fixed bare URLs in Markdown swallowing the CJK punctuation and text after them (from pi-tui 1.5.0)
  • Fixed Kitty-protocol F1–F12 shortcuts, including modified function keys, not being recognized, and F13+ keys inserting invisible characters into the editor (from pi-tui 1.5.0)
  • Fixed Kitty images inside padded or colored boxes disappearing once their first row scrolled above the fullscreen viewport (from pi-tui 1.5.0)
  • Fixed deeply nested Markdown blockquotes producing exponentially many lines and hanging or crashing the renderer (from pi-tui 1.5.0)
  • tui.select.pageUp and tui.select.pageDown now move the selection in the model, scoped models, auth, user message (/fork), settings submenu, llama.cpp search and /mcp selectors and in editor autocomplete, and PageUp/PageDown reach a focused list instead of scrolling the transcript. An open autocomplete menu takes the page keys before app actions, so binding a page key to Ctrl+V pages the menu instead of pasting an image. Extension components can set capturesViewportInput to receive the fullscreen viewport keys while focused (upstream #7629)
  • github-copilot models discovered from the account's /models list are named by the display name their list entry advertises (for example Claude Sonnet 4.5) instead of repeating their ID, and fall back to the ID when the entry has no name. IDs are unchanged, so requests still use the exact advertised ID (#4303).
  • A plain JSON Schema tool whose schema has a $ref that names nothing (a missing definition, an external document), as some MCP servers send, now rejects only arguments at that place (- extra: unresolved reference #/definitions/Missing) instead of failing every call of the tool (upstream 6536f801)
  • OpenAI Codex SSE retries no longer leave an abort listener on the caller's signal after each completed backoff (upstream #11708)
  • The default output limit now leaves 15% of the estimated input unused (at least 4096 tokens), so OpenRouter, which counts the input with its own tokenizer and rejects input plus max_tokens above the window, no longer rejects models whose output limit is close to the window with a 400 that was then treated as a context overflow. When that margin would leave less than 32768 tokens of output, only the fixed 4096 margin is kept and output is capped at 32768 (upstream #10497)
  • Anthropic non-strict tool schemas keep the root $defs and definitions, so property $refs resolve. Before, Claude could send a JSON string where an object was expected, and some Anthropic-compatible gateways rejected the request. Root anyOf/oneOf/allOf are still left out because Anthropic rejects them (upstream #9557)
  • OpenCode Go and Zen Claude 5.5 models, Opus 5 and Fable 5.1 now use per-message effort and send thinking.block_binding: drop_block, like the Anthropic provider. OpenCode Go Claude Haiku 5.5 failed with a 400 (Invalid signature in thinking block ... bound to a different conversation) after the tool list, system prompt or earlier history changed, and kept failing on every retry (upstream #10954)
  • Streamable HTTP JSON replies now respect the configured message-size limit, and oversized HTTP error bodies are cancelled after reading a bounded excerpt (upstream #11749)
  • Closing a stdio MCP server whose main process already exited now still stops the children it started. On Linux and macOS a server can exit while a child it started keeps running and holds its output pipes; close() returned early, skipped the process-group cleanup, never fired the close event, and the child also survived Atomic exiting. If a process outside the server's process group still holds the pipes after the kill, close() stops waiting for it a second later (upstream #11724)
  • A Streamable HTTP response, or the notification stream, whose SSE event exceeds the message-size limit is now cancelled instead of left open. If the server kept the response open, for example with heartbeats, the failed request held its HTTP connection until the server ended it or the client closed (upstream #11729)
  • The task supervisor settles a command as soon as its process group is empty instead of always waiting out the 250 ms termination grace after the command exits. It now reaps the exited shell right after sending its group TERM and probes the group: a group with nobody left settles at once, and members still alive get KILL when the grace ends, as before. A stopped command whose processes exit on TERM settles at once too. On Linux 6.9 and later the group is signalled and probed through a pidfd of the shell (PIDFD_SIGNAL_PROCESS_GROUP), so a group ID the kernel has since handed to another process can never receive the KILL; elsewhere the KILL goes through kill(-pgid) straight after a probe that found the group alive (#4268).
  • Several processes opening one file: database at once no longer fail the open with SQLITE_BUSY: database is locked while the file is switched to the write-ahead log. SQLite does not wait for another connection's lock while it changes a file's journal mode, so the switch failed at once whenever another process was writing a file still in the rollback journal, such as a new file or one from before the adapter used WAL, or switching it at the same moment. The open now retries the switch for up to the 5-second busy timeout, as every other statement waits (#4270)
  • A slow route announcement no longer delays the route changes after it by four times its own duration. Once announcing is costly, the pending-stage Intercom bridge spaces its route announcements so that announcing stays within about a fifth of the host's time, but it sized the gap from the wall time of the last announcement alone and set no upper bound, so one announcement stretched by a garbage collection pause or a busy machine held back the next stage aliases, pending-to-running changes and possible-stage rows (a 30 ms stall delayed the next change by 112 ms). The gap now follows the median of the last three announcements, so one slow announcement is ignored, and no route change waits longer than 250 ms for its announcement. Announcements that are repeatedly expensive are still spaced to about a fifth of the host's time (#4264)

Removed

  • Removed the native sandbox extension example; use the documented container and VM isolation options instead (upstream pi b4c2e92a).