From cb90b4a8e74288b11e9def5fa8949c5faf451144 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Mon, 9 Nov 2026 03:31:40 +0800 Subject: [PATCH 1/2] fix(explore): reuse cited measurements in Yardstick comparisons Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../target-yardstick-stride-decisions-v0.md | 6 ++++ ...get-yardstick-stride-decisions-v0.zh-CN.md | 4 +++ .../capabilities/explore_measurements.ts | 6 +++- .../work_items/yardstick_decision.ts | 17 +++++++--- .../yardstick_decision.test.ts | 34 +++++++++++++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.md b/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.md index 8b4adc4cfa..42ed1609c1 100644 --- a/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.md +++ b/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.md @@ -188,6 +188,12 @@ an obligation to satisfy Stride v2. Replans retain their existing v2 contract. An absent attachment preserves the current decision; an explicit `proxy: null` chooses direct evaluation while historical profiles remain readable. +A current decision can reuse explicitly cited measurements from earlier +findings by the same agent on the linked question. Each profile keeps its own +input and evaluator revisions, and cross-profile comparison still requires the +same candidate pair. Findings with their own Yardstick decision retain their +original work and target scope. + ### 5.3 Co-discovery: invest in a pair The model compares the incumbent pair, direct evaluation and a small relevant diff --git a/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.zh-CN.md b/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.zh-CN.md index d19f45ab96..e4567259c0 100644 --- a/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.zh-CN.md +++ b/docs/architecture/rfcs/target-yardstick-stride-decisions-v0.zh-CN.md @@ -151,6 +151,10 @@ M1 在既有 `explore_result_attachment_v0` 中添加可选 `work_yardstick_deci replan 沿用既有 v2 契约。省略附件保留当前决策;显式 `proxy: null` 选择直接评测, 历史 profile 仍可读取。 +当前决策可以复用同一 Agent 在关联问题中已记录、且本次明确引用的测量。 +各 profile 保留自己的输入和 evaluator 版本,跨 profile 比较仍须对应同一候选对。 +已附带 Yardstick 决策的 finding 继续遵循原工作与目标范围。 + ### 5.3 Co-discovery:投资于组合 模型比较当前组合、直接评测及少量相关替代方案,需要时才扩读有来源绑定的细节。它考虑: diff --git a/loopx/control_plane/capabilities/explore_measurements.ts b/loopx/control_plane/capabilities/explore_measurements.ts index 8c4e470fa7..21daa1d0db 100644 --- a/loopx/control_plane/capabilities/explore_measurements.ts +++ b/loopx/control_plane/capabilities/explore_measurements.ts @@ -104,7 +104,10 @@ function comparisonKey(node: string, metric: ExploreMeasurement): string { * previous best includes earlier declared observations; order is not inferred. * Keep all matching evidence when computing best; presentation limits apply later. */ -export function projectExploreMeasurementFeedback(findings: readonly JsonObject[]): ExploreMeasurementFeedback[] { +export function projectExploreMeasurementFeedback( + findings: readonly JsonObject[], + includeMeasurement?: (finding: JsonObject, measurement: ExploreMeasurement) => boolean, +): ExploreMeasurementFeedback[] { const groups = new Map(); const ordered = findings.filter(finding => finding.measurements !== undefined); // Canonical readers derive this ordinal from append order; it is not stored @@ -124,6 +127,7 @@ export function projectExploreMeasurementFeedback(findings: readonly JsonObject[ // source when its declared execution contradicts its measurements. if (executionMeasurementError(measurements, finding.execution_observation) !== null) continue; for (const measurement of measurements) { + if (includeMeasurement && !includeMeasurement(finding, measurement)) continue; const key = comparisonKey(node, measurement), prior = groups.get(key); const previous = prior?.best ?? null; const point = measurement.value === null ? null : {finding_id: id, diff --git a/loopx/control_plane/work_items/yardstick_decision.ts b/loopx/control_plane/work_items/yardstick_decision.ts index b27d24953a..222e6e34a0 100644 --- a/loopx/control_plane/work_items/yardstick_decision.ts +++ b/loopx/control_plane/work_items/yardstick_decision.ts @@ -174,13 +174,22 @@ export function projectWorkYardstickCue(params: JsonObject): JsonObject | null { row.node_id === source.node_id && profiles.some(p => matchesProfile(row, p))); // An earlier target can share the same evaluator profile and work identity. // Filter source findings before ranking so its best cannot displace a legal pair. + const cited = new Set(counter); const scopedFindings = findings.filter(row => { + if (row.node_id !== source.node_id) return false; + // A decision can cite the agent's earlier measurements on this linked + // question without changing their provenance or assigning earlier work. + if (row.work_yardstick === undefined) { + return cited.size > 0 && row.agent_id === capture.binding.agent_id && Array.isArray(row.evidence_refs) + && row.evidence_refs.some(ref => typeof ref === "string" && cited.has(ref)); + } const target = (row.work_yardstick as WorkYardstickCapture | undefined)?.decision?.target; - return row.node_id === source.node_id && belongsToWork(row, params) && target !== undefined - && target.summary === decision.target.summary && target.scope === decision.target.scope - && target.acceptance_ref === decision.target.acceptance_ref; + return belongsToWork(row, params) && target !== undefined && target.summary === decision.target.summary + && target.scope === decision.target.scope && target.acceptance_ref === decision.target.acceptance_ref; }); - const searchComparison = measuredSearchComparison(projectExploreMeasurementFeedback(scopedFindings), decision); + const scopedFeedback = projectExploreMeasurementFeedback(scopedFindings, (row, measurement) => + row.work_yardstick !== undefined || cited.has(measurement.evidence_ref)); + const searchComparison = measuredSearchComparison(scopedFeedback, decision); return {source_finding_id: source.finding_id, node_id: source.node_id, binding: capture.binding, ...decision, measurement_feedback: feedback, ...(searchComparison ? {search_comparison: searchComparison} : {}), diff --git a/tests/control_plane_ts/yardstick_decision.test.ts b/tests/control_plane_ts/yardstick_decision.test.ts index 2b406e6e08..02dd35d4a4 100644 --- a/tests/control_plane_ts/yardstick_decision.test.ts +++ b/tests/control_plane_ts/yardstick_decision.test.ts @@ -102,6 +102,40 @@ test("goal and proxy join the same two candidates regardless of declared order a } }); +test("a current decision can reuse explicitly cited measurements written before its separate outcome", () => { + const preview = {...proxy, input_revision: "preview-inputs-v2"}; + const cited = ["result:preview-a", "result:preview-b"]; + const declared = {...decision, proxy: preview, counterevidence_refs: cited}; + const source = {...finding, recorded_order: 2, work_yardstick: {decision: declared, binding}, + evidence_refs: [...cited, "result:goal-a", "result:goal-b"], measurements: [ + {...authoritative, value: 20, candidate_ref: "candidate:a", evidence_ref: "result:goal-a"}, + {...authoritative, value: 14, candidate_ref: "candidate:b", evidence_ref: "result:goal-b"}, + ]}; + const recorded = {finding_id: "recorded-preview", node_id: source.node_id, agent_id: binding.agent_id, + recorded_order: 1, last_updated_at: "2026-01-01T00:00:00Z", + evidence_refs: [...cited, "result:preview-uncited"], measurements: [ + {...preview, value: 6, candidate_ref: "candidate:a", evidence_ref: cited[0]!}, + {...preview, value: 8, candidate_ref: "candidate:b", evidence_ref: cited[1]!}, + {...preview, value: 99, candidate_ref: "candidate:uncited", evidence_ref: "result:preview-uncited"}, + ]}; + const project = (row = recorded, current = source) => projectWorkYardstickCue({...binding, enabled: true, + findings: [current, row], measurement_feedback: projectExploreMeasurementFeedback([current, row])})!.search_comparison; + const comparison = project(); + assert.equal(comparison.goal.preferred_candidate_ref, "candidate:a"); + assert.equal(comparison.proxy?.preferred_candidate_ref, "candidate:b"); + assert.equal(comparison.proxy_order_reversed, true); + + assert.equal(project({...recorded, agent_id: "peer"}).proxy, undefined); + assert.equal(project({...recorded, node_id: "different-question"}).proxy, undefined); + const differentInputs = recorded.measurements.map(row => ({...row, input_revision: "different-preview-inputs"})); + assert.equal(project({...recorded, measurements: differentInputs}).proxy, undefined); + const partial = {...source, work_yardstick: {binding, decision: {...declared, counterevidence_refs: [cited[0]!]}}}; + assert.equal(project(recorded, partial).proxy, undefined); + const differentTarget = {...recorded, work_yardstick: {binding, decision: {...declared, + target: {...declared.target, acceptance_ref: "acceptance:another-target"}}}}; + assert.equal(project(differentTarget).proxy, undefined); +}); + test("only strictly older committed replay preserves a stale predecessor as history", () => { const params = {enabled: true, decision, binding, findings: [finding]}; assert.deepEqual(validateWorkYardstickContinuation({...params, From cde7f7f3189ddb18f7587544c03af2b1ef8b6cca Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Mon, 9 Nov 2026 05:07:26 +0800 Subject: [PATCH 2/2] Keep Yardstick citations within the decision's recorded history Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../work_items/yardstick_decision.ts | 7 ++- .../control_plane/test_work_yardstick_cli.py | 51 +++++++++++++++++++ .../yardstick_decision.test.ts | 10 ++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/loopx/control_plane/work_items/yardstick_decision.ts b/loopx/control_plane/work_items/yardstick_decision.ts index 643fbeae66..a70ee43d70 100644 --- a/loopx/control_plane/work_items/yardstick_decision.ts +++ b/loopx/control_plane/work_items/yardstick_decision.ts @@ -183,12 +183,17 @@ export function projectWorkYardstickCue(params: JsonObject): JsonObject | null { // An earlier target can share the same evaluator profile and work identity. // Filter source findings before ranking so its best cannot displace a legal pair. const cited = new Set(counter); + const sourceOrder = source.recorded_order; const scopedFindings = findings.filter(row => { if (row.node_id !== source.node_id) return false; // A decision can cite the agent's earlier measurements on this linked // question without changing their provenance or assigning earlier work. if (row.work_yardstick === undefined) { - return cited.size > 0 && row.agent_id === capture.binding.agent_id && Array.isArray(row.evidence_refs) + const order = row.recorded_order; + // A repeated reference appended later cannot change the decision's evidence. + return cited.size > 0 && typeof sourceOrder === "number" && Number.isSafeInteger(sourceOrder) + && typeof order === "number" && Number.isSafeInteger(order) && order >= 0 && order < sourceOrder + && row.agent_id === capture.binding.agent_id && Array.isArray(row.evidence_refs) && row.evidence_refs.some(ref => typeof ref === "string" && cited.has(ref)); } const target = (row.work_yardstick as WorkYardstickCapture | undefined)?.decision?.target; diff --git a/tests/control_plane/test_work_yardstick_cli.py b/tests/control_plane/test_work_yardstick_cli.py index 51712ec09f..cd1c91b84c 100644 --- a/tests/control_plane/test_work_yardstick_cli.py +++ b/tests/control_plane/test_work_yardstick_cli.py @@ -554,6 +554,57 @@ def dictionary_encode(items): assert "work_yardsticks" not in disabled and disabled["measurement_feedback"] == before +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_later_plain_measurement_cannot_replace_cited_decision_evidence(tmp_path, provider, monkeypatch): + project, runtime, path = setup(tmp_path, active=True, provider=provider, write_scope=wide_write_scope()) + refs = ["result:preview-a", "result:preview-b"] + plain = attachment() + chosen = plain.pop("work_yardstick_decision") + plain["evidence_refs"] = refs + plain["measurements"] = [ + {**chosen["proxy"], "candidate_ref": f"candidate:{candidate}", + "evidence_ref": ref, "value": value} + for candidate, ref, value in zip(("a", "b"), refs, (8, 6)) + ] + + def retain(turn_id, payload): + evidence = tmp_path / f"{turn_id}.json" + evidence.write_text(json.dumps(payload)) + receipt = refresh(project, runtime, path, turn_id, evidence, "--delivery-boundary", "in_flight_continuation") + result = receipt["explore_result_delivery"] + assert result["ok"] + run(project, runtime, path, *_projected_cli_args(receipt["settlement_owed"]["command"], + turn_instance_id=turn_id)) + return result + + first = retain("previous-proxy", plain) + selected = attachment() + selected.update(node_id=first["node_id"], evidence_refs=[*refs, "result:outcome-a", "result:outcome-b"]) + selected["work_yardstick_decision"]["counterevidence_refs"] = refs + selected["measurements"] = [ + {**chosen["authoritative"], "candidate_ref": f"candidate:{candidate}", + "evidence_ref": f"result:outcome-{candidate}", "value": value} + for candidate, value in (("a", 20), ("b", 14)) + ] + result = retain("current-decision", selected) + before = context(runtime, path)["graph"]["work_yardsticks"][0] + assert before["search_comparison"]["proxy_order_reversed"] is False + + late = {**plain, "node_id": first["node_id"], "evidence_refs": refs[1:], + "measurements": [{**plain["measurements"][1], "value": 99}]} + retain("later-duplicate-reference", late) + after = context(runtime, path)["graph"]["work_yardsticks"][0] + assert after["source_finding_id"] == result["finding_id"] + assert after["search_comparison"] == before["search_comparison"] + turn = binding("inspect-original-citation") + capture = tmp_path / "capture" + run(project, runtime, path, "quota", "should-run", "--codex-app", *turn, + "--decision-output-dir", str(capture), "--context-delivery", "on", "--delta-delivery", "off") + page = starting_page(project, runtime, path, turn, capture / "decision.json", monkeypatch) + assert "proxy reverses the observed goal order" not in page["content"] + assert "candidate:a" in page["content"] + + def test_sqlite_current_work_to_ordinary_turn_to_concrete_action_and_readback(tmp_path): project, runtime, path = setup(tmp_path) source = tmp_path / "capture.json" diff --git a/tests/control_plane_ts/yardstick_decision.test.ts b/tests/control_plane_ts/yardstick_decision.test.ts index 572e1a8271..55e075bf1e 100644 --- a/tests/control_plane_ts/yardstick_decision.test.ts +++ b/tests/control_plane_ts/yardstick_decision.test.ts @@ -164,6 +164,16 @@ test("a current decision can reuse explicitly cited measurements written before const differentTarget = {...recorded, work_yardstick: {binding, decision: {...declared, target: {...declared.target, acceptance_ref: "acceptance:another-target"}}}}; assert.equal(project(differentTarget).proxy, undefined); + + const later = {...recorded, finding_id: "later-preview", recorded_order: 3, + // Claimed observation time is older. Only recorded order binds the citation. + last_updated_at: "2025-12-31T23:59:59Z", evidence_refs: [cited[1]!], + measurements: [{...recorded.measurements[1]!, value: 1}]}; + const after = projectWorkYardstickCue({...binding, enabled: true, findings: [source, later, recorded]})!; + assert.deepEqual(after.search_comparison, comparison); + const unknown = projectWorkYardstickCue({...binding, enabled: true, + findings: [source, {...recorded, recorded_order: undefined}]})!; + assert.equal(unknown.search_comparison.proxy, undefined); }); test("only strictly older committed replay preserves a stale predecessor as history", () => {