refactor: RFC-005 — Separate Agent and Role types #272
Reference in New Issue
Block a user
Delete Branch "refactor/rfc-005-phase-1"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Refactor workflow type system to cleanly separate Agent (pure execution) from Role (identity + purpose + structured output).
All 4 phases of RFC-005:
Key Changes
ThreadContext<M> = { threadId, start, steps }replacesWorkflowContextandModeratorContextAgentFn = (ctx: ThreadContext, systemPrompt: string) => Promise<string>Role<Meta> = (ctx: ThreadContext) => Promise<RoleResult<Meta>>dryRunremoved fromStartStep.meta→ adapter/engine configVerification
pnpm run build— all packagespnpm test— 543 tests passedpnpm run check— clean (2 pre-existing complexity warnings)Closes #267
🍊 PR Review: RFC-005 Phase 1 — Separate Agent and Role Types
Reviewer: 小橘 🍊(NEKO Team)
Summary
Clean, well-scoped refactor that introduces
ThreadContextas the unified context type for roles, moderators, and agent adapters. The old(start, messages)signature collapses into(ctx: ThreadContext), anddryRunmoves from the engine start frame into adapter/extract config where it belongs.34 files changed, +370 / -310 — mostly mechanical signature updates with good test coverage.
✅ What looks good
ThreadContext<M>is well-designed —{ threadId, start, steps }is minimal and sufficient. Generic parameter flows cleanly throughRoleStep<M>.WorkflowContextandModeratorContext— backward-compatible, no cliff.dryRunlifted out ofStartStep.meta— correct move. Engine metadata shouldn't carry adapter config.AgentFnarg order changed to(ctx, prompt)— context-first is idiomatic and matches Role signature.create-role.test.ts,role-decorators.test.ts,role-factories.test.tsall exercise the new signatures.resolveHermesOptionsrefactored from repetitive per-key checks to a loop overHERMES_OPTION_KEYSwithsatisfies— cleaner.tableexport anddrizzle-orm/sqlite-corefor sense schema — aligns with runtime expectations.⚠️ Issues & Questions
1.
sense-worker.ts— behavioral change hidden in refactorThis changes the IPC message shape — the kernel's
routeSenseComputeOutputmust already handle the new shape. Is there a corresponding kernel-side change? If not, this is a silent breakage. The commit message doesn't mention this change, which makes it easy to miss.2. Adapters lost
workdirandabortSignaladapter-cursor:cwdhardcoded toprocess.cwd(),abortSignal: nulladapter-hermes:abortSignal: nullPreviously these came from
WorkflowContext. The RFC-005 note says "workdirandsignalare adapter/engine config — not part of this shape" — but there's no replacement mechanism yet. These adapters now cannot be cancelled mid-execution and always use CWD. Is this intentional as Phase 1, with restoration planned for Phase 2?3.
Partial<Defaults>pattern creates optional propertiesPartial<>generates?:— technically violates the "no optional properties" convention. This is pre-existing, but worth noting since the convention is strict. Consider aWithDefaults<Required, Defaults>utility type that usesT | nullfor defaultable fields.4.
dryRuninconsistency across role typesLlmExtractorConfig:dryRun: boolean | null✅ follows conventionCursorRoleDefaults,HermesRoleDefaults,LlmRoleDefaults,ReActRoleDefaults:dryRun: boolean— no null optionIf the intent is "omit = use default", the
Partial<>makes it optional (?:). If following convention strictly, these should beboolean | nullwith explicit null meaning "use default".5.
isDryRunremoved from exports but no migration noteisDryRunwas a public export fromworkflow-utils/src/index.ts. Removing it is a breaking change for downstream consumers. Consider:@deprecatedre-export for one release, or6. Minor:
adapter-cursorlostdryRunfrom contextHardcoded to
false— cursor adapter can never dry-run now. If this is intentional, a comment would help.📝 Nits (non-blocking)
hermes-agent.tsloop creates a new spread object per iteration (resolved = { ...resolved, [k]: o[k] }). A singleObject.assignor accumulator would be cleaner, though perf is negligible here.e2e-harness.tsfunctionsbuildCounterIndexJs()andbuildCounterIndexJsWithNoopWorkflow()are good — thepathToFileURLtrick for drizzle import is clever.Verdict
The core type refactor (
ThreadContext, Role/Moderator/AgentFn signatures) is solid and well-executed. The main concerns are:sense-worker.tsIPC shape change needs verificationworkdir/abortSignalin adapters needs to be documented as intentionalisDryRunremoval is a breaking public API changeThese are addressable — happy to approve once clarified.
小橘 🍊(NEKO Team)
感谢小橘的详细 review 🍊
逐条回应:
⚠️ Issue 1: sense-worker.ts IPC 变更
✅ 已 revert。这是 cursor-agent 越界改的,不属于 RFC-005 范围。已恢复到 main 版本。
⚠️ Issue 2: Adapters 丢失 workdir/abortSignal
有意为之。RFC-005 的核心观点是
workdir和signal属于 adapter config,不属于 thread context。当前用process.cwd()和null占位,后续应从 YAML agent config 或createXxxAdapter(config)factory 注入。可以开一个 follow-up issue 补充 adapter config 层。⚠️ Issue 3:
Partial<Defaults>的?:问题认同,这是 pre-existing 的技术债。
WithDefaults<R, D>utility type 是个好方向,但超出本次 RFC 范围。⚠️ Issue 4: dryRun 类型不一致
LlmExtractorConfig用boolean | null是因为 extract 层 null 表示"不 dry-run"。Role defaults 用boolean配合Partial<>的默认值语义。一致性上确实可以改进,follow-up 处理。⚠️ Issue 5: isDryRun 移除
✅ 已修复。
isDryRun作为@deprecated函数(固定返回 false)恢复导出。不会 break 下游。⚠️ Issue 6: adapter-cursor dryRun hardcode
同 Issue 2,属于 adapter config 层的事。当前占位,后续从 config 读取。
🍊 Follow-up Review
感谢修复!sense-worker 和 isDryRun 的处理都很好 👍
发现一个小 bug:
⚠️
isDryRunexport 放错了位置packages/workflow-utils/src/index.ts里isDryRun被放在export type { ... }块里,但它是个函数。在isolatedModules/verbatimModuleSyntax下export type会被 TypeScript 擦除,运行时导入会是undefined。应该单独用 value export:
其余 LGTM ✅
— 小橘 🍊(NEKO Team)