Skip to content
Open
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
29 changes: 29 additions & 0 deletions .agent/notes/vm-friction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# VM Friction Log

Tracks behaviors in the agent-os VM that differ from a standard POSIX/Node.js system.

---

## `node:sqlite` not available on Bun

**Deviation**: `node:sqlite` is a Node.js-only experimental built-in (requires Node >= 22.5.0). Bun provides `bun:sqlite` instead with a different API surface.

**Root cause**: The VM's SQLite bindings proxy host-side SQLite into the VM. The original implementation hard-coded `require("node:sqlite")` at the module top level, which crashed on any runtime without it.

**Fix**: `sqlite-bindings.ts` now lazy-loads the SQLite module and auto-selects `node:sqlite` or `bun:sqlite` based on runtime detection (`process.versions.bun`). A `BunStatementAdapter`/`BunDatabaseAdapter` layer normalizes the bun:sqlite API to match the node:sqlite shape.

**Remaining differences on Bun**:
- `setReadBigInts()` is a no-op (Bun uses `safeIntegers` at DB level).
- `columns()` returns `[{ name }]` only (no `table`/`type`/`database` fields).
- Database constructor options aren't translated (`readOnly` vs `readonly`).
- `get()` return value normalized: Bun returns `null` for no rows, node:sqlite returns `undefined`. Adapter normalizes to `undefined`.

---

## SQLite bindings use temp-file sync

**Deviation**: When VM code opens a file-backed SQLite database, the kernel VFS file is copied to a host temp directory, opened with host SQLite, and synced back on mutations. This means the database is not truly "in the VM" -- it lives on the host filesystem temporarily.

**Root cause**: The secure-exec kernel's VFS doesn't support SQLite's file locking and mmap requirements natively. The bindings work around this by proxying through host SQLite.

**Fix exists**: No. This is a fundamental architecture limitation. A proper fix would be an in-VM SQLite compiled to WASM (the `registry/software/sqlite3` package exists but is for the CLI tool, not the library).
16 changes: 15 additions & 1 deletion .agent/todo/remove-sqlite-bindings.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Remove sqlite-bindings.ts

The `packages/core/src/sqlite-bindings.ts` file provides SQLite database access inside the VM by proxying to host-side Node.js SQLite. It has pre-existing type errors and the approach (temp files synced between host and VM) is fragile.
The `packages/core/src/sqlite-bindings.ts` file provides SQLite database access inside the VM by proxying to host-side SQLite. The approach (temp files synced between host and VM) is fragile.

Consider replacing with a proper in-VM SQLite implementation or removing if no longer needed.

## Current state (2026-04-03)

The file was refactored in `fix/lazy-sqlite-bun-compat` to:
- **Lazy-load** the SQLite module on first `AgentOs.create()` instead of at import time (was crashing Bun).
- **Support both runtimes**: `node:sqlite` on Node.js, `bun:sqlite` on Bun via an adapter layer.
- **Promise-cached** module loading (no race conditions on concurrent calls).
- Pre-existing type errors were resolved by introducing internal `SqliteDatabase`/`SqliteStatement`/`SqliteModule` interfaces.

### Known adapter limitations (bun:sqlite)
- `setReadBigInts()` is a no-op — Bun uses `safeIntegers` at the database level, not per-statement.
- `setAllowBareNamedParameters()` / `setAllowUnknownNamedParameters()` are no-ops — Bun's `strict` mode covers similar ground at the database level.
- `columns()` returns `[{ name }]` only — Bun's `columnNames` string array doesn't include `column`, `table`, `database`, or `type` fields that node:sqlite provides.
- Constructor options aren't translated between runtimes (`readOnly` vs `readonly`).
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ The registry software packages depend on `@rivet-dev/agent-os-registry-types` (i
## Dependencies

- **secure-exec** is published on npm as `secure-exec`, `@secure-exec/core`, `@secure-exec/nodejs`, `@secure-exec/v8`, etc. Pinned at `^0.2.1`.
- **SQLite host bindings** — The VM proxies SQLite through the host runtime. On Node.js, requires `node:sqlite` (Node >= 22.5.0 with `--experimental-sqlite`, or Node >= 23.4.0 where it is stable). On Bun, uses `bun:sqlite` (>= 1.0.0) via an adapter layer that normalizes to the `node:sqlite` API shape. The module is lazy-loaded on first `AgentOs.create()` — importing `@rivet-dev/agent-os-core` never eagerly loads SQLite.
- **Rivet repo** — A modifiable copy lives at `~/r-aos`. Use this when you need to make changes to the Rivet codebase.
- Mount host `node_modules` read-only for agent packages (pi-acp, etc.)

Expand Down Expand Up @@ -146,6 +147,7 @@ Each agent type needs:
- Kernel child_process.spawn can't resolve bare commands from PATH (e.g., `pi`). Use `PI_ACP_PI_COMMAND` env var to point to the `.js` entry directly. The Node runtime resolves `.js`/`.mjs`/`.cjs` file paths as node scripts.
- `kernel.readFile()` does NOT see the ModuleAccessFileSystem overlay — read host files directly with `readFileSync` for package.json resolution
- Native ELF binaries cannot execute in the VM — the kernel's command resolver only handles `.js`/`.mjs`/`.cjs` scripts and WASM commands. `child_process.spawnSync` returns `{ status: 1, stderr: "ENOENT: command not found" }` for native binaries.
- **bun:sqlite adapter parity gaps** — When running on Bun, the SQLite bindings use an adapter layer with known behavioral differences: `setReadBigInts()` is a no-op (Bun handles big integers at the database level via `safeIntegers`), `columns()` returns `[{ name }]` only (no `table`, `type`, `database`, or `column` fields), and `get()` normalizes Bun's `null`-for-no-rows to `undefined` to match `node:sqlite`. Database constructor options are not translated between runtimes (`readOnly` vs `readonly`).

### Debugging Policy

Expand Down
Loading
Loading