Skip to content

Decisions Log

Baseline delta: 0.3.00.4.15 Date: 2026-08-05 Status: Accepted for implementation

This log freezes previously open questions into concrete decisions.

A. High-priority architecture decisions

IDTopicDecisionRationale
D001Electron ↔ Rust transportRust sidecar + stdio JSON-RPC (NDJSON)Simple isolation, debuggable, replaceable later
D002SQLite ownershipRust host-core owns SQLite exclusivelySingle writer, clearer privilege boundary
D003Default modeAgentProduct is an agent desktop, not pure chat
D004Former restricted profile(superseded by D189) The former Chat read-only profile is removed; persisted Chat values migrate to Plan.The product now has one Agent with a planning state and a separate approval boundary
D005Permission timeout120s → denyFail closed, do not hang forever
D006allow-session scopeBy toolNameSimple UX; workspace sandbox still enforces path safety
D007~/.pi compatibilityNo auto-import in MVPKeep config ownership clean in ~/.pi-desktop
D008Node runtime packagingDev uses system Node; release runs the bundled sidecar on the Electron binary via ELECTRON_RUN_AS_NODE=1 (no separate Node shipped)Unblock M1–M4; resolved at M5, see 03-runtime/07-process-model §6
D009Plugin runtime isolationTarget = separate process; M4 may use host-managed sandboxed runtimeShip plugin foundation pragmatically without weakening API gateway
D010First release platformmacOS arm64 onlyFocus acceptance and packaging

B. Secondary implementation defaults

IDTopicDecision
D011TS schema validationtypebox
D012i18n libraryi18next + react-i18next
D013Bash execution style in M3Non-interactive only (no PTY yet)
D014Command palette shortcutCmd/Ctrl + Shift + P
D015Plugin tool exposed nameForced prefix plugin_<pluginIdSafe>_<toolName>
D016Uninstall plugin dataDelete by default, optional keep-data later
D017enable → load failureAuto fallback to disabled
D018Plugin secrets in settingsNot allowed in MVP
D019Plugin session summary accessDenied by default
D020Auto-updatePost-MVP
D021First-run onboardingInline checklist (not modal wizard)
D022Local telemetryLocal logs only in MVP (no remote telemetry)

C. Provider & model coverage decisions (0.3.4)

IDTopicDecisionRationale
D023Provider coverage goalUniversal market coverage (not a tiny fixed vendor list)Globalization + real coding workflows
D024Coverage strategypi-ai native providers + first-class OpenAI-compatible + custom providersMaximum reach without rewriting every SDK
D025Model allowlistNo closed product allowlistModels churn; power users need free-form IDs
D026Catalog sourcesbundled snapshot + discovery/refresh + user-definedWorks offline and stays current
D027Default identityModel selection is (providerId, modelId)Same model id can exist on many gateways
D028SecretsOS safeStorage (or controlled fallback) via secretRef; never in provider JSONSecurity boundary with Rust host ownership
D029Local modelsSupported through OpenAI-compatible local gatewaysOllama/LM Studio/vLLM without special-case architecture
D030Connection testFirst-class host method before trusting provider for runsFail early, actionable setup UX
D031Secrets backendOS safeStorage primary + encrypted file fallbackRobust on macOS first release
D032Workspace ignoresecurity denylist + defaults + .pi-desktopignoreSafe/predictable tool FS behavior
D033Tool result limits256KB/4000 lines defaults with explicit truncation markersProtect context & UI

D. Codex visual parity decisions (0.3.5+)

Gold source: local Codex electron captures; latest row wins where rows conflict.

IDTopicDecisionRationale
D034Desktop visual baselineCodex electron-dark 1:1 shell (charcoal gray, floating composer, ~275px sidebar)Match local Codex usability and density; keep PI-Desktop product branding
D035Shell display name(superseded by D094) UI chrome uses shellName "Codex"; product/about remains PI-DesktopSatisfy visual 1:1 replica goal while preserving product identity in about/settings
D036Theme chrome tokensAll shell chrome (nav, threads, chips, title buttons) uses semantic --ds-* text/surface tokens; no raw gray-0 text in light modeLight macOS default was unusable when nav used white ink on #f3f3f3
D037Dark sidebar surfaceDark sidebar uses #000000 (Codex surface-under); main pane stays #181818 (gray-900)Match electron-dark sideBar vs main surface separation
D038Dark composer plate(superseded by D047, then D061) Dark floating composer uses solid #212121 with stronger elevation shadow than lightCodex elevated-primary must read as a box against #181818; transparent mix alone looks flat
D039Stage Manager boundsPermanent host watchdog restores footprint while width/height remain collapsed20s burst was insufficient under Stage Manager thrash
D040Composer intelligence control(superseded by D091) Custom effort chip opens a popover (effort radio + model heading + settings) instead of cycling on clickReplaced because the control changed labels without configuring pi
D041Profile footer(superseded by D113) Custom footer opens profile menu (Settings / Logs / Theme); cloud badge remains update stand-inD113 replaces the cloud/update stand-in and generic gear row with a truthful local-profile footer
D042Projects page(superseded by D066 index table) Projects is a card grid of recent/active local workspaces with pin + glyph color (localStorage recents)Match Codex Projects destination density without cloud project backend
D043Settings shell(superseded by D062/D063 full-page shell) Settings uses left nav rail on sidebar surface + content pane (General/Providers/Plugins/About)Closer to Codex settings IA than a top-only tab strip
D044Destination list chromePRs/Scheduled/Plugins use shared dest-row list + filter chips; light cards white elevatedMatch Codex destination density without full cloud backends
D045Home empty stackEmpty chat keeps composer in home flow (not absolute bottom-only dock); refined by D047 split growInitial fix for large empty gap; D047 corrects dual-grow vertical model
D046Composer placeholder(superseded by D094) Empty draft uses Codex placeholder (EN/zh-CN) instead of blankEmpty white plate read as broken without ink; match the earlier visual gold copy
D047Home split grow(geometry superseded by D111) Empty home used upper/lower grow regions (hero items-end + composer justify-end); dark box uses Codex elevation-prominentMatch electron dual grow; D111 replaces dual-grow portal with a scrollable flow stack to stop composer/card collisions
D048Sidebar recents label(superseded by D088) Recents section uses live Codex gold label EN Recents / zh-CN 最近 (not asar-only Tasks/任务)Visual gold + live coding shell section heading between plugins and thread list
D049Home suggestion cards(superseded by D131) Empty home shows 4 ambient cards under hero (auto-fit row) and prefills starter prompts on clickD131 removes the card row and prompt-prefill entry points from the empty home
D050Empty composer plate height(superseded by D055, then D061) Home empty composer min-height ~112px (compact); model chip shows model id (effort stays in menu)Match Codex empty plate density; model chip chrome closer to electron model picker trigger
D051Sidebar nav density(session-list IA superseded by D088; row density retained) Nav rows ~32px pitch, recents rows ~28–31px, section label 最近/RecentsClose light-home sidebar residual vs cx-home-clean
D052Home vertical + night box polish(workspace-chip surface superseded by D095; remaining guidance retained) Upper pb ~62px (hero first-ink ~y305); light chips #f3f3f3; light composer elevation stronger; dark home composer solid #212121; toolbar controls 28pxClose residual heat at hero y≈300 and composer band; night plate must not flatten into #181818
D053Stage Manager CG detectionCG bounds helper matches any window layer by pid; missing-CG needs streak≥3 before shelf recovery; avoid permanent alwaysOnTopalwaysOnTop floating layer broke layer-0 helpers and caused restore thrash
D054Empty draft row + infinity cue(∞ cue superseded by D094; leading brand cue superseded by D160) Composer auto-resize must never collapse empty textarea height (<28px); keep a visible brand cue left of draft; solid disabled send (#bdbdbd light); denser placeholder ink; night plate solid #212121Empty height:0 auto-resize hid placeholder and read as broken night/light box; gold draft row needs visible mark + ink density
D055Empty plate draft Y(plate-height guidance superseded by D061; workspace-chip density superseded by D095) Home empty shell min-height ~148px (bottom-aligned) so draft densest ink ≈y556 vs gold; chips compact 28px112px plate left draft ~30px low; grow plate upward without moving toolbar footing
D056Empty-home workspace chips(superseded by D095) Hide project/Local/branch capsule on empty home always; show only in thread-docked composercx-home-clean empty gold has no capsule band above the plate even with project title
D057Home mark + hero title optical(home mark superseded by D094; title guidance retained) Empty-home Codex mark uses denser stroke; short workspace basenames display as PI-Desktop for gold title spanHero residual was thin mark + short project label under-inking title vs Codex gold
D058Home content width + dark ink tokensHome dual-grow max width uses 768px (not 48rem under 14px root); home horizontal pad 12px; hero title/night controls use theme tokens; night home plate scoped to dark only48rem at 14px root shrank plate ~120px vs Codex gold; hardcoded light hero ink made night title unreadable
D059Light disabled send inkDisabled send chip #8e8e90 + white arrow (not #bdbdbd)Pixel-match cx-home-clean empty send control
D060Light New task ghost rowLight empty-home New task is transparent (no solid chip); only hover washGold has icon+label without filled pill; filled #e8 chip was main nav residual
D061Empty plate Y + night elevated-primaryHome empty plate min-height 140px + wrap bottom pad 16px (top ~y536–538 / draft ~y552 / foot shadow ~y674); light+dark home plates use elevated-primary fill and downward elevation (no upward omni glow); dark fill #212121f5Plate was high with pre-plate halo; solid night plate + heavy omni shadow diverged from Codex elevated-primary and gold foot band
D062Settings Codex shellSettings uses Codex grouped rail (Personal/Integrations) + search + Back to app; content is elevated row panels; Providers/Plugins retained for local-first; MCP empty state under IntegrationsDestination parity gap; prior 4-item flat rail diverged from Codex settings IA
D063Settings full-page takeoverSettings replaces app sidebar with Codex full-page shell: back+search+icon groups (Personal/Integrations/Coding), elevated permission/general cards, local Providers/Plugins retainedNested settings-inside-main-pane diverged from live Codex settings gold
D064Settings general content parityBasics card rows match Codex: default open target, language, menu bar, bottom panel; nav adds Pets/Appshots; sun/pet/snapshot icons; pill selectsCloser 1:1 to live Codex settings gold content band
D065Settings general gold polishPermission rows include blue Learn more links + full-access risk copy; open-target pill shows VS Code glyph; Agent uses circular-arrow icon; Integrations order Appshots→Plugins→Browser→Computer→MCP; Enter-to-send moves to AgentResidual gaps vs cx-settings-try after full-page shell
D066Home-with-project chrome + projects index(composer intelligence label superseded by D091; workspace-chip portion superseded by D095) Home shows workspace chips when project open (no ∞); home placeholder 随心输入/Ask anything; footer gear+help; Projects page is Codex index (search/columns/expand/actions) using setProjectGold cx-home-clean with project + projects-index-page parity
D067Home suggestion glyphs + chip gap(suggestion-glyph portion superseded by D131; composer chip-gap portion superseded by D095) Suggestion icons match Codex (code/hammer/refresh/bug) with blue/purple/green/orange tones; composer chip gap 8px and denser capsuleD131 removes the cards; D095 remains authoritative for composer spacing
D068Recents row actions + fixture titles(sidebar actions superseded by D088, then reorganized by D093; fixture-title guidance retained) Active/hover recent rows show pin + panel trailing actions; capture/fixtures prefer Chinese titled empty sessions (同步代码) over bare New taskGold sidebar selected row chrome; reduce selection residual
D069Destination title scale + dark New task ghostDestination page titles use Codex 28px/560 weight; New task is transparent ghost in dark too; capture drops English noise fixtures and pins 同步代码PR/Projects title mismatch; dark New task read as selected chip
D070Settings gold metric polishSettings rail 275px/#f4f4f4; denser nav; content title offset; 32×20 accent toggles; Account arrow-up-right; 14px cards; 720px content bandResidual vs cx-settings-try (rail width, toggle size, title Y, external mark)
D071Transcript interaction parityTool calls render as Codex-style lightweight disclosure rows (caret + name + mono arg hint + spinner/status, clamped inset body) replacing boxed cards; auto-scroll only while pinned to bottom with floating jump-to-latest pill (send / retry / regenerate re-pin per D151); shimmer Working… line with elapsed time; hover copy on messages and code blocksBoxed tool cards and forced scrollIntoView diverged from Codex transcript feel; spec 7.4 scroll pause was unimplemented
D072Typography/radius token enforcementAll font-size/weight/line-height/letter-spacing/border-radius values must use @theme token vars (--text-* ramp with -plus half-steps, --font-weight-* incl. 520/560, --leading-*, --tracking-*, 12-step --radius-*); raw literals in CSS and TSX arbitrary utilities are blocked by scripts/check-style-tokens.mjs wired into pnpm lint; pixel values preserved exactly (no visual change)~130 scattered literals drifted from any scale; design-system doc §5.2/§6.2 tables were stale vs implementation
D073Full renderer i18n coverageEvery user-visible renderer string flows through i18next (en source of truth, zh-CN via satisfies EnglishCatalog): ContextPanel/CommandPalette/PermissionDialog wired; toast/aria/title/placeholder literals keyed; session default titles come from i18n.t with a shared case-insensitive isDefaultSessionTitle matcher covering legacy titles across locales; proper nouns (VS Code, Finder) and native language names stay untranslatedSix components bypassed i18n entirely; default-title matching was duplicated in store and Sidebar and missed zh "新对话"
D131Empty home without suggestion cards(starter-grid clause amended by D205, then superseded by D206) The empty chat home temporarily rendered only the hero, optional first-run checklist, and composer; the original four Explore / Build / Review / Fix cards, their colored glyphs, and their prompt-prefill actions were removed. This superseded D049/D067 and the original card-specific clauses of D111 while retaining its single scrollable flow layout.The direct composer remained the primary task entry; removing the original decorative starter row addressed noisy card treatment. D205 briefly reintroduced a quieter, non-submitting developer grid before D206 returned the empty state to direct entry.
D133Project index moves to Settings archive(five-destination count/order superseded by D166; flat-list presentation superseded by D168) The home sidebar no longer has a standalone Projects destination. Settings adds Project archive (zh-CN: 项目归档) after Import and before Info, bringing the compact directory to Basics / Model configuration / Import / Project archive / Info. The archive reuses the durable Projects index and always includes archived records, with search, add, activate, task expansion, pin, archive/restore, and close actions. Project and session groups remain in the home sidebar for active work. Global search exposes the archive as a Settings result, not a standalone page. This supersedes the standalone-destination clauses of D042/D066 and D090's four-destination limit without changing project storage or activation semantics.Active project work already lives in retained sidebar groups; moving historical project management into Settings reduces primary navigation while keeping recovery and archive controls discoverable.
D168Project archive presentation redesignProject archive renders three stacked bands: an overview banner (intent sentence, primary Add project, and four derived counters for projects, open, archived, sessions), a toolbar (search with clear affordance and live match count, plus a Recent/Name sort segmented control), and a grouped index whose always-visible sections run Pinned / All projects / Archived with per-section counts, one settings panel per section, and hairline row separators. Rows carry a disclosure control, glyph, name with Active/Open/pinned/Archived tags, one meta line (shortened monospace path, branch, session count), relative last-active time, and hover/focus-revealed New task plus row menu; the menu groups create/edit above pin, archive/restore, and destructive Close, and dismisses on Escape or outside press. Archived rows are grouped and softened, never hidden or filtered, so D133's no-visibility-toggle rule still holds. This supersedes D133's flat-list presentation without changing project storage, search matching, session batching, or activation semantics.The flat single-list archive gave equal weight to pinned, working, and archived records and hid its disclosure and row actions behind hover, so scanning a long durable index meant reading every row. Grouping with derived counters and an explicit sort makes state legible at a glance while keeping archived history permanently reachable.

E. M5 hardening decisions (0.4.0)

IDTopicDecisionRationale
D078macOS signing lanesStatic config stays unsigned (identity: null) for local builds; scripts/release-macos.sh injects Developer ID + hardened runtime + optional notarization from envContributors build without certs; releases sign per 06-delivery/06-release-runbook
D079App icon / brand mark v1build/icon_1024.png is the canonical PI-Desktop logo; scripts/make-icon.py derives build/icon.icns without overwriting the PNG; packaged macOS builds, pnpm dev, and renderer chrome reuse those assetsKeep one visual identity across development, renderer, and packaged lanes while preventing the derivation script from restoring the obsolete generated mark
D080Backend supervisionChild exit rejects in-flight RPCs immediately; backoff restarts (0.5s→4s, max 3 per 2min); hostStatus events drive renderer degradation UICrash recovery without hangs; fail visible, not silent
D081Renderer sandboxsandbox: true with fully bundled CJS preload; production CSP drops unsafe-eval and localhost connect-srcElectron security baseline; verified by test:e2e:boot
D082Log channels(superseded in part by D182) app/host/agent NDJSON files with 5MB rotation (keep 2 rotated) via main-process Logger; audit channel stays in host-core SQLiteDiagnosable failures without unbounded growth; audit needs queryability
D083Window statePersist last good bounds to window-state.json (min 960×640 to restore); Stage Manager shelf recovery keeps the Codex footprint; capture runs force deterministic boundsUsers keep their window; shelf recovery and pixel captures stay deterministic
D084Cross-platform shell strategy(superseded by D190) The Bash tool runs bash on every platform, resolved once per process: PI_DESKTOP_BASH override → Unix well-known paths + PATH → Windows bash.exe derived from Git for Windows (git on PATH, standard install dirs, then PATH minus the WSL System32 launcher); Unix uses bash -lc, Windows bash -c + CREATE_NO_WINDOW; no bash bundled in installers; missing shell surfaces stable SHELL_NOT_FOUND with install guidanceSuperseded by the selectable shell catalog and stable execution identity in D190
D085Toast system v2Single global toast stack (ToastHost + store queue) replaces the string setToast: showToast(message, {variant, duration}) with info/success/warning/error variants (Lucide icon tinted by semantic token on a neutral elevated plate), auto-dismiss 4s / error 8s / 0 sticky owned by the system (no caller timers), hover pause, max 4 with dedupe, enter/exit motion + reduced-motion-safe removal, aria-live + role status/alert; usage rules in 08-component-spec §17Old toast was a bare fixed div: no variants or stacking, and most call sites never cleared it so messages persisted forever; callers hand-rolled timeouts
D086Storage schema v2Single pi.sqlite (host-core exclusive) rebuilt per 03-runtime/04: kv namespaces replace meta/settings and host plugin settings; projects replaces the workspace singleton; transcripts become canonical block arrays (messages.content_json + extracted text, ms-integer times, O(1) per-session seq, stable mid rowid) with turns carrying state-machine status + usage rollups and FTS5 trigram search; new models catalog, artifacts, scheduled_tasks+task_runs (moved out of Electron's JSON, fixing a D002 violation); indexed prunable audit_log; PRAGMA user_version migrations with pre-migration .bak; dead plugins/provider_models tables dropped (registry.json stays authoritative)v1 schema was a lossy UI projection (no turns/usage/blocks/attachments), ordered by MAX+1 scans, had zero secondary indexes, two dead tables, RFC3339 text times, and scheduled tasks bypassing host ownership; spec'd features (artifacts view, cost chips, run history, project grouping, global search, catalog refresh) had no storage to land on
D087Immersive composer context rail(superseded by D095) Project / Local / branch remain one rail, but the rail now attaches directly to the composer shell, shares its theme surface and sole elevation, and drops the visible 8px gap plus independent capsule shadow; supersedes the gap portion of D067The detached capsule and differently colored plate made context and prompt input read as unrelated controls instead of one Codex-style immersive composer
D089Composer draft heightThe prompt textarea shows one visible line by default, auto-grows from wrapped content through seven visible lines, scrolls internally beyond line seven, and contracts as content is removed; the home shell is content-driven instead of keeping D061's fixed 140px minimumPreserve transcript space and Codex-like density while keeping multiline editing usable
D088Scoped home sidebar sessions(superseded by D093 only for the one-current-project and no-row-actions limitations) Replace the Recents aggregate with one current-project session group plus persistent path-less Temporary sessions; keep other projects in the Projects index; remove Recents pin/panel row actions; scope empty-draft reuse and explicit + creation by project contextD093 retains exact-path grouping, scoped draft reuse, and the Temporary boundary while allowing several retained project groups and scoped organization actions

F. Baseline 0.4.2 product decisions

IDTopicDecisionRationale
D090Compact settings directory(four-destination count and order superseded by D133) Settings retains the D063 full-page shell and D070 visual metrics, but its rail contains exactly Basics, Agent, Import, and Info in that order. Appearance moves into Basics; Providers moves into Agent. Plugin management remains in the app shell's existing Plugins destination, with load/enable/disable/uninstall available there, and is not duplicated in Settings. This supersedes the broader grouped navigation and standalone Appearance/Providers/Plugins placements in D062–D065, plus D070's Account-specific rail metric.Remove empty, low-value, and duplicate destinations while keeping every shipped workflow reachable and making the local-first settings surface easier to scan
D091Composer runtime configurationMode and provider/model controls update the active session and are read from that session by the pi prompt path; controls without an end-to-end runtime implementation are not rendered.Prevent decorative effort/attachment controls and keep every visible composer action operational
D092Responsive settings contentThe settings content fills the width available after the fixed 275px rail and pane gutters, resizing through CSS flex layout with the native window. This supersedes only D070's fixed 720px content band and the corresponding visual-metric retention in D090.Use wide desktop windows efficiently without adding renderer resize state or changing the compact settings directory
D104Settings rail menu rename(Agent label superseded by D110; directory order superseded by D133) Settings rail destination labels are Basics / Agent / Import / Info (zh-CN: 基础 / 智能体 / 导入 / 信息). Destination IDs remain general / agent / import / about; only user-facing labels change. This renames the D090 compact directory labels without changing order, contents, or deep-link targets.Shorter, action-oriented labels scan faster while preserving the compact directory

G. Baseline 0.4.3 sidebar and project decisions

IDTopicDecisionRationale
D093Sidebar organization, retained project tabs, and session workspace isolationThe renderer retains normalized open-project paths and local project/session presentation metadata for pin, archive, collapse, sort, and optional compatibility order. The sidebar renders one independently collapsible group per retained path plus Temporary sessions. User-facing sort modes are recent, created, oldest, and name; manual remains a persisted compatibility value without a new reorder gesture. Activating a group reuses project.set, so the shell still has one selected host workspace. Tool execution resolves its root from the durable session project, and per-session turns/grants remain independent when another tab becomes active. Archive and close are non-destructive.Preserve a multi-repository working set and make long conversation lists manageable without creating multiple host workspace singletons or allowing an active-tab switch to redirect a background session's tools
D094Renderer product branding(docked composer logo superseded by D160) All user-visible shell identity uses PI-Desktop: the sidebar shell name, composer placeholder, and settings copy. BrandLogo imports canonical build/icon_1024.png through Vite for the home hero, expanded/collapsed sidebar, and docked composer; new-session controls use a dedicated message-plus icon. Codex remains only where it identifies an external import source or a design reference.Remove accidental third-party branding and vector approximations from the product surface while preserving import compatibility and the Codex-derived layout system
D135Distinct sidebar task status indicatorsConversation rows reserve one compact leading status slot with semantic, shape-distinct states: neutral-accent outlined ring for selected, warning-orange breathing dot for in progress, success-green check for completed, and error-red circled alert for failed. Precedence is in progress, selected, then latest terminal outcome. Starting a new turn clears the prior outcome; abort produces no failure. Every state has localized accessible text and reduced motion makes the in-progress dot static.The prior running dot reused the accent token and disappeared when idle, making selected, active work, completion, and failure difficult to scan or distinguish by more than row background

H. Baseline 0.4.4 composer decisions

IDTopicDecisionRationale
D095Remove composer workspace context railThe home and thread-docked composer variants never render the passive project / Local / branch rail or reserve layout/elevation for it. Project selection, session binding, branch metadata, and workspace-scoped tools remain available through non-composer surfaces. This supersedes the context-rail portions of D052, D055, D056, D066, D067, and D087.The rail duplicated navigation, showed two passive values, and could display misleading fallback branch metadata while consuming prompt space

I. Baseline 0.4.5 thinking-mode decisions

IDTopicDecisionRationale
D096End-to-end thinking mode**Thinking level is session-scoped with canonical values `offminimal
D102Custom provider thinking presetsSettings expose Off / On-off only / Graded (plus advanced custom lists). On-off only persists supportedThinkingLevels: ["off","high"]; Graded clears the sparse override and uses the conservative default set; Composer renders only the resolved set and never invents graded options for boolean-like models such as mimo.Custom OpenAI-compatible endpoints often expose boolean thinking rather than a full effort ladder

K. Work panel decisions

IDTopicDecisionRationale
D097Docked work panel replaces context panelThe right side hosts a docked, drag-resizable (320–min(720px, 60vw)) work panel with Review / Terminal / Browser / Files tabs, toggled by the titlebar button or Cmd/Ctrl+J; {open, tab, width} persist in localStorage. The former ContextPanel overlay and its context.* copy are removed; workspace/model/status live in composer chips and Settings.Codex-parity working surface for inspecting agent output; the info-only overlay duplicated data available elsewhere
D098Review tab reads the git working tree (superseded by D180)Historical decision: Review rendered the workspace's uncommitted state through the git CLI.Superseded because commits erased message-level evidence and could not support safe per-message rollback
D099Terminal tab is a real PTYTerminal runs an interactive login shell in the active workspace via node-pty in the Electron main process with xterm.js in the renderer; sessions are keyed per workspace, survive tab switches and panel close through a main-side replay ring, and die with the app.Codex parity requires a usable terminal, not a command replay; PTY ownership stays in main so the sandboxed renderer never touches processes
D100Browser tab embeds a WebContentsViewThe preview browser is a main-process WebContentsView with renderer-driven bounds sync, hardened (deny popups→external, deny permission requests, http(s)-only navigation, isolated persist partition); it hides while blocking overlays (palette, permission dialog, settings) are open. The user drives it; the agent does not.Recommended modern embedding without webview-tag caveats; hide rules resolve its compositor z-order over renderer overlays
D138Session-scoped inline permission requestsTool approval is an inline PermissionCard owned by its originating sessionId, never a global dialog. Different sessions retain independent pending requests and absolute timeout deadlines; background message/tool/permission events update only scoped state and never activate, cover, or focus another conversation. Resolution and cleanup match both session and request identity. This supersedes only D100's permission-dialog overlay/hide clause; palette, search, settings, and other blocking surfaces retain their existing browser hide behavior.Concurrent agents must not steal the active workflow or overwrite each other's approval requests; the existing protocol already carries the required session/request identity.
D139Navigation intent and shortcut event guardsEvery explicit session, project, page, fork, or history navigation begins or reuses one renderer navigation intent; asynchronous work commits visible state only while that intent remains current. Global shortcuts ignore modifier-only and IME composition events, while history navigation also ignores key-repeat events.Late session/project loads and incomplete keyboard events must not cause unexplained page or history jumps.
D128Artifact-driven work panel tabs (shortcut clause superseded by D207)The work panel has no empty manual entry point, welcome chooser, titlebar/menu command, or Cmd/Ctrl+J shortcut. A file/URL/BrowserPreview/successful-command artifact creates and activates a closeable top tab; successful active-session workspace Write/Edit artifacts create and activate the singleton Review tab. File tabs are keyed by lexically normalized path, singleton tool tabs deduplicate, closing the active tab selects its right neighbor then left, closing the last tab hides the panel, and the sole panel-level control collapses it without deleting retained runtime tabs. Changing the visible session or workspace closes and clears tabs so relative resources never cross context boundaries. Startup is closed with no tabs; only panel width persists, while temporary OS-window expansion is excluded from launch bounds. Background-session, failed, and scratch writes do not steal focus. This supersedes D097's fixed tab entry points and {open, tab} persistence, refines D098's automatic refresh, and supersedes only D112's welcome-chooser clause.Match Codex's output-driven work surface, avoid an empty tool launcher, and make each visible tab correspond to work the session actually produced or explicitly previewed. D128 corrects the initially duplicated D119 identifier; D119 remains the transcript file-store decision.
D140Session-owned dirty-workspace transcript review entry (superseded by D179)After a session produces a successful workspace Write/Edit, its transcript ends with one explicit Review changes command outside collapsed activity groups while that Git working tree remains dirty; other sessions in the same project do not inherit the command. It reports the capped file count and addition/deletion totals and creates, reopens, or activates D128's singleton Review tab. The entry and Review share one workspace-keyed diff refreshed on workspace activation, successful Write/Edit/Bash completion (500ms debounce), explicit Review refresh, and window focus; sequenced requests discard prior-workspace responses. Clean and non-Git results clear review ownership for that workspace; clean, non-Git, missing-workspace, and failed-refresh states hide the entry. Review ownership is renderer-memory state discarded on relaunch with D142's work-panel contexts. This is a contextual artifact/status entry, not the empty manual launcher forbidden by D128.Automatic panel opening alone leaves no discoverable return path after collapse or tab close, while session ownership prevents unrelated conversations from claiming project-wide edits. Sharing the real diff makes the conversation entry accurate and keeps Review deduplicated.
D179Message-scoped inline review cards (superseded by D180)Historical decision: each successful workspace Write/Edit row rendered a card by matching the current workspace diff.Superseded because current Git state disappears after commit and cannot provide message-owned rollback
D180Message-owned review snapshots and guarded rollbackSuccessful workspace Write/Edit results carry bounded details.review evidence: snapshot id, message/tool id, path, operation, added/modified/deleted status, +/− counts, hunks, and rollback state. The host stores pre-tool bytes and before/after hashes outside the workspace under the session, and the renderer derives the adjacent card and chronological Review history from transcript messages only. review.rollback verifies the current post-tool hash before restoring prior bytes or removing a newly-created file; conflicts never overwrite later edits. Session deletion and startup cleanup remove snapshots; forked evidence is visible but non-reversible.A message-owned snapshot survives Git commits, separates same-path edits, and makes rollback explicit without trusting mutable repository state
D142Session-scoped work-panel runtime contexts (no-launcher clause amended by D207)Each conversation owns an in-memory work-panel context containing open state, ordered tabs, active tab, and Browser resource. Session selection atomically projects that context, switching away never deletes it, and switching back restores it. File/URL/BrowserPreview/command/Review artifacts are recorded against their originating sessionId; BrowserPreview renderer events therefore carry sessionId. Background artifacts may update their retained context but never open, activate, resize, navigate, or focus the visible panel. A workspace selection without an active conversation hides the panel, and relative resources remain bound to their session/workspace. Relaunch discards every context and Browser resource; only panel width persists. This supersedes only D128's requirement to close and clear tabs when the visible session or workspace changes; D128's artifact triggers, deduplication, and close/collapse behavior remain unchanged, while D207 adds an active-session shortcut that only reveals the retained context. No process ownership boundary changes.Permission-gated tools can finish while another conversation is loading or visible. A global destructive tab set either flashes open before being cleared or loses the originating conversation's tools; session-keyed renderer state preserves continuity without allowing background work to steal focus or making transient resources durable.
D154Work-panel activity rail and resource switcherOnce an artifact has opened the work panel, a 44px activity rail exposes Review, Terminal, and Browser as one-click 32px tool buttons; opening a missing tool still uses D128's openWorkPanelTab path. The 46px content header shows and closes the active resource and exposes a bounded keyboard-operable switcher for every open tool/file resource. The panel minimum becomes 364px so the rail preserves the previous 320px content floor (width clamp superseded by D167). This replaces the horizontally scrolling top tabs and hidden empty-header context menu while preserving D128's artifact-driven panel entry, resource deduplication/order, close-neighbor behavior, and session-scoped runtime ownership.High-frequency tools should be visible and spatially stable, while long or numerous file names need a labeled overflow surface rather than compressing every action into one titlebar row.
D157One visual assistant turn per user turnProvider-level assistant messages separated by thinking/tool activity remain distinct canonical transcript records, but ChatTranscript composes all records after one user message and before the next into one role=article assistant turn. Ordered markdown fragments and activity disclosures remain visible; only the composed turn owns the trailing aggregate model/usage row and Copy/Fork/Retry toolbar. Copy joins all contentful fragments with paragraph breaks; Fork and Retry use the last contentful assistant record as their durable boundary.Tool-capable providers close and reopen assistant messages around every tool call. Rendering those transport boundaries as separate responses duplicated action toolbars and made one agent run look like many AI replies.
D162Latest-wins cached session switchingThe renderer marks the newest selected row immediately, coalesces session-detail prefetch/load work, retains an LRU-style five-transcript memory cache, and starts the newest transcript read without waiting for superseded reads. Workspace alignment may overlap transcript IO; navigation generations gate the atomic visible commit. ChatSurface keeps the previous complete view non-interactive while React defers a changed session tree, then paints the destination at its final record. Cached snapshots are always revalidated.The former global selection promise queue made every click wait behind obsolete full-transcript JSONL reads, while one synchronous long Markdown commit delayed visible feedback. Latest-wins IO plus a stable deferred frame matches Codex-style navigation without weakening session/workspace isolation.
D156Independent native-window and work-panel resize ownership(responsive-clamp and no-native-reservation clauses superseded by D163) Electron Main exclusively owns BrowserWindow bounds at a 1040x700 minimum, while the renderer owns a persisted preferred work-panel width. The divider uses anchored, frame-coalesced preview and commit-on-release; Escape, pointer cancellation, and lost capture roll back. Native window-edge resize never rewrites the panel preference. This supersedes D083's restore minimum and removes the window/resizeBy delta channel and resize-attribution heuristic.Circular ownership made divider release resize the OS window a second time, introduced async races and platform differences, moved windows near display edges, and rewrote a panel preference during unrelated native resize.
D163Native width reservation for the fixed work panel(width range superseded by D167) **An open docked panel keeps one committed fixed width in 364..720; native window/sidebar changes never clamp it. Renderer sets the visible target through idempotent `window/setWorkPanelReservation({width: 0364..720}) -> {requested, reserved}: open requests the committed width, collapse/final close requests zero, and divider commit updates it. In normal state Main adds available native work-area width and reverses it symmetrically, so chat stays stable when the full target fits; otherwise the panel remains fixed and chat absorbs requested - reserved`. Native edges resize chat only. Maximized/fullscreen requests defer until normal; display/work-area changes reconcile the target against current available width without reapplying it during ordinary same-display movement. Persisted base bounds exclude reservation width and its x shift, and background artifacts cannot alter the visible reservation. This supersedes ADR 0029/D156 clauses that clamp the panel inside current client width or prohibit panel-driven target geometry, while preserving Main bounds ownership and divider gesture rules (ADR 0032).**
D167Slimmer default work-panel widthThe docked work panel opens at a 280px committed default (one third narrower than D163's 420px) and clamps to 244..720px; the renderer constant, the Electron reservation validator, and the .work-panel CSS floor share those bounds. Double-click on the divider restores 280px. The 44px-rail-plus-320px-content rationale behind D154's 364px floor no longer applies now that tools live in the header switcher, so the floor scales with the default. Persisted wider widths stay valid and are unchanged on upgrade; the 720px maximum, divider gesture rules, and inert-reservation behavior (ADR 0033) are untouched.The panel opened wider than most review/terminal/file content needs, taking readable width from chat inside the fixed client area, and the old 364px floor made a narrower default unreachable.
D164Dual-locale in-app product changelogProduct "what's new" text for app updates is maintained as a dual EN/zh-CN catalog in packages/shared (CHANGELOG). Electron Main formats notes for the discovered availableVersion using the product UI locale and attaches them as optional UpdateState.releaseNotes plain text on the existing updates IPC/event path. The ambient banner and Settings → Info Updates row show a compact What's new section when notes exist. English is the source of truth; zh-CN mirrors versions and highlight counts. GitHub auto-generated release bodies remain web-only and are not the in-app source. No new feed URL, notes channel, or renderer-owned remote fetch is introduced (extends D120 / ADR 0022).Users need bilingual release highlights at update time without a second network surface or weakening Main's sole ownership of update delivery.

L. Transcript presentation decisions

IDTopicDecisionRationale
D101WorkBuddy-inspired transcript densityUser turns render as compact right-aligned soft plates (min(78%, 560px), subtle border + hairline shadow). Assistant turns stay transparent full-width prose (max 720px). Message row vertical padding tightens to 10px. Hover/focus-within reveals quiet copy chips under each turn (right-aligned for user, left-aligned for assistant). Streaming assistant answers use a thin accent left rule. No mascot, reactions, or cost-chip UI yet.Current right-aligned user bubbles were underspec'd and visually sparse versus WorkBuddy's task chat; denser plates improve scanability without abandoning the Codex/developer restraint
D103Per-message model + token meta and retryCompleted assistant turns surface modelId + token usage chips under the answer (tokens-only; hover breakdown for input/output/cache/reasoning). Usage is attached on runtime message_end from pi-ai Usage, persisted in message meta_json, and reloaded with the transcript. Action row adds Retry, which re-sends the nearest preceding user prompt. No currency pricing and no like/dislike.WorkBuddy per-message meta improves trust/scanability; token totals already flow from the provider while priced cost still needs a catalog
D105In-place regenerate for assistant turnsRegenerate truncates the session transcript to the nearest preceding user prompt (exclusive of that prompt and everything after), disposes the live pi-agent for the session, and re-sends the prompt so the new assistant/tool tail replaces the discarded branch instead of stacking a duplicate turn.Users expect regenerate to rewrite the current turn; append-only retry polluted long sessions and left stale answers above the redo
D106Preserve user hard newlines in transcriptUser bubbles render plaintext with hard newlines intact. Composer only trims leading/trailing whitespace; transcript uses message-user-text with white-space: pre-wrap (no forced mid-glyph word-break) so multi-line prompts never collapse into one paragraph. Copy and session reload keep the original line breaks.Multi-line prompts (code snippets, lists, pasted blocks) are common in coding agents; collapsing newlines makes the transcript hard to re-read and re-edit
D107Configuration provider studioSettings → Agent uses a provider studio: summary hero, segmented defaults, collapsible OpenAI-compatible composer, and card-based provider management with secret badges, thinking presets, test connection, and make-default/delete actions. This refines the Providers presentation inside the compact settings directory without adding rail destinations.Dense stacked forms and cramped list rows made multi-provider setup hard to scan and over-emphasized secondary fields
D110Model configuration label + add-provider dialogSettings rail label for the agent destination is Model configuration (zh-CN: 模型配置). Adding a provider opens a modal dialog instead of an inline collapsible composer; the destination id remains agent.Clarify the model-setup purpose of the tab and reduce page churn while editing provider credentials
D108Conversation minimap only when overflowingThe left-edge conversation minimap rail renders only when at least two visible user/assistant messages exist and the transcript overflows one viewport (scrollHeight > clientHeight). Short one-page threads hide the rail; streaming growth, content resize, and window resize re-evaluate visibility.A navigation rail is noise when every message already fits on screen; overflow is the signal that jump/preview navigation is useful
D109ChatGPT-style regenerate revision historyRegenerate archives the discarded assistant/tool tail under a stable revisionRootId family in message_revisions (schema v4). The live root user turn carries revisionCount / activeRevision and a quiet current / total pager switches linear variants in place. First regenerate stores the original branch as revision 1; later regenerates append new branches and mark the newest active. No free-form branch tree.Users expect regenerate to keep prior answers reachable like ChatGPT; D105 in-place rewrite alone deleted history that was still useful for comparison
D111Empty home scroll stack(card-specific clauses superseded by D131) Empty chat home is a single scrollable vertical stack inside home-main-content. Short windows top-align and scroll; content stays in document flow and the home composer remains non-docked.Dual-grow + absolute portal let the home composer overlap guidance on shorter windows; flow layout preserves every remaining block without collision
D112Readable chat beside the work panel(dynamic width clamp superseded by D163; welcome chooser superseded by D128) MainChat has a 360px readability target beside the panel. D163 preserves it through native width reservation whenever the display work area can supply the complete committed panel width; otherwise MainChat absorbs the unavoidable shortfall while the panel remains fixed.A panel-only width cap could leave roughly 109–205px for chat at supported window sizes; native reservation now preserves chat without compressing the tool surface.
D113WorkBuddy-inspired local profile footerThe expanded sidebar ends in a transparent 58px footer. Its 44px profile trigger contains a 30px circular local-user glyph, two-line Custom + Local profile / 本地配置 identity, and a chevron; a separate 32px Help shortcut opens Settings → Info. The 280px profile menu opens 8px above the footer with a repeated identity header, divider, and Settings / Logs / Theme actions, preserving Escape, outside-click, arrow-key, and focus-restore behavior. This supersedes D041; no cloud account, notification, share, or update capability is implied.Adapt WorkBuddy's avatar-and-actions footer grammar to PI-Desktop's truthful local-only capabilities while improving identity hierarchy and eliminating the stale cloud stand-in
D137Glyph-only message toolbars; edit means edit-the-promptMessage toolbars carry icons only: the label lives in a CSS hover/focus tooltip (data-tip) plus aria-label, never as a visible chip caption (worded buttons stay only on error surfaces). Edit moves off the assistant answer onto the user turn: it opens the prompt in an inline textarea (slash turns seed the typed command form so the resend re-expands the template) and saving replays the D105/D109 regenerate path with the new text in the same session — the replaced prompt and its whole answer tail are archived as a revision, so the existing current / total pager walks back to the original. Editing the assistant's own text and its fork-into-a-child-session variant (D134) are dropped; Fork stays as the explicit divergence action.Four worded chips under every answer read as a sentence and crowded the transcript; and the useful correction is almost always "I asked it wrong", which users expect to re-run in place with history intact (ChatGPT semantics) rather than to hand-edit the model's words in a new session
D165Safe lazy Mermaid diagrams in assistant answersA completed mermaid fence in assistant answer prose renders as a theme-aware SVG after entering the near-viewport band. Partial stream fences and all thinking prose stay source code. The renderer dynamically loads official Mermaid, serializes its global theme renders, caps source at 20,000 characters and edges at 500, locks strict/no-HTML/no-link configuration, and applies a second SVG-profile sanitizer. Invalid or oversized diagrams fall back to visible copyable source; the diagram toolbar toggles source and copies it.Diagrams improve architecture and flow explanations, but parsing partial streams or every offscreen historical fence would undermine direct-stream and fast-session-switch behavior. Strict bounded local rendering adds the capability without a new protocol, network, or Electron privilege boundary.

M. Agent runtime decisions

IDTopicDecisionRationale
D114Per-session scratch directory for agent temp filesEach session gets <data_dir>/scratch/<sessionId>/ as a second containment root for Read/Write/Edit (absolute paths only; relative paths stay workspace-bound). The path is advertised in the system prompt and as PI_SCRATCH_DIR in Bash. Scratch writes auto-allow without a permission card and are excluded from the artifacts table. Scratch is created lazily, deleted with the session, and swept at startup (orphans, >7 days stale). Glob/Grep/BrowserPreview remain workspace-only. Plan does not expose Write/Edit; Plan Bash can still mutate scratch under its resolved permission mode.Temp/intermediate files (one-off scripts, downloaded data, drafts) were dirtying the user's project and git status; a host-owned root with identical lexical + symlink defenses keeps the sandbox model intact while giving the model a legitimate place for scratch work
D115Permission modes: global default + per-session override(composer presentation superseded by D132; checkpoint Plan clause superseded by D189) A permission mode governs high-risk tool approval: ask (confirm everything, default), accept-edits (auto-allow Write/Edit, confirm Bash/plugins), auto (auto-allow all). Global default lives in settings defaultPermissionMode; each session stores permission_mode (schema v5, default inherit) which overrides it when not inherit. Resolution: session override → global default → ask, enforced solely in host-core tools.execute. Plan retains the selector; its Write/Edit/plugin hard deny outranks every mode, while Bash follows the selected mode.Confirming every Write/Edit made long agent runs high-friction, but a single global toggle is too coarse — trusted scratch sessions and risky repo sessions need different postures; keeping enforcement host-side preserves the security boundary
D132Composer permission menu shows effective modes onlyThe agent-mode composer chip and menu expose only ask, accept-edits, and auto, with the effective mode selected directly and no global-default/inherit entry or provenance label. Choosing an item stores that explicit session override. Existing inherit persistence and resolution from D115 remain unchanged until the user chooses a mode. This supersedes only D115's composer-presentation clause.The inherited entry repeated a selectable mode and exposed storage provenance instead of the permission posture the user is choosing; presenting the three effective modes makes the control direct without changing host enforcement
D116Provider failures as assistant messagesEvery provider/model turn failure is attached to a durable role=assistant, status=error transcript message through optional UiMessage.error. The message shows a localized summary and stable code, keeps redacted provider detail behind an accessible disclosure, and offers context-appropriate Retry or Settings actions. Message-bound failures never use toast/global banner presentation and never re-enter later model context.Errors belong to the failed turn; preserving them in the transcript makes the response diagnosable after session switches/restarts without contaminating the next model request or exposing credentials
D127Context-preserving reseed + transport retryReseeding a recreated pi runtime from the persisted transcript restores tool call/result pairs (from tool rows' toolCallId/toolArgs/toolResult) in addition to user/assistant text and thinking. Interrupted tool rows restore as errored results; orphaned tool rows get a synthesized call-only assistant carrier so pairs stay adjacent and well-formed. Failed assistant turns stay transcript-only. Separately, request setup uses one bounded pi-ai retry for transient transport/provider failures; post-response stream recovery is defined by D186.Text-only reseed collapsed a session's context after any runtime recreation (regenerate/edit, config change, restart): the model lost every tool result it had gathered and — seeing its own history answer without visible tool use — stopped calling tools, degrading agent sessions into bare chat. The incident trigger was a single un-retried provider timeout that forced the user into regenerate. D127 corrects the initially duplicated D120 identifier; D120 remains the earlier application-update decision frozen by baseline 0.4.6.
D136pi-ai owns known-model metadataFor every model resolved from the pinned pi-ai catalog, Electron main passes the complete pi model snapshot to the sidecar and PI-Desktop replaces only connection identity. Provider Settings and the model menu do not override reasoning support, thinking levels, context/output limits, temperature, or compatibility. Unknown free-form ids remain usable through an explicit generic text-only, non-reasoning fallback. This supersedes D102 and the provider-override clauses of D096/D107.A second desktop-owned model matrix discarded pi metadata, drifted from adapter behavior, and made model semantics depend on conflicting configurations; fixes for known models now belong in pi-ai or a pi-ai upgrade.
D183Segmented tool and model latency logs (UI clause superseded by D184)Every tools.execute call is timed in segments instead of one opaque duration: host-core emits a tool timing line on the host channel and persists prompted, permissionWaitMs, overheadMs, and totalMs next to the existing durationMs on tool_execute / tool_denied audit rows; the sidecar writes greppable [timing] kind=tool … (hostRttMs) and [timing] kind=model … (providerWaitMs, streamMs, including failed/aborted turns) lines to the agent channel, suppressible with PI_DESKTOP_TIMING=0. The original no-UI clause is superseded by D184; logging remains unchanged."Executing a command is slow" was undiagnosable from the logs: approval waiting, the tool body, and the provider round trip were indistinguishable, so a 45s gap between two audit rows with 0ms durations gave no clue whether it was the user, the model, or the host. Splitting the stages makes the answer readable without reproducing the run.
D158Turn-boundary context checkpoint compaction (soft-boundary, model-tool, and visibility clauses superseded by D200; the model tool and visibility restored in Codex's shape by D203)PI-Desktop reuses pi-agent-core's context estimation, session-context, and compaction primitives but owns the orchestration and durability. After every turn_end, before any next provider request, the runtime evaluates model-aware soft/hard budgets. A transient deduplicated instruction can ask the model to call the internal CompactContext tool; the tool's normal activity row is visible/durable, while the instruction is not. Crossing the hard budget forces checkpoint generation and blocks the request on failure. A final atomic tool batch that reaches half the hard budget is fairly head/tail-truncated only in the checkpoint copy, with explicit markers and every call/result envelope retained; original transcript rows remain complete. Exact provider overflow removes the failed assistant from model context, creates one checkpoint, and retries once. Host protocol v6 appends checkpoint records beside the untouched visible JSONL transcript; restart, late truncation, and included-boundary forks preserve the newest valid checkpoint. Disabling automatic compaction removes the tool and all automatic threshold/overflow recovery, while /compact remains available. OpenCode DCP is an AGPL-3.0 behavioral reference only and is neither linked nor copied (ADR 0030).pi's end-of-run-only behavior cannot protect long tool loops, and a model reminder alone cannot guarantee provider safety. Reusing pi's tested compaction format while adding a deterministic turn_end gate prevents another provider request from crossing the known window, retains user-visible history, and avoids importing an incompatible plugin/runtime and license boundary.
D185Lazy per-turn tool activation (the always-active CompactContext clause is void under D200, and holds again for new_context under D203)The sidecar keeps a complete local tool registry but sends only the mode core set and local ToolSearch on each new prompt. BrowserPreview, plugin tools, Skill, and plugin-development helpers appear as bounded compact catalog entries and are activated by exact-name or capability search; the next turn receives their schemas, native pi-ai deferred search is used when supported, and the set resets before the next user prompt. Host permissions, containment, timeouts, and audits are unchanged.Full tool schemas made simple first requests disproportionately large and repeated optional capability cost across turns. A pi-style active set preserves core coding ergonomics while making ancillary tools pay-as-you-go and provider-independent.
D200Imperceptible background context compaction (background pre-computation, incremental trigger, silence, and no-model-tool clauses superseded by D203)**Compaction becomes a host-owned background activity with no user-visible surface. contextBudget() keeps the D158 hard limit and request headroom, derives the retained-tail target from the model window (clamp(hardLimit * 0.2, 8k, 64k), still capped at half the hard budget) instead of settings, and adds backgroundLimit = floor(hardLimit * 0.7) as a pre-computation trigger; the soft boundary is deleted. Checkpoint generation is split from installation: buildCheckpoint produces one without persisting or activating it, and installation re-estimates, appends through host-core, and emits compaction_end. Pre-computation runs only in provider-idle windows — while a tool executes and after a run ends — and only when the context is past the background limit and grew by at least the retained-tail target since the newest checkpoint's baseline, so a large tail cannot trigger a summary every turn. A pre-computed checkpoint installs at the next turn boundary or prompt only if its base is still active, its throughMessageId anchor still exists, and it still fits the current model's budget; any miss falls through to the unchanged blocking path, and a failed background build is discarded with no event, no persistence, and no ADR 0049 fallback. The CompactContext tool, the <context_management> nudge, and the host no-confirmation allowlist entry are removed, so triggering is entirely deterministic. compaction_start/compaction_end gain an optional phase (backgroundblocking, absent means blocking) and compaction_end gains an optional status { generation, summaryTokens }; both are additive inside protocol v9. A successful automatic compaction produces no toast, no run-state change, and no transcript row; only a retained_tail fallback, an overflow retry, and manual /compact still notify. The context usage inspector is the single visible trace, reading status and the durable SessionDetail.compaction, with the generation counter carried inside the checkpoint's opaque details so no record schema change is needed. Settings exposes no compaction controls and persisted contextCompaction values are ignored (ADR 0061).**
D201Bounded subagents behind a Task toolAgent mode gains one delegation tool, Task(agent, task, description?), built only when a session has a non-empty subagent catalog. Definitions are Markdown documents — three builtins shipped inline in agent-runtime (explorer, code-reviewer, test-runner) plus <workspace>/.pi/agents/*.md, project documents shadowing builtins by name, capped at 16 — loaded by Electron main on every launch, so editing one takes effect on the next prompt. A definition declares its own tools from Read, Glob, Grep, BrowserPreview, Bash, Edit, Write (default Read, Glob, Grep) and may pin model: <provider>/<model>, resolved once per launch against up to 8 providers with no fallback to the session model. A delegate is a second pi Agent in the same sidecar with the same host connection, bounded by maxTurns (default 24, max 80) and the parent's retry policy; its report is the tool result, capped at 12k characters, with status (completed / truncated / failed / aborted), turns, toolCalls and usage in the details. Only Task is executionMode: "parallel", so one assistant message of Task calls fans out under MAX_SUBAGENT_CONCURRENCY (4) while any other batch stays sequential; a PathMutex orders same-path mutations. Delegate events carry parentToolCallId and agentName, which persist in message meta with no schema change, nest the rows under their Task call in the transcript, and are excluded when the runtime rebuilds model context — the parent only ever sees the report. Permission requests keep session semantics but queue per session, head-rendered and answered by requestId. Task stays in the Agent core set rather than the on-demand catalog (ADR 0062).Long searches and reviews cost the parent its whole context even when only the conclusion matters, and a session-level fan-out would have meant a second orchestrator, a second permission owner and a second turn owner. Keeping delegation a tool makes it one tool result the parent pays for, leaves permissions and containment on the session that owns the workspace, and makes read-only-by-default definitions the thing that bounds a delegate rather than trust in its prompt.
D202Managed subagent definitions with a user-level registrySubagent definitions gain a third source and a management UI. host-core owns a user registry (<data>/agents/registry.json plus flat <data>/agents/<id>.md documents, id == name, duplicate names refused, capped at 16 to match MAX_SUBAGENT_DEFINITIONS) whose records carry enabled and the D192 activation scope beside the frontmatter the UI edits (tools, model, thinkingLevel, maxTurns). Shadowing order becomes project > user registry > builtin, so a committed .pi/agents/<name>.md still wins. host-core is the only writer and parseSubagentDefinition stays the only reader; Electron main reads the scope-matching documents on every prompt and passes them to loadSubagentDefinitions(projectPath, { userDocuments }), so an edit takes effect on the next turn with no restart. Nine IPC channels (subagent/list, catalog, create, update, read, remove, setEnabled, setScope, reveal) mirror the D194 skill API and every mutation emits pluginChanged. The extensions page gains a fifth segment, Subagents (amending D196), with a writable registry list (scope control, editor sheet, reveal, delete) above a read-only list of the effective catalog computed by subagent/catalog in main rather than merged in the renderer. Builtins and project documents are read-only — no enable switch, no scope — and offer “copy as my definition”, which pre-fills the sheet so a registry entry of the same name outranks the builtin it came from. A registry row reports when a project document owns its name and when it is not active in the current project (ADR 0063).ADR 0062 shipped delegation with no surface: a definition could only be hand-written into a tracked project file, the shipped builtins were invisible outside the source, and a definition that silently lost to shadowing, an off switch or another project's scope was unreportable. Writing into .pi/agents/ from the UI would create uncommitted diffs and force a personal delegate to be re-created per repository, so the writable layer is user-level while the project source keeps its precedence; answering the read-only list from the real loader keeps “what Task offers right now” from becoming a second precedence implementation that can disagree with the one that matters.
D203Codex-parity context compactionCompaction is rebuilt to match Codex's mechanism, reversing four D200 clauses and keeping the rest. All background pre-computation and the incremental trigger scope are deleted: prepareNextTurn() compacts synchronously when the total context crosses hardLimit or the model asked for a new window. A checkpoint carries the summary plus recent user messages only — pi's cut point still marks the boundary, but its split-turn prefix and recent tail are folded back into the summary input so the summary covers the whole range, and the retained tail is rebuilt newest-first from user messages up to 20,000 tokens (capped at half the hard budget) with the crossing message truncated rather than dropped, then restored to chronological order. Two families run the identical lifecycle: summary, and a fresh_window rollover that requests no summary and stores a fixed marker text, selected by construction option or PI_DESKTOP_COMPACTION_STRATEGY and exposed in neither settings nor i18n. The model-facing new_context tool returns (parameterless, Codex's description verbatim, on the host no-confirmation allowlist, never assignable to a subagent) together with two per-window budget reminders appended to the current turn's system prompt — one at clamp(hardLimit * 0.15, 8k, 32k) remaining, one at 2,000 — each claimed once and reset on install. host-core persists the whole checkpoint chain (read_compactions, write_transcript_with_compactions, per-record fork validation, SessionDetail.compactions) with compaction kept as its newest element. compaction_start/compaction_end drop phase, and compaction_end replaces status with mark { id, throughMessageId, generation, summaryTokens, summarized }; the transcript draws one divider row per compaction after the message it covers, ending the assistant turn it lands inside and dropping a mark whose anchor is gone, and every successful compaction raises one warning toast on top of the fallback/overflow/manual toasts. Deliberate deviations from Codex: the summary precedes the retained users because buildSessionContext fixes that order, hardLimit stays "window − output reserve" instead of 90% of the window, the tool is registered in both families, and the reminders are system-prompt appends with our own thresholds and wording (ADR 0064).The previous round cited Codex while implementing its opposite on four counts, and its Context section claimed Codex has no model-side compaction tool when new_context exists. The user asked for Codex's mechanism specifically, after being told it reverses the imperceptibility goal. Parity also buys three things on its own merits: a checkpoint that keeps only user messages is far cheaper and cannot strand a tool call without its result, a visible row makes a lossy operation auditable from the transcript again, and a warning puts the "start a fresh session instead" decision where it belongs. The cost is accepted: the user waits for compaction again.
D186Bounded provider stream recovery and diagnosticsProvider request setup uses one bounded pi-ai retry. A transient STREAM_FAILED, NETWORK_ERROR, or TIMEOUT after streaming starts is replayed once in the same turn after abortable backoff; the failed assistant is removed from model context and its visible message id is reused. A second failure is terminal. Provider AppError.details carries only bounded phase, timing, provider status/code, and retry-attempt diagnostics. Mutation guidance uses one fresh read/regeneration after an Edit mismatch; a second failed Edit for the same path or a second failed shell patch command returns a terminating tool hint instead of repairing old patch artifacts.Unbounded or regenerate-driven recovery made transient stream termination expensive and made patch loops consume turns without new information; finite runtime budgets preserve context and user control while keeping failures diagnosable (ADR 0050).

| D187 | Resource-isolated host RPC stdio | host-core reads stdin and serializes stdout through one dedicated named OS thread per direction, never through Tokio's dynamic blocking pool. The threads retry interrupted and transient EAGAIN/EWOULDBLOCK errors while preserving NDJSON framing; inability to create a control thread is a structured startup failure. The login-shell PATH probe also treats helper-thread creation as best effort and falls back to the inherited PATH. RPC/tool admission limits remain unchanged. | Tokio stdio can panic when OS thread creation returns Resource temporarily unavailable (errno 35 on macOS), turning temporary resource pressure into HOST_UNAVAILABLE; isolating the control pipe removes that process-level crash path while retaining bounded overload behavior (ADR 0051). | | D191 | Agent-only mode; Chat renamed to read-only | (superseded by D188/D189: the mode selector returned as Agent | Plan, and chat migrates to plan) agent is the only session mode the product exposes. The former chat profile is renamed read-only and keeps its Read/Glob/Grep hard deny in host-core, but it has no UI surface: no top-bar toggle, no composer chip, no Settings row, no palette command or slash alias, and no localized labels. The host normalizes chat to read-only on every write path (session.create, session.configure, session.import) and the permission gate is negative — anything that is not agent gets the read-only surface — so an unknown or legacy value can never widen the tool set. Error codes become BASH_DISABLED_IN_READ_ONLY / WRITE_DISABLED_IN_READ_ONLY. A boot fix-up rewrites existing sessions.mode = 'chat' rows and a stored defaultMode of chat to agent. | A mode switch the product never intends users to reach is a footgun and dead UI weight: sessions could be stranded on a read-only profile with no way back, and two toolsets doubled the surface every tool, prompt, and permission change had to be reasoned about. Keeping the narrow profile enforced host-side preserves the security boundary for imported and legacy rows without shipping a control for it (ADR 0055). |

N. Notification decisions

IDTopicDecisionRationale
D117Durable task notification inboxRust host-core exclusively owns a schema-v6 notifications table and atomically inserts one structured task.completed / task.failed row when session.endTurn moves a running turn to completed/error only if Electron reports that result was not already visible. Renderer supplies the current chat session through an allowlisted viewing-context IPC; Main suppresses insertion only when its window is visible/focused and that session matches, while unknown, background, hidden, or unfocused state fails safe to notification. turn_id UNIQUE prevents duplicates, abort is silent, session deletion cascades, and only the newest 200 rows remain. The titlebar bell exposes exact unread count, All/Unread, row mark-read/session activation, mark-all-read, and clear with complete keyboard/accessibility behavior. Protocol v4 adds singular notification.list/markRead/markAllRead/clear; session.endTurn returns an inserted record, Electron emits renderer notification.changed, and only while the main window is unfocused it also shows a native system notification whose click restores/focuses the window and emits notification.activated. Persisted rows contain structured kind/session/turn/error data plus the session-name snapshot, never localized notification title/body prose. The task inbox has no permission, scheduled-reminder, preference, cloud-notification, or plugin-notification source; plugin-native notifications are the separate D213 surface. D113's profile footer remains unchanged.Notifications should recover task outcomes the user did not see, not duplicate a result already visible in the current chat. A bounded host-owned inbox keeps background/unfocused outcomes durable and navigable without violating SQLite ownership, duplicating events, or turning every terminal event into notification history.

O. Desktop shell decisions

IDTopicDecisionRationale
D118Platform application menu and window chromemacOS installs a conventional system application menu and keeps hidden-inset traffic lights. Windows/Linux use the shared 46px frameless shell with localized File/Edit/View/Window/Help menus and renderer-drawn minimize/maximize-or-restore/close controls. Both menu surfaces route renderer-owned actions through a fixed AppMenuCommand allowlist; renderer menus route native editing/window actions through a separate fixed allowlist. Target packaging builds the local release host before Electron packaging. This adds platform-ready shell behavior but does not reverse D010: Windows/Linux release qualification remains post-MVP.A default Electron menu leaves macOS shell commands incomplete, while a frameless Windows/Linux window otherwise loses both application menus and window controls. Shared allowlists keep behavior consistent without exposing an arbitrary privileged command bridge.
D120Application update deliveryElectron Main exclusively owns a fixed GitHub Releases feed, update polling, typed state, and install lifecycle. Development is disabled; packaged macOS and non-AppImage Linux use notify-and-link delivery, while Windows NSIS and Linux AppImage download in-app and install on quit. Renderer IPC cannot provide feed URLs. Automatic failures stay ambient, explicit checks surface status, and downloaded state remains actionable. The updater always forces allowPrerelease = false so prerelease installs (for example 0.2.0-rc.6) track GitHub's latest stable release instead of electron-updater's default same-channel pin. D126 later publishes every platform feed produced by the tag matrix while macOS remains manual until a signed channel is qualified.Keep package installation outside the sandboxed renderer, match delivery to each installer format, and provide one consistent state across menus, Settings, and the update banner (ADR 0022). Without the stable-channel pin, RC builds never surface newer stables because electron-updater treats rc as a custom channel.
D121Branded macOS development hostpnpm dev on macOS launches electron-vite through a fingerprinted, ad-hoc-signed PI-Desktop copy of the installed Electron host bundle under .cache/electron-dev/. The generated bundle changes only development host metadata, executable name, bundle identifier, and the ICNS resource; it never mutates node_modules. Windows/Linux keep the stock development executable, while packaged lanes remain electron-builder-owned.AppKit ignores runtime app-name/menu overrides for the top-level application identity and takes the native menu name and About icon from the host bundle; a branded development host is required for parity with packaged PI-Desktop.
D129Menu-free Windows/Linux window chromeThe application menu is a macOS system-menu surface only. Windows/Linux retain the shared frameless 46px titlebar and renderer-drawn minimize/maximize-or-restore/close controls, but render no File/Edit/View/Window/Help menu inside the window and reserve no left-side titlebar space for one. Existing application, editing, zoom, fullscreen, and close shortcuts remain available through renderer/native web-content handling; update checks remain reachable from Settings -> Info. This supersedes only the Windows/Linux renderer-menubar portions of D118 and ADR 0021.An in-window desktop menu duplicates macOS-specific system-menu chrome, consumes navigation space, and does not belong in PI-Desktop's frameless Windows/Linux titlebar.
D130Sidebar-footer notification entryThe durable notification Bell moves from the main titlebar to the separate 32px action at the right of the expanded sidebar footer, replacing D113's Help shortcut. Its unread badge and complete D117 inbox behavior remain unchanged; the popover opens above and to the right of the footer, and no duplicate Bell remains in the main titlebar. This supersedes only the entry-location clauses of D113 and D117.Notification history belongs with the persistent local profile controls and the footer position keeps the main titlebar quiet while preserving a compact, familiar status entry.
D141Canonical Windows native application identityElectron Main sets the product name before readiness and registers com.pi-desktop.app as the Windows process AppUserModelID before creating any window. That ID is the existing electron-builder/NSIS application ID; Windows packaging explicitly retains PI-Desktop for the executable and Start menu shortcut. Native notification attribution, notification settings, taskbar grouping, installed shortcuts, and packaged executable identity must expose PI-Desktop, not the stock Electron host. D121 remains unchanged: Windows development may use the stock Electron executable while its OS-facing runtime identity uses the canonical AUMID.app.setName changes Electron's internal name but not the Windows identity used by notifications and shell integration. One stable ID across runtime and packaging prevents both the observed notification-source leak and adjacent shell-brand drift without changing the published NSIS upgrade identity.
D204Empty home task-entry surface(supersedes D111's non-docked home-composer clause and its contextual quick-action clauses; starter-grid amendment in D205 is superseded by D206) The empty chat home keeps the restrained hero and optional first-run checklist in the scrollable content region. The home composer is a bottom-reserved sibling of the scroller, remains visible at the bottom of the chat surface, and never covers the content.The direct composer remains the stable primary action and the flow layout preserves checklist reachability in short windows (ADR 0066).
D205ChatGPT-inspired empty-home guidance(superseded by D206) The empty chat home adds a compact four-card developer starter grid between the hero and optional checklist: Explore a codebase, Build a feature, Fix a bug, and Review a change. Each localized card only prefills and focuses the bottom composer; it never sends a prompt or creates a turn. The bottom-reserved composer and single scrollable home flow from D204 remain unchanged.The previous hero-only middle left too much unused space and offered no starting cues. ChatGPT's clear empty-state hierarchy improves first-task discoverability while developer-specific prompts keep the surface purposeful rather than promotional.
D206Remove empty-home developer starter cardsThe empty chat home does not render developer starter cards, starter glyphs, or a contextual quick-action row. It keeps the restrained hero, short supporting line, optional onboarding checklist, and D204's bottom-reserved composer; task entry starts directly in the composer. This supersedes D205 without changing D204's scroll and bottom-reservation layout.Review confirmed that the direct composer is the preferred task-entry surface and that the cards add an unnecessary decision layer to the empty home.
D208Recoverable native-tool path contractsKeep D185's deferred Glob/Grep boundary, but make every prompt and schema explicit that Read accepts an existing regular file, Glob accepts a directory, and Grep accepts a file or directory. A directory Read returns INVALID_ARGUMENT plus structured Glob recovery args; an explicit-file Grep searches only that file and applies include to its basename. Tool errors remain visible on their ToolCallRows, while activity groups report processing duration only and never infer terminal turn failure from a child row; terminal agent events and the dedicated outcome surfaces remain authoritative (ADR 0069).Durable sessions showed directory Read and file-as-directory Grep mistakes repeatedly, then displayed recovered work as terminally failed. Compatibility at the narrow host boundary plus one outcome owner removes retries and false failure UI without restoring every search schema to the Agent core.
D216Cross-platform tray-resident minimizeElectron Main creates one packaged-resource tray icon on macOS, Windows, and Linux. Every main-window minimize path is intercepted and hides the window without disposing the host or sidecar; tray click/double-click, Show, and macOS app activation restore and focus the existing window (or create one if it was closed). The localized tray menu exposes Show PI-Desktop and an explicit Quit PI-Desktop action. Closing the window remains a quit action, and tray destruction plus Quit use the existing ordered before-quit shutdown path.Users need background work to continue without losing the app window, while minimizing must mean the same thing across the native macOS controls and the custom Windows/Linux shell. Main-owned tray lifecycle avoids renderer privilege expansion and keeps explicit exit observable.
D218Host-owned cross-platform plugin panel chromePlugin panel windows adopt the main window's 46px platform chrome: macOS uses hiddenInset with traffic lights at {x:16,y:16}, while Windows/Linux are frameless with a 112px custom minimize/maximize-or-restore/close band. The sandboxed plugin preload renders the manifest title and controls in a closed Shadow DOM, offsets content by the titlebar height in addition to existing top padding, and consumes a private sender-validated fixed window-action channel; window.pluginBridge, the per-plugin partition, and host protocol v9 do not expand. Reopening a minimized panel restores and focuses it.Default Electron frames made plugin tools look detached from PI-Desktop and varied by platform. Preload-owned chrome provides parity without moving untrusted plugin HTML into the host renderer or exposing general Electron window authority (ADR 0081).

P. Transcript storage decisions

IDTopicDecisionRationale
D119Transcript file store; SQLite index-onlySchema v7: message content moves out of SQLite into per-session JSONL files under ~/.pi-desktop/sessions/<id>.jsonl (a session-header line, then one canonical block-array message line per message, RFC3339 stamps) plus an append-only <id>.revisions.jsonl for regenerate branches. messages drops content_json/meta_json and becomes a pure index (ordering, promoted filter columns, extracted text feeding FTS); message_revisions swaps messages_json for message_count, with is_active tracked in the DB only. Writes are file-first then index transaction; reads skip unknown/torn lines and dedupe repeated message ids keep-last; full rewrites are temp-file + atomic rename; session files are deleted only with their session and never age/orphan-swept. Opening a pre-v7 database archives it as pi.sqlite.v6.bak and bootstraps fresh — an explicit breaking reset, with all v1–v6 migration code removed. RPC wire format is unchanged, so Electron/renderer/importers need no changes.The database grew without bound carrying tool args/results and thinking payloads; codex/claude-code-style per-session files keep transcripts human-readable, greppable, and portable while SQLite stays a small, fast index (list, search, badges). A dev-phase breaking reset was chosen over migration machinery.
D122Independent conversation session forkProtocol v5 adds host-owned session.fork: an idle source's complete active canonical transcript is copied into a new independent session with remapped message/tool-call ids and inherited project/provider/model/mode/thinking/permission configuration. Turns, regenerate revisions, notifications, artifacts, session grants, scratch/runtime state, pin state, and parent-child lineage are not copied. Create branch activates the child; D109 remains unchanged because no message-level branch tree is introduced.A single host-owned snapshot preserves canonical blocks and persistence consistency while giving users a Codex-style divergence workflow without conflating independent conversations with regenerate variants.
D134Assistant response fork and reversible edit(edit clause superseded by D137) The completed-assistant toolbar exposes Copy, Fork, Edit, and Regenerate but no Delete. Fork calls the existing host-owned session.fork with optional throughMessageId, producing an independent session whose canonical transcript ends at that response. Edit uses the same isolated child, replaces only the selected assistant text there, and stores original/edited tails as a two-entry D109 revision family so the existing pager can restore either. Both require an idle source, remap message/tool-call ids, and never share the source session id, runtime, transcript, revisions, or provider cache state.Response-level divergence and correction should remain reversible without mutating the source or letting an edited history reuse cached runtime state built from different assistant content.
D199Regenerate branch archived under the host RPC locksession.saveActiveRevision performs the read, the branch archive, and the revisionCount / activeRevision stamp in one host call under the state lock, replacing Electron main's session.get + session.replaceMessages read-modify-write. The stamp rewrites only the root user's transcript line and re-reads the file at write time, so a line appended meanwhile survives; Electron main drains the persistence outbox first and skips the archive with a warning rather than archiving an incomplete branch. session.replaceMessages carries each surviving message's owning turn_id across a rewrite and is documented as safe only for a caller that owns the whole transcript for the call's duration (ADR 0060).Assistant and tool messages reach SQLite asynchronously through the ADR 0041 outbox, so the renderer-side snapshot could predate the turn's final message — and the whole-transcript rewrite then deleted it from both the transcript file and the index, along with every row's turn_id. Only the host can read and write the transcript atomically.

Q. Still deferred

  1. Exact marketplace domain / provider IDs
  2. Private marketplace auth mechanism
  3. Signature key distribution operational details
  4. Remote catalog update channel details (URL/signature)
  5. Exact recommended default model per vendor preset

The full open list lives in open-questions.md; this section mirrors only marketplace/catalog items still blocking nothing.

U. Settings rail iconography

IDTopicDecisionRationale
D143Settings directory rail iconsThe five settings destinations use fixed Lucide glyphs: Basics=SlidersHorizontal, Model configuration=Bot, Import=Download, Project archive=Archive, Info=Info. Refresh/rotate glyphs are not used on this rail.Prior mapping reused Settings/RefreshCcw/RotateCw, which read as generic gear/reload rather than the destination semantics; monochrome Lucide keeps the compact directory scannable.
D166Settings directory split into AI and Shortcuts destinationsThe Settings rail grows from five to seven destinations in order: Basics (SlidersHorizontal), 全局 AI/AI (Sparkles, new), Shortcuts (Keyboard, new), Model configuration (Bot), Import (Download), Project archive (Archive), Info (Info). Permissions and Context management move from Basics to the new AI destination; Keyboard shortcuts moves from Basics to the new Shortcuts destination; Developer moves from Basics to Info. Basics keeps only Appearance and Defaults. This supersedes the five-destination count/order of D133 and the five-icon set of D143 (adding Sparkles and Keyboard) without changing any setting's semantics, the full-page shell, or rail metrics.Basics accumulated six unrelated cards, burying global AI behavior (permission mode, context compaction) and shortcut configuration alongside look-and-feel; splitting them gives each concern a scannable home while keeping provider/connection config separate in Model configuration.

R. Decision rules going forward

  • Architecture-boundary changes require a new ADR
  • Implementation defaults can be updated in this log + related specs
  • Any reversal of D001–D010 requires explicit baseline bump

S. Composer input decisions

IDTopicDecisionRationale
D123Composer slash commands, three sourcesTyping / at position 0 of the composer opens an inline command menu merging (a) pi prompt templates from <workspace>/.pi/prompts/*.md and ~/.pi/agent/prompts/*.md (project overrides user-global on name conflict; frontmatter description/argument-hint), (b) builtin palette commands through slash aliases defined in one registry shared with palette search, and (c) plugin palette commands. On send, builtin/plugin invocations execute locally through the existing renderer switch / commandPalette/execute without creating a session or prompting the model; template invocations are expanded in the Electron main agent/prompt handler before persistence (parseCommandArgs + substituteArgs from pi-agent-core), persisting content = expanded plus a new optional command field carrying the typed form; unknown /foo is sent as literal text.Reuses pi's exact CLI semantics and template assets, keeps agent reseed faithful (reseed replays content), and keeps the transcript readable by rendering the typed invocation as a chip (ADR 0024).
D124@ file references are plain-text light references@ at a token boundary opens a fuzzy file menu over a workspace index served by the new Electron-only channel pi-desktop/fs/index (files+dirs, git ls-files -co --exclude-standard fast path with ignore-set walk fallback, 8000-entry cap with truncation flag, short TTL cache, workspace-rooted, fails soft without a workspace). Accepting creates the canonical @rel/path reference for files (quoted @"a b.txt" when the path has spaces) and inserts @dir/ without trailing space for directories; D209 amends completed-file draft presentation without changing the dispatched plain-text path. The model follows references with its Read tool in both Plan and Agent. No prompt content inlining or provider attachment conversion; clipboard file materialization is defined by D197.Matches pi CLI's model-facing semantics, avoids context inflation and truncation rules, and keeps user-driven file browsing out of the agent tool/permission path per ADR 0019.
D125Composer autocomplete interaction and IME contract**One menu component anchored above the composer, full composer width, focus always stays in the textarea. Keys while open: ↑/↓ cycle with wraparound, Enter/Tab accept, Escape closes only the menu (takes precedence over the composer's clear/blur Escape), typing filters live; Enter never sends while an item is highlighted, and an empty result list counts as closed. The menu closes on outside mousedown, blur, deleting past the trigger character, or session switch. All key handling and trigger detection sit behind the IME guard (`isComposing
D197Composer clipboard files become session-scratch referencesWhen the composer clipboard contains one or more OS files/images, the renderer intercepts the file paste, transfers bounded bytes plus name/MIME metadata through the Electron-only pi-desktop/composer/pasteFiles channel, and includes the durable session id. Electron main verifies the session, sanitizes names, and writes unique files below <data_dir>/scratch/<sessionId>/pasted/; D209 amends the draft presentation while preserving each returned absolute path as the quoted-or-unquoted @ reference sent to the agent. Text-only paste remains native. A home composer creates or reuses a durable session first. Clipboard bytes never enter the prompt, workspace, or artifact store, and session deletion removes the pasted files with the scratch root.Fixes the broken file/image paste path without dirtying the project or inventing provider-specific binary prompt plumbing; it reuses the existing scratch containment and lifecycle contract.
D209Composer file references separate compact display from canonical prompt pathsCompleted workspace-file selections and materialized clipboard files become renderer-owned, session-scoped reference chips above the textarea. A chip persistently displays only the leaf name, exposes the canonical path through tooltip/accessibility metadata, and can be removed without deleting scratch bytes. Immediately before submit, references serialize in stable order after the visible draft with D124's exact quoting, so reference-only drafts, slash templates, and Agent/Plan/Goal mode aliases retain their current behavior. Accepted dispatch clears the active editor but keeps a renderer-only, turn-scoped pre-serialization snapshot while smart Stop can still undo an unanswered send. That undo restores the original text and chips rather than prefilling serialized message content; abort after reply start keeps the partial transcript and restores nothing. Rejected/failed sends retain the active draft. ComposerPastedFile.name is the sanitized original leaf label while path retains the UUID-backed absolute storage identity. No binary payload, provider attachment, host RPC, or schema is added.Long relative paths and UUID-backed scratch paths overwhelmed the prompt row even though only the agent needs them. Separating presentation from serialization preserves exact tool-readable paths, duplicate-name identity, textarea/IME behavior, and the text-only provider contract (ADR 0070).
D211Global plugin launcherA customizable openPluginLauncher shortcut defaults to Option+Space on macOS and Alt+Space on Windows/Linux. Electron main registers it globally after boot and owns one post-boot prewarmed 620×440 frameless utility window centered on the display nearest the pointer; macOS uses a cross-workspace panel, while Windows uses a host-core low-level hook for the reserved default Alt+Space chord and retains a focused-window fallback if that hook is unavailable. The sandboxed renderer lists only enabled, ready panel plugins, matches Chinese names by original characters, tone-free full pinyin, or pinyin initials (plus normalized name/id/description), and opens the selected result through the existing plugin panel host. Up/Down selects, Enter/click opens, Escape/blur hides, and IME composition never dispatches. The additive toggle/dismiss/shown IPC is Electron-local; host-core's additive shortcut method/notification carries only the binding event.Plugin panels need a keyboard-first entry point that works while PI-Desktop is unfocused, without restoring the full shell or weakening the existing panel sandbox/permission boundary (ADR 0072/0076/0080).
D212Running composers stage next-turn configuration and stopped turns preserve throughputDuring an active turn, draft text and mode/thinking/permission selectors remain editable, Stop remains active, and only Send is disabled. The renderer projects the latest full configuration per session and calls the existing idle-only session/configure only after the terminal event, so the running runtime remains pinned; pending Plan/Goal approval still gates editing. User-stopped partial answers preserve responseDurationMs; when final provider output usage is absent, visible thinking+answer text is estimated at four Unicode code points per token into optional responseOutputTokens. Rust transcript metadata persists both fields, exact usage wins, and the UI marks estimate-only throughput.Preparing the next prompt should not wait for the current stream, but host safety requires immutable in-flight configuration. A stopped visible answer still has enough measured data for useful, explicitly approximate generation speed (ADR 0073).
D215Windows-reserved global shortcut fallbackOn Windows, host-core installs a narrow WH_KEYBOARD_LL hook for the effective Alt+Space default binding because the shell may reserve it from Electron's globalShortcut. The hook consumes only the matching chord and emits keyboard.shortcut({ binding: "Alt+Space" }); Electron toggles the existing plugin launcher. Electron calls additive keyboard.setGlobalShortcut({ binding }) whenever settings change, and host-core enables the hook only for that exact binding. Other platforms and custom bindings remain on Electron's global shortcut path.Windows' active-window system menu reservation made the shipped global launcher unavailable whenever PI-Desktop was unfocused. A narrowly scoped host fallback preserves the shortcut without reading text or expanding plugin/renderer privileges (ADR 0076).
D217Post-boot plugin launcher warm-upAfter the main application and backends finish booting, Electron creates and loads the retained plugin launcher while it remains hidden. Window creation uses one shared in-flight promise, so a shortcut received during warm-up joins the same renderer load; failures destroy the incomplete window, clear the promise, and remain retryable on the next invocation. Each show still refreshes the live plugin catalog.Removing BrowserWindow allocation and renderer startup from the first visible invocation makes launch latency consistent without delaying primary application boot or weakening plugin freshness, IPC, or sandbox boundaries (ADR 0080).
D198Goal mode is the second contract modeGoal is a third durable operating mode (agent / plan / goal) that reuses the Plan approval pipeline end to end. EnterGoalMode switches an active Agent turn into the Goal contract state; SubmitGoal(title, markdown, question) writes an immutable host-owned .pi/goal/<unique-name>.md artifact and inserts one pending plan_approvals row. The single approval table gains a kind column (plan or goal, defaulting to plan) in schema v11; the kind — not the projected planning state — selects the prompt, artifact directory, submit tool, and i18n namespace, so one approval bar and one execution queue serve both. Plan and Goal are together the contract modes: they share one tool allowlist (Read/Glob/Grep/BrowserPreview/Bash plus their own submit tool), one host hard deny for Write/Edit/plugin/unknown tools evaluated against the durable mode, the shared *_IN_PLAN error codes, and the PLAN_REQUIRES_INTERACTIVE_SESSION rejection of unattended runs. An approved Goal executes autonomously in Agent: it chooses its own approach and keeps working until every acceptance criterion is verified or a boundary blocks it, then reports criterion by criterion. The wire kind is optional and absent means plan, so this is additive inside protocol v9.Plan answers "carry out these steps"; users also need "reach this outcome, decide the steps yourself" (Claude Code's plan mode and Codex's goal-shaped runs). Making the goal statement, acceptance criteria, and boundaries an approved contract keeps autonomy auditable, and discriminating one pipeline by kind avoids a parallel table, approval surface, and permission boundary that would inevitably drift.
D144Sidebar primary chrome at 14pxExpanded sidebar primary chrome (New task, Plugins, session titles, footer identity name, profile menu actions) uses --text-base (14px). Project/group titles and empty-state copy use --text-md (13px). Section labels use --text-sm (12px). Primary sidebar content must not use the micro --text-xs band.13px sidebar body felt undersized next to the 14px chat surface; bumping only primary chrome keeps density while restoring visual balance without a global type-scale change.
D145Disable browser text correction on editable fieldsEvery text input and textarea in the desktop renderer disables browser text correction: spellCheck={false}, autoCorrect="off", and autoCapitalize="off". Shared Input/Textarea primitives default these values; raw fields (composer, message edit, command palette, global search, settings/plugins/projects search, model search, browser URL bar, provider model combo) set them explicitly. Checkboxes and non-text controls are unchanged.Coding prompts, paths, model ids, and URLs must not be red-underlined or auto-mutated by Chromium/OS text correction; the shell is an application, not a document editor.

| D146 | Startup splash + motion tokens | While bootstrap is incomplete the renderer shows a branded full-window startup splash (logo, shell name, tagline, accessible app.starting, soft progress bar) instead of plain status text. After ready, the splash holds a short minimum dwell (~420ms), then fades out (~280ms) over the mounted shell. Global motion uses CSS tokens --motion-duration-{fast,normal,slow} and --motion-ease-{out,in,standard} with shared overlay/surface enter keyframes; interactive transitions prefer these tokens. Reduced motion collapses splash/overlay motion to near-zero and freezes the progress bar. Crash chrome uses app.uiCrashed. | Boot is a first-run moment that previously felt unfinished; a short branded splash communicates readiness without decorative theatre, and shared motion tokens make shell transitions consistent and silkier while remaining feedback-only. | | D147 | Interaction detail polish (selection, CJK labels, motion fills) | Copyable surfaces use theme-aware ::selection (text-primary mix), caret-color, and accent-color on the monochrome ramp; focus rings mix accent with transparent (no white wash). High-traffic chrome (jump-latest, stop, menus, search rows, notifications, work-panel tab close, brand chip) transitions via --motion-duration-fast. Scrollbars are 8px with a stronger hover thumb. Empty-home stack gap is 24px. Under lang=zh-CN, section labels drop uppercase/wide tracking. Undefined --radius-token-row is replaced by --radius-sm. | Residual gold-polish gaps after the neutral accent + motion-token pass: browser-blue selection, abrupt hover fills, Latin-only label styling on Chinese chrome, and one undefined radius token. | | D150 | Composer runtime chip descenders | Composer toolbar chips (Agent/Plan, Thinking, permission mode, model ID) keep labels fully inked inside the 28px capsule: chip and label line-height is --leading-compact, chips do not clip with overflow: hidden on the control, and the model label uses horizontal ellipsis without leading-none. Descenders on g/y/p/q/j must remain visible in light and dark. | leading-none plus truncate overflow crushed glyph descenders on model IDs and labels such as Agent / Accept edits, making the bottom toolbar look cut off. | | D151 | Send re-pins transcript follow | Starting a turn via send, retry, or regenerate always re-pins transcript follow mode, hides the jump-to-latest pill, and scrolls to the bottom before new content arrives. Manual scroll during a turn still pauses follow; the jump control remains the only non-turn way to resume. This refines D071 without restoring forced scroll on every token. | Users who scroll up to inspect history still expect the next prompt they send to land at the latest exchange; leaving follow paused after send hid the new turn behind the jump pill. | | D152 | Direct runtime stream rendering | Assistant content renders each runtime stream chunk directly through the incremental Markdown block cache. The renderer does not add a requestAnimationFrame typewriter state loop. KaTeX's Vite-inlined fonts remain local-only assets and are admitted by the narrow font-src 'self' data: CSP directive. | The duplicate animation loop could trip React's nested-update guard during sustained streams, while the previous CSP blocked bundled math fonts and produced console errors. | | D153 | Reasoning sessions default to maximum thinking | A newly created session whose inherited default model supports reasoning starts at the highest canonical entry in that model's pi-published supportedThinkingLevels. Non-reasoning models and missing capability metadata start at off; existing sessions retain their durable choice. This refines D096 without adding a provider override. | Reasoning-capable models should use their strongest available effort by default while preserving explicit per-session choices and pi-ai's model authority. |

T. Release delivery decisions

IDTopicDecisionRationale
D126Three-platform release delivery (lifts D010)Tag builds publish every artifact the matrix produces to the GitHub Release: macOS dmg/zip (arm64), Windows NSIS x64, Linux AppImage + deb (x64), each with blockmaps and the platform's latest*.yml electron-updater feed. Publishing the feeds activates D120's in-app update lanes for Windows NSIS and Linux AppImage; macOS stays in notify-and-link mode until a signed channel is qualified. The NSIS artifact name is pinned space-free (PI-Desktop-Setup-${version}.${ext}) because GitHub asset URLs mangle spaces. D010's macOS-only scope is lifted per the baseline-bump rule (baseline 0.4.7); the release pipeline itself was qualified end-to-end on v0.1.1-rc.1/v0.1.1.The pipeline builds and validates all three platforms on every tag anyway; keeping installers as expiring Actions artifacts (90-day retention) withheld them from users without adding safety. Publishing the update feeds is the point of shipping: platforms with in-app lanes update silently, and future platform regressions surface through real installs instead of unused artifacts.

V. Extension activation decisions

IDTopicDecisionRationale
D192Activation scope shared by every extension kindPlugins, user MCP servers and user skills each carry enabled: boolean plus scope: { mode: "global" | "projects"; projects: string[] }. enabled stays separate so switching an extension off never discards its project list, and the three-state control (off / projects / global) is derived, not stored. Matching is case-insensitive, trailing-separator-insensitive and subdirectory-inclusive; a missing scope resolves to global; a projects-mode extension is inactive in a session with no project. Scope is enforced both when a per-turn catalog is assembled and again at dispatch (tools.execute, UserMcpRuntime.callTool, loadUserSkillBody, command execution). Agent-facing surfaces filter on the session's project, app-facing surfaces on the active window's; themes are not scoped at all.A single boolean made every extension global, so a work-only MCP server spent context in every unrelated session and the only remedy was toggling by hand on each project switch. One scope shape lets one control and one predicate serve all three kinds, and enforcing at dispatch closes the window in which a session still remembers a tool the user has just scoped away (ADR 0056).
D193User-owned MCP servers need no pluginhost-core owns McpServerRecord values in <data>/mcp/servers.json behind mcp.list / active / upsert / remove / setEnabled / setScope; Electron main owns the processes in UserMcpRuntime, reusing ADR 0038's McpServerClient. Tools are exposed as mcp_<serverId>_<tool>, distinct from D015's plugin_ namespace. A server connects on first use by a session that can see it and caches its tool list; a failed handshake stays failed until the user edits it or presses Test; saving a change to transport, command, args, env, url or headers drops the connection while label, description and scope do not. Cap 16 live processes, 64 tools per server, commandPolicy: "trusted" with the user's home as cwd. Adding one is primarily a pasted mcpServers block: parseMcpImport accepts the mcpServers / servers / bare-map / single-object spellings, infers http from a url, coerces non-string env and header values, honours disabled: true, caps at 32 entries, and reports per-entry reasons instead of failing the whole paste.Every MCP server in the wild ships as three lines of JSON; requiring a manifest, a package and a signature to run them was a wrapper around nothing. Caching plus sticky failure keeps session assembly off the connect timeout, and invalidating only on identity changes avoids serving a stale tool list after an edit (ADR 0056).
D194User-owned skills are one Markdown documenthost-core stores <data>/skills/<id>/SKILL.md with a registry.json index behind skills.list / active / create / import / update / read / remove / setEnabled / setScope. D174's delivery contract is unchanged: only the description enters the prompt, the body is fetched when the model invokes Skill, 128KB per document. The editor therefore requires a description and places it above the body. User skill ids are bare and plugin skill ids contain /, so loadUserSkillBody rejects any id with a separator and falls through to the plugin catalog with no registry lookup.A skill is a single Markdown file; the plugin path made the smallest possible extension carry the largest possible envelope. Keeping the id shapes disjoint separates the two catalogs without a lookup, and keeping D174's contract means user skills cost the same context as plugin ones (ADR 0056).

2026-07-28 — Plugin marketplace, panels, and high-risk APIs

  • Official local marketplace provider can browse/search/install .piplug packages with checksum verification.
  • Plugin panels run in sandboxed isolated windows via pluginBridge.
  • High-risk plugin host APIs (fs.write, net.fetch, clipboard, openExternal) are available only with explicit grants.
  • Per-plugin auto-update is supported; permission-expanding upgrades require review.

2026-07-28 — Official plugin marketplace repository

  • Dedicated repo vastsa/pi-desktop-plugins is the official marketplace source.
  • PI-Desktop fetches catalog.json remotely (cached under ~/.pi-desktop/plugins/market/).
  • Plugin maintainers pack sources with repo scripts and publish by pushing to that repository.
  • Local bundled catalog remains fallback only when remote fetch fails.

2026-07-28 — Marketplace template + detail pane

  • Official warehouse gained a practical template plugin demo.workspace-summary and CONTRIBUTING guide.
  • PI-Desktop marketplace UI now opens a detail pane with README, changelog, and version list via market.getDetail.

2026-07-28 — Sidebar type balance

  • Expanded sidebar primary chrome uses --text-base (14px) instead of --text-md (13px).
  • Project/group titles step to --text-md; section labels stay secondary at --text-sm.
  • Decision D144: keep micro tokens off primary left-rail content so the rail matches body readability without changing the global type ramp.

2026-07-28 — Chat markdown prose redesign

  • Assistant markdown (.prose-chat) was restyled for denser, calmer transcript reading: clearer heading hierarchy, accent-tinted blockquotes, quieter list markers, bordered inline code, zebra/hover tables, and inset code cards with monospace language tags.
  • Thinking prose reuses the same hierarchy at secondary color / text-sm-plus so reasoning stays visually subordinate to the answer.
  • Renderer behavior (streaming block split, GFM/math, Shiki) is unchanged; this is a presentation-only pass in globals.css + component/design specs.

2026-07-28 — Neutral gray accent (no blue brand)

  • --ds-accent / --ds-accent-hover / --ds-accent-soft / --ds-info now resolve to the gray scale (dark: white→gray-100→gray-300; light: #1a1c1f#303030#5d5d5d) instead of Codex blue.
  • Markdown links, blockquotes, focus rings, plugin CTAs, toggles, and selected-session rings inherit the neutral accent automatically via tokens.
  • Project color dots and docs/specs updated to drop blue as the brand accent.

2026-07-28 — Plugins page light-theme token pass

  • Marketplace/plugin chrome CSS dropped raw blue-slate fallbacks (#4f7cff, #2a3144, #121826, …) and now consumes only --ds-* tokens.
  • Tabs, actions, search, cards, permission modal, and badges adapt to light/dark via the neutral gray accent system.

2026-07-28 — Markdown light-theme paper pass

  • Light .prose-chat / .code-block surfaces were retuned for white chat paper: softer underlined links, flat gray code cards (no muddy shadow), quieter blockquotes/tables/kbd/math, and secondary thinking ink.
  • Dark markdown treatment is unchanged in spirit (inset charcoal code, light-gray links via accent-soft).

2026-07-28 — One Dark Pro code highlighting

  • Chat fence highlighting switched from github-light/github-dark to one-light/one-dark-pro.
  • Code cards paint a single editor surface (#fafafa / #282c34); nested pre/code/token backgrounds are forced transparent so there is no double wash.

2026-07-28 — Disable text correction on editable fields

  • Shared Input/Textarea primitives default spellCheck={false}, autoCorrect="off", and autoCapitalize="off".
  • Composer, message edit, palette/search fields, settings/plugins/projects search, model search, browser URL bar, and provider model combo follow the same contract.
  • Decision D145: browser/OS spelling and autocorrect chrome stays off across the desktop shell.

2026-07-28 — Startup splash and motion tokens

  • Boot path paints StartupSplash (brand mark, shell name, tagline, progress bar) until host/settings bootstrap finishes.
  • Shared CSS motion tokens and overlay/surface enter keyframes polish dialogs, search, toasts, and interactive fills.
  • Decision D146: splash is boot feedback with reduced-motion-safe exit; catalogs gain app.uiCrashed and finish zh-CN empty-home/custom copy.

2026-07-28 — Interaction detail polish

  • Theme-aware ::selection, caret-color, and accent-color keep copy/edit chrome on the neutral gray accent ramp.
  • Hover fills on jump-latest, stop, composer-plus, search rows, profile/notification menus, and work-panel tab close use shared motion tokens.
  • CJK section labels under :lang(zh-CN) use normal tracking without forced uppercase.
  • Empty-home stack gap clamped to 24px; scrollbars refined to 8px with hover thumb; brand chip radius uses --radius-sm.
  • Decision D147.

2026-07-28 — Work panel / settings light-surface polish

  • Light work panel uses a quiet #fafafa inset column with a white header band so it separates from white chat paper without a heavy border.
  • Browser URL, generic field controls, shortcut keycaps, segment tracks, and toggle knobs receive light-theme surfaces and focus rings aligned with settings pills.
  • File tree, diff headers, resize handle, and destination filters ease hover fills with shared motion tokens.
  • Light dialog scrim softens to 28% ink so elevated white dialogs stay readable.
  • Decision D148.

2026-07-28 — User-facing i18n copy pass

  • English and zh-CN catalogs rewrite high-traffic shell copy away from internal jargon: local service instead of host/backend, AI provider instead of bare provider, project instead of workspace in user strings, marketplace refresh instead of "from repo", temporary chats, and calmer status/error phrasing.
  • Empty states, onboarding, settings help, plugin permissions, and notifications explain outcomes in plain language while keeping stable i18n keys and interpolation names.
  • Decision D149.

2026-07-28 — Composer runtime chip descenders

  • Model, mode, thinking, and permission chips no longer use leading-none under overflow clipping; labels use --leading-compact so descenders stay visible.
  • Long model IDs still ellipsize horizontally via .model-chip-label.
  • Decision D150.

2026-07-28 — Send re-pins transcript follow

  • Starting a turn from send, retry, or regenerate re-pins the transcript and jumps to the bottom even if the user had scrolled up through history.
  • Stream follow remains paused only for manual scroll during an active turn; the jump-to-latest pill still resumes follow without starting a new turn.
  • Decision D151.

2026-07-28 — Direct runtime stream rendering

  • Assistant responses now display the runtime's progressive chunks directly through the incremental Markdown block cache, without a second per-frame React state loop.
  • Renderer CSP admits only local and Vite-inlined data fonts, allowing bundled KaTeX glyphs without opening a remote font origin.
  • Decision D152.

2026-07-28 — Reasoning sessions default to maximum thinking

  • New sessions inherit the app's default model and select its highest pi-published thinking level when that model supports reasoning.
  • Non-reasoning or unresolved models remain off, while existing sessions keep their stored thinking preference.
  • Decision D153.

2026-07-28 — Work-panel activity rail

  • The open work panel now keeps Review, Terminal, and Browser in a compact 44px activity rail with clear active/open states.
  • The content header shows the active resource, closes it directly, and uses a bounded keyboard-operable switcher for all open tool and file resources.
  • The former horizontally scrolling tabs and hidden header context menu are removed; artifact-driven panel opening and session ownership are unchanged.
  • Decision D154.

2026-07-28 — Sidebar project/session list type density

  • Sidebar project group titles and session/thread titles step one token quieter than the previous body-chrome sizing so dense lists scan more cleanly.
  • Session titles use --text-md (13px); project/group titles and empty-state copy use --text-sm (12px). Primary chrome (New task, Plugins, footer) stays at --text-base.
  • Decision D155; superseded by D159 for the expanded sidebar's primary list content.

2026-07-28 — Independent window and work-panel resizing

  • Native window edges now resize only the Electron shell; work-panel open, collapse, close, and divider commits leave outer bounds unchanged.
  • The divider uses anchored pointer delta, frame-coalesced preview, rollback on cancellation, and a wider stable hit area. Responsive clamping no longer overwrites the persisted preferred width.
  • Removed the renderer-to-Main window/resizeBy channel, programmatic resize attribution, and panel-specific window-state offset.
  • Decision D156; ADR 0029.

2026-07-28 — Single assistant toolbar per user turn

  • Provider-level assistant messages separated by tool calls remain canonical transcript records for model reseeding, persistence, and fork boundaries.
  • The chat transcript composes every assistant/thinking/tool record after one user message and before the next into one visual assistant turn, preserving order while exposing one aggregate meta row and one Copy/Fork/Retry toolbar.
  • Copy joins all contentful assistant fragments; Fork and Retry target the last contentful assistant record. Decision D157.

2026-07-28 — Turn-boundary context checkpoint compaction

  • PI-Desktop now evaluates context after every turn_end, before the next provider request, with a transient soft reminder and a deterministic hard checkpoint guard.
  • Durable checkpoint records rebuild model context without deleting or hiding visible transcript messages; exact provider overflow receives one compacted retry.
  • The implementation reuses pi-agent-core primitives. OpenCode DCP remains an AGPL-3.0 behavioral reference, not a dependency or copied implementation.
  • Decision D158; ADR 0030.

2026-07-28 — Sidebar typography aligned with the global body scale

  • Expanded-sidebar session titles return to --text-base (14px), matching the app body and primary sidebar chrome; project/group titles and empty-state copy return to the adjacent --text-md (13px) tier.
  • Section labels and secondary metadata remain at --text-sm (12px), and the existing compact row pitch, truncation, and sidebar dimensions are unchanged.
  • Decision D159; supersedes D155 and restores D144's primary-list hierarchy.

2026-07-28 — Icon-free composer prompt row

  • Home and thread-docked composer prompt rows render no leading brand icon; draft text and placeholder ink align directly with the input gutter.
  • The canonical logo remains in the home hero, sidebar, native application identity, startup splash, and About surfaces. Session-creation controls keep their dedicated message-plus icon. Decision D160; ADR 0031.

2026-07-28 — Smooth latest-wins session switching

  • Session rows now acknowledge the newest selection immediately, prefetch on deliberate hover/focus, coalesce detail reads, and retain five recent transcripts for warm revisits with background revalidation.
  • Superseded transcript reads no longer block the latest request. Workspace alignment overlaps transcript IO where summary metadata permits, while the navigation generation still owns the only visible commit.
  • The previous complete transcript remains as a dimmed, non-interactive frame while React prepares a changed session's Markdown tree; reduced motion uses a static progress track. Decision D162.

2026-07-28 — Compact sidebar session titles

  • Expanded-sidebar session titles use --text-md (13px), matching the compact project/group tier while primary actions and footer identity remain at --text-base (14px).
  • Row height, truncation, indentation, weight, and sidebar dimensions remain unchanged.
  • Decision D161; refines D159's session-title size.

2026-07-29 — Native width reservation for the fixed work panel

  • The docked panel now keeps its committed 364..720px width while open; native edges resize MainChat only.
  • Open, collapse/final close, and divider commit set one idempotent native-width target. Chat remains stable when the work area can reserve the full width and absorbs only the unavoidable shortfall otherwise.
  • Maximized/fullscreen geometry waits for normal state. Persisted base bounds exclude reservation width/x shift, and background artifacts cannot change the visible target.
  • Decision D163; ADR 0032 (the window-expansion portion is superseded by ADR 0033: the work panel is an internal dock that never expands the OS window). This supersedes the contrary portions of D156 and ADR 0029.

2026-07-29 — Dual-locale in-app product changelog

  • Ship EN + zh-CN product highlights in packages/shared and attach them to UpdateState.releaseNotes from Electron Main when an update is discovered.
  • The update banner and Settings → Info surface a compact What's new list in the active product locale without a new feed or IPC domain.
  • Release tagging requires updating both locale catalogs before the tag build.
  • Decision D164; extends D120 / ADR 0022.

2026-07-29 — Release process: mandatory dual-locale changelog gate

  • Stable app version bumps / tags must update packages/shared/src/changelog.ts (EN + zh-CN) before the tag; shipping without catalog entries is a release process failure (D164).
  • Codified in the release runbook §4.1, AI development workflow matrix + forbidden practices, change checklist, AGENTS.md, and scripts/release.mjs header so agents and humans hit the same gate.
  • GitHub auto-generated release notes remain web-only.

2026-07-29 — Bounded atomic tool batches in context checkpoints

  • Automatic compaction now bounds an oversized final parallel tool-result batch inside the checkpoint copy instead of repeatedly failing at the same transcript boundary.
  • The retained copy preserves every call/result envelope, distributes text budget fairly with explicit head/tail truncation markers, and omits duplicate provider-irrelevant details; original transcript rows remain complete.
  • Decision D158; amends ADR 0030's previous policy of blocking an indivisible batch above the retained-tail cap.

2026-07-29 — Safe lazy Mermaid diagrams in assistant answers

  • Completed mermaid fences in answer prose render through a dynamically loaded, theme-aware Mermaid chunk only near the viewport; partial streams and thinking prose remain source code.
  • Rendering is serialized and bounded at 20,000 source characters / 500 edges. Strict Mermaid configuration plus DOMPurify SVG sanitization removes links, embedded media, foreign HTML, and URL attributes before DOM insertion.
  • Invalid or oversized diagrams fall back to source with copy and view controls; no IPC, storage, process, CSP, or external-network boundary changes.
  • Decision D165.

2026-07-31 — Slimmer default work-panel width

  • The docked work panel now opens at 280px instead of 420px, a third narrower, and its fixed clamp becomes 244..720px.
  • WORK_PANEL_DEFAULT_WIDTH, WORK_PANEL_MIN_WIDTH (renderer and Electron Main), and the .work-panel CSS floor stay in sync; divider double-click restores the new default.
  • D154's 364px floor was sized for a 44px activity rail beside 320px of content; the rail became a header switcher, so the floor scales with the default.
  • Persisted wider widths remain valid, and the 720px maximum is unchanged.
  • Decision D167; supersedes the width clamp in D154/D163 and ADR 0033 §4.

2026-07-31 — Project archive presentation redesign

  • Settings → Project archive is rebuilt as three bands: an overview banner with four derived counters, a search + Recent/Name sort toolbar, and a grouped index in the order Pinned / All projects / Archived with per-section counts.
  • Rows gain an always-visible disclosure control, state tags, a single meta line (path, branch, session count), a relative last-active time, and a quick New task action beside the row menu; the menu now groups create/edit above pin, archive/restore, and Close and dismisses on Escape or outside press.
  • Archived records stay grouped and softened rather than filtered, so the destination still has no visibility toggle (D133). Project storage, search matching, batch-of-eight session reveal, and activation semantics are unchanged.
  • Decision D168; supersedes D133's flat-list presentation.

2026-07-31 — Plugins page redesign (presentation amended by D196)

  • The original Plugins page was rebuilt as four bands: an overview band with four derived counters (installed, enabled, updates, high-risk access), a header that keeps one contextual primary action and moves check-updates / apply-auto-updates / install-package / load-local into an overflow menu, a segmented Installed / Marketplace switch carrying counts, and the tab body.
  • Installed rows group by state as Needs attention / Updates available / Active / Turned off inside one hairline-separated panel. status: "error" | "load_error" and errorMessage are now surfaced instead of dropped; row actions collapse to a hover-revealed panel button plus an overflow menu (auto-update, Uninstall as a danger item) beside an always-visible switch.
  • Permissions are tiered from the risk column of 07-plugins/13-plugin-permissions-matrix.md: risk-tinted chips with collapsed overflow on rows, and High / Medium / Low sections in the detail sheet and the install dialog. Upgrade reviews tag permissions the new version adds as New, and the install queue deduplicates the declared set against the diff.
  • Details move from a nested sidebar to a right-side sheet (scrim, Escape and outside-press dismiss, sticky install action, selectable version rows). Marketplace cards render a monogram glyph and never fetch iconUrl, so the renderer performs no remote image loads.
  • Decision D169; supersedes the flat list, pill tabs, and inline detail pane of 07-plugins/07-plugin-marketplace.md §7/§14.

2026-07-31 — Renderer stylesheet split into per-surface partials

  • apps/desktop/src/styles/globals.css becomes an import-only entry point. The rules move into 22 partials in the same directory, each owning one surface: tokens, base, chrome, chat-shell, composer, sidebar-threads, messages, prose, ui-kit, overlays, theme-overrides, composer-menus, settings, destinations, projects, sessions, work-panel, providers, chat-links, composer-autocomplete, plugins, responsive.
  • Import order is the cascade and must not be reordered: tokens and base first, feature layers in build order, the responsive / reduced-motion tail last so it can still override what precedes it.
  • The split is contiguous — no rule changed position relative to another. The joined partials reproduce the pre-split file byte for byte, and the built renderer CSS is byte-identical before and after, so Tailwind v4 resolves the @theme block from tokens.css unchanged.
  • Style assertions load the effective cascade through apps/desktop/test/helpers/styles.mjs, which inlines the local @import lines in declaration order. Tests must not read a partial directly.
  • Sidebar session styles live in both sidebar-threads.css and sessions.css because the original file interleaved them; the file headers cross-reference.
  • The design-token scales now live in styles/tokens.css; the guard in scripts/check-style-tokens.mjs walks the whole src tree and is unaffected.
  • Decision D170; the single-file layout assumed by 04-ux/07-ui-design-system.md §Typography and 04-ux/08-component-spec.md no longer holds.

2026-07-31 — Plugin skills activation and the plugin devkit

  • contributes.skills is activated. PluginRuntime.getSkills() reads each declared skill file at prompt time, only for plugins granted agent.prompt.inject, and only through the containment guard the gated fs APIs use. The agent runtime renders a # Plugin skills section capped at 16 KiB total and 8 KiB per skill — its own budget, not the 32 KiB instruction chain of ADR 0037 — and orders it after the built-in skills but before project instructions, so a user's own files keep the last word. Runtime reuse keys on a skills digest, so enabling a plugin, revoking the permission, or editing a skill file retires the idle runtime instead of reusing a stale prompt. Closes roadmap gap R2.
  • Plugin authoring ships as a first-party package, @pi-desktop/plugin-devkit, which owns scaffold / check / pack over one implementation shared by the pi-plugin CLI, the PluginScaffold / PluginCheck / PluginPack agent tools served from Electron main, and the plugins page's New plugin from template action. check reproduces the rules host-core enforces, so passing it implies install will pass; pack writes store-only (method 0) .piplug entries because extract_zip_bytes accepts nothing else. Closes roadmap gap R3's template and check/pack items.
  • A bundled plugin was rejected as the delivery vehicle: a plugin cannot produce a .piplug (no archive API in HOST_API_ALLOWLIST) and scaffolding would need high-risk fs.write.workspace for a capability the application should provide itself.
  • The built-in plugin-development skill activates only for plugin workspaces — a plugin manifest.json at the workspace root, or a loaded development plugin inside it — so an ordinary session pays only for three tool descriptions.
  • Development plugins are watched and hot reload on save, debounced 300 ms, ignoring node_modules / .git / dist / target, capped at 16 plugins, and re-armed across restarts. A reload can never widen a permission set: the manifest is compared against the set approved when the folder was picked and a new permission stops the reload with PERMISSION_DENIED, while removed permissions do take effect. A failed reload keeps the watch so the fixing save recovers the plugin, and reports through a toast plus pluginChanged — host-core has no RPC for a runtime-side load failure, so the registry row does not move to load_error. Closes roadmap gap R3's hot-reload item.
  • Decision D171; recorded as ADR 0039.
  • The prompt-injection half of this decision was replaced the same day by D174: skills now reach the model as a catalog plus a Skill tool. The devkit, hot-reload and workspace-gate clauses stand unchanged.

2026-07-31 — Creating a plugin from a template opens the folder

  • Creating a plugin from a template now also opens the chosen folder as the active project (workspace.set, which registers the project and switches to chat), not just as a loaded development plugin. Loading only makes the plugin run; development needs the sources inside the workspace the agent, the file panel and the built-in plugin-development skill all read, and requiring the user to re-pick the same folder through Open folder was pure friction.
  • The activation runs in the renderer through the existing activateProject action rather than from the template IPC handler, so project state, the sidebar project list and the navigation intent guard keep their single owner.
  • The success toast distinguishes the two outcomes: if the folder cannot be opened as a project the plugin stays loaded and the toast says only that, instead of claiming a workspace that is not there.
  • Loading an existing local plugin folder (Load local plugin) deliberately keeps its current behavior: running someone else's plugin is not a reason to switch the user's project.
  • Decision D172; no ADR — this completes the flow ADR 0039 describes.

2026-07-31 — Work panel header menu: tools first, no duplicated entries

  • The unified header menu lists the four tools (Review, Terminal, Browser, Files) first, in a fixed order, and only then — after a divider, and only when they exist — the resources the transcript opened. The previous layout listed every open tool twice: once in the resource switcher and again in the create-new section, which made "open" and "switch to" indistinguishable. Each tool row now carries its own open state and its own close control, so a single row is the whole affordance for that tool.
  • Activating a tool that is already open activates its existing tab instead of replacing it with a fresh singleton, so the Browser keeps its URL. The header action cluster is pinned right (margin-left: auto) so the close/collapse controls no longer slide with the label length, and the trailing close slot in each row is always reserved so labels and open dots never shift.
  • Menu rows own real DOM focus (WAI-ARIA menu pattern) rather than a roving highlight: the trigger's ArrowDown/ArrowUp opens on the active or last row, Arrow/Home/End walk rows only, Delete/Backspace closes the focused row while the menu stays open with focus on its neighbor, and Escape/Tab/selection return focus to the trigger. Only a session switch dismisses the menu implicitly — selecting a row closes it explicitly, so the previous active-tab-keyed auto-dismiss (which fired whenever a background artifact changed the active tab) is gone.
  • Missing panel.tabs.file was the reason a bare Files tab showed a literal file label; the catalogs now carry it plus panel.tools, and the obsolete panel.openTool is removed.
  • Decision D173; no ADR — presentation and input handling only, inside the existing work-panel architecture (ADR 0033).

2026-07-31 — Plugin skills are model-invoked

  • contributes.skills is activated at load time behind agent.prompt.inject. Each entry may be a path or { path, id?, name?, description? }; front matter in the document supplies name / description when the manifest does not.
  • The base system prompt carries only a catalog — skill id, name, and a description trimmed to 240 chars. Bodies are not in the prompt; the model fetches one through a built-in Skill tool that Electron main serves locally against plugins.loadSkillBody(id), so the sidecar never holds skill text.
  • Caps: 32 skills per plugin, 128KB per document. A manifest without agent.prompt.inject still validates — skills predate the permission gate — and the runtime simply skips them.
  • Skill ids join the runtime-reuse key in packages/agent-runtime/src/runtime.ts, so enabling or disabling a plugin rebuilds the runtime instead of serving a stale catalog.
  • Rejected: user-facing slash commands. A skill is guidance the agent should reach for when a task calls for it, not a command the user has to know exists.
  • Decision D174; closes the "parsed but never activated" gap in 07-plugins/14-plugin-roadmap.md R2.

2026-07-31 — Plugin themes ship CSS files

  • contributes.themes declares { id, label, path, base? } and requires ui.theme. path is a plugin-relative .css file; base (light | dark, default dark) names the palette the overrides layer on.
  • The main process reads the file and runs sanitizeThemeCss(): no @import, no url() outside data:, no unparseable url(, no javascript: / expression(, no markup sequences, 256KB cap, 8 themes per plugin. The renderer receives finished text over plugin/themes and injects it into one <style id="pi-plugin-theme"> appended after the app's own stylesheets.
  • AppSettings.theme widens to plugin:<pluginId>:<themeId>. When the providing plugin is disabled, uninstalled, or fails to load, the app falls back to system rather than rendering an unstyled shell.
  • Rejected: a token-JSON contribution. It would have been safer to validate, but it can only express the tokens we thought to enumerate; a stylesheet lets a theme reach a surface the token list forgot, and the sanitizer plus append-order rule bound the risk to appearance.
  • Decision D175.

2026-07-31 — Plugin MCP servers over stdio and remote HTTP

  • contributes.mcpServers declares { id, label?, transport } plus exactly one transport's fields: stdio takes command / args / env, http takes url / headers. Permissions are separate: mcp.server.local for stdio, mcp.server.remote for HTTP.
  • apps/desktop/electron/main/plugin-mcp.ts speaks protocol 2025-06-18initialize, tools/list, tools/call — as NDJSON over stdio or streamable HTTP/SSE. Budgets: 10s connect, 100s per call, 8 tools/list pages, 4MB per stdio line, 64 tools per server, 8 servers per plugin. Connection is lazy; teardown follows unload.
  • Discovered tools register as plugin_<pluginIdSafe>_<serverId>_<toolName> in the existing plugin tool map, so they inherit the audit trail, the timeout, and the disable switch with no new routing. They are always risk: "medium": the schema and description come from a third party, so a self-declared risk level is not trustworthy.
  • env and headers resolve only from the plugin's own settings via { "setting": "<key>" }; the host environment is never passed through (D018). A stdio child gets PATH, temp/locale vars, and the declared values — nothing else. command must be a bare PATH name or plugin-relative; url must be https unless the host is loopback.
  • Both transports ship rather than stdio alone: a hosted MCP endpoint is common enough that stdio-only would have pushed plugins to wrap it in a local shim, which is strictly worse — an extra process and an unreviewable proxy.
  • Decision D176; ADR 0038.

2026-07-31 — Resident plugin services and their restart policy

  • contributes.services declares { id, label?, autoRestart? } behind background.service, at most 4 per plugin. The plugin calls pi.services.register({ id, start, stop }); the broker calls start after onLoad (5s budget) and stop before onUnload, so a service is never live outside the plugin's own lifetime.
  • A service lives in the plugin's utilityProcess, so a crash takes it down with the process and the supervisor restarts the whole plugin: backoff 1s, 2s, 4s, 8s, 16s capped at 30s; at most 5 attempts; a process that survives 60s is healthy and the counter resets. autoRestart: false opts out. After the last attempt the plugin stays failed — a visible failure beats a silent crash loop.
  • Per-service state (starting | running | stopped | failed) and the restart count are read over plugin/services and rendered as chips on the Plugins page; every transition emits pluginChanged with reason: "service" and a plugin.service.* audit entry.
  • Manual enable / disable outranks the supervisor: an explicit action cancels the pending timer and clears the attempt counter.
  • Decision D177; ADR 0040.

2026-07-31 — Inter-plugin message bus routes declared topics only

  • pi.bus.publish / subscribe require bus.publish / bus.subscribe and a matching entry in contributes.bus: publishers list concrete topics, subscribers list patterns. A granted permission alone routes nothing, so the manifest stays a complete description of what a plugin says and hears.
  • Topics are dot-separated segments ([a-zA-Z0-9][a-zA-Z0-9_-]*, ≤8 segments, ≤128 chars). * matches one segment; ** matches one or more trailing segments and may appear only last.
  • Routing lives in the broker. A message carries topic, from, payload, and a host-assigned at; the publisher is excluded from its own fan-out; delivery is fire-and-forget over a new one-way { t: "event" } frame, so a wedged subscriber cannot stall the sender. That frame also makes the previously stubbed pi.events.on / off real.
  • Caps: 64KB per payload, 16 subscriptions per plugin, 100 publishes per rolling 10s window, failing with LIMIT_EXCEEDED / RATE_LIMITED and an audit line.
  • A payload conveys data, never capability: receiving a message grants the subscriber nothing it did not already hold, so a topic should be treated as public within the app.
  • Decision D178; ADR 0040.

2026-08-02 — Bash tool inherits the user's login-shell PATH

  • On Unix, the first Bash call probes the user's login shell for its PATH — $SHELL (fallback /bin/zsh/bin/bash/bin/sh) with -lic 'printf %s "$PATH"' — so -l sources login files and -i sources the interactive rc, matching a fresh terminal. The probe is bounded to 5s and cached per process (OnceLock); only the last stdout line is kept (rc banners are ignored), stderr is discarded (missing-tty noise), and a non-zero exit, missing shell, or timeout silently falls back to the host PATH.
  • Every Bash subprocess gets the probed PATH injected via cmd.env("PATH", ...); bash -lc still re-runs the bash profile at startup (conda/brew hooks may prepend/dedupe/reorder entries on top of the injected base). Agent commands remain POSIX bash; the resolved bash binary is unchanged. Windows keeps bash -c with the host environment (no change).
  • Fixes macOS Finder/Dock launches where bash -lc alone cannot see nvm, pnpm, or Homebrew tooling initialized in ~/.zshrc / ~/.zprofile.
  • Decision D181; ADR 0045.

2026-08-02 — Route process logs into category files

  • The app, host, and agent channels remain local NDJSON files, but each channel is now a directory containing focused <category>.log files. App records use explicit lifecycle/session/tool/permission/plugin/provider/ persistence/updater/diagnostics/terminal/runtime categories; host and agent stderr is classified into the same categories, with timing lines isolated in timing.log.
  • Every record carries a category field. Child stderr is buffered by line, decoded as UTF-8, and stripped of ANSI control sequences before it is persisted. Unknown child output goes to runtime.log.
  • Rotation remains 5 MB with two rotated files, but the limit applies to each category file. The logger uses byte length for UTF-8 records and treats rotation and disk failures as best effort. Existing flat log files are not deleted during migration.
  • Decision D182; ADR 0046.

2026-08-02 — Context usage inspector

  • Replace the oversized context ring with a compact Codex-style trigger that combines a remaining-capacity ring, Context label, and percentage. Hover and keyboard focus open a non-modal panel with the context window, exact provider input/output/cache/reasoning usage, aggregate generation speed in tokens/s, and each unique tool type in first-seen execution order.
  • Tool rows aggregate repeated calls and expose call count, argument tokens, result tokens, total estimated footprint, share bar, and cumulative known duration. Runtime estimates use pi-agent-core's existing four-characters- per-token heuristic; provider-reported usage remains the authoritative total and the UI labels tool rows as estimates.
  • Generation speed is a completed-turn snapshot from provider output and final stream duration; active assistant streams do not show a live token-rate counter.
  • The context-window total comes from the matching pi-ai model metadata used by the agent sidecar; provider metadata and the 128K default remain fallbacks for unknown models.
  • UiMessage.responseDurationMs, UiMessage.toolUsage, and the optional tool_end.toolUsage event field are additive, so older persisted messages and peers remain readable.
  • The inspector panel is rendered at the document body level as a fixed, collision-aware viewport overlay. It follows transcript scroll and window resize, flips around the trigger, and clamps to viewport margins instead of being clipped by the transcript scroll container.
  • The inspector resolves its context-window total from the same pi-ai model record passed to the agent sidecar, enriching cached/discovered model rows; provider metadata and the 128K default remain fallbacks for unknown models.
  • Decision D184; ADR 0047.

2026-08-02 — Lazy per-turn tool activation

  • The sidecar keeps a complete local registry but sends only the mode's core tools and local ToolSearch on a new prompt. (CompactContext was also always-active here until D200 removed the tool; D203 restores it as new_context, again always-active.) Agent follows pi's coding-agent core (Read/Bash/Edit/Write), while Chat keeps (Read/Glob/Grep). Agent-mode Glob/Grep, BrowserPreview, plugin tools, Skill, and plugin-development helpers are represented by bounded compact catalog entries instead of full parameter schemas.
  • An exact-name or capability search activates at most four matches for the next model turn. addedToolNames lets pi-ai providers with native deferred search serialize those definitions at the load point; other providers use the rebuilt active tool list. Activation resets before the next user prompt.
  • Host permissions, workspace and scratch containment, timeouts, and audit behavior are unchanged. Persisted tool results retain activation markers for valid transcript reconstruction.
  • Decision D185; ADR 0048.

2026-08-04 — Bound provider stream recovery and diagnostics

  • Provider request setup now has one bounded pi-ai retry. A transient stream, network, or timeout failure after streaming begins gets one same-turn retry with a short abortable backoff; the failed assistant is removed from model context and the visible assistant id is reused.
  • terminated and equivalent incomplete stream messages map to STREAM_FAILED. A second failure remains terminal and carries bounded phase, timing, provider status/code, and retry-attempt diagnostics when available.
  • Mutation recovery is explicitly finite: use Edit for one unique local replacement, use Write for a coherent rewrite, then allow one fresh read/regeneration after a mismatch. A second same-path Edit failure or failed shell patch command emits the terminating tool hint instead of repairing an old patch artifact.
  • Decision D186; see ADR 0050.

2026-08-04 — Recover automatic compaction failures with a retained tail

  • Automatic threshold and provider-overflow compaction failures now attempt a deterministic, aggressively bounded retained-tail checkpoint before ending the turn. The previous checkpoint summary is preserved when available, the complete visible transcript remains untouched, and host persistence plus the hard-budget recheck remain mandatory.
  • The summary input is preflighted against the model window so an obviously oversized summary request goes directly to the bounded recovery path instead of waiting for a provider rejection or timeout.
  • The lifecycle event marks recovery with fallback: "retained_tail", so the renderer keeps the run active and shows a warning. Manual /compact remains fail-fast and never silently discards historical context.
  • Decision D158; amends ADR 0030 and adds ADR 0049.

2026-08-04 — Resource-isolated host RPC stdio

  • Host-core no longer uses Tokio's stdio adapters for its NDJSON control pipe. One named OS thread reads stdin and one named OS thread serializes stdout, keeping per-message framing and retrying interrupted or transient EAGAIN/EWOULDBLOCK errors.
  • This closes the process-exit path where Tokio's blocking pool panicked after the OS refused another worker thread with Resource temporarily unavailable (errno 35 on macOS). Failure to create a control thread is reported as a startup error; it is not an unhandled thread-spawn panic.
  • The login-shell PATH probe uses thread::Builder as well and falls back to the inherited PATH when the optional helper cannot start.
  • Decision D187; see ADR 0051.

Plan checkpoint and shell decisions (0.4.14)

The earlier local Plan operating-state decision remains recorded as D188 for history. D189 is the implementation authority and supersedes D188 and ADR 0052. D190 defines the shell execution contract used by D189. The agent-only decision developed in parallel is renumbered D191 (ADR 0055) and is superseded by D188/D189; the three decisions that shipped alongside it follow as D192, D193, and D194.

IDTopicDecisionRationale
D188Replace Chat profile with Plan state(superseded by D189) **PI-Desktop has one pi Agent and one product selector: `AgentPlan. Plan is that Agent after entering planning state, never a second Agent, planner model, planner service, or permission mode. Agent remains the default. Persisted sessions, app defaults, and scheduled values stored as chatmigrate toplan; the internal page = "chat"route may remain as a conversation-surface detail. Plan exposesRead, Glob, Grep, BrowserPreview, Bash, CompactContext, and ExitPlanMode; it denies Write, Edit, plugin tools, and unknown tools. Plan retains permission-mode selection: Bash prompts under askandaccept-edits, and runs without confirmation under auto`, so Plan is planning intent rather than a strict read-only security profile.**
D189Plan checkpoint artifact, approval, and execution epoch**The same pi Agent uses `AgentPlan, with Agent default. Plan calls SubmitPlan(title, markdown, question)as the only tool in its assistant batch. Rust host-core writes the submitted Markdown bytes unchanged to a new immutable unique file under<workspaceRoot>/.pi/plan/*.md; it stores the relative artifact path, SHA-256, and byte size together with structured title/question fields in the existing plan_approvalsrow. No title/question wrapper is added and no prior artifact is replaced. The approval surface displays title, question, an artifact opener, absolute expiry, and status, and offers only Approve or Reject. Approve requires an explicitask, accept-edits, or autopermission mode, with Ask selected by default; Reject carries no mode. The approval expires at one absolute 30-minute deadline and usesPLAN_APPROVAL_TIMEOUT. The same plan_approvalsrow carriesexecution_idandexecution_statethroughqueued → running → completed
D190Selectable command shell catalog and execution identityHost-core exposes stable platform-aware catalog IDs: windows-powershell, cmd, git-bash, and bash; the platform catalog contains only IDs supported by that platform. defaultCommandShell persists in host settings, and settings writes reject unavailable or wrong-platform IDs. If a persisted choice later becomes unavailable, the effective shell intentionally falls back to the first available platform shell. The Bash tool and tools.execute protocol name remain unchanged; each turn pins the effective shell ID and dialect, and host rejects a stale ID/dialect before spawn with COMMAND_SHELL_CHANGED. Shell identity is the catalog selection, not an executable path hash. Bash streams stdout and stderr separately, uses a mandatory 60-second default timeout with a 1–300 second override, and cancellation/timeout shuts down the complete process tree.Users can choose the command language without multiplying protocol tools, while platform validation, explicit fallback, and turn-pinned catalog identity keep execution predictable

2026-08-05 — Agent-only mode

  • agent is the only session mode. The former chat profile is renamed read-only; host-core still hard-denies Write/Edit/Bash for it, but nothing in the UI can select, display, or command it.
  • The mode gate is negative everywhere (mode != "agent" → read-only surface) and chat normalizes to read-only on every host write path, so a legacy or unknown value fails closed instead of widening tools.
  • BASH_DISABLED_IN_CHAT / WRITE_DISABLED_IN_CHAT become BASH_DISABLED_IN_READ_ONLY / WRITE_DISABLED_IN_READ_ONLY.
  • Database open rewrites sessions.mode = 'chat' and a stored defaultMode of chat to agent — a data fix-up inside schema v7, not a version bump.
  • Decision D191, superseded by D189; see ADR 0055. The mode concept it removed returned as Plan (D188/D189, ADR 0052/0053); the entry is retained because the read-only normalization and the chat fix-up it describes shipped.

2026-08-05 — Structured tool-result presentation in the transcript

  • Expanded tool rows no longer render JSON.stringify of the arguments and the result. A pure renderer module maps each known payload to labeled blocks: file and written content as highlighted code, commands as shell, stdout and stderr as separate blocks, Glob results as clickable paths, Grep hits grouped per file, failures as an error note. Unmapped plugin payloads degrade to label/value fields and labeled blocks; only nested objects keep a JSON body.
  • The pi-ai result envelope carries the structured payload in details and repeats it as text for the model. Only the structured half is rendered, so no byte is shown twice.
  • Collapsed rows carry outcome chips (exit code, match/file counts, replacement count, written or read size, truncated, scratch) so a result reads without expanding. A successful exit earns no chip.
  • Search results follow the host's outputMode: grouped hits, a path list, or per-file totals. A host notice (scoping, clipped lines, Read window) renders as a neutral note under the blocks it qualifies, never as an error.
  • Edit rows draw their own diff only when no ReviewChangeCard owns one, keeping workspace edits single-sourced. The inline permission card shares the same block renderer for its args preview.
  • Blocks are built on expansion only and highlighting is skipped above 100 KB or 800 lines; lists and diffs are capped and report the hidden remainder.
  • Decision D192; renderer-only presentation, so no ADR. See 04-ux/08-component-spec.md §9 and E2E-097.

2026-08-05 — A silent assistant turn re-runs once before it is an error

  • A turn that ends with no tool call and no visible text is no longer reported as complete. The runtime pops the empty assistant out of model context, appends a no-output nudge to the system prompt, and calls agent.continue() once — reusing the visible bubble id and swallowing the first attempt's turn_end / agent_end, so a successful recovery is invisible to the user.
  • Blank visible text triggers it even when reasoning content is present. The observed failure was exactly that shape: a 2830-character conclusion in thinking and an empty text, which reached the user as nothing. The timing log keeps thinkingOnly so the two shapes stay distinguishable.
  • The nudge rides on state.systemPrompt rather than prepareNextTurn, which only shapes in-flight turns and whose context is discarded once the loop stops. It is restored only if still unchanged, so a concurrent prompt rebuild wins.
  • A second silence is terminal: retriable EMPTY_MODEL_RESPONSE, which the existing assistant error row renders with a "Try again" action. One re-run per prompt, so overflow recovery in the same prompt cannot multiply attempts.
  • Decision D193; recovery inside the existing loop contract, so no ADR. See 03-runtime/02-agent-runtime.md §5e, 03-runtime/08-error-codes.md §3.2, and E2E-098.

2026-08-05 — Per-tool output budgets, scoped search, and stated collaboration rules

  • The single 256KB / 4000-line cap is replaced by per-tool budgets: 48KB for Read/Glob/Grep, 96KB for Bash stdout, 96KB tail-kept for Bash stderr, 2000 chars per line everywhere. Search results are re-fetchable — narrow the pattern, advance the offset — so they earn the tighter half; a failing command's last line is the actionable one, so stderr keeps its tail.
  • Read paginates with offset / limit and never refuses on file size. The old >512KB rejection said "use Grep or Bash to sample it", which is how an unpaginated read became an unbounded sed pipeline. Grep takes path, include, outputMode, headLimit; Glob takes path, limit; both order by modification time, newest first.
  • An explicit path disables parent ignore files. Without that, scoping a search to node_modules or dist returned zero and pushed the model back to shell — and Grep could not read its own spill files.
  • Over-budget Bash output spills into the per-session scratch dir so the marker names a real file. Read/Glob/Grep embed no marker: content stays byte-faithful so text copied out of it still matches for Edit, and the window metadata plus notice carry the same facts as sibling fields.
  • The system prompt now states collaboration rules outright — answer in the user's language, a sentence before each tool batch, never more than one batch without visible text, answer in text rather than only in reasoning, finish end to end — and states a preference for the scoped tools over shell equivalents. "Prefer concise, actionable answers" was the only nearby rule, and a reasoning model executed it as saying nothing.
  • Decision D194; tool schemas widen without breaking callers and prompt text is not an interface, so no ADR. See 03-runtime/16-tool-result-limits.md, 03-runtime/02-agent-runtime.md §7, and E2E-099.

2026-08-05 — MCP servers and skills the user owns, scoped per project

  • Plugins, user MCP servers and user skills now share one activation shape: enabled plus { mode: "global" | "projects", projects: [] }. The boolean stays separate from the scope so switching something off keeps the project list it was narrowed to, and the off / projects / global control the UI renders is derived from the pair rather than stored as a third mode.
  • Matching is case-insensitive and trailing-separator-insensitive because macOS and Windows both hand us case-varying spellings of one directory, and a scoped path covers its subdirectories so a monorepo root does not have to be listed package by package. A projects-scoped extension is inactive in a session with no project: "these projects" is a claim about projects.
  • Scope is checked when the per-turn catalog is built and again at dispatch. A session outlives the prompt that listed its tools, and a tool the model can see is a tool it will try to call, so filtering in one place leaves a hole between re-scoping and the next prompt. Themes stay unscoped — appearance is app-wide, not a per-project capability.
  • Adding an MCP server is a paste. parseMcpImport reads the mcpServers document every README prints plus the servers, bare-map and single-object variants, infers http from a url because half the configs omit type, and reports per-entry skip reasons so one bad entry in fifteen is not fatal.
  • A user MCP server connects on first use and caches its tools; a failed handshake stays failed until the user edits it or presses Test. Saving a change to what the server is drops the connection, renaming it does not — a stale tool list is worse than a missing one.
  • A user skill is one SKILL.md. D174's contract is untouched, which is why the editor requires a description and puts it above the body: the description is the only part that enters the prompt.
  • Four tabs rather than one merged list. Plugins are installed, MCP servers are configured, skills are written; their rows need a connection light, a byte counter and a permissions matrix respectively, and one list would hide all of that behind a lowest-common-denominator row.
  • Decisions D192, D193, D194; new host-core registries, new RPC methods and a new main-process runtime, so ADR 0053. See 07-plugins/01-plugin-system.md and 07-plugins/03-plugin-api.md.
  • Explicit path arguments for Read, Glob, Grep, Write, and Edit are classified against the durable session workspace and scratch roots before the normal risk matrix. In ask and accept-edits, an outside path emits the existing permission card with the requested path preview; in auto, it executes without a card. Allow-once and the existing tool-scoped allow-session grant remain available, while denial, timeout, and cancellation return TOOL_DENIED without touching the path.
  • The approved resolver canonicalizes the deepest existing ancestor again at execution time, so .. and symlink escapes cannot skip the boundary. The outside location is not promoted to a workspace root; external reads and searches retain absolute paths, and external mutations stay outside Review and workspace artifact records.
  • The sidecar exposes the host's bounded search controls (Read.offset/limit, Glob.path/limit, and Grep.path/include/outputMode/headLimit) with the canonical filesWithMatches spelling; the host normalizes common files_with_matches and files-with-matches provider aliases. Guidance prefers native tools and portable workspace-relative paths, with shell search as a bounded, platform-specific fallback.
  • Decision D195; the external path capability and widened search schemas change the host/runtime boundary, so ADR 0057. See 03-runtime/03-tools-and-permissions.md, 03-runtime/06-host-rpc-protocol.md, 03-runtime/15-workspace-ignore-rules.md, 03-runtime/16-tool-result-limits.md, and E2E-019/E2E-019e.

2026-08-05 — Extensions page density and theme-readable actions

  • The Extensions destination no longer renders the four-card numeric overview band. Installed, MCP, Skills, and Marketplace remain separate tabs; tab counts, installed state-group counts, and the pending-update alert retain actionable state without duplicating it in a static summary row.
  • Shared primary and secondary buttons use semantic accent, surface, text, and border tokens. Primary actions invert the current theme surface; secondary actions keep an opaque elevated surface and visible border in both themes.
  • Decision D196 amends the presentation portion of D169 without changing plugin, MCP, skill, marketplace, permission, or runtime contracts. See ADR 0058, 04-ux/01-ui-ia.md, 04-ux/07-ui-design-system.md, 07-plugins/07-plugin-marketplace.md, and E2E-024N/E2E-060b.

2026-08-05 — Clipboard files become session-scratch references

  • The composer now distinguishes native text paste from a clipboard payload containing files or images. File bytes are sent to Electron main only after the renderer has a durable session id; a home composer creates or reuses one before the transfer.
  • Main validates the session and writes bounded, uniquely named files under <data_dir>/scratch/<sessionId>/pasted/. The draft receives ordinary @absolute/path references, with quoting for whitespace, so the existing Read/Glob/Grep path semantics handle the pasted material without binary prompt content or project mutations.
  • Decision D197 and ADR 0059 define the new renderer/main boundary. See 04-ux/08-component-spec.md §11.7–11.8, 03-runtime/01-ipc-protocol.md §13c, 03-runtime/03-tools-and-permissions.md §4b, 03-runtime/04-data-storage.md, and E2E-102.

2026-08-06 — Goal mode joins Plan as a contract mode

  • Sessions now persist one of three operating modes: agent, plan, or goal. The Composer-left chip cycles Agent → Plan → Goal → Agent, and /goal-mode (builtin.mode.goal) is its palette/slash entry.
  • Goal negotiates a goal statement, acceptance criteria, and boundaries instead of ordered steps. EnterGoalMode and SubmitGoal mirror EnterPlanMode and SubmitPlan, writing .pi/goal/<unique-name>.md and one pending plan_approvals row. plan_approvals.kind (schema v11) is the only discriminator; a submit tool run against the other kind fails with PLAN_KIND_MISMATCH.
  • Plan and Goal are the contract modes. The host hard deny for Write/Edit/plugin/unknown tools, the Bash-follows-permission-mode rule, the *_IN_PLAN error codes, the single-pending-approval invariant, and the PLAN_REQUIRES_INTERACTIVE_SESSION rejection of scheduled runs all apply to both, evaluated against the session's durable mode.
  • After approval the session becomes Agent and the queued execution carries the kind, so a Goal run chooses its own approach, self-checks against every acceptance criterion, stops at a stated boundary, and reports criterion by criterion.
  • Decision D198 defines this. See 03-runtime/01-ipc-protocol.md §5.4, 03-runtime/02-agent-runtime.md §5b/§7.2a, 03-runtime/03-tools-and-permissions.md §10.1, 03-runtime/04-data-storage.md §4.6a/§7, 03-runtime/06-host-rpc-protocol.md §5.1, 03-runtime/08-error-codes.md, 03-runtime/10-session-state-machine.md, 04-ux/04-builtin-commands.md, and 04-ux/08-component-spec.md §11.

2026-08-06 — The regenerate branch is archived under the host RPC lock

  • Turn completion used to archive the finished regenerate branch with a read-modify-write from Electron main: session.get, then session.replaceMessages to stamp revisionCount / activeRevision on the user root. Assistant and tool messages reach SQLite asynchronously through the persistence outbox (ADR 0041), so the snapshot could predate the turn's final message and the whole-transcript rewrite deleted it from the transcript file and the index, along with every row's turn_id.
  • session.saveActiveRevision now performs the read, the archive, and the stamp in one host call under the state lock. The stamp rewrites only the root user's transcript line and re-reads the file at write time, so a line appended in the meantime survives. Electron main drains the outbox first and skips the archive with a warning rather than archiving an incomplete branch.
  • session.replaceMessages carries each surviving message's owning turn_id across the rewrite, and is documented as safe only for a caller that owns the whole transcript for the duration of the call.
  • Decision D199 and ADR 0060 define this. See 03-runtime/04-data-storage.md §4.9/§7, 03-runtime/06-host-rpc-protocol.md §4, and E2E-118.

2026-08-06 — Context compaction becomes imperceptible

  • Compaction is host-driven and silent. The CompactContext tool, the <context_management> soft-boundary nudge, and the host no-confirmation allowlist entry are gone, so a long session never spends a model turn asking to be compacted and never grows a transcript row for it.
  • contextBudget() keeps the D158 hard limit and headroom untouched, derives the retained-tail target from the model window instead of settings, and adds a background limit at 70% of the hard budget. Background pre-computation also requires growth of at least the retained-tail target since the newest checkpoint's baseline, so a large tail cannot re-trigger every turn.
  • Generation and installation are separate. A checkpoint is built only in provider-idle windows — while a tool executes, and after a run ends — so the summary request never shares the provider connection with a streaming turn. It installs at the next turn boundary or prompt if its base is still active, its anchor still exists, and it still fits the current model's budget; otherwise the unchanged blocking hard boundary handles it. A failed background build is discarded with no event, no persistence, and no ADR 0049 fallback.
  • compaction_start/compaction_end gain optional phase, and compaction_end gains optional status { generation, summaryTokens }; both are additive inside protocol v9. A successful automatic compaction notifies nobody. The context usage inspector is the only visible trace, and Settings exposes no compaction controls at all.
  • Decision D200 and ADR 0061 amend D158 / ADR 0030 / ADR 0049. See 03-runtime/01-ipc-protocol.md §6, 03-runtime/02-agent-runtime.md §5.1/§7.1, 03-runtime/03-tools-and-permissions.md §0/§2/§10.1, 03-runtime/06-host-rpc-protocol.md, 04-ux/06-settings-ia.md, 04-ux/08-component-spec.md §7.3/§8.3/§11.5, 04-ux/09-interaction-patterns.md §3A, and E2E-084.

2026-08-06 — Context compaction is rebuilt to match Codex

  • The previous round cited Codex and then implemented its opposite on four counts. Codex compacts only synchronously, measures the whole context by default, emits a ContextCompaction item and a Warning for every compaction, and has a real model-facing tool (new_context). D200's Context section also stated that Codex has no such tool, which is wrong. The user asked for Codex's mechanism after being told it reverses the imperceptibility they had asked for one round earlier.
  • Compaction is inline again. Background pre-computation, its 70% limit, the increment-scoped trigger, and the three idle call sites are deleted; prepareNextTurn() compacts when the context crosses hardLimit or the model called new_context, matching Codex's should_roll_over.
  • A checkpoint carries the summary plus recent user messages, nothing else. pi's cut point still marks the boundary, but its split-turn prefix and recent tail are folded back into the summary input, so no message leaves the model context uncovered — the reason a filtered tail alone would have been a data-loss bug. Retention is newest-first to 20,000 tokens with the crossing message truncated rather than dropped, and assistant messages take their tool calls with them, so no orphaned call reaches a provider.
  • Both Codex families exist: the summary path, and a fresh_window rollover that asks for no summary and installs a fixed marker text. The family is an internal switch (construction option, then PI_DESKTOP_COMPACTION_STRATEGY), absent from settings and i18n, because no user can judge that trade-off from a settings row and Codex does not ask them to.
  • new_context is parameterless with Codex's description verbatim, sits on the host no-confirmation allowlist, and is never assignable to a subagent. Two budget reminders — at clamp(hardLimit * 0.15, 8k, 32k) and at 2,000 tokens remaining — are appended to the current turn's system prompt, claimed once per checkpoint window, and never persisted or shown.
  • host-core keeps the whole checkpoint chain, per-record valid, because one transcript row per compaction has to survive a restart, a rewrite, and a fork. compaction_end drops phase and carries mark instead of status; the transcript draws a divider row after the message each checkpoint covers, and every successful compaction raises one warning toast. The context inspector keeps its line, now the newest mark's.
  • Three deviations from Codex are deliberate: the summary precedes the retained users because buildSessionContext fixes that order, hardLimit stays "window − output reserve" rather than 90% of the window (we have no separate full-window guard), and the tool is registered in both families rather than only the rollover one.
  • Decision D203 and ADR 0064 amend D200 / ADR 0061 and restore D158 / ADR 0030's visibility property. See 03-runtime/01-ipc-protocol.md §6, 03-runtime/02-agent-runtime.md §5.1, 03-runtime/03-tools-and-permissions.md §2/§10.1, 03-runtime/04-data-storage.md §2.1, 03-runtime/16-tool-result-limits.md §4, 04-ux/08-component-spec.md §7.3/§7.4/§8.4a/§11.5, 04-ux/09-interaction-patterns.md §3A, and E2E-084.

2026-08-06 — Bounded subagents behind a Task tool

  • A session can delegate one self-contained piece of work to a subagent and get back a single written report. Definitions are Markdown documents — three inline builtins (explorer, code-reviewer, test-runner) plus <workspace>/.pi/agents/*.md, project shadowing builtins by name, re-read on every launch, capped at 16, with malformed documents degraded to launch diagnostics.
  • A definition declares its own tools from Read/Glob/Grep/BrowserPreview/ Bash/Edit/Write and is read-only (Read, Glob, Grep) when it declares none. A delegate never inherits mutation rights from its session, cannot reach plugin, skill, mode or meta tools, and has no Task tool of its own. Its calls run through the same tools.execute host path, so containment and permission modes are unchanged.
  • A definition may pin model: <provider>/<model>, resolved once per launch in Electron main against configured providers (by id, vendor key or display name, 8 distinct providers maximum). An unresolvable pin fails the Task call with a tool error instead of falling back to the session model.
  • Task is offered in Agent mode only. Fan-out comes from execution modes: the session Agent runs toolExecution: "parallel", every other tool is sequential, and only an all-Task batch runs concurrently, capped at 4 slots and each delegate at its own maxTurns (default 24, maximum 80). The sidecar serializes same-path Write/Edit calls through a PathMutex.
  • The parent's model context gains the bounded report (12k chars) and nothing else. Delegate messages and tool rows are emitted and persisted with parentToolCallId / agentName in the message meta, but the runtime skips them when rebuilding context, and a delegate's termination collapses into the tool result rather than reaching Electron main's turn handling.
  • The transcript nests attributed rows one level inside their Task row and keeps them out of the turn stream and the minimap. One Task stays compact; two or more in an activity group derive one renderer-only delegation card with aggregate status and a main-agent-to-delegate topology. Nodes reuse the existing row disclosure, structured outcome and nested rows. The topology is derived from persisted attribution on live and reload, adds no protocol or storage shape, and invents neither delegate dependencies nor an unavailable parent-summary node. Pending permission requests become a per-session queue: head-only answering, id-matched removal, whole queue denied on abort, and a card that names the delegate that asked and how many wait behind it.
  • Decision D201 and ADR 0062 define this. See 03-runtime/02-agent-runtime.md §5f/§7.2b/§8, 03-runtime/03-tools-and-permissions.md §10.2, 03-runtime/04-data-storage.md §4.7a, 04-ux/03-permission-ux.md §6a, 04-ux/08-component-spec.md §9.9, and E2E-119.

2026-08-07 — Empty home uses direct bottom task entry

  • Empty chat home uses a direct bottom composer and keeps the hero and optional onboarding content in one scrollable region.
  • The home composer is a bottom-reserved sibling of that region, so it remains visible at the bottom while checklist content scrolls independently on short windows.
  • Decision D204 and ADR 0066 define the layout. See 04-ux/01-ui-ia.md, 04-ux/07-ui-design-system.md, 04-ux/08-component-spec.md, and E2E-063 / US-UI-64.

2026-08-07 — Empty home gains developer starter guidance (superseded)

  • The empty home now presents four compact, localized developer starters under the hero so the middle surface is useful without becoming a marketing panel.
  • Activating a starter pre-fills and focuses the composer. It does not send a prompt or create a session turn, and short-window scrolling still preserves access to every block.
  • Decision D205 defined this amendment to D204 before D206 superseded it. See 04-ux/01-ui-ia.md, 04-ux/07-ui-design-system.md, 04-ux/08-component-spec.md, and E2E-063 / US-UI-64.

2026-08-07 — Empty home removes developer starter cards

  • The empty home no longer renders development task cards, starter glyphs, or contextual quick actions.
  • The hero, optional onboarding checklist, and direct bottom composer remain; task entry starts directly in the composer.
  • Decision D206 supersedes D205 while retaining D204's scrollable layout. See 04-ux/01-ui-ia.md, 04-ux/07-ui-design-system.md, 04-ux/08-component-spec.md, and E2E-063 / US-UI-34 / US-UI-48.

2026-08-07 — Work panel gains a direct keyboard entry point

  • Cmd/Ctrl + J is a customizable renderer shortcut named openWorkPanel. It reveals the active session's retained work-panel context at the committed width without creating a resource tab; the context menu remains the place to create Review, Terminal, Browser, or Files. No active session and the Settings page are no-op contexts.
  • Artifact-driven resource creation, session ownership, background-event isolation, collapse behavior, and width persistence remain unchanged.
  • Decision D207 supersedes D128's no-global-shortcut clause and amends D142's no-launcher presentation. See ADR 0068, 04-ux/01-ui-ia.md, 04-ux/08-component-spec.md, 04-ux/09-interaction-patterns.md, and E2E-056.

2026-08-10 — Native path mistakes recover without false turn failure

  • Read is file-only, Glob is directory-only, and Grep accepts a file or directory; directory Read errors include structured Glob recovery args.
  • Recovered tool errors remain visible on their own rows but no longer turn the containing processing group into a terminal failure surface.
  • D185's deferred Agent search tools remain unchanged. Decision D208 and ADR 0069 define the compatible tool and transcript contracts.

2026-08-10 — Composer file references use compact draft chips

  • Completed workspace-file selections and pasted session-scratch files now live as renderer-owned, session-scoped references instead of exposing their canonical paths in the textarea.
  • The composer shows only removable leaf-name chips. Full paths remain available to tooltips and assistive technology, and duplicate leaf names retain distinct canonical identities.
  • Immediately before the existing submit dispatcher, references serialize after visible text with the unchanged quoted-or-unquoted @path grammar. Reference- only sends, slash templates, and Agent/Plan/Goal aliases keep their existing behavior; failed sends retain both text and references.
  • An accepted send retains only an in-memory, session/turn-scoped copy of its pre-serialization draft while unanswered smart Stop remains possible. That Stop restores the original text and chips; after reply content begins, abort preserves the partial transcript and restores no draft.
  • ComposerPastedFile.name is the sanitized original leaf label, while path keeps the UUID-backed absolute storage name. Clipboard bytes, scratch containment, cleanup, host RPC, storage schema, and provider payloads remain unchanged.
  • Decision D209 and ADR 0070 amend the draft-presentation clauses of D124 / ADR 0024 and D197 / ADR 0059. See 03-runtime/01-ipc-protocol.md §13c, 03-runtime/03-tools-and-permissions.md §4b, 04-ux/07-ui-design-system.md §8.1, 04-ux/08-component-spec.md §11.7–11.8, 04-ux/09-interaction-patterns.md §8a, and E2E-102/E2E-102a.

2026-08-11 — Global corners use an Apple-inspired shape hierarchy

  • Fixed radii use a regular 4/6/8/10/12/14/16/18/20/24px ladder. Compact and medium desktop controls use rounded rectangles; explicit pill and circle tokens remain reserved for semantic capsules, equal-width icon controls, switches, tracks, avatars, and dots.
  • Nested surfaces follow Apple's concentricity rule when their corners align: the outer radius equals the inner radius plus the inset between their edges. Structural full-width shell panels remain square.
  • The composer keeps its 20px visible radius through the shared radius scale. Experimental corner-shape rendering is deferred because Electron 37's Chromium 138 runtime does not support it.
  • Decision D210 amends D072's previously pixel-preserving radius ladder without changing its token-only enforcement. ADR 0071 records the rationale. See 04-ux/07-ui-design-system.md §6.2 and US-UI-72.

2026-08-11 — Global plugin launcher and uninterrupted next-turn preparation

  • Option+Space on macOS and Alt+Space on Windows/Linux opens a centered, frameless plugin launcher. It searches launchable panels by Chinese name, full pinyin, pinyin initials, normalized name/id, and description, then opens the highlighted result through the existing panel host.
  • During an active answer the composer draft and mode, thinking, and permission choices remain editable for the next turn, while Send remains disabled. The renderer flushes only the newest full choice after the host reports idle.
  • User-stopped partial responses retain elapsed duration and exact provider output when present; absent final usage falls back to a visibly approximate output count so generation throughput survives persistence and reload.
  • Decisions D211/D212 and ADRs 0072/0073 define the native-window, IPC, in-flight configuration, and transcript metadata contracts. See 03-runtime/01-ipc-protocol.md, 03-runtime/02-agent-runtime.md, 04-ux/07-ui-design-system.md, 04-ux/08-component-spec.md, 04-ux/09-interaction-patterns.md, and E2E-120.

2026-08-12 — Plugins can request and send native notifications

  • pi.ui.notify remains an in-app Toast for compatibility. Plugins with the existing notify permission gain getNotificationPermission, requestNotificationPermission, and showNativeNotification APIs.
  • Native plugin notification objects remain owned by Electron main. The API returns a best-effort granted / denied / unknown / unsupported state because Electron has no cross-platform read-only permission query. Native plugin notifications do not create durable task inbox rows or session activation events.
  • Decision D213 and ADR 0074 define the public API and the separation from D117's application-owned task notification contract. See 07-plugins/01-plugin-system.md, 07-plugins/03-plugin-api.md, 07-plugins/13-plugin-permissions-matrix.md, and E2E-122.

2026-08-12 — Development plugins get an explicit permission-ceiling reload

  • Automatic development-plugin reloads continue to reject manifest permission additions, preserving the no-silent-widening boundary.
  • The Plugins page now offers Reload for source: "dev" rows. The new plugin/reload invoke resolves the registered folder, reloads it through the existing Electron runtime path, and re-arms the watcher with the current declared permissions as its new ceiling.
  • Decision D214 and ADR 0075 define this additive renderer/main IPC surface. Host RPC and storage schema versions remain unchanged; see 07-plugins/10-plugin-devex.md, 07-plugins/12-plugin-ipc-and-host-services.md, and E2E-022B.

2026-08-12 — Windows reserves the plugin launcher chord

  • Windows' Alt+Space system-menu reservation can cause Electron's global shortcut registration to fail while the application is unfocused. The host core now owns a narrow low-level keyboard hook for the default binding, consumes the chord, and emits keyboard.shortcut to Electron. Electron toggles the existing launcher window; custom bindings continue to use the normal Electron global shortcut path.
  • Decision D215 and ADR 0076 define this additive host integration. Protocol version 9 and storage schema 11 remain unchanged.

2026-08-13 — Plugin launcher renderer warms after boot

  • Electron creates and loads the retained plugin launcher after the main application and backends finish booting, while keeping the window hidden.
  • Shortcut delivery during warm-up joins the same in-flight creation promise; failed warm-up remains retryable, and each visible invocation still refreshes the plugin catalog.
  • Decision D217 and ADR 0080 supersede only D211/ADR 0072's first-use lazy creation clause. IPC, host RPC, protocol v9, and storage schema v11 are unchanged.

2026-08-12 — Approval cards focus on the artifact and remember the mode

  • Plan/Goal approval cards now show only the title, host-created artifact opener/path, Reject, and Approve. Submitted question/description, status, validity/deadline, and inline warnings are not rendered.
  • The selected Ask / Accept edits / Auto approval mode is remembered in renderer-local device preferences and becomes the next approval's default; Ask remains the safe fallback.
  • Title-derived artifact filenames preserve Unicode alphanumeric characters so localized plan titles remain recognizable. The host's existing internal deadline remains a compatibility/fail-closed boundary but is not exposed by the approval card.
  • Decision D215 and ADR 0076 amend D189/ADR 0053. Protocol and storage versions remain unchanged.

Built for local-first development.