Skip to content

Write each reuse verdict where something actually reads it - #21

Merged
forrestlinfeng merged 1 commit into
mainfrom
feat/local-reuse-ledger
Sep 23, 2026
Merged

forrestlinfeng merged 1 commit into
mainfrom
feat/local-reuse-ledger

Conversation

@forrestlinfeng

Copy link
Copy Markdown
Contributor

The problem, in one number

On this machine:

~/.evomap/evolution/root_events.jsonl      21822 lines

value.inject          1873    ← the injection half has recorded all along
value.reuse_hit          0
value.reuse_outcome      0    ← the return half never existed

Every verdict this plugin has produced went nowhere:

  • the Proxy forwards /asset/reuse-result to the Hub, and the Hub has no such route — its OpenAPI describes 1353 paths, 407 under /a2a/, none of them this one. A live probe returns {"recorded": false, "reason": "hub 404"}.
  • proxyDaemon has no ingestor, so nothing was written locally either. The value.reuse_hit write lives on the MCP tools path, which this plugin does not use.

The consumers were never missing

// evolver-core/src/algo/orchestrator.ts:114
const reuseCounts = opts.reuseOutcomes ? reuseCountsFromSummary(opts.reuseOutcomes) : new Map();
// → candidateAssembly's reuseSentiment: a soft re-order of future candidates

summarizeReuseOutcomes and pruneCandidates are written and correct, the actuator is wired, and EVOLVER_REUSE_SIGNAL defaults on. All of it reads local root events, not the Hub. It was simply starved.

What changes

Each verdict is written to both ledgers:

outcome root event
success value.reuse_hit — the $ rail
failed / mismatched / stale / unsafe value.reuse_outcome — the keep/prune rail

That split is what the accounting expects; summarizeReuseOutcomes reads success from one and the negatives from the other.

A report counts as delivered when either ledger took it, so a Hub without the route no longer leaves an asset pending forever.

The first runtime dependency, and why it is not optional

@evomap/evolver-core pinned at 2.0.38.

Ingestor is the repository's single write entrypoint for root events, and EventStore — single-writer, file-locked, monotonic seq, fsync, schema-validated, 4096-byte lines — is deliberately not exported. Hand-rolling the append would forge the one thing that file guarantees.

Mitigations:

  • imported lazily inside a catch, so an install that cannot resolve it degrades to the old behaviour instead of breaking a turn
  • the ingestor is cached by path, not as a singleton, so it follows the home directory rather than pinning the first one it saw
  • the koffi native dependency it lists is loaded lazily from benchmark/referenceNativeRuntime.ts, which this path never touches. events/ and ops/ never reference it, and the global install here does not have koffi at all and works — which matters, since the dsh profile sets allowBuilds: { koffi: false }

cycleId is dsh:<sessionId>. deriveReuseEntries drops any entry missing either audit anchor (assetId + cycleId), and a task id is neither.

Verification

Before wiring anything, end to end against the real environment:

Ingestor wrote value.reuse_outcome + value.reuse_hit
readEvents → summarizeReuseOutcomes → pruneCandidates: ["sha256:probe"]

Three tests. Neutering recordReuseLocally fails the one that matters. 92/92.

🤖 Generated with Claude Code

Every verdict this plugin has produced went nowhere. The Proxy forwards
/asset/reuse-result to the Hub, the Hub has no such route -- its OpenAPI
describes 407 paths under /a2a/ and none of them is this one -- and the Proxy
has no ingestor of its own, so nothing was written locally either. On this
machine root_events.jsonl holds 1873 value.inject lines and zero value.reuse_*
lines: the injection half has been recording all along and the return half
never existed.

The consumers were never the problem. summarizeReuseOutcomes and
pruneCandidates are written and correct, and orchestrator.ts folds their counts
into a soft re-order of future candidates under EVOLVER_REUSE_SIGNAL, which
defaults on. All of it reads local root events. It was simply starved.

So the verdict is now written to both: the Proxy as before, and a root event
through evolver-core's Ingestor -- value.reuse_hit for a success, and
value.reuse_outcome carrying the verdict otherwise, which is the split the
accounting expects. A report counts as delivered when either ledger took it,
so a Hub without the route no longer leaves an asset pending forever.

This adds the plugin's first runtime dependency. @evomap/evolver-core is not
optional for this: Ingestor is the repository's single write entrypoint for
root events, and EventStore -- single-writer, file-locked, monotonic seq,
fsync, schema-validated, 4096-byte lines -- is deliberately not exported.
Hand-rolling the append would be forging the one thing that file guarantees.
It is imported lazily inside a catch, so an install that cannot resolve it
degrades to the old behaviour instead of breaking a turn. The koffi native
dependency it lists is loaded lazily from a benchmark module this path never
touches; the global install here does not have koffi at all and works.

cycleId is dsh:<sessionId>. deriveReuseEntries drops an entry missing either
audit anchor, and a task id is neither.

Verified end to end against the live Proxy before wiring: two events written,
read back, pruneCandidates naming the failed asset. Three tests cover it;
neutering the local write fails the one that matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 23, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@forrestlinfeng
forrestlinfeng merged commit 705932f into main Sep 23, 2026
4 checks passed
forrestlinfeng added a commit that referenced this pull request Sep 23, 2026
The slow-prime test asserted the step returned within 120ms against a server
that answers in 150ms -- a 30ms margin on shared CI. It went red on main right
after #21 and passed on a rerun, which is the definition of a test that reports
the runner's load rather than the code.

The assertion below it already proves the thing: a step that waited for the
slow search would come back carrying its strategy, so an empty `primed` is the
evidence. Raising the wait budget until the step really does wait still fails
the test with the stopwatch gone.

#21 made the suite heavier -- evolver-core is imported for the root-event
write -- so a margin that was always thin was going to keep flaking.

Co-authored-by: forrestlinfeng <linfengchen@tencent.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@forrestlinfeng
forrestlinfeng deleted the feat/local-reuse-ledger branch September 24, 2026 14:00
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