c8e42c838b
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
2.6 KiB
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 (orinitialStateon first run)- Returns
{ state: S; workflow: WorkflowTrigger | null } workflow: null→ no workflow triggeredworkflow: { 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,
initialStateis 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(orinitialStateif 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