docs: add implementation plan for @uncaged/workflow extraction
Refs #320
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user