#14879: fix: persist session metadata to sessions.json after context pruning
agents
stale
size: L
Cluster:
Session Pruning Enhancements
## Summary
Fixes #14857
After context pruning runs (cache-ttl mode), `sessions.json` retains stale `contextTokens` values until the next inbound message triggers a usage update. This causes Mission Control dashboards and monitoring systems to show incorrect context usage (e.g. 200k/200k when actual is much lower post-pruning).
**Root cause:** The pruning extension modifies messages in-flight during the Pi context event but never writes the reduced token estimate back to the session store. The store only updates when `persistSessionUsageUpdate` runs after the next agent reply.
**Fix:** After a successful prune, fire-and-forget persist the new estimated token count to `sessions.json` via `updateSessionStoreEntry`. This is best-effort -- a failure here never blocks the agent run.
## Changes
| File | Change |
|------|--------|
| `src/agents/pi-extensions/context-pruning/runtime.ts` | Add `sessionKey` and `storePath` to `ContextPruningRuntimeValue` type |
| `src/agents/pi-extensions/context-pruning/pruner.ts` | Export `estimateContextChars` and `CHARS_PER_TOKEN_ESTIMATE` for reuse |
| `src/agents/pi-extensions/context-pruning/extension.ts` | After pruning, calculate new token estimate and persist to sessions.json |
| `src/agents/pi-embedded-runner/extensions.ts` | Accept `sessionKey`/`storePath` params, resolve `storePath` from config, thread to runtime |
| `src/agents/pi-embedded-runner/run/attempt.ts` | Pass `sessionKey` to `buildEmbeddedExtensionPaths` |
| `src/agents/pi-embedded-runner/compact.ts` | Pass `sessionKey` to `buildEmbeddedExtensionPaths` |
## Tests (30 new tests across 3 files)
| Test File | Tests | Coverage |
|-----------|-------|----------|
| `extension.test.ts` | 12 | Persistence after pruning, no-persist when session info missing, error swallowing, update callback token calculation, TTL behavior |
| `runtime.test.ts` | 9 | Store/retrieve runtime, sessionKey/storePath storage, deletion, non-object guards, isolation between sessions, backward compatibility |
| `pruner-exports.test.ts` | 9 | CHARS_PER_TOKEN_ESTIMATE value/type, estimateContextChars for empty/single/multiple/mixed messages, token consistency |
## Test plan
- [x] No persist when no runtime registered
- [x] No persist when TTL not expired
- [x] No persist when messages unchanged (no pruning occurred)
- [x] Persist fires after successful pruning with sessionKey + storePath
- [x] No persist when sessionKey missing
- [x] No persist when storePath missing
- [x] Persistence errors swallowed (fire-and-forget)
- [x] Update callback computes correct estimated tokens
- [x] totalTokens is at least the estimated context tokens
- [x] lastCacheTouchAt updated after pruning
- [x] Runtime stores/retrieves sessionKey and storePath
- [x] Backward compatible -- sessionKey/storePath are optional
- [x] Existing session and pruning config tests still pass
- [x] All 30 new tests pass
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR wires `sessionKey`/`storePath` into the context-pruning runtime and, after a successful prune in cache-TTL mode, fire-and-forget persists an updated `contextTokens` estimate to the session store so monitoring dashboards reflect post-pruning usage immediately.
The main new flow is: embedded runner builds extension paths → initializes context-pruning runtime → extension prunes messages on the `context` event → computes an estimated token count from the pruned messages and calls `updateSessionStoreEntry` to patch `contextTokens`/`totalTokens`.
Issue found: the embedded runner currently resolves `storePath` via `resolveStorePath()` without passing the active agent id/config store, which defaults to the *default agent* sessions store; this can cause writes to the wrong `sessions.json` when running under a non-default agent.
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable but has a correctness issue in multi-agent setups that can write pruning metadata to the wrong sessions store.
- Core pruning/persistence logic is straightforward and best-effort, but `storePath` resolution currently falls back to `DEFAULT_AGENT_ID` (via `resolveStorePath()` with no opts), so embedded runs for non-default agents can fail to persist or update the wrong `sessions.json` entry.
- src/agents/pi-embedded-runner/extensions.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#14913: fix: update context pruning to notify session metadata after prunin...
by ScreenTechnicals · 2026-02-12
89.6%
#11999: fix: add session-growth guard to prevent unbounded session store gr...
by reverendrewind · 2026-02-08
81.8%
#10567: feat(agents): add configurable startup session pruning
by abutlabs · 2026-02-06
81.5%
#15726: fix(sessions): use model contextWindow instead of agent contextToke...
by lailoo · 2026-02-13
80.3%
#10997: fix: enable cache-ttl pruning on first load after restart
by anotb · 2026-02-07
79.9%
#19878: fix: Handle compaction when fallback model has smaller context window
by gaurav10gg · 2026-02-18
78.2%
#10465: Context pruning: strip image blocks instead of skipping
by quentintou · 2026-02-06
78.1%
#13841: fix: use last attempt prompt tokens for session total instead of ac...
by 1kuna · 2026-02-11
77.4%
#12581: feat(hooks): emit session prune lifecycle event
by vincentkoc · 2026-02-09
77.3%
#13388: feat(session): Auto-prune consecutive NO_REPLY messages from context
by Masha-L · 2026-02-10
77.1%