How pi-web Session List Caching Prevents Redundant File System Scans
pi-web eliminates redundant file system scans by storing session lists in a TTL-based in-memory cache, deduplicating concurrent requests through promise coalescing, and invalidating entries only when mutations occur.
The pi-web repository (agegr/pi-web) serves as the web interface for Pi sessions, requiring frequent access to the list of active sessions located in ~/.pi/agent/sessions/. Without optimization, every UI interaction would trigger expensive directory traversals and Git process spawns. The codebase solves this through a robust pi-web session list caching mechanism implemented in lib/session-reader.ts that balances performance with data consistency.
The Three-Layer Caching Strategy
The caching system employs three coordinated mechanisms to minimize I/O overhead while ensuring stale data is never served.
TTL-Based Freshness Checks
At the core of the implementation lies globalThis.__piSessionListCache, a global variable storing the cached array. When listAllSessions() is invoked, it first validates cache freshness against SESSION_LIST_CACHE_TTL_MS (set to 30000 ms at line 95 of lib/session-reader.ts). If the timestamp at lines [52‑55] indicates the cache is still fresh, the function returns the cached array immediately, bypassing all file system operations.
Promise Coalescing for Concurrent Requests
When the cache expires or initializes, the system prevents redundant work during concurrent requests by creating a single coalesced promise attached to globalThis.__piSessionListPromise. As implemented at lines [57‑62] and [78‑80], this pattern ensures loadAllSessions() executes exactly once even if multiple components simultaneously request the session list. Each generation of requests shares the same promise instance until resolution.
Generation-Based Invalidation
The cache tracks state through globalThis.__piSessionListGeneration. When mutations occur—such as session creation, deletion, or renaming—the function invalidateSessionListCache() bumps this generation counter (lines [97‑100]) and clears the cached data. This forces the next invocation to trigger a fresh scan, guaranteeing consistency without requiring expensive validation checks on every read.
How Mutating Operations Trigger Invalidation
The caching system remains transparent to users because mutation sites explicitly invalidate stale data. In lib/rpc-manager.ts (lines [147‑455]), every session-mutating RPC—including fork, delete, and rename operations—calls invalidateSessionListCache() immediately after completing the mutation. Similarly, the REST endpoint in app/api/sessions/[id]/route.ts invalidates the cache when handling PATCH or DELETE requests.
This pattern mirrors the per-CWD caching found in lib/worktree.ts (where resolveProject caches project metadata), demonstrating a consistent architectural approach to expensive I/O operations throughout the codebase.
Practical Implementation Examples
Fetch the session list efficiently using the cached helper:
// Example: fetch the current session list (uses cache if still fresh)
import { listAllSessions } from "@/lib/session-reader";
async function showSessions() {
const sessions = await listAllSessions(); // ← cached after first call
console.table(sessions.map(s => ({ id: s.id, name: s.name })));
}
Explicitly invalidate the cache after creating sessions to ensure immediate consistency:
// Example: explicitly invalidate the cache after creating a new session
import { invalidateSessionListCache } from "@/lib/session-reader";
async function createSession() {
// …call the API that creates a session…
await fetch("/api/agent/new", { method: "POST", body: JSON.stringify({ cwd: "/repo" }) });
// Force the next listAllSessions() call to rescan the filesystem
invalidateSessionListCache();
}
Summary
- TTL-based freshness: The cache remains valid for 30 seconds (configurable via
SESSION_LIST_CACHE_TTL_MS), eliminating repetitive scans during rapid UI interactions. - Promise coalescing: Concurrent requests for
listAllSessions()share a single execution ofloadAllSessions()viaglobalThis.__piSessionListPromise, preventing redundant Git and file system operations under load. - Explicit invalidation: Mutating operations in
rpc-manager.tsand API routes callinvalidateSessionListCache(), ensuring the generation counter increments and subsequent reads fetch fresh data. - Global state management: The implementation uses
globalThisto maintain__piSessionListCache,__piSessionListPromise, and__piSessionListGenerationacross the application lifecycle.
Frequently Asked Questions
How long does pi-web cache the session list?
According to lib/session-reader.ts line 95, the default time-to-live (TTL) is 30 seconds (SESSION_LIST_CACHE_TTL_MS = 30000). After this duration, the next call to listAllSessions() will trigger a fresh file system scan.
What happens when multiple components request sessions simultaneously?
The system uses promise coalescing via globalThis.__piSessionListPromise (lines [57‑62]). Even if ten components request the list at once, loadAllSessions() executes only one time, and all callers receive the same resolved array.
When is the session cache explicitly invalidated?
Any operation that modifies session state—such as creating, deleting, forking, or renaming a session—calls invalidateSessionListCache(). This function appears in lib/rpc-manager.ts (lines [147‑455]) and app/api/sessions/[id]/route.ts, ensuring the generation counter increments immediately after mutations.
Does this caching affect real-time session status updates?
The cache only affects the list of sessions, not individual session state. While the list itself may lag up to 30 seconds behind reality (or until an invalidation occurs), active session monitoring occurs through separate mechanisms. For immediate consistency after mutations, the codebase explicitly invalidates the cache.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →