fix(pi): stop mutating systemPrompt, inject via context hook to preserve prefix cache#822
Conversation
… context hook (mksglu#598) The Pi adapter previously used before_agent_start to modify systemPrompt with dynamic content (active_memory, resume snapshot, behavioralDirective usage stats). Since systemPrompt sits at messages[0], any change to it invalidates prefix prompt caching on DeepSeek, Anthropic, and OpenAI — causing cache hit rates to drop from 90%+ to ~18%. This fix: 1. Adds a module-level _pendingContext variable 2. In before_agent_start, stores extra context there instead of returning a modified { systemPrompt } 3. Registers a 'context' hook (same pattern as hindsight does) that appends the pending context as a user message at the END of the message array — preserving all prefix cache This aligns the Pi adapter with how context-mode works on Claude Code, where hook output is appended to the message stream rather than modifying the system prompt. Closes: mksglu#598
…n catch Addresses review feedback: - Wrap context hook body in try/catch to prevent hook errors from breaking context assembly (consistency with before_agent_start pattern) - Explicitly reset _pendingContext in the before_agent_start catch block as a defensive measure against stale-data escape paths
|
Hi @xuli500177 Did you test that on manually? Are you sure that's not the dead code etc? |
|
Yes, tested manually across multiple turns in a live Pi session with DeepSeek (OpenCode Go provider). What I verified:
The fix is minimal (+30/-2 lines) and aligns the Pi adapter with how all other platforms (Claude Code, Codex, etc.) work — hook output appended to message stream, not injected into system prompt. |
|
For context on why we chose the
Then there's a third, separate hook — We went with
|
… return
before_agent_start no longer returns { systemPrompt }. The routing anchor,
active_memory, and behavioral_directive are now injected via the context
hook as a user message appended at the end of the message array.
Updated 5 tests to trigger the context hook after before_agent_start and
assert content appears in event.messages instead of systemPrompt.
|
@xuli500177 Thanks for the PR. I validated this following the maintainer review process, including git archaeology and 3OS build/test checks. What this PR changesThis PR moves Pi dynamic context injection away from mutating Instead, it stores pending context and appends it later through the Pi What passedLinux:
macOS:
Windows:
Remaining validation gapI do not think this should be treated as fully verified yet. The key runtime assumption is that real Pi calls There is also a test gap: the resume/context injection test does not strongly prove that resume content is delivered through the new context-hook path. Requested changesCould you please update this PR with the following?
|
|
Hi @ken-jo, Thanks for the detailed review and for running the cross-platform build/test checks. Here's our response to each of your three requested changes. 1. Stronger regression test ✅Added two new tests in Test A — Resume snapshot via context hook: Triggers a full session lifecycle:
Test B — End-to-end with existing messages: Simulates a realistic multi-tool session (read/write/bash), triggers compaction, then verifies:
2. Live Pi validation ✅We ran the extension in a real Pi session (v0.79.3) with debug instrumentation to verify the runtime behavior: This confirms:
Regarding prompt-cache behavior: 3. Concurrency/session safety ✅
Why it's safe under Pi's current model:
If Pi ever introduces multi-session-per-process, all module-level singletons would need refactoring to a Files changed
|
|
Ci has error @xuli500177 |
|
Pushed an update in What changed:
Local verification:
On the previous Ubuntu CI failure: it failed before install/build/test during macOS, Windows, and OpenClaw E2E passed on that run, so that failure looks like a Hex/network setup flake rather than a failure in this PR's TypeScript/tests. The new GitHub Actions runs for this pushed commit are currently marked |
Problem
The Pi adapter uses
before_agent_startto inject dynamic content (active_memory, resume snapshot, behavioralDirective usage stats) into thesystemPrompt. SincesystemPromptsits atmessages[0], any change invalidates the entire prefix prompt cache on DeepSeek, Anthropic, and OpenAI APIs — cache hit rates plummet from 90%+ to ~18%.This was originally reported in #598, but the fix there only moved
behavioralDirectivewithin the systemPrompt, which does not solve the problem for API-level prefix caching.Root Cause
Unlike Claude Code (where hook output is appended to the message stream by the CC runtime), the Pi extension API's
before_agent_startdirectly modifies the system prompt. Dynamic data at the prefix position = guaranteed cache miss every turn.Fix
Three changes to
src/adapters/pi/extension.ts:_pendingContextmodule variable — buffer for context that should reach the LLM without touching systemPromptbefore_agent_start: instead ofreturn { systemPrompt }, store the extra context in_pendingContext, with defensive reset in the catch blockcontexthook (wrapped in try/catch) — same pattern used by hindsight — appends_pendingContextas a user message at the end of the message arrayThis aligns the Pi adapter with how context-mode works on Claude Code and all other platforms — content injected at the end of messages, leaving the static prefix intact.
Before / After
systemPromptmodificationcontexthook → message appendmessages[0](prefix)messages(suffix)Testing
--checksyntax validation