Skip to content

perf: improve prompt-prefix caching for the agentic model#100

Open
DavertMik wants to merge 1 commit into
mainfrom
perf/agentic-prompt-prefix-caching
Open

perf: improve prompt-prefix caching for the agentic model#100
DavertMik wants to merge 1 commit into
mainfrom
perf/agentic-prompt-prefix-caching

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

Implements plans/025-agentic-prompt-prefix-caching.md.

Problem

In a recent session the two models diverged sharply on prompt cache reuse:

Model Calls Input Cached Cache rate
default (tester/researcher/navigator) 219 2.74M 2.05M 75%
agentic (planner/pilot) 47 192K 18K 9%

The 66-point gap is exactly the three properties the default-model agents already have and the agentic ones lacked: a byte-identical prefix, an unchanging tool list, and append-only history.

Changes

Pilot — static-first prompts. getSystemPrompt, buildVerdictSystemPrompt and buildResetSystemPrompt now open with their static supervisor/evidence/decision bodies and close with the per-test task block. The dynamic parts of buildSharedEvidenceRules (scenario, deletion scope, expected results) moved into a new buildTaskContext(task) that both verdict and reset prompts append last. The verdict opener no longer embeds Tester requested ${type} — the user message already states it.

Pilot — constant tool set. sendToPilot always builds pickPlanningTools() + buildPreconditionTool(task) and gates calls with toolChoice: 'auto' | 'none'. Previously planTest sent tools, reviewNewPage sent none, and analyzeProgress sent them only on failures; tool definitions are part of the cached prefix, so every flip invalidated the whole conversation. reviewNewPage now passes task, so task became required on the options object.

Pilot — append-only history. Dropped the three cleanupTag calls. Pilot conversations are light by design (tool summaries, no HTML) and bounded by the tester iteration cap.

Planner — stable prefix order. Messages now run system → static rules → static output format → page research → approach + URL context → the rest. The output-format block became getTasksMessage(), and its hasCurrentPlan conditional collapsed into one sentence covering both fresh and expanding plans.

Disambiguator routing. getAgenticModel()getModelForAgent(). These ~350-token one-shot prompts are below the 1024-token cache floor and don't need the expensive model. Cost fix, not a cache fix — the one commit-separable piece here.

Prompts are reordered, not rewritten; total length is unchanged. No provider.ts change was needed: buildGenerateConfig spreads ...options after the toolChoice: 'auto' default. Per the plan, this stays provider-agnostic — no providerOptions, no promptCacheKey.

Trade-offs

  • Task context moves from the top of the system prompts to the end. End-of-prompt is still a high-attention position, but verdict/planning behavior could shift.
  • Planner's output-format instructions now precede the page data. generateObject's schema still constrains output shape, but adherence to the prose rules could weaken on some models.
  • Without cleanupTag, pilot conversations grow by the trimmed blocks (~1-3K tokens on long tests), traded for cache hits across the conversation.
  • A provider that ignores tool_choice: none could still emit a tool call on review-only turns; maxToolRoundtrips: 0 keeps that bounded to one roundtrip.

Testing

bun test tests/unit/ tests/integration/ — 871 pass, 0 fail. The planner integration tests needed no updates: they assert against the concatenated prompt rather than message order.

Not yet done, and the real gate on this change:

  • Regression replay comparing the per-model token summary (agentic model expected ≥40% cached, up from 9%, with the same pass/fail outcomes).
  • Langfuse spot-check that consecutive pilot.analyze calls show growing cached-token counts instead of resets to 0.

🤖 Generated with Claude Code

Planner and Pilot ran at a 9% prompt cache rate while the default-model
agents sat at 75%. The gap was three missing properties: a stable
byte-identical prefix, an unchanging tool list, and append-only history.

- Pilot: system, verdict and reset prompts now open with their static
  bodies and close with the per-test task block. Extract buildTaskContext()
  for the dynamic part; drop the request type from the verdict opener since
  the user message already carries it.
- Pilot: always send the same tool set and gate it with toolChoice
  'auto'/'none' instead of flipping the tool list between calls, which
  invalidated the whole conversation's cache.
- Pilot: stop rewriting history via cleanupTag. Pilot conversations are
  light by design; the trimmed KBs cost more re-tokenized than they save.
- Planner: order messages system -> static rules -> static output format ->
  page research -> approach + URL context, so re-plans of the same page
  reuse the research prefix.
- Route the click disambiguator off the agentic model: ~350-token one-shot
  prompts can never cache and don't need the expensive model.

Prompts are reordered, not rewritten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Explorbot Self-Regression

Commit 1043e1a · run

Scenario Result Attempts Duration
basic (native) PASS 1/3 9m
experience: control OK — failed as expected 1/1 1m
experience: seeded PASS 1/3 2m

Attempt details

  • basic (native) attempt 1 — PASS: login evidence: PASS (post-login plan=true, post-login research=true); research: PASS (files=6, wellFormed=true, keywords=7/3); scenarios: PASS (tests=8/5, features=4/3); tests passed: PASS (5 passed, 0 failed (reporter: 5 passed, 0 failed))
  • experience: control attempt 1 — PASS: control: OK — failed as expected (0 passed, 1 failed)
  • experience: seeded attempt 1 — PASS: seeded: PASS (1 passed, 0 failed)

Session analysis — basic (native):

Session Analysis

The Issues module core functionality is operational: creating, searching, viewing details, and filtering issues by label or status all work as expected. An automation sequencing issue in one test did not prevent successful verification.

Coverage

  • Pages: /issues (Issues list), /issues/{id} (Issue detail)
  • Features: Create issue, Search issues, Navigate to detail, Filter by label, Filter by status

What works

  • Issue creationET-1 Create a new issue via toolbar button
  • Issue searchET-2 Search for existing issue by keyword
  • Issue detail navigationET-3 Navigate to issue detail from list
  • Label filteringET-4 Filter issues by bug label
  • Status filteringET-5 Filter issues by status

Execution Issues

  • ET-4 Filter issues by bug label — unnecessary "Back to issues" clicks failed before filter logic ran, did not affect final verification

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.

2 participants