Skip to content

feat(skippy): expose authoritative generation lifecycle events - #1149

Merged
ndizazzo merged 7 commits into
mainfrom
agent/generation-lifecycle-events
Aug 4, 2026
Merged

feat(skippy): expose authoritative generation lifecycle events#1149
ndizazzo merged 7 commits into
mainfrom
agent/generation-lifecycle-events

Conversation

@i386

@i386 i386 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Use case

A host application integrating with Skippy may need accurate progress metrics, request accounting, speculative-resolution telemetry, or cleanup on cancellation. A benchmark runner can use the same events to separate committed generation from attempted work without parsing logs.

Change

Optional typed hooks now report authoritative generation transitions: start, committed token progress, proposal resolution, completion, and abort. Events carry correlation data and are emitted where Skippy commits each transition.

Skippy continues to own model execution, tokenization, verification, scheduling, and cleanup. The hooks are absent by default and cannot replace or control those responsibilities.

This PR deliberately does not expose an alternate Mesh launcher or an injected local-model entrypoint. The normal Mesh executable and serving startup path remain unchanged; a separate focused draft will propose any native plugin loading boundary.

Emitting typed events at the authoritative transition points gives integrations a stable view of what actually happened without duplicating generation logic.

Validation

  • just with-lld cargo check -p mesh-llm -p mesh-llm-host-runtime -p skippy-server
  • just with-lld cargo test -p skippy-server (352 passed)
  • Launcher symbols removed and the working-tree diff passes git diff --check.

Summary by CodeRabbit

  • New Features

    • Added configurable serving hooks for generation receipts and linear-proposal integrations.
    • Added native serving plugin support for local model deployments, including deadline-aware proposal handling.
    • Added agent-session identity tracking across supported OpenAI requests.
    • Generation receipts now include exact prompt tokens, first-token latency, per-token timing, and lifecycle events.
  • Bug Fixes

    • Improved handling of failed or incomplete generations.
    • Added validation for inconsistent linear-proposal boundaries and plugin configuration.
    • Improved stage preparation timeout accuracy based on selected artifacts.
  • Documentation

    • Added guidance for configuring native serving integrations.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Skippy loading now supports tokenizer-aware serving hooks and native serving plugins. OpenAI requests propagate agent-session identities. Generation receipts track lifecycle events and timing. Linear proposal queries use validated position metadata.

Changes

Serving hooks and native plugin integration

Layer / File(s) Summary
Native plugin ABI and host execution
crates/mesh-native-serving-plugin-api/*, crates/mesh-native-serving-plugin-host/*
Adds a versioned C-compatible ABI and a validated, queued host for lifecycle and proposal callbacks.
Serving-hook factories and runtime loading
crates/skippy-server/src/serving_hooks.rs, crates/mesh-llm-host-runtime/src/inference/skippy/mod.rs, crates/mesh-llm-host-runtime/src/runtime/*
Creates hooks from tokenizer capabilities and forwards factories through direct, split, and stage-0 loading paths.
Native plugin configuration and stage preparation
crates/mesh-llm-cli/src/parser/commands.rs, crates/mesh-llm-host-runtime/src/runtime/options.rs, crates/mesh-llm-host-runtime/src/runtime/local_split/*, scripts/*, website/src/docs/pages/CLI.md
Adds plugin settings, validates required options, forwards runtime configuration, and calculates split preparation timeouts from selected artifact sizes.
Agent-session request propagation
crates/openai-frontend/src/*, crates/skippy-server/src/frontend/backend.rs
Validates session identities, captures configured headers and Responses conversation IDs, rejects conflicts, and forwards session IDs into generation identifiers.
Timed generation receipt lifecycle
crates/skippy-server/src/frontend/generation_receipt.rs, crates/skippy-server/src/frontend/generation_flow.rs, crates/skippy-server/src/frontend/local_generation/*
Adds lifecycle ingress, asynchronous delivery, prompt token evidence, agent-session metadata, timing data, and abort handling.
Bounded linear proposal queries
crates/skippy-server/src/frontend/linear_proposal.rs
Uses owned prompt and committed-token counts and validates prompt/decode boundaries before proposal ingress.

Estimated code review effort: 5 (Critical) | ~100 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OpenAiFrontend
  participant StageOpenAiBackend
  participant GenerationReceiptIngress
  participant NativeServingPluginHost
  participant NativeServingPluginV1
  OpenAiFrontend->>StageOpenAiBackend: dispatch request with agent session
  StageOpenAiBackend->>GenerationReceiptIngress: submit lifecycle observations
  GenerationReceiptIngress->>NativeServingPluginHost: queue lifecycle or proposal event
  NativeServingPluginHost->>NativeServingPluginV1: invoke ABI callback
  NativeServingPluginV1-->>NativeServingPluginHost: return lifecycle or proposal result
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 35.78% 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 PR's primary change: exposing authoritative generation lifecycle events in Skippy.
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/generation-lifecycle-events

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 2, 2026 06:28
@github-actions

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

@github-actions
github-actions Bot requested a review from michaelneale August 2, 2026 06:28
@i386
i386 force-pushed the agent/generation-lifecycle-events branch from 18f90ff to 3b40df4 Compare August 2, 2026 07:16
@i386
i386 force-pushed the agent/generation-lifecycle-events branch from 3b40df4 to 608ea63 Compare August 2, 2026 07:37
@ndizazzo

ndizazzo commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

@i386 seems like you touched on this before I could get to it, but I have a more substantial event-hook lifecycle system planned.

The work I have planned converts native llama.cpp and Rust runtime observations into validated, typed events instead of relying on parsed log text, and adds missing hooks for lifecycle (as this PR does).

A bounded Rust event pipeline reduces those into backend-neutral lifecycle and availability state for the CLI, TUI, API, telemetry, and diagnostics.... wires up to literally everything and anything that needs it. Native execution remains authoritative, while event consumers stay nonblocking, privacy-safe, and unable to control inference.

I opened up an issue #1167 that outlines the plan at a high level, and it'd be rad if you could have an agent shape this specific PR in a way that would make it integrate with the plan smoothly

@ndizazzo

ndizazzo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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

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

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

Remove new crate-root forwarding re-exports.

These exports add public paths that do not identify the owning subsystem. Export the APIs from their owning skippy_server modules and import them directly at consumers.

  • crates/skippy-server/src/lib.rs#L32-L42: expose lifecycle types from frontend and serving-hook types from a public owning module instead of new crate-root re-exports.
  • crates/mesh-llm-host-runtime/src/lib.rs#L40-L40: remove the forwarding exports and import the skippy_server owning-module types where the host runtime uses them.

As per coding guidelines: “Minimize crate-root re-exports. Temporary compatibility re-exports are allowed during refactors, but new code should import from the owning module directly and transitional re-exports should be removed afterward.” As per coding guidelines: “Keep the host-runtime lib.rs slim and use it as an entry point rather than a general-purpose code container.”

🤖 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/lib.rs` around lines 32 - 42, Remove the new
crate-root forwarding re-exports from crates/skippy-server/src/lib.rs lines
32-42, exposing lifecycle APIs through frontend and serving-hook APIs through
their public owning module instead. In crates/mesh-llm-host-runtime/src/lib.rs
line 40, remove the forwarding exports and update host-runtime consumers to
import those types directly from the corresponding skippy_server owning modules;
keep both crate-root entry points slim.

Source: Coding guidelines

🤖 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/runtime/local_split/loading.rs`:
- Line 199: Update SplitGenerationLoadSpec and the split stage-0 loading path in
load_stage0_runtime_options_with_openai_args_and_open_events to carry and pass
the serving hooks factory instead of None, matching
start_local_layer_package_model; if split serving intentionally excludes
lifecycle hooks, explicitly document that contract and add coverage for the
hook-free stage-0 behavior.

In `@crates/skippy-server/src/frontend/generation_flow.rs`:
- Around line 447-459: Ensure split multimodal generations also emit the
complete lifecycle events: begin, record, committed, and abort. Update the
shared generation wrapper around the current GenerationStart emission, or add
equivalent receipt observation and finalization within
generate_split_multimodal_text, while preserving existing behavior for non-split
generations.

In `@crates/skippy-server/src/frontend/generation_receipt.rs`:
- Around line 97-111: The GenerationReceiptSink callbacks currently run
synchronously on the generation path and propagate observer failures as
OpenAiError. Update the call sites around local_generation and generation_flow
to enqueue lifecycle events through a bounded, nonblocking delivery mechanism
that preserves per-request ordering, and report queue or callback failures via
telemetry or explicit delivery state rather than model execution errors. Adjust
GenerationReceiptSink integration as needed while preserving the
begin/committed/record-or-abort lifecycle.

In `@crates/skippy-server/src/frontend/linear_proposal.rs`:
- Around line 804-829: Extract the entire #[cfg(test)] mod tests from the linear
proposal implementation into a sibling module named linear_proposal_tests.rs,
including FakeIngress and all related fixtures and test helpers. Leave only the
test-module declaration in the original file, and adjust visibility/imports so
the moved tests continue to exercise the same behavior while keeping the new
module under 1,000 lines.

In `@crates/skippy-server/src/frontend/local_generation.rs`:
- Around line 1124-1133: Update abort_after_failed_receipt so a record error
does not trigger config.sink().abort, since the receipt may already have been
persisted; preserve the receipt_result return value while preventing both
terminal events from being emitted for one begun request/session. Align the
terminal delivery behavior with GenerationReceiptSink’s acknowledged outcome
contract, using durable idempotency or outcome sequencing if needed.

In `@crates/skippy-server/src/serving_hooks.rs`:
- Around line 28-46: Update ModelServingHooks::new and the surrounding API to
allow configuring generation_receipt and linear_proposal_ingress independently,
adding builder methods or single-purpose constructors while preserving the
existing both-hooks convenience path. Ensure callers can enable either hook
without supplying a dummy configuration, and retain the optional getter
behavior.

---

Nitpick comments:
In `@crates/skippy-server/src/lib.rs`:
- Around line 32-42: Remove the new crate-root forwarding re-exports from
crates/skippy-server/src/lib.rs lines 32-42, exposing lifecycle APIs through
frontend and serving-hook APIs through their public owning module instead. In
crates/mesh-llm-host-runtime/src/lib.rs line 40, remove the forwarding exports
and update host-runtime consumers to import those types directly from the
corresponding skippy_server owning modules; keep both crate-root entry points
slim.
🪄 Autofix (Beta)

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: 30f81668-4b8a-4de2-9a5c-fd5b89a5c009

📥 Commits

Reviewing files that changed from the base of the PR and between a2b6e4e and 1d004fa.

📒 Files selected for processing (13)
  • crates/mesh-llm-host-runtime/src/inference/skippy/mod.rs
  • crates/mesh-llm-host-runtime/src/lib.rs
  • crates/mesh-llm-host-runtime/src/runtime/local.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_model_only.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs
  • crates/skippy-server/src/frontend.rs
  • crates/skippy-server/src/frontend/generation/cache_hints.rs
  • crates/skippy-server/src/frontend/generation_flow.rs
  • crates/skippy-server/src/frontend/generation_receipt.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/local_generation.rs
  • crates/skippy-server/src/lib.rs
  • crates/skippy-server/src/serving_hooks.rs

Comment thread crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs Outdated
Comment thread crates/skippy-server/src/frontend/generation_flow.rs
Comment thread crates/skippy-server/src/frontend/generation_receipt.rs
Comment thread crates/skippy-server/src/frontend/linear_proposal.rs
Comment thread crates/skippy-server/src/frontend/local_generation.rs Outdated
Comment thread crates/skippy-server/src/serving_hooks.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.

nice

@ndizazzo
ndizazzo force-pushed the agent/generation-lifecycle-events branch from 18aeb33 to 5303997 Compare August 4, 2026 18:59
Base automatically changed from agent/model-only-openai-serving to main August 4, 2026 19:49
i386 and others added 6 commits August 4, 2026 15:49
* feat(openai): propagate stable agent session identity

* feat(mesh): load deadline-safe native serving plugins

* docs: document native serving integrations

* fix(skippy): preserve native proposal query contract

* fix(skippy): compile native proposal plugin tests

* fix(plugin): use public proposal query constructor
@ndizazzo
ndizazzo force-pushed the agent/generation-lifecycle-events branch from e979b9f to 7ef248f Compare August 4, 2026 19:49

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

Caution

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

⚠️ Outside diff range comments (1)
crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs (1)

342-352: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Budget timeout from selected transfer artifacts, not uniform layer weights.

stage_source_prepare_timeout estimates layer-package timeouts as source_model_bytes / layer_count * stage_layers, but artifact transfer fetches required_stage_package_artifacts(...) for layer_start..layer_end, plus manifest/embeddings/outputs/projectors when selected. A stage that covers the heavy manifest or first/last artifacts can exceed the configured transfer rate and hit this timeout. Pass selected artifact byte sizes to the timeout formula, or derive it from the artifact selection so uneven packages do not under-budget.

🤖 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-llm-host-runtime/src/runtime/local_split/loading.rs` around lines
342 - 352, Update stage_source_prepare_timeout to calculate the transfer budget
from the byte sizes of the artifacts selected by
required_stage_package_artifacts for the stage range, including optional
manifest, embeddings, outputs, and projectors when selected, rather than
distributing source_model_bytes uniformly across layers. Use the summed
selected-artifact bytes with the existing transfer-rate, allowance, and
minimum-timeout logic, and adjust callers to provide or derive that selection.
🧹 Nitpick comments (10)
crates/mesh-native-serving-plugin-host/src/lib.rs (1)

701-712: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tie the returned &str lifetime to the caller-provided data.

read_utf8<'a> produces an unbounded lifetime from a raw pointer. The compiler cannot check that the plugin table outlives the returned reference. The single current caller converts to String immediately, so behavior is correct today, but the signature invites an unsound use later.

Return String, or accept a lifetime-carrying argument that anchors 'a.

🤖 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 701 - 712,
Update read_utf8 so it no longer returns an unbounded borrowed &str from the raw
ByteSlice; return an owned String instead, preserving the existing null-pointer
validation and UTF-8 error context, and adjust its sole caller to use the owned
value while retaining the current behavior.
crates/mesh-llm-cli/src/parser/commands.rs (1)

501-503: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reject a zero deadline during parsing.

NativeServingPluginFactory::load rejects a zero proposal deadline at runtime. A value_parser range moves that failure to argument parsing and gives a clearer message.

♻️ Proposed refactor
     /// Mesh-enforced hard proposal deadline in milliseconds.
-    #[arg(long, hide = true, requires = "native_serving_plugin")]
+    #[arg(
+        long,
+        hide = true,
+        requires = "native_serving_plugin",
+        value_parser = clap::value_parser!(u64).range(1..)
+    )]
     pub native_serving_plugin_deadline_ms: Option<u64>,
🤖 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-llm-cli/src/parser/commands.rs` around lines 501 - 503, Update
the native_serving_plugin_deadline_ms argument definition in the CLI command
parser to use a value_parser that accepts only positive u64 values, rejecting
zero during argument parsing while preserving the existing optional and
dependency behavior.
crates/mesh-native-serving-plugin-api/src/lib.rs (1)

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

Add an explicit extension rule for the event structs.

ActivationContext and NativeServingPluginV1 both carry struct_size, so the host can validate their layout. The lifecycle and proposal event structs carry no size field. A later additive field in GenerationFinish or ProposalQuery therefore changes the layout with no way for a v1 plugin to detect it, and the only remaining option is a full ABI-version bump.

Either add a leading struct_size to each event struct, or document that every layout change requires a new NATIVE_SERVING_PLUGIN_ABI_V* table so v1 plugins keep working.

As per coding guidelines: "When changing the plugin protocol, preserve support for the previous version unless a breaking change is explicitly intended and confirmed."

♻️ Example: size-prefixed event structs
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Default)]
 pub struct GenerationStart {
+    pub struct_size: usize,
     pub request_id: u64,
     pub session_id: u64,
     pub agent_session_id: ByteSlice,
     pub prompt_token_ids: TokenSlice,
 }
🤖 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-api/src/lib.rs` around lines 152 - 204, Add
an explicit ABI extension strategy for the lifecycle and proposal event structs
shown here: preferably add a leading struct_size field to GenerationStart,
GenerationCommit, GenerationAbort, GenerationFinish, and ProposalQuery, matching
the existing size-validation approach used by ActivationContext and
NativeServingPluginV1. Ensure the host can validate truncated/older layouts so
additive fields remain compatible; otherwise document and implement a new ABI
table for every layout change.

Source: Coding guidelines

crates/openai-frontend/src/completions.rs (1)

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

Duplicated agent-session metadata helpers in two request types. ChatCompletionRequest and CompletionRequest each define the same two internal key constants and the same three methods. The shared root cause is the absence of one helper in common.rs. Two copies of the key names can drift, and drift would silently break the strip-then-set protection against body-supplied session keys.

  • crates/openai-frontend/src/completions.rs#L47-L74: replace set_agent_session, agent_session, and agent_session_source with calls to the shared helper in common.rs, and delete the local key constants at Lines 14-15.
  • crates/openai-frontend/src/chat.rs#L51-L78: apply the same replacement, and delete the local key constants at Lines 14-15.
🤖 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/openai-frontend/src/completions.rs` around lines 47 - 74, Centralize
the duplicated agent-session metadata logic by adding or reusing one shared
helper in common.rs. In crates/openai-frontend/src/completions.rs lines 47-74
and crates/openai-frontend/src/chat.rs lines 51-78, replace the local
set_agent_session, agent_session, and agent_session_source implementations with
the shared helper, and remove each file’s local agent-session key constants at
lines 14-15; preserve the strip-then-set behavior.
crates/openai-frontend/src/responses.rs (1)

552-586: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Capture ordering is correct.

The conversation read at Lines 563-565 runs before translate_openai_responses_input removes the field at Line 570, so the identity survives normalization.

One naming point: responses_conversation_cache_key now serves two purposes, the prompt-cache key and the agent-session identity. Rename it to responses_conversation_id so both call sites read correctly. The doc comment on AgentSessionSource in crates/openai-frontend/src/common.rs Lines 13-15 states the identity is not a prompt-cache key, while both values now derive from the same helper. Update that comment or the helper name.

🤖 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/openai-frontend/src/responses.rs` around lines 552 - 586, Rename the
shared helper responses_conversation_cache_key to responses_conversation_id and
update both its prompt-cache and agent-session call sites, including the usage
in the normalization flow around translate_openai_responses_input. Revise the
AgentSessionSource documentation in common.rs to reflect that the conversation
ID is used as agent-session identity and may also derive the prompt-cache key,
without changing the existing capture ordering.
crates/skippy-server/src/frontend/generation_receipt.rs (1)

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

Replace the source-text ordering test with a behavioral assertion.

This test reads local_generation/token_generation.rs with include_str! and asserts on the byte offsets of literal source fragments. Renaming a local variable or reformatting a call breaks the test without any behavior change. The fragment at Line 886 already encodes an exact expression, including the ? operator.

Assert the ordering through a recording GenerationLifecycleIngress instead. Drive one generation, then assert that the observed sequence is Started, Committed, and a terminal Completed or Aborted, and that cleanup ran last.

🤖 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/generation_receipt.rs` around lines 876 -
901, Replace
receipt_lifecycle_begins_before_generation_and_closes_before_cleanup’s
include_str!/source-offset checks with a behavioral test using a recording
GenerationLifecycleIngress. Drive one local generation and record lifecycle
events, then assert the sequence contains Started, Committed, and a terminal
Completed or Aborted event, with cleanup observed last; avoid asserting source
text or exact local expressions.
crates/skippy-server/src/lib.rs (1)

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

Align the re-export surface for the two ModelServingHooks parameter types.

ModelServingHooks::new takes GenerationReceiptConfig and LinearProposalIngressConfig. After this change, Line 33 still re-exports LinearProposalIngressConfig from the crate root, but GenerationReceiptConfig is reachable only through frontend. An external caller must therefore write skippy_server::LinearProposalIngressConfig and skippy_server::frontend::GenerationReceiptConfig in the same call.

Pick one convention for both types. The coding guidelines favor importing from the owning module, so removing LinearProposalIngressConfig from this list is consistent with the receipt-type removal.

As per coding guidelines: "Minimize crate-root re-exports. Temporary compatibility re-exports are allowed during refactors, but new code should import from the owning module directly and transitional re-exports should be removed afterward."

🤖 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/lib.rs` around lines 32 - 37, Remove
LinearProposalIngressConfig from the crate-root re-export list in lib.rs,
matching GenerationReceiptConfig’s module-owned access through frontend. Keep
the type available via its owning module so ModelServingHooks::new callers use
consistent import paths.

Source: Coding guidelines

crates/skippy-server/src/frontend/local_generation/token_generation.rs (1)

94-101: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider sharing the prompt token IDs instead of copying them twice.

Line 99 copies the prompt into a Box<[i32]> for GenerationStart. build_generation_receipt in crates/skippy-server/src/frontend/generation_receipt.rs, Line 455, copies the same prompt again for the receipt. Each configured request therefore performs two prompt-sized allocations on the generation path.

Storing the prompt as Arc<[i32]> in GenerationStart and GenerationReceipt removes both copies and keeps the exact token evidence. This is a public type change, so apply it only if the plugin ABI conversion in crates/mesh-native-serving-plugin-host/src/lib.rs stays unchanged.

🤖 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/token_generation.rs`
around lines 94 - 101, Change prompt token storage in GenerationStart and
GenerationReceipt to Arc<[i32]> so the existing prompt allocation can be shared
instead of copied by both receipt paths. Update the construction and propagation
in the surrounding generation flow, including build_generation_receipt, while
preserving the exact token evidence; verify the plugin ABI conversion in lib.rs
remains unchanged.
crates/skippy-server/src/frontend/generation_flow.rs (1)

636-640: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Two modified Rust files exceed the 1,000-line extraction threshold. This PR modifies both files, and each is already over 1,000 lines. The coding guidelines require extracting a separable responsibility into a semantically named module and keeping the new file under 1,000 lines. Both files contain a clearly separable flow that can move without changing behavior.

  • crates/skippy-server/src/frontend/generation_flow.rs#L636-L640: extract generate_split_multimodal_text into a module such as generation_flow/split_multimodal.rs, and move its tests with it.
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs#L84-L101: extract the linear-proposal decode branch, for example try_execute_linear_proposal and its helpers, into a module such as local_generation/linear_decode.rs, and move its tests with it.

As per coding guidelines: "When modifying a Rust 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."

🤖 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/generation_flow.rs` around lines 636 - 640,
Extract the split multimodal generation responsibility centered on
generate_split_multimodal_text from
crates/skippy-server/src/frontend/generation_flow.rs:636-640 into a semantically
named module such as generation_flow/split_multimodal.rs, moving its related
tests and preserving behavior; separately extract try_execute_linear_proposal
and its helpers from
crates/skippy-server/src/frontend/local_generation/token_generation.rs:84-101
into a module such as local_generation/linear_decode.rs, moving the associated
tests. Ensure both original files are reduced appropriately and each new module
remains under 1,000 lines.

Source: Coding guidelines

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

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

Assert that the sink failure is accounted.

This phase sets sink.fail and then verifies that session cleanup still happened. It does not verify the new accounting path. The sink pushes the receipt before it fails, so wait_for_receipts(&sink, 2) succeeds even if the worker never records the delivery failure.

Add an assertion on GenerationReceiptConfig::delivery_failures for the configured receipt config. That covers the asynchronous failure counter introduced in this PR.

🤖 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 208
- 234, Extend the failing-sink test around `sink.fail` and `failing_ids` to
assert that the configured `GenerationReceiptConfig::delivery_failures` counter
increments after the failed receipt is processed. Keep the existing session
cleanup assertion, and ensure the check occurs after waiting for asynchronous
receipt 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-llm-host-runtime/src/runtime/options.rs`:
- Around line 40-43: Wire the native serving plugin options into the local
startup path used by start_runtime_local_model, ensuring the configured plugin,
config, state, and deadline reach the serving hooks factory instead of always
passing None; if this path cannot consume them, remove the corresponding runtime
option fields and CLI handling.

In `@crates/mesh-native-serving-plugin-host/src/lib.rs`:
- Around line 633-676: Update convert_termination, convert_disposition, and
convert_discard_reason to add explicit mappings for all currently defined source
enum variants before their wildcard arms. Preserve the existing fallback
behavior only for genuinely unknown future variants, ensuring newly supported
authoritative outcomes map to their corresponding ABI values rather than
defaulting to CANCELLED, STOPPED, or EXECUTION_FAILED.
- Around line 124-127: Update the host initialization around
ModelServingHooks::new and LinearProposalIngressConfig::new to use an explicit,
appropriate proposal-token cap instead of usize::MAX, then modify
poll_until_deadline to allocate the token buffer once and reuse it across
polling iterations. Preserve existing proposal decoding behavior while
preventing per-proposal allocations sized directly by an unbounded request.
- Around line 333-345: Update the failure handling in the proposal polling flow:
call cancel_proposal for the current decision_id before returning an error from
the ProposalPollStatus::FAILED branch and when proposal_from_output rejects in
the READY branch. Preserve the existing status_error and parsing errors, and
ensure cancellation occurs before either error is returned.
- Around line 594-601: Update the error handling around the worker’s command
result and ensure_healthy so non-terminal lifecycle event failures do not
populate fatal_error or permanently reject later enqueue/propose calls. Keep
lifecycle_delivery_failures as the accounting mechanism for recoverable Begin,
Committed, and Report errors, while reserving fatal_error and terminal state for
errors that actually stop the worker.
- Around line 321-334: Replace the std::hint::spin_loop() pending path in the
proposal polling loop with a short sleep, capping the sleep duration by the time
remaining until deadline. Preserve immediate polling for non-PENDING statuses
and ensure the worker yields CPU while waiting so lifecycle commands can be
processed.

In `@crates/skippy-server/src/frontend/generation_flow.rs`:
- Around line 654-675: Move the GenerationStart emission block from before
request.lane_pool.checkout to immediately after the checkout succeeds,
preserving its existing configuration and token data. Keep checkout failure
propagation unchanged so no begin is emitted unless a lane is successfully
acquired.

In `@crates/skippy-server/src/frontend/generation_receipt.rs`:
- Around line 350-372: Update GenerationReceipt::record_token so budget and
non-monotonic timing violations are recorded rather than returned as errors.
Count rejected tokens, disable further receipt recording after the first
rejection, and allow both emit_token call sites to continue generation without
propagating bookkeeping failures; expose the count through delivery_failures or
an equivalent dedicated counter.
- Around line 173-195: Update the documentation for GenerationReceiptSink and
GenerationReceiptConfig::new to state that the queued adapter guarantees
ordering and at-most-once delivery, but not completeness or durable delivery
because pending observations may be lost during shutdown. Do not change the
implementation or add a JoinHandle unless implementing queue draining is
required; ensure accounting consumers are directed not to assume every
observation will be delivered.
- Around line 282-287: Remove the per-observation eprintln! from enqueue when
try_submit fails, while preserving the Relaxed submission_failures increment.
Use the existing delivery_failures accessor or telemetry path to expose the
accumulated count instead, and apply the same change to the analogous worker
submission path.

---

Outside diff comments:
In `@crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs`:
- Around line 342-352: Update stage_source_prepare_timeout to calculate the
transfer budget from the byte sizes of the artifacts selected by
required_stage_package_artifacts for the stage range, including optional
manifest, embeddings, outputs, and projectors when selected, rather than
distributing source_model_bytes uniformly across layers. Use the summed
selected-artifact bytes with the existing transfer-rate, allowance, and
minimum-timeout logic, and adjust callers to provide or derive that selection.

---

Nitpick comments:
In `@crates/mesh-llm-cli/src/parser/commands.rs`:
- Around line 501-503: Update the native_serving_plugin_deadline_ms argument
definition in the CLI command parser to use a value_parser that accepts only
positive u64 values, rejecting zero during argument parsing while preserving the
existing optional and dependency behavior.

In `@crates/mesh-native-serving-plugin-api/src/lib.rs`:
- Around line 152-204: Add an explicit ABI extension strategy for the lifecycle
and proposal event structs shown here: preferably add a leading struct_size
field to GenerationStart, GenerationCommit, GenerationAbort, GenerationFinish,
and ProposalQuery, matching the existing size-validation approach used by
ActivationContext and NativeServingPluginV1. Ensure the host can validate
truncated/older layouts so additive fields remain compatible; otherwise document
and implement a new ABI table for every layout change.

In `@crates/mesh-native-serving-plugin-host/src/lib.rs`:
- Around line 701-712: Update read_utf8 so it no longer returns an unbounded
borrowed &str from the raw ByteSlice; return an owned String instead, preserving
the existing null-pointer validation and UTF-8 error context, and adjust its
sole caller to use the owned value while retaining the current behavior.

In `@crates/openai-frontend/src/completions.rs`:
- Around line 47-74: Centralize the duplicated agent-session metadata logic by
adding or reusing one shared helper in common.rs. In
crates/openai-frontend/src/completions.rs lines 47-74 and
crates/openai-frontend/src/chat.rs lines 51-78, replace the local
set_agent_session, agent_session, and agent_session_source implementations with
the shared helper, and remove each file’s local agent-session key constants at
lines 14-15; preserve the strip-then-set behavior.

In `@crates/openai-frontend/src/responses.rs`:
- Around line 552-586: Rename the shared helper responses_conversation_cache_key
to responses_conversation_id and update both its prompt-cache and agent-session
call sites, including the usage in the normalization flow around
translate_openai_responses_input. Revise the AgentSessionSource documentation in
common.rs to reflect that the conversation ID is used as agent-session identity
and may also derive the prompt-cache key, without changing the existing capture
ordering.

In `@crates/skippy-server/src/frontend/generation_flow.rs`:
- Around line 636-640: Extract the split multimodal generation responsibility
centered on generate_split_multimodal_text from
crates/skippy-server/src/frontend/generation_flow.rs:636-640 into a semantically
named module such as generation_flow/split_multimodal.rs, moving its related
tests and preserving behavior; separately extract try_execute_linear_proposal
and its helpers from
crates/skippy-server/src/frontend/local_generation/token_generation.rs:84-101
into a module such as local_generation/linear_decode.rs, moving the associated
tests. Ensure both original files are reduced appropriately and each new module
remains under 1,000 lines.

In `@crates/skippy-server/src/frontend/generation_receipt.rs`:
- Around line 876-901: Replace
receipt_lifecycle_begins_before_generation_and_closes_before_cleanup’s
include_str!/source-offset checks with a behavioral test using a recording
GenerationLifecycleIngress. Drive one local generation and record lifecycle
events, then assert the sequence contains Started, Committed, and a terminal
Completed or Aborted event, with cleanup observed last; avoid asserting source
text or exact local expressions.

In `@crates/skippy-server/src/frontend/local_generation/tests.rs`:
- Around line 208-234: Extend the failing-sink test around `sink.fail` and
`failing_ids` to assert that the configured
`GenerationReceiptConfig::delivery_failures` counter increments after the failed
receipt is processed. Keep the existing session cleanup assertion, and ensure
the check occurs after waiting for asynchronous receipt handling.

In `@crates/skippy-server/src/frontend/local_generation/token_generation.rs`:
- Around line 94-101: Change prompt token storage in GenerationStart and
GenerationReceipt to Arc<[i32]> so the existing prompt allocation can be shared
instead of copied by both receipt paths. Update the construction and propagation
in the surrounding generation flow, including build_generation_receipt, while
preserving the exact token evidence; verify the plugin ABI conversion in lib.rs
remains unchanged.

In `@crates/skippy-server/src/lib.rs`:
- Around line 32-37: Remove LinearProposalIngressConfig from the crate-root
re-export list in lib.rs, matching GenerationReceiptConfig’s module-owned access
through frontend. Keep the type available via its owning module so
ModelServingHooks::new callers use consistent import paths.
🪄 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: 72d78506-c801-4aac-95c2-fad8d3ae0a67

📥 Commits

Reviewing files that changed from the base of the PR and between 1d004fa and 7ef248f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • Cargo.toml
  • crates/mesh-llm-cli/src/parser/commands.rs
  • crates/mesh-llm-host-runtime/Cargo.toml
  • crates/mesh-llm-host-runtime/src/inference/skippy/mod.rs
  • crates/mesh-llm-host-runtime/src/lib.rs
  • crates/mesh-llm-host-runtime/src/runtime/local.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_model_only.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/coordinator.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/test_support.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/tests.rs
  • crates/mesh-llm-host-runtime/src/runtime/options.rs
  • crates/mesh-llm/src/lib.rs
  • crates/mesh-native-serving-plugin-api/Cargo.toml
  • 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/openai-frontend/src/chat.rs
  • crates/openai-frontend/src/common.rs
  • crates/openai-frontend/src/completions.rs
  • crates/openai-frontend/src/lib.rs
  • crates/openai-frontend/src/responses.rs
  • crates/openai-frontend/src/router.rs
  • crates/skippy-server/src/frontend.rs
  • crates/skippy-server/src/frontend/backend.rs
  • crates/skippy-server/src/frontend/generation/cache_hints.rs
  • crates/skippy-server/src/frontend/generation_flow.rs
  • crates/skippy-server/src/frontend/generation_receipt.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/local_generation.rs
  • crates/skippy-server/src/frontend/local_generation/tests.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
  • crates/skippy-server/src/frontend/tests/generation.rs
  • crates/skippy-server/src/lib.rs
  • crates/skippy-server/src/serving_hooks.rs
  • scripts/affected-crates.sh
  • scripts/plan-clippy-batches.sh
  • scripts/publish-crates.sh
  • website/src/docs/pages/CLI.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/mesh-llm-host-runtime/src/runtime/local.rs
  • crates/mesh-llm-host-runtime/src/inference/skippy/mod.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs

Comment thread crates/mesh-llm-host-runtime/src/runtime/options.rs
Comment thread crates/mesh-native-serving-plugin-host/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
Comment thread crates/mesh-native-serving-plugin-host/src/lib.rs
Comment thread crates/mesh-native-serving-plugin-host/src/lib.rs
Comment thread crates/skippy-server/src/frontend/generation_flow.rs
Comment thread crates/skippy-server/src/frontend/generation_receipt.rs
Comment thread crates/skippy-server/src/frontend/generation_receipt.rs
Comment thread crates/skippy-server/src/frontend/generation_receipt.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.

🧹 Nitpick comments (2)
crates/skippy-server/src/frontend/generation_flow.rs (1)

250-260: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the receipt lifecycle into one shared helper.

generate_multimodal_text and generate_split_multimodal_text now repeat the same five steps: tokenize the prompt into Arc<[i32]>, emit begin, create the observation through config.observation, record and commit each token, then finalize with generation_succeeded. Lines 250-260 mirror lines 464-474, lines 299-309 mirror lines 705-715, and lines 416-432 mirror lines 878-891.

Both functions also remain very long after this change. Extract the lifecycle into a helper that owns the prompt tokens, the observation, and the terminal event. That removes the duplication and reduces the chance that one path diverges from the other on a later edit.

Also applies to: 299-309, 416-432

🤖 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/generation_flow.rs` around lines 250 - 260,
Extract the duplicated generation-receipt lifecycle from
generate_multimodal_text and generate_split_multimodal_text into one shared
helper. Have the helper tokenize and own the Arc<[i32]> prompt tokens, call
config.begin and config.observation, record and commit each token, and emit
generation_succeeded; replace the repeated blocks at the referenced begin,
observation/record/commit, and finalization sites while preserving each
function’s existing request and session identifiers.
crates/skippy-server/src/frontend/local_generation.rs (1)

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

Close the terminal-event hole when the observation is absent.

Line 78 returns Ok(()) when config exists but observation is None. The callers emit begin whenever self.generation_receipt is Some. In that combination the sink receives begin with no record and no abort.

The provided callers always create an observation when the config exists, so this branch is currently unreachable. The type does not enforce that. Emit abort in the None branch so the contract holds for any future caller.

♻️ Proposed change: abort instead of returning silently
         match finalization.observation {
             Some(observation) => {
                 self.deliver_local_generation_receipt(LocalGenerationReceiptDelivery {
                     config,
                     session_label: finalization.session_label,
                     request_id: finalization.request_id,
                     session_id: finalization.session_id,
                     agent_session_id: finalization.agent_session_id,
                     prompt_token_ids: finalization.prompt_token_ids,
                     observation,
                 })
             }
-            None => Ok(()),
+            None => {
+                config.abort(crate::frontend::GenerationAbort {
+                    request_id: finalization.request_id,
+                    session_id: finalization.session_id,
+                });
+                Ok(())
+            }
         }
🤖 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.rs` around lines 66 - 79,
Update the None branch in the finalization observation match within
deliver_local_generation_receipt to emit an abort event when a generation
receipt config exists but no observation is present, instead of returning Ok(())
silently. Preserve the existing receipt delivery for Some(observation) and
ensure the terminal event contract is completed for this case.
🤖 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-server/src/frontend/generation_flow.rs`:
- Around line 250-260: Extract the duplicated generation-receipt lifecycle from
generate_multimodal_text and generate_split_multimodal_text into one shared
helper. Have the helper tokenize and own the Arc<[i32]> prompt tokens, call
config.begin and config.observation, record and commit each token, and emit
generation_succeeded; replace the repeated blocks at the referenced begin,
observation/record/commit, and finalization sites while preserving each
function’s existing request and session identifiers.

In `@crates/skippy-server/src/frontend/local_generation.rs`:
- Around line 66-79: Update the None branch in the finalization observation
match within deliver_local_generation_receipt to emit an abort event when a
generation receipt config exists but no observation is present, instead of
returning Ok(()) silently. Preserve the existing receipt delivery for
Some(observation) and ensure the terminal event contract is completed for this
case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 960faa6c-5cbb-48c3-a88a-4797a4ace443

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef248f and a09d53f.

📒 Files selected for processing (19)
  • crates/mesh-llm-cli/src/parser/commands.rs
  • crates/mesh-llm-host-runtime/src/models/artifact_transfer.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/loading.rs
  • crates/mesh-llm-host-runtime/src/runtime/local_split/tests.rs
  • crates/mesh-native-serving-plugin-api/README.md
  • crates/mesh-native-serving-plugin-api/src/lib.rs
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/openai-frontend/src/chat.rs
  • crates/openai-frontend/src/common.rs
  • crates/openai-frontend/src/completions.rs
  • crates/openai-frontend/src/responses.rs
  • crates/skippy-server/src/frontend/generation_flow.rs
  • crates/skippy-server/src/frontend/generation_flow/text_generation.rs
  • crates/skippy-server/src/frontend/generation_receipt.rs
  • crates/skippy-server/src/frontend/local_generation.rs
  • crates/skippy-server/src/frontend/local_generation/decode_step.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
  • crates/skippy-server/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/skippy-server/src/lib.rs
  • crates/openai-frontend/src/chat.rs
  • crates/openai-frontend/src/responses.rs
  • crates/openai-frontend/src/completions.rs
  • crates/mesh-llm-cli/src/parser/commands.rs
  • crates/mesh-native-serving-plugin-host/src/lib.rs
  • crates/mesh-native-serving-plugin-api/src/lib.rs
  • crates/skippy-server/src/frontend/generation_receipt.rs

@ndizazzo
ndizazzo merged commit 4bd1453 into main Aug 4, 2026
58 checks passed
@ndizazzo
ndizazzo deleted the agent/generation-lifecycle-events branch August 4, 2026 21: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