fix(agents): move channel-varying system-prompt sections below the ca…#98285
fix(agents): move channel-varying system-prompt sections below the ca…#98285LEXES7 wants to merge 1 commit into
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Close this PR as superseded: it implements the same runtime fix as the earlier, still-open and mergeable canonical PR, while that PR has stronger maintainer-facing state with completed CI, sufficient proof, and provider-override regression coverage. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Canonical path: Use #98267 as the canonical landing path for #98261, optionally porting this PR's prefix-equality assertion before merge. So I’m closing this here and keeping the remaining discussion on #98267 and #98261. Review detailsBest possible solution: Use #98267 as the canonical landing path for #98261, optionally porting this PR's prefix-equality assertion before merge. Do we have a high-confidence way to reproduce the issue? Yes. Current main still emits exec-approval guidance and Authorized Senders before Is this the best way to solve the issue? No for this PR as the landing path. The earlier open PR is mergeable, proof-positive, CI-green, and includes provider-override coverage, so this branch is redundant except for a small test variant. Security review: Security review cleared: The diff only reorders existing prompt text and updates a colocated test, with no dependency, workflow, secret, install, auth, or code-execution surface. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 3811001d2783. |
|
Superseded by #98267, now merged on The canonical landing moves both exec-approval guidance and Authorized Senders below Exact-head CI, Blacksmith Testbox/ARM/build-artifacts gates, Dependency Guard, and mandatory autoreview all passed. Full maintainer proof is recorded on #98267: #98267 (comment) Closing this overlapping implementation in favor of the landed canonical fix. Thanks for independently identifying and proving the cache-boundary issue. |
Closes #98261
What Problem This Solves
Fixes a prompt-cache stability bug in
buildAgentSystemPrompt()(src/agents/system-prompt.ts). Two channel/identity-varying sections were emitted into the static prefix, aboveSYSTEM_PROMPT_CACHE_BOUNDARY:/approveinstruction differs by channel (CLI/plain/approvevs native approval-card channels like iMessage / Slack / Telegram-with-inline-buttons).## Authorized Senders— varies by owner identity, and is dropped entirely in minimal (cron/subagent) mode.Because their content differs by channel/identity, they fork the supposedly byte-identical cacheable prefix very early. On local models with client-managed prefix caching (llama.cpp / MLX / Ollama), this re-prefills the entire system prompt from scratch on any turn whose channel context differs from the previous one — e.g. an interactive turn right after background cron/heartbeat traffic — which the linked issue measured as multi-minute cold prefills.
This is the direct follow-on to #40256, which relocated Messaging/Voice/Reactions below the boundary but missed these two sections.
Why This Change Was Made
Both sections are instructional context — position-independent for correctness — so they were moved into the volatile suffix below
SYSTEM_PROMPT_CACHE_BOUNDARY, mirroring the existing treatment of Messaging/Voice/Reactions. The channel-independent/approverules stay above the boundary, keeping the stable prefix as large as possible.One behavioral subtlety is preserved: exec-approval guidance previously lived inside the
tool_call_stylefallback array, so a provider that overrodetool_call_stylesuppressed it. The relocated emission keeps that gating (it stays suppressed whenproviderSectionOverrides.tool_call_styleis set). This is therefore a pure cache-stability change with no behavioral difference on any channel.User Impact
Local-model deployments stop paying repeated cold prefills when the channel context changes between turns: the cacheable prefix is now channel/identity-independent, so it stays warm across interactive/cron/heartbeat traffic. No behavioral change for any channel or provider; cloud providers (server-side caching) are unaffected either way. No configuration changes.
Evidence
Real behavior proof (standalone, non-test)
Drove the real
buildAgentSystemPromptfrom a standalone script (node --import tsx) for two interactive turns on different channels/owners (CLI vs iMessage), and measured the byte-identical prefix they share — i.e. the region a prefix cache can actually reuse. Run on a minimal workspace, so absolute offsets are smaller than the issue's full ~17.8K-token prompt, but the mechanism and direction are identical:BEFORE — unpatched
main:AFTER — this branch (identical inputs):
The byte-identical cacheable prefix grows from 2,020 → 6,850 chars and now reaches the boundary (
false → true); the first cross-channel divergence (the exec-approval line) moves from char 2,020 (above the boundary) to char 6,850 (below it). The two channels' boundaries also converge to the same offset (6560/6653 → 6324/6324), confirming the prefix is byte-identical.Regression test
vitest run src/agents/system-prompt.test.ts→ 91 passed (90 existing + 1 new guard).New guard
keeps channel/identity-varying sections below the cache boundary (#98261)asserts both sections sit below the boundary, and that the above-boundary prefix is byte-identical when only the owner identity changes, and when only the approval-UI capability changes. It fails onmainand passes here:(the exec-approval guidance is at index 2020, above the boundary at 6653, on
main).Sibling suites also pass:
system-prompt.memory.test.ts,bootstrap-budget.test.ts,sanitize-for-prompt.test.ts,heartbeat-system-prompt.test.ts→ 36 passed. oxlint andoxfmt --checkare clean on both files.