Refactor: Bundle contract → AsyncGenerator, engine drives the loop #6

Closed
opened 2026-05-06 05:19:29 +00:00 by xiaoju · 0 comments
Owner

背景

RFC-001 Section 2 已更新:bundle 的 default export 从 WorkflowDefinition 对象改为 AsyncGenerator 函数。这是一个 DIP(依赖反转)改进 — bundle 不再依赖框架类型,engine 通过 generator 协议控制执行。

详见 docs/rfc-001-workflow-engine.md Section 2 和 Section 8。

需要改什么

1. 新增核心类型 (packages/workflow/src/types.ts)

type RoleOutput = {
  role: string;
  content: string;
  meta: Record<string, unknown>;
};

type WorkflowResult = {
  returnCode: number;
  summary: string;
};

type WorkflowFn = (
  prompt: string,
  options: { isDryRun: boolean; maxRounds: number }
) => AsyncGenerator<RoleOutput, WorkflowResult>;

保留现有的 Role/Moderator/ThreadContext 等类型,它们作为 helper 的内部类型继续存在。

2. 新增 createRoleModerator helper (packages/workflow/src/create-role-moderator.ts)

function createRoleModerator<M extends RoleMeta>(
  def: { roles: { [K in keyof M & string]: Role<M[K]> }; moderator: Moderator<M> }
): WorkflowFn

返回一个 AsyncGenerator 函数。内部实现:

  • 创建 StartStep + ThreadContext
  • 循环:moderator → role → yield { role, content, meta }
  • moderator 返回 END 或达到 maxRounds 时 return { returnCode, summary }

3. 重写 engine (packages/workflow/src/engine.ts)

executeThread 不再自己调 moderator/role,而是:

  1. 动态 import bundle,得到 default export(一个 AsyncGenerator 函数)
  2. 调用 gen = bundleFn(prompt, options)
  3. 循环 for await (const step of gen) — 每个 step 写入 .data.jsonl
  4. 每次 yield 后检查 AbortSignal
  5. generator return 值作为最终结果

4. 重写 worker (packages/workflow/src/worker.ts)

Worker 加载 bundle 后得到的是 WorkflowFn(AsyncGenerator),不再是 WorkflowDefinition。
调整 import() 后的类型检查:检查 default export 是 function(不再检查 name/roles/moderator)。

5. 更新测试 bundle

所有测试里的 mock bundle 从 export default { name, roles, moderator } 改为 export default async function*

包括:

  • packages/workflow/__tests__/engine.test.ts
  • packages/workflow/__tests__/worker.test.ts
  • packages/cli-workflow/__tests__/commands.test.ts
  • packages/cli-workflow/__tests__/thread-cli.test.ts

6. 更新 bundle-validator

validateWorkflowBundle 不再检查 default export 是否有 .name/.roles/.moderator,只检查 default export 是 function。

不要改的

  • Base32、XXH64、ULID、Logger、Registry — 不变
  • CLI 命令接口 — 不变
  • .data.jsonl / .info.jsonl 格式 — 不变
  • 存储布局 — 不变

验证标准

  • bun test 全部通过
  • bunx biome check . 无错误
  • 测试 bundle 使用 async function* 语法
  • engine 通过 for await 消费 generator
  • bundle 不 import 任何 @uncaged/workflow 类型(零依赖)
  • createRoleModerator 作为可选 helper 导出
  • 所有现有 CLI 功能正常工作

Ref: #1

## 背景 RFC-001 Section 2 已更新:bundle 的 default export 从 `WorkflowDefinition` 对象改为 **AsyncGenerator 函数**。这是一个 DIP(依赖反转)改进 — bundle 不再依赖框架类型,engine 通过 generator 协议控制执行。 详见 `docs/rfc-001-workflow-engine.md` Section 2 和 Section 8。 ## 需要改什么 ### 1. 新增核心类型 (`packages/workflow/src/types.ts`) ```typescript type RoleOutput = { role: string; content: string; meta: Record<string, unknown>; }; type WorkflowResult = { returnCode: number; summary: string; }; type WorkflowFn = ( prompt: string, options: { isDryRun: boolean; maxRounds: number } ) => AsyncGenerator<RoleOutput, WorkflowResult>; ``` 保留现有的 Role/Moderator/ThreadContext 等类型,它们作为 helper 的内部类型继续存在。 ### 2. 新增 `createRoleModerator` helper (`packages/workflow/src/create-role-moderator.ts`) ```typescript function createRoleModerator<M extends RoleMeta>( def: { roles: { [K in keyof M & string]: Role<M[K]> }; moderator: Moderator<M> } ): WorkflowFn ``` 返回一个 AsyncGenerator 函数。内部实现: - 创建 StartStep + ThreadContext - 循环:moderator → role → yield { role, content, meta } - moderator 返回 END 或达到 maxRounds 时 return { returnCode, summary } ### 3. 重写 engine (`packages/workflow/src/engine.ts`) `executeThread` 不再自己调 moderator/role,而是: 1. 动态 import bundle,得到 default export(一个 AsyncGenerator 函数) 2. 调用 `gen = bundleFn(prompt, options)` 3. 循环 `for await (const step of gen)` — 每个 step 写入 `.data.jsonl` 4. 每次 yield 后检查 AbortSignal 5. generator return 值作为最终结果 ### 4. 重写 worker (`packages/workflow/src/worker.ts`) Worker 加载 bundle 后得到的是 WorkflowFn(AsyncGenerator),不再是 WorkflowDefinition。 调整 `import()` 后的类型检查:检查 default export 是 function(不再检查 name/roles/moderator)。 ### 5. 更新测试 bundle 所有测试里的 mock bundle 从 `export default { name, roles, moderator }` 改为 `export default async function*`。 包括: - `packages/workflow/__tests__/engine.test.ts` - `packages/workflow/__tests__/worker.test.ts` - `packages/cli-workflow/__tests__/commands.test.ts` - `packages/cli-workflow/__tests__/thread-cli.test.ts` ### 6. 更新 bundle-validator `validateWorkflowBundle` 不再检查 default export 是否有 `.name`/`.roles`/`.moderator`,只检查 default export 是 function。 ## 不要改的 - Base32、XXH64、ULID、Logger、Registry — 不变 - CLI 命令接口 — 不变 - `.data.jsonl` / `.info.jsonl` 格式 — 不变 - 存储布局 — 不变 ## 验证标准 - [ ] `bun test` 全部通过 - [ ] `bunx biome check .` 无错误 - [ ] 测试 bundle 使用 `async function*` 语法 - [ ] engine 通过 `for await` 消费 generator - [ ] bundle 不 import 任何 `@uncaged/workflow` 类型(零依赖) - [ ] `createRoleModerator` 作为可选 helper 导出 - [ ] 所有现有 CLI 功能正常工作 Ref: #1
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#6