# How Pi‑Web Persists Startup Preferences Without Replaying Agent Commands

> Discover how Pi-Web persists startup preferences by writing UI selections directly to settings.json, avoiding agent command replays for efficient session management.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-08-13

---

**TLDR:** Pi‑Web writes explicit UI selections directly to the user's [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) via `SettingsManager` immediately after session creation, comparing them against the effective values already persisted by the `AgentSession` constructor. If they match, it updates the defaults and flushes to disk without ever invoking `setModel()` or `setThinkingLevel()`, preventing duplicate session entries.

When working with the **pi-web** coding agent, preserving a user's selected model and thinking level across restarts presents a unique architectural challenge. The system must remember these startup preferences without appending redundant commands to the session history or firing duplicate extension events. According to the pi-web source code, this is achieved by persisting preferences to a standalone settings file rather than replaying agent commands, ensuring clean `.jsonl` session files and idempotent configuration.

## The Problem: Constructor Persistence Creates Side Effects

When a new `AgentSession` is instantiated, its constructor immediately writes the effective **model** and **thinking level** into the session file. If the UI were to subsequently call `setModel()` or `setThinkingLevel()` to "save" these preferences, the agent would append extra entries to the session history and fire redundant extension events. This duplication corrupts the session log and wastes computational resources. Pi‑Web therefore avoids re-invoking these setter methods entirely, opting instead to persist the explicit browser selections directly into the user's [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) *before* any setter commands are processed.

## The Pre‑Session Persistence Workflow

Pi‑Web solves this through a strict seven-step validation and persistence pipeline implemented in [`lib/startup-preferences.ts`](https://github.com/agegr/pi-web/blob/main/lib/startup-preferences.ts) and orchestrated by [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts):

1. **Collect explicit preferences** supplied by the UI (`initialModel` and `thinkingLevel`).
2. **Create the session** with those values (or with defaults derived from current settings).
3. **Determine effective values** that the session actually started with (`inner.model`, `inner.thinkingLevel`, `inner.supportsThinking()`).
4. **Compare** the explicit UI values with the effective ones.
5. **If they match**, write them to the `SettingsManager` as defaults via `setDefaultModelAndProvider` and/or `setDefaultThinkingLevel`.
6. **Flush** the `SettingsManager` to persist the changes to disk.
7. **Never call** `inner.setModel()` or `inner.setThinkingLevel()` again—the preferences are now stored, and future sessions will start with them automatically.

### Validation Through Comparison

The core logic resides in [`lib/startup-preferences.ts`](https://github.com/agegr/pi-web/blob/main/lib/startup-preferences.ts) (lines 33‑44). The function receives both the **explicit** UI choices and the **effective** session values, performing an equality check before writing. This ensures that only valid, actually-applied configurations become defaults, preventing the persistence of unsupported model selections or invalid states.

### Atomic Disk Persistence

Once validated, the helper invokes `settingsManager.setDefaultModelAndProvider` and `settingsManager.setDefaultThinkingLevel` (lines 39‑50), followed by `settingsManager.flush()` (line 53). This sequence atomically writes to `~/.pi/agent/settings.json`, guaranteeing that preferences survive application restarts without requiring any command replay from the session history.

## Implementation: lib/startup-preferences.ts

The `persistExplicitStartupPreferences` function encapsulates the comparison and persistence logic. Located at lines 15‑55, it accepts the user's explicit choices and the session's effective state, returning a flag indicating whether the model default changed (to trigger cache invalidation).

```typescript
// Example: manual persistence (rarely needed – used internally by Pi‑Web)
import { SettingsManager } from '@earendil-works/pi-coding-agent';
import {
  persistExplicitStartupPreferences,
  ExplicitStartupPreferences,
} from '@/lib/startup-preferences';

async function saveUserPreferences(
  settings: SettingsManager,
  uiPrefs: ExplicitStartupPreferences,
  sessionEffective: {
    model?: { provider: string; modelId: string };
    thinkingLevel: ThinkingLevel;
    supportsThinking: boolean;
  },
) {
  const { modelDefaultChanged } = await persistExplicitStartupPreferences(
    settings,
    uiPrefs,
    sessionEffective,
  );

  // Optional: refresh UI if the default model changed
  if (modelDefaultChanged) {
    // e.g. invalidate UI‑side model cache
  }
}

```

## Integration: lib/rpc-manager.ts

The RPC manager orchestrates the workflow immediately after `AgentSession` construction (lines 1633‑1648). It extracts the effective values from the session inner object, invokes the persistence helper, and conditionally clears the model cache if the default changed.

```typescript
// Inside lib/rpc-manager.ts – how Pi‑Web invokes the helper
const persistedPreferences = await persistExplicitStartupPreferences(
  services.settingsManager,
  {
    ...(initialModel ? { model: initialModel } : {}),
    ...(thinkingLevel ? { thinkingLevel } : {}),
  },
  {
    ...(inner.model
      ? { model: { provider: inner.model.provider, modelId: inner.model.id } }
      : {}),
    thinkingLevel: inner.thinkingLevel,
    supportsThinking: inner.supportsThinking(),
  },
);

if (persistedPreferences.modelDefaultChanged) invalidateModelsCache();

```

This integration ensures that the [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) file is updated synchronously with session startup, eliminating the need to replay any configuration commands when the application restarts.

## Summary

- **Pi‑Web** avoids command replay by writing startup preferences directly to [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) via the `SettingsManager` API rather than invoking agent setters.
- The `persistExplicitStartupPreferences` helper in [`lib/startup-preferences.ts`](https://github.com/agegr/pi-web/blob/main/lib/startup-preferences.ts) (lines 15‑55) validates that explicit UI choices match the effective session values before persisting.
- Defaults are only updated when the comparison succeeds, ensuring that unsupported or invalid configurations are never written to disk.
- `SettingsManager.flush()` atomically commits changes to `~/.pi/agent/settings.json`, making preferences durable across restarts.
- [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (lines 1633‑1648) coordinates the workflow and triggers `invalidateModelsCache()` when the default model changes, keeping the UI state synchronized.

## Frequently Asked Questions

### Why doesn't pi-web just call setModel() after creating the session?

The `AgentSession` constructor immediately persists effective values to the session `.jsonl` file upon instantiation. Re-invoking `setModel()` or `setThinkingLevel()` would append duplicate entries to this log and fire redundant extension events, corrupting the session history. By writing directly to [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) instead, pi-web stores preferences without any side effects on the active session.

### Where exactly are these startup preferences stored on disk?

The preferences are persisted to the user's local settings file at `~/.pi/agent/settings.json`. The `SettingsManager` class provides the abstraction for this file, exposing methods like `setDefaultModelAndProvider`, `setDefaultThinkingLevel`, and `flush` to perform atomic disk writes without manual file handling.

### What happens if the explicit UI values don't match the effective session values?

The comparison logic in [`lib/startup-preferences.ts`](https://github.com/agegr/pi-web/blob/main/lib/startup-preferences.ts) (lines 33‑44) verifies that the explicit user selections exactly match the values actually adopted by the session. If they differ—perhaps due to model availability changes or validation failures—the function skips writing to `SettingsManager`, preventing the persistence of invalid or unsupported defaults.

### Does this persistence method survive application restarts?

Yes. Because preferences are written to the standalone [`settings.json`](https://github.com/agegr/pi-web/blob/main/settings.json) file on disk via `SettingsManager.flush()`, they are completely independent of the transient session state or `.jsonl` log files. When pi-web restarts, it reads these defaults during `AgentSession` construction, applying the previous configuration without replaying any historical commands.