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/.knowledge/sense.md
T
xiaoju c8e42c838b fix(daemon): harden state persistence, ReadonlyArray triggers
1. writeState: atomic write via temp file + rename
2. readState: distinguish missing file vs corrupt JSON (warn on error)
3. executeCompute: write disk before updating memory state
4. SenseInfo.triggers: ReadonlyArray<string>
5. CLAUDE.md: added Sense State Persistence docs
6. .knowledge/: updated architecture, sense, worker-isolation,
   storage-layer, cli, coding-conventions for stateful sense

Fixes #313
2026-05-01 12:08:59 +00:00

2.6 KiB

Sense

A stateful compute(state) function that samples or derives external data. Returns new state and an optional workflow trigger.

Contract

Each sense module (src/index.ts) must export:

type MyState = { lastRun: number | null };

export const initialState: MyState = { lastRun: null };

export async function compute(state: MyState): Promise<{
  state: MyState;
  workflow: WorkflowTrigger | null;
}> {
  // ... observe external world, derive new state
  return { state: { lastRun: Date.now() }, workflow: null };
}

Function Signature:

  • compute(state: S) — receives previous state (or initialState on first run)
  • Returns { state: S; workflow: WorkflowTrigger | null }
  • workflow: null → no workflow triggered
  • workflow: { name, maxRounds, prompt, dryRun } → triggers a workflow

State Persistence:

  • State stored as JSON at data/senses/<name>.json
  • Read on worker startup; if missing or corrupt, initialState is used
  • Written atomically (temp file + rename) after each successful compute
  • Memory state updated only after disk write succeeds

Error Handling:

  • Exceptions caught by worker, logged as errors (state unchanged)
  • State payload must be JSON-serializable
  • Invalid workflow triggers rejected by daemon (workflow not started, compute still succeeds)

Timeout & Scheduling Semantics:

  • Timeout priority: explicit config → AbortSignal → DEFAULT_TIMEOUT_MS (30s)
  • Enforced via Promise.race() with timeout promise
  • Grace period can trigger process.exit(1) after timeout (kills worker group)
  • Interval translation: YAML config values used directly as milliseconds in setInterval()
  • Jitter control: throttle mechanism prevents rapid-fire, single deferred trigger per throttle window

Config (nerve.yaml)

senses:
  cpu-usage:
    group: system        # senses in same group share a worker
    throttle: 10s        # min interval between computes
    timeout: 30s         # max compute duration
    grace_period: 5s     # wait before first compute
    interval: 30s        # periodic trigger (optional)
    on: [disk-pressure]  # trigger when another sense completes (optional)

Manual Trigger Context

nerve sense trigger <name> sends IPC message to running daemon. The compute runs in the sense's worker process with:

  • State: Read from data/senses/<name>.json (or initialState if missing)
  • Environment: Inherits daemon process environment
  • Isolation: Runs in forked child process (worker) with full filesystem access within user permissions
  • Persistence: State written to JSON file after successful compute