Skip to content

fix(agent): drop the hour from the system-prompt date line (day-stable prompt-cache prefix) - #1126

Merged
philmerrell merged 2 commits into
developfrom
fix/system-prompt-date-daily-stable
Sep 16, 2026
Merged

philmerrell merged 2 commits into
developfrom
fix/system-prompt-date-daily-stable

Conversation

@philmerrell

@philmerrell philmerrell commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Why

get_current_date_pacific() rendered YYYY-MM-DD (Weekday) HH:00 TZ, and SystemPromptBuilder.build() appends it to the system prompt, which is the head of the Bedrock prompt-cache prefix. Every Pacific hour boundary flipped systemPromptHash and 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 systemPromptHash values 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:00 format, (Weekday) HH:00 pins, 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 renders PST in 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.
Suite Result
timezone + builder + cache-points 43 passed
all of tests/agents/main_agent 1,274 passed
ruff on changed files clean

Compaction 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) on file-preview-panel.component.spec.ts even though this PR touches no frontend file. Cause: DocxViewerComponent memoizes its dynamic import('docx-preview') on a static field, and the builder runs vitest with isolate: false, so when docx-viewer.component.spec.ts runs first in a worker it pins its mocked module for the panel spec, whose mockRejectedValue then 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, systemPromptHash on a session's C# rows should change at most once per Pacific day (and at the PST/PDT switch) instead of hourly. Verify via GET /admin/costs/sessions/{id}/calls or the distinctSystemPromptHashes diagnosis.

🤖 Generated with Claude Code

philmerrell and others added 2 commits September 15, 2026 19:58
…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>
@philmerrell
philmerrell merged commit ac2ac1a into develop Sep 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant