Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 61 additions & 20 deletions docs/design/dsh5-10-replay-preference-rows.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DSH5-10: replay-grounded preference rows from undo/redo history (SLM-418)

**Status:** partial slice, in progress (fourth increment).
**Status:** partial slice, in progress (fifth increment).
**Claim class:** `wiring`.
**Honest verdict:** not yet dispositioned -- this PR extends a scoped
subset, not the full issue.
Expand All @@ -13,11 +13,13 @@ SFT/preference training against four context-view baselines, and (4)
held-out measurement of action/operator/argument/reference/branch accuracy,
calibration, and CAP0/CAP1/CAP2 retention.

This slice adds the sixth pattern, **merge-success**, plus an explicit,
tested disposition for **merge-conflict** (the pattern's other named half):
conflict is honestly *not* modeled as a preference row (see "Fourth slice
(v5)" below for why). Only **pronoun/focus follow-ups** remain fully
unattempted after this PR.
This slice adds the seventh and final named pattern, **pronoun-focus-
followup** (see "Fifth slice (v6)" below), bringing extraction coverage to
7 of 7. All seven named patterns from the issue's own list now extract and
replay-verify. What remains is the issue's separate, still fully unattempted
training/measurement scope: SFT/preference training against the
DSH3-selected policy/control heads, the four-baseline comparison, held-out
benefit measurement, and turn-depth/context-view ablations.

## What this PR delivers

Expand Down Expand Up @@ -99,12 +101,6 @@ unattempted after this PR.

Per the issue's own scope, not attempted here:

* **Pronoun/focus follow-ups** -- the one remaining named pattern. No
representation for ambiguous natural-language reference resolution (e.g.
"it"/"that one") exists anywhere in `ConversationTraceV1`,
`ReferenceTableV1`, or the legal-set machinery this module builds on; this
is new machinery, not an extraction path over an existing primitive, and
is left for follow-on work.
* **Merge conflict as a preference row.** Deliberately not attempted --
see "Fourth slice (v5)" for the honesty argument. This is a considered
scope decision, not an oversight: modeling it would require inventing a
Expand All @@ -129,11 +125,10 @@ evidence for the row-extraction primitive only.
Unlike SLM-336 (AP-035) or SLM-419 (DSH5-11), SLM-418's own prerequisites
(DSH3 policy/control heads, the conversation/collapse/legal-set substrate)
are already merged and available -- there is no unmet upstream gate here.
The remaining scope is genuinely large (a new pronoun/focus representation,
plus training + held-out evaluation across a five-baseline, multi-metric
matrix) and is left for follow-on work rather than rushed to a false "Done."
The issue should stay open against the pattern and training/evaluation work
enumerated above.
The remaining scope is genuinely large (training + held-out evaluation
across a five-baseline, multi-metric matrix) and is left for follow-on work
rather than rushed to a false "Done." The issue should stay open against the
training/evaluation work enumerated above.

## Review fixes (v2)

Expand Down Expand Up @@ -245,12 +240,58 @@ enumerated above.
snapshot is untouched, staying immutable point-in-time evidence from
before this slice landed.

## Fifth slice (v6)

* Added `pronoun_focus_followup` to `ReplayPreferenceRelation` -- the last
of the issue's seven named patterns. Unlike merge-success, this **is**
another branch inside `extract_replay_preference_rows`'s existing
turn-pair scan loop: a second consecutive `AST_EDIT` turn.
* **Focus**, the module's only concept for it, is never a transcript
pronoun or a semantic descriptor: it is `_touched_refs`, the exact
`OperatorRef` values the *immediately preceding* `AST_EDIT` turn's own
verified `OperatorApplicationV1.arguments` bound. A pair of consecutive
edits is classified `PRONOUN_FOCUS_FOLLOWUP` only when (1) that focus set
is non-empty (a zero-argument operator, like the base fixture every other
pattern in this module uses, never establishes one), (2) the following
edit's own bound arguments intersect it (the user kept operating on a ref
they had just touched), and (3) the exact legal set at the shared decision
state (`enumerate_operator_legal_set`, matched to the following turn's
recorded application by `operator_fingerprint` and bound `arguments`)
contains a **sibling**: another legal action for the *same operator* whose
own bound refs do **not** overlap the focus set -- a genuinely available,
equally legal "switch to something else" the user did not take. Without a
real sibling candidate, no row is emitted, matching every other pattern's
convention that undo/redo/checkout/continued-focus is never asserted
preferred by default.
* This directly answers the issue's own "ambiguous sibling" and "pronoun
focus" matrix rows: the pattern only ever fires when a second, disjoint
legal target genuinely existed at that state, and the row records that
the user's implicit "it" continuation was chosen over it.
* Deliberately does **not** attempt: switching to an explicit, different,
legal reference (the issue's "exact named reference" matrix case) is
honestly left unrowed rather than asserted a correction -- there is no
"user was wrong" signal to record when they simply named something else.
Multi-argument operators, transaction-commit turns, and any true
natural-language pronoun/reference-resolution machinery over
`ReferenceTableV1` remain out of scope; this slice is DAG-argument-set
overlap only, exactly as adversarial control requires ("text history
cannot reconstruct a different state than the DAG").
* `dsl.operators.replay_preference` bumped v5 -> v6 in
`src/slm_training/resources/versions.json`.
* Corrected the hardcoded SLM-418 evidence string in
`src/slm_training/evals/advanced_operator_disposition.py` (previously "6
of 7"; now "7 of 7", with the remaining-gap claim narrowed from "1 of 7
(pronoun/focus)" to the issue's training/measurement scope only) via a
`no-bump:` history note on `evals.advanced_operator_disposition` -- no
disposition logic or schema changed, and the already-published
`docs/design/dsh5-12-advanced-operator-disposition-20260727-local/`
snapshot is untouched, staying immutable point-in-time evidence from
before this slice landed.

## Reproducibility

```bash
NODE_OPTIONS= pytest -q tests/test_dsl/test_replay_preference.py tests/test_dsl/test_operator_merge.py tests/test_dsl/test_operator_conversation.py tests/test_evals/test_advanced_operator_disposition.py tests/test_scripts/test_validate_advanced_operator_disposition.py
```

Result (this PR, sandboxed run with `NODE_OPTIONS` cleared -- the ambient
`--import tsx` flag is rejected by this Node 22 build, unrelated to this
change): `57 passed`.
Result (this PR, real run in a fresh `.venv` -- Python 3.12, `pip install -e ".[dev,grammar]"`, plus `NODE_OPTIONS= npm ci` in `src/apps/openui_bridge` for the G2/G8 schema-oracle gates the pack authority requires; the ambient `--import tsx` `NODE_OPTIONS` is rejected by this Node 22 build both for `npm ci` and for `pytest`, unrelated to this change): `61 passed`. Also verified: `ruff check` clean on every changed file; `python -m scripts.verify_version_stamps --check --base origin/claude/great-dirac-v82ph9` -- `ok (2 component(s) touched)`; `python -m scripts.repo_policy` -- `ok`; `python -m scripts.verify_decode_invariants` -- clean.
132 changes: 113 additions & 19 deletions src/slm_training/dsl/operators/replay_preference.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
"""SLM-418 (DSH5-10): replay-grounded preference rows from undo/redo history.

Builds versioned preference rows over one exact input state from six
Builds versioned preference rows over one exact input state from all seven
verified conversation patterns -- edit-then-undo, undo-then-redo, partial
rollback (a second or later consecutive undo, chosen over redo/checkout/edit
alternatives), checkout-another-state, fork-then-choose-one-branch, and
merge-success -- where the chosen and rejected control-or-operator actions
are checked against the exact legal set available at that state
(``enumerate_operator_legal_set``), never against transcript text.

This is an honestly partial slice of SLM-418. It does not implement:
pronoun/focus follow-up patterns; it does not train an SFT/preference
variant, measure held-out benefit, or produce turn-depth / context-view
ablations. See ``docs/design/dsh5-10-replay-preference-rows.md`` for the
full disposition and the remaining scope.
alternatives), checkout-another-state, fork-then-choose-one-branch,
merge-success, and pronoun-focus-followup -- where the chosen and rejected
control-or-operator actions are checked against the exact legal set
available at that state (``enumerate_operator_legal_set``), never against
transcript text.

This is an honestly partial slice of SLM-418. All seven named extraction
patterns now exist, but it does not train an SFT/preference variant,
measure held-out benefit, or produce turn-depth / context-view ablations.
See ``docs/design/dsh5-10-replay-preference-rows.md`` for the full
disposition and the remaining scope.

Merge conflict (the other half of the issue's "merge success/conflict"
pattern) is deliberately *not* modeled as a preference row here: unlike
Expand Down Expand Up @@ -45,8 +46,12 @@
ConversationOperation,
ConversationTraceV1,
)
from slm_training.dsl.operators.contracts import ApplicationProvenanceV1
from slm_training.dsl.operators.contracts import (
ApplicationProvenanceV1,
OperatorApplicationV1,
)
from slm_training.dsl.operators.legal_set import (
LegalOperatorActionV1,
OperatorLegalSetV1,
enumerate_operator_legal_set,
)
Expand All @@ -69,6 +74,7 @@ class ReplayPreferenceRelation(str, Enum):
CHECKOUT_ANOTHER_STATE = "checkout_another_state"
FORK_THEN_CHOOSE_ONE_BRANCH = "fork_then_choose_one_branch"
MERGE_SUCCESS = "merge_success"
PRONOUN_FOCUS_FOLLOWUP = "pronoun_focus_followup"


@dataclass(frozen=True)
Expand Down Expand Up @@ -203,22 +209,42 @@ def _pick_rejected(legal_set: OperatorLegalSetV1, chosen: str) -> str | None:
return candidates[0] if candidates else None


def _touched_refs(application: OperatorApplicationV1) -> frozenset:
"""The opaque refs one ``AST_EDIT`` application actually bound as arguments.

This is the module's only notion of "focus": never a transcript pronoun,
never a semantic descriptor, just the exact ``OperatorRef`` values the
prior turn's own verified application used -- so pronoun-focus
classification stays grounded in the DAG, per the issue's own adversarial
control that text history cannot reconstruct a different state than the
DAG.
"""
return frozenset(argument.value for argument in application.arguments)


def _action_refs(action: LegalOperatorActionV1) -> frozenset:
return frozenset(argument.value for argument in action.arguments)


def extract_replay_preference_rows(
trace: ConversationTraceV1,
*,
pack: DslPack,
library: OperatorLibraryV1,
provenance_for: ProvenanceFactory,
) -> OperatorEventMemoryReportV1:
"""Scan ``trace.turns`` for five replay-grounded preference patterns.
"""Scan ``trace.turns`` for six replay-grounded preference patterns.

edit-then-undo, undo-then-redo, partial-rollback (a second or later
consecutive undo), checkout-another-state, and fork-then-choose-one-
branch (a checkout that crosses a branch boundary a prior ``FORK`` turn
opened). Each match produces one row whose chosen and rejected actions
are both verified members of the exact legal set at the shared input
state. A row is only emitted when an unchosen alternative actually
exists in that legal set -- undo/redo/checkout is never asserted
consecutive undo), checkout-another-state, fork-then-choose-one-branch
(a checkout that crosses a branch boundary a prior ``FORK`` turn opened),
and pronoun-focus-followup (a second ``AST_EDIT`` that continues
operating on the same ref its immediate predecessor touched, over an
equally legal same-operator action targeting a different, untouched
ref). Each match produces one row whose chosen and rejected actions are
both verified members of the exact legal set at the shared input state.
A row is only emitted when an unchosen alternative actually exists in
that legal set -- undo/redo/checkout/continued-focus is never asserted
preferred by default.
"""
rows: list[OperatorReplayPreferenceRowV1] = []
Expand Down Expand Up @@ -308,6 +334,74 @@ def extract_replay_preference_rows(
)
)

if (
current.operation is ConversationOperation.AST_EDIT
and following.operation is ConversationOperation.AST_EDIT
and following.input_state_id == current.output_state_id
):
assert current.application is not None
assert following.application is not None
focus_refs = _touched_refs(current.application)
chosen_refs = _touched_refs(following.application)
# No focus was ever established (e.g. a zero-argument operator),
# or the follow-up shares nothing with it: not this pattern.
if focus_refs and (focus_refs & chosen_refs):
decision_state_id = following.input_state_id
legal_set = _legal_set_at(
trace,
pack=pack,
library=library,
state_id=decision_state_id,
provenance_for=provenance_for,
)
entry = next(
(
candidate
for candidate in legal_set.entries
if candidate.operator_fingerprint
== following.application.operator_fingerprint
),
None,
)
chosen_match = (
next(
(
action
for action in entry.legal_actions
if action.arguments == following.application.arguments
),
None,
)
if entry is not None
else None
)
if entry is not None and chosen_match is not None:
sibling_candidates = sorted(
(
action
for action in entry.legal_actions
if action.serialized != chosen_match.serialized
and not (_action_refs(action) & focus_refs)
),
key=lambda action: action.serialized,
)
if sibling_candidates:
rows.append(
OperatorReplayPreferenceRowV1(
input_state_id=decision_state_id,
chosen_action=chosen_match.serialized,
rejected_action=sibling_candidates[0].serialized,
chosen_output_state_id=following.output_state_id,
semantic_relation=(
ReplayPreferenceRelation.PRONOUN_FOCUS_FOLLOWUP
),
correction_reason=(
"user_continued_implicit_focus_over_sibling_target"
),
legal_set_fingerprint=legal_set.fingerprint,
)
)

fork_branch_digests = {
trace.node(turn.output_state_id).branch_digest
for turn in turns
Expand Down
4 changes: 2 additions & 2 deletions src/slm_training/evals/advanced_operator_disposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ def build_advanced_operator_disposition(
component_id="dsl.operators.replay_preference",
version_stamp=component_version_stamps["SLM-418"],
suite="tests/test_dsl/test_replay_preference.py",
result="6 of 7 named patterns (edit-then-undo, undo-then-redo, partial-rollback, checkout-another-state, fork-then-choose-one-branch, merge-success) extract and replay-verify; partial slice",
result="7 of 7 named patterns (edit-then-undo, undo-then-redo, partial-rollback, checkout-another-state, fork-then-choose-one-branch, merge-success, pronoun-focus-followup) extract and replay-verify; partial slice",
),
_fixture_evidence(
evidence_id="SLM-419.dsh5-11",
Expand Down Expand Up @@ -780,7 +780,7 @@ def dims(

dv, dr = dims(
(S, "The delivered slice (edit-then-undo, undo-then-redo) extracts rows whose chosen/rejected actions are verified legal-set members and whose chosen_output_state matches independent replay."),
(R, "1 of 7 named patterns (pronoun/focus follow-ups) is not attempted; merge conflict is honestly scoped out as a non-row legality constraint rather than a row. No SFT/preference training, no four-baseline comparison, no held-out benefit measurement exists."),
(R, "All 7 of 7 named extraction patterns now extract and replay-verify; merge conflict is honestly scoped out as a non-row legality constraint rather than a row. No SFT/preference training, no four-baseline comparison, no held-out benefit measurement exists."),
(R, "No CAP0/CAP1/CAP2 retention or calibration measurement exists yet for rows sourced from this module."),
(R, "No turn-depth or context-view ablation exists; OperatorEventMemoryReportV1 is row counts only."),
)
Expand Down
12 changes: 11 additions & 1 deletion src/slm_training/resources/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1091,13 +1091,18 @@
]
},
"dsl.operators.replay_preference": {
"version": "v5",
"version": "v6",
"kind": "harness",
"paths": [
"src/slm_training/dsl/operators/replay_preference.py",
"tests/test_dsl/test_replay_preference.py"
],
"history": [
{
"version": "v6",
"date": "2026-07-27",
"note": "SLM-418 (DSH5-10) fifth slice: add pronoun_focus_followup, the last of the issue's seven named patterns, as a new branch inside extract_replay_preference_rows's existing turn-pair scan (a second consecutive AST_EDIT turn). Focus is never a transcript pronoun or semantic descriptor -- it is _touched_refs, the exact OperatorRef values the immediately preceding AST_EDIT turn's own verified OperatorApplicationV1.arguments bound. The pair is classified pronoun_focus_followup only when that focus set is non-empty, the following edit's own bound arguments intersect it, and the exact legal set at the shared decision state contains a sibling -- another legal action for the same operator whose bound refs do not overlap the focus set, i.e. a genuinely available equally-legal switch the user did not take. Switching to a different, legal, explicit reference (no focus overlap) is honestly left unrowed rather than asserted a correction. Coverage now 7 of 7 named extraction patterns; only the issue's SFT/preference training, four-baseline comparison, held-out benefit measurement, and turn-depth/context-view ablations remain"
},
{
"version": "v5",
"date": "2026-07-27",
Expand Down Expand Up @@ -10885,6 +10890,11 @@
"docs/design/dsh5-12-advanced-operator-disposition-20260727-local/"
],
"history": [
{
"version": "v2",
"date": "2026-07-27",
"note": "no-bump: correct the hardcoded SLM-418/DSH5-10 evidence string again (dsl.operators.replay_preference is now on its own v6, with pronoun-focus-followup completing coverage to 7 of 7 named patterns; only the issue's SFT/preference training, four-baseline comparison, held-out benefit measurement, and turn-depth/context-view ablations remain) so a freshly-generated disposition would not understate already-landed replay-preference-row coverage; no change to disposition logic, schema, or the already-published dsh5-12-*-20260727-local snapshot, which stays immutable point-in-time evidence"
},
{
"version": "v2",
"date": "2026-07-27",
Expand Down
Loading
Loading