Decisions Log
Baseline delta:
0.3.0→0.4.15Date:2026-08-05Status: Accepted for implementation
This log freezes previously open questions into concrete decisions.
A. High-priority architecture decisions
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D001 | Electron ↔ Rust transport | Rust sidecar + stdio JSON-RPC (NDJSON) | Simple isolation, debuggable, replaceable later |
| D002 | SQLite ownership | Rust host-core owns SQLite exclusively | Single writer, clearer privilege boundary |
| D003 | Default mode | Agent | Product is an agent desktop, not pure chat |
| D004 | Former 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 |
| D005 | Permission timeout | 120s → deny | Fail closed, do not hang forever |
| D006 | allow-session scope | By toolName | Simple UX; workspace sandbox still enforces path safety |
| D007 | ~/.pi compatibility | No auto-import in MVP | Keep config ownership clean in ~/.pi-desktop |
| D008 | Node runtime packaging | Dev 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 |
| D009 | Plugin runtime isolation | Target = separate process; M4 may use host-managed sandboxed runtime | Ship plugin foundation pragmatically without weakening API gateway |
| D010 | First release platform | macOS arm64 only | Focus acceptance and packaging |
B. Secondary implementation defaults
| ID | Topic | Decision |
|---|---|---|
| D011 | TS schema validation | typebox |
| D012 | i18n library | i18next + react-i18next |
| D013 | Bash execution style in M3 | Non-interactive only (no PTY yet) |
| D014 | Command palette shortcut | Cmd/Ctrl + Shift + P |
| D015 | Plugin tool exposed name | Forced prefix plugin_<pluginIdSafe>_<toolName> |
| D016 | Uninstall plugin data | Delete by default, optional keep-data later |
| D017 | enable → load failure | Auto fallback to disabled |
| D018 | Plugin secrets in settings | Not allowed in MVP |
| D019 | Plugin session summary access | Denied by default |
| D020 | Auto-update | Post-MVP |
| D021 | First-run onboarding | Inline checklist (not modal wizard) |
| D022 | Local telemetry | Local logs only in MVP (no remote telemetry) |
C. Provider & model coverage decisions (0.3.4)
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D023 | Provider coverage goal | Universal market coverage (not a tiny fixed vendor list) | Globalization + real coding workflows |
| D024 | Coverage strategy | pi-ai native providers + first-class OpenAI-compatible + custom providers | Maximum reach without rewriting every SDK |
| D025 | Model allowlist | No closed product allowlist | Models churn; power users need free-form IDs |
| D026 | Catalog sources | bundled snapshot + discovery/refresh + user-defined | Works offline and stays current |
| D027 | Default identity | Model selection is (providerId, modelId) | Same model id can exist on many gateways |
| D028 | Secrets | OS safeStorage (or controlled fallback) via secretRef; never in provider JSON | Security boundary with Rust host ownership |
| D029 | Local models | Supported through OpenAI-compatible local gateways | Ollama/LM Studio/vLLM without special-case architecture |
| D030 | Connection test | First-class host method before trusting provider for runs | Fail early, actionable setup UX |
| D031 | Secrets backend | OS safeStorage primary + encrypted file fallback | Robust on macOS first release |
| D032 | Workspace ignore | security denylist + defaults + .pi-desktopignore | Safe/predictable tool FS behavior |
| D033 | Tool result limits | 256KB/4000 lines defaults with explicit truncation markers | Protect context & UI |
D. Codex visual parity decisions (0.3.5+)
Gold source: local Codex electron captures; latest row wins where rows conflict.
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D034 | Desktop visual baseline | Codex electron-dark 1:1 shell (charcoal gray, floating composer, ~275px sidebar) | Match local Codex usability and density; keep PI-Desktop product branding |
| D035 | Shell display name | (superseded by D094) UI chrome uses shellName "Codex"; product/about remains PI-Desktop | Satisfy visual 1:1 replica goal while preserving product identity in about/settings |
| D036 | Theme chrome tokens | All shell chrome (nav, threads, chips, title buttons) uses semantic --ds-* text/surface tokens; no raw gray-0 text in light mode | Light macOS default was unusable when nav used white ink on #f3f3f3 |
| D037 | Dark sidebar surface | Dark sidebar uses #000000 (Codex surface-under); main pane stays #181818 (gray-900) | Match electron-dark sideBar vs main surface separation |
| D038 | Dark composer plate | (superseded by D047, then D061) Dark floating composer uses solid #212121 with stronger elevation shadow than light | Codex elevated-primary must read as a box against #181818; transparent mix alone looks flat |
| D039 | Stage Manager bounds | Permanent host watchdog restores footprint while width/height remain collapsed | 20s burst was insufficient under Stage Manager thrash |
| D040 | Composer intelligence control | (superseded by D091) Custom effort chip opens a popover (effort radio + model heading + settings) instead of cycling on click | Replaced because the control changed labels without configuring pi |
| D041 | Profile footer | (superseded by D113) Custom footer opens profile menu (Settings / Logs / Theme); cloud badge remains update stand-in | D113 replaces the cloud/update stand-in and generic gear row with a truthful local-profile footer |
| D042 | Projects 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 |
| D043 | Settings 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 |
| D044 | Destination list chrome | PRs/Scheduled/Plugins use shared dest-row list + filter chips; light cards white elevated | Match Codex destination density without full cloud backends |
| D045 | Home empty stack | Empty chat keeps composer in home flow (not absolute bottom-only dock); refined by D047 split grow | Initial fix for large empty gap; D047 corrects dual-grow vertical model |
| D046 | Composer placeholder | (superseded by D094) Empty draft uses Codex placeholder (EN/zh-CN) instead of blank | Empty white plate read as broken without ink; match the earlier visual gold copy |
| D047 | Home split grow | (geometry superseded by D111) Empty home used upper/lower grow regions (hero items-end + composer justify-end); dark box uses Codex elevation-prominent | Match electron dual grow; D111 replaces dual-grow portal with a scrollable flow stack to stop composer/card collisions |
| D048 | Sidebar 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 |
| D049 | Home suggestion cards | (superseded by D131) Empty home shows 4 ambient cards under hero (auto-fit row) and prefills starter prompts on click | D131 removes the card row and prompt-prefill entry points from the empty home |
| D050 | Empty 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 |
| D051 | Sidebar nav density | (session-list IA superseded by D088; row density retained) Nav rows ~32px pitch, recents rows ~28–31px, section label 最近/Recents | Close light-home sidebar residual vs cx-home-clean |
| D052 | Home 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 28px | Close residual heat at hero y≈300 and composer band; night plate must not flatten into #181818 |
| D053 | Stage Manager CG detection | CG bounds helper matches any window layer by pid; missing-CG needs streak≥3 before shelf recovery; avoid permanent alwaysOnTop | alwaysOnTop floating layer broke layer-0 helpers and caused restore thrash |
| D054 | Empty 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 #212121 | Empty height:0 auto-resize hid placeholder and read as broken night/light box; gold draft row needs visible mark + ink density |
| D055 | Empty 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 28px | 112px plate left draft ~30px low; grow plate upward without moving toolbar footing |
| D056 | Empty-home workspace chips | (superseded by D095) Hide project/Local/branch capsule on empty home always; show only in thread-docked composer | cx-home-clean empty gold has no capsule band above the plate even with project title |
| D057 | Home 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 span | Hero residual was thin mark + short project label under-inking title vs Codex gold |
| D058 | Home content width + dark ink tokens | Home 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 only | 48rem at 14px root shrank plate ~120px vs Codex gold; hardcoded light hero ink made night title unreadable |
| D059 | Light disabled send ink | Disabled send chip #8e8e90 + white arrow (not #bdbdbd) | Pixel-match cx-home-clean empty send control |
| D060 | Light New task ghost row | Light empty-home New task is transparent (no solid chip); only hover wash | Gold has icon+label without filled pill; filled #e8 chip was main nav residual |
| D061 | Empty plate Y + night elevated-primary | Home 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 #212121f5 | Plate was high with pre-plate halo; solid night plate + heavy omni shadow diverged from Codex elevated-primary and gold foot band |
| D062 | Settings Codex shell | Settings 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 Integrations | Destination parity gap; prior 4-item flat rail diverged from Codex settings IA |
| D063 | Settings full-page takeover | Settings replaces app sidebar with Codex full-page shell: back+search+icon groups (Personal/Integrations/Coding), elevated permission/general cards, local Providers/Plugins retained | Nested settings-inside-main-pane diverged from live Codex settings gold |
| D064 | Settings general content parity | Basics card rows match Codex: default open target, language, menu bar, bottom panel; nav adds Pets/Appshots; sun/pet/snapshot icons; pill selects | Closer 1:1 to live Codex settings gold content band |
| D065 | Settings general gold polish | Permission 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 Agent | Residual gaps vs cx-settings-try after full-page shell |
| D066 | Home-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 setProject | Gold cx-home-clean with project + projects-index-page parity |
| D067 | Home 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 capsule | D131 removes the cards; D095 remains authoritative for composer spacing |
| D068 | Recents 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 task | Gold sidebar selected row chrome; reduce selection residual |
| D069 | Destination title scale + dark New task ghost | Destination 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 |
| D070 | Settings gold metric polish | Settings rail 275px/#f4f4f4; denser nav; content title offset; 32×20 accent toggles; Account arrow-up-right; 14px cards; 720px content band | Residual vs cx-settings-try (rail width, toggle size, title Y, external mark) |
| D071 | Transcript interaction parity | Tool 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 blocks | Boxed tool cards and forced scrollIntoView diverged from Codex transcript feel; spec 7.4 scroll pause was unimplemented |
| D072 | Typography/radius token enforcement | All 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 |
| D073 | Full renderer i18n coverage | Every 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 untranslated | Six components bypassed i18n entirely; default-title matching was duplicated in store and Sidebar and missed zh "新对话" |
| D131 | Empty 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. |
| D133 | Project 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. |
| D168 | Project archive presentation redesign | Project 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)
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D078 | macOS signing lanes | Static config stays unsigned (identity: null) for local builds; scripts/release-macos.sh injects Developer ID + hardened runtime + optional notarization from env | Contributors build without certs; releases sign per 06-delivery/06-release-runbook |
| D079 | App icon / brand mark v1 | build/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 assets | Keep one visual identity across development, renderer, and packaged lanes while preventing the derivation script from restoring the obsolete generated mark |
| D080 | Backend supervision | Child exit rejects in-flight RPCs immediately; backoff restarts (0.5s→4s, max 3 per 2min); hostStatus events drive renderer degradation UI | Crash recovery without hangs; fail visible, not silent |
| D081 | Renderer sandbox | sandbox: true with fully bundled CJS preload; production CSP drops unsafe-eval and localhost connect-src | Electron security baseline; verified by test:e2e:boot |
| D082 | Log 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 SQLite | Diagnosable failures without unbounded growth; audit needs queryability |
| D083 | Window state | Persist last good bounds to window-state.json (min 960×640 to restore); Stage Manager shelf recovery keeps the Codex footprint; capture runs force deterministic bounds | Users keep their window; shelf recovery and pixel captures stay deterministic |
| D084 | Cross-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 guidance | Superseded by the selectable shell catalog and stable execution identity in D190 |
| D085 | Toast system v2 | Single 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 §17 | Old 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 |
| D086 | Storage schema v2 | Single 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 |
| D087 | Immersive 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 D067 | The detached capsule and differently colored plate made context and prompt input read as unrelated controls instead of one Codex-style immersive composer |
| D089 | Composer draft height | The 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 minimum | Preserve transcript space and Codex-like density while keeping multiline editing usable |
| D088 | Scoped 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 context | D093 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D090 | Compact 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 |
| D091 | Composer runtime configuration | Mode 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 |
| D092 | Responsive settings content | The 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 |
| D104 | Settings 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D093 | Sidebar organization, retained project tabs, and session workspace isolation | The 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 |
| D094 | Renderer 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 |
| D135 | Distinct sidebar task status indicators | Conversation 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D095 | Remove composer workspace context rail | The 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D096 | End-to-end thinking mode | **Thinking level is session-scoped with canonical values `off | minimal |
| D102 | Custom provider thinking presets | Settings 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D097 | Docked work panel replaces context panel | The 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 |
| D098 | Review 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 |
| D099 | Terminal tab is a real PTY | Terminal 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 |
| D100 | Browser tab embeds a WebContentsView | The 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 |
| D138 | Session-scoped inline permission requests | Tool 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. |
| D139 | Navigation intent and shortcut event guards | Every 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. |
| D128 | Artifact-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. |
| D140 | Session-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. |
| D179 | Message-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 |
| D180 | Message-owned review snapshots and guarded rollback | Successful 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 |
| D142 | Session-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. |
| D154 | Work-panel activity rail and resource switcher | Once 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. |
| D157 | One visual assistant turn per user turn | Provider-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. |
| D162 | Latest-wins cached session switching | The 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. |
| D156 | Independent 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. |
| D163 | Native 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: 0 | 364..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).** |
| D167 | Slimmer default work-panel width | The 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. |
| D164 | Dual-locale in-app product changelog | Product "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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D101 | WorkBuddy-inspired transcript density | User 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 |
| D103 | Per-message model + token meta and retry | Completed 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 |
| D105 | In-place regenerate for assistant turns | Regenerate 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 |
| D106 | Preserve user hard newlines in transcript | User 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 |
| D107 | Configuration provider studio | Settings → 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 |
| D110 | Model configuration label + add-provider dialog | Settings 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 |
| D108 | Conversation minimap only when overflowing | The 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 |
| D109 | ChatGPT-style regenerate revision history | Regenerate 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 |
| D111 | Empty 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 |
| D112 | Readable 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. |
| D113 | WorkBuddy-inspired local profile footer | The 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 |
| D137 | Glyph-only message toolbars; edit means edit-the-prompt | Message 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 |
| D165 | Safe lazy Mermaid diagrams in assistant answers | A 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D114 | Per-session scratch directory for agent temp files | Each 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 |
| D115 | Permission 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 |
| D132 | Composer permission menu shows effective modes only | The 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 |
| D116 | Provider failures as assistant messages | Every 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 |
| D127 | Context-preserving reseed + transport retry | Reseeding 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. |
| D136 | pi-ai owns known-model metadata | For 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. |
| D183 | Segmented 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. |
| D158 | Turn-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. |
| D185 | Lazy 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. |
| D200 | Imperceptible 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 (background | blocking, 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).** |
| D201 | Bounded subagents behind a Task tool | Agent 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. |
| D202 | Managed subagent definitions with a user-level registry | Subagent 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. |
| D203 | Codex-parity context compaction | Compaction 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. |
| D186 | Bounded provider stream recovery and diagnostics | Provider 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D117 | Durable task notification inbox | Rust 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D118 | Platform application menu and window chrome | macOS 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. |
| D120 | Application update delivery | Electron 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. |
| D121 | Branded macOS development host | pnpm 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. |
| D129 | Menu-free Windows/Linux window chrome | The 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. |
| D130 | Sidebar-footer notification entry | The 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. |
| D141 | Canonical Windows native application identity | Electron 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. |
| D204 | Empty 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). |
| D205 | ChatGPT-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. |
| D206 | Remove empty-home developer starter cards | The 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. |
| D208 | Recoverable native-tool path contracts | Keep 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. |
| D216 | Cross-platform tray-resident minimize | Electron 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. |
| D218 | Host-owned cross-platform plugin panel chrome | Plugin 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D119 | Transcript file store; SQLite index-only | Schema 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. |
| D122 | Independent conversation session fork | Protocol 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. |
| D134 | Assistant 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. |
| D199 | Regenerate branch archived under the host RPC lock | session.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
- Exact marketplace domain / provider IDs
- Private marketplace auth mechanism
- Signature key distribution operational details
- Remote catalog update channel details (URL/signature)
- 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D143 | Settings directory rail icons | The 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. |
| D166 | Settings directory split into AI and Shortcuts destinations | The 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D123 | Composer slash commands, three sources | Typing / 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. |
| D125 | Composer 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 | |
| D197 | Composer clipboard files become session-scratch references | When 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. |
| D209 | Composer file references separate compact display from canonical prompt paths | Completed 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). |
| D211 | Global plugin launcher | A 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). |
| D212 | Running composers stage next-turn configuration and stopped turns preserve throughput | During 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). |
| D215 | Windows-reserved global shortcut fallback | On 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). |
| D217 | Post-boot plugin launcher warm-up | After 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). |
| D198 | Goal mode is the second contract mode | Goal 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. |
| D144 | Sidebar primary chrome at 14px | Expanded 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. |
| D145 | Disable browser text correction on editable fields | Every 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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D126 | Three-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
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D192 | Activation scope shared by every extension kind | Plugins, 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). |
| D193 | User-owned MCP servers need no plugin | host-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). |
| D194 | User-owned skills are one Markdown document | host-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
.piplugpackages 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-pluginsis the official marketplace source. - PI-Desktop fetches
catalog.jsonremotely (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-summaryand 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-plusso 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-infonow 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-blocksurfaces 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-darktoone-light/one-dark-pro. - Code cards paint a single editor surface (
#fafafa/#282c34); nestedpre/code/token backgrounds are forced transparent so there is no double wash.
2026-07-28 — Disable text correction on editable fields
- Shared
Input/Textareaprimitives defaultspellCheck={false},autoCorrect="off", andautoCapitalize="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.uiCrashedand finish zh-CN empty-home/custom copy.
2026-07-28 — Interaction detail polish
- Theme-aware
::selection,caret-color, andaccent-colorkeep 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
#fafafainset 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-noneunder overflow clipping; labels use--leading-compactso 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/resizeBychannel, 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..720pxwidth 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/sharedand attach them toUpdateState.releaseNotesfrom 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, andscripts/release.mjsheader 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
mermaidfences 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-panelCSS 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"anderrorMessageare 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.cssbecomes 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
@themeblock fromtokens.cssunchanged. - Style assertions load the effective cascade through
apps/desktop/test/helpers/styles.mjs, which inlines the local@importlines in declaration order. Tests must not read a partial directly. - Sidebar session styles live in both
sidebar-threads.cssandsessions.cssbecause the original file interleaved them; the file headers cross-reference. - The design-token scales now live in
styles/tokens.css; the guard inscripts/check-style-tokens.mjswalks the wholesrctree and is unaffected. - Decision D170; the single-file layout assumed by
04-ux/07-ui-design-system.md§Typography and04-ux/08-component-spec.mdno longer holds.
2026-07-31 — Plugin skills activation and the plugin devkit
contributes.skillsis activated.PluginRuntime.getSkills()reads each declared skill file at prompt time, only for plugins grantedagent.prompt.inject, and only through the containment guard the gatedfsAPIs use. The agent runtime renders a# Plugin skillssection 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 ownsscaffold/check/packover one implementation shared by thepi-pluginCLI, thePluginScaffold/PluginCheck/PluginPackagent tools served from Electron main, and the plugins page's New plugin from template action.checkreproduces the rules host-core enforces, so passing it implies install will pass;packwrites store-only (method 0).piplugentries becauseextract_zip_bytesaccepts nothing else. Closes roadmap gap R3's template andcheck/packitems. - A bundled plugin was rejected as the delivery vehicle: a plugin cannot produce a
.piplug(no archive API inHOST_API_ALLOWLIST) and scaffolding would need high-riskfs.write.workspacefor a capability the application should provide itself. - The built-in
plugin-developmentskill activates only for plugin workspaces — a pluginmanifest.jsonat 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 withPERMISSION_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 pluspluginChanged— host-core has no RPC for a runtime-side load failure, so the registry row does not move toload_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
Skilltool. 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-inplugin-developmentskill 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
activateProjectaction 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.filewas the reason a bare Files tab showed a literalfilelabel; the catalogs now carry it pluspanel.tools, and the obsoletepanel.openToolis 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.skillsis activated at load time behindagent.prompt.inject. Each entry may be a path or{ path, id?, name?, description? }; front matter in the document suppliesname/descriptionwhen 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
Skilltool that Electron main serves locally againstplugins.loadSkillBody(id), so the sidecar never holds skill text. - Caps: 32 skills per plugin, 128KB per document. A manifest without
agent.prompt.injectstill 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.mdR2.
2026-07-31 — Plugin themes ship CSS files
contributes.themesdeclares{ id, label, path, base? }and requiresui.theme.pathis a plugin-relative.cssfile;base(light|dark, defaultdark) names the palette the overrides layer on.- The main process reads the file and runs
sanitizeThemeCss(): no@import, nourl()outsidedata:, no unparseableurl(, nojavascript:/expression(, no markup sequences, 256KB cap, 8 themes per plugin. The renderer receives finished text overplugin/themesand injects it into one<style id="pi-plugin-theme">appended after the app's own stylesheets. AppSettings.themewidens toplugin:<pluginId>:<themeId>. When the providing plugin is disabled, uninstalled, or fails to load, the app falls back tosystemrather 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.mcpServersdeclares{ id, label?, transport }plus exactly one transport's fields:stdiotakescommand/args/env,httptakesurl/headers. Permissions are separate:mcp.server.localfor stdio,mcp.server.remotefor HTTP.apps/desktop/electron/main/plugin-mcp.tsspeaks protocol2025-06-18—initialize,tools/list,tools/call— as NDJSON over stdio or streamable HTTP/SSE. Budgets: 10s connect, 100s per call, 8tools/listpages, 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 alwaysrisk: "medium": the schema and description come from a third party, so a self-declared risk level is not trustworthy. envandheadersresolve only from the plugin's own settings via{ "setting": "<key>" }; the host environment is never passed through (D018). A stdio child getsPATH, temp/locale vars, and the declared values — nothing else.commandmust be a bare PATH name or plugin-relative;urlmust behttpsunless 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.servicesdeclares{ id, label?, autoRestart? }behindbackground.service, at most 4 per plugin. The plugin callspi.services.register({ id, start, stop }); the broker callsstartafteronLoad(5s budget) andstopbeforeonUnload, 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: falseopts out. After the last attempt the plugin staysfailed— a visible failure beats a silent crash loop. - Per-service state (
starting|running|stopped|failed) and the restart count are read overplugin/servicesand rendered as chips on the Plugins page; every transition emitspluginChangedwithreason: "service"and aplugin.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/subscriberequirebus.publish/bus.subscribeand a matching entry incontributes.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-assignedat; 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 stubbedpi.events.on/offreal. - Caps: 64KB per payload, 16 subscriptions per plugin, 100 publishes per rolling 10s window, failing with
LIMIT_EXCEEDED/RATE_LIMITEDand 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-lsources login files and-isources 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 -lcstill 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 keepsbash -cwith the host environment (no change). - Fixes macOS Finder/Dock launches where
bash -lcalone 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, andagentchannels remain local NDJSON files, but each channel is now a directory containing focused<category>.logfiles. 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 intiming.log. - Every record carries a
categoryfield. Child stderr is buffered by line, decoded as UTF-8, and stripped of ANSI control sequences before it is persisted. Unknown child output goes toruntime.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,
Contextlabel, 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 intokens/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-aimodel metadata used by the agent sidecar; provider metadata and the 128K default remain fallbacks for unknown models. UiMessage.responseDurationMs,UiMessage.toolUsage, and the optionaltool_end.toolUsageevent 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-aimodel 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
ToolSearchon a new prompt. (CompactContextwas also always-active here until D200 removed the tool; D203 restores it asnew_context, again always-active.) Agent follows pi's coding-agent core (Read/Bash/Edit/Write), while Chat keeps (Read/Glob/Grep). Agent-modeGlob/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.
addedToolNameslets 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.
terminatedand equivalent incomplete stream messages map toSTREAM_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
Editfor one unique local replacement, useWritefor a coherent rewrite, then allow one fresh read/regeneration after a mismatch. A second same-pathEditfailure 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/compactremains 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/EWOULDBLOCKerrors. - 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::Builderas 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.
| ID | Topic | Decision | Rationale |
|---|---|---|---|
| D188 | Replace Chat profile with Plan state | (superseded by D189) **PI-Desktop has one pi Agent and one product selector: `Agent | Plan. 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.** |
| D189 | Plan checkpoint artifact, approval, and execution epoch | **The same pi Agent uses `Agent | Plan, 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 |
| D190 | Selectable command shell catalog and execution identity | Host-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
agentis the only session mode. The formerchatprofile is renamedread-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) andchatnormalizes toread-onlyon every host write path, so a legacy or unknown value fails closed instead of widening tools. BASH_DISABLED_IN_CHAT/WRITE_DISABLED_IN_CHATbecomeBASH_DISABLED_IN_READ_ONLY/WRITE_DISABLED_IN_READ_ONLY.- Database open rewrites
sessions.mode = 'chat'and a storeddefaultModeofchattoagent— 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-onlynormalization and thechatfix-up it describes shipped.
2026-08-05 — Structured tool-result presentation in the transcript
- Expanded tool rows no longer render
JSON.stringifyof 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
detailsand 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 hostnotice(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'sturn_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 keepsthinkingOnlyso the two shapes stay distinguishable. - The nudge rides on
state.systemPromptrather thanprepareNextTurn, 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.
Readpaginates withoffset/limitand 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 unboundedsedpipeline.Greptakespath,include,outputMode,headLimit;Globtakespath,limit; both order by modification time, newest first.- An explicit
pathdisables parent ignore files. Without that, scoping a search tonode_modulesordistreturned 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:
contentstays byte-faithful so text copied out of it still matches forEdit, and the window metadata plusnoticecarry 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:
enabledplus{ mode: "global" | "projects", projects: [] }. The boolean stays separate from the scope so switching something off keeps the project list it was narrowed to, and theoff/projects/globalcontrol 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.
parseMcpImportreads themcpServersdocument every README prints plus theservers, bare-map and single-object variants, infershttpfrom aurlbecause half the configs omittype, 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.mdand07-plugins/03-plugin-api.md.
2026-08-05 — Permission-gated external paths and portable native search
- Explicit
patharguments forRead,Glob,Grep,Write, andEditare classified against the durable session workspace and scratch roots before the normal risk matrix. Inaskandaccept-edits, an outside path emits the existing permission card with the requested path preview; inauto, it executes without a card. Allow-once and the existing tool-scoped allow-session grant remain available, while denial, timeout, and cancellation returnTOOL_DENIEDwithout 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, andGrep.path/include/outputMode/headLimit) with the canonicalfilesWithMatchesspelling; the host normalizes commonfiles_with_matchesandfiles-with-matchesprovider 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/pathreferences, 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, orgoal. 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.
EnterGoalModeandSubmitGoalmirrorEnterPlanModeandSubmitPlan, writing.pi/goal/<unique-name>.mdand one pendingplan_approvalsrow.plan_approvals.kind(schema v11) is the only discriminator; a submit tool run against the other kind fails withPLAN_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_PLANerror codes, the single-pending-approval invariant, and thePLAN_REQUIRES_INTERACTIVE_SESSIONrejection 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, and04-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, thensession.replaceMessagesto stamprevisionCount/activeRevisionon 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'sturn_id. session.saveActiveRevisionnow 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.replaceMessagescarries each surviving message's owningturn_idacross 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
CompactContexttool, 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_endgain optionalphase, andcompaction_endgains optionalstatus { 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
ContextCompactionitem and aWarningfor 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 crosseshardLimitor the model callednew_context, matching Codex'sshould_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_windowrollover that asks for no summary and installs a fixed marker text. The family is an internal switch (construction option, thenPI_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_contextis parameterless with Codex's description verbatim, sits on the host no-confirmation allowlist, and is never assignable to a subagent. Two budget reminders — atclamp(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_enddropsphaseand carriesmarkinstead ofstatus; 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
buildSessionContextfixes that order,hardLimitstays "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
toolsfrom 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 noTasktool of its own. Its calls run through the sametools.executehost 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 theTaskcall with a tool error instead of falling back to the session model. Taskis offered in Agent mode only. Fan-out comes from execution modes: the session Agent runstoolExecution: "parallel", every other tool issequential, and only an all-Taskbatch runs concurrently, capped at 4 slots and each delegate at its ownmaxTurns(default 24, maximum 80). The sidecar serializes same-pathWrite/Editcalls through aPathMutex.- 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/agentNamein the messagemeta, 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
Taskrow and keeps them out of the turn stream and the minimap. OneTaskstays 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 + Jis a customizable renderer shortcut namedopenWorkPanel. 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
@pathgrammar. 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.nameis the sanitized original leaf label, whilepathkeeps 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-shaperendering 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.notifyremains an in-app Toast for compatibility. Plugins with the existingnotifypermission gaingetNotificationPermission,requestNotificationPermission, andshowNativeNotificationAPIs.- Native plugin notification objects remain owned by Electron main. The API returns a best-effort
granted/denied/unknown/unsupportedstate 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 newplugin/reloadinvoke 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+Spacesystem-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 emitskeyboard.shortcutto 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.