fix: reconcile Hermes attribution per bucket without global equality - #197
Merged
Conversation
This was referenced Aug 24, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per-bucket Hermes reconciliation (option B) — replaces the exact-equality grand-total gate
Follow-up to the findings in this comment. Closes #169's historical-reconciliation blocker.
Problem
reconcileHermesMixedModelUsage()requiredsameHermesTokenTotals(existingTotals, authoritativeTotals)exactly before rebuilding. On any real long-running install this precondition never holds:state.dbdeletes old sessions after ingestion, leaving existing buckets with tokens the authoritative view can never see again (verified orphan:hermes|gpt-5.5|2026-06-23T15:30Z, 15,787 tokens;state.dbretains only sessions from 2026-08-05).state.dbbetween the two reads, so the totals drift on every attempt (4 production attempts all returnedblocked_total_mismatch; delta oscillated 1.06M–1.32M).Change
Replaces the all-or-nothing gate with per-(model, half-hour)-bucket comparison:
state.dbas today.appendHermesReconciliationRows).total_tokens > 0→ zero-out latest-wins row:hour < MIN(started_at)across sources) → provable orphan;last_seenbucket). Unfinished sessions re-emit growth at their new bucket on the next incremental sync.bucketsCorrected / bucketsSkipped / orphansZeroed / stalePlacementsZeroed;status: appliedshort-circuits reruns (idempotent).Verification
state.dbsnapshot): receiptapplied, 275 corrected, 78 skipped, 1 orphan zeroed (gpt-5.5), 348 stale placements zeroed. After persisting cursors: hourly state == canonical DB view == queue latest-wins exactly (7,611,980,055 tokens, diff 0); fable per-model diff 0. Second run appended 0 bytes.65a6b242: sound implementation, unfinished-session trade-off verified safe.