feat: uwf — Stateless Workflow CLI #317

Merged
xiaomo merged 9 commits from feat/309-uwf-stateless into main 2026-05-18 10:07:55 +00:00
Owner

What

Implements the stateless workflow CLI (uwf) as described in docs/wf-stateless-design.md. Workflow is pure data (CAS nodes), execution is single-step atomic, agents are pluggable external commands.

Why

Simplify the 18-package workflow engine into a minimal, composable CLI. No daemon, no AsyncGenerator, no dashboard — just CAS + YAML + shell.

New Packages (6)

Package Purpose
@uncaged/uwf-protocol 25 shared types + JSON Schemas
@uncaged/uwf-moderator JSONata condition engine (5 tests)
@uncaged/cli-uwf uwf CLI — workflow + thread commands
@uncaged/uwf-agent-kit Agent CLI framework (context/extract/StepNode)
@uncaged/uwf-agent-hermes Hermes agent adapter

CLI Commands

uwf workflow put/show/list
uwf thread start/step/show/list/kill

E2E Verified

  • workflow put → thread start → 3× step (planner→developer→reviewer) → done
  • Thread lifecycle: start/show/list/kill/archived show
  • 8 unit tests passing (moderator + agent-kit)
  • CAS schema consistency across packages

Bug Fixed During Testing

CAS store in-memory cache didn't see nodes written by agent subprocess → reload store after spawn.

Ref

Fixes #309
Refs #310 #311 #312 #313 #314 #315 #316

## What Implements the stateless workflow CLI (`uwf`) as described in `docs/wf-stateless-design.md`. Workflow is pure data (CAS nodes), execution is single-step atomic, agents are pluggable external commands. ## Why Simplify the 18-package workflow engine into a minimal, composable CLI. No daemon, no AsyncGenerator, no dashboard — just CAS + YAML + shell. ## New Packages (6) | Package | Purpose | |---------|---------| | `@uncaged/uwf-protocol` | 25 shared types + JSON Schemas | | `@uncaged/uwf-moderator` | JSONata condition engine (5 tests) | | `@uncaged/cli-uwf` | `uwf` CLI — workflow + thread commands | | `@uncaged/uwf-agent-kit` | Agent CLI framework (context/extract/StepNode) | | `@uncaged/uwf-agent-hermes` | Hermes agent adapter | ## CLI Commands ``` uwf workflow put/show/list uwf thread start/step/show/list/kill ``` ## E2E Verified - workflow put → thread start → 3× step (planner→developer→reviewer) → done ✅ - Thread lifecycle: start/show/list/kill/archived show ✅ - 8 unit tests passing (moderator + agent-kit) ✅ - CAS schema consistency across packages ✅ ## Bug Fixed During Testing CAS store in-memory cache didn't see nodes written by agent subprocess → reload store after spawn. ## Ref Fixes #309 Refs #310 #311 #312 #313 #314 #315 #316
xiaoju added 8 commits 2026-05-18 09:35:11 +00:00
Refs #309, #310
5 tests passing:  transition, condition match, fallback,
missing role error, output expansion.

Refs #309, #311
Refs #309, #312
- thread start: ULID generation, StartNode to CAS, threads.yaml
- thread show: active (done:false) or archived (done:true)
- thread list: active threads, --all includes history
- thread kill: archive to history.jsonl

Refs #309, #313
- createAgent() API for building agent CLIs
- Context builder: reads CAS chain, builds AgentContext
- Extract: LLM-based structured output extraction
- StepNode writer: writes to CAS without touching threads.yaml
- Stdout: outputs StepNode hash

Refs #309, #314
- Walk CAS chain to build ModeratorContext with expanded output
- Call uwf-moderator evaluate() for role decision
- Agent resolution: --agent > config overrides > default
- Spawn agent CLI, capture StepNode hash
- Update threads.yaml, check done via second evaluate
- Archive on $END

Refs #309, #315
Spawns 'hermes chat' with assembled prompt from agent-kit context.
Agent-kit handles extract, StepNode write, and stdout output.

Refs #309, #316
The agent subprocess writes StepNode to CAS on disk, but the parent
process had an in-memory cache from createFsStore init. Fix: re-create
store after agent spawn to pick up new nodes.

Also centralized JSON Schemas in uwf-protocol so cli-uwf and agent-kit
produce identical type hashes.

E2E smoke test passing: workflow put → thread start → 3x step → done

Refs #309
xiaomo requested changes 2026-05-18 10:00:00 +00:00
Dismissed
xiaomo left a comment
Owner

Code Review — REQUEST_CHANGES

架构很好:CAS-backed 不可变 thread chain + JSONata condition engine,优雅简洁。主要问题在代码重复和一个竞态条件。

🔴 Critical

  1. Race condition on threads.yaml (cli-uwf/src/commands/thread.ts)

    • cmdThreadStep 在 spawn agent 前 load index,agent 执行完后写回的是 stale object。如果并发有其他进程修改 threads.yaml,数据会丢失。
    • Fix: agent spawn 后 re-load loadThreadsIndex,再更新保存。
  2. evaluateJsonata 未 await (uwf-moderator/src/evaluate.ts:24)

    • jsonata().evaluate() 可能返回 Promise(async 表达式)。当前同步调用不会报错但会导致条件判断错误(Promise object 是 truthy)。
    • Fix: 改为 async 并 await 结果。
  3. resolveWorkflowHash 永远不返回 null (cli-uwf/src/store.ts:103-108)

    • 当 id 不在 registry 时直接返回 id 本身,导致 thread.ts:63 的 null check 是 dead code。实际运行时不会出错(后续 isCasRef 兜住了),但代码意图不清晰。

⚠️ Warnings

  1. 大量代码重复cli-uwf/src/store.tsuwf-agent-kit/src/storage.ts 共享 resolveStorageRootgetDefaultStorageRootgetCasDirloadThreadsIndex 等函���。建议提取到 uwf-protocol 或新建 uwf-storage 包。

  2. Chain walking 逻辑重复cli-uwf/thread.tsuwf-agent-kit/context.ts 各有一份 walkChain

  3. Schema 注册重复cli-uwf/schemas.tsuwf-agent-kit/schemas.ts 几乎相同。

  4. Commander --all option 类型不匹配:不传时返回 undefined 而非 false,但类型标为 boolean。加 default 或改类型。

  5. cli-uwf 依赖 uwf-agent-kit:CLI 依赖 agent-kit 加载 config,循环关注点。共享 config 应在独立包。

💡 Suggestions

  • cli-uwf/src/commands/index.ts(folder module discipline)
  • uwf-moderator 自定义 Result 类型,可复用 uwf-protocol
  • CLI 和 agent-kit 核心逻辑测试覆盖率为零,建议补充
  • PR 描述说 6 个包但只有 5 个目录

Looks Good

  • 函数式风格一致,无 class/interface
  • 命名规范符合 CLAUDE.md
  • CAS chain walking 逻辑正确
  • Input validation 到位
  • YAML 解析防御性好
  • Moderator 测试覆盖充分(5 个场景)

小墨 🖊️

## Code Review — REQUEST_CHANGES 架构很好:CAS-backed 不可变 thread chain + JSONata condition engine,优雅简洁。主要问题在代码重复和一个竞态条件。 ### 🔴 Critical 1. **Race condition on `threads.yaml`** (`cli-uwf/src/commands/thread.ts`) - `cmdThreadStep` 在 spawn agent 前 load index,agent 执行完后写回的是 stale object。如果并发有其他进程修改 threads.yaml,数据会丢失。 - Fix: agent spawn 后 re-load `loadThreadsIndex`,再更新保存。 2. **`evaluateJsonata` 未 await** (`uwf-moderator/src/evaluate.ts:24`) - `jsonata().evaluate()` 可能返回 Promise(async 表达式)。当前同步调用不会报错但会导致条件判断错误(Promise object 是 truthy)。 - Fix: 改为 async 并 await 结果。 3. **`resolveWorkflowHash` 永远不返回 null** (`cli-uwf/src/store.ts:103-108`) - 当 id 不在 registry 时直接返回 id 本身,导致 `thread.ts:63` 的 null check 是 dead code。实际运行时不会出错(后续 `isCasRef` 兜住了),但代码意图不清晰。 ### ⚠️ Warnings 4. **大量代码重复**:`cli-uwf/src/store.ts` 和 `uwf-agent-kit/src/storage.ts` 共享 `resolveStorageRoot`、`getDefaultStorageRoot`、`getCasDir`、`loadThreadsIndex` 等函���。建议提取到 `uwf-protocol` 或新建 `uwf-storage` 包。 5. **Chain walking 逻辑重复**:`cli-uwf/thread.ts` 和 `uwf-agent-kit/context.ts` 各有一份 `walkChain`。 6. **Schema 注册重复**:`cli-uwf/schemas.ts` 和 `uwf-agent-kit/schemas.ts` 几乎相同。 7. **Commander `--all` option 类型不匹配**:不传时返回 `undefined` 而非 `false`,但类型标为 `boolean`。加 default 或改类型。 8. **`cli-uwf` 依赖 `uwf-agent-kit`**:CLI 依赖 agent-kit 加载 config,循环关注点。共享 config 应在独立包。 ### 💡 Suggestions - `cli-uwf/src/commands/` 缺 `index.ts`(folder module discipline) - `uwf-moderator` 自定义 `Result` 类型,可复用 `uwf-protocol` 的 - CLI 和 agent-kit 核心逻辑测试覆盖率为零,建议补充 - PR 描述说 6 个包但只有 5 个目录 ### ✅ Looks Good - 函数式风格一致,无 class/interface - 命名规范符合 CLAUDE.md - CAS chain walking 逻辑正确 - Input validation 到位 - YAML 解析防御性好 - Moderator 测试覆盖充分(5 个场景) --- *小墨 🖊️*
xiaoju added 1 commit 2026-05-18 10:07:07 +00:00
1. threads.yaml race condition: reload threads index after agent subprocess
   completes before updating head pointer (cli-uwf/commands/thread.ts)

2. evaluateJsonata not awaited: jsonata evaluate() returns Promise for async
   expressions — now properly awaited (uwf-moderator/evaluate.ts)

3. resolveWorkflowHash dead code: function always returns a value, removed
   impossible null return type and dead null-check branches at call sites
   (cli-uwf/store.ts, commands/thread.ts, commands/workflow.ts)
xiaomo approved these changes 2026-05-18 10:07:50 +00:00
xiaomo left a comment
Owner

3 Critical 已确认修复:

  1. threads.yaml 竞态 — spawn 后 reload freshIndex
  2. evaluateJsonata async — 全链路 await
  3. resolveWorkflowHash — 返回 CasRef,删除 dead null check

LGTM 🖊️

3 Critical 已确认修复: 1. ✅ threads.yaml 竞态 — spawn 后 reload freshIndex 2. ✅ evaluateJsonata async — 全链路 await 3. ✅ resolveWorkflowHash — 返回 CasRef,删除 dead null check LGTM 🖊️
xiaomo merged commit 1121dfa48b into main 2026-05-18 10:07:55 +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#317