8dd82d99da
Senses trigger shell commands only. Workflows are invoked via CLI.
SenseTrigger is now { command: string } — no discriminated union.
Closes #318
Co-authored-by: Cursor <cursoragent@cursor.com>
2.9 KiB
2.9 KiB
Sense
A compute() function that samples or derives external data. The only first-class citizen in nerve.
Contract
Each sense module (src/index.ts) must export:
export { snapshots as table } from "./schema.ts"; // drizzle table for runtime to insert into
export async function compute(): Promise<ComputeResult<T>> { ... } // pure, no args
Function Signature & Input Schema:
compute()is parameterless — no direct inputs, environment variables available- No database access within compute — runtime provides isolated execution context
- Must be pure function (no side effects, no external API calls)
Return Value Contract (current engine):
compute(state)returnsPromise<{ state: S; trigger: SenseTrigger | null }>whereSenseTrigger = { command: string }.trigger: null→ persist state only; no shell commandtrigger: { command }→ persist state; worker runs the command withshell: trueafter a successful compute
- Workflows are not started from
trigger; use CLI / daemon IPC (nerve workflow trigger, etc.).
Error Handling & Serialization:
- Exceptions caught by worker, logged as errors (state unchanged)
- State must be JSON-serializable (persisted to
data/senses/<name>.json) - Invalid
triggershapes fail IPC validation when the worker sendscompute-result
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 on signals from other senses (optional)
Manual Trigger Context
nerve sense trigger <name> sends IPC message to running daemon. The compute context is initialized as follows:
- SQLite Database: Opened in read-write mode at
data/senses/<name>.db - Migrations: All
*.sqlfiles insenses/<name>/migrations/applied in lexicographic order - Environment: Inherits daemon process environment (no special secrets injection)
- Arguments: No runtime arguments or mock inputs supported —
compute()is always pure function with no parameters - Isolation: Runs in forked child process (worker) with full filesystem access within user permissions
- Persistence: Runtime automatically calls
db.insert(table).values(result.signal)if compute returns non-null signal