feat(workflow): workflow-generator — meta-workflow that creates new workflows from natural language #99

Closed
opened 2026-04-24 22:49:41 +00:00 by xiaoju · 1 comment
Owner

Background

Currently creating a new workflow requires manually writing a TypeScript module that implements WorkflowDefinition<M> — defining roles, moderator, and wiring up @uncaged/nerve-workflow-utils. This is powerful but has a high barrier to entry.

A workflow-generator workflow would let users describe what they want in natural language, and produce a working workflow directory (~/.uncaged-nerve/workflows/<name>/) ready to run.

How It Works

Input

A prompt describing the desired workflow, e.g.:

"Create a workflow that monitors a GitHub repo for new issues, triages them by label, and posts a summary to Feishu every morning."

Roles

Role Responsibility
analyst Parse the user prompt → extract workflow name, role names, triggers, external tools needed, data flow between roles
architect Design the WorkflowDefinition structure — role graph, moderator routing logic, message types, error handling strategy
coder Generate the actual TypeScript code — index.ts + any helper files, using @uncaged/nerve-core types and @uncaged/nerve-workflow-utils utilities
reviewer Validate the generated code — type-check with tsc --noEmit, verify imports resolve, check moderator covers all transitions, lint for common pitfalls

Moderator Flow

START → analyst → architect → coder → reviewer → (pass ? END : coder)

Reviewer can loop back to coder with fix instructions (max 3 rounds).

Output

~/.uncaged-nerve/workflows/<name>/
├── index.ts          # WorkflowDefinition implementation
├── package.json      # { "type": "module", dependencies }
└── tsconfig.json     # extends base config

Plus auto-register in nerve.yaml under workflows: if not already present.

Key Design Decisions

  1. Use llmExtract<T>() in analyst — structured extraction with Zod schema for workflow spec
  2. Use cursorAgent() in coder — leverage cursor-agent for code generation with .cursor/rules context from the nerve repo
  3. Use spawnSafe() in reviewer — run tsc --noEmit safely to validate generated code
  4. Template-based scaffolding — architect produces a structural plan, coder fills in implementation using sense-generator as reference
  5. Idempotent — running twice with same name overwrites (no duplicates)

Acceptance Criteria

  • ~/.uncaged-nerve/workflows/workflow-generator/index.ts implements WorkflowDefinition
  • 4 roles: analyst, architect, coder, reviewer
  • Moderator with retry loop (reviewer → coder, max 3 rounds)
  • Generated workflows pass tsc --noEmit
  • Generated workflows follow the same structure as sense-generator
  • Auto-registers new workflow in nerve.yaml
  • Works with DashScope (qwen) provider via llmExtract

References

  • sense-generator workflow as the canonical example
  • packages/workflow-utils for shared utilities
  • packages/core/src/types.ts for WorkflowDefinition, Role, Moderator

小橘 🍊(NEKO Team)

## Background Currently creating a new workflow requires manually writing a TypeScript module that implements `WorkflowDefinition<M>` — defining roles, moderator, and wiring up `@uncaged/nerve-workflow-utils`. This is powerful but has a high barrier to entry. A **workflow-generator** workflow would let users describe what they want in natural language, and produce a working workflow directory (`~/.uncaged-nerve/workflows/<name>/`) ready to run. ## How It Works ### Input A prompt describing the desired workflow, e.g.: > "Create a workflow that monitors a GitHub repo for new issues, triages them by label, and posts a summary to Feishu every morning." ### Roles | Role | Responsibility | |------|---------------| | `analyst` | Parse the user prompt → extract workflow name, role names, triggers, external tools needed, data flow between roles | | `architect` | Design the `WorkflowDefinition` structure — role graph, moderator routing logic, message types, error handling strategy | | `coder` | Generate the actual TypeScript code — `index.ts` + any helper files, using `@uncaged/nerve-core` types and `@uncaged/nerve-workflow-utils` utilities | | `reviewer` | Validate the generated code — type-check with `tsc --noEmit`, verify imports resolve, check moderator covers all transitions, lint for common pitfalls | ### Moderator Flow ``` START → analyst → architect → coder → reviewer → (pass ? END : coder) ``` Reviewer can loop back to coder with fix instructions (max 3 rounds). ### Output ``` ~/.uncaged-nerve/workflows/<name>/ ├── index.ts # WorkflowDefinition implementation ├── package.json # { "type": "module", dependencies } └── tsconfig.json # extends base config ``` Plus auto-register in `nerve.yaml` under `workflows:` if not already present. ## Key Design Decisions 1. **Use `llmExtract<T>()` in analyst** — structured extraction with Zod schema for workflow spec 2. **Use `cursorAgent()` in coder** — leverage cursor-agent for code generation with `.cursor/rules` context from the nerve repo 3. **Use `spawnSafe()` in reviewer** — run `tsc --noEmit` safely to validate generated code 4. **Template-based scaffolding** — architect produces a structural plan, coder fills in implementation using `sense-generator` as reference 5. **Idempotent** — running twice with same name overwrites (no duplicates) ## Acceptance Criteria - [ ] `~/.uncaged-nerve/workflows/workflow-generator/index.ts` implements `WorkflowDefinition` - [ ] 4 roles: analyst, architect, coder, reviewer - [ ] Moderator with retry loop (reviewer → coder, max 3 rounds) - [ ] Generated workflows pass `tsc --noEmit` - [ ] Generated workflows follow the same structure as `sense-generator` - [ ] Auto-registers new workflow in `nerve.yaml` - [ ] Works with DashScope (qwen) provider via `llmExtract` ## References - `sense-generator` workflow as the canonical example - `packages/workflow-utils` for shared utilities - `packages/core/src/types.ts` for `WorkflowDefinition`, `Role`, `Moderator` --- 小橘 🍊(NEKO Team)
Author
Owner

Implementation Notes (小橘 🍊)

Design Decision: Business failures are workflow-level, not exit-code-level

After discussion with 主人, we agreed:

  • Exit codes (0/1/2/137/255) are infrastructure-level signals set by the daemon
  • "Task cannot be completed" (e.g. missing info) is business logic — handled by moderator routing, not exit codes
  • Coder returns { status: "blocked", reason: "..." } → moderator decides retry/END

Reference Implementation

~/.uncaged-nerve/workflows/sense-generator/index.ts — follow this exact pattern:

  • Same file structure, imports from @uncaged/nerve-core and @uncaged/nerve-workflow-utils
  • Uses cursorAgent() for code generation, llmExtract() for structured extraction, spawnSafe() for validation
  • resolveDashScopeProvider() pattern for LLM provider resolution
  • Moderator is a pure function returning role name or END

Key Files to Read

  • packages/core/src/workflow.tsWorkflowDefinition<M>, Role, Moderator, RoleResult, StartStep, WorkflowMessage
  • packages/workflow-utils/src/index.ts — exports cursorAgent, llmExtract, spawnSafe, readNerveYaml, nerveAgentContext
  • CLAUDE.md — coding conventions (functional-first, no class, no optional props, Result type)

Coding Conventions (from CLAUDE.md)

  • type over interface, function over class, no this, no inheritance
  • No optional properties (?:), use T | null instead
  • Named exports only (but workflow files use export default workflow — this is the one exception for workflow entry points)
  • Result<T, E> for expected failures, throw only for bugs

Exit Point

The output directory is ~/.uncaged-nerve/workflows/<name>/ with:

  • index.ts — WorkflowDefinition implementation
  • package.json{ "type": "module" } + deps
  • tsconfig.json — extends base

Plus auto-register in ~/.uncaged-nerve/nerve.yaml under workflows:.

## Implementation Notes (小橘 🍊) ### Design Decision: Business failures are workflow-level, not exit-code-level After discussion with 主人, we agreed: - Exit codes (0/1/2/137/255) are infrastructure-level signals set by the daemon - "Task cannot be completed" (e.g. missing info) is business logic — handled by moderator routing, not exit codes - Coder returns `{ status: "blocked", reason: "..." }` → moderator decides retry/END ### Reference Implementation `~/.uncaged-nerve/workflows/sense-generator/index.ts` — follow this exact pattern: - Same file structure, imports from `@uncaged/nerve-core` and `@uncaged/nerve-workflow-utils` - Uses `cursorAgent()` for code generation, `llmExtract()` for structured extraction, `spawnSafe()` for validation - `resolveDashScopeProvider()` pattern for LLM provider resolution - Moderator is a pure function returning role name or END ### Key Files to Read - `packages/core/src/workflow.ts` — `WorkflowDefinition<M>`, `Role`, `Moderator`, `RoleResult`, `StartStep`, `WorkflowMessage` - `packages/workflow-utils/src/index.ts` — exports `cursorAgent`, `llmExtract`, `spawnSafe`, `readNerveYaml`, `nerveAgentContext` - `CLAUDE.md` — coding conventions (functional-first, no class, no optional props, Result type) ### Coding Conventions (from CLAUDE.md) - `type` over `interface`, `function` over `class`, no `this`, no inheritance - No optional properties (`?:`), use `T | null` instead - Named exports only (but workflow files use `export default workflow` — this is the one exception for workflow entry points) - `Result<T, E>` for expected failures, throw only for bugs ### Exit Point The output directory is `~/.uncaged-nerve/workflows/<name>/` with: - `index.ts` — WorkflowDefinition implementation - `package.json` — `{ "type": "module" }` + deps - `tsconfig.json` — extends base Plus auto-register in `~/.uncaged-nerve/nerve.yaml` under `workflows:`.
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#99