feat: persist completed sample outcomes so interrupted runs resume instead of restarting - #48
feat: persist completed sample outcomes so interrupted runs resume instead of restarting#48jamespsterling wants to merge 11 commits into
Conversation
…stead of restarting Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Original prompt from James
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Merges main and replaces silent orElseSucceed fallbacks in evaluateOneResumable with wLog'd catchAll handlers so dropped persistence (and therefore lost resume coverage) is observable. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ace store error causes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
E2E re-verification after main merge + logging fix (c55e2bc) ✅Re-ran the deterministic resume harness (20 samples × ~200 ms, file-backed
One nit found during verification (logged Tested by Devin — session |
…silently omit them Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Fixed by f03ffb0 — |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…fields Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…og schema rejections Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-run collisions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…tore parameter names Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
TL;DR
Adds a
SampleResultStoreEffect service to the shared harness: completed per-sample outcomes are persisted (keyed bysampleId/epoch) and replayed on a re-run, so an interrupted run (e.g. a Temporal Activity retry after a worker kill) only re-executes samples that were actually in flight.What changed?
src/harness/sample-result-store.ts:PersistedSampleOutcomeSchema(Zod),SampleResultStoreTag,SampleResultStoreServiceinterface,sampleResultKey(sampleId, epoch), andNOOP_SAMPLE_RESULT_STORE.src/harness/run.ts: the sample/epoch stream now goes throughevaluateOneResumable— read persisted outcome → return it if present, elseevaluateOnethen best-effortwrite. Read failure = cache miss; write failure never fails the run — but both are now logged viawLog(Failed to read persisted sample outcome…/Failed to persist sample outcome…) instead of being silently swallowed.src/runner/run-by-id.ts:RunBenchmarkInputgains optionalsampleResultStore; the layer defaults to the noop store.package.json: exports./sample-result-store.noopSampleResultLayer; branch merged up to currentmain(postrun-by-idrefactor).Why?
In the openrouter-web monorepo (which vendors this repo as a subtree), Temporal Cloud's Worker Controller scale-in kills Cloud Run workers mid-Activity; heartbeat-timeout retries currently restart every sample from scratch. Wiring a GCS-backed store happens downstream: OpenRouterTeam/openrouter-web#35767.
This write-through design was chosen over abort-time aggregate flushing (#52, now closed) after an empirical head-to-head: on hard SIGKILL, write-through preserved 8/13 completed samples vs 0 for the abort-flush approach; clean aborts and repeated interruptions were parity with exact-baseline aggregates. See the comparison summary on #52.
Scope guarding (epochs/range) is intentionally not needed here: outcomes are keyed per
sampleId/epochand the store instance is provided per run/session downstream, so a persisted outcome is only ever reused for the identical sample-epoch.How to test
bun test src/harness/run.test.ts src/harness/sample-result-resume.test.ts— fresh run, full resume (0 solver/scorer calls), partial resume (only missing keys evaluated), and throwing-store behavior (run completes, all evaluated, failures logged).SampleResultStoreServiceinrunBenchmarkById, run once, interrupt, run again with the same store — completed samples return without invoking solver/scorer.Benchmark impact
No score changes with the default noop store. With a persistent store, replayed samples reuse their recorded score/usage instead of re-evaluating, which is the intended behavior for Activity retries.
Reviewer focus
PersistedSampleOutcomeSchemavsEvalOutcomecompatibility (usage/generation-id accounting on replay).catchAll+wLog) around read/write.Checklist
Link to Devin session: https://openrouter.devinenterprise.com/sessions/4899435489c14bba8242921472c31807
Requested by: @jamespsterling