Repository navigation
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-modelsis 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. Runatomic update --modelsonce 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
AgentToolfrom@earendil-works/pi-agent-core1.5.0 takes any JSON Schema object asparametersand typesparamsthrough@earendil-works/pi-schema. Extension code that builds anAgentTooldirectly 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 asType.Enum,Type.TupleandType.Intersectnow giveparams: unknown, so narrowparamsyourself or use one of the mapped kinds. Tools registered throughregisterTool()andToolDefinitionare 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 intocustom(custom messages without a registered renderer and skill blocks),summary(branch summaries), andchangelog(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-1for text classification in codemode, using Copilot OAuth or an API key (upstream #11679). - GitHub Copilot discovers models from the account's
/modelscatalog, so models the account offers but the built-in catalog lacks appear in/model,--list-modelsand workflow catalogs. It works with OAuth and API-key credentials, and the list is cached inmodels-store.jsonfor offline startup. A discovered model keeps the exact advertised ID, is markeddiscovered: true, and can be adjusted withmodelOverrides; built-in entries keep their curated metadata. atomic updatelists each updated package with its npm version or git commit change, for examplenpm:foo 1.2.0 -> 1.3.0, and printsPackages are already up to dateonly 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 newresultprogress event whosemessageholds 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
anthropicprovider onapi.anthropic.com; setcompat.supportsMessageThreadsinmodels.jsonto enable it for gateways that forward thethreadfield, 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-1classifier with OAuth and API-key support (upstream #11679). github-copilotdiscovers models from the account's/modelslist.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 markeddiscovered: true;parseGitHubCopilotDiscoveredModels(response, catalog)applies the same rules to a response. It returnsundefined, 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 getscompat.forceAdaptiveThinking, so the selected effort is sent. A fast entry carries fast route metadata only when itsfamilynames another model.Model.discoveredmarks a model found in the account's own model list rather than in the built-in catalog.RefreshModelsContext.updateCredentiallets 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(andcacheRetentionnot"none") now use message threads (message-threads-2026-08-12beta): 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 theanthropicprovider onapi.anthropic.com; setcompat.supportsMessageThreadsto change that, for example for gateways that forward thethreadfield. Fast-mode requests keep theirspeed, and changing it starts a new thread; requests made with a caller-suppliedclientnever use threads.cleanupSessionResources(sessionId)frees a session's thread state (upstream earendil-works/pi#11545) StreamOptions.disableContinuationkeeps 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,nullfor a required string becomes"null", a$refthat 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 (upstream6536f801, adapted to keep TypeBox) - GitHub Copilot fast models are the exact IDs the account's model list advertises; Atomic no longer derives Copilot
<base>-fastvariants 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 runatomic update --modelsinstead 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,enabledModelsand workflowmodel:fields take. This covers/modeland its scoped list, the footer, the startup header, the status line after/modelor 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 readsprovider/id level, orid levelwhere 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 theprovider/idandlevelthat--model provider/id:leveltakes. Display names stay as secondary metadata:/modelshows 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-modelsoutput changed for scripts that parse it. Columns are tab-separated. Theproviderandmodelcolumns are now onemodelcolumn holdingprovider/id, and thethinkingyes/no column is nowlevel: the reasoning level--model provider/idstarts that model at, empty for a model without reasoning. The header row ismodel,level,context,max-out,images(#4269)model_fallback_startsession events carry the optionalfromThinkingLevelandtoThinkingLevelof reasoning models, so a fallback that only changes the level can say so.fromandtoare unchangedprovider/idstrings (#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-coreand@earendil-works/pi-codemodeto 1.5.0 (see Breaking Changes for theAgentTooltyping) - 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()andvalidateToolCall()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'sValue.Convertrules and pi-ai's own:"1"/"0"totrue/false,"null"tonull, a fractional numeric string to an integer ("42.1"to 42), a single value wrapped into an array, andnullfor a required string is now"null"instead of"". Their validation errors list pi-schema's messages (- value: missing,- limit: expected number, got string), and theurlandjson-pointer-uri-fragmentformats are checked. TypeBox-built schemas keep TypeBox's conversion, checks (refinements andFormat.Set()formats included) and messages, andType,Static,TSchemaand the TypeBoxStringEnum()stay exported (upstream6536f801, adapted to keep TypeBox)Tool.parametersaccepts any JSON Schema object:Tool<TParameters extends object = TSchema>. The default is stillTSchema, so existingToolandTool<typeof schema>types are unchanged (upstream6536f801)github-copilotavailability 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 rawprovider/idfollowed by its reasoning level,provider/id high, instead ofprovider/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 asprovider/id. A gateway model ID that contains a slash, such as OpenRouter'santhropic/claude-sonnet-4-5, now shows in full on the card instead of losing its first segment (#4269)
Fixed
/modelshows 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. Runatomic update --modelsonce 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 andSIGTERMarrives 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
bashcall, 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 lockedwhile 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 withwrapNodeSqliteDatabase, now retries the switch until the busy timeout has passed, as every other statement waits. Storage adapters can do the same with the newswitchToWriteAheadLogfrom@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
inputhook 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
inputhook 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'sclearQueue()and the RPCclear_queueresponse keep listing the messages as queued understeeringandfollowUp, and now also list them as typed undertypedwhen any of them changed (#4284) - A message an extension's
inputhook 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 updateno longer stops whengit cleancannot 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
/treenow 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,/resumenames and other fuzzy pickers ranking a query scattered over an earlier part of the text above a contiguous match later in it, so typingopuslists 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.pageUpandtui.select.pageDownnow move the selection in the model, scoped models, auth, user message (/fork), settings submenu, llama.cpp search and/mcpselectors 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 setcapturesViewportInputto receive the fullscreen viewport keys while focused (upstream #7629)github-copilotmodels discovered from the account's/modelslist are named by the display name their list entry advertises (for exampleClaude 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
$refthat 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 (upstream6536f801) - OpenAI Codex SSE retries no longer leave an
abortlistener 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_tokensabove 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
$defsanddefinitions, so property$refs resolve. Before, Claude could send a JSON string where an object was expected, and some Anthropic-compatible gateways rejected the request. RootanyOf/oneOf/allOfare 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 throughkill(-pgid)straight after a probe that found the group alive (#4268). - Several processes opening one
file:database at once no longer fail the open withSQLITE_BUSY: database is lockedwhile 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).