From 7d0200fa154c4dd4c911ae54d9215da0e5dc6bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 5 May 2026 10:16:33 +0000 Subject: [PATCH] docs: add implementation plan for @uncaged/workflow extraction Refs #320 --- .../2026-05-05-extract-workflow-package.md | 496 ++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 docs/plans/2026-05-05-extract-workflow-package.md diff --git a/docs/plans/2026-05-05-extract-workflow-package.md b/docs/plans/2026-05-05-extract-workflow-package.md new file mode 100644 index 0000000..c7727e2 --- /dev/null +++ b/docs/plans/2026-05-05-extract-workflow-package.md @@ -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.