07. Process Model
1. Processes
MVP target topology:
PI-Desktop.app
├── Electron Main
│ ├── Renderer (React UI)
│ ├── Rust host-core sidecar
│ └── Node pi agent sidecar2. Ownership
| Process | Owns |
|---|---|
| Electron Main | window lifecycle, cross-platform tray integration, IPC fan-in/out, child process supervision, fixed-feed app update lifecycle |
| Renderer | UI only |
| Rust host-core | DB, tools, permissions, immutable Plan/Goal artifacts/plan_approvals execution fields, shell catalog, plugin host services, secrets adapters |
| Node pi sidecar | pi agent loop, provider streaming, tool-call planning |
3. Boot order
- Electron main starts
- Load English locale defaults
- Spawn Rust host-core
app.handshakewith host-core- Spawn Node pi agent sidecar
- Connect agent sidecar tool-bridge to host-core via main
- Create main window / renderer
- Renderer performs
app/getVersionhealthcheck through main
If step 3–4 fails: block app with recovery message. Before a successful host boot serves RPC, host-core transactionally marks prior pending approvals and queued/running plan_approvals execution states interrupted and aborts their running turns. This internal process-epoch fence is not serialized or sent over the protocol.
4. Crash policy
| Crash | Policy |
|---|---|
| Renderer crash | reload window, keep host/agent processes; same-host reload restores only live pending Plan/Goal approvals and their deadlines, not terminal cards |
| Rust host crash | mark app degraded, interrupt pending/queued/running approval work, keep pending sessions in their contract mode (Plan or Goal) and already-approved sessions in Agent, attempt restart host, and fail active sessions closed |
| Node agent crash | abort active turns and live approval waiters/queue entries, keep pending sessions in their contract mode, preserve already-approved Agent mode in Rust, restart sidecar, and never replay an execution |
| Electron main crash | full app exit |
Supervision parameters (implemented in Electron main):
- Child exit rejects all in-flight RPCs for that child immediately (no 130s timeout wait).
- Auto-restart with exponential backoff
0.5s → 1s → 2s(cap 4s). - At most 3 restarts per 2-minute window per child; beyond that the app stays degraded and emits
hostStatus { ok: false, component, fatal: true }. - Restart supervision is single-flight per child. A host process has a unique generation; stale generation requests and notifications are rejected before they reach the current bridge.
- Host persistence appends are buffered in an Electron-main-owned outbox while the host is unavailable and flushed sequentially after a new handshake.
- Host-core's stdin/stdout control path uses one dedicated OS thread per direction rather than Tokio's dynamic blocking pool. Transient pipe resource errors are retried; control-thread creation failures are surfaced as a boot error, so OS thread pressure cannot become an unhandled host panic. The login-shell PATH probe is best effort and falls back to the inherited PATH if its helper thread cannot be created.
- Renderer is notified on every transition via the
hostStatusevent:{ ok, component?: "host" | "sidecar", restarting?, restarted?, fatal?, message? }. - Every rejection that only reports a gone transport — refused before it was sent, or in flight when the transport closed — carries
errorCode: HOST_UNAVAILABLE, so a caller classifies routine teardown by code rather than by matching message text. - Reads of host-owned registries that only add optional context to a launch or a panel (MCP servers, user skills, user subagents) check transport availability first and drop a
HOST_UNAVAILABLErejection quietly, degrading to empty. A dead transport during shutdown or between restarts is routine; logging it atwarnfiles it under the same line as a registry that genuinely cannot be read. - A renderer panel backed by a host-owned registry reloads on
hostStatus { ok: true }, so a call that lost a race with teardown or a restart does not leave the panel showing a transport error for a registry that is fine. - Intentional shutdown (quit/dispose) never triggers restart.
5. Shutdown order
- Reject new prompts
- Abort active turns
- Interrupt pending/queued/running Plan and Goal work and reject late responses
- Unload plugins
- Stop Node agent sidecar
- Flush/close Rust host DB
- Stop Rust host
- Dispose update polling
- Close windows / exit
Minimizing the main window is a resident-shell action, not an application shutdown: Electron Main hides the window and keeps the process alive through the cross-platform tray. The tray owns restore/focus and an explicit Quit action. Quit from the tray, the existing close path, or an update install still enters the normal shutdown sequence above; destroying the tray happens during before-quit so shutdown cannot be intercepted by a stale shell affordance.
updates/install invokes Electron's quit-and-install path only after an update reaches downloaded. Electron still emits before-quit, so the normal sidecar/host shutdown sequence runs before the updater replaces the app.
6. Dev vs release
Dev
- Electron via electron-vite
- Rust via
cargo runbinary path - Node via system Node (
>= 22.19) desktoppredevrebuilds every workspace dependency in topological order (shared,i18n,plugin-sdk, andagent-runtime) before host-core and Electron startup; Electron must never compile against or load stale ignored package artifacts from an earlier source revision
Release
- package Electron app
- ship Rust host binary in resources (
Resources/bin/pi-desktop-host-core) - agent sidecar runs the bundled
agent-runtime/sidecar.json the Electron binary itself withELECTRON_RUN_AS_NODE=1— no separate Node runtime is shipped (resolves D008) Resources/agent-runtime/sidecar.jsis the sidecar's only independent release entry. ASAR does not carry a second complete@pi-desktop/agent-runtimepackage tree; Electron Main may inline the pure-JS helpers it calls without changing process or protocol ownership- renderer dependencies ship through Vite output rather than duplicate raw package trees;
node-ptyremains external and unpacked for its native ABI - packaged builds use the Main-owned update controller. macOS and non-AppImage Linux are manual-delivery modes; Windows NSIS and Linux AppImage use the in-app feeds published by D126 tag releases
7. Acceptance
- Clean boot path documented and scriptable
- Host crash does not silently continue tool execution
- Agent crash does not corrupt SQLite
- A host crash does not create a persistence error storm or replay a completed message twice.
- Host/sidecar crash never turns a pending Plan or Goal approval into Agent execution; restart recovery leaves it interrupted and the durable session in its contract mode
- A queued/running execution that was already approved is interrupted without replay and its durable session remains Agent
- Bash timeout/abort shuts down the complete child process tree