Skip to content

feat(mesh): expose model-bound tokenizer capability - #1227

Merged
i386 merged 7 commits into
mainfrom
agent/cacheline-tokenizer-capability
Aug 11, 2026
Merged

feat(mesh): expose model-bound tokenizer capability#1227
i386 merged 7 commits into
mainfrom
agent/cacheline-tokenizer-capability

Conversation

@i386

@i386 i386 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Why

Native-serving plugins need access to the active model's tokenizer through a bounded, model-bound interface. Metadata alone is insufficient for structured input, and unsupported or non-lossless input must fail explicitly rather than be silently transformed.

This PR adds that capability at the Mesh/plugin boundary while keeping tokenizer preparation outside latency-sensitive proposal callbacks.

Old API (V1)

Activation provided a borrowed native tokenizer inventory:

ActivationContext {
    tokenizer_inventory: *const TokenizerInventoryView,
    // ...
}

Plugins resolved:

mesh_native_serving_plugin_v1

The inventory was metadata only. There was no model-bound encode callback, explicit encode limits, or binding digest.

New API (V2)

Activation provides a model-bound capability:

ActivationContext {
    tokenizer_capability: *const TokenizerCapability,
    // ...
}

TokenizerCapability {
    model_id,
    source_model_sha256,
    tokenizer_id,
    limits,
    binding_digest,
    inventory,
    encode,
    // ...
}

Plugins resolve:

mesh_native_serving_plugin_v2

The structured encoder accepts bounded, tagged pieces:

enum InputPiece {
    Bytes(Vec<u8>),
    Control { descriptor: Vec<u8> },
}

Tokenizer::encode(EncodeRequest) -> Result<EncodeResponse, TokenizerError>

The contract:

  • accepts ordinary byte pieces without replacement-decoding;
  • carries control descriptors opaquely;
  • validates identity and input/output limits;
  • returns explicit errors for invalid or unsupported input;
  • is prepared and called outside latency-sensitive proposal callbacks.

Implementation

  • Replace the native-serving plugin ABI table with breaking ABI V2.
  • Add the bounded skippy-tokenizer structured encode API and typed errors.
  • Bind the host capability to the active model/tokenizer.
  • Pass the capability through native plugin activation and retain it for the plugin lifetime.
  • Keep the existing text/tokenization REST route as an out-of-band adapter.
  • Add focused API, host, tokenizer, and backend tests.
  • Update the related documentation.

Validation

  • cargo fmt --all -- --check
  • cargo clippy -p skippy-tokenizer -p skippy-server -p mesh-native-serving-plugin-api -p mesh-native-serving-plugin-host --all-targets -- -D warnings
  • cargo test -p skippy-tokenizer -p skippy-server -p mesh-native-serving-plugin-api -p mesh-native-serving-plugin-host
  • cargo check --workspace

This is intentionally a breaking replacement for V1. V1 plugins must migrate to mesh_native_serving_plugin_v2; backward compatibility is not provided.

Summary by CodeRabbit

  • New Features
    • Added bounded, structured tokenizer encoding for byte sequences and opaque control-token descriptors.
    • Added tokenizer identity, limits, binding validation, and machine-readable errors.
    • Native serving plugins now receive a model-bound tokenizer capability during preparation.
  • Compatibility
    • Updated the native serving plugin contract to ABI V2.
  • Bug Fixes
    • Unsupported, invalid, or non-lossless input is rejected explicitly instead of silently transformed.
    • Added validation for tokenizer inventories, input sizes, piece counts, and encoded output limits.
    • Improved standalone N-gram serving and speculative mode handling.
  • Documentation
    • Updated plugin and tokenizer documentation with the new capability and encoding behavior.

@coderabbitai

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

The tokenizer contract now supports bounded structured encoding of byte runs and opaque control descriptors. Native serving plugins use a model-bound tokenizer capability through ABI V2, with host-side validation, callback handling, binding metadata, and explicit error statuses. Standalone N-gram speculation is also enabled for non-staged serving when native MTP is disabled.

Changes

Tokenizer capability and ABI integration

Layer / File(s) Summary
Structured tokenizer contract
crates/skippy-tokenizer/src/lib.rs, crates/skippy-tokenizer/README.md
Adds bounded byte/control input pieces, encode requests and responses, tokenizer trait support, and machine-readable errors.
Server tokenizer capability and binding
crates/skippy-server/src/tokenizer.rs, crates/skippy-server/src/tokenizer/binding.rs, crates/skippy-server/README.md
Implements structured encoding, validation, binding digests, GGUF inventory construction, HTTP error mapping, and encoding tests.
Native serving plugin ABI V2
crates/mesh-native-serving-plugin-api/src/lib.rs, crates/mesh-native-serving-plugin-api/README.md
Replaces ABI V1 with V2 and adds the model-bound tokenizer capability, limits, opaque descriptors, callback statuses, and activation wiring.
Host capability and plugin loading
crates/mesh-native-serving-plugin-host/src/*, crates/mesh-native-serving-plugin-host/Cargo.toml, crates/mesh-native-serving-plugin-host/README.md
Constructs and retains the tokenizer capability, validates inventory and callback data, maps results to ABI statuses, and loads ABI V2 plugins.

Standalone N-gram speculation

Layer / File(s) Summary
Standalone N-gram resolution
crates/mesh-llm-host-runtime/src/inference/skippy/resolver/translation.rs, crates/mesh-llm-host-runtime/src/inference/skippy/resolver/native_mtp_tests.rs
Allows standalone N-gram mode when native MTP is disabled and updates the test to validate successful direct serving.

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

Sequence Diagram(s)

sequenceDiagram
  participant Plugin
  participant HostTokenizerCapability
  participant TokenizerCapability
  participant TokenizerSource
  Plugin->>HostTokenizerCapability: invoke encode callback
  HostTokenizerCapability->>TokenizerCapability: validate pieces and limits
  TokenizerCapability->>TokenizerSource: encode structured input
  TokenizerSource-->>TokenizerCapability: return token IDs
  TokenizerCapability-->>HostTokenizerCapability: return response
  HostTokenizerCapability-->>Plugin: write tokens and return status
Loading

Possibly related PRs

Suggested labels: experimental

Suggested reviewers: ndizazzo, michaelneale

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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: exposing a model-bound tokenizer capability for Mesh.
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/cacheline-tokenizer-capability

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.

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

🧹 Nitpick comments (1)
crates/skippy-tokenizer/src/lib.rs (1)

15-15: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Tie the piece bound to the ABI constant.

MAX_TOKENIZE_PIECES is 4,096 here and MAX_TOKENIZER_INPUT_PIECES is 4,096 in crates/mesh-native-serving-plugin-api/src/lib.rs. The host checks the ABI constant and the server checks this constant. If one value changes, the two layers report different statuses for the same request. Add a compile-time assertion in crates/mesh-native-serving-plugin-host/src/lib.rs so the values cannot drift.

♻️ Suggested assertion in the host crate
const _: () = assert!(
    abi::MAX_TOKENIZER_INPUT_PIECES == skippy_tokenizer::MAX_TOKENIZE_PIECES,
    "ABI and tokenizer piece bounds must match",
);
🤖 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-tokenizer/src/lib.rs` at line 15, Add a compile-time assertion
in the host crate’s lib module comparing abi::MAX_TOKENIZER_INPUT_PIECES with
skippy_tokenizer::MAX_TOKENIZE_PIECES, and fail compilation with a clear message
when they differ. Keep both existing constants unchanged.
🤖 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 266-340: The tokenizer capability code in
crates/mesh-native-serving-plugin-host/src/lib.rs#L266-L340 must move into a new
tokenizer_capability module, including HostTokenizerCapability,
ActivationInventory, encode_tokenizer, write_encode_output, encode_status, and
the inventory test; update module visibility/imports while preserving behavior.
The binding helpers in crates/skippy-server/src/tokenizer.rs#L441-L474 must move
into a tokenizer/binding module, including inventory_from_stage,
tokenizer_binding_digest, update_string, and their related tests; update
references and keep each new module under 1,000 lines.
- Around line 219-263: Update TokenizerCapability::new so the Arc<Self> is
created before initializing abi.inventory and abi.context, then use Arc::get_mut
to assign both pointers from the finalized Arc allocation. Preserve the existing
inventory_view and tokenizer references, ensuring encode_tokenizer and
activation reads access memory rooted in the returned Arc.

In `@crates/skippy-server/src/tokenizer.rs`:
- Around line 157-184: Update encode to coalesce each consecutive run of
InputPiece::Bytes before UTF-8/NUL validation and tokenization, preserving the
unsupported-input error for non-byte pieces. Tokenize each merged run once so
split UTF-8 characters and token IDs match a single-piece input, and avoid
calling tokenize with a zero remaining limit by returning once max_tokens is
reached or otherwise passing the actual positive bound. Add a test comparing
token IDs for a byte run split across two pieces with the equivalent
single-piece input.

---

Nitpick comments:
In `@crates/skippy-tokenizer/src/lib.rs`:
- Line 15: Add a compile-time assertion in the host crate’s lib module comparing
abi::MAX_TOKENIZER_INPUT_PIECES with skippy_tokenizer::MAX_TOKENIZE_PIECES, and
fail compilation with a clear message when they differ. Keep both existing
constants unchanged.
🪄 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: 7a4f83c6-0b7a-4ca5-869e-0981b8950d5e

📥 Commits

Reviewing files that changed from the base of the PR and between f4d530c and 3637c05.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • crates/mesh-native-serving-plugin-api/README.md
  • crates/mesh-native-serving-plugin-api/src/lib.rs
  • crates/mesh-native-serving-plugin-host/Cargo.toml
  • crates/mesh-native-serving-plugin-host/README.md
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/mesh-native-serving-plugin-host/src/test_support.rs
  • crates/skippy-server/README.md
  • crates/skippy-server/src/tokenizer.rs
  • crates/skippy-tokenizer/README.md
  • crates/skippy-tokenizer/src/lib.rs

Comment thread crates/mesh-native-serving-plugin-host/src/lib.rs Outdated
Comment thread crates/mesh-native-serving-plugin-host/src/lib.rs Outdated
Comment thread crates/skippy-server/src/tokenizer.rs

@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

🧹 Nitpick comments (6)
crates/skippy-server/src/tokenizer/binding.rs (2)

21-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the failure reason instead of collapsing every path to None.

Five distinct root causes return None here: no GGUF file on disk, an unscannable GGUF, a token-ID conversion overflow, a token_pieces error, and a vocabulary/piece length mismatch. The host converts None into a single message, "bound model does not expose a tokenizer inventory" (crates/mesh-native-serving-plugin-host/src/tokenizer_capability.rs Lines 84-88), and then fails plugin activation.

Line 31 also discards the TokenizerCapabilityError from source.token_pieces. A transient RuntimeUnavailable becomes indistinguishable from a permanently unsupported model.

Return Result<TokenizerInventory, TokenizerCapabilityError> (or log the reason at each early return) so an operator can tell these cases apart from the activation failure alone.

🤖 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/tokenizer/binding.rs` around lines 21 - 34, The
inventory_from_stage function currently collapses missing files, scan failures,
ID conversion overflow, token_pieces errors, and length mismatches into None.
Change its return type and each early-return path to preserve distinct
TokenizerCapabilityError reasons, including propagating the error from
source.token_pieces, so the host can distinguish transient failures from
unsupported inventories.

1-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the relocated test here and add digest coverage.

This new module has no test module. The source_gguf_path test stayed in crates/skippy-server/src/tokenizer.rs (Lines 882-896), and tokenizer_binding_digest has no direct test.

Add a #[cfg(test)] module here that covers digest determinism for identical inputs and digest change when a token ID, a piece kind, or piece bytes change. Move the source_gguf_path fallback test into this module.

As per coding guidelines: "When modifying a source file over 1,000 lines, extract any separable responsibility into a semantically named module, keep the new file under 1,000 lines, and move relevant tests with the extracted behavior when practical."

🤖 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/tokenizer/binding.rs` around lines 1 - 8, Add a
#[cfg(test)] module in binding.rs covering tokenizer_binding_digest determinism
for identical inputs and digest changes when token IDs, piece kinds, or piece
bytes differ. Move the source_gguf_path fallback test from tokenizer.rs into
this module, preserving its existing behavior and using the binding module’s
relevant symbols.

Source: Coding guidelines

crates/mesh-native-serving-plugin-host/src/lib.rs (1)

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

Document that this field is load-bearing, and drop the underscore prefix.

The _ prefix marks a field as unused. This field is not unused: context.tokenizer_capability at Line 104 points into this Arc<HostTokenizerCapability> allocation, and the plugin keeps that pointer for its whole active lifetime. If a later change removes the field as dead state, the plugin dereferences freed memory.

Rename the field to tokenizer_capability and add a comment on the ActivePlugin field declaration that states the raw ABI pointers handed to the plugin remain valid only while this field is alive.

🤖 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` at line 129, Rename the
ActivePlugin field from _tokenizer_capability to tokenizer_capability and update
its initializer and any references accordingly. Add a field-level comment
explaining that the raw ABI pointers provided to the plugin remain valid only
while this Arc<HostTokenizerCapability> is alive.
crates/skippy-server/src/tokenizer.rs (1)

606-631: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Collapse the two loops and consider making the stub content-sensitive.

The check at Lines 611-618 already rejects every InputPiece::Control, so the let ... else at Lines 620-622 is unreachable. A single match over the pieces removes the dead branch.

The stub also ignores piece content and returns a fixed token list. A stub that derives tokens from the merged bytes would let a test assert that a byte run split across two pieces produces the same token IDs as the single-piece input, which guards the segmentation behavior of encode.

♻️ Proposed refactor
     fn encode(
         &self,
         pieces: &[InputPiece],
         max_tokens: usize,
     ) -> Result<Vec<i32>, TokenizerCapabilityError> {
-        if pieces
-            .iter()
-            .any(|piece| matches!(piece, InputPiece::Control { .. }))
-        {
-            return Err(TokenizerCapabilityError::UnsupportedInput {
-                reason: "recording source does not support controls".to_owned(),
-            });
-        }
-        for piece in pieces {
-            let InputPiece::Bytes(bytes) = piece else {
-                unreachable!();
-            };
-            std::str::from_utf8(bytes).map_err(|_| {
-                TokenizerCapabilityError::UnsupportedInput {
-                    reason: "recording source only accepts UTF-8".to_owned(),
-                }
-            })?;
-        }
-        self.tokenize("", false, max_tokens)
+        let mut merged = Vec::new();
+        for piece in pieces {
+            match piece {
+                InputPiece::Bytes(bytes) => merged.extend_from_slice(bytes),
+                InputPiece::Control { .. } => {
+                    return Err(TokenizerCapabilityError::UnsupportedInput {
+                        reason: "recording source does not support controls".to_owned(),
+                    });
+                }
+            }
+        }
+        std::str::from_utf8(&merged).map_err(|_| {
+            TokenizerCapabilityError::UnsupportedInput {
+                reason: "recording source only accepts UTF-8".to_owned(),
+            }
+        })?;
+        self.tokenize("", false, max_tokens)
     }
🤖 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/tokenizer.rs` around lines 606 - 631, Update encode
to validate pieces in one match-based pass, rejecting controls and invalid UTF-8
without retaining the unreachable InputPiece::Bytes else branch. Merge the
validated byte contents in order and derive the returned token IDs from that
merged input instead of always calling tokenize with an empty string, while
preserving max_tokens handling and existing error types.
crates/mesh-native-serving-plugin-host/src/tokenizer_capability.rs (2)

69-80: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Correct the SAFETY comment to state the real aliasing invariant.

Two details in the comment do not match the code.

The value is an Arc<Self>, not a boxed value.

More importantly, abi.inventory does not only reference self.inventory. ActivationInventory::from_inventory builds each ByteSlice at Line 52 from the &abi::TokenizerInventory argument, so self.inventory.entries[i].bytes points into heap buffers owned by self.tokenizer. The soundness condition is therefore that self.tokenizer and its inventory are never mutated, reallocated, or dropped while the plugin holds abi.inventory.

State that condition explicitly. Note also that tokenizer is declared before inventory, so it drops first; that is safe only because dropping Vec<TokenizerInventoryEntry> never dereferences a ByteSlice.

🤖 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/tokenizer_capability.rs` around
lines 69 - 80, Update the SAFETY comment above the Send/Sync implementations for
HostTokenizerCapability to refer to the Arc<Self> lifetime rather than a boxed
value, and explicitly state that tokenizer and its inventory remain immutable,
non-reallocated, and alive while the plugin holds abi.inventory because its
ByteSlice values reference tokenizer-owned buffers. Note that tokenizer drops
before inventory and this is safe because dropping Vec<TokenizerInventoryEntry>
does not dereference ByteSlice.

255-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the validation and status-mapping paths.

This module is the FFI trust boundary, and only the ActivationInventory happy path has a test. Add tests for these cases:

  • ActivationInventory::from_inventory rejects a wrong schema_version, an empty token list, a list over MAX_TOKENIZER_INVENTORY_ENTRIES, non-increasing IDs, and an empty control descriptor.
  • write_encode_output returns OUTPUT_TOO_SMALL and still writes the required length, so a plugin can size its buffer and retry.
  • encode_status maps each TokenizerError variant to the intended TokenizerEncodeStatus.
  • HostTokenizerCapability::new exposes identity bytes that match the bound tokenizer identity, which also guards the dangling-slice defect flagged on Lines 118-127.
🤖 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/tokenizer_capability.rs` around
lines 255 - 284, Add focused tests in the existing tests module covering the
validation failures in ActivationInventory::from_inventory: incorrect
schema_version, empty tokens, more than MAX_TOKENIZER_INVENTORY_ENTRIES,
non-increasing token IDs, and an empty control descriptor. Add a
write_encode_output test that verifies OUTPUT_TOO_SMALL and the required length
is populated for retry. Test every TokenizerError variant through encode_status
against its expected TokenizerEncodeStatus, and verify
HostTokenizerCapability::new exposes identity bytes equal to the bound tokenizer
identity, retaining the returned capability while inspecting the identity slice.
🤖 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/tokenizer_capability.rs`:
- Around line 140-152: In the tokenizer encoding function, after validating that
output_length is non-null, initialize *output_length to zero before the
remaining INVALID_ARGUMENT checks. Remove the redundant zero writes from the
LIMIT_EXCEEDED paths, and update the documented ABI contract in the tokenizer
plugin API README to state that non-OK results report an output length of zero.
- Around line 118-127: Update the tokenizer capability constructor around
TokenizerCapability creation so abi.model_id, abi.source_model_sha256, and
abi.tokenizer_id are built from the tokenizer parameter before it is moved into
the Arc, ensuring their buffers remain valid for the capability lifetime. Keep
inner.inventory_view and abi.context unchanged, and add a test that reads all
three ByteSlice fields from the constructed capability and compares them with
the expected identity strings.

---

Nitpick comments:
In `@crates/mesh-native-serving-plugin-host/src/lib.rs`:
- Line 129: Rename the ActivePlugin field from _tokenizer_capability to
tokenizer_capability and update its initializer and any references accordingly.
Add a field-level comment explaining that the raw ABI pointers provided to the
plugin remain valid only while this Arc<HostTokenizerCapability> is alive.

In `@crates/mesh-native-serving-plugin-host/src/tokenizer_capability.rs`:
- Around line 69-80: Update the SAFETY comment above the Send/Sync
implementations for HostTokenizerCapability to refer to the Arc<Self> lifetime
rather than a boxed value, and explicitly state that tokenizer and its inventory
remain immutable, non-reallocated, and alive while the plugin holds
abi.inventory because its ByteSlice values reference tokenizer-owned buffers.
Note that tokenizer drops before inventory and this is safe because dropping
Vec<TokenizerInventoryEntry> does not dereference ByteSlice.
- Around line 255-284: Add focused tests in the existing tests module covering
the validation failures in ActivationInventory::from_inventory: incorrect
schema_version, empty tokens, more than MAX_TOKENIZER_INVENTORY_ENTRIES,
non-increasing token IDs, and an empty control descriptor. Add a
write_encode_output test that verifies OUTPUT_TOO_SMALL and the required length
is populated for retry. Test every TokenizerError variant through encode_status
against its expected TokenizerEncodeStatus, and verify
HostTokenizerCapability::new exposes identity bytes equal to the bound tokenizer
identity, retaining the returned capability while inspecting the identity slice.

In `@crates/skippy-server/src/tokenizer.rs`:
- Around line 606-631: Update encode to validate pieces in one match-based pass,
rejecting controls and invalid UTF-8 without retaining the unreachable
InputPiece::Bytes else branch. Merge the validated byte contents in order and
derive the returned token IDs from that merged input instead of always calling
tokenize with an empty string, while preserving max_tokens handling and existing
error types.

In `@crates/skippy-server/src/tokenizer/binding.rs`:
- Around line 21-34: The inventory_from_stage function currently collapses
missing files, scan failures, ID conversion overflow, token_pieces errors, and
length mismatches into None. Change its return type and each early-return path
to preserve distinct TokenizerCapabilityError reasons, including propagating the
error from source.token_pieces, so the host can distinguish transient failures
from unsupported inventories.
- Around line 1-8: Add a #[cfg(test)] module in binding.rs covering
tokenizer_binding_digest determinism for identical inputs and digest changes
when token IDs, piece kinds, or piece bytes differ. Move the source_gguf_path
fallback test from tokenizer.rs into this module, preserving its existing
behavior and using the binding module’s relevant symbols.
🪄 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: 9b68558b-63bc-494f-90df-d0c31ff14180

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9d0fc and ebab9d9.

📒 Files selected for processing (4)
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/mesh-native-serving-plugin-host/src/tokenizer_capability.rs
  • crates/skippy-server/src/tokenizer.rs
  • crates/skippy-server/src/tokenizer/binding.rs

Comment thread crates/mesh-native-serving-plugin-host/src/tokenizer_capability.rs
Comment thread crates/mesh-native-serving-plugin-host/src/tokenizer_capability.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: 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/mesh-llm-host-runtime/src/inference/skippy/resolver/native_mtp_tests.rs`:
- Around line 792-800: The existing test only validates resolver translation via
to_embedded_openai_args and does not exercise native plugin ingress. Add a
direct-serving integration test using a fake serving_hooks_factory, send a
greedy request through the loaded single-stage backend, and assert the plugin
receives propose and report callbacks with a target-authoritative verification
receipt; retain the existing NgramProposerKind::Suffix assertion as
resolver-only coverage.
🪄 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: 0e3812b2-ef7a-41bd-a49b-143e104445ca

📥 Commits

Reviewing files that changed from the base of the PR and between bf9eea0 and dda18a5.

📒 Files selected for processing (2)
  • crates/mesh-llm-host-runtime/src/inference/skippy/resolver/native_mtp_tests.rs
  • crates/mesh-llm-host-runtime/src/inference/skippy/resolver/translation.rs

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

🤖 Reviewed on micn's behalf.

Verified all CodeRabbit findings are fixed on the branch tip: the identity ByteSlice use-after-free (now owned bytes wired via Arc), byte-piece concatenation before UTF-8 validation/tokenization, consistent *output_length = 0 on all error paths, and the module splits bringing both files under 1,000 lines. CI is fully green.

The V1 → V2 plugin ABI break is acknowledged and accepted — fine to strand V1 plugins in this case.

Looks good. ✅

@i386
i386 merged commit f2b8727 into main Aug 11, 2026
45 checks passed
@i386
i386 deleted the agent/cacheline-tokenizer-capability branch August 11, 2026 07:36
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.

2 participants