docs: add implementation plan for @uncaged/workflow extraction

Refs #320
This commit is contained in:
2026-05-05 10:16:33 +00:00
parent 8dd82d99da
commit 7d0200fa15
@@ -0,0 +1,496 @@
# Extract Workflow Engine into `@uncaged/workflow` — Implementation Plan
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:** Extract the workflow engine (types, runtime, IPC, manager) from nerve-core and nerve-daemon into a standalone `@uncaged/workflow` package.
**Architecture:** Create `packages/workflow/` as a new pnpm workspace package. Move workflow types from core and workflow runtime from daemon into it. The daemon becomes a consumer of `@uncaged/workflow`. No backward-compat re-exports — breaking change, update all consumers in one shot.
**Tech Stack:** TypeScript, pnpm workspace, rslib (bundler, same as other packages), Biome
**Ref:** Fixes #320
---
## Phase 1: Scaffold `packages/workflow/`
### Task 1: Create package skeleton
**Objective:** Create the `@uncaged/workflow` package with package.json, tsconfig, rslib config.
**Files:**
- Create: `packages/workflow/package.json`
- Create: `packages/workflow/tsconfig.json`
- Create: `packages/workflow/rslib.config.ts`
- Create: `packages/workflow/src/index.ts` (empty barrel)
**Steps:**
1. Copy `packages/workflow-utils/package.json` as template, change name to `@uncaged/workflow`, remove all dependencies except dev deps (typescript, rslib, etc.). No runtime deps initially.
2. Copy `packages/workflow-utils/tsconfig.json` and `rslib.config.ts` as-is (same monorepo conventions).
3. Create empty `packages/workflow/src/index.ts`:
```typescript
// @uncaged/workflow — standalone workflow orchestration engine
```
4. Verify:
```bash
cd packages/workflow && pnpm install && pnpm run build
```
5. Commit:
```bash
git add packages/workflow/
git commit -m "chore(workflow): scaffold @uncaged/workflow package
Refs #320"
```
---
## Phase 2: Move Types from Core
### Task 2: Move `workflow.ts` types to `@uncaged/workflow`
**Objective:** Move all workflow types and constants from `packages/core/src/workflow.ts``packages/workflow/src/types.ts`.
**Files:**
- Create: `packages/workflow/src/types.ts`
- Modify: `packages/workflow/src/index.ts`
**Steps:**
1. Copy `packages/core/src/workflow.ts``packages/workflow/src/types.ts` verbatim (all 83 lines — `START`, `END`, `DEFAULT_ENGINE_MAX_ROUNDS`, all types).
2. Export everything from `packages/workflow/src/index.ts`:
```typescript
export {
START,
END,
DEFAULT_ENGINE_MAX_ROUNDS,
} from "./types.js";
export type {
WorkflowMessage,
RoleResult,
Role,
RoleMeta,
StartStep,
ThreadContext,
WorkflowContext,
AgentFn,
RoleStep,
ModeratorContext,
Moderator,
WorkflowDefinition,
} from "./types.js";
```
3. Build to verify types compile:
```bash
cd packages/workflow && pnpm run build
```
4. Commit:
```bash
git commit -am "refactor(workflow): move workflow types from core to @uncaged/workflow
Refs #320"
```
### Task 3: Move `WorkflowConfig` to `@uncaged/workflow`
**Objective:** Move the `WorkflowConfig` type (and its constituent types `DropOverflowConfig`, `QueueOverflowConfig`) from core/config.ts to the workflow package.
**Files:**
- Create: `packages/workflow/src/config.ts`
- Modify: `packages/workflow/src/index.ts`
- Modify: `packages/core/src/config.ts` — remove `WorkflowConfig`, `DropOverflowConfig`, `QueueOverflowConfig`; import from `@uncaged/workflow` instead
**Steps:**
1. Create `packages/workflow/src/config.ts` with the three types:
```typescript
export type DropOverflowConfig = {
concurrency: number;
overflow: "drop";
};
export type QueueOverflowConfig = {
concurrency: number;
overflow: "queue";
maxQueue: number;
};
export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig;
```
2. Re-export from `packages/workflow/src/index.ts`.
3. In `packages/core/package.json`, add dependency: `"@uncaged/workflow": "workspace:*"`.
4. In `packages/core/src/config.ts`:
- Remove the three type definitions
- Add `import type { WorkflowConfig, DropOverflowConfig, QueueOverflowConfig } from "@uncaged/workflow";`
- Keep the re-export from `packages/core/src/index.ts` pointing to config.ts (which now re-exports from workflow)
5. Build entire workspace:
```bash
pnpm run build
```
6. Commit:
```bash
git commit -am "refactor(workflow): move WorkflowConfig types to @uncaged/workflow
Refs #320"
```
### Task 4: Remove workflow types from core, update core exports
**Objective:** Delete `packages/core/src/workflow.ts` entirely. Core re-exports workflow types from `@uncaged/workflow`.
**Files:**
- Delete: `packages/core/src/workflow.ts`
- Modify: `packages/core/src/index.ts` — change workflow exports to re-export from `@uncaged/workflow`
- Modify: `packages/core/src/config.ts` — remove import of `DEFAULT_ENGINE_MAX_ROUNDS` from deleted file
**Steps:**
1. In `packages/core/src/config.ts`, replace:
```typescript
import { DEFAULT_ENGINE_MAX_ROUNDS } from "./workflow.js";
```
with:
```typescript
import { DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow";
```
2. In `packages/core/src/index.ts`, replace all workflow.js imports with re-exports from `@uncaged/workflow`:
```typescript
// Workflow types — re-exported from @uncaged/workflow
export {
START, END, DEFAULT_ENGINE_MAX_ROUNDS,
} from "@uncaged/workflow";
export type {
WorkflowMessage, RoleResult, Role, RoleMeta, StartStep,
ThreadContext, WorkflowContext, AgentFn, RoleStep,
ModeratorContext, Moderator, WorkflowDefinition,
} from "@uncaged/workflow";
```
3. Delete `packages/core/src/workflow.ts`.
4. Full build + test:
```bash
pnpm run build && pnpm test
```
5. Commit:
```bash
git commit -am "refactor(core): remove workflow.ts, re-export from @uncaged/workflow
Refs #320"
```
---
## Phase 3: Move Workflow IPC Messages
### Task 5: Extract workflow IPC types to `@uncaged/workflow`
**Objective:** Move workflow-related IPC message types from `packages/daemon/src/ipc.ts` to `packages/workflow/src/ipc.ts`. Sense IPC stays in daemon.
**Files:**
- Create: `packages/workflow/src/ipc.ts`
- Modify: `packages/workflow/src/index.ts`
- Modify: `packages/daemon/src/ipc.ts` — remove workflow IPC types, import from `@uncaged/workflow`
**Steps:**
1. Extract to `packages/workflow/src/ipc.ts`:
- `StartThreadMessage`, `ResumeThreadMessage`, `KillThreadMessage`
- `ThreadLifecycleEvent`, `ThreadEventMessage`, `WorkflowErrorMessage`, `ThreadWorkflowMessage`
- Workflow-related validation logic from `parseParentToWorkerMessage`
- Union type for workflow parent→worker messages
2. Keep in daemon `ipc.ts`:
- `ComputeMessage`, `ShutdownMessage`, `HealthRequestMessage`
- Sense-related worker→parent messages
- The combined `ParentToWorkerMessage` union (imports workflow types from `@uncaged/workflow`)
3. Add `@uncaged/workflow` as dependency to `packages/daemon/package.json`.
4. Build + test:
```bash
pnpm run build && pnpm test
```
5. Commit:
```bash
git commit -am "refactor(workflow): move workflow IPC types to @uncaged/workflow
Refs #320"
```
---
## Phase 4: Move Workflow Runtime
### Task 6: Move `workflow-worker.ts` to `@uncaged/workflow`
**Objective:** Move workflow execution runtime (the worker that runs inside a child process) from daemon to the workflow package.
**Files:**
- Move: `packages/daemon/src/workflow-worker.ts` → `packages/workflow/src/worker.ts`
- Modify: `packages/workflow/src/index.ts`
- Modify: `packages/daemon/` — update worker spawn path
**Steps:**
1. Copy `workflow-worker.ts` to `packages/workflow/src/worker.ts`.
2. Update imports: replace `@uncaged/nerve-core` with local imports from `./types.js`, `./ipc.js`.
3. Export the worker entry point or the worker file path from `@uncaged/workflow` so daemon can spawn it.
4. In daemon, update worker spawn to reference `@uncaged/workflow`'s worker.
5. Delete `packages/daemon/src/workflow-worker.ts`.
6. Build + test:
```bash
pnpm run build && pnpm test
```
7. Commit:
```bash
git commit -am "refactor(workflow): move workflow-worker runtime to @uncaged/workflow
Refs #320"
```
### Task 7: Move `workflow-manager.ts` and `workflow-manager-support.ts` to `@uncaged/workflow`
**Objective:** Move workflow process management from daemon to workflow package.
**Files:**
- Move: `packages/daemon/src/workflow-manager.ts` → `packages/workflow/src/manager.ts`
- Move: `packages/daemon/src/workflow-manager-support.ts` → `packages/workflow/src/manager-support.ts`
- Modify: `packages/daemon/src/kernel.ts` — import from `@uncaged/workflow`
**Steps:**
1. Copy both files to `packages/workflow/src/`.
2. Update imports to use local paths within the workflow package.
3. Export manager creation function from `packages/workflow/src/index.ts`.
4. Update `packages/daemon/src/kernel.ts` to import workflow manager from `@uncaged/workflow`.
5. Delete the original files from daemon.
6. Build + test:
```bash
pnpm run build && pnpm test
```
7. Commit:
```bash
git commit -am "refactor(workflow): move workflow-manager to @uncaged/workflow
Refs #320"
```
---
## Phase 5: Update All Consumers
### Task 8: Update `workflow-utils` to depend on `@uncaged/workflow`
**Objective:** Change `@uncaged/nerve-workflow-utils` to import workflow types from `@uncaged/workflow` instead of `@uncaged/nerve-core`.
**Files:**
- Modify: `packages/workflow-utils/package.json` — replace `@uncaged/nerve-core` dep with `@uncaged/workflow`
- Modify: all `packages/workflow-utils/src/*.ts` — update import paths
**Steps:**
1. In `package.json`, replace `"@uncaged/nerve-core": "workspace:*"` with `"@uncaged/workflow": "workspace:*"`.
2. Find-and-replace all `from "@uncaged/nerve-core"` → `from "@uncaged/workflow"` in `packages/workflow-utils/src/`.
3. Build + test:
```bash
pnpm run build && pnpm test
```
4. Commit:
```bash
git commit -am "refactor(workflow-utils): import from @uncaged/workflow
Refs #320"
```
### Task 9: Update `workflow-meta` to depend on `@uncaged/workflow`
**Objective:** Same as Task 8 but for `packages/workflow-meta/`.
**Files:**
- Modify: `packages/workflow-meta/package.json`
- Modify: all `packages/workflow-meta/src/**/*.ts`
**Steps:** Same pattern as Task 8.
### Task 10: Update adapter packages
**Objective:** Update `adapter-cursor` and `adapter-hermes` to import workflow types from `@uncaged/workflow`.
**Files:**
- Modify: `packages/adapter-cursor/src/index.ts`
- Modify: `packages/adapter-cursor/package.json`
- Modify: `packages/adapter-hermes/src/index.ts`
- Modify: `packages/adapter-hermes/package.json`
**Steps:** Same pattern — add `@uncaged/workflow` dep, update imports.
### Task 11: Update CLI package
**Objective:** Update CLI to import workflow types from `@uncaged/workflow`.
**Files:**
- Modify: `packages/cli/package.json`
- Modify: `packages/cli/src/commands/workflow.ts`
- Modify: `packages/cli/src/commands/thread.ts`
- Modify: `packages/cli/src/commands/create.ts`
- Modify: `packages/cli/src/workflow-agent-validation.ts`
- Modify: other files that import workflow types from nerve-core
**Steps:**
1. Add `"@uncaged/workflow": "workspace:*"` to `packages/cli/package.json`.
2. In each file, change workflow-related imports from `@uncaged/nerve-core` to `@uncaged/workflow`.
3. Build + test:
```bash
pnpm run build && pnpm test
```
4. Commit:
```bash
git commit -am "refactor(cli): import workflow types from @uncaged/workflow
Refs #320"
```
---
## Phase 6: Clean Up Core Re-exports
### Task 12: Remove workflow re-exports from `@uncaged/nerve-core`
**Objective:** Core no longer re-exports any workflow types. All consumers import directly from `@uncaged/workflow`.
**Files:**
- Modify: `packages/core/src/index.ts` — remove all workflow re-exports
- Modify: `packages/core/package.json` — remove `@uncaged/workflow` dependency (core no longer needs it)
**Steps:**
1. Remove all workflow-related `export` lines from `packages/core/src/index.ts`.
2. Remove `@uncaged/workflow` from core's dependencies.
3. Full build + full test:
```bash
pnpm run build && pnpm test
```
4. Run biome check:
```bash
pnpm run check
```
5. Commit:
```bash
git commit -am "refactor(core): remove workflow re-exports, clean break
Fixes #320"
```
---
## Phase 7: Verify & PR
### Task 13: Final verification
**Objective:** Full workspace build, all tests pass, biome clean.
**Steps:**
1. Clean build:
```bash
pnpm run build
```
2. Full tests:
```bash
pnpm test
```
3. Biome:
```bash
pnpm run check
```
4. Verify monorepo structure matches issue spec:
```
packages/
core/ # @uncaged/nerve-core — sense types, config (no workflow)
workflow/ # @uncaged/workflow — standalone orchestration engine
workflow-utils/ # helper roles, extract layer
workflow-meta/ # meta-workflows
daemon/ # @uncaged/nerve-daemon — sense engine + workflow integration
cli/ # @uncaged/nerve-cli
```
5. Create PR:
```bash
tea pr create --title "refactor: extract workflow engine into @uncaged/workflow" \
--description "## What
Extract workflow engine into standalone @uncaged/workflow package.
## Why
Workflow engine is now independent of sense observation — can be used standalone.
## Changes
- packages/workflow/ — new package with types, IPC, worker, manager
- packages/core/ — removed workflow types, no longer re-exports them
- packages/daemon/ — imports workflow runtime from @uncaged/workflow
- packages/cli/ — imports workflow types from @uncaged/workflow
- packages/workflow-utils/ — depends on @uncaged/workflow
- packages/workflow-meta/ — depends on @uncaged/workflow
- packages/adapter-*/ — depends on @uncaged/workflow
## Ref
Fixes #320"
```
---
## Pitfalls & Notes
1. **Worker spawn path**: `workflow-worker.ts` is spawned as a child process via `node`. After moving to `@uncaged/workflow`, the daemon needs to resolve the worker entry point from the package (e.g. `require.resolve("@uncaged/workflow/worker")`). This likely requires a separate export in `package.json` exports map.
2. **Dynamic import exception**: `workflow-worker.ts` uses dynamic `import()` for user workflow modules — this exception carries over to the new package. Add comment per CLAUDE.md conventions.
3. **IPC split**: The `ipc.ts` in daemon has both sense and workflow messages in one file with shared validation. The split needs careful handling of the `ParentToWorkerMessage` union type and `parseParentToWorkerMessage` function.
4. **No backward compat**: Per user preference, no deprecated re-exports — straight breaking change. Phase 6 removes re-exports from core entirely.
5. **`workflow-utils` may still need `nerve-core`**: If workflow-utils imports non-workflow types (like `Schema`, `ExtractFn`, `ExtractError`) from core, it will need both deps. Check carefully.
6. **Test files**: Many test files in daemon import workflow types. They need updating in Phase 5 alongside the source files.