fix(agent): drop the hour from the system-prompt date line (day-stable prompt-cache prefix) - #1126
Merged
Merged
Conversation
…e prompt-cache prefix day-stable get_current_date_pacific() rendered "YYYY-MM-DD (Weekday) HH:00 TZ" and SystemPromptBuilder puts it at the tail of the system prompt, which is the head of the Bedrock prompt-cache prefix. Every Pacific hour boundary therefore flipped systemPromptHash and re-wrote the whole cached prefix for every active session. The 2026-09-15 prod cost audit measured 23 distinct systemPromptHash values in one 85-call session and attributed ~2.6% of September cache-write spend to this. The line now renders date, weekday and timezone only, so it is byte-stable for a whole Pacific day. Nothing in the prompt, skills, local tools or the BFF reads the hour (grepped for the helper, the %H:00 format and "current time"-style language), so no flow needed the hour moved into the user turn. Function name and signature are unchanged; the docstring and a comment at the render site record why the hour must stay out of the prefix. Tests: test_timezone.py gets the new regex plus frozen-clock tests that the string is identical across all hours of a day, flips at Pacific midnight, and uses the Pacific (not UTC) date; test_system_prompt_builder.py's mocked return values lose the hour. test_bedrock_cache_points.py is unchanged and still passes; the full tests/agents/main_agent suite is green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pecs
CI's "Test frontend (coverage build)" failed twice on this PR, which
touches no frontend file, in file-preview-panel.component.spec.ts:
"keeps download reachable when the document cannot be rendered" received
a fully rendered document instead of the failure message.
DocxViewerComponent memoizes its dynamic import('docx-preview') on a
private static field, and @angular/build runs vitest with isolate: false,
so every spec file in a worker shares one module registry and one
DocxViewerComponent class. When docx-viewer.component.spec.ts runs first
in the worker it pins its own mocked module into that field; the panel
spec's renderAsync.mockRejectedValue() then programs a vi.fn the
component never calls. The received text ("QuarterRev Q1100 Q2120") is
the viewer spec's last table mock, byte for byte.
Both specs now null the memo in beforeEach. Reproduced and verified with
a single-worker, alphabetically ordered runner config (viewer spec before
panel spec), then the full coverage build: 257 files / 3212 tests pass.
Develop's exact frontend tree was never exercised by this job: CI runs
only on PRs, the docx specs landed in 76e022a, and later PRs that passed
all add or change spec files, which reshuffles worker assignment.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
get_current_date_pacific()renderedYYYY-MM-DD (Weekday) HH:00 TZ, andSystemPromptBuilder.build()appends it to the system prompt, which is the head of the Bedrock prompt-cache prefix. Every Pacific hour boundary flippedsystemPromptHashand re-wrote the whole cached prefix for every active session, at the cache-write premium.The 2026-09-15 prod cost audit measured 23 distinct
systemPromptHashvalues in one 85-call session and attributed ~2.6% of September cache-write spend to this.What changed
backend/src/agents/main_agent/utils/timezone.py: the line now renders date, weekday and timezone only (2026-09-15 (Tuesday) PDT), in all three code paths (zoneinfo, pytz fallback, UTC fallback). Function name and signature are unchanged. The docstring records why the hour must stay out of the prefix and where to put one if a flow ever needs it (the user turn, never the system prompt).backend/src/agents/main_agent/core/system_prompt_builder.py: two-line comment at the render site so the hour isn't reintroduced.Hour-dependency check
Grepped develop for the helper, the
%H:00format,(Weekday) HH:00pins, and "current time" / "time of day" language across the agent code, shared APIs, skills, local tools, the BFF chat route, docs and the SPA. The only consumers are the builder and the two test files below. No prompt, skill or tool reads the hour, so nothing needed to move into a turn-scoped message.Tests
tests/agents/main_agent/utils/test_timezone.py: new format regex, plus frozen-clock tests that the string is identical across all 48 half-hours of a Pacific day, flips exactly at Pacific midnight, uses the Pacific (not UTC) date, and rendersPSTin standard time.tests/agents/main_agent/core/test_system_prompt_builder.py: the six mocked return values lose the hour.tests/agents/main_agent/core/test_bedrock_cache_points.py: unchanged, still passes.tests/agents/main_agentCompaction code is untouched (that work is on
feature/compaction-model-relative-thresholds).Second commit: pre-existing SPA spec order dependency (unrelated to the fix above)
The first CI run failed twice in
Test frontend (coverage build)onfile-preview-panel.component.spec.tseven though this PR touches no frontend file. Cause:DocxViewerComponentmemoizes its dynamicimport('docx-preview')on a static field, and the builder runs vitest withisolate: false, so whendocx-viewer.component.spec.tsruns first in a worker it pins its mocked module for the panel spec, whosemockRejectedValuethen programs a function the component never calls.Develop's exact frontend tree had never been exercised by this job: CI runs only on PRs, the docx specs landed yesterday in 76e022a, and every PR that passed since adds or changes spec files, which reshuffles worker assignment. Any PR without frontend spec changes would hit this.
Fix is test-only (9 lines): both specs null the memo in
beforeEach. Reproduced locally with a single-worker, alphabetically ordered runner config, then verified with the full coverage build (257 files / 3,212 tests pass).Expected effect
Once deployed,
systemPromptHashon a session'sC#rows should change at most once per Pacific day (and at the PST/PDT switch) instead of hourly. Verify viaGET /admin/costs/sessions/{id}/callsor thedistinctSystemPromptHashesdiagnosis.🤖 Generated with Claude Code