Skip to content

feat(rivetkit): add services integration - #5648

Merged
NathanFlurry merged 12 commits into
mainfrom
naming
Sep 3, 2026
Merged

feat(rivetkit): add services integration#5648
NathanFlurry merged 12 commits into
mainfrom
naming

Conversation

@NathanFlurry

@NathanFlurry NathanFlurry commented Sep 3, 2026

Copy link
Copy Markdown
Member
  • Add automatic Services startup alongside the local Engine with a dedicated services pool and lifecycle.
  • Add typed streaming and concurrent HTTP actor APIs used by Durable Streams.
  • Load @rivet-dev/services without bundling native binaries into edge builds.
  • Add the Durable Streams local development and deployment guide.

@railway-app

railway-app Bot commented Sep 3, 2026

Copy link
Copy Markdown

🚅 Deployed to the actors-pr-5648 environment in rivet-frontend

Service Status Web Updated
website ❌ Build Failed (View Logs) Web Sep 3, 2026 at 11:25 am UTC
ladle 🕒 Building (View Logs) Web Sep 3, 2026 at 11:25 am UTC
frontend-cloud 🕒 Building (View Logs) Web Sep 3, 2026 at 11:25 am UTC
kitchen-sink 🕒 Building (View Logs) Web Sep 3, 2026 at 11:25 am UTC
frontend-inspector 🕒 Building (View Logs) Web Sep 3, 2026 at 11:25 am UTC
mcp-hub ✅ Success (View Logs) Web Sep 3, 2026 at 2:31 am UTC

@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 02:30 Destroyed
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Review: feat(rivetkit): add services integration

Reviewed the Services subprocess integration (Rust core, NAPI/TS, gateway3 typed HTTP). Verified the findings below directly against the diff.

Bugs

1. Services silently never starts for non-Auto-managed engines despite start_services: true (rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs:596-609)
DevelopmentProcessManager::start (which owns the Services spawn) is only invoked when manage_engine = should_manage_engine(&config.endpoint, config.engine_spawn)? is true. If start_services is true but should_manage_engine is false (e.g. engine_spawn isn't Auto, or the endpoint isn't loopback), development_processes stays None and ServicesProcessManager::start is never called — with no error or log pointing at the cause. The same gating pattern is duplicated in serverless.rs's CoreServerlessRuntime::new.

2. wait_for_readiness can pass against a stale registration, not the newly spawned child (rivetkit-rust/packages/rivetkit-core/src/services_process.rs:321-360)
The readiness poll queries GET /envoys?namespace=...&name=<pool_name> and treats any non-empty response as ready. It never checks that the returned envoy entry corresponds to the child process just spawned (no PID/envoy-key/generation match). If a prior Services instance for the same namespace/pool is still registered (crash-restart race, slow expiry), a fresh spawn can report ready immediately while the old instance is actually what's serving.

3. MAX_CONCURRENT_LIVE_HTTP_CALLBACK_STARTS defaults to 0, silently blackholing "Live" HTTP callbacks (rivetkit-rust/packages/rivetkit/src/actor.rs:54, wired in start.rs:258-300)
MAX_CONCURRENT_HTTP_CALLBACKS defaults to 128, but the sibling MAX_CONCURRENT_LIVE_HTTP_CALLBACK_STARTS defaults to 0, and try_acquire_owned() against a 0-permit semaphore always fails. An actor that sets CONCURRENT_HTTP_CALLBACKS = true and overrides classify_http_request to route requests to HttpCallbackClass::Live, without also overriding the max constant, will have every such request permanently rejected with no compile-time signal.

4. Rust host's start_services default diverges from the TypeScript host's (rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs:169 vs rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts:366-372)
TS defaults startServices to Boolean(endpoint && isLocalEngineEndpoint(endpoint)) — on whenever RivetKit is managing a local engine. The Rust ServeSettings::from_env defaults it to a bare RIVET_RUN_SERVICES == "1" check, i.e. off by default regardless of local-engine management. This also contradicts the BinaryUnavailable error text ("...or set RIVET_RUN_SERVICES=0 to disable Services"), which implies Services is on by default.

Architecture (CLAUDE.md layering)

5. HTTP-callback admission control lives in the thin rivetkit (Rust) wrapper instead of rivetkit-core (rivetkit-rust/packages/rivetkit/src/start.rs:258-320, actor.rs)
Per this repo's layering rule, rivetkit (Rust) should stay a thin typed wrapper with no load-bearing logic, and dispatch/admission logic belongs in rivetkit-core so every host (NAPI/TS, future wasm) benefits. HttpCallbackPools, HttpCallbackAdmission, and the semaphore-based Standard/Live admission gating are new load-bearing policy implemented only in the Rust crate — NAPI/TS-hosted actors get no equivalent protection, and this duplicates the shape of rivetkit-core's existing SQLite-tx admission semaphores rather than sharing it.

Tests

6. New test extends an existing vi.mock violation (rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts:10-12)
Adds vi.mock("@rivet-dev/services", () => ({ getServicesPath: () => "/tmp/rivet-services" })). Root CLAUDE.md explicitly prohibits vi.mock/module-level mocking ("Write tests against real infrastructure"). This follows the file's pre-existing vi.mock("@rivetkit/engine-cli", ...), but compounds rather than fixes the violation — worth reconsidering both while touching this file.

7. New services-binary resolution logic was added to a dead code path (rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts:805-861)
runtime.ts has its own buildServeConfig, parallel to the live one in native.ts. Grepping the repo, runtime.ts's copy has zero callers anywhere (only native.ts's buildServeConfig is imported by tests/other code). This PR adds the new startServices/servicesBinaryPath/loadServicesPath handling to both copies. Since nothing exercises the runtime.ts copy, future edits to the live version (error wording, retry/backoff behavior) can silently drift from the dead one — worth deleting the unused copy or wiring it in.

Minor / worth a second look

  • services_process.rs's wait_for_readiness (flat 60×500ms poll) and shutdown reimplement patterns that already exist with different tuning in the sibling engine-process crate (e.g. exponential backoff there vs. flat retry here). Not necessarily wrong, but a future fix to one poll loop's backoff is easy to miss applying to the other.
  • rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts (servicesPid()) parses stdout for an info!-level readiness log; if the spawned process doesn't have RUST_LOG/RIVET_LOG_LEVEL set to at least info, the default warn-level filter in rivetkit-napi's init_tracing would drop the line and fail the test independent of whether Services actually started.

Overall the feature is well-structured (dedicated ServicesProcessManager, structured RivetError variants, version/protocol compatibility checks before spawn), but items 1-4 are functional bugs worth fixing before merge, and item 5 is a layering violation per this repo's own conventions.

@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 05:45 Destroyed
@NathanFlurry NathanFlurry changed the title feat(ci): add managed services dispatch action feat(rivetkit): add managed services integration Sep 3, 2026
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 05:52 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 10:15 Destroyed
@NathanFlurry NathanFlurry changed the title feat(rivetkit): add managed services integration feat(rivetkit): add services integration Sep 3, 2026
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 10:56 Destroyed
@NathanFlurry
NathanFlurry changed the base branch from prod to main September 3, 2026 10:56
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 10:58 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 11:00 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 11:02 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 11:04 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 11:07 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / actors-pr-5648 September 3, 2026 11:25 Destroyed
@NathanFlurry
NathanFlurry merged commit 89c31e9 into main Sep 3, 2026
9 of 15 checks passed
@NathanFlurry
NathanFlurry deleted the naming branch September 3, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant