From d48cce3f81a0bb6320200ecbde34da5b87fcf5ec Mon Sep 17 00:00:00 2001 From: jiachun <2609024259@qq.com> Date: Tue, 18 Aug 2026 16:18:27 +0800 Subject: [PATCH] Remove unconditional stateful-turn truncation --- README.md | 11 ++++++----- changelog.md | 13 +++++++++++++ src/swe_data_processing/audit.py | 21 +++++++-------------- tests/test_audit.py | 31 ++++++++++++++++++++++++++++--- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f882e90..5a1539c 100644 --- a/README.md +++ b/README.md @@ -219,11 +219,12 @@ Training use is tracked separately as `SFT_FULL`, `SFT_STEP_ONLY`, score. For a complete trajectory, the quality call also sees its final model patch. A -truncated prefix never receives that suffix-derived patch. Every non-success -trajectory (`resolved=0` or `-1`) is capped before its first statically detected -state-changing assistant turn. The effective boundary is the earlier of this -local cap and the GLM boundary. This intentionally prefers a shorter, safer -investigation trace. +truncated prefix never receives that suffix-derived patch. State-changing turns +are recorded as audit telemetry but are not automatic boundaries: writing code +is not itself an error, and recovered experiments are useful process data. The +effective boundary is the earliest unrecovered major or critical behavior found +by the boundary stage. The isolated quality stage rejects a retained prefix that +still contains an unrecovered severe problem. Successful trajectories skip boundary selection and are scored as complete trajectories. The command remains simple: diff --git a/changelog.md b/changelog.md index 415549e..04adc06 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,19 @@ This file records strategy changes that materially affect dataset decisions or training-data semantics. Generated audit manifests are not treated as stable API contracts. +## Unreleased + +### Strategy correction + +- Removed the unconditional cutoff before the first state-changing turn for + failed and unverified trajectories. A write or edit is not evidence of an + error by itself, and this cap discarded normal implementation, recovery, and + verification work. +- The semantic boundary now remains the earliest unrecovered major or critical + assistant behavior selected by the boundary stage. The isolated prefix-quality + stage remains the safety gate. The first stateful turn is retained as telemetry + for paired evaluation against the former policy. + ## 2.0.0 - 2026-08-07 ### Strategy diff --git a/src/swe_data_processing/audit.py b/src/swe_data_processing/audit.py index b29d798..b315fa3 100644 --- a/src/swe_data_processing/audit.py +++ b/src/swe_data_processing/audit.py @@ -72,30 +72,23 @@ def materialize_prefix( def effective_boundary_policy( record: dict[str, Any], model_result: dict[str, Any] ) -> dict[str, Any]: - """Choose a conservative effective boundary for one trajectory. + """Use the semantic boundary while retaining the old cap as telemetry. - A failed or unverified record may not retain a statically detected - state-changing assistant turn. This deliberately favors a shorter - investigation prefix over a late semantic boundary that could retain a bad - implementation. + A state-changing turn is not evidence of an error by itself. Normal coding, + failed experiments, recovery, and verification are useful process data, so + the first stateful turn must not become an unconditional cutoff. Prefix + safety is enforced independently by the isolated quality stage. """ model_boundary = model_result["truncate_before_turn"] first_stateful = min( extract_static_signals(record)["stateful_turns"], default=None ) - candidates = [] if model_result["decision"] == "TRUNCATE": - candidates.append((model_boundary, "MODEL_BOUNDARY")) - if int(record.get("resolved", -1)) != 1 and first_stateful is not None: - candidates.append((first_stateful, "NON_SUCCESS_FIRST_STATEFUL_CAP")) - - if candidates: - boundary, source = min(candidates, key=lambda item: item[0]) return { "decision": "TRUNCATE", - "truncate_before_turn": boundary, - "source": source, + "truncate_before_turn": model_boundary, + "source": "MODEL_BOUNDARY", "model_truncate_before_turn": model_boundary, "first_stateful_turn": first_stateful, } diff --git a/tests/test_audit.py b/tests/test_audit.py index fb5f83d..8959ad5 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -140,7 +140,7 @@ def test_unrecovered_major_issue_is_locally_invalid() -> None: @pytest.mark.parametrize("resolved", [0, -1]) -def test_non_success_outcome_is_capped_before_first_stateful_turn(resolved: int) -> None: +def test_non_success_outcome_is_not_capped_at_first_stateful_turn(resolved: int) -> None: record = _record() record["resolved"] = resolved record["tools"] = [ @@ -159,9 +159,34 @@ def test_non_success_outcome_is_capped_before_first_stateful_turn(resolved: int) result = _boundary() result.update(decision="KEEP_FULL", truncate_before_turn=None) policy = effective_boundary_policy(record, result) + assert policy["decision"] == "KEEP_FULL" + assert policy["truncate_before_turn"] is None + assert policy["source"] == "MODEL_DECISION" + assert policy["first_stateful_turn"] == 3 + + +def test_semantic_boundary_is_not_replaced_by_earlier_stateful_turn() -> None: + record = _record() + record["resolved"] = 0 + record["tools"] = [ + {"type": "function", "function": {"name": "str_replace_editor"}} + ] + record["trajectory"][2]["tool_calls"] = [ + { + "id": "call-1", + "type": "function", + "function": { + "name": "str_replace_editor", + "arguments": '{"command":"str_replace","path":"src/a.py"}', + }, + } + ] + result = _boundary() + policy = effective_boundary_policy(record, result) assert policy["decision"] == "TRUNCATE" - assert policy["truncate_before_turn"] == 3 - assert policy["source"] == "NON_SUCCESS_FIRST_STATEFUL_CAP" + assert policy["truncate_before_turn"] == 5 + assert policy["source"] == "MODEL_BOUNDARY" + assert policy["first_stateful_turn"] == 3 def test_boundary_payload_hides_outcome_and_patch_metadata() -> None: