Repository navigation
Conversation
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
The following comment was made by an LLM, it may be inaccurate: |
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
|
Measured on v2.2.2 with real sessions; table and script in #51497 (comment). With 32,153 rows since the checkpoint, one per-step chain takes 3.07 s: The branch no longer applies to v2.2.2: cherry-picking 86ee410 conflicts in |
What this contains
Three commits:
HistoryCache(L1 history cache) — process-local cache of decoded session messages, keyed by(sessionID, compaction boundary):seq > watermark) and decode only those; merge with cached entries; replay the two pure in-memory filters (native-checkpoint retirement, replayability); advance the watermark.UPDATEof an already-read row, revert-committedDELETE, transfer import.entriesForRunner(the per-request hot path) opts in;load/previewkeep the uncached path.OPENCODE_GC_ON_STEP=N(default off) —Bun.gc(true)every Nth step at the runner-loop boundary, right afterrunStepreturns, where step transients (decoded history, converted messages) are unreachable.test-history-cache.ts(10-scenario correctness suite) andbench-history.ts(tiered microbenchmark over a copy of a real session DB).Design notes
(session, boundary)so a boundary change can never serve a stale range.SessionMessageTable. Non-append writes (UPDATE of a settled row, revert DELETE) drop the session's cache; transfer import (direct INSERT outside the projector) invalidates as well; the one-time v1 migration runs before any cache exists.history-cache.ts): the runner converts history throughtoLLMMessagesbefore plugin hooks see messages, and that conversion constructs fresh objects per message, so cachedSessionMessage.Infovalues never flow into mutable request state. The v2 plugin session API has no message-level rewrite; the request hooks (context/compaction/generate/title) mutate converted copies in place. A future conversion cache (L2) would additionally needhooks.has()gating — deliberately out of scope here.Verification
v2tip.tsgo -b tsconfig.json tsconfig.tests.jsonafterbun install --frozen-lockfile).Measurements
All numbers from self-compiled v2.0.10 baseline vs patched builds with identical build flags (
OPENCODE_CHANNEL=latest,--single,--skip-web-ui), mock provider, 100 scripted steps, per-run sandbox wipe, pairwise interleaved A/B rounds. Harness: konghayao/harness-perf-benchmark.Per-row cost of the current full reload (microbenchmark)
toLLMMessagesconversionJSON.stringifyThe cache eliminates the first two (~70% of opencode's own chain) for all but the newest rows.
Heavy scenario, 4 MB history (20 runs)
(
GC(N)=OPENCODE_GC_ON_STEP=N; heap cap =BUN_JSC_forceRAMSize=536870912. A dedicated 8-run batch measured L1 alone at CPU -8.5% / wall -6.1%, 4/4 pairwise wins — the CPU win magnitude varies with machine load, the direction does not.)Standard scenario, 400 KB history (12 runs)
Peak RSS variance collapses from σ≈53 MB to σ≈14 MB with the GC knob on — memory behavior becomes deterministic instead of GC-timing-dependent.
Mechanism notes
Honest tradeoffs / why this is not ready
v2tip with identical logic; the correctness suite passes on the tip.Happy to split this into cache-only (commits 1+3) if the knob is out of scope for core.