# Workflow Engine Stateful multi-step execution driven by Roles and a Moderator. ## Workspace Layout (authoring) User Nerve workspaces use a **flat** build: one root `package.json`, one root bundle script (typically `scripts/build.mjs` wired from `scripts.build`), and **no** per-workflow `package.json` or `tsconfig.json`. | Location | Purpose | |----------|---------| | `workflows//index.ts` | Default export: `WorkflowDefinition` (moderator + role map). | | `workflows//roles/.ts` | One module per role — schemas, prompts, `createRole` factories, or hand-written async role functions. | | `dist/workflows//index.js` | Emit of the root build; this is what the daemon loads. | **Naming:** Workflow ids should be **verb-first** kebab-case phrases (e.g. `deploy-staging`, `scan-dependencies`), not opaque nouns alone. Senses follow the same flat pattern: `senses//src/*.ts`, `migrations/`, root build → `dist/senses//index.js`. See `.knowledge/sense.md`. ## Core Concepts - **Workflow** — definition with concurrency strategy - **Thread** — one execution instance, unique `runId` - **Role** — executes actions (has side effects). `(ctx: ThreadContext) → Promise>` - **Moderator** — pure routing function. `(ctx: ThreadContext) → next role | END` ## Thread Lifecycle ``` trigger → queued → started → step_complete ↺ → completed ↓ failed / crashed ``` ## Concurrency Config (nerve.yaml) ```yaml workflows: cleanup: concurrency: 1 overflow: drop # discard if already running code-review: concurrency: 3 overflow: queue max_queue: 20 # queue limit, oldest discarded ``` ## createRole Helper `createRole` builds a `Role` from an adapter, prompt, Zod schema, and extract config: ```ts import { createRole } from "@uncaged/nerve-workflow-utils"; import { cursorAdapter } from "@uncaged/nerve-adapter-cursor"; import { z } from "zod"; const coderSchema = z.object({ plan: z.string(), files: z.array(z.string()) }); const coder = createRole(cursorAdapter, coderPrompt, coderSchema, { provider: { baseUrl: "...", apiKey: "...", model: "qwen-plus" }, }); // Use in WorkflowDefinition const workflow: WorkflowDefinition = { name: "develop", roles: { coder, reviewer }, moderator, }; ``` - `adapter: AgentFn` — direct function reference - `prompt: string | ((ctx: ThreadContext) => Promise)` — static or dynamic - `meta: z.ZodType` — Zod schema, directly (no wrapper needed) - `extract: LlmExtractorConfig` — provider for structured extraction ## Runtime Enforcement Mechanisms ### Role Authority & Validation **Role Function Lookup**: - Roles accessed via `def.roles[nextRole]` dictionary lookup - Unknown roles trigger immediate workflow error (`Unknown role: ${nextRole}`) - No dynamic role registration during execution **Result Validation** (`validateRoleResult()`): ```typescript // Required return shape from every role function { content: string, meta: Record } ``` - `content` must be string (non-string → workflow error) - `meta` must be plain object (array/null/primitive → workflow error) - Validation failure terminates thread immediately ### Moderator Authority & Routing Control **Next Role Selection**: - Moderator must return role name from `roles` keys OR `END` symbol - Called after every role completion (receives full context) - No validation of role name until execution attempt - Pure function constraint: cannot perform side effects **Causal Chain Integrity**: - Moderator receives immutable **ThreadContext**: `{ threadId, start, steps }` - Steps array contains ALL role outputs in chronological order - No role can modify prior steps or start metadata - Thread context built from log store on crash recovery ### Unauthorized Command Event Prevention **Message Flow Control**: - Role functions have NO direct access to kernel IPC - All outputs flow through `sendWorkflowMessage()` wrapper - Worker process validates messages before kernel transmission - No direct log store database access from roles **Process Isolation**: - Roles execute in forked worker processes (not kernel) - File system access limited to user permissions - No network isolation (roles can make arbitrary HTTP calls) - Worker has read/write access to workflow workspace only ### Concurrent Thread Management **Kill Flag Implementation**: ```typescript type KillFlag = { value: boolean }; // Checked before role execution and after completion if (killFlag.value) { sendThreadEvent(runId, "killed", { exitCode: 137 }); return; } ``` **Concurrency Enforcement**: - Workflow manager enforces per-workflow limits in kernel - Excess threads queued/dropped per overflow policy - No role can spawn additional threads (no access to workflow manager)