fix(review): the four deferred #42 findings — causal finding attribution and a member-aware v2 lifecycle - #62
Conversation
…ttribution and a member-aware v2 lifecycle All four change learning-loop semantics, which is why they were deferred: each one lets the Brain train on a fiction rather than fail loudly. 1. research_subjects.resolve_round_durability inferred an issue's implementation outcome with `ORDER BY r.ts DESC LIMIT 1` over every run that had ever targeted it, so a later review, audit or unrelated run silently replaced the delivery outcome and rewrote both `per_arm_durability` and the accepted influence edge. The delivering run cannot be known at filing time (the issue is filed first), so this is a schema change, not a query tweak: a new `finding_implemented` decision in the SAME event log carries a Brain-validated binding (the run must exist and must have targeted that issue), and the arm is INHERITED from the filing so a binding can never re-attribute. With no binding, resolution now requires exactly one scored run; two or more stays unresolved as `ambiguous_outcome_runs`. The report carries the blocking quantity beside its drain — `unresolved_by_reason`, `drainable_by_binding` and the `finding-implemented` command that clears it — because `no_outcome_run` drains on its own and ambiguity drains through nothing. 2. record_finding_issue stored a caller-supplied `arm`, so a finding could be attributed to another participating agent and resolve_round_durability would then build an accepted edge from that agent's run. Membership is now read from the round's registered arm set or a run bound to the round. The rule already existed at a second seam (completion_event_adapter's `selected_arm_not_in_subject_set`), so the reader is centralised as research_subjects.round_arm_evidence rather than hand-rolled a third time. 3. partitioned_review.register_review_round recorded every `--round-agents` entry as an arm while run_plan dispatches every partition to `--agent` alone, so `--agent gemini --round-agents gemini,cursor` registered two arms of comparative evidence one of which never ran. An arm is now admitted only on a record — the executing seat, a round-bound run, or a prior registration (so the set can only grow, since #2 validates against it and a shrink would refuse real findings). Declared arms with no record are dropped and named in `arms_unproven`. 4. exp_abcd.followup measured eligibility per AGENT (`<agent>.log` from meta["agents"]) while prepare_arms writes one log per MEMBER, so no v2 experiment ever reached collect/evaluate and the v2 identity work produced zero `evaluations_v2` rows — commit eaf2f7b half-fixed. Logs now come from experiment_members + exp_log_path, which keeps the legacy shape working because a legacy member's id IS its agent. collect()'s branch recovery had the matching gap (v2 branches are `exp/<exp_id>-<member_id>`), reporting a recoverable arm as evidence-gone and stamping followup-skip.json, so arm_diff now takes member_id. Three new tests, each with a deliberate-break -> revert demonstration run: - test_multi_agent_arm_set_is_admitted_only_on_a_round_bound_attempt (break: believe the declaration -> 2 tests fail) - test_followup_collects_and_evaluates_a_v2_manifest (break: list meta["agents"] logs -> legacy passes, v2 skipped, i.e. the exact half-fixed state that made this invisible) - test_branch_recovery_resolves_the_v2_member_branch (break: drop member_id -> recovery misses the branch) Plus research_subjects --selftest cases for both refusals (unknown arm, an agent that is an arm of a DIFFERENT round), the ambiguity->binding->resolution path, and binding validation (break 1: newest-run-wins -> durable becomes reverted; break 2: trust the caller's arm -> a bogus arm is accepted). verify.py green in BOTH trees: 390 passed, 0 failed, 0 skipped, 83/83 selftests, 5/5 gates, from this checkout and from a scratch exec-mirror layout built by orch-sync-mirror.sh (ORCH_MIRROR pointed at a scratch path — the LIVE mirror is deliberately NOT synced from an unmerged branch). Floor 387 -> 390, no ceiling moved and nothing new skips. Dedup findings recorded in the capability ledger notes for abcd-experiment and partitioned-review, per CLAUDE.md §0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 33 minutes Limit details: You’ve used the included review currently available. Your 74 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
Comment |
Only `.verify-floor.json` conflicted. Resolved as the UNION per that file's own rule: #59's rationale (floor 387 -> 391, the improvement-log accessor's four tests) is retained, this branch's entry is appended, and the count was RE-MEASURED on the merge result -- 394 -- rather than arithmetic on either side's number. No ceiling moved; the three new tests need no agent CLI, ledger or installed runtime, so nothing new is skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Workflow source detectedPR #62 now has valid workflow source context (origin=review_followup). No linked GitHub issue is required for this PR. |
Automated Status SummaryHead SHA: fe920fb
Updated automatically; will refresh on subsequent CI/Docker completions. Keepalive checklistScopeNo scope information available Tasks
Acceptance criteria
|
|
Runner dispatch state for autofix on PR #62. Do not edit. |
|
Runner dispatch state for codex on PR #62. Do not edit. |
|
Workflow state fingerprint for Agents Gate Followups. Do not edit. |
Now fully green — and the earlier red was upstream, as measuredAll 23 checks pass, That diagnosis was measured before the fix landed, not guessed. Against
The only textual difference between the two error lists was two line numbers drifting in verify.py counts on the final head
Local and CI agree on 405 collected. The CI skips are the bare-runner profile The mirror run is the On the floor
Supersedes an earlier comment on this PR that reported the pre-merge counts and said "83/83 selftests"; the merge with main adds |
|
Workflow state fingerprint for Keepalive Loop Reporter. Do not edit. |
Only `.verify-floor.json` conflicted, for the third time. Resolved the same way: the UNION of both rationales, with the count RE-MEASURED on the merge result (405 = main's 402 + this branch's 3) rather than computed from either side. No ceiling moved; the three new tests build their own tmp_path Brain and manifests, so nothing new is skipped on any machine. Picks up bd6da2e, which fixes the `autofix-versions.env` pin-file failure that was reddening lint/typecheck/pytest on every consumer PR — including this one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d' into claude/pr42-deferred-four-rebased
|
Fixed: added 🤖 Addressed by Claude Code |
…anch Fourth `.verify-floor.json` conflict for this branch, and the first under the EQUALITY that #56 introduced — which is the mechanism working as designed, not friction: that change's own rationale says git conflict detection is what enforces "measure on the merge result, not on the branch". Measured accordingly: 405 = main's 402 + this branch's 3. Under the old minimum, keeping the larger number would have passed; under the equality it has to be measured, and it was. Union of both rationales retained in the note. No ceiling moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # .verify-floor.json # langsmith-fleet-worker-attempt.json
Seventh floor union on this branch. main a247a55 records 410; the merge result MEASURES 411 with `pytest --collect-only -q` -- main's 410 plus this branch's one test. Measured, never derived: #56 made `collected` an EQUALITY, so an assumed number is a hard RED. #69 also flips CI coverage ON, so this branch's merge is the first to be measured under it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
langsmith-fleet-worker-attempt.json is a per-run CI provenance record, not source. reusable-codex-run.yml (in stranske/Workflows) rewrites it on every invocation and uploads it as artifact langsmith-fleet-v1-worker-attempt-<pr>. It became tracked in 2118f57, and because gitignore does not apply to an already-tracked path, the runner's `git add -A` commit step staged it on every run: ten commits across PRs #59/#61/#62/#63, one add/add conflict per concurrent PR, and a silently widened diff on each. Nothing reads it from the tree. The langsmith-fleet/v1 consumers here (langsmith_pull.py, langsmith_fetch.py) ingest the NDJSON Actions artifact `langsmith-fleet.ndjson`; neither names this path. The 30-day artifact upload already provides the replacement path, so untracking loses no provenance. stranske/Workflows reached the same verdict for its own copy. The ignore entry sits outside the synced WORKFLOWS STATUS FILES block, since that block mirrors a consumer template which does not carry this pattern and would drop it on the next rewrite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
langsmith-fleet-worker-attempt.json is a per-run CI provenance record, not source. reusable-codex-run.yml (in stranske/Workflows) rewrites it on every invocation and uploads it as artifact langsmith-fleet-v1-worker-attempt-<pr>. It became tracked in 2118f57, and because gitignore does not apply to an already-tracked path, the runner's `git add -A` commit step staged it on every run: ten commits across PRs #59/#61/#62/#63, one add/add conflict per concurrent PR, and a silently widened diff on each. Nothing reads it from the tree. The langsmith-fleet/v1 consumers here (langsmith_pull.py, langsmith_fetch.py) ingest the NDJSON Actions artifact `langsmith-fleet.ndjson`; neither names this path. The 30-day artifact upload already provides the replacement path, so untracking loses no provenance. stranske/Workflows reached the same verdict for its own copy. The ignore entry sits outside the synced WORKFLOWS STATUS FILES block, since that block mirrors a consumer template which does not carry this pattern and would drop it on the next rewrite. Co-authored-by: t <t@e> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…rop a claim #3210 falsifies This branch ended up with two independent explanations of the same rule, because a concurrent session added `4b5764a` while this branch already carried its own block. Both were correct; having both in a file whose header is specifically about gitignore-comment discipline is not. Kept from the second block, because the first did not have it: - the artifact was tracked from 2118f57, not merely "on main until today"; - ten commits across PRs #59/#61/#62/#63 rewrote it, one add/add conflict per concurrent PR, silently widening unrelated diffs; - untracking loses no provenance — the producer already uploads the same bytes as Actions artifact `langsmith-fleet-v1-worker-attempt-<pr>` with 30-day retention, which is the only reason the file is written at all. Dropped from it, because it is about to become false: "that block mirrors Workflows' templates/consumer-repo/.gitignore, which does NOT carry this pattern". Workflows PR #3210 adds exactly these two patterns to that template at Template-Version 6. The placement above the managed block is still right, but the reason is that a version bump rewrites the block wholesale — not that the template lacks the pattern. Also drops that block's bare literal `langsmith-fleet-worker-attempt.json`, which was already subsumed by the root-anchored `/langsmith-fleet*.json` above it. Comments only. Verified identical behaviour with `git check-ignore`: the root artifact and artifacts/langsmith/ stay ignored, docs/contracts/schemas/langsmith-fleet-v1.schema.json and langsmith_*.py stay committable, and no tracked file is shadowed by any pattern. 427 collected = floor 427; black, ruff and the 11 guard tests unchanged.
The four findings from the CodeRabbit review of #42 that were deliberately deferred because each one changes learning-loop semantics, where CLAUDE.md §2 says a wrong call corrupts the Brain quietly rather than failing loudly.
They were fixed and pushed to
claude/profile-per-agentat3f43569, but #42 had already merged at9239e06btwelve minutes earlier, so that commit never reached main. This is the same commit cherry-picked ontoaf6654d6— clean, and nothing else on main touched those files after #42 landed. The three inline threads on #42 carry the full per-finding replies; #42 also carries a note that they did not ship with the merge.1. Durability inferred from an arbitrary run —
research_subjects.py(thread)resolve_round_durabilitypicked an issue's outcome withORDER BY r.ts DESC LIMIT 1over every run that had ever targeted it, so a later review, audit or unrelated run silently replaced the delivery outcome and rewrote bothper_arm_durabilityand the accepted influence edge.The delivering run cannot be known at filing time — the issue is filed before anything implements it — so this is a schema change, not a query tweak:
finding_implemented, a newdecisionin the sameresearch_subject_eventslog (no new table, so no migration), written byrecord_finding_implementation(). Brain-validated: the run must exist and its recordedtargetmust be that issue. The arm is inherited from the filing, never taken as an argument, so a binding cannot re-attribute a finding.ambiguous_outcome_runs. Newest / first / "looks like an implement" would each let an unrelated run rewrite an arm's durability, which is the defect.unresolved_by_reasonsplits the causes,drainable_by_bindingcounts the only one that drains through nothing on its own (no_outcome_rundrains when work lands,bound_run_has_no_outcomewhen the run is scored), anddrainnames the command.2. Unvalidated arm attribution —
research_subjects.py(thread)record_finding_issuestored a caller-suppliedarm, so a finding could be attributed to another participating agent andresolve_round_durabilitywould then build an accepted edge from that agent's run. Membership now comes fromround_arm_evidence()— the round's registered arm set, or a run bound to the round byexperiment_id. A round with neither is refused outright.The rule already existed at a second seam (
completion_event_adapter'sselected_arm_not_in_subject_set), so it is centralised in one reader rather than hand-rolled a third time — two seams disagreeing about who was in a round is exactly the drift that matters, because the learner treats each arm as independent evidence.3. Arms registered for agents that never ran —
partitioned_review.py(review body, reported outside the diff)register_review_roundrecorded every--round-agentsentry as an arm whilerun_plandispatches every partition to--agentalone, so--agent gemini --round-agents gemini,cursorregistered two arms of comparative evidence one of which never ran.Neither option the reviewer offered was right: restricting the set to the executing agent breaks the real multi-seat case (
--round-agentsexists so severalruninvocations land on one subject). So an arm is admitted only on a record — the executing seat, a round-bound run, or a prior registration, meaning the set can only grow. That last clause is load-bearing: finding 2 validates against this set, so a resumed single-seat run that shrank it would start refusing the other seats' real findings. Declared arms with no record are dropped and named inarms_unproven.4. v2 experiment lifecycle not member-aware —
exp_abcd.py,experiment_recovery.py(thread)followupmeasured eligibility per AGENT (<agent>.logfrommeta["agents"]) whileprepare_armswrites one log per MEMBER, so no v2 experiment ever reached collect/evaluate and the v2 identity work produced zeroevaluations_v2rows —eaf2f7bhalf-fixed. Logs now come fromexperiment_members+exp_log_path, which keeps the legacy shape working because a legacy member's id is its agent (that is what recovered directories write).collect()'s branch recovery had the matching gap — v2 branches areexp/<exp_id>-<member_id>— so it asked for a branch that cannot exist, read that as "the evidence is gone", andfollowupstampedfollowup-skip.jsonwhile the commits sat intact in the shared store.arm_diffnow takes a keyword-onlymember_id.Test gate
Three new tests plus selftest coverage, each with a deliberate-break → revert demonstration actually run:
per_arm_durability: {'codex': {'reverted': 1}}— the mis-attribution, reproducedarm outside the round's set accepted: nobody--round-agentsdeclarationmeta["agents"]logs['legacy-exp'] == ['legacy-exp', 'v2-exp']— legacy passes, v2 skipped: the half-fixed state exactlymember_idfromarm_difftest_partitioned_review.test_multi_agent_arm_set_is_admitted_only_on_a_round_bound_attempt— covers all three reviewer cases (dropped / correctly scheduled / never shrinking), then files from both real seats and asserts a third agent getsarm_not_in_subject_settest_experiment_arm_identity.test_followup_collects_and_evaluates_a_v2_manifest— builds the manifest through the production launcher path (research_v2_arms→_normalize_arm_members), asserts the member log names really are disjoint from the agent names, drivesfollowupto collect+evaluate both a v2 and a legacy experiment, and checks idempotencytest_experiment_arm_identity.test_branch_recovery_resolves_the_v2_member_branch— stubs_gitrather thanarm_diff, so the real branch name is composed and assertedresearch_subjects --selftest— both refusals (unknown arm; a real agent that is an arm of a different round), the ambiguity → binding → resolution path, and binding validation (unknown run, run that targeted a different issue, binding with no filing)The existing round test was asserting the buggy behaviour (
arms == ["gemini","cursor","codex"]from a gemini-only dispatch) and now asserts the fix.Verification
python3 verify.pygreen, and run twice — once here and once from a mirror-shaped tree, per the rule thatcmp-clean is not agreement. The live~/.codex/orchestrator-mirrorwas deliberately not synced:ORCH_MIRRORwas pointed at a scratch path, so the layout check runs while the dispatcher keeps running merged code and CLAUDE.md §1's manual circuit breaker stays intact.Floor re-measured on this base rather than carried over from the pre-merge branch. Dedup findings recorded in the capability ledger
notesforabcd-experimentandpartitioned-reviewper CLAUDE.md §0 — in both cases the concept already existed and this wired or centralised it rather than building anything new.🤖 Generated with Claude Code
Scope
Only the four findings from the #42 CodeRabbit review that were deliberately deferred there because each changes learning-loop semantics. Touches
research_subjects.py,partitioned_review.py,exp_abcd.py,experiment_recovery.py, their two test files, and.verify-floor.json. No new table, no migration, no new event log, no flag flipped, no capability added.Tasks
ORDER BY r.ts DESC LIMIT 1durability inference with a persisted, Brain-validatedfinding_implementedbinding; leave ambiguous targets unresolved and report the drainFINDING_FILEDarms_unprovenfollowupeligibility fromexperiment_members+exp_log_path, and passmember_idthrough branch recovery.verify-floor.jsonon the merge result (405), union-resolving the three conflicts rather than taking a sideAcceptance criteria
verify.pygreen in this checkout: 405 passed, 0 failed, 0 skipped; 84/84 selftests; 5/5 gatesverify.pygreen in the exec-mirror layout (thecmp-clean-is-not-agreement second run): 394 passed + 11 named skips = 405 collected; 84/84 selftests; 5/5 gatesverify.pygreen on CI: 379 passed + 26 named skips = 405 collected; 77/84 selftests; 3/5 gates — no ceiling raisedmainwith the Gate's own commands (identical ruff histograms)ORDER BY r.ts DESC LIMIT 1(AST-verified; the three textual hits are the docstring and comments explaining the defect)Workflow source: review follow-up to #42. Not a new issue — #42's review is the origin, and its three inline threads carry the per-finding detail.