Skip to content

fix(skippy): isolate chat grammar during speculative verification - #1172

Merged
i386 merged 9 commits into
mainfrom
agent/isolate-speculative-chat-grammar
Aug 5, 2026
Merged

fix(skippy): isolate chat grammar during speculative verification#1172
i386 merged 9 commits into
mainfrom
agent/isolate-speculative-chat-grammar

Conversation

@i386

@i386 i386 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Why this matters

While testing a possible shortcut, Mesh could accidentally change the rules used to produce valid tool calls. Even when that shortcut was rejected, later generation could produce malformed output.

This change makes speculative verification a safe dry run. Rejected candidate tokens cannot damage the real grammar state used by normal generation.

Technical details

  • clone the active sampling and chat-grammar state before speculative verification
  • stop sampling at the first target mismatch so rejected branch-conditioned rows cannot mutate grammar state
  • preserve the authoritative sampler until the verified prefix is known to be valid
  • tighten verify-window classification for truncated predictions and missing full-accept boundary tokens

Non-grammar and ordinary generation paths are unchanged.

Validation

  • bash scripts/prepare-llama.sh — complete 66-patch queue applied successfully
  • cargo test -p skippy-runtime --quiet — 68 passed
  • cargo test -p skippy-server verify_window_ — 10 passed

The optional model-backed lazy-grammar correctness test compiled but skipped because SKIPPY_CORRECTNESS_MODEL was not configured locally.

This PR is based directly on main and has no dependency on the companion fixes.

Summary by CodeRabbit

  • Bug Fixes
    • Improved structured generation verification by stopping at the first prediction mismatch.
    • Prevented rejected prediction suffixes from affecting grammar state.
    • Added clearer handling for incomplete prediction output and missing decision boundaries.
    • Improved reliability for long tool-context requests, including grammar generation, batched verification, and draft-token acceptance.
  • Documentation
    • Clarified which prediction data is considered authoritative during proposal processing.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Speculative verification

Layer / File(s) Summary
Mismatch boundary handling
third_party/llama.cpp/patches/0066-Stop-chat-grammar-verification-at-the-first-mismatch.patch
Grammar verification stops at the first proposal mismatch, records the verified prefix length, and suppresses MTP proposal generation.
Prediction boundary validation
crates/skippy-server/src/frontend/native_mtp/hybrid.rs, crates/skippy-server/src/frontend/linear_proposal.rs
Native MTP verification reports truncated predictions before a decision and missing boundary predictions after full acceptance. Receipt documentation defines the authoritative prediction prefix. Tests cover these cases.
Long-context correctness coverage
crates/skippy-runtime/src/tests.rs
Correctness helpers share tool-call options and support configurable context sizes. A long resident-tool-context test covers grammar metadata, prefix restoration, batched verification, native MTP decoding, and draft-token acceptance.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NativeMTPVerification
  participant skippy_verify_tokens_frame_sampled
  participant GrammarSampler
  participant MTPProposalGeneration
  NativeMTPVerification->>skippy_verify_tokens_frame_sampled: verify proposal tokens
  skippy_verify_tokens_frame_sampled->>GrammarSampler: compare sampled tokens
  GrammarSampler-->>skippy_verify_tokens_frame_sampled: first mismatch or full acceptance
  skippy_verify_tokens_frame_sampled-->>NativeMTPVerification: return authoritative prediction prefix
  skippy_verify_tokens_frame_sampled-->>MTPProposalGeneration: generate proposals only after full match
Loading

Possibly related PRs

  • Mesh-LLM/mesh-llm#1144: Extends related sampled-verification and grammar-state handling in the same correctness and native MTP paths.
  • Mesh-LLM/mesh-llm#1173: Modifies related sampled-verification logic and causal token-history handling.

Suggested labels: experimental

Suggested reviewers: michaelneale, ndizazzo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: isolating chat grammar during speculative verification.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/isolate-speculative-chat-grammar

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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This pull request is currently a draft. Reviews will not take place until the PR is marked as ready for review.

@i386
i386 marked this pull request as ready for review August 4, 2026 22:06
@github-actions
github-actions Bot requested a review from michaelneale August 4, 2026 22:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/skippy-runtime/src/tests.rs`:
- Around line 296-305: Update the serial verification loop surrounding
verify_tokens_sampled to stop immediately after the first prediction differs
from the next proposed token, rather than decoding the rejected suffix. Use that
truncated serial result as the canonical prefix for comparing
batched_predictions and the corresponding positions, preserving the existing
first-mismatch boundary.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e4bde22-e2a7-4dad-bdd1-f05cc468ef4a

📥 Commits

Reviewing files that changed from the base of the PR and between 4bd1453 and 8c19d97.

📒 Files selected for processing (4)
  • crates/skippy-runtime/src/tests.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/native_mtp/hybrid.rs
  • third_party/llama.cpp/patches/0066-Isolate-chat-grammar-during-speculative-verification.patch

Comment thread crates/skippy-runtime/src/tests.rs Outdated

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth adding one more model-backed regression check for tool calling with a long resident KV context (8k+ tokens)... The current tests validate the short verification window, but the proposed test would sample tokens from the existing (potentially large) KV cache.

Adding a fallback check for latency and native-MTP acceptance at a sizeable context length would guard against a long-context throughput regression.

@i386

i386 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@ndizazzo coming right up

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
crates/skippy-runtime/src/tests.rs (3)

454-482: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Report when the model returns no MTP draft.

The acceptance checks run only when draft is Some. If the model returns no draft, the test passes without exercising draft acceptance. The test name states that it verifies native MTP acceptance, so a silent pass hides the coverage gap.

Log the skip, or fail if the configured model is expected to produce a draft.

♻️ Proposed change
         if let Some(draft) = draft {
             let mut target =
                 model.create_session_from_resident_prefix(RESIDENT_PREFIX_ID, prompt_prefix)?;
@@
                 draft.token_ids
             );
+        } else {
+            eprintln!(
+                "native MTP returned no draft after the resident tool context; \
+                 draft-acceptance coverage was skipped"
+            );
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-runtime/src/tests.rs` around lines 454 - 482, Update the native
MTP acceptance test around the draft match so a missing draft is explicitly
reported instead of silently skipping the acceptance assertions. Handle the None
branch with a clear diagnostic, or fail the test when the configured model is
expected to produce a draft, while preserving the existing checks for
Some(draft).

373-376: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bound the prompt against the verify window, not only the context size.

Line 374 allows a prompt of CONTEXT_SIZE - 1 tokens. The test then decodes verify_inputs (about 20 tokens) plus 4 MTP draft tokens on top of the prefix. If the growth loop overshoots, the failure surfaces as an opaque decode error instead of a clear assertion.

Assert the remaining headroom explicitly.

♻️ Proposed assertion
+        // Leave room for the verification window and MTP drafts.
+        const VERIFY_HEADROOM: usize = 128;
         assert!(
-            prompt_tokens.len() < CONTEXT_SIZE as usize,
-            "resident prompt must leave room for tool sampling"
+            prompt_tokens.len() + VERIFY_HEADROOM <= CONTEXT_SIZE as usize,
+            "resident prompt must leave room for tool sampling: {} tokens with context {CONTEXT_SIZE}",
+            prompt_tokens.len()
         );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-runtime/src/tests.rs` around lines 373 - 376, Update the
prompt-size assertion in the relevant test to bound resident prompt tokens by
the available context after reserving space for verify_inputs and the four MTP
draft tokens, rather than only checking against CONTEXT_SIZE. Make the assertion
explicitly validate sufficient remaining headroom so oversized growth fails with
a clear message before verify_inputs decoding.

408-415: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the first-mismatch serial loop into a shared helper.

This loop is identical to the loop at Lines 292-299. The first-mismatch boundary is a contract shared by both tests. Duplicating it lets the two copies diverge.

Extract one helper and call it from both tests.

♻️ Proposed helper
fn serial_predictions_until_mismatch(
    session: &mut StageSession,
    verify_inputs: &[i32],
    sampling: &SamplingConfig,
) -> anyhow::Result<Vec<i32>> {
    let mut predictions = Vec::with_capacity(verify_inputs.len());
    for (index, token) in verify_inputs.iter().copied().enumerate() {
        let predicted = session.decode_step_sampled(token, Some(sampling))?;
        predictions.push(predicted);
        if index + 1 < verify_inputs.len() && predicted != verify_inputs[index + 1] {
            break;
        }
    }
    Ok(predictions)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-runtime/src/tests.rs` around lines 408 - 415, Extract the
duplicated first-mismatch loop into a shared `serial_predictions_until_mismatch`
helper that accepts the session, verification inputs, and sampling
configuration, performs the existing prediction and boundary logic, and returns
`anyhow::Result<Vec<i32>>`. Replace both serial prediction loops, including the
one near `Lines 292-299` and the loop in the current test, with calls to this
helper while preserving their existing error propagation and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/skippy-runtime/src/tests.rs`:
- Around line 454-482: Update the native MTP acceptance test around the draft
match so a missing draft is explicitly reported instead of silently skipping the
acceptance assertions. Handle the None branch with a clear diagnostic, or fail
the test when the configured model is expected to produce a draft, while
preserving the existing checks for Some(draft).
- Around line 373-376: Update the prompt-size assertion in the relevant test to
bound resident prompt tokens by the available context after reserving space for
verify_inputs and the four MTP draft tokens, rather than only checking against
CONTEXT_SIZE. Make the assertion explicitly validate sufficient remaining
headroom so oversized growth fails with a clear message before verify_inputs
decoding.
- Around line 408-415: Extract the duplicated first-mismatch loop into a shared
`serial_predictions_until_mismatch` helper that accepts the session,
verification inputs, and sampling configuration, performs the existing
prediction and boundary logic, and returns `anyhow::Result<Vec<i32>>`. Replace
both serial prediction loops, including the one near `Lines 292-299` and the
loop in the current test, with calls to this helper while preserving their
existing error propagation and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cb38a816-fa07-4c3d-b86f-1fb291283f32

📥 Commits

Reviewing files that changed from the base of the PR and between 948d381 and 00e2c70.

📒 Files selected for processing (1)
  • crates/skippy-runtime/src/tests.rs

@i386
i386 enabled auto-merge (squash) August 5, 2026 03:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
third_party/llama.cpp/patches/0066-Stop-chat-grammar-verification-at-the-first-mismatch.patch (1)

34-34: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Clear MTP state on grammar mismatch.

When proposal_mismatched stops proposal generation, an existing draft can still be returned in out_mtp_draft. Set out_mtp_draft->available = false and clear session MTP state, for example with skippy_mtp_clear_session_state(session), before returning from this mismatch path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@third_party/llama.cpp/patches/0066-Stop-chat-grammar-verification-at-the-first-mismatch.patch`
at line 34, Update the proposal-mismatch path guarded by proposal_mismatched so
any existing out_mtp_draft is marked unavailable and the session MTP state is
cleared via skippy_mtp_clear_session_state(session) before returning. Keep
normal draft handling unchanged when no grammar mismatch occurs.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@third_party/llama.cpp/patches/0066-Stop-chat-grammar-verification-at-the-first-mismatch.patch`:
- Line 34: Update the proposal-mismatch path guarded by proposal_mismatched so
any existing out_mtp_draft is marked unavailable and the session MTP state is
cleared via skippy_mtp_clear_session_state(session) before returning. Keep
normal draft handling unchanged when no grammar mismatch occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 11057db4-5123-477a-86d1-97cdab7dfaee

📥 Commits

Reviewing files that changed from the base of the PR and between 00e2c70 and a79378b.

📒 Files selected for processing (1)
  • third_party/llama.cpp/patches/0066-Stop-chat-grammar-verification-at-the-first-mismatch.patch

@i386
i386 merged commit 4d400b3 into main Aug 5, 2026
62 checks passed
@i386
i386 deleted the agent/isolate-speculative-chat-grammar branch August 5, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants