This project has four complementary layers. They answer different questions and should not be conflated.
bun test (and therefore CI) and
enforce the payload invariants directly against the hook pipeline. This is
the first line of defense against cache regressions.Provider prompt caches are exact byte-prefix matches over the rendered request. Instead of enumerating known-good payload shapes, these suites assert the properties every transform must uphold, so they also catch mistakes that have not been made before:
src/hooks/cache-safety.property.test.ts — re-renders a growing
conversation through the real transform pipeline (mirroring the
composition in src/index.ts) and asserts turn-over-turn byte-prefix
stability, isolation of volatile content to the tagged trailing message,
determinism under wall-clock/randomness changes, and pass-through of
specialist payloads. A drift guard fails when src/index.ts gains, loses,
or reorders transform steps without the suite being updated.src/hooks/cache-payload.snapshot.test.ts — golden snapshots of every
prompt surface the plugin injects (phase reminder, orchestrator system
prompt, canonical transformed payload). A failure means the change will
invalidate provider caches for existing sessions once; update deliberately
with bun test --update-snapshots so the PR diff documents the impact.src/cache-safety-tripwire.test.ts — scans prompt-assembly directories
for volatile-input patterns (Date.now, new Date, Math.random,
randomUUID, performance.now) outside a justified allowlist.All hook injections must go through src/hooks/cache-safe-injection.ts; see
the Prompt Cache Safety section in AGENTS.md for the authoring rules.
src/hooks/cache-monitor/ observes message.updated events and the
provider-reported tokens.cache.read / tokens.cache.write counters. When a
session that previously hit the cache reports zero cache-read tokens on a
sizeable request, it logs a [cache-monitor] possible prompt-cache bust
warning (once per bust streak) to the plugin log. Providers that never report
cache telemetry produce no warnings. This is the field safety net for
provider-side behavior no offline test can model.
Install dependencies and build the local plugin before either check:
bun install
bun run build
The deterministic harness requires a separately pre-provisioned absolute path
to exactly opencode-ai@1.18.2. It deliberately does not install the host
binary or dependencies itself.
Run the harness with the pre-provisioned host binary:
OPENCODE_BIN=/absolute/path/to/node_modules/.bin/opencode \
bun run verify:cache-stability
The script rejects a missing, non-absolute, nonexistent, or non-1.18.2
OPENCODE_BIN. It uses the built local plugin and a local capture provider,
disables Bun auto-install, and makes no external provider or dependency
resolution network calls.
It asserts the deterministic request shape, including:
read and todowrite.Passing this check demonstrates payload stability under the fixture. It does not establish that any external model provider accepted or reused a cache prefix.
The runner talks directly to an existing OpenCode HTTP server. It does not start a server, load credentials, or use the SDK. The required CLI contract is:
bun scripts/benchmark-opencode-cache.ts \
--server http://127.0.0.1:4096 \
--provider PROVIDER_ID \
--model MODEL_ID \
--runs N \
--output /safe/output.json \
--arm ARM_ID \
--plugin-build BUILD_ID
--server, --provider, --model, --runs, --output, --arm, and
--plugin-build are required. Harmless timing/retry controls have defaults:
--timeout-ms (120000), --retries (2), and --retry-delay-ms (500).
Use repeated --header NAME:VALUE only when the already-running server needs
an HTTP header; headers are not persisted. --overwrite permits replacing an
existing output file.
Each fresh session targets orchestrator and the requested provider/model. The
fixed sequence requires Read(package.json), a completed TodoWrite, a final
answer, and a second user follow-up. The runner establishes SSE coverage before
prompting, deletes every created session, and records discards rather than
resampling. Retry, compaction, routing mismatch, incomplete trace, stream
coverage failure, and assistant/provider errors all invalidate a session.
For OpenCode Zen free routing, use provider opencode and model hy3-free.
Keep credentials outside the repository, in the environment of an isolated
server process. In the isolated OpenCode configuration, make model selection
explicit and disable title generation so its separate small-model request does
not confound the run:
{
"preset": "opencode-zen-free",
"default_agent": "orchestrator",
"model": "opencode/hy3-free",
"small_model": "opencode/hy3-free",
"agent": {
"orchestrator": { "model": "opencode/hy3-free" },
"title": { "disable": true }
}
}
With that isolated server running, a live invocation is:
bun scripts/benchmark-opencode-cache.ts \
--server http://127.0.0.1:4096 \
--provider opencode \
--model hy3-free \
--runs 12 \
--arm current \
--plugin-build "$(git rev-parse HEAD)" \
--output /tmp/opencode-zen-hy3-current.json
The JSON report is intentionally redacted. It excludes prompt and response text, request bodies, tool inputs/outputs, workspace paths, headers, nonce values, and raw event properties. It retains normalized routing, tool status, safe assistant/provider error-code classifications, cache token telemetry, timing observations, discard reasons, and comparison metadata.
Request observations preserve cache fields as null when the provider did not
report them; null is not a cache miss. totalInput is only defined when it
can be reconstructed as input + cacheRead + cacheWrite. Position one in each
eligible session is preserved as raw warm-up data but excluded from aggregate
cache and timing metrics.
The primary metric block contains session-clustered bootstrap 95% confidence
intervals for warm-up-excluded zero-cache miss rate, cache coverage,
prompt-loop TTFT p50, and prompt-loop latency p50. Prompt-loop timings are one
observation per submitted user turn, not duplicated over provider steps.
eligiblePrefixCoverage is only meaningful when the provider reports enough
telemetry to calculate it; otherwise treat it as unavailable.
Use the same OpenCode host version, workspace fixture, provider configuration, agent/model routing, and runner revision for every arm. Build or package each plugin arm equivalently, run a one-session clean preflight per arm, then run separate baseline and current arms. Compare the resulting redacted reports; do not mix arms in one server or silently replace discarded sessions.
This is not CI. Provider cache semantics, free-route policy, cache TTL, capacity, model revisions, entitlement, and routing can change independently of this repository. A live result is evidence for its recorded environment, not a portable performance claim.
The following is an environment-specific controlled observation, not a
guarantee. It used isolated OpenCode 1.18.2 hosts, preset
opencode-zen-free, title generation disabled, opencode/hy3-free routing,
the fixed tool trace, and 12 independent eligible sessions per arm.
| Metric | Baseline 67e2a556 |
Current 08e9fa5 |
|---|---|---|
| Eligible / discarded sessions | 12 / 0 | 12 / 0 |
| Warm-up-excluded request observations | 24 | 24 |
| Zero-cache miss rate (95% CI) | 1.000 [1.000, 1.000] | 0.000 [0.000, 0.000] |
| Cache coverage (95% CI) | 0.000 [0.000, 0.000] | 0.941345 [0.940887, 0.942179] |
| Median eligible-prefix coverage | 0.000000 | 0.903893 |
| Cache read / write tokens | 0 / 0 | 397760 / 0 |
| Prompt-loop TTFT p50 ms (95% CI) | 2915.24 [2883.83, 2983.40] | 2115.54 [2033.55, 2326.98] |
| Prompt-loop latency p50 ms (95% CI) | 3216.77 [3072.35, 3255.93] | 2267.81 [2248.48, 3012.78] |
Every eligible session completed read and todowrite; observed routing was
orchestrator / opencode / hy3-free. Repeat the controlled procedure
before drawing conclusions under a different provider account, host, model
revision, or cache policy.
This single-session manual smoke result is not a baseline comparison. It
used an isolated host with existing local OpenAI authentication, the current
plugin build, and openai/gpt-5.6-terra-fast.
| Metric | Observed value |
|---|---|
| Eligible sessions | 1 |
| Cache-read tokens after warm-up | 12288, 13824, 14336 |
| Warm-up-excluded cache coverage | 93.33% |
| Eligible-prefix coverage | 85.64% |
| Second-turn prompt-loop TTFT | 1983.02 ms |
| Second-turn prompt-loop latency | 2278.46 ms |
The required read and todowrite trace completed with no assistant or
provider error. Repeat a controlled baseline/current comparison before making
a provider-wide performance claim.