refactor(core): rename RoleSignal → RoleStep, StartSignal → StartStep

- RoleStep now includes content and timestamp fields (aligned with StartStep)
- ModeratorContext.signal → ModeratorContext.step
- workflow-utils: start-signal.ts → start-step.ts, isDryRun updated

Fixes #109
This commit is contained in:
2026-04-25 02:34:33 +00:00
parent 47d23bc1a7
commit beada2ae09
7 changed files with 32 additions and 30 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ Shared types and configuration parser for the [nerve](../../README.md) observati
- **Config parser** — `parseNerveConfig(yaml)` validates and parses `nerve.yaml` into `NerveConfig` (rejects reflex entries that declare a `workflow` key; reflexes only schedule senses)
- **Sense → workflow routing** — `parseSenseWorkflowDirective`, `routeSenseComputeOutput`, and types `ParsedSenseWorkflowDirective`, `SenseComputeRoute`
- **Daemon IPC protocol** — request/response types (`DaemonIpcRequest`, `DaemonIpcResponse`, …) and `parseDaemonIpcRequest` for newline-delimited JSON on the CLI ↔ daemon socket
- **Workflow automaton types** — `START` / `END` sentinel constants, `WorkflowMessage`, `StartSignal`, `RoleSignal`, `Moderator`, `WorkflowDefinition`, `Role`, `SenseResult`, plus `DEFAULT_ENGINE_MAX_ROUNDS`
- **Workflow automaton types** — `START` / `END` sentinel constants, `WorkflowMessage`, `StartStep`, `RoleStep`, `Moderator`, `WorkflowDefinition`, `Role`, `SenseResult`, plus `DEFAULT_ENGINE_MAX_ROUNDS`
- **Result type** — `Result<T>` with `ok()` / `err()` helpers for explicit error handling (no thrown exceptions for parse paths)
## Usage
+2 -2
View File
@@ -12,8 +12,8 @@ export type {
RoleResult,
Role,
RoleMeta,
StartSignal,
RoleSignal,
StartStep,
RoleStep,
ModeratorContext,
Moderator,
WorkflowDefinition,
+8 -8
View File
@@ -82,7 +82,7 @@ export type RoleResult<Meta> = { content: string; meta: Meta };
* script, HTTP request, etc.
*/
export type Role<Meta> = (
start: StartSignal,
start: StartStep,
messages: WorkflowMessage[],
) => Promise<RoleResult<Meta>>;
@@ -90,25 +90,25 @@ export type Role<Meta> = (
export type RoleMeta = Record<string, Record<string, unknown>>;
/** Engine start frame: prompt, max rounds cap, dry-run flag, and timestamps for the thread. */
export type StartSignal = {
export type StartStep = {
role: START;
content: string;
meta: { maxRounds: number; dryRun: boolean };
timestamp: number;
};
/** A discriminated union of signals from each role, derived from the meta map. */
export type RoleSignal<M extends RoleMeta> = {
[K in keyof M & string]: { role: K; meta: M[K] };
/** A discriminated union of role steps after each execution, aligned with `StartStep` shape. */
export type RoleStep<M extends RoleMeta> = {
[K in keyof M & string]: { role: K; meta: M[K]; content: string; timestamp: number };
}[keyof M & string];
/**
* Moderator input: either the initial start frame or a role signal after a step.
* Moderator input: either the initial start frame or a role step after a step.
* Lets implementations branch on `context.kind` with full typing for each arm.
*/
export type ModeratorContext<M extends RoleMeta> =
| { kind: "start"; start: StartSignal }
| { kind: "step"; signal: RoleSignal<M> };
| { kind: "start"; start: StartStep }
| { kind: "step"; step: RoleStep<M> };
/**
* The moderator — a pure routing function. Receives start vs step context,
+14 -12
View File
@@ -15,7 +15,7 @@ import { join, resolve } from "node:path";
import type {
ModeratorContext,
RoleMeta,
StartSignal,
StartStep,
WorkflowDefinition,
WorkflowMessage,
} from "@uncaged/nerve-core";
@@ -85,13 +85,13 @@ function validateRoleResult(
return true;
}
function isStartMeta(meta: unknown): meta is StartSignal["meta"] {
function isStartMeta(meta: unknown): meta is StartStep["meta"] {
return (
isPlainRecord(meta) && typeof meta.maxRounds === "number" && typeof meta.dryRun === "boolean"
);
}
function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartSignal["meta"] {
function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartStep["meta"] {
if (!isPlainRecord(meta)) {
return { maxRounds: maxRoundsFallback, dryRun: false };
}
@@ -100,10 +100,7 @@ function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartSign
return { maxRounds, dryRun };
}
function startSignalFromWorkflowMessage(
msg: WorkflowMessage,
maxRoundsFallback: number,
): StartSignal {
function startStepFromWorkflowMessage(msg: WorkflowMessage, maxRoundsFallback: number): StartStep {
if (msg.role !== START) {
return {
role: START,
@@ -122,7 +119,7 @@ function startSignalFromWorkflowMessage(
}
type ThreadMessagesState = {
start: StartSignal;
start: StartStep;
/** Role outputs only; never includes the `__start__` frame. */
messages: WorkflowMessage[];
};
@@ -138,7 +135,7 @@ function initThreadMessages(
const [first, ...rest] = resumeMessages;
if (first.role === START) {
return {
start: startSignalFromWorkflowMessage(first, maxRounds),
start: startStepFromWorkflowMessage(first, maxRounds),
messages: [...rest],
};
}
@@ -154,7 +151,7 @@ function initThreadMessages(
};
}
const prompt = freshPrompt ?? "";
const start: StartSignal = {
const start: StartStep = {
role: START,
content: prompt,
meta: { maxRounds, dryRun },
@@ -172,7 +169,7 @@ function initThreadMessages(
async function executeRole(
def: WorkflowDefinition<RoleMeta>,
nextRole: string,
start: StartSignal,
start: StartStep,
messages: WorkflowMessage[],
runId: string,
): Promise<{ content: string; meta: Record<string, unknown> } | null> {
@@ -236,7 +233,12 @@ async function runThread(
const stepContext: ModeratorContext<RoleMeta> = {
kind: "step",
signal: { role: nextRole, meta: result.meta },
step: {
role: nextRole,
meta: result.meta,
content: result.content,
timestamp: message.timestamp,
},
};
nextRole = def.moderator(stepContext, roleRound, maxRounds);
+1 -1
View File
@@ -19,4 +19,4 @@ export {
type SpawnResult,
type SpawnSafeOptions,
} from "./spawn-safe.js";
export { isDryRun } from "./start-signal.js";
export { isDryRun } from "./start-step.js";
@@ -1,6 +0,0 @@
import type { StartSignal } from "@uncaged/nerve-core";
/** Returns the thread-level dry-run flag from the workflow start frame. */
export function isDryRun(start: StartSignal): boolean {
return start.meta.dryRun;
}
@@ -0,0 +1,6 @@
import type { StartStep } from "@uncaged/nerve-core";
/** Returns the thread-level dry-run flag from the workflow start frame. */
export function isDryRun(start: StartStep): boolean {
return start.meta.dryRun;
}