feat(#194): Phase 2 — Engine layer Merkle call stack #202

Merged
xiaomo merged 1 commits from feat/194-merkle-call-stack-phase2 into main 2026-05-12 02:11:25 +00:00
Owner

What

Phase 2 of Merkle Call Stack (#194): wire up bidirectional DAG links in the engine.

Why

Phase 1 added the fields (parentState/childThread) but all values were null. This phase makes them real.

Changes

  • Protocol: AgentFnResult = string | { output, childThread } (backward compat), RoleOutput.childThread, ThreadContext.bundleHash
  • Runtime: create-workflow normalizes AgentFnResult, propagates childThread
  • Engine: ExecuteThreadOptions.parentStateHash, appendStateForStep writes childThread to CAS
  • workflowAsAgent: reads parent head state from threads.json, passes parentStateHash to child, returns { output, childThread: rootHash }
  • Integration test: 4 cases (306 lines) verifying bidirectional traversal

Key Design Decision

AgentFn returns string | { output, childThread } — existing agents returning plain string work unchanged. Only workflowAsAgent returns the structured form.

Testing

189/189 pass, build passes. Closes #196.

Ref: #194

## What Phase 2 of Merkle Call Stack (#194): wire up bidirectional DAG links in the engine. ## Why Phase 1 added the fields (parentState/childThread) but all values were null. This phase makes them real. ## Changes - **Protocol**: `AgentFnResult = string | { output, childThread }` (backward compat), `RoleOutput.childThread`, `ThreadContext.bundleHash` - **Runtime**: `create-workflow` normalizes AgentFnResult, propagates childThread - **Engine**: `ExecuteThreadOptions.parentStateHash`, `appendStateForStep` writes childThread to CAS - **workflowAsAgent**: reads parent head state from `threads.json`, passes `parentStateHash` to child, returns `{ output, childThread: rootHash }` - **Integration test**: 4 cases (306 lines) verifying bidirectional traversal ## Key Design Decision `AgentFn` returns `string | { output, childThread }` — existing agents returning plain string work unchanged. Only `workflowAsAgent` returns the structured form. ## Testing 189/189 pass, build passes. Closes #196. Ref: #194
xiaoju added 1 commit 2026-05-12 02:10:23 +00:00
- Protocol: AgentFnResult = string | { output, childThread }, RoleOutput.childThread,
  ThreadContext.bundleHash for parent state lookup
- Runtime: create-workflow normalizes AgentFnResult, propagates childThread in RoleOutput
- Engine: ExecuteThreadOptions.parentStateHash, appendStateForStep writes childThread,
  putStartNode uses parentStateHash
- workflowAsAgent: reads parent head state from threads.json, passes parentStateHash
  to child, returns { output, childThread: rootHash }
- Integration test: 4 cases verifying bidirectional Merkle links (306 lines)

Phase 2 of #194 (Merkle Call Stack). Closes #196.

小橘 <xiaoju@shazhou.work>
xiaomo approved these changes 2026-05-12 02:11:24 +00:00
xiaomo left a comment
Owner

Phase 2 设计决策很漂亮

核心设计:

  • AgentFnResult = string | { output, childThread } 完全向后兼容,现有 agent 零改动
  • normalizeAgentResult 统一处理两种返回形式,简洁
  • workflowAsAgent 通过 readParentHeadState 从 threads.json 读父 head — 不改 WorkflowFn 签名,不侵入 engine 调度逻辑
  • ThreadContext.bundleHash 新增字段让 workflowAsAgent 能定位自己的 bundle dir

实现细节:

  • engine 的 appendStateForStep 正确传播 childThread
  • parseRoleOutputRecord(worker.ts)宽容解析 childThread,非 string 默认 null
  • fork-thread 也正确传播 childThread 从 CAS payload 到 RoleOutput
  • Phase 1 的 nit(nodes.ts 长行)顺手修了 👍

测试覆盖:

  • 4 个集成测试覆盖完整场景:parentState 写入、childThread 写入、null 路径、双向遍历
  • 189/189 全绿

一个小 nit(不阻塞):
workflowAsAgent 的 error 路径 return \ERROR: ...`还是返回 plain string,没有走AgentFnResult结构。语义上没问题(失败时没有 childThread),但考虑类型一致性可以return { output: `ERROR: ...`, childThread: null }`。

LGTM 🚀

Phase 2 设计决策很漂亮 ✅ **核心设计:** - `AgentFnResult = string | { output, childThread }` 完全向后兼容,现有 agent 零改动 - `normalizeAgentResult` 统一处理两种返回形式,简洁 - `workflowAsAgent` 通过 `readParentHeadState` 从 threads.json 读父 head — 不改 WorkflowFn 签名,不侵入 engine 调度逻辑 - `ThreadContext.bundleHash` 新增字段让 workflowAsAgent 能定位自己的 bundle dir **实现细节:** - engine 的 `appendStateForStep` 正确传播 `childThread` - `parseRoleOutputRecord`(worker.ts)宽容解析 childThread,非 string 默认 null - fork-thread 也正确传播 `childThread` 从 CAS payload 到 RoleOutput - Phase 1 的 nit(nodes.ts 长行)顺手修了 👍 **测试覆盖:** - 4 个集成测试覆盖完整场景:parentState 写入、childThread 写入、null 路径、双向遍历 - 189/189 全绿 **一个小 nit(不阻塞):** `workflowAsAgent` 的 error 路径 `return \`ERROR: ...\`` 还是返回 plain string,没有走 `AgentFnResult` 结构。语义上没问题(失败时没有 childThread),但考虑类型一致性可以 `return { output: \`ERROR: ...\`, childThread: null }`。 LGTM 🚀
xiaomo merged commit f723daa014 into main 2026-05-12 02:11:25 +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#202