Skip to content

feat(ocap-kernel): add evaluate RPC method for vat REPL#817

Closed
rekmarks wants to merge 49 commits intomainfrom
rekm/vat-evaluate
Closed

feat(ocap-kernel): add evaluate RPC method for vat REPL#817
rekmarks wants to merge 49 commits intomainfrom
rekm/vat-evaluate

Conversation

@rekmarks
Copy link
Member

@rekmarks rekmarks commented Feb 6, 2026

Add an evaluate RPC method to VatSupervisor that evaluates code in a vat's isolated compartment, enabling REPL functionality while maintaining security isolation from the supervisor.

Key changes:

  • New evaluate.ts RPC spec and handler
  • VatSupervisor creates a separate eval compartment with vat exports in scope
  • VatHandle.evaluate() method for kernel-side API
  • Result serialization for JSON-RPC transport (handles functions, symbols, bigints)
  • Errors return as { success: false, error } without crashing the vat

Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com

@rekmarks
Copy link
Member Author

rekmarks commented Feb 6, 2026

Warning

This PR is part of a stack and targets branch rekm/system-vats-redux, not main.
DO NOT MERGE until feat: Add system subclusters and kernel facet service #803 is merged into main.

Stack


Managed by gh-stack

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 77.58%
⬇️ -0.60%
6238 / 8040
🔵 Statements 77.52%
⬇️ -0.62%
6338 / 8175
🔵 Functions 76.12%
⬇️ -0.50%
1578 / 2073
🔵 Branches 76.94%
⬇️ -1.44%
2286 / 2971
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/ocap-kernel/src/rpc/vat/evaluate.ts 100% 100% 100% 100%
packages/ocap-kernel/src/rpc/vat/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/vats/VatHandle.ts 84.5%
⬇️ -1.21%
82.14%
🟰 ±0%
94.44%
⬇️ -5.56%
84.5%
⬇️ -1.21%
194-197, 318, 369-374, 376-378, 380-386
packages/ocap-kernel/src/vats/VatSupervisor.ts 52.63%
⬇️ -22.01%
23.07%
⬇️ -21.75%
53.33%
⬇️ -5.00%
53.09%
⬇️ -21.55%
131, 142, 150, 189, 227-231, 242, 251-252, 269-331, 348-350, 353, 357-359, 391-393, 410, 427-435
Generated in workflow #3599 for commit c6c235c by the Vitest Coverage Report Action

rekmarks and others added 28 commits February 6, 2026 15:03
Implement system vats that are launched at kernel initialization and have
access to privileged kernel services. Key changes:

- Add SystemVatConfig type and getSystemVatRoot method to Kernel
- Launch system vats after queue starts to avoid deadlock
- Terminate and relaunch existing system vat subclusters on restart
- Add bootstrap-vat.js for Omnium system services with CapletController
- Add baggage-backed storage adapter for vat persistence
- Pass systemVats config via URL params from offscreen to kernel worker
- Update background.ts to use system vat for caplet operations
- Add process.env.NODE_ENV replacement in vat bundler for SES compatibility
- Simplify kernel-facet.ts by removing SystemVatManager
- Add duplicate name check in KernelServiceManager.registerKernelServiceObject

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename bootstrap-vat.js to bootstrap-vat.ts with full type annotations
- Export Baggage type from baggage-adapter.ts
- Make logger optional throughout controller hierarchy
- Simplify defineMethods to take array of method names instead of object map
- Update background.ts to use simplified method names (install, uninstall, etc.)
- Update package.json build script to reference .ts file

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Expose the kernel's reset method via CapTP so it can be called from
the background script.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The vat hosts controllers, which better describes its purpose than
the generic "bootstrap" name.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…cranks

Changed invokeKernelService to not await the service method result. Instead,
it uses Promise chaining to resolve the kernel promise when the method
eventually completes. This allows service methods to internally use
waitForCrank() without causing deadlock - the crank can complete, and the
resolution happens in a future turn of the event loop.

Key changes:
- KernelServiceManager.invokeKernelService() now returns void instead of
  Promise<void> and uses Promise.resolve().then().catch() for async handling
- KernelRouter.#deliverKernelServiceMessage() is now synchronous
- Updated tests to use delay() for microtask flushing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a `globals` field to VatConfig that allows specifying which globals
should be available in the vat's SES Compartment. This fixes the
`Date.now()` error when the controller-vat runs under SES lockdown.

VatSupervisor reads the globals list and adds requested globals from an
allowlist to the compartment endowments. Currently only `Date` is allowed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add `callCapletMethod` to the omnium.caplet API for invoking methods on
installed caplets directly from the console. Simplify the echo caplet ID
from 'com.example.echo' to 'echo' and update the response format.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove `initializeControllers` and `makeChromeStorageAdapter` which are
no longer used now that the caplet controller runs inside the vat with
baggage-backed storage instead of chrome.storage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use proper return type from KernelFacade['queueMessage'] instead of
generic type parameter. Add error handling for missing root kref.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix callBootstrap type annotations to use proper return type
- Handle null tombstones in baggage adapter get() method
- Add promise rejection handling to controller-vat bootstrap()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add console forwarding to omnium-gatherum to match the extension implementation. This prevents "Unexpected message" errors in the background script when receiving console-forward messages, and ensures console output from the offscreen document and vat iframes is visible in the background devtools console.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix baggage adapter to re-add deleted keys to tracking on set
- Clear system vat roots map on kernel reset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…dd persistence

- Rename SystemVatConfig to SystemSubclusterConfig with nested config
- Rename getSystemVatRoot to getSystemSubclusterRoot
- Add persistent storage for system subcluster mappings
- Restore system subclusters on kernel restart instead of relaunching
- Update kernel-browser-runtime and omnium-gatherum for new API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… boot

- Add #handlePersistedSystemSubclusters to delete orphaned subclusters
  (those without configs) before vat initialization
- Split system subcluster init into restore (before vats) and launch (after queue)
- Add SubclusterManager.deleteSubcluster for deleting without terminating
- Make kernel facet registration idempotent with #ensureKernelFacetRegistered
- Add test for orphaned system subcluster cleanup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add proper TypeScript types for KernelFacet, BootstrapServices, VatParameters
- Use types from @MetaMask/ocap-kernel (Baggage, ClusterConfig, etc.)
- Remove JSDoc type annotations in favor of TypeScript types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Throw errors instead of silently recovering when a persisted system
subcluster has an empty vats array or missing root object. These
conditions indicate database corruption and should fail fast.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…estart

The controller vat created a new PromiseKit on every initialization but
only resolved it in bootstrap(). Since bootstrap() is not called during
resuscitation (kernel restart), all caplet methods would hang.

Fix by restoring kernelFacet from baggage and initializing the
CapletController immediately in buildRootObject when available.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a TODO comment noting that the define block should be replaced
with a process shim in VatSupervisor workerEndowments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix baggage adapter to use actual delete() instead of null tombstones
- Rename root to rootObject in KernelFacetLaunchResult for clarity
- Add subclusterId format validation in Kernel.getSubcluster()
- Add duplicate system subcluster name detection at kernel init
- Clarify comment in controller-vat resuscitation path

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Async kernel service invocations can cause multiple concurrent connection
attempts when processing many messages, which triggers the default rate
limiter. Increase maxConnectionAttemptsPerMinute to avoid interference
with the queue limit test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the KernelFacade type and makeKernelFacade factory (from
kernel-browser-runtime) with KernelFacet and makeKernelFacet (from
ocap-kernel). The kernel facet is now a thin delegate layer over the
kernel, with the only additions being ping() and getVatRoot().

Key changes:
- Add missing methods to KernelFacet (ping, pingVat,
  getSystemSubclusterRoot, reset, queueMessage)
- Add Kernel.provideFacet() for idempotent facet creation, replacing
  the boolean flag and #ensureKernelFacetRegistered()
- Move throw-on-missing logic for getSystemSubclusterRoot into Kernel
- Rename bootstrapRootKref to rootKref in SubclusterLaunchResult
- Remove KernelFacade type, makeKernelFacade, KernelFacetLaunchResult,
  and LaunchResult from kernel-browser-runtime
- Update all consumers (omnium-gatherum, extension) to use KernelFacet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Kernel.getPresence(kref, iface = 'Kernel Object') as a public
method that wraps kslot(). Remove getVatRoot from KernelFacet and
replace it with getPresence, which is now a delegated dependency
rather than a standalone kslot call.

Update controller-vat.ts to call E(kernelFacet).getPresence(kref,
'vatRoot') instead of E(kernelFacet).getVatRoot(kref).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace individual method declarations with a spread of the deps
object. Since every method except ping() is a direct delegate, the
facet is now just `makeDefaultExo('kernelFacet', { ...deps, ping })`.

Simplify tests accordingly — use plain functions instead of vi.fn()
mocks (which get frozen by harden()).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rekmarks and others added 21 commits February 6, 2026 15:03
…string, VatId>

Replace the array-based vat storage with a name-keyed record, making the
vat name→ID relationship explicit and eliminating the fragile index-based
bootstrap vat lookup in Kernel.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… CapTP integration test

Delegate each vi.fn() mock through a wrapper function before passing to
makeKernelFacet, so harden() freezes the wrappers instead of the original
mock instances, keeping vitest call tracking intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
reloadSubcluster() creates a new subcluster with a new ID, but was not
updating #systemSubclusterRoots or the persisted systemSubcluster.*
mappings. This left stale mappings that caused 'has no bootstrap vat'
errors on subsequent kernel restarts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o SubclusterManager

System subcluster state and logic (persist/restore/cleanup mappings,
launch new named subclusters, track roots) belongs in SubclusterManager
which already owns subcluster CRUD, termination, and reload. This moves
~140 lines out of Kernel.ts into SubclusterManager, keeping Kernel as a
thin orchestration layer that delegates to its managers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…registration

The kernelFacet kernel service now takes ko3, shifting all vat root
ko IDs by 1. Update hardcoded ko references in control-panel,
object-registry, and remote-comms e2e tests accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…usters

reloadAllSubclusters bypasses reloadSubcluster and has its own loop
that calls addSubcluster + launchVatsForSubcluster directly, so it
never updated the in-memory systemSubclusterRoots map or persisted
mappings. After a reload-all, getSystemSubclusterRoot() would return
stale krefs pointing to deleted objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace local Baggage type definition with the one exported from
ocap-kernel, which includes keys() for native iteration. This
eliminates the manual __storage_keys__ tracking in the baggage adapter.
Also replace local LaunchResult type with SubclusterLaunchResult from
ocap-kernel, and remove dead resuscitation guard in controller-vat
bootstrap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Construct a fallback Logger in controller-vat when vatPowers.logger is
not provided, ensuring a real Logger is always passed downstream. This
makes the logger property non-optional in ControllerConfig,
Controller, ControllerStorage, and CapletController, eliminating
optional chaining on logger calls throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nally

Instead of the caller manually binding each method, makeKernelFacet
now takes the kernel instance directly and iterates over a const array
of method names to bind them. This reduces the call site in Kernel.ts
from 12 lines to 1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the positional (resetStorage, mnemonicOrOptions) parameters
with a single options object. resetStorage defaults to true since
nearly every call site uses that value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply vitest eslint config to `**/test/**/*` in addition to
`**/*.test.ts` files, so non-test-named files under test directories
also get the right rules. Remove now-unnecessary eslint-disable
comments in system-vat.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The omnium.caplet type was declared as Promisified<CapletControllerFacet>
but the implementation routes through queueMessage, returning raw
CapData instead of deserialized values. Replace with explicit method
signatures using QueueMessageResult, and add the missing
callCapletMethod and getCapletRoot methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On restart with an empty systemSubclusters array, the kernel facet was
never registered because provideFacet() was guarded by configs.length > 0.
Persisted run queue items targeting the kernel facet kref would cause
invokeKernelService to throw, crashing the kernel queue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add an `evaluate` RPC method to VatSupervisor that evaluates code in a vat's isolated compartment, enabling REPL functionality while maintaining security isolation from the supervisor.

Key changes:
- New evaluate.ts RPC spec and handler
- VatSupervisor creates a separate eval compartment with vat exports in scope
- VatHandle.evaluate() method for kernel-side API
- Result serialization for JSON-RPC transport (handles functions, symbols, bigints)
- Errors return as { success: false, error } without crashing the vat

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Surface the vat REPL evaluate capability through the full stack:
- Add evaluateVat to VatManager, Kernel, and KernelFacet
- Add evaluateVat RPC handler in kernel-browser-runtime
- Add Vat REPL tab to kernel-ui with vat selector and code input
- Add useEvaluate hook for the UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/system-vats-redux branch from 48ff5d1 to 31868d8 Compare February 6, 2026 23:04
Base automatically changed from rekm/system-vats-redux to main February 10, 2026 22:56
@rekmarks
Copy link
Member Author

If we're doing this, we'll do it on top of #831.

@rekmarks rekmarks closed this Feb 10, 2026
@rekmarks rekmarks deleted the rekm/vat-evaluate branch February 10, 2026 23:10
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

Comments