This repository has been archived on 2026-06-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
nerve/packages/daemon
xiaoju c98e14e9e6 refactor(cli): single-package workspace init and root dist build (#274)
Init templates match ~/.uncaged-nerve: scripts/build.mjs writes dist/senses/*/index.js and dist/workflows/*/index.js; drop @uncaged/nerve-skills from generated package.json; refresh Cursor skills rule copy.

Sense worker sends full compute result on signal IPC so the kernel can route workflow triggers; update e2e harness paths (migrations under senses/, noop under dist/workflows).

Fixes #274

Made-with: Cursor
2026-04-30 10:17:44 +00:00
..

@uncaged/nerve-daemon

The observation engine runtime for nerve — runs senses, routes signals, runs the sense scheduler, and manages workflows.

Architecture

Module Source (indicative) Responsibility
Kernel kernel.ts Orchestrator — worker pool, signal bus, sense scheduler, workflow manager, optional file watcher and daemon IPC, config reload hooks
Worker pool worker-pool.ts Fork and supervise one child process per sense group; restart/shutdown; crash cleanup hooks for scheduler state
Kernel sense groups kernel-sense-groups.ts Derive sense groups from config; list senses per group for scheduling
Sense runtime sense worker + Drizzle Per-sense SQLite (node:sqlite), migrations, peer DB reads
Sense worker sense-worker.ts (fork target) Child process entry — runs compute() per sense in a group
Signal bus signal-bus.ts In-memory pub/sub for sense signals
Sense scheduler sense-scheduler.ts Interval + on subscriptions, throttle/coalesce
Workflow manager workflow-manager.ts One worker per workflow name, concurrency (drop/queue), queue caps
Workflow worker workflow-worker.ts Child process — runs RFC-002 threads (start-thread, resume-thread IPC)
IPC (parent ↔ workers) ipc.ts Typed messages for sense and workflow workers (includes resume-thread for recovery)
Log / workflow persistence via @uncaged/nerve-store Structured logs, workflow_runs, thread messages (used for recovery)
Blob store @uncaged/nerve-store CAS under data/blobs/ — sense workers construct createBlobStore(join(nerveRoot, "data", "blobs")) for artifact writes
File watcher file-watcher.ts Watches workspace paths for config / sense / workflow file changes
Kernel file watch kernel-file-watch.ts Maps watcher events to reloadConfig, sense group restart, workflow drainAndRespawn
Daemon IPC daemon-ipc.ts Unix socket server — parses @uncaged/nerve-core DaemonIpcRequest, dispatches trigger-workflow / trigger-sense / list-senses

Crash recovery (workflow workers)

If a workflow worker exits unexpectedly while threads are active:

  • In-flight runs are marked crashed in the log store; the manager respawns a fresh worker.
  • Runs still in started state can be resume-thread’d: the manager rebuilds the message chain from persisted workflow log rows and sends resume-thread to the new worker.
  • Crash-loop backoff: repeated crashes for the same workflow name are counted in a sliding window (60s); after 5 crashes in that window, the manager stops respawning that worker and logs the condition (avoids tight crash loops).

Hot reload (drainAndRespawn) uses a controlled drain: in-flight runs may be marked interrupted when the old worker is torn down after a timeout — that path is distinct from unexpected crash recovery.

Key Design Decisions

  • One worker process per sense group — isolation between groups, shared compute within a group
  • node:sqlite (DatabaseSync) — zero native addons, WAL mode, built into Node.js ≥ 22.5
  • Throttle + coalesce — if compute is in-flight, at most one pending trigger is queued (no unbounded accumulation)
  • Log ≠ Signal — logs are queryable data assets but cannot trigger the sense scheduler or workflows (prevents feedback loops)

Usage

The daemon is typically started via the CLI (nerve daemon start / nerve dev), but you can embed the kernel:

import { readFileSync } from "node:fs";
import { join } from "node:path";

import { parseNerveConfig } from "@uncaged/nerve-core";
import { createKernel } from "@uncaged/nerve-daemon";

const nerveRoot = "/path/to/workspace";
const yamlPath = join(nerveRoot, "nerve.yaml");
const parsed = parseNerveConfig(readFileSync(yamlPath, "utf8"));
if (!parsed.ok) {
  throw parsed.error;
}

const kernel = createKernel(parsed.value, nerveRoot, {
  enableFileWatcher: true,
  ipcSocketPath: join(nerveRoot, "nerve.sock"),
});

await kernel.ready;

kernel.triggerSense("cpu-usage");
const health = kernel.getHealth();

await kernel.stop();

createKernel(config, nerveRoot, options?)config is a parsed NerveConfig; nerveRoot is the workspace root (contains nerve.yaml, data/, etc.). Optional KernelOptions:

Field Meaning
workerScript Override path to the sense worker entry script (defaults to the package’s resolved worker)
enableFileWatcher Watch config / senses / workflows for hot reload
logStore Inject a LogStore instance (defaults to createLogStore(join(nerveRoot, "data", "logs.db")))
ipcSocketPath When non-null, listen for daemon IPC on this Unix socket path

Install

pnpm add @uncaged/nerve-daemon

Requires Node.js ≥ 22.5 (for node:sqlite).

License

MIT