Skip to content

feat(cli,nodejs): add daemon process with ocap daemon CLI#843

Open
rekmarks wants to merge 17 commits intomainfrom
rekm/grypez/daemon
Open

feat(cli,nodejs): add daemon process with ocap daemon CLI#843
rekmarks wants to merge 17 commits intomainfrom
rekm/grypez/daemon

Conversation

@rekmarks
Copy link
Member

@rekmarks rekmarks commented Feb 19, 2026

Adds a long-running daemon process to the OCAP kernel, managed via new ocap daemon CLI subcommands. The daemon spawns as a detached child process, exposes the kernel's RPC service over a Unix domain socket (~/.ocap/daemon.sock), and auto-starts on first exec invocation.

Supersedes #842, and defers the introduction of its notion of a "console vat" and repl / IO functionality to a later date.

New CLI commands

  • ocap daemon start — start the daemon (or confirm it is already running)
  • ocap daemon stop — gracefully shut down the daemon
  • ocap daemon purge --force — stop the daemon and delete all persisted state
  • ocap daemon exec [method] [params-json] — send a JSON-RPC call to the daemon (defaults to getStatus)

Kernel changes

  • makeKernel() now returns { kernel, kernelDatabase } and accepts optional systemSubclusters
  • ifDefined utility moved from kernel-agents to kernel-utils
  • startRelay moved from cli to kernel-utils/libp2p

New modules

  • @ocap/nodejs/daemon — daemon orchestration (startDaemon, deleteDaemonState, startRpcSocketServer, socket line protocol)
  • @ocap/cli/commands/daemon* — CLI-side daemon client, spawner, and command handlers

Note

High Risk
Introduces a new local JSON-RPC daemon that can execute kernel RPC methods (including arbitrary SQL via executeDBQuery) over a Unix socket and adds process-management/spawn logic, which increases security and operational risk if misused or misconfigured.

Overview
Adds a detached, long-running daemon that runs the kernel and exposes its RPC surface over a Unix domain socket (~/.ocap/daemon.sock), plus new ocap daemon start|stop|purge --force|exec commands that can auto-spawn the daemon and issue JSON-RPC requests.

Introduces @ocap/nodejs/daemon (socket line protocol, JSON-RPC socket server with shutdown interception, and state purge helper), updates makeKernel() to return { kernel, kernelDatabase } (and accept optional systemSubclusters), and adjusts tests accordingly (including new end-to-end coverage for the socket protocol).

Refactors shared pieces by moving startRelay to @metamask/kernel-utils/libp2p (and updating callers/tests) and moving ifDefined into @metamask/kernel-utils, along with dependency and script invocation updates (e.g., yarn run -T ocap ..., ESLint/LavaMoat allowances).

Written by Cursor Bugbot for commit e10fe79. This will update automatically on new commits. Configure here.

grypez and others added 13 commits February 17, 2026 18:29
…tils

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

The system console vat manages a REPL loop over an IO channel,
dispatching CLI commands (help, status, launch, terminate, subclusters,
listRefs, revoke) and managing refs in persistent baggage. Refs use
a monotonic counter (d-1, d-2, ...) since crypto.randomUUID() is
unavailable under SES lockdown. Cross-vat errors are serialized via
JSON.stringify fallback for reliable error reporting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add startDaemon() which boots a kernel with a system console vat
listening on a UNIX domain socket IO channel. The kernel process IS
the daemon — no separate HTTP server. Includes socket channel fix to
block reads when no client is connected, flush-daemon utility, and
e2e tests for the full daemon stack protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the 'ok' CLI that communicates with the kernel daemon over a UNIX
domain socket using newline-delimited JSON. Uses yargs for command
definitions with --help support on all commands. Supports three input
modes: file arg (ok file.ocap method), stdin redirect (ok launch <
config.json), and pipe (cat config.json | ok launch). Relative
bundleSpec paths in launch configs are resolved to file:// URLs
against CWD. Ref results are output as .ocap files when stdout is
not a TTY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement a two-tier access model: unauthenticated daemon-tier commands
(help, status) and privileged ref-based dispatch via .ocap capability
files. Self-ref dispatch bypasses kernel round-trip for the console
root object. Fix kref leaks, improve socket channel reliability with
stale connection detection and client-side retry.
…rect JSON-RPC daemon

Replace the system-console-vat architecture with direct JSON-RPC over Unix
socket. The old flow routed CLI commands through IOChannels and a REPL vat;
the new flow sends JSON-RPC requests directly to kernel RPC handlers.

- Add RPC socket server and daemon lifecycle to @ocap/nodejs under ./daemon
  export path, reusing RpcService and rpcHandlers from the kernel
- Simplify CLI: ok.ts sends JSON-RPC commands, daemon-entry.ts boots kernel
  and starts the daemon socket server
- Move libp2p relay from @ocap/cli to @metamask/kernel-utils under ./libp2p
  export path, breaking the cli<->nodejs dependency cycle
- Remove @ocap/cli devDep from packages that only used the binary; use
  yarn run -T ocap for workspace-wide binary access
- Delete system-console-vat and related IOChannel/ref plumbing
- makeKernel now returns { kernel, kernelDatabase }

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Preserve kernel state across restarts (resetStorage: false)
- Clean up stale socket files before listen
- Add socket-based shutdown RPC with PID+SIGTERM fallback
- Stop daemon before flushing state in begone handler
- Narrow sendCommand retry to ECONNREFUSED/ECONNRESET only
- Replace bare socket probe with getStatus RPC ping
- Use JsonRpcResponse from @metamask/utils with runtime validation
- Extract shared readLine/writeLine into socket-line.ts
- Document 6 known limitations in CLI readme

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge the standalone `ok` binary into the existing `ocap` CLI as nested
`daemon` subcommands (start, stop, begone, exec), removing the need for
two separate entry points.

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

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

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

Move n/no-process-env exemption for cli package to eslint config,
replace process.exit() calls with process.exitCode to allow pending
I/O to complete, and simplify daemon-entry.ts error handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks rekmarks requested a review from a team as a code owner February 19, 2026 06:03
@rekmarks rekmarks changed the title Consolidate ok CLI into ocap daemon command feat(cli,nodejs): add daemon process with ocap daemon CLI Feb 19, 2026
Guard the shutdown function with a stored promise so concurrent calls
from RPC shutdown, SIGTERM, and SIGINT coalesce into a single
handle.close() instead of throwing ERR_SERVER_NOT_RUNNING.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

const { kernel, kernelDatabase } = await makeKernel({
resetStorage: false,
logger,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Daemon creates in-memory database, no state persistence

High Severity

The makeKernel call in daemon-entry.ts omits dbFilename, which causes makeSQLKernelDatabase to default to DEFAULT_DB_FILENAME = ':memory:' — an ephemeral in-memory SQLite database. All kernel state is lost when the daemon process stops, contradicting the PR's purge --force command which promises to "delete all persisted state." Additionally, deleteDaemonState assumes the DB lives at ~/.ocap/kernel.sqlite, a file that will never exist.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

2 participants

Comments