Repository navigation
runtime::mtp: the MTP draft's policy and MtpDraft move to runtime behind a Host trait - #1155
Conversation
… so a backend without the card engine can draft with it The MTP window policy and its tests now live in the host-only runtime crate, beside runtime::lookup and runtime::dspark. Metal will use them. It cannot link crates/app, which links the card engine. Moved, code unchanged except visibility, use lines and doc links: - app mtp/policy.rs: Policy, Shape, Refresh, Chain, Call, Walk, Exec, Fail, Refused, SKIPS, and 21 policy tests. - gpu draft.rs: Hidden and WalkMode. - app mtp.rs: the Join and Parked structs, and WHAT. The old paths re-export them. What names a card type stays: - Feed (CallRows); - DraftHead (check_opened returns GpuError); - From<StepMode>. Parked's byte codec (put/take/check, gpu::checkpoint::bytes) stays in app as the ParkedBytes trait. Refresh::feed stays as a private app trait. Its two byte tests stay in app. The only caller edit is drafted.rs's `use` line. Tests 24 before (policy 23, app 1) and 24 after (runtime 21, app 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjHkkntc2L1xRN21xB39mj
…te machine has one owner for every backend MtpDraft moves from app to runtime::mtp::MtpDraft<H: Host>, with its Draft impl. The new Host: Verify trait carries: - the arena and path kinds; - the window constants; - six calls: held, walk, chain, prompt_with, retain_prompt_row, prompt_scope. app keeps the CUDA side: - impl Host for Session<B> over MtpBody; - the alias MtpDraft<B> = runtime::mtp::MtpDraft<Session<B>>; - open_draft; - the media prompt path; - the slot paths, which now read the draft through accessors. The text and media prompt calls share one path, MtpDraft::prompt_joined. Media changes only the executor it is given, and Host has no media concept. prompt_scope keeps prompt_busy over the join walk and the call, as before. The join walk blocks on the inbox copy (store_rows -> Inbox::host_mut -> synchronize). The From<Fail<GpuError>> impl, which had no caller, is gone. drafts_mtp in tools/owners.tsv names the runtime files. Nine mock-Host tests cover the moved state machine on a machine with no card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjHkkntc2L1xRN21xB39mj
There was a problem hiding this comment.
Verdict: MUST_FIX: the reset test misses its stated contract, the text adapter duplicates an existing owner, and the conversion trait is unnecessary.
Must fix
1. Assert that unpark clears the pending proposal
crates/runtime/src/mtp/draft.rs:1087: removing w.pending = None from unpark at line 147 leaves this test green. restart also clears pending; the final assertion only reads kept through take_windows, and this test never fills kept.
Kind B: the new test cannot detect a violation of its stated reset contract. Assert the pending state before and after each reset so this regression is caught now. Replace the test function, retaining its #[test] attribute:
fn a_parked_draft_keeps_what_waits_and_an_unpark_drops_the_kept_proposal() {
let (mut d, mut t, first) = begun::<true>();
d.keep_windows();
let parked = d.park();
assert!(parked.next.is_none() && parked.skip.is_some());
d.retain_prompt(&mut t).unwrap();
assert_eq!(t.take(), [Ev::Retain(Ar::Ubatch, 0, 9)]);
let mut out = [0u32; 3];
let n = d.propose(&mut t, first, &mut out).unwrap();
assert!(d.keeps_windows());
assert!(d.windows.as_ref().unwrap().pending.is_some());
d.unpark(&parked);
assert!(d.windows.as_ref().unwrap().pending.is_none());
d.proposed(&out[..n]);
assert!(d.windows.as_ref().unwrap().pending.is_some());
d.restart();
assert!(d.windows.as_ref().unwrap().pending.is_none());
assert!(d.take_windows().is_empty());
}2. Reuse Card for the text host walk
crates/app/src/mtp.rs:535 repeats crates/app/src/mtp.rs:622: both translate the same Walk into Feed and invoke B::walk. The text case supplies media: None.
Kind B, R14. Delegating leaves one adapter to update when the feed changes and adds no owner. Replace the new method with:
fn walk(&mut self, w: &Walk<'_, B::Arena>, head: DraftHead) -> Result<(), GpuError> {
Card {
m: self.model_mut(),
head,
media: None,
}
.walk(w)
}3. Remove the single-implementation RefreshFeed trait
crates/app/src/mtp.rs:92 declares a trait used only by its Refresh<A> implementation. No caller has a trait bound or dispatches through it.
Kind B, fewest elements. Remove the extra declaration and impl while this conversion has only three callers. Replace both definitions with:
fn refresh_feed<A: Copy>(r: &Refresh<A>) -> Feed<'_, A> {
Feed {
tokens: &r.tokens,
pos0: r.pos0,
hidden: Hidden::Target {
walk: r.walk,
first: r.first,
},
media: None,
}
}Replace both c.refresh.feed() calls with refresh_feed(&c.refresh), and r.feed() with refresh_feed(r).
Non-blocking
⚠ Supply the move proof for the reviewed head: crates/runtime/src/mtp/draft.rs:254. The prompt dispatch moved here, but the identical scans in the PR body compare pre-rebase commits. The body says the replacement batch is running and contains no result.
Attach just narrow 6490d03f3 1adccd30f output and the selected host structural/e2e results. AGENTS.md's move class requires identical scans and host dispatch structural lines for the code being merged.
Checked
Duplicates: both Walk adapters and all three RefreshFeed callers confirmed.
Complexity: the cited adapters add small dispatch matches; crapper did not run.
Silent failure: the cited walk and chain adapters propagate backend errors.
Tests: the unpark reset mutant survives by inspection. Tests were not executed here.
Gates: no thresholds relaxed in the cited hunks.
Escape catalog: C1–C4 do not match the cited code.
Notes
Dryer and crapper did not run because their Python modules are missing. GPU evidence lacks the card/driver line and model file identification.
Card check: needs-gpu. This pull request changes crates/app/src/mtp.rs, crates/gpu-gates/src/bin/gate_glm5next_e2e.rs, crates/gpu-gates/src/bin/gate_glm5next_mtp.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
…moved to runtime The policy module left crates/app/src/mtp/, so the row's mtp/** glob matched no file and the real-table self-test refused it. The runtime files have no row: the derivation reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjHkkntc2L1xRN21xB39mj
…xt walk runs through Card, and the refresh feed is a function The review found three things: - the unpark test never read `pending`, so dropping its reset left it green; - Host::walk repeated Card's Exec::walk; - RefreshFeed was a trait with one impl and no bound. The test now asserts `pending` after the propose, the unpark, a second proposal and the restart. Dropping `w.pending = None` from unpark fails it at draft.rs:1088. Host::walk builds a Card with no media and walks it. refresh_feed is a free function at its three callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjHkkntc2L1xRN21xB39mj
There was a problem hiding this comment.
Since the last review
Verdict: CLEAN: no findings remain within this increment, which changes only the MTP gate-path row.
Checked
Duplicates: no new functions, types or constants.
Complexity: no functions changed.
Silent failure: no error-handling code changed.
Tests: none changed; tests were not executed.
Gates: the row removes the mtp/** alternative and updates its description. No thresholds or pins changed.
Escape catalog: C1–C4 do not match the changed row.
Notes
Dryer and crapper did not run because their Python modules are missing.
Card check: needs-gpu. This pull request changes crates/app/src/mtp.rs, crates/gpu-gates/src/bin/gate_glm5next_e2e.rs, crates/gpu-gates/src/bin/gate_glm5next_mtp.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
There was a problem hiding this comment.
Since the last review: no must-fix or non-blocking items were open.
Verdict: CLEAN: this increment preserves feed construction and walk behavior, and strengthens pending-proposal checks.
Checked
Duplicates: 1 replacement function checked by Grep; no new duplicate. Host::walk reuses the existing Card executor. Dryer produced no pairs because it did not run.
Complexity: the adapter changes add no branches. Crapper produced no function results because it did not run.
Silent failure: 1 changed error-returning delegation followed through Card::walk to MtpBody::walk; callers receive the same GPU error.
Tests: 1 changed test inspected. Removing the pending clear at crates/runtime/src/mtp/draft.rs:113 or crates/runtime/src/mtp/draft.rs:147 fails the corresponding new assertion by inspection. Tests and mutants were not executed.
Gates: no thresholds, pins, tests or cases removed or relaxed.
Performance: decode and prompt adapters retain the same backend calls, allocations, launches and copies.
Adversarial checks: host-backed hidden rows, target arena offsets, absent media and walk errors retain their previous handling.
Escape catalog: C1–C4 do not match this increment.
Notes
Dryer and crapper did not run because their Python modules are missing.
Card check: needs-gpu. This pull request changes crates/app/src/mtp.rs, crates/gpu-gates/src/bin/gate_glm5next_e2e.rs, crates/gpu-gates/src/bin/gate_glm5next_mtp.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
What and why
The MTP draft lived in
crates/app, which links the card engine, so the Metal backend could not use it. This PR moves it to the host-onlyruntimecrate in two commits (plus a gate-paths row fix and the review's fixes), besideruntime::lookupandruntime::dspark. It supersedes #1148, which held the first commit; that branch could not be updated after the rebase onto the uutils fix (0e763b8).Commit 1 (R0a): the card-free window policy.
Policy,Shape,Refresh,Chain,Call,Walk,Exec,Fail,RefusedandSKIPSmove toruntime::mtp::policywith their 21 tests.Hidden,WalkMode,JoinandParkedmove toruntime::mtp. The old paths re-export them. Only visibility,uselines and two doc links change.Commit 2 (R0b):
MtpDraftover aHosttrait.runtime::mtp::MtpDraft<H: Host>and itsDraftimpl hold the state machine.Host: Verifycarries:held,walk,chain,prompt_with,retain_prompt_rowandprompt_scope.app keeps the CUDA side:
impl Host for Session<B>overMtpBody;MtpDraft<B> = runtime::mtp::MtpDraft<Session<B>>andopen_draft;The text and media prompt calls share one path,
MtpDraft::prompt_joined. Media changes only the executor it is given, soHosthas no media concept.prompt_scopekeepsprompt_busyover the join walk and the call, as before. The join walk blocks on the inbox copy (store_rows→Inbox::host_mut→synchronize).The
From<Fail<GpuError>>impl had no caller and is removed.drafts_mtpintools/owners.tsvnow names the runtime files.What still names a card type stays in app or gpu:
Feed(gpu::media::CallRows),DraftHead,From<StepMode>, and Parked's byte codec (theParkedBytestrait).Release notes: internal — the MTP draft is shared code in
runtime, ready for a second backend.Code shape
From<Fail<GpuError>>impl is removed.cargo fmt;just mac-lintratchet 0.Duplication found or removed: the text and media prompt calls had about 20 shared lines; they now share one path.
What I ran
The move proof uses
just narrow:--no-jitscans of the covering bin set on the box, base 99a6bfd, head 0826a3941 (both commits, before the rebase).The box batch for that list ran on the fixture tier, at
tools/gate-batch.sh --tier fixture --ledger:The one red was our box's gap, not this change: the FP4-Local cut folder (from #1116) had not been made on our box. It is made now (
fixture cut+fixture verify: 31111 tensors, 37.16 GB), and the gate is green (below).After the rebase onto main (head 1adccd3), main's
crates/levers/src/registry.rsandtools/gate-bound.shmoved every item's key, so the same list ran again there:The third commit fixes a
tools/gate-paths.tsvrow whosecrates/app/src/mtp/**glob matched nothing after the move (CI's gate-paths self-test). A ledger dry-run at that head skips 113 of 113 items: no key moved.The fourth commit applies the review's three fixes. The proof at that head (15a8421, base 0e763b8):
FAIL-first for the strengthened test: removing
w.pending = Nonefromunparkpanics it at draft.rs:1088.The weekly recipes the change triggers (
weekly-gpu-ds41-serve,weekly-gpu-glm5next-e2e-long,weekly-gpu-glm5next-media-serve) run after the merge.On the Mac:
FAIL-first:
Policy::chain'sctx as usize - 1 - endtoctx as usize - endfailsa_chain_takes_the_refresh_and_caps_its_own_walksandno_call_sequence_leaves_the_store_past_the_target_or_refuses.before_plainwith an empty body fails 2 mock-Host tests.prompt_callpassingNonefor the sink fails 8.Every mutant was reverted.
The Metal side that uses this (
impl Host for Sessionandgenerate_metal --draft mtp) follows in its own PR.AI use: Claude Code wrote this change (Sonnet rounds, reviewed by the lead session, which ran the box proof).
🤖 Generated with Claude Code
https://claude.ai/code/session_01CjHkkntc2L1xRN21xB39mj