fix(kv-unified): exact token key in the leaf engine (#109); tests from the #110 review - #118
Conversation
…R 110 review The leaf engine grouped labels by bucketed tokens and pruned across token counts, but has no envelope and reports a zero score bound. Fewer tokens is not always better (budgetPenalty), so it could return a worse cut than the oracle while claiming it was exact (anima-research#109). Key on exact tokens; continuity and fidelity buckets stay, as they are monotone in the score. Tests added: - anima-research#109 repro: leaf engine matches the oracle with buckets, with and without a relevant cache and a protected hole; the auto route's regret stays within its reported bound. - packed and object DAG storage both throw SparseLabelCeilingError. - selective scoring: lower bound equal to the best upper bound is evaluated (fails if the strict > becomes >=). - trace ancestry across the 2^18 PackedTraceArena page boundary. Closes anima-research#109. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q1H4PTNHqyTg3G74YnFK9e
…ck every trace assignment Review follow-up (TASK-KY4QE, gpt-6-astra xhigh): - solveLeaf reported the configured tokenBucketSize though it keys tokens exactly, so latent-demand evaluations were marked approximate. - The trace paging test missed a wrong-page action lookup; it now checks every visited assignment, with 3 actions so page aliasing shows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q1H4PTNHqyTg3G74YnFK9e
Review round 2 (TASK-2F927, Opus xhigh): - The latent-demand `approximate` flag read the configured bucket sizes, and explicit options require all three to be positive, so it was always true, even for exact results. It now follows approximationScoreErrorBound. The leaf engine's stats go back to reporting the configured buckets, as the DAG engine does. - Add a tie-heavy random sweep (equal chunk sizes) so the anima-research#109 guard does not rest on one fixture; it fails on the old key. - Changelog: say the leaf fix needs an explicit engine: 'leaf' and may need a larger labelCeiling; add the latent-demand flag change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q1H4PTNHqyTg3G74YnFK9e
Anarchid
left a comment
There was a problem hiding this comment.
Verdict: approve
Reviewed at 8bc3788 against main eca70da (0.11.0).
The #109 fix is sound, the tests actually pin the behaviour, and the scope is stated honestly. The one follow-up is the approximate flag: it still reports true in every live steady-state solve, so the second changelog line promises more than it delivers. CI hasn't run yet: this is a fork PR, and both workflow runs are waiting for maintainer approval.
Minor
- In live solves the
approximateflag is still always true. (Sol)approximationBoundaddshysteresis = adoptEpsilonwheneveroptions.presentationis set (kv-unified-pareto.ts:700-709), and the live adapter passes both. So with any positiveadoptEpsilon, once the first presentation is accepted, every auto-route latent-demand evaluation reportsapproximate: true, even when nothing was pruned. The new test sets neitherpresentationnoradoptEpsilon. Fix: split the pruning error from the hysteresis allowance in the propagation stats and key the flag on pruning only. Otherwise, reword the comment and changelog as "nonzero regret bound, hysteresis included". - The flag reads only the
conservativesolve. (Sol)expectedImprovement(also used as a ranking tiebreak) and the shared baseline can come from a solve with a nonzero bound while the flag saysfalse. Sol has a deterministic case: expected bound 2226.68, conservative 0 →approximate = false. Fix: OR the same predicate across baseline, expected and conservative.
Neither #1 nor #2 changes any decision. Nothing in CM, AF or connectome-host readsapproximate; it's only exposed throughlastDemandEvaluations. - A recipe can reach
engine: 'leaf'.KvUnifiedOptions extends ParetoSolveOptions. connectome-host passes the wholekvUnifiedobject through without rejecting unknown keys, and it's spread intosolve()(kv-unified.ts:64). A recipe withkvUnified.engine: "leaf"therefore gets the extra label growth, andSparseLabelCeilingErroris uncaught. That's already true on every engine, and the body and changelog disclose the ceiling change. No recipe we know of setsengine. Follow-up: treatengineas solver-internal and pin it to'auto'at the adapter.
Verified fine
stateKey(label, 0, …)means the exactrenderedTokens, with no division (:960-968).dominates()compares real values, so with an exact token key no cross-token prune can happen. The continuity and fidelity buckets only partition comparisons, dominance still uses the real losses, and those are monotone inpolicyScore. So the engine stays exact.- Routing: omitted,
autoanddagall go to the DAG branch, and only the literal'leaf'reachessolveLeaf(:152-166). Nothing in CM, AF or connectome-hostsrc/sets it. - The 60-case sweep uses a fixed seed and a local LCG, with no timing assertions. The #109 repro and the latent-demand test fail on the old logic.
- main + #114–#118 merged together builds and passes 852/852 (node 22), including this PR's new tests. That's the only test run so far, since GitHub CI is still pending approval.
Method
Two reviewers in parallel. Sol (Codex gpt-5.6-sol, xhigh, read-only, no network; ~10 min, 56 commands, 2.8M input tokens (2.5M cached), 26k output) and Claude (the combined-tree build and run, recipe and CI state, and a check of every Sol citation). Sol only: #1, #2, and the pass-through detail in #3. Claude only: the cause of the empty status rollup (fork approval) and the check that no recipe sets engine. Sol rated #3 major; it was lowered because ceiling errors are already fatal on every engine and the change is disclosed.
Follow-up to #110 with the items from its review thread. Closes #109.
What changes
Leaf engine fix (#109). The leaf engine now groups labels by the exact token count (
stateKey(label, 0, k, f)). Before, a token bucket put labels with different token counts in one group and dropped the one with more tokens. But fewer tokens is not always better:budgetPenaltypunishes going under budget. The engine has no error envelope, so it returned a worse cut and still reported a bound of 0. Continuity and fidelity buckets stay, because they only split groups; dominance still compares the real values. This is fix 1 from #109.Who it affects. After #110 the auto route never uses the leaf engine. Only an explicit
engine: 'leaf'reaches it, and nothing insrc/sets that, so the default route is unchanged. A caller that does pickleafgets more labels and may need a largerlabelCeiling(numbers below).Latent-demand
approximateflag. It used to check whether bucket sizes were configured. Explicit options require all three to be positive, so it was always true, even for exact results. It now reads the solve's reported score error bound: approximate only when the bound is above 0.Tests
Each new test was checked by breaking the line it guards; it fails every time.
SparseLabelCeilingError(labelCeiling: 1).>→>=atkv-unified-selective.ts:121).PackedTraceArena: every assignment is right across the 2^18 page boundary.approximate, on both the auto and the leaf engine.npm test: 844/844.Cost of the fix (leaf engine only)
Measured in review round 2: explicit
engine: 'leaf', buckets 100/100/100,maxTokens= 60% of raw.It grows faster than the forest. At 32 chunks with
labelCeiling: 20000, the old key returned a (possibly wrong) cut; the new key throwsSparseLabelCeilingError. This is the input for the open question below.Review rounds
Round 1: GPT-6 Astra, xhigh. Approve.
Round 2: Claude Opus, xhigh. Approve.
labelCeiling.approximateflag was still always true. Fixed: the flag now follows the reported bound.engine: 'leaf'. Fixed.Open, not in this PR
continuityLambda: 0, orcacheLambda: 0with a relevant cache).🤖 Generated with Claude Code
https://claude.ai/code/session_01Q1H4PTNHqyTg3G74YnFK9e