Problem
When loading session history (timeline pages, SDK attach/resume), the server:
- Scans directories to find
.jsonl files — O(N) readdir calls per session load, even though the indexer already knows the path
- Re-reads and re-parses unchanged
.jsonl files on every request — no caching between consumers (WsHandler, timeline service)
- Makes N+1 HTTP requests for timeline pages — 1 request for the page metadata + N requests for individual turn bodies
The most impactful issue is #3: loading a 20-turn timeline page requires 21 HTTP round-trips.
Solution
Three-layer caching, all with optional injection (zero behavior change when deps aren't provided):
Layer 1 — O(1) Path Resolution
loadSessionHistory accepts an optional resolveFilePath callback that short-circuits the directory scan using the indexer's path map. Falls back to scan if resolver returns undefined.
Layer 2 — LRU Content Cache
New SessionContentCache class (~120 lines):
- Stat-based invalidation (mtime + size) — no stale data
- Request coalescing — concurrent reads share a single file read
- Configurable byte budget (default 100MB,
FRESHELL_SESSION_CACHE_MAX_MB env var)
- LRU eviction using Map insertion-order iteration
Layer 3 — Timeline Body Batching
includeBodies query parameter on AgentTimelinePageQuery. When true, timeline page response includes full turn bodies inline, eliminating per-turn HTTP requests.
Impact
- Layer 3 is the clear win — reduces 21 HTTP requests to 1 for a typical timeline page
- Layer 2 is defensive — prevents redundant reads when multiple consumers load the same session
- Layer 1 is a micro-optimization — saves a few readdirs on local filesystem
Problem
When loading session history (timeline pages, SDK attach/resume), the server:
.jsonlfiles — O(N) readdir calls per session load, even though the indexer already knows the path.jsonlfiles on every request — no caching between consumers (WsHandler, timeline service)The most impactful issue is #3: loading a 20-turn timeline page requires 21 HTTP round-trips.
Solution
Three-layer caching, all with optional injection (zero behavior change when deps aren't provided):
Layer 1 — O(1) Path Resolution
loadSessionHistoryaccepts an optionalresolveFilePathcallback that short-circuits the directory scan using the indexer's path map. Falls back to scan if resolver returns undefined.Layer 2 — LRU Content Cache
New
SessionContentCacheclass (~120 lines):FRESHELL_SESSION_CACHE_MAX_MBenv var)Layer 3 — Timeline Body Batching
includeBodiesquery parameter onAgentTimelinePageQuery. When true, timeline page response includes full turn bodies inline, eliminating per-turn HTTP requests.Impact