feat: derive curation outcomes instead of paying the judge to guess them - #304
Merged
Conversation
Owner
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Bundle ReportBundle size has no change ✅ Affected Assets, Files, and Routes:view changes for bundle: engram-frontend-client-array-pushAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #304 +/- ##
==========================================
+ Coverage 91.05% 91.09% +0.04%
==========================================
Files 222 223 +1
Lines 23201 23344 +143
==========================================
+ Hits 21126 21266 +140
- Misses 2075 2078 +3 ☔ View full report in Codecov by Harness. |
This was referenced Jul 29, 2026
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.
What
The curation judge no longer chooses an outcome. It reports a relation per shortlist
target; the code derives the outcome from those relations plus deterministic facts.
judge_policy_deniedbecomes impossible by construction.Why
_apply_evidence_policyadmits a model-chosenoutcomeonly when it satisfies predicatesover facts the model cannot fully observe — including
latest_evidence_atordering, whichis not in the prompt at all. The admissible set is computable before the call, yet we
asked the model to guess into it and paid for every guess.
Measured on the dogfood stand:
judge_policy_deniedin 6hif not okrevise_memory/supersede_memory/open_conflict, all timecandidate_decisionworks interminal_failureThe denial is deterministic — the same candidate against the same corpus is denied
identically forever — but it is classified
PROVIDER_TRANSIENT, so every denial retriesand buys another discarded call.
Two things the measurements refuted
Brute-forcing the policy over every (outcome, relation, target, applicability,
temporal_order) tuple for 30 stuck works, with zero judge calls:
The corpus is fully embedded. The embedding cliff is not the current cause; the
feasibility precheck here is insurance against a regression, not the main saving.
revise_memoryandsupersede_memoryare structurally unreachable. They needcandidate_tier == 'corroborated', and every distillation candidate comes from exactlyone window (4243 of 4243), which the independence union-find collapses to one group:
Two of six outcomes are dead code for the only candidate producer that exists. Whether
single-window multi-observation evidence should corroborate is a separate design
question and is not changed here.
How
curation_derivation.pyis pure and total. Eligibility rules mirror_apply_evidence_policyconjunct for conjunct. The ladder, in priority order:
open_conflict— a contradiction blocks every other actionsupersede_memoryrevise_memorymerge_evidencereject_candidate/ redundant — a redundant target means publishing would duplicatepublish_newreject_candidate/ unsupportedTies within a rung resolve to shortlist order, which is already part of
manifest_hash.The safety claim is that the derivation is a strict restriction of the old policy:
_apply_evidence_policyis kept and still runs on the derived verdict, as an assertion.The property tests prove it never fires — exhaustively over the lattice (relations ×
candidate tier × target tier × comparison_complete × conflict × visibility × precedence ×
applicability, for one- and two-target shortlists), not by sampling.
Also proven exhaustively:
comparison_complete=True⟹ derivation is total (never returns "no decision")curation_infeasibleisINFRASTRUCTURE_TRANSIENT: it retries with backoff, for free, and self-healsJudgeCurationCandidate.executecomputes the feasible set before resolving a policy orbuilding a prompt, and raises without calling the provider when it is empty.
This slice changes only how the same response bytes are interpreted — no prompt or wire
change. Fields the model still emits but we now ignore get trimmed in slice 2, which is
where the token saving lands.
Eval
The frozen corpus is a CI gate (
backend.yml). Exactly one existing case changed meaning:fault-policy-004expected a provider fault from a policy denial, which can no longerhappen. It is replaced by a genuinely-still-faulting case (
fault-comparisons, a comparisonoutside the shortlist manifest), and the semantic content it was testing becomes a new
safe_supersessioncase: a zero-evidence candidate claiming supersession must derivereject_candidateand must never be destructive. No threshold was weakened.The corpus as committed could not see the new decision surface. Every case had 0 or 1
shortlist entries (
{1: 86, 0: 35}) while production shortlists carry 4 ({4: 30}across30 sampled works), so cross-target rung ordering — the entire behaviour the ladder
introduces — had zero coverage. An earlier draft of this PR wrongly named the eval as the
guard for the last risk below. A
multi_target_ladderbucket (minimum 5) closes it, andevery case pins what the LADDER derives against a model that said something else:
ladder-rung-order-000ladder-conflict-first-001ladder-cross-visibility-002ladder-tie-order-003ladder-unsupported-duplicate-004nonetargetAll twelve thresholds stay at their bound with these added.
Risks
A false
equivalentno longer overwrites — because fix: make merge_evidence attach evidence instead of replacing content #303 landed first. Derivationremoves the pressure to misreport an outcome, not the ability to misjudge a relation.
merge_evidenceused to route through_execute_candidate_revisionand setmemory.title/memory.bodyfrom the candidate: a content replacement wearing the name"merge". It fired 69 times all-time only because most attempts were denied; this change
puts it at rung 4, reachable for every
supportedcandidate, against a backlog of 5,824mostly-duplicate proposed candidates. Shipping this alone would have knowingly amplified a
destructive path, so fix: make merge_evidence attach evidence instead of replacing content #303 made MERGE evidence-only first and is already in master. Both
deploy together.
The revision signal degrades to a duplicate. Since candidates are always
supported,candidate_revisescan never reach its rung and falls through topublish_new. The oldgate admitted exactly this state, so the restriction invariant holds and nothing is
overwritten, but the corpus accumulates near-duplicates instead of revisions.
suppressed_identity_relationsis logged to measure it.open_conflictis structurally unreachable, so contradictions publish silently._conflict_eligiblerequires¬deterministic_precedence— the twolatest_evidence_atequal or missing — and in production both are always populated and essentially never
equal. This mirrors the old gate exactly, so the unreachability is pre-existing, but the
consequence changes: a
mutually_incompatibleused to be denied and terminalise, writingnothing; now it falls through to
publish_new. Widening the rung would break therestriction invariant, so it is deliberately not done here. Tracked as B-008.
applicabilityis top-level in v1 but the ladder is per-target. The conservativedirection is safe; the unsafe one (model said
sameabout target A, ladder acts on B) isnarrow because the ladder picks the strongest identity relation, usually the model's own
choice. Slice 2 moves it into the comparison object.
judge_cross_visibility_deniedbecomes unreachable from the parse path. The laddernever selects a cross-visibility target for a mutation rung, so that terminal
INVALID_INPUTstop cannot fire. On redrive, works that previously died there now derivepublish_newand create a memory — a data-creating change applied to an existing backlog,which is why the redrive is batched with the outcome mix checked between batches.
Derivation may act more destructively than the model asked. The model's own
outcomeis ignored, so an identity relation on a target it did not select now drives the outcome
—
ladder-rung-order-000is exactly that case. Post-fix: make merge_evidence attach evidence instead of replacing content #303 this is no longer a contentoverwrite; it is evidence attached to a memory the model itself called equivalent. It is
now a pinned expectation rather than an emergent accident.
Redrive protocol
write_exact_memory_projectionlooks the document up bymemory_version_id, so every writeoutcome creates a new version, a new
RetrievalDocument, and therefore one withembedding_pgvectorNULL until its embedding work lands — whilecorpus_fully_embeddedisFalse if any current document in the authorized corpus is unembedded. Every successful
decision briefly sets
comparison_complete=Falseproject-wide. Invisible at low volume;under a mass redrive it would stay false continuously, starving the
publish_newrung andmaking
derive_decisionreturnNoneafter the call was paid for. The ~2918 terminalworks are therefore redriven in bounded batches, draining embeddings between them.