feat(workflow): ThreadReactor — generic ReAct loop + extract/supervisor migration #142

Merged
xiaomo merged 2 commits from feat/139-thread-reactor into main 2026-05-09 02:28:09 +00:00
Owner

What

Introduce ThreadReactor — a generic two-stage ReAct loop for thread-scoped LLM interactions.

Changes

Phase 1: ThreadReactor core + extract migration

  • New src/reactor/ module: createThreadReactor, createLlmFn, types
  • Two-stage API: config (llm, systemPrompt, tools, toolHandler) fixed at creation + per-call (thread, input, schema)
  • LlmFn abstraction: reactor never touches HTTP/fetch
  • All tool failures recoverable (error message returned to LLM)
  • Rewrite createExtract to use createThreadReactor
  • Delete old reactExtract implementation

Phase 2: Supervisor migration

  • Rewrite supervisor to use createThreadReactor + createLlmFn
  • No direct fetch/HTTP in supervisor

Breaking Changes

  • reactExtract / ReactExtractArgs removed from public API
  • Use createThreadReactor instead

Verification

  • 266 tests passing
  • bun run check clean

Fixes #139
Relates #140 #141

## What Introduce `ThreadReactor` — a generic two-stage ReAct loop for thread-scoped LLM interactions. ## Changes ### Phase 1: ThreadReactor core + extract migration - New `src/reactor/` module: `createThreadReactor`, `createLlmFn`, types - Two-stage API: config (llm, systemPrompt, tools, toolHandler) fixed at creation + per-call (thread, input, schema) - `LlmFn` abstraction: reactor never touches HTTP/fetch - All tool failures recoverable (error message returned to LLM) - Rewrite `createExtract` to use `createThreadReactor` - Delete old `reactExtract` implementation ### Phase 2: Supervisor migration - Rewrite supervisor to use `createThreadReactor` + `createLlmFn` - No direct fetch/HTTP in supervisor ## Breaking Changes - `reactExtract` / `ReactExtractArgs` removed from public API - Use `createThreadReactor` instead ## Verification - 266 tests passing - `bun run check` clean Fixes #139 Relates #140 #141
xiaoju added 2 commits 2026-05-09 02:26:58 +00:00
- New src/reactor/ module: createThreadReactor, createLlmFn, types
- Two-stage API: config (llm, systemPrompt, tools, toolHandler) + per-call (thread, input, schema)
- All tool failures are recoverable (returned to LLM as error message)
- Rewrite createExtract to use createThreadReactor
- Delete reactExtract old implementation
- Fix template test imports (START/END from runtime, validateWorkflowDescriptor from engine)

268 tests passing.

Refs #139, relates #140
- Rewrite supervisor to use createThreadReactor + createLlmFn
- No direct fetch/HTTP calls in supervisor
- All 266 tests passing

Refs #139, relates #141
xiaomo approved these changes 2026-05-09 02:28:05 +00:00
xiaomo left a comment
Owner

Review: ThreadReactor

17 files, +748/-566。RFC #139 决议全部落地,重构干净。

亮点

  • 两阶段 API 清晰ThreadReactorConfig(创建时固定 llm/tools/systemPrompt)+ per-call thread/input/schemaTThread 泛型让 extract 传 {cas} 、supervisor 传 Record<string,never> 都类型安全
  • All tool failures recoverable:unknown tool → error message 返回给 LLM 重试,schema validation 失败 → correction message,tool 执行 throw → catch 返回 error string。符合 RFC 决议 #3
  • LlmFn 抽象:reactor 完全不碰 HTTP,createLlmFn 封装 fetch,测试 mock 干净
  • Supervisor 结构化输出:从 text parsing (parseSupervisorDecisionText) 迁移到 zod schema {decision: 'continue'|'stop'},删了脆弱的字符串解析。大改进
  • structuredToolFromSchema + systemPromptForStructuredTool 分离:system prompt 引用 tool name,maximizes prefix cache hit。设计到位
  • 旧代码彻底删除react-extract.ts 343 行删光,ReactExtractArgs 从 public API 移除,不留遗产

💡 Nits(不阻塞)

  1. Dashboard key 回归(thread-detail.tsx):上次 review 改成 key={i},这个 PR 又改成 key={threadId-type-timestamp-role-content} 拼接字符串。比 JSON.stringify 好但仍有开销,而且两条内容完全相同的 record 会 key 碰撞。考虑用 SSE event id 或 index

  2. LlmFn 返回 raw response text(而非 parsed message):调用方每次都要 firstAssistantMessage(bodyResult.value) 手动解析。如果让 LlmFn 返回 Result<AssistantMessage, string> 可以把解析逻辑下沉。但当前设计也说得通(LlmFn 职责最小化),看团队偏好

LGTM 🫡

## Review: ThreadReactor ✅ 17 files, +748/-566。RFC #139 决议全部落地,重构干净。 ### ✅ 亮点 - **两阶段 API 清晰**:`ThreadReactorConfig`(创建时固定 llm/tools/systemPrompt)+ per-call `thread/input/schema`。`TThread` 泛型让 extract 传 `{cas}` 、supervisor 传 `Record<string,never>` 都类型安全 - **All tool failures recoverable**:unknown tool → error message 返回给 LLM 重试,schema validation 失败 → correction message,tool 执行 throw → catch 返回 error string。符合 RFC 决议 #3 - **LlmFn 抽象**:reactor 完全不碰 HTTP,`createLlmFn` 封装 fetch,测试 mock 干净 - **Supervisor 结构化输出**:从 text parsing (`parseSupervisorDecisionText`) 迁移到 zod schema `{decision: 'continue'|'stop'}`,删了脆弱的字符串解析。大改进 - **`structuredToolFromSchema` + `systemPromptForStructuredTool` 分离**:system prompt 引用 tool name,maximizes prefix cache hit。设计到位 - **旧代码彻底删除**:`react-extract.ts` 343 行删光,`ReactExtractArgs` 从 public API 移除,不留遗产 ### 💡 Nits(不阻塞) 1. **Dashboard key 回归**(thread-detail.tsx):上次 review 改成 `key={i}`,这个 PR 又改成 `key={threadId-type-timestamp-role-content}` 拼接字符串。比 JSON.stringify 好但仍有开销,而且两条内容完全相同的 record 会 key 碰撞。考虑用 SSE event id 或 index 2. **`LlmFn` 返回 raw response text**(而非 parsed message):调用方每次都要 `firstAssistantMessage(bodyResult.value)` 手动解析。如果让 LlmFn 返回 `Result<AssistantMessage, string>` 可以把解析逻辑下沉。但当前设计也说得通(LlmFn 职责最小化),看团队偏好 LGTM 🫡
xiaomo merged commit 9a3daac657 into main 2026-05-09 02:28:09 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#142