SLM-418: first step onto the DSH3 typed policy target (DSH5-10) - #1127
Conversation
Seventh slice, stacked on #1124 (the sixth slice's row->PreferencePair renderer). Adds scripts/build_replay_preference_pairs.py: builds one small, deterministic, honestly-labeled fixture_or_scratch conversation exercising three of the seven named patterns (edit-then-undo, undo-then-redo, checkout-another-state), extracts rows, renders them, and writes a real pairs.jsonl -- the first real, on-disk pairs corpus this feature line has ever produced. 2 of 3 rows render; undo_then_redo never does, for a structural reason, not a bug: for any deterministic zero-argument operator, redo and reapplying the same operator at the same input state are, by construction, identical text, so the renderer's own dedup guard correctly declines rather than emitting a self-contradictory pair. Confirmed (again) while scoping this slice: no real captured ConversationTraceV1 corpus exists anywhere in this repo, and no harness ingests one -- build_symbolic_operator_corpus synthesizes traces combinatorially from gold DSL records, it doesn't read captured usage. Also ran the first real end-to-end chain using DSH5-10 rows: a scratch SFT checkpoint (wf_smoke_v2, seed 0, 8 steps -- last_loss identical to the 16+ prior independently-verified rows in the smoke-loop ledger, confirming this is the same deterministic artifact those rows already verified), the new demo pairs corpus, and one bounded scripts/train_preference.py train call against it (6 steps, 2 pairs, last_loss=1.077). Both well under MAX_RUN_MINUTES=3. Still not a training or held-out-benefit claim: n_pairs=2 on a scratch fixture says the pipeline runs end to end for real, nothing about whether the signal helps the model or should train the DSH3-selected policy head the issue actually asks about. harness.preference.replay_pairs bumped v1 -> v2 (adds the new script+test to its watched paths). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDctCUvBtGrdMvgNd34oHR
Eighth slice, stacked on #1125 (the seventh slice's demo trace + first real training run). Adds build_demo_merge_scenario to scripts/build_replay_preference_pairs.py: a second scratch fixture (two branches forked from a shared base, editing disjoint node refs), mirroring the exact disjoint-target shape tests/test_dsl/test_operator_merge.py already verifies merges cleanly, replayably, and order-invariantly. Reaches merge_success, the one named pattern the seventh slice's single-trace corpus structurally cannot (extract_merge_preference_row never operates on a shared ConversationTraceV1). main() now combines both sources into one report and one pairs.jsonl: 4 rows (3 trace-scan + 1 merge_success), 3 render -- undo_then_redo is still the only drop, for the same structural reason the seventh slice documented. Reran the full training chain against the richer corpus: same deterministic SFT checkpoint (last_loss=32.610084533691406, matching every prior wf_smoke_v2/seed-0/8-step row), preference training now over 3 pairs instead of 2 (9 steps, last_loss=0.531). Still not a training or held-out-benefit claim -- a larger, more structurally diverse scratch corpus, not evidence the signal helps the model. harness.preference.replay_pairs bumped v2 -> v3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDctCUvBtGrdMvgNd34oHR
Ninth slice, stacked on #1126. Every prior slice since the sixth has flagged the same gap: the TwoTower PreferencePair path (#1124-#1126) exists only for tooling compatibility -- the disposition doc's own remaining-scope note names typed_operator_policy.py's TypedOperatorPolicyScorer, not TwoTower, as the issue's actual training target. This slice takes the first real step onto it, and honestly narrows what's reachable there. Adds src/slm_training/harnesses/experiments/argument_preference.py: build_argument_preference_example renders only pronoun_focus_followup rows into a TypedOperatorArgumentPreferenceExampleV1 -- the one named pattern whose chosen/rejected actions are the same operator with a differing argument for the same slot. The other six patterns are history controls (undo/redo/checkout) or merge, none of which has a row in OperatorPolicyInputV1.action_rows (built only from legal_set.entries); wiring them needs a real scope decision this slice leaves open rather than guesses at. typed_operator_argument_preference_loss is a Bradley-Terry pairwise margin over CandidateScoringHead logits (surrogate, not textbook DPO, same honesty class as train_preference.py's own dpo_loss). Real, structural finding, not a bug: training on the actual pronoun-focus-followup fixture provably cannot move the loss. Both sibling refs share every field ReferenceModelViewV1 exposes -- they differ only by semantic_fingerprint, which is deliberately stripped from every model input for anti-identity-leakage. Two feature-identical candidates get byte-identical embeddings through the shared-weight CandidateScoringHead regardless of any parameter update, so the loss sits at -log_sigmoid(0)=ln(2) structurally. Proven exact (pytest.approx) and distinct from a mechanism failure via a second test with a synthetic, feature-distinguishable pair, where training does reduce the loss. Consequence for future corpus-building: only pairs whose candidates differ in an allowed feature carry learnable signal for this scorer -- worth knowing before anyone builds a real corpus and is puzzled why it plateaus. harness.experiments.argument_preference registered fresh (v1). No change to typed_operator_policy.py itself (only consumed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDctCUvBtGrdMvgNd34oHR
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
#1128) Stacked on #1127. Tenth slice of SLM-418's replay-grounded preference work: extends scripts/build_replay_preference_pairs.py with two more standalone scratch traces (build_demo_partial_rollback_scenario, build_demo_fork_choose_scenario) covering the two named patterns (partial_rollback, fork_then_choose_one_branch) extraction has supported since the fifth slice but the demo TwoTower corpus never exercised. 6 of 7 named patterns are now in this corpus; only pronoun_focus_followup remains absent, deliberately, since the ninth slice already covers it on the separate typed_operator_policy path. Reran the full SFT -> build-pairs -> preference-training chain against the richer 6-pair corpus. Claude-Session: https://claude.ai/code/session_01Hzz1DzercsjW7yBQaddo4Z Co-authored-by: Claude <noreply@anthropic.com>
…HEAD # Conflicts: # docs/design/dsh5-10-replay-preference-rows.md # scripts/build_replay_preference_pairs.py # src/slm_training/resources/versions.json # tests/test_scripts/test_build_replay_preference_pairs.py
Summary
Stacked on #1126 (which is stacked on #1125, which is stacked on #1124). This is the ninth slice of SLM-418's replay-grounded preference work (DSH5-10).
Every slice since the sixth has flagged the same gap: the
PreferencePair/TwoTower path used in #1124–#1126 exists only for tooling compatibility — the disposition doc's own remaining-scope note namestyped_operator_policy.py'sTypedOperatorPolicyScorer, not TwoTower, as the issue's actual training target. This slice takes the first real step onto that target, and honestly narrows what's reachable there rather than overclaiming.src/slm_training/harnesses/experiments/argument_preference.py:build_argument_preference_examplerenders a row into aTypedOperatorArgumentPreferenceExampleV1— but only forpronoun_focus_followup.OperatorPolicyInputV1.action_rowsis built only fromlegal_set.entries(operator-registry actions); the other six named patterns' chosen/rejected actions are history controls (undo/redo:/checkout:) ormerge:, none of which has a row there at all.pronoun_focus_followupis the one pattern whose chosen/rejected are the same operator with a different bound argument for the same slot — a genuine argument-selection preference. Wiring the other six patterns needs a real scope decision, left open here rather than guessed at.typed_operator_argument_preference_lossis a Bradley-Terry pairwise margin (-log_sigmoid(chosen_logit - rejected_logit)) overCandidateScoringHeadlogits — surrogate, not textbook DPO, same honesty class asscripts/train_preference.py's owndpo_loss.pronoun_focus_followupfixture provably cannot move the loss. Both sibling refs share every fieldReferenceModelViewV1exposes — they differ only bysemantic_fingerprint, which is deliberately stripped from every model input as anti-identity-leakage. Two feature-identical candidates get byte-identical embeddings through the shared-weightCandidateScoringHeadregardless of any parameter update, so the loss sits at-log_sigmoid(0) = ln(2)structurally. Proven exact (pytest.approx) and shown to be a fixture property, not a mechanism failure, via a second test with a synthetic feature-distinguishable pair, where training does reduce the loss.ref_kind/value_type/compiler_facts/has_parent/relative_position/selector fields) carry learnable signal for this scorer — worth knowing before anyone builds a real corpus and is puzzled why training on it plateaus.harness.experiments.argument_preferenceregistered fresh (v1). No change totyped_operator_policy.pyitself (only consumed).Full disposition in
docs/design/dsh5-10-replay-preference-rows.md's new "Ninth slice" section.Test plan
env -u 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_harnesses/preference/test_replay_pairs.py tests/test_scripts/test_build_replay_preference_pairs.py tests/test_harnesses/experiments/test_argument_preference.py tests/test_harnesses/experiments/test_typed_operator_policy.py tests/test_evals/test_advanced_operator_disposition.py tests/test_scripts/test_validate_advanced_operator_disposition.py—92 passed(72 from SLM-418: add merge_success to the demo pairs corpus + rerun (DSH5-10) #1126 + 6 new + 14 fromtest_typed_operator_policy.py, included in this repro command for the first time since this slice touches that module's consumer surface)ruff checkclean on both new filespython -m scripts.verify_version_stamps --check --base origin/main—ok (2 component(s) touched)python -m scripts.repo_policy—okpython -m scripts.verify_decode_invariants— cleanGenerated by Claude Code