Agent kernel
The guarded agent lifecycle FSM: VERIFY failing reopens PLAN, there's no edge to SHIP.
What it is
Agent kernel is the engine-neutral base for governed AI agent work: a Zod schema for agent/skill/rule artifacts, a seven-act lifecycle state machine (spec through ship), allow/deny/mutate governance guards, a hooks dispatcher, and an opt-in tamper-evident audit-chain recorder. It imports no vendor SDK and runs no LLM: composition only, consumed by both the base CLI and the Agentic-Dev bundle.
What ships in the module
Browser-safe entry point
Import @caisson/agent-kernel/browser inside a client bundle for the artifact schema and its authoring helpers, the lifecycle act FSM, the governance decision algebra, and the redacting logger. The main entry keeps the complete node-capable surface (the execFile command handler and the audited hash-chain lifecycle), and every browser-entry export is also on it.
Typed agent/skill/rule schema
AgentArtifact, SkillArtifact, and RuleArtifact are a Zod discriminatedUnion on kind, built on @caisson/kernel's strictObject: an unknown field is rejected outright, not silently dropped. A bad artifact fails through parseArtifact as a redaction-safe ValidationError, never the rejected values.
Seven-act lifecycle FSM
ACTS runs spec through ship in canonical order. transition() is the only way to move between acts and throws on any edge outside the fixed TRANSITIONS adjacency. The two branches that matter: verify can reopen plan, and eval has no edge but ship.
allow / deny / mutate governance
governance.ts gives transition guards and hook vetoes one shared decision shape. evaluateGuards folds a guard list fail-closed: the first deny short-circuits, and a guard that throws is itself treated as a deny, so a buggy guard can never accidentally admit a transition.
Hooks dispatcher: fail-open on crashes, fail-closed on vetoes
HookDispatcher.dispatch runs registered before:/after: act handlers in order. A handler that throws is isolated, reported to an optional sink (hook name + error type only, never a message or stack), and treated as allow; a handler that returns deny() still short-circuits the loop.
Safe shell hooks: no interpolation is possible
commandHandler runs a fixed argv array through node:child_process execFile: no shell is spawned, and no HookContext value can reach the command's arguments, so shell injection through a hook is structurally impossible, not just avoided by convention.
Opt-in tamper-evident audit chain
AuditedLifecycle wraps every governed transition with the kernel's chainEntry/anchorChain/verifyChain hash-chain primitives, the same mechanism the Compliance bundle's audit-worm package uses. Off by default; set audited: true and each admitted step becomes an append-only, tamper-evident chain entry.
/**
* Legal forward adjacency. The two branch edges:
* - `verify → plan` — a failed goal-backward verify opens a fresh PLAN cycle (does not SHIP).
* - `sweep → ship` — an untagged phase skips EVAL straight to SHIP.
* An EVAL regression is a fail-stop (no edge out of `eval` but `ship`); `ship` is terminal.
*/
const TRANSITIONS: Record<Act, readonly Act[]> = {
spec: ["plan"],
plan: ["execute"],
execute: ["verify"],
verify: ["sweep", "plan"],
sweep: ["eval", "ship"],
eval: ["ship"],
ship: [],
};- verify is the only act with two outgoing edges, a failed VERIFY reopens plan, it has no edge to ship.
- ship: [], an empty adjacency list makes SHIP a hard terminal state in the type itself, not just a documented convention.