Conversation
The Coordinator is the only producer of these rows and stamps them with
`{name, extra_server_args, extra_envs, gain_pct, source}`
(`writeback._build_recipe_payload`). The store normalised each row down to
the arbor pair — `_normalise_str_dicts(what_worked, ("description",
"measured_impact"))` — a vocabulary the Coordinator never writes, and
nothing translated between the two, so every value was dropped:
sent: {"name": "mtp_on", "extra_server_args": "--speculative-num-steps 3",
"extra_envs": {...}, "gain_pct": 4.2, "source": "https://pr/123"}
stored: {"description": "", "measured_impact": ""}
Not a missing field — the row count survived and every value was "".
`prelude._warm_recipe_proven_items` reads `name` and skips rows without
one, so it always returned [], and the `already_proven` list the scout is
handed in `phases/internal` carried only the current session's accepted
variants. Work proven in an earlier session was invisible, so the scout
re-researched it.
`Finding` and `Failure` now carry the producer's fields as hyperloom
superset fields — the same convention as `Pitfall.severity`, which arbor
consumers ignore — and `_normalise_experience` preserves them.
`description` falls back to `name` so an arbor consumer gets readable text
where it previously got "". A row carrying only the arbor pair still
serialises byte-identically, so the wire shape is unchanged for rows that
never had these fields.
Both halves of this seam were already tested: one test asserts writeback
stamps `name`, another asserts prelude reads it. But the prelude test
hand-injected the row, so nothing exercised the persistence step between
them — which is exactly where the data died. The added tests write
through LocalRecipeStore and assert on what comes back.
The explore dedup pre-fill stays inert for now: it fingerprints from
extra_server_args/extra_envs, which the Coordinator does not record on
what_failed rows. Storage no longer stands in the way; that fix is
producer-side.
Co-authored-by: Cursor <cursoragent@cursor.com>
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 breaks
what_worked/what_failedrows reach the KB with every value replaced by"".The Coordinator is the only producer of these rows and stamps them with
{name, extra_server_args, extra_envs, gain_pct, source}(writeback._build_recipe_payload). The store then normalised each row down to the arbor pair —_normalise_str_dicts(what_worked, ("description", "measured_impact"))— a vocabulary the Coordinator never writes. Nothing translated between the two, so every value was dropped:This is not a missing field. The row count survived, so the KB looked populated while carrying nothing.
What that cost
prelude._warm_recipe_proven_itemsreadsnameand skips rows without one, so it always returned[]. Thealready_provenlist handed to the scout inphases/internaltherefore carried only the current session's accepted variants. Anything proven in an earlier session was invisible, and the scout re-researched it — the KB's central promise, not paying out.The fix
FindingandFailurenow carry the producer's fields as hyperloom superset fields — the same convention as the existingPitfall.severity, which arbor consumers ignore — and a new_normalise_experiencepreserves them instead of discarding them.Two compatibility properties hold by construction:
descriptionfalls back toname, so an arbor consumer gets operator-readable text where it previously got"".Why no test caught it
Both halves of the seam were already covered: one test asserts writeback stamps
name, another asserts prelude reads it. But the prelude test hand-injected the row, so nothing exercised the persistence step between them — exactly where the data died.The two added tests write through
LocalRecipeStoreand assert on what comes back out. Againstmainthey fail with the bug in plain view:Scope
The explore dedup pre-fill stays inert for now: it fingerprints from
extra_server_args/extra_envs, which the Coordinator does not record onwhat_failedrows. Storage no longer stands in the way; that remaining fix is producer-side.Verification
569 passedacross therecipe_kb/ sedimentation / warm-start suites.22696 passed. The 9 failures are pre-existing onmainat the same base (2cc382a83) intest_agentx_repair,test_external_multi_node,test_preflight_auth_overrideandtest_supervisor— none touched here. The one that differed between worktrees (test_dotenv_fallback_ignores_arbitrary_cwd_dotenv) was traced to a stray.envin the local worktree: it fails there withmain's code too.ruff check src/hyperloomclean.