Skip to content

SLM-418: first step onto the DSH3 typed policy target (DSH5-10) - #1127

Merged
Tyler-R-Kendrick merged 5 commits into
claude/great-dirac-ni43ohfrom
claude/great-dirac-ni43oh-pronoun-focus-policy
Jul 27, 2026
Merged

SLM-418: first step onto the DSH3 typed policy target (DSH5-10)#1127
Tyler-R-Kendrick merged 5 commits into
claude/great-dirac-ni43ohfrom
claude/great-dirac-ni43oh-pronoun-focus-policy

Conversation

@Tyler-R-Kendrick

Copy link
Copy Markdown
Owner

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 names typed_operator_policy.py's TypedOperatorPolicyScorer, 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.

  • New module src/slm_training/harnesses/experiments/argument_preference.py: build_argument_preference_example renders a row into a TypedOperatorArgumentPreferenceExampleV1 — but only for pronoun_focus_followup. OperatorPolicyInputV1.action_rows is built only from legal_set.entries (operator-registry actions); the other six named patterns' chosen/rejected actions are history controls (undo/redo:/checkout:) or merge:, none of which has a row there at all. pronoun_focus_followup is 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_loss is a Bradley-Terry pairwise margin (-log_sigmoid(chosen_logit - rejected_logit)) over CandidateScoringHead logits — surrogate, not textbook DPO, same honesty class as scripts/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 as 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 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.
  • Consequence flagged for future corpus-building: only argument-preference pairs whose candidates differ in an allowed feature (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_preference registered fresh (v1). No change to typed_operator_policy.py itself (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.py92 passed (72 from SLM-418: add merge_success to the demo pairs corpus + rerun (DSH5-10) #1126 + 6 new + 14 from test_typed_operator_policy.py, included in this repro command for the first time since this slice touches that module's consumer surface)
  • ruff check clean on both new files
  • python -m scripts.verify_version_stamps --check --base origin/mainok (2 component(s) touched)
  • python -m scripts.repo_policyok
  • python -m scripts.verify_decode_invariants — clean
  • Both the real-fixture null result and the synthetic-fixture positive result are directly asserted in tests, not just described in prose

Generated by Claude Code

claude added 3 commits July 27, 2026 08:01
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
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
slm-training Ready Ready Preview, Comment Jul 27, 2026 2:51pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ea9c9fc6-0c17-4e6d-9d2b-912b9130593c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/great-dirac-ni43oh-pronoun-focus-policy

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from claude/great-dirac-ni43oh-replay-pairs-merge to claude/great-dirac-ni43oh-replay-pairs-run July 27, 2026 14:46
#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>
Base automatically changed from claude/great-dirac-ni43oh-replay-pairs-run to claude/great-dirac-ni43oh July 27, 2026 14:46
…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
@Tyler-R-Kendrick
Tyler-R-Kendrick merged commit cfc9a3c into claude/great-dirac-ni43oh Jul 27, 2026
6 checks passed
@Tyler-R-Kendrick
Tyler-R-Kendrick deleted the claude/great-dirac-ni43oh-pronoun-focus-policy branch July 27, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants