Skip to content

feat(rivetkit): phase 1c — agentOs() native ladder + engine driver branch + driver-suite e2e green#5192

Open
abcxff wants to merge 1 commit into
stack/feat-rivetkit-napi-phase-1b-napiactorfactory-fromagentos-static-constructor-writefile-arm-e2e-factory-round-trip-mtlnunovfrom
stack/feat-rivetkit-phase-1c-agentos-native-ladder-engine-driver-branch-driver-suite-e2e-green-nxzrussy
Open

feat(rivetkit): phase 1c — agentOs() native ladder + engine driver branch + driver-suite e2e green#5192
abcxff wants to merge 1 commit into
stack/feat-rivetkit-napi-phase-1b-napiactorfactory-fromagentos-static-constructor-writefile-arm-e2e-factory-round-trip-mtlnunovfrom
stack/feat-rivetkit-phase-1c-agentos-native-ladder-engine-driver-branch-driver-suite-e2e-green-nxzrussy

Conversation

@abcxff

@abcxff abcxff commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@abcxff

abcxff commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Stack for rivet-dev/rivet

Check out this stack: forklift get https://github.com/rivet-dev/rivet/pull/5192
Pull/update this stack: forklift sync
Publish local edits: forklift submit
Merge when ready: forklift merge

@claude

claude Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code Review — Phase 1c: agentOs() native ladder + engine driver branch

Overview

This PR migrates the agentOs() actor from a pure JS implementation (~1,500 lines across cron/db/filesystem/network/preview/process/session/shell modules) to a Rust-backed factory via a new nativeFactoryBuilder marker on ActorDefinition. The ladder: agentOs() sets nativeFactoryBuilder, buildRegistryWithRuntime dispatches on it, and EngineActorDriver stubs out the JS actor lifecycle for the native path.


Issues

handler.actorStartPromise?.resolve() uses optional chaining in a lifecycle path

actor-driver.ts line ~1654:

handler.actorStartPromise?.resolve();

Per CLAUDE.md's Fail-By-Default Runtime section, optional chaining is only acceptable for best-effort diagnostics and cleanup paths. Actor start is a required lifecycle step. If actorStartPromise is absent when it shouldn't be, the actor silently hangs instead of surfacing an explicit error. The corresponding native/static branches (isStaticActorDefinition, buildNativeFactory) also use optional chaining here — if that's already accepted, this is consistent. If it isn't, both should be fixed together.

NATIVE_NAPI_ACTOR_STUB is an underspecified stub

const NATIVE_NAPI_ACTOR_STUB = {
    onStop: async (_reason: string) => {},
    debugForceCrash: async () => {},
} as unknown as AnyActorInstance;

The double cast through unknown hides any future AnyActorInstance methods added to the interface. If the engine driver ever routes an onRequest, onConnect, or similar call through the JS actor layer for a native-factory actor, this will throw at runtime instead of at compile time. Worth auditing every callsite of handler.actor.* to confirm only onStop/debugForceCrash are reachable after setting the stub.

toolCallbacks ?? undefined is redundant

napi-runtime.ts:

const factory = this.#bindings.NapiActorFactory.fromAgentOs(
    options,
    toolCallbacks ?? undefined,
);

toolCallbacks is already typed as Record<string, unknown> | undefined | null. Passing toolCallbacks ?? undefined converts null → undefined but the call site always passes undefined. Either remove the ?? undefined or accept null at the NAPI boundary and let Rust handle it.

AgentOsToolCallbacks mixes undefined and null

export type AgentOsToolCallbacks = Record<string, unknown> | undefined | null;

The tri-valued type signals uncertainty. Since this is explicitly "always undefined" until Phase 5, just type it undefined now and broaden it when Phase 5 lands. An opaque unknown placeholder type would also be cleaner than an all-null/undefined union.

Phase/task references in source comments

Several comments reference phased implementation context that belongs in the PR description, not in the code. Per CLAUDE.md: "Don't reference the current task, fix, or callers". Examples:

  • // Phase 1c minimum: empty config. Future phases thread software, permissions, mounts, etc. through here.
  • // Reserved for Phase 5 (toolkit dispatch); currently always undefined.
  • // Phase 3+ adds...

These rot immediately once future phases land and the comments aren't updated.

Multi-line comment blocks

CLAUDE.md says "Never write multi-paragraph docstrings or multi-line comment blocks — one short line max." Several additions are multi-line JSDoc blocks (the NATIVE_NAPI_ACTOR_STUB header, the NapiAgentOsOptions and AgentOsToolCallbacks JSDoc, the nativeFactoryBuilder property docs on ActorDefinition). The intent is clear but the form conflicts with the convention.

buildConfigJson ignores its argument

function buildConfigJson<TConnParams>(
    _parsed: AgentOsActorConfig<TConnParams>,
): string {
    return "{}";
}

The _parsed parameter with leading-underscore signals intentional non-use — fine for now. But the function call site passes parsed as a named argument, which will make future reviewers wonder if the omission was intentional. A bare function buildConfigJson(): string { return "{}"; } is clearer until the body actually uses config, or add a one-line comment on the first field that will be threaded through.


Positive Notes

  • WriteFileContent + JsonCompatUint8Array deserialization is clean and handles the three wire shapes correctly (CBOR text, CBOR bytes, ["$Uint8Array", base64]). The error messages on malformed base64 and bad tags are explicit.
  • The dispatch in buildRegistryWithRuntime is clean — one branch, no other code touched.
  • The runtime guard in buildNativeFactoryBuilder is appropriately explicit:
    if (runtime.kind !== "napi") { throw new Error(...) }
    if (!runtime.createAgentOsFactory) { throw new Error(...) }
    Fail-by-default at registration time, not at first action dispatch.
  • Deleting ~1,500 lines of JS action builders while preserving the public agentOs() signature is the right direction.

Questions

  1. Are onStop and debugForceCrash truly the only AnyActorInstance methods the engine driver calls post-start for a native factory actor? A quick grep or a compile-time exhaustiveness check on the stub would be worth adding to CI.
  2. The removed JS agentOs() actions (sessions, processes, filesystem, shell, cron, preview) are not yet re-implemented in Rust. Is this branch expected to break callers of those actions until a later phase, or is there a compatibility shim somewhere?

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