Skip to content

fix(skippy): make local proposal handoff deadline-safe - #1193

Merged
i386 merged 5 commits into
mainfrom
agent/local-generation-lifecycle-commits
Aug 8, 2026
Merged

fix(skippy): make local proposal handoff deadline-safe#1193
i386 merged 5 commits into
mainfrom
agent/local-generation-lifecycle-commits

Conversation

@i386

@i386 i386 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

4bd1453fad1ab93ce14b010f76f91c368e929fb6 moved canonical commit delivery into the lifecycle path. Local generation did not publish the same deltas, so after one locally decoded token the next proposal query could observe a stale consumer position and fail closed.

Delivering every local token through an ordered lifecycle barrier fixes that position mismatch, but it also puts queue pressure and arbitrary lifecycle callback latency on inference. That is unsafe for the 8 ms proposal deadline: a slow or blocked consumer must not block or fail target decoding.

What changed

  • Carry the bounded canonical token delta directly on LinearProposalQuery instead of emitting a per-token lifecycle event from local generation.
  • Apply ordinary decoded-token deltas in the native plugin worker before proposal lookup.
  • Commit verified proposal tokens immediately before reporting the proposal outcome.
  • Reconcile any remaining generated suffix before finish_generation.
  • Track the committed generated prefix per request/session to preserve ordering and prevent duplicate commits.
  • Reject a saturated plugin queue before accepting proposal handoff. Proposal-path failures disable proposals for the request and target decoding continues.
  • Keep lifecycle start, finish, abort, and optional commit observations for integrations outside this local proposal hot path.

Deadline and performance

  • Local token emission no longer allocates or submits one lifecycle observation per token.
  • There is no lifecycle flush, spin loop, or 250 ms delivery timeout on the inference path.
  • The synchronous proposal caller waits only until the query's existing absolute deadline.
  • A regression test blocks commit_generation for 250 ms behind a stricter 5 ms proposal deadline and verifies that decode returns without waiting for the callback to finish.

Correctness coverage

  • Pending target tokens are committed before proposal lookup.
  • Verified tokens are committed before proposal outcome delivery.
  • Final tokens not seen at a proposal boundary are committed before generation finish.
  • Skipped or rejected handoffs retain pending state for later reconciliation.
  • Cancellation accounting is isolated per fake plugin instance so timing tests remain deterministic under parallel execution.

Validation

  • cargo fmt --all -- --check
  • just with-lld cargo check -p mesh-native-serving-plugin-api -p skippy-server -p mesh-native-serving-plugin-host
  • just with-lld cargo clippy -p skippy-server -p mesh-native-serving-plugin-host --all-targets -- -D warnings
  • just with-lld cargo test -p mesh-native-serving-plugin-host --lib — 13 passed

Summary by CodeRabbit

  • Improvements

    • Improved token tracking during generation and proposal processing.
    • Preserved pending tokens when proposal requests are skipped.
    • Added request and session details to proposal receipts.
    • Improved handling of proposal errors and unavailable proposal capacity.
    • Ensured committed tokens are delivered contiguously and receipt updates remain consistent during completion or cancellation.
  • Reliability

    • Expanded coverage for token forwarding, commit handling, shutdown behavior, and proposal deadlines.

@github-actions

github-actions Bot commented Aug 8, 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.

@coderabbitai

coderabbitai Bot commented Aug 8, 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

Local generation now tracks canonical tokens emitted between proposal boundaries. Linear proposal queries carry these tokens and request/session identities. The plugin host validates and commits token prefixes before proposal events and final receipt completion.

Changes

Pending token proposal flow

Layer / File(s) Summary
Proposal token tracking
crates/skippy-server/src/frontend/linear_proposal.rs, crates/skippy-server/src/frontend/linear_proposal/execution.rs, crates/skippy-server/src/frontend/local_generation/linear_decode.rs, crates/skippy-server/src/frontend/local_generation/token_generation.rs
Decode state accumulates pending token IDs. Linear proposal queries forward the IDs, return Skipped when capacity is unavailable, and preserve request and session identities in receipts.
Plugin commit lifecycle
crates/mesh-native-serving-plugin-host/src/lib.rs, crates/mesh-native-serving-plugin-host/src/test_support.rs
ActivePlugin validates contiguous token-prefix growth, commits pending tokens before proposal lookup and reporting, commits final receipt suffixes, and clears tracking on abort or finish.
Lifecycle validation and receipt observation
crates/skippy-server/src/frontend/generation_receipt.rs, crates/skippy-server/src/frontend/local_generation/tests.rs, crates/mesh-native-serving-plugin-host/src/plugin_dispatch/tests.rs
Receipt documentation and recording tests cover canonical token observation. Plugin tests cover commit ordering, deadlines, and backlog draining.

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

Sequence Diagram(s)

sequenceDiagram
  participant LocalGeneration
  participant LinearProposalQuery
  participant ActivePlugin
  participant Plugin
  LocalGeneration->>LinearProposalQuery: submit pending canonical token IDs
  LinearProposalQuery->>ActivePlugin: proposal query and request/session identities
  ActivePlugin->>Plugin: commit pending tokens
  ActivePlugin->>Plugin: request proposal lookup or report outcome
  ActivePlugin->>Plugin: commit final receipt suffix
Loading

Possibly related issues

Possibly related PRs

Suggested labels: experimental

Suggested reviewers: michaelneale, ndizazzo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: making local proposal handoff deadline-safe.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/local-generation-lifecycle-commits

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.

@i386
i386 marked this pull request as ready for review August 8, 2026 05:00
@github-actions
github-actions Bot requested a review from michaelneale August 8, 2026 05:00

@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

🧹 Nitpick comments (1)
crates/skippy-server/src/frontend/local_generation/tests.rs (1)

89-110: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Add coverage for the verified-proposal boundary.

The direct test calls commit_local_generation_token twice. It does not exercise emit_token. The integration test sets linear_proposal_ingress to None and native_mtp_enabled to false, so it covers ordinary decoding only.

Add a controlled proposal-path test that checks lifecycle visibility before the next proposal lookup. Otherwise, rename the direct test to describe helper sequencing rather than proposal-boundary coverage.

Also applies to: 226-236

🤖 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-server/src/frontend/local_generation/tests.rs` around lines 89
- 110, The test
local_generation_commits_advance_before_the_next_proposal_boundary currently
validates only direct helper sequencing, not proposal-path lifecycle visibility.
Add a controlled test exercising emit_token with a verified proposal and assert
the commit is visible before the next proposal lookup; alternatively, rename the
existing test to describe commit_local_generation_token sequencing and remove
the proposal-boundary claim.
🤖 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-server/src/frontend/local_generation/token_generation.rs`:
- Around line 45-50: The run_decode_loop token-emission path must ensure
GenerationReceiptConfig::committed delivery completes before the next
try_execute_linear_proposal lookup. Update the flow around emit_token and
GenerationReceiptConfig::committed to enforce synchronous ordering, or skip the
queued configuration for this changed path, while preserving commit contents and
ordering.

---

Nitpick comments:
In `@crates/skippy-server/src/frontend/local_generation/tests.rs`:
- Around line 89-110: The test
local_generation_commits_advance_before_the_next_proposal_boundary currently
validates only direct helper sequencing, not proposal-path lifecycle visibility.
Add a controlled test exercising emit_token with a verified proposal and assert
the commit is visible before the next proposal lookup; alternatively, rename the
existing test to describe commit_local_generation_token sequencing and remove
the proposal-boundary claim.
🪄 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: fbbcc4d5-b63d-4c3c-8eb1-9efb85fb9371

📥 Commits

Reviewing files that changed from the base of the PR and between 126109b and e762e78.

📒 Files selected for processing (2)
  • crates/skippy-server/src/frontend/local_generation/tests.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs

Comment thread crates/skippy-server/src/frontend/local_generation/token_generation.rs Outdated

@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: 2

🤖 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-server/src/frontend/generation_receipt.rs`:
- Around line 241-256: Update flush and submit_ordered so ordered delivery
cannot wait indefinitely when the lifecycle worker or deliver_to stalls. Add a
bounded deadline or cancellation-aware receive/send path that returns an error
on timeout, allowing the caller at Line 340 to record a failed receipt while
preserving successful ordering behavior.
- Around line 206-212: Update the QueuedGenerationLifecycleMessage handling in
the generation worker so each Barrier reflects observation delivery failures
since the preceding barrier. In the Observation branch, record failures as
currently; in the Barrier branch, consume or reset that interval’s failure state
and send an appropriate Err(...) through completion when any occurred, otherwise
retain Ok(()). Ensure committed_before_proposal cannot proceed after a failed
GenerationCommit.
🪄 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: d78fa543-6b7f-4ca5-b1d3-39dea5660a59

📥 Commits

Reviewing files that changed from the base of the PR and between e762e78 and 512b725.

📒 Files selected for processing (3)
  • crates/skippy-server/src/frontend/generation_receipt.rs
  • crates/skippy-server/src/frontend/local_generation/tests.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
  • crates/skippy-server/src/frontend/local_generation/tests.rs

Comment thread crates/skippy-server/src/frontend/generation_receipt.rs Outdated
Comment thread crates/skippy-server/src/frontend/generation_receipt.rs Outdated

@michaelneale michaelneale 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.

🤖 (micn's agent) Reviewed for 0.75.1. The diagnosis is convincing — the lifecycle refactor (4bd1453) gave remote generation commit delivery but left local generation updating only the final receipt, so lifecycle consumers hold a stale canonical position and the boundary check correctly fails closed on the next proposal. Emitting an authoritative commit per local token from the single emit_token closure is the right shape, and covering both ordinary decode and verified proposal spans through one emission point avoids the double-count trap. CI is green and the strengthened receipt test verifying the exact commit stream is good regression coverage.

Three things worth a look before this ships in a point release:

1. Inference can now fail on lifecycle delivery problems — previously best-effort. commit_local_generation_token propagates committed_before_proposal errors as OpenAiResult out of emit_token, so a stalled/failed lifecycle sink aborts user generation after the 250ms timeout. The pre-existing contract elsewhere in this file is explicitly the opposite (recording_failures: "These failures disable receipt recording for the affected generation but never fail inference"). Failing closed is arguably correct when a proposal consumer genuinely depends on the canonical position — a stale position would just fail the boundary check anyway — but when receipts are enabled with no proposal consumer (metrics/audit-only sinks), a slow plugin sink now kills generations that previously succeeded. Was that trade-off intended? If not, one option: only use the ordered path when the linear-proposal ingress is active, and fall back to best-effort committed() otherwise.

2. Per-token synchronous barrier on the hot decode path. Every emitted token now does: boxed-slice alloc → channel send (with yield_now spin when full) → rendezvous-channel barrier → wait for the worker to drain the queue. At local decode rates this serializes token emission against the delivery worker, so a sink that takes even ~1ms per commit caps generation at ~1k tok/s and any transient stall inserts visible latency. The function is named committed_before_proposal but it runs on every token whenever receipts are enabled, including when the proposal path is disabled (linear_proposal_enabled == false, native MTP, hooks active). Same conditional as (1) would fix both: pay the ordered cost only where ordering buys correctness.

3. Double-counted failure metric. One failed commit increments both the worker's delivery_failures and (via the barrier error → inspect_err) submission_failures, so delivery_failures() reports 2 for a single failure — the new queued_barrier_reports_delivery_failure_and_resets_interval test even asserts == 2. Anyone alerting on this counter will see inflated numbers. Minor, but worth either not incrementing submission_failures for barrier-reported failures, or documenting that the counter can count one failure twice.

Also a small naming nit: failures_since_barrier resets only on the error path — on the Ok path it's already 0 so behavior is fine, just slightly odd to read.

None of these block the correctness fix; (1) is the one I'd want a deliberate answer on before 0.75.1, since it changes user-visible failure semantics for existing receipt users.

@michaelneale michaelneale 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.

might want to address point 1 so it doesn't stop inference

@michaelneale michaelneale 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.

might want to consider breaking inference case

Comment on lines +71 to +80
fn wait_for_commits(sink: &RecordingReceiptSink, expected: usize) {
for _ in 0..100 {
if sink.commits.lock().unwrap().len() >= expected {
return;
}
thread::sleep(Duration::from_millis(1));
}
panic!("timed out waiting for {expected} generation commits");
}

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.

@coderabbitai does rust have a stronger pattern for this instead of busy-wait looping?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ndizazzo Yes. Use std::sync::Condvar with the commit storage.

RecordingReceiptSink::committed can notify the condition variable after it pushes a commit. Then wait_for_commits can use Condvar::wait_timeout_while to block until the expected count arrives or a single timeout expires.

This avoids the 1 ms polling interval and reduces test scheduler sensitivity. A channel with recv_timeout is also valid, but Condvar fits the existing shared Vec<GenerationCommit> state best.

You are interacting with an AI system.

@i386 i386 changed the title fix(skippy): emit local generation lifecycle commits fix(skippy): make local proposal handoff deadline-safe Aug 8, 2026

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
crates/mesh-native-serving-plugin-host/src/lib.rs (1)

378-405: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clear tracking when final suffix delivery fails.

commit_final_suffix can return before Line 405. The entry then remains in committed_generated_tokens, and finish_generation is not called. Remove the entry on every finish result.

🤖 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/mesh-native-serving-plugin-host/src/lib.rs` around lines 378 - 405,
Update finish so the committed_generated_tokens entry for the receipt key is
removed on every exit, including when commit_final_suffix returns an error.
Ensure cleanup occurs before propagating that error while preserving the
existing finish_generation result handling.
🤖 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/mesh-native-serving-plugin-host/src/lib.rs`:
- Around line 1379-1386: Update the test around the existing proposal commit and
raw callback calls to construct a LinearProposalReceipt and invoke
ActivePlugin::report via active.report(&receipt). Use this high-level method in
place of the direct report_proposal callback while preserving the expected event
ordering assertions.
- Around line 338-342: Reject lifecycle events for generations without a
successful start: at crates/mesh-native-serving-plugin-host/src/lib.rs:338-342,
require an existing committed_generated_tokens tracking entry instead of
deriving a fallback prior count; at
crates/mesh-native-serving-plugin-host/src/lib.rs:415-420, reject finish
processing when that tracking entry is absent so no suffix commit or finish
event is emitted.

---

Outside diff comments:
In `@crates/mesh-native-serving-plugin-host/src/lib.rs`:
- Around line 378-405: Update finish so the committed_generated_tokens entry for
the receipt key is removed on every exit, including when commit_final_suffix
returns an error. Ensure cleanup occurs before propagating that error while
preserving the existing finish_generation result handling.
🪄 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: dc43fe68-5cc2-4bd8-b7d9-37afc948d51f

📥 Commits

Reviewing files that changed from the base of the PR and between b54f3d7 and 52b077e.

📒 Files selected for processing (7)
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/skippy-server/src/frontend/generation_receipt.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/linear_proposal/execution.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
  • crates/skippy-server/src/frontend/local_generation/tests.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs

Comment on lines +338 to +342
let prior_generated_token_count = self
.committed_generated_tokens
.get(&key)
.copied()
.unwrap_or_else(|| generated_token_count.saturating_sub(token_ids.len()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject lifecycle events for generations that did not start.

A failed begin_generation does not create a tracking entry. The fallback accepts later commits, and finish can then send a suffix commit and finish event without a successful start.

  • crates/mesh-native-serving-plugin-host/src/lib.rs#L338-L342: Require an existing tracking entry instead of deriving a prior count from the incoming commit.
  • crates/mesh-native-serving-plugin-host/src/lib.rs#L415-L420: Reject finish processing when no tracking entry exists.
📍 Affects 1 file
  • crates/mesh-native-serving-plugin-host/src/lib.rs#L338-L342 (this comment)
  • crates/mesh-native-serving-plugin-host/src/lib.rs#L415-L420
🤖 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/mesh-native-serving-plugin-host/src/lib.rs` around lines 338 - 342,
Reject lifecycle events for generations without a successful start: at
crates/mesh-native-serving-plugin-host/src/lib.rs:338-342, require an existing
committed_generated_tokens tracking entry instead of deriving a fallback prior
count; at crates/mesh-native-serving-plugin-host/src/lib.rs:415-420, reject
finish processing when that tracking entry is absent so no suffix commit or
finish event is emitted.

Comment on lines +1379 to +1386
active.commit_proposal_tokens(1, 2, &[4, 5]).unwrap();
let status = unsafe {
(active.definition.api().report_proposal)(active.instance().unwrap(), std::ptr::null())
};
assert_eq!(status, abi::PluginStatus::OK);
active.commit_final_suffix(1, 2, &[4, 5]).unwrap();

assert_eq!(*events.lock().unwrap(), ["begin", "commit", "report"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Call ActivePlugin::report in this test.

The test calls commit_proposal_tokens and the raw report_proposal callback. It does not test the ordering in ActivePlugin::report. Build a LinearProposalReceipt and call active.report(&receipt).

🤖 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/mesh-native-serving-plugin-host/src/lib.rs` around lines 1379 - 1386,
Update the test around the existing proposal commit and raw callback calls to
construct a LinearProposalReceipt and invoke ActivePlugin::report via
active.report(&receipt). Use this high-level method in place of the direct
report_proposal callback while preserving the expected event ordering
assertions.

@michaelneale michaelneale 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.

🤖 (micn's agent) Re-reviewed after 52b077ed ("keep proposal commits off lifecycle hot path"). This is a substantially better design than the barrier approach and resolves both of my earlier concerns at the root rather than patching around them:

  • Hot path: the synchronous submit-and-flush barrier is gone entirely (submit_ordered/flush/Barrier machinery deleted). The canonical token delta now travels on the proposal query itself (LinearProposalQuery::pending_token_ids), so the cost is only paid when a proposal is actually being made — a single boxed-slice clone on a path that's about to do a runtime decode anyway. Ordinary decode with no proposal consumer is back to fire-and-forget. This also kills the failure-semantics concern: lifecycle delivery can no longer abort inference.
  • Double-counted failure metric: gone with the barrier.
  • The new Skipped outcome is a nice touch — when there's no proposal capacity the pending delta stays owned by the caller instead of being consumed-but-undelivered, and query_does_not_accept_pending_tokens_without_proposal_capacity locks that in.
  • The plugin-host side is careful: per-(request_id, session_id) committed-count tracking, prefix-extension validation (commit does not extend the tracked generated-token prefix), cleanup on abort/finish, and commit_final_suffix covering the tail between the last proposal boundary and generation end — so a plugin still receives a complete, contiguous commit stream even for generations where the proposal path never fires.

Two small observations, neither blocking:

  1. unwrap_or_else(|| generated_token_count.saturating_sub(token_ids.len())) in commit_tokens — for an unknown key this trusts the caller's count to back-derive the prior prefix. It's only reachable if a commit arrives without a begin (or after cleanup), where failing closed with a bail! might be preferable to synthesizing a prefix. In practice begin always seeds the entry, so this is defensive-path behavior only.
  2. Semantics shift worth one line in the PR description: GenerationReceiptSink::committed deliveries are now proposal-boundary-driven (plus final suffix) rather than strictly per-token-real-time for every configuration. The doc comment on the trait was updated to say exactly this ("optionally observes ... deltas"), which is honest — just make sure the PR body's "every token emitted" framing matches the final shape so 0.75.1 notes are accurate.

CI is green on the branch. Happy with this for the point release once the merge conflict with main (the #1180 proposal-deadline change touched the same files) is resolved.

@i386
i386 enabled auto-merge (squash) August 8, 2026 08:44

@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 (2)
crates/mesh-native-serving-plugin-host/src/lib.rs (2)

469-479: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop proposal lookup after the commit reaches the deadline.

commit_tokens can block in commit_generation. If that callback reaches query.deadline, this method still calls start_proposal. The expired request can then run a new plugin callback and delay later FIFO work.

After the pending-token commit, return Ok(None) when the deadline has elapsed. Add a test that verifies the fake proposal callback is not called in this case.

🤖 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/mesh-native-serving-plugin-host/src/lib.rs` around lines 469 - 479,
After the pending-token `commit_tokens` call in the surrounding
request-processing method, check whether `query.deadline` has elapsed and return
`Ok(None)` before invoking `start_proposal`. Add coverage using the fake
proposal callback to verify it is not called when the commit reaches or passes
the deadline.

570-575: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize receipt token commits before later proposal queries.

PluginCommand::Report runs on the passive worker. Its commit_proposal_tokens call can occur after the primary worker processes a later Proposal for the same request.

For example, if a receipt must commit token A and the next query carries token B at generated count 2, the later query can see tracked count 0. The contiguous-prefix check then rejects B because it expects count 1.

Commit the receipt prefix in primary-queue order before dispatching report_proposal on the passive worker. Add a delayed-report regression test with a later proposal query for the same request and session.

🤖 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/mesh-native-serving-plugin-host/src/lib.rs` around lines 570 - 575,
Serialize receipt token commits before passive-worker reporting by moving the
commit performed by report (and its report_proposal dispatch path) into
primary-queue order, ensuring later Proposal queries observe the committed
prefix. Update the relevant PluginCommand::Report/report flow without changing
token validation semantics, and add a regression test covering a delayed report
followed by a later proposal query for the same request and session.
🤖 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 `@crates/mesh-native-serving-plugin-host/src/lib.rs`:
- Around line 469-479: After the pending-token `commit_tokens` call in the
surrounding request-processing method, check whether `query.deadline` has
elapsed and return `Ok(None)` before invoking `start_proposal`. Add coverage
using the fake proposal callback to verify it is not called when the commit
reaches or passes the deadline.
- Around line 570-575: Serialize receipt token commits before passive-worker
reporting by moving the commit performed by report (and its report_proposal
dispatch path) into primary-queue order, ensuring later Proposal queries observe
the committed prefix. Update the relevant PluginCommand::Report/report flow
without changing token validation semantics, and add a regression test covering
a delayed report followed by a later proposal query for the same request and
session.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b2cb0572-b869-420b-9072-a093f9acff10

📥 Commits

Reviewing files that changed from the base of the PR and between 52b077e and 50ad3c5.

📒 Files selected for processing (5)
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/mesh-native-serving-plugin-host/src/plugin_dispatch/tests.rs
  • crates/mesh-native-serving-plugin-host/src/test_support.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs

@i386
i386 merged commit 915e691 into main Aug 8, 2026
45 checks passed
@i386
i386 deleted the agent/local-generation-lifecycle-commits branch August 8, 2026 09:09
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