Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
- Dashboard: `https://hub.rivet.dev`
- Documentation: `https://rivet.dev/docs`

The `rivet.gg` domain is deprecated and should never be used in this codebase.

**ALWAYS use `github.com/rivet-dev/rivet` - NEVER use `rivet-dev/rivetkit` or `rivet-gg/*`**
- The `rivet.gg` domain is deprecated and should never be used in this codebase.
- ALWAYS use `github.com/rivet-dev/rivet`; never use `rivet-dev/rivetkit` or `rivet-gg/*`.

## Commands

Expand Down Expand Up @@ -69,7 +68,7 @@ docker-compose up -d
gt c -m "chore(my-pkg): foo bar"
```

**Never push to `main` unless explicitly specified by the user.**
- Never push to `main` unless explicitly specified by the user.

## Graphite CLI Commands
```bash
Expand All @@ -82,12 +81,22 @@ gt m
### pnpm Workspace
- Use pnpm for all npm-related commands. We're using a pnpm workspace.

### RivetKit Type Build Troubleshooting
- If `rivetkit` type or DTS builds fail with missing `@rivetkit/*` declarations, run `pnpm build -F rivetkit` from repo root (Turbo build path) before changing TypeScript `paths`.
- Do not add temporary `@rivetkit/*` path aliases in `rivetkit-typescript/packages/rivetkit/tsconfig.json` to work around stale or missing built declarations.

### RivetKit Driver Registry Variants
- Keep `rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry.ts` as the canonical type anchor for fixtures and test typing.
- Run driver runtime suites through `registry-static.ts` and `registry-dynamic.ts` instead of executing `registry.ts` directly.
- Load static fixture actors with dynamic ESM `import()` from the `fixtures/driver-test-suite/actors/` directory.
- Skip dynamic registry parity only for the explicit nested dynamic harness gate or missing secure-exec dist, and still treat full static and dynamic compatibility as the target for all normal driver suites.

### SQLite Package
- Use `@rivetkit/sqlite` for SQLite WebAssembly support.
- Do not use the legacy upstream package directly. `@rivetkit/sqlite` is the maintained fork used in this repository and is sourced from `rivet-dev/wa-sqlite`.

### RivetKit Package Resolutions
The root `/package.json` contains `resolutions` that map RivetKit packages to their local workspace versions:
- The root `/package.json` contains `resolutions` that map RivetKit packages to local workspace versions:

```json
{
Expand All @@ -100,7 +109,7 @@ The root `/package.json` contains `resolutions` that map RivetKit packages to th
}
```

When adding RivetKit dependencies to examples in `/examples/`, use `*` as the version. The root resolutions will automatically resolve these to the local workspace packages:
- Use `*` as the dependency version when adding RivetKit packages to `/examples/`, because root resolutions map them to local workspace packages:

```json
{
Expand All @@ -111,7 +120,19 @@ When adding RivetKit dependencies to examples in `/examples/`, use `*` as the ve
}
```

If you need to add a new `@rivetkit/*` package that isn't already in the root resolutions, add it to the `resolutions` object in `/package.json` with `"workspace:*"` as the value. Internal packages like `@rivetkit/workflow-engine` should be re-exported from `rivetkit` subpaths (e.g., `rivetkit/workflow`) rather than added as direct dependencies.
- Add new internal `@rivetkit/*` packages to root `resolutions` with `"workspace:*"` if missing, and prefer re-exporting internal packages (for example `@rivetkit/workflow-engine`) from `rivetkit` subpaths like `rivetkit/workflow` instead of direct dependencies.

### Dynamic Import Pattern
- For runtime-only dependencies, use dynamic loading so bundlers do not eagerly include them.
- Build the module specifier from string parts (for example with `["pkg", "name"].join("-")` or `["@scope", "pkg"].join("/")`) instead of a single string literal.
- Prefer this pattern for modules like `@rivetkit/sqlite-vfs`, `sandboxed-node`, and `isolated-vm`.
- If loading by resolved file path, resolve first and then import via `pathToFileURL(...).href`.

### Fail-By-Default Runtime Behavior
- Avoid silent no-ops for required runtime behavior.
- Do not use optional chaining for required lifecycle and bridge operations (for example sleep, destroy, alarm dispatch, ack, and websocket dispatch paths).
- If a capability is required, validate it and throw an explicit error with actionable context instead of returning early.
- Optional chaining is acceptable only for best-effort diagnostics and cleanup paths (for example logging hooks and dispose/release cleanup).

### Rust Dependencies

Expand All @@ -125,38 +146,39 @@ If you need to add a new `@rivetkit/*` package that isn't already in the root re

### Docs (`website/src/content/docs/**/*.mdx`)

Required frontmatter fields:
- Required frontmatter fields:

- `title` (string)
- `description` (string)
- `skill` (boolean)

### Blog + Changelog (`website/src/content/posts/**/page.mdx`)

Required frontmatter fields:
- Required frontmatter fields:

- `title` (string)
- `description` (string)
- `author` (enum: `nathan-flurry`, `nicholas-kissel`, `forest-anderson`)
- `published` (date string)
- `category` (enum: `changelog`, `monthly-update`, `launch-week`, `technical`, `guide`, `frogs`)

Optional frontmatter fields:
- Optional frontmatter fields:

- `keywords` (string array)

## Examples

All example READMEs in `/examples/` should follow the format defined in `.claude/resources/EXAMPLE_TEMPLATE.md`.
- All example READMEs in `/examples/` should follow the format defined in `.claude/resources/EXAMPLE_TEMPLATE.md`.

## Notes Tracking

- When the user asks to track something in a note, store it in `.agent/notes/` by default.
- When the user asks to update any `CLAUDE.md`, add one-line bullet points only, or add a new section containing one-line bullet points.

## Architecture

### Monorepo Structure
This is a Rust workspace-based monorepo for Rivet. Key packages and components:
- This is a Rust workspace-based monorepo for Rivet with the following key packages and components:

- **Core Engine** (`packages/core/engine/`) - Main orchestration service that coordinates all operations
- **Workflow Engine** (`packages/common/gasoline/`) - Handles complex multi-step operations with reliability and observability
Expand All @@ -172,7 +194,7 @@ This is a Rust workspace-based monorepo for Rivet. Key packages and components:
- Custom error system at `packages/common/error/`
- Uses derive macros with struct-based error definitions

To use custom errors:
- Use this pattern for custom errors:

```rust
use rivet_error::*;
Expand Down Expand Up @@ -201,13 +223,13 @@ let error = AuthInvalidToken.build();
let error_with_meta = ApiRateLimited { limit: 100, reset_at: 1234567890 }.build();
```

Key points:
- Key points:
- Use `#[derive(RivetError)]` on struct definitions
- Use `#[error(group, code, description)]` or `#[error(group, code, description, formatted_message)]` attribute
- Group errors by module/domain (e.g., "auth", "actor", "namespace")
- Add `Serialize, Deserialize` derives for errors with metadata fields
- Always return anyhow errors from failable functions
- For example: `fn foo() -> Result<i64> { /* ... */ }`
- For example: `fn foo() -> Result<i64> { /* ... */ }`
- Do not glob import (`::*`) from anyhow. Instead, import individual types and traits

**Rust Dependency Management**
Expand All @@ -233,7 +255,7 @@ Key points:

## Naming Conventions

Data structures often include:
- Data structures often include:

- `id` (uuid)
- `name` (machine-readable name, must be valid DNS subdomain, convention is using kebab case)
Expand All @@ -252,7 +274,7 @@ Data structures often include:

### Structured Logging
- Use tracing for logging. Do not format parameters into the main message, instead use tracing's structured logging.
- For example, instead of `tracing::info!("foo {x}")`, do `tracing::info!(?x, "foo")`
- For example, instead of `tracing::info!("foo {x}")`, do `tracing::info!(?x, "foo")`
- Log messages should be lowercase unless mentioning specific code symbols. For example, `tracing::info!("inserted UserRow")` instead of `tracing::info!("Inserted UserRow")`

## Configuration Management
Expand All @@ -278,7 +300,7 @@ Data structures often include:
- When talking about "Rivet Actors" make sure to capitalize "Rivet Actor" as a proper noun and lowercase "actor" as a generic noun

### Documentation Sync
When making changes to the engine or RivetKit, ensure the corresponding documentation is updated:
- Ensure corresponding documentation is updated when making engine or RivetKit changes:
- **Limits changes** (e.g., max message sizes, timeouts): Update `website/src/content/docs/actors/limits.mdx`
- **Config changes** (e.g., new config options in `engine/packages/config/`): Update `website/src/content/docs/self-hosting/configuration.mdx`
- **RivetKit config changes** (e.g., `rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts` or `rivetkit-typescript/packages/rivetkit/src/actor/config.ts`): Update `website/src/content/docs/actors/limits.mdx` if they affect limits/timeouts
Expand All @@ -295,8 +317,8 @@ When making changes to the engine or RivetKit, ensure the corresponding document

#### Common Vercel Example Errors

After regenerating Vercel examples, you may see type check errors like:
- You may see type-check errors like the following after regenerating Vercel examples:
```
error TS2688: Cannot find type definition file for 'vite/client'.
```
with warnings about `node_modules missing`. This happens because the regenerated examples need their dependencies reinstalled. Fix by running `pnpm install` before running type checks.
- You may also see `node_modules missing` warnings; fix this by running `pnpm install` before type checks because regenerated examples need dependencies reinstalled.
95 changes: 95 additions & 0 deletions docs-internal/rivetkit-typescript/DYNAMIC_ACTORS_ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Dynamic Actors Architecture

## Overview

Dynamic actors let a registry entry resolve actor source code at actor start time.

Dynamic actors are represented by `dynamicActor(loader, config)` and still
participate in normal registry routing and actor lifecycle.

Driver parity is verified by running the same driver test suites against two
fixture registries:

- `fixtures/driver-test-suite/registry-static.ts`
- `fixtures/driver-test-suite/registry-dynamic.ts`

Both registries are built from `fixtures/driver-test-suite/actors/` to keep
actor behavior consistent between static and dynamic execution.

## Main Components

- Host runtime manager:
`rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts`
Creates and owns one `NodeProcess` isolate per dynamic actor instance.
- Isolate bootstrap runtime:
`rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/src/index.cts`
Runs inside the isolate and exports envelope handlers.
- Bridge contract:
`rivetkit-typescript/packages/rivetkit/src/dynamic/runtime-bridge.ts`
Shared envelope and callback payload types for host and isolate.
- Driver integration:
`drivers/file-system/global-state.ts` and `drivers/engine/actor-driver.ts`
Branch on definition type, construct dynamic runtime, and proxy fetch and websocket traffic.

## Lifecycle

1. Driver resolves actor definition from registry.
2. If definition is dynamic, driver creates `DynamicActorIsolateRuntime`.
3. Runtime calls loader and gets `{ source, sourceFormat?, nodeProcess? }`.
4. Runtime writes source into actor runtime dir:
- `sourceFormat: "esm-js"` -> `dynamic-source.mjs` (written unchanged)
- `sourceFormat: "commonjs-js"` -> `dynamic-source.cjs` (written unchanged)
- default `sourceFormat: "typescript"` -> transpiled to `dynamic-source.cjs`
5. Runtime writes isolate bootstrap entry into actor runtime dir.
6. Runtime builds a locked down sandbox driver and creates `NodeProcess`.
7. Runtime injects host bridge refs and bootstrap config into isolate globals.
8. Runtime loads bootstrap module and captures exported envelope refs.

## Bridge Contract

Host to isolate calls:

- `dynamicFetchEnvelope`
- `dynamicOpenWebSocketEnvelope`
- `dynamicWebSocketSendEnvelope`
- `dynamicWebSocketCloseEnvelope`
- `dynamicDispatchAlarmEnvelope`
- `dynamicStopEnvelope`
- `dynamicGetHibernatingWebSocketsEnvelope`
- `dynamicDisposeEnvelope`

Isolate to host callbacks:

- KV: `kvBatchPut`, `kvBatchGet`, `kvBatchDelete`, `kvListPrefix`
- Lifecycle: `setAlarm`, `startSleep`, `startDestroy`
- Networking: `dispatch` for websocket events
- Runner ack path: `ackHibernatableWebSocketMessage`
- Inline client bridge: `clientCall`

Binary payloads are normalized to `ArrayBuffer` at the host and isolate boundary.

## Security Model

- Each dynamic actor runs in its own sandboxed `NodeProcess`.
- Sandbox permissions deny network and child process access.
- Filesystem access is restricted to dynamic runtime root and read only `node_modules` paths.
- Environment is explicitly injected by host config for the isolate process.

## Temporary Compatibility Layer

Current implementation materializes a runtime `node_modules` tree under `/tmp`
and patches specific dependencies to CJS safe output.

This is temporary. Remove this path when sandboxed-node can load required
RivetKit runtime dependencies without package source patching.

## Driver Test Skip Gate

The dynamic registry variant in driver tests has a narrow skip gate for two
cases only:

- secure-exec dist is not available on the local machine
- nested dynamic harness mode is explicitly enabled for tests

This gate is only to avoid invalid test harness setups. Static and dynamic
behavior parity remains the expected target for normal driver test execution.
Loading
Loading