refactor(workflow-utils): Role factory templates — createCursorRole, createHermesRole, createLlmRole, createReActRole #208

Closed
opened 2026-04-28 01:37:14 +00:00 by xiaoju · 0 comments
Owner

Summary

Refocus @uncaged/nerve-workflow-utils from a grab-bag of utilities into a Role factory package. The public API should be template functions that create Role<Meta> instances for common agent types.

Public API

export function createCursorRole<T>(options: CursorRoleRequired<T> & Partial<CursorRoleDefaults>): Role<T>
export function createHermesRole<T>(options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>): Role<T>
export function createLlmRole<T>(options: LlmRoleRequired<T>): Role<T>
export function createReActRole<T>(options: ReActRoleRequired<T> & Partial<ReActRoleDefaults>): Role<T>

Design Decisions

1. Meta Extraction — all Roles need extract

Agents only guarantee content: string. Structured metadata is extracted via a separate cheap LLM call (llmExtract) with a Zod schema + tool_choice constraint.

type MetaExtractConfig<T> = {
  provider: LlmProvider       // cheap model for extraction
  schema: z.ZodType<T>        // target meta schema
}

Internal flow: agent executes → content → llmExtract(content, schema) → RoleResult<Meta>

2. Prompt — two shapes for two categories

CLI Roles (Cursor / Hermes) — return a single string prompt. The agent fetches richer context itself via nerve thread / MCP. Keep it simple to avoid shell escaping issues.

type CliPromptFn = (threadId: string) => Promise<string>

API Roles (LLM / ReAct) — return a full message array for chat completions.

type LlmMessage = { role: "system" | "user" | "assistant"; content: string }
type LlmPromptFn = (threadId: string) => Promise<LlmMessage[]>

3. Required vs Defaults — no nullable, no optional properties

Required params = no reasonable default, caller must provide.
Default params = have sensible defaults, caller can override via Partial<Defaults>.
No | null, no ?: — use default values as fallback.

Type Definitions

Cursor

type CursorRoleRequired<T> = {
  cwd: string
  prompt: CliPromptFn
  extract: MetaExtractConfig<T>
}

type CursorRoleDefaults = {
  mode: "plan" | "ask" | "default"  // default: "default"
  model: string                      // default: "auto"
  env: SpawnEnv                      // default: {}
  timeoutMs: number                  // default: 300_000
}

Hermes

type HermesRoleRequired<T> = {
  prompt: CliPromptFn
  extract: MetaExtractConfig<T>
}

type HermesRoleDefaults = {
  model: string                // default: config default model
  provider: string             // default: "auto"
  skills: string[]             // default: []
  quiet: boolean               // default: true
  maxTurns: number             // default: 90
  env: SpawnEnv                // default: {}
  timeoutMs: number            // default: 600_000
}

No cwd (Hermes decides where to work). yolo always on (non-interactive). toolsets not exposed (all enabled by default).

LLM

type LlmRoleRequired<T> = {
  provider: LlmProvider
  prompt: LlmPromptFn
  extract: MetaExtractConfig<T>
}

No defaults — all params are required.

ReAct

type ReActRoleRequired<T> = {
  provider: LlmProvider
  tools: ReActTool[]
  prompt: LlmPromptFn
  extract: MetaExtractConfig<T>
}

type ReActRoleDefaults = {
  maxIterations: number        // default: 10
}

Existing Code

Current utils (spawnSafe, llmExtract, schemaDefaults, readNerveYaml, etc.) stay as internal utils within the package. No other package depends on workflow-utils, so no migration needed.

Tasks

  • Define shared types (MetaExtractConfig, CliPromptFn, LlmPromptFn, LlmMessage, ReActTool)
  • Implement createCursorRole
  • Implement createHermesRole
  • Implement createLlmRole
  • Implement createReActRole
  • Update index.ts exports — role factories as primary API
  • Tests for each factory
  • Update existing workflows to use new factories (if any)

小橘 🍊(NEKO Team)

## Summary Refocus `@uncaged/nerve-workflow-utils` from a grab-bag of utilities into a **Role factory** package. The public API should be template functions that create `Role<Meta>` instances for common agent types. ## Public API ```typescript export function createCursorRole<T>(options: CursorRoleRequired<T> & Partial<CursorRoleDefaults>): Role<T> export function createHermesRole<T>(options: HermesRoleRequired<T> & Partial<HermesRoleDefaults>): Role<T> export function createLlmRole<T>(options: LlmRoleRequired<T>): Role<T> export function createReActRole<T>(options: ReActRoleRequired<T> & Partial<ReActRoleDefaults>): Role<T> ``` ## Design Decisions ### 1. Meta Extraction — all Roles need `extract` Agents only guarantee `content: string`. Structured metadata is extracted via a separate cheap LLM call (`llmExtract`) with a Zod schema + tool_choice constraint. ```typescript type MetaExtractConfig<T> = { provider: LlmProvider // cheap model for extraction schema: z.ZodType<T> // target meta schema } ``` Internal flow: `agent executes → content → llmExtract(content, schema) → RoleResult<Meta>` ### 2. Prompt — two shapes for two categories **CLI Roles (Cursor / Hermes)** — return a single string prompt. The agent fetches richer context itself via `nerve thread` / MCP. Keep it simple to avoid shell escaping issues. ```typescript type CliPromptFn = (threadId: string) => Promise<string> ``` **API Roles (LLM / ReAct)** — return a full message array for chat completions. ```typescript type LlmMessage = { role: "system" | "user" | "assistant"; content: string } type LlmPromptFn = (threadId: string) => Promise<LlmMessage[]> ``` ### 3. Required vs Defaults — no nullable, no optional properties Required params = no reasonable default, caller must provide. Default params = have sensible defaults, caller can override via `Partial<Defaults>`. **No `| null`, no `?:`** — use default values as fallback. ## Type Definitions ### Cursor ```typescript type CursorRoleRequired<T> = { cwd: string prompt: CliPromptFn extract: MetaExtractConfig<T> } type CursorRoleDefaults = { mode: "plan" | "ask" | "default" // default: "default" model: string // default: "auto" env: SpawnEnv // default: {} timeoutMs: number // default: 300_000 } ``` ### Hermes ```typescript type HermesRoleRequired<T> = { prompt: CliPromptFn extract: MetaExtractConfig<T> } type HermesRoleDefaults = { model: string // default: config default model provider: string // default: "auto" skills: string[] // default: [] quiet: boolean // default: true maxTurns: number // default: 90 env: SpawnEnv // default: {} timeoutMs: number // default: 600_000 } ``` No `cwd` (Hermes decides where to work). `yolo` always on (non-interactive). `toolsets` not exposed (all enabled by default). ### LLM ```typescript type LlmRoleRequired<T> = { provider: LlmProvider prompt: LlmPromptFn extract: MetaExtractConfig<T> } ``` No defaults — all params are required. ### ReAct ```typescript type ReActRoleRequired<T> = { provider: LlmProvider tools: ReActTool[] prompt: LlmPromptFn extract: MetaExtractConfig<T> } type ReActRoleDefaults = { maxIterations: number // default: 10 } ``` ## Existing Code Current utils (`spawnSafe`, `llmExtract`, `schemaDefaults`, `readNerveYaml`, etc.) stay as **internal utils** within the package. No other package depends on `workflow-utils`, so no migration needed. ## Tasks - [ ] Define shared types (`MetaExtractConfig`, `CliPromptFn`, `LlmPromptFn`, `LlmMessage`, `ReActTool`) - [ ] Implement `createCursorRole` - [ ] Implement `createHermesRole` - [ ] Implement `createLlmRole` - [ ] Implement `createReActRole` - [ ] Update `index.ts` exports — role factories as primary API - [ ] Tests for each factory - [ ] Update existing workflows to use new factories (if any) 小橘 🍊(NEKO Team)
This repo is archived. You cannot comment on issues.
No Label
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#208