docs: update all documentation for status-based routing (#497)

Replace all JSONata/ConditionDefinition/ConditionalEdge references with
status-based routing terminology across 8 files:

- README.md, CLAUDE.md: moderator description, key terms
- docs/architecture.md: dependency jsonata→mustache, evaluate signature
- docs/wf-stateless-design.md: type definitions, routing context
- packages/workflow-moderator/README.md: full rewrite for new API
- packages/workflow-protocol/README.md: Target type, remove Transition
- packages/workflow-dashboard/context.md: StatusEdge, graph type
- docs/builtin-agent-research.md: stale JSONata references
This commit is contained in:
2026-05-25 05:52:27 +00:00
parent e40e41555b
commit 298b944169
8 changed files with 56 additions and 85 deletions
+3 -3
View File
@@ -8,10 +8,10 @@ This monorepo implements a stateless workflow engine driven by a single-step CLI
| Concept | What it is | | Concept | What it is |
|---------|-----------| |---------|-----------|
| **Workflow** | A YAML definition (`WorkflowPayload`) with roles, conditions, and a routing graph. Stored as a CAS node, identified by its XXH64 hash. | | **Workflow** | A YAML definition (`WorkflowPayload`) with roles, status-based routing, and a directed graph. Stored as a CAS node, identified by its XXH64 hash. |
| **Thread** | A single execution of a workflow, identified by a ULID. State is an immutable CAS chain; active threads indexed in `threads.yaml`; completed threads in `history.jsonl`. | | **Thread** | A single execution of a workflow, identified by a ULID. State is an immutable CAS chain; active threads indexed in `threads.yaml`; completed threads in `history.jsonl`. |
| **Role** | A named actor within a workflow. Each role has a system prompt and a JSON Schema `outputSchema`. | | **Role** | A named actor within a workflow. Each role has a system prompt and a JSON Schema `outputSchema`. |
| **Moderator** | JSONata-based graph evaluator — determines the next role (or `$END`) with zero LLM cost. | | **Moderator** | Status-based graph evaluator — determines the next role (or `$END`) with zero LLM cost. |
| **Agent** | An external CLI command (`uwf-hermes`, etc.) spawned by `uwf thread step`. Produces frontmatter markdown output. | | **Agent** | An external CLI command (`uwf-hermes`, etc.) spawned by `uwf thread step`. Produces frontmatter markdown output. |
| **CAS** | Content-Addressed Storage via `@uncaged/json-cas` — all workflow definitions, thread nodes, and outputs are immutable CAS nodes. | | **CAS** | Content-Addressed Storage via `@uncaged/json-cas` — all workflow definitions, thread nodes, and outputs are immutable CAS nodes. |
| **Registry** | `~/.uncaged/workflow/registry.yaml` — maps workflow names to current CAS hashes. | | **Registry** | `~/.uncaged/workflow/registry.yaml` — maps workflow names to current CAS hashes. |
@@ -23,7 +23,7 @@ workflow/
packages/ packages/
workflow-protocol/ # @uncaged/workflow-protocol — shared types (WorkflowPayload, StepNodePayload, WorkflowConfig, etc.) workflow-protocol/ # @uncaged/workflow-protocol — shared types (WorkflowPayload, StepNodePayload, WorkflowConfig, etc.)
workflow-util/ # @uncaged/workflow-util — Crockford Base32, ULID, logger, frontmatter parsing/validation workflow-util/ # @uncaged/workflow-util — Crockford Base32, ULID, logger, frontmatter parsing/validation
workflow-moderator/ # @uncaged/workflow-moderator — JSONata graph evaluator workflow-moderator/ # @uncaged/workflow-moderator — Status-based graph evaluator
workflow-agent-kit/ # @uncaged/workflow-agent-kit — createAgent factory, context builder, extract pipeline workflow-agent-kit/ # @uncaged/workflow-agent-kit — createAgent factory, context builder, extract pipeline
workflow-agent-hermes/ # @uncaged/workflow-agent-hermes — uwf-hermes CLI binary (spawns hermes chat) workflow-agent-hermes/ # @uncaged/workflow-agent-hermes — uwf-hermes CLI binary (spawns hermes chat)
cli-workflow/ # @uncaged/cli-workflow — uwf CLI binary cli-workflow/ # @uncaged/cli-workflow — uwf CLI binary
+4 -4
View File
@@ -1,10 +1,10 @@
# @uncaged/workflow # @uncaged/workflow
A stateless workflow engine driven by a single-step CLI. Workflows are YAML definitions with roles, JSONata routing conditions, and a directed graph. Threads are immutable CAS-linked chains — each `uwf thread step` runs one moderator→agent→extract cycle and exits. A stateless workflow engine driven by a single-step CLI. Workflows are YAML definitions with roles, status-based routing, and a directed graph. Threads are immutable CAS-linked chains — each `uwf thread step` runs one moderator→agent→extract cycle and exits.
## Overview ## Overview
This monorepo implements **uwf**, a workflow engine with no long-running daemon. You register YAML workflow definitions in a content-addressed store (CAS), start a thread with an initial prompt, then invoke `uwf thread step` repeatedly until the moderator routes to `$END`. Each step is a complete process: the moderator evaluates JSONata conditions to pick the next role, an external agent CLI produces frontmatter markdown output, and an extract pipeline validates or structures that output against the role's JSON Schema. This monorepo implements **uwf**, a workflow engine with no long-running daemon. You register YAML workflow definitions in a content-addressed store (CAS), start a thread with an initial prompt, then invoke `uwf thread step` repeatedly until the moderator routes to `$END`. Each step is a complete process: the moderator evaluates status-based routing to pick the next role, an external agent CLI produces frontmatter markdown output, and an extract pipeline validates or structures that output against the role's JSON Schema.
Workflow state lives entirely on disk under `~/.uncaged/workflow/`: CAS nodes for definitions and step payloads, `registry.yaml` for workflow name→hash mappings, and `threads.yaml` for active thread head pointers. Completed threads are archived to `history.jsonl`. Because there is no server process, workflows are easy to debug, fork, and inspect with ordinary CLI tools. Workflow state lives entirely on disk under `~/.uncaged/workflow/`: CAS nodes for definitions and step payloads, `registry.yaml` for workflow name→hash mappings, and `threads.yaml` for active thread head pointers. Completed threads are archived to `history.jsonl`. Because there is no server process, workflows are easy to debug, fork, and inspect with ordinary CLI tools.
@@ -20,7 +20,7 @@ Layer 0 — Contract
Layer 1 — Shared infra Layer 1 — Shared infra
workflow-util Encoding, IDs, logging, frontmatter, paths workflow-util Encoding, IDs, logging, frontmatter, paths
workflow-moderator JSONata graph evaluator workflow-moderator Status-based graph evaluator
Layer 2 — Agent framework Layer 2 — Agent framework
workflow-agent-kit createAgent factory, context builder, extract pipeline workflow-agent-kit createAgent factory, context builder, extract pipeline
@@ -47,7 +47,7 @@ See [docs/architecture.md](docs/architecture.md) for the full design — three-p
|---------|-----|-------------|------|--------| |---------|-----|-------------|------|--------|
| `cli-workflow` | `@uncaged/cli-workflow` | `uwf` CLI — thread lifecycle, workflow registry, CAS inspection, setup | cli | [README](packages/cli-workflow/README.md) | | `cli-workflow` | `@uncaged/cli-workflow` | `uwf` CLI — thread lifecycle, workflow registry, CAS inspection, setup | cli | [README](packages/cli-workflow/README.md) |
| `workflow-protocol` | `@uncaged/workflow-protocol` | Shared TypeScript types and JSON Schema constants | lib | [README](packages/workflow-protocol/README.md) | | `workflow-protocol` | `@uncaged/workflow-protocol` | Shared TypeScript types and JSON Schema constants | lib | [README](packages/workflow-protocol/README.md) |
| `workflow-moderator` | `@uncaged/workflow-moderator` | JSONata graph evaluator — next role or `$END` | lib | [README](packages/workflow-moderator/README.md) | | `workflow-moderator` | `@uncaged/workflow-moderator` | Status-based graph evaluator — next role or `$END` | lib | [README](packages/workflow-moderator/README.md) |
| `workflow-agent-kit` | `@uncaged/workflow-agent-kit` | `createAgent` factory, context builder, extract pipeline | lib | [README](packages/workflow-agent-kit/README.md) | | `workflow-agent-kit` | `@uncaged/workflow-agent-kit` | `createAgent` factory, context builder, extract pipeline | lib | [README](packages/workflow-agent-kit/README.md) |
| `workflow-util` | `@uncaged/workflow-util` | Crockford Base32, ULID, logger, frontmatter parsing, storage paths | lib | [README](packages/workflow-util/README.md) | | `workflow-util` | `@uncaged/workflow-util` | Crockford Base32, ULID, logger, frontmatter parsing, storage paths | lib | [README](packages/workflow-util/README.md) |
| `workflow-agent-hermes` | `@uncaged/workflow-agent-hermes` | `uwf-hermes` — spawns Hermes chat via ACP | agent | [README](packages/workflow-agent-hermes/README.md) | | `workflow-agent-hermes` | `@uncaged/workflow-agent-hermes` | `uwf-hermes` — spawns Hermes chat via ACP | agent | [README](packages/workflow-agent-hermes/README.md) |
+7 -8
View File
@@ -16,7 +16,7 @@ The implementation lives in **6** active packages under `packages/`, plus two ex
|-------|---------|---------------| |-------|---------|---------------|
| Contract | `@uncaged/workflow-protocol``workflow-protocol` | Shared TypeScript types (`WorkflowPayload`, `StepNodePayload`, `ModeratorContext`, `WorkflowConfig`, etc.). No runtime deps beyond `@uncaged/json-cas-fs`. | | Contract | `@uncaged/workflow-protocol``workflow-protocol` | Shared TypeScript types (`WorkflowPayload`, `StepNodePayload`, `ModeratorContext`, `WorkflowConfig`, etc.). No runtime deps beyond `@uncaged/json-cas-fs`. |
| Shared infra | `@uncaged/workflow-util``workflow-util` | Crockford Base32, ULID generation, `createLogger`, frontmatter parsing/validation. | | Shared infra | `@uncaged/workflow-util``workflow-util` | Crockford Base32, ULID generation, `createLogger`, frontmatter parsing/validation. |
| Moderator | `@uncaged/workflow-moderator``workflow-moderator` | JSONata-based graph evaluator: given a `WorkflowPayload` and `ModeratorContext`, returns the next role or `$END`. | | Moderator | `@uncaged/workflow-moderator``workflow-moderator` | Status-based graph evaluator: given a routing graph, last role, and last output, returns the next role or `$END`. |
| Agent framework | `@uncaged/workflow-agent-kit``workflow-agent-kit` | `createAgent` entrypoint factory, context builder, frontmatter fast-path extractor, LLM extract fallback, output format instruction builder. | | Agent framework | `@uncaged/workflow-agent-kit``workflow-agent-kit` | `createAgent` entrypoint factory, context builder, frontmatter fast-path extractor, LLM extract fallback, output format instruction builder. |
| Agent: Hermes | `@uncaged/workflow-agent-hermes``workflow-agent-hermes` | `uwf-hermes` CLI binary — spawns `hermes chat`, pipes prompt, captures session detail. | | Agent: Hermes | `@uncaged/workflow-agent-hermes``workflow-agent-hermes` | `uwf-hermes` CLI binary — spawns `hermes chat`, pipes prompt, captures session detail. |
| CLI | `@uncaged/cli-workflow``cli-workflow` | `uwf` binary — thread lifecycle, workflow registry, CAS inspection, setup. | | CLI | `@uncaged/cli-workflow``cli-workflow` | `uwf` binary — thread lifecycle, workflow registry, CAS inspection, setup. |
@@ -27,7 +27,7 @@ The implementation lives in **6** active packages under `packages/`, plus two ex
|---------|------| |---------|------|
| `@uncaged/json-cas` | Content-addressed store API, XXH64 hashing, JSON Schema registration and validation. | | `@uncaged/json-cas` | Content-addressed store API, XXH64 hashing, JSON Schema registration and validation. |
| `@uncaged/json-cas-fs` | Filesystem backend for `json-cas`. | | `@uncaged/json-cas-fs` | Filesystem backend for `json-cas`. |
| `jsonata` | JSONata expression evaluator (used by `workflow-moderator`). | | `mustache` | Template renderer for edge prompts (used by `workflow-moderator`). |
| `commander` | CLI argument parsing (used by `cli-workflow`). | | `commander` | CLI argument parsing (used by `cli-workflow`). |
| `dotenv` | Loads `.env` files for API keys. | | `dotenv` | Loads `.env` files for API keys. |
| `yaml` | YAML parse/stringify. | | `yaml` | YAML parse/stringify. |
@@ -148,8 +148,7 @@ graph:
Key properties: Key properties:
- **`roles`** — inline role definitions; each `meta` is a JSON Schema (stored as its own CAS node on registration) - **`roles`** — inline role definitions; each `meta` is a JSON Schema (stored as its own CAS node on registration)
- **`conditions`** — named JSONata expressions evaluated against the `ModeratorContext` - **`graph`** — `Record<Role | "$START", Record<Status, Target>>` — status-based routing; each role maps statuses to targets
- **`graph`** — `Record<Role | "$START", Transition[]>` — first matching transition wins; `condition: null` = fallback
- **No agent binding** — agent selection is a deployment concern, configured in `config.yaml` - **No agent binding** — agent selection is a deployment concern, configured in `config.yaml`
- **No Zod** — all schemas are JSON Schema, validated through `@uncaged/json-cas` - **No Zod** — all schemas are JSON Schema, validated through `@uncaged/json-cas`
@@ -159,8 +158,8 @@ Each `uwf thread step` runs exactly one cycle: moderator → agent → extract.
``` ```
┌─→ Phase 1: MODERATOR ┌─→ Phase 1: MODERATOR
│ Input: WorkflowPayload + ModeratorContext { start, steps[] } │ Input: graph + lastRole + lastOutput
│ Engine: JSONata conditions evaluated against the graph │ Engine: Status-based map lookup against lastOutput.status
│ Output: next role name | $END │ Output: next role name | $END
│ Phase 2: AGENT │ Phase 2: AGENT
@@ -207,7 +206,7 @@ type AgentContext = ModeratorContext & {
### Key properties ### Key properties
- **Moderator** — pure JSONata evaluation; no LLM call, no I/O beyond CAS reads. Evaluates `workflow.graph[currentRole]` transitions in order, returns first match. - **Moderator** — pure status-based map lookup; no LLM call, no I/O beyond CAS reads. Looks up `graph[lastRole][lastOutput.status]` to get the next target.
- **Agent** — receives `AgentContext` with thread history + role system prompt + output format instruction. Raw output is frontmatter markdown. - **Agent** — receives `AgentContext` with thread history + role system prompt + output format instruction. Raw output is frontmatter markdown.
- **Extractor** — two-layer: tries frontmatter fast-path first (zero LLM cost), falls back to LLM extract if frontmatter is absent or invalid. - **Extractor** — two-layer: tries frontmatter fast-path first (zero LLM cost), falls back to LLM extract if frontmatter is absent or invalid.
- **Stateless** — each `uwf thread step` is an atomic, self-contained operation. No in-memory state between steps. - **Stateless** — each `uwf thread step` is an atomic, self-contained operation. No in-memory state between steps.
@@ -485,7 +484,7 @@ Binary: `uwf`
| **YAML workflow definitions** | Human-readable, versionable, no build step required. JSON Schema inline in YAML, registered as CAS nodes on `workflow put`. | | **YAML workflow definitions** | Human-readable, versionable, no build step required. JSON Schema inline in YAML, registered as CAS nodes on `workflow put`. |
| **Stateless single-step CLI** | Each `uwf thread step` is atomic — no in-memory state, no daemon, no long-running process. OS handles lifecycle. | | **Stateless single-step CLI** | Each `uwf thread step` is atomic — no in-memory state, no daemon, no long-running process. OS handles lifecycle. |
| **CAS-backed thread state** | Immutable linked nodes enable fork, replay, and GC without copying data. Content-addressed deduplication across threads. | | **CAS-backed thread state** | Immutable linked nodes enable fork, replay, and GC without copying data. Content-addressed deduplication across threads. |
| **JSONata moderator** | Declarative condition expressions evaluated against thread history. No LLM cost for routing decisions. | | **Status-based moderator** | Status-based map routing — `graph[role][status]` lookup against last output. No LLM cost for routing decisions. |
| **Frontmatter markdown output** | Agents produce structured meta (YAML frontmatter) alongside free-form content (markdown body). Enables zero-cost extraction when frontmatter is well-formed. | | **Frontmatter markdown output** | Agents produce structured meta (YAML frontmatter) alongside free-form content (markdown body). Enables zero-cost extraction when frontmatter is well-formed. |
| **Two-layer extract** | Fast path avoids LLM calls when agents follow the format; LLM fallback handles messy output gracefully. | | **Two-layer extract** | Fast path avoids LLM calls when agents follow the format; LLM fallback handles messy output gracefully. |
| **Prompt injection for format** | Output format instruction prepended to system prompt ensures agents produce parseable output without per-agent configuration. | | **Prompt injection for format** | Output format instruction prepended to system prompt ensures agents produce parseable output without per-agent configuration. |
+2 -2
View File
@@ -288,7 +288,7 @@ export type BuildContextMeta = {
1. 从 `threads.yaml[threadId]` 取 `headHash` 1. 从 `threads.yaml[threadId]` 取 `headHash`
2. `walkChain`:若 head 是 `StartNode`,`stepsNewestFirst=[]`;否则沿 `prev` 收集所有 `StepNode`, newest-first 2. `walkChain`:若 head 是 `StartNode`,`stepsNewestFirst=[]`;否则沿 `prev` 收集所有 `StepNode`, newest-first
3. `buildHistory`:反转为时间序,`expandOutput` 把每步 `output` CasRef 展开为 JSON payload(供 prompt / JSONata 使用) 3. `buildHistory`:反转为时间序,`expandOutput` 把每步 `output` CasRef 展开为 JSON payload(供 prompt / moderator 使用)
4. `loadWorkflow`:从 `start.workflow` CasRef 加载 `WorkflowPayload` 4. `loadWorkflow`:从 `start.workflow` CasRef 加载 `WorkflowPayload`
#### Role definition 来源 #### Role definition 来源
@@ -572,7 +572,7 @@ Hermes 自带完整 agent runtime(`--yolo`、max-turns),tool 集由 Hermes
| P1 | `grep` | 搜索符号/引用 | | P1 | `grep` | 搜索符号/引用 |
| P2 | `fetch_url` | 查文档(planner 偶尔需要) | | P2 | `fetch_url` | 查文档(planner 偶尔需要) |
**不需要**在 builtin 里实现 moderator / workflow 路由工具——仍由 `uwf thread step` + JSONata 负责。 **不需要**在 builtin 里实现 moderator / workflow 路由工具——仍由 `uwf thread step` + status-based moderator 负责。
#### Agent loop 必须能力 #### Agent loop 必须能力
+23 -44
View File
@@ -75,7 +75,7 @@ uwf thread step 01J7K9M2XNPQR5VWBCDF8G3H4T --agent "bunx uwf-cursor"
**做的事:** **做的事:**
1. 读链头 → 当前 StepNode(或 StartNode) 1. 读链头 → 当前 StepNode(或 StartNode)
2. 收集 thread 历史(遍历链) 2. 收集 thread 历史(遍历链)
3. 调 moderator:评估 JSONata conditions → 得到下一个 role(或 END) 3. 调 moderator:status-based map lookup → 得到下一个 role(或 END)
4. 若 END → 归档 thread,输出最后链头,退出 4. 若 END → 归档 thread,输出最后链头,退出
5. 确定 agent command(`--agent` override > config.yaml per-workflow/role > config.yaml defaultAgent) 5. 确定 agent command(`--agent` override > config.yaml per-workflow/role > config.yaml defaultAgent)
6. 调用:`<agent-cmd> <thread-id> <role>`,捕获 stdout 得到新 StepNode hash 6. 调用:`<agent-cmd> <thread-id> <role>`,捕获 stdout 得到新 StepNode hash
@@ -199,29 +199,21 @@ payload:
``` ```
- `roles` — 内联定义,每个 role 的 `meta` 是独立的 cas_ref(指向 json-cas 内置 JSON Schema 节点) - `roles` — 内联定义,每个 role 的 `meta` 是独立的 cas_ref(指向 json-cas 内置 JSON Schema 节点)
- `conditions``Record<Name, JSONata>`,命名条件,方便画图描述 - `graph``Record<Role | "$START", Record<Status, Target>>`,每个 Target = `{ role, prompt }`
- `graph``Record<Role | "$START", Transition[]>`,每个 Transition = `{ role, condition }` - Status 来自上一个 role 输出的 `status` 字段,`$START``_` 作为初始 status
- `condition` 引用 conditions 中的 key,`null` = fallback - Prompt 模板使用 Mustache 渲染,变量来自 lastOutput
- 按数组顺序求值,第一个匹配的 transition 胜出
- 不含 agent binding — agent 配置在 `~/.uncaged/workflow/config.yaml` 中管理 - 不含 agent binding — agent 配置在 `~/.uncaged/workflow/config.yaml` 中管理
JSONata 表达式的求值上下文 Moderator 的求值逻辑
```jsonc ```typescript
{ evaluate(graph, lastRole, lastOutput) { role, prompt }
"start": { // StartNode 信息 // 1. status = lastRole === "$START" ? "_" : lastOutput.status
"workflow": "4KNM2PXR3B1QW", // 2. target = graph[lastRole][status]
"prompt": "Fix the login bug..." // 3. prompt = mustache.render(target.prompt, lastOutput)
},
"steps": [ // 所有已完成 steps,从旧到新
{ "role": "planner", "output": { "phases": [...] }, "detail": "7BQST3VW9F2MA", "agent": "uwf-hermes" },
{ "role": "developer", "output": { "filesChanged": ["src/auth.ts"], "summary": "Fixed redirect" }, "detail": "9KRVW3TN5F1QA", "agent": "uwf-cursor" },
{ "role": "reviewer", "output": { "approved": false }, "detail": "2MXBG6PN4A8JR", "agent": "uwf-hermes" }
]
}
``` ```
注:`output` 在上下文中会被自动展开为实际的 CAS 节点内容(而非 hash),方便 JSONata 表达式直接访问字段 注:routing 基于 `lastOutput.status` 字段的值,直接在 graph map 中查找对应的 Target
#### `StartNode`(Thread 起点) #### `StartNode`(Thread 起点)
@@ -350,7 +342,7 @@ OPENROUTER_API_KEY=sk-or-...
``` ```
packages/ packages/
├── cli-workflow/ # @uncaged/cli-workflow — uwf CLI(thread/workflow 命令) ├── cli-workflow/ # @uncaged/cli-workflow — uwf CLI(thread/workflow 命令)
├── workflow-moderator/ # @uncaged/workflow-moderator — JSONata moderator 引擎 ├── workflow-moderator/ # @uncaged/workflow-moderator — Status-based moderator 引擎
├── workflow-agent-kit/ # @uncaged/workflow-agent-kit — Agent CLI 框架(含 extractor) ├── workflow-agent-kit/ # @uncaged/workflow-agent-kit — Agent CLI 框架(含 extractor)
├── workflow-agent-hermes/ # @uncaged/workflow-agent-hermes — uwf-hermes CLI ├── workflow-agent-hermes/ # @uncaged/workflow-agent-hermes — uwf-hermes CLI
├── workflow-agent-cursor/ # @uncaged/workflow-agent-cursor — uwf-cursor CLI ├── workflow-agent-cursor/ # @uncaged/workflow-agent-cursor — uwf-cursor CLI
@@ -367,7 +359,7 @@ packages/
## 4. 关键数据类型 ## 4. 关键数据类型
JSONata 求值上下文本质上是 thread 链表的线性化表达。StepNode payload 和上下文中的 step 共享大量字段,提取为公共类型。 Moderator 通过 status-based map lookup 进行路由。StepNode payload 和上下文中的 step 共享大量字段,提取为公共类型。
### 4.1 公共类型 ### 4.1 公共类型
@@ -378,7 +370,7 @@ type CasRef = string;
/** Thread ID — ULID, 26-char Crockford Base32 */ /** Thread ID — ULID, 26-char Crockford Base32 */
type ThreadId = string; type ThreadId = string;
/** 一个 step 的核心数据,被 StepNode payload 和 JSONata 上下文共享 */ /** 一个 step 的核心数据,被 StepNode payload 和 moderator 上下文共享 */
type StepRecord = { type StepRecord = {
role: string; role: string;
output: CasRef; // cas_ref → 结构化输出节点(符合 role meta schema) output: CasRef; // cas_ref → 结构化输出节点(符合 role meta schema)
@@ -399,22 +391,16 @@ type RoleDefinition = {
meta: CasRef; // cas_ref → json-cas 内置 JSON Schema 节点 meta: CasRef; // cas_ref → json-cas 内置 JSON Schema 节点
}; };
type Transition = { type Target = {
role: string; // 目标 role 名 或 "$END" role: string; // 目标 role 名 或 "$END"
condition: string | null; // 引用 conditions 中的 key,null = fallback prompt: string; // Mustache 模板,渲染时注入 lastOutput
};
type ConditionDefinition = {
description: string;
expression: string; // JSONata expression
}; };
type WorkflowPayload = { type WorkflowPayload = {
name: string; name: string;
description: string; description: string;
roles: Record<string, RoleDefinition>; roles: Record<string, RoleDefinition>;
conditions: Record<string, ConditionDefinition>; graph: Record<string, Record<string, Target>>; // Record<Role | "$START", Record<Status, Target>>
graph: Record<string, Transition[]>; // Record<Role | "$START", Transition[]>
}; };
``` ```
@@ -432,20 +418,14 @@ type StepNodePayload = StepRecord & {
}; };
``` ```
### 4.4 JSONata 求值上下文 ### 4.4 Moderator 求值
Thread 链表的线性化。`steps[n]` 的字段和 `StepRecord` 一致,但 `output` 被展开为实际内容。 Moderator 使用 `evaluate(graph, lastRole, lastOutput)` 进行同步 status-based routing:
```typescript ```typescript
/** JSONata 上下文中的 step — output 被展开 */ // graph[lastRole][lastOutput.status] → Target { role, prompt }
type StepContext = Omit<StepRecord, "output"> & { // $START 角色使用 "_" 作为初始 status
output: unknown; // 展开后的 CAS 节点内容,非 hash // prompt 通过 Mustache 模板渲染,变量来自 lastOutput
};
type ModeratorContext = {
start: StartNodePayload;
steps: StepContext[]; // 从旧到新
};
``` ```
### 4.5 CLI 输出 ### 4.5 CLI 输出
@@ -534,6 +514,5 @@ StepNodePayload ──extends──→ StepRecord ←──maps to──→ Step
└── start.workflow → WorkflowPayload └── start.workflow → WorkflowPayload
├── roles: Record<name, RoleDefinition> ├── roles: Record<name, RoleDefinition>
── conditions: Record<name, JSONata> ── graph: Record<role, Record<status, Target>>
└── graph: Record<role, Transition[]>
``` ```
+4 -5
View File
@@ -123,7 +123,7 @@ type RoleNodeData = {
**边类型** **边类型**
- `default`(GradientEdge)→ 渐变色边(绿→蓝),节点仅有一条出边时使用 - `default`(GradientEdge)→ 渐变色边(绿→蓝),节点仅有一条出边时使用
- `conditional`(ConditionalEdge)→ 带条件标签的渐变色边,节点有多条出边时使用 - `status`(StatusEdge)→ 带 status 标签的渐变色边,节点有多条出边时使用
**边渲染特性** **边渲染特性**
- 渐变色:SVG linearGradient,从 source 端绿色(#10b981)到 target 端蓝色(#3b82f6 - 渐变色:SVG linearGradient,从 source 端绿色(#10b981)到 target 端蓝色(#3b82f6
@@ -234,7 +234,7 @@ Model 提供事务机制:
``` ```
ReactFlow ReactFlow
├─ nodeTypes: { start: NodeStart, end: NodeEnd, role: NodeRole } ├─ nodeTypes: { start: NodeStart, end: NodeEnd, role: NodeRole }
└─ edgeTypes: { default: GradientEdge, conditional: ConditionalEdge } └─ edgeTypes: { default: GradientEdge, status: StatusEdge }
``` ```
`NodeRole` 显示角色名(data.name),使用 teal 色系图标和标签。Handle 分蓝色(in)和绿色(out)两种颜色。 `NodeRole` 显示角色名(data.name),使用 teal 色系图标和标签。Handle 分蓝色(in)和绿色(out)两种颜色。
@@ -324,12 +324,11 @@ type WorkflowPayload = {
name: string; name: string;
description: string; description: string;
roles: Record<string, RoleDefinition>; // 角色定义(4 段式:identity/prepare/execute/report) roles: Record<string, RoleDefinition>; // 角色定义(4 段式:identity/prepare/execute/report)
conditions: Record<string, ConditionDefinition>; // JSONata 条件表达式 graph: Record<string, Record<string, Target>>; // status-based 路由图
graph: Record<string, Transition[]>; // 角色间的转移图
}; };
``` ```
workflow-dashboard 使用 `WorkFlowSteps` 格式作为交换数据,其中 `WorkFlowRole` 的字段与 `RoleDefinition` 对齐(description/identity/prepare/execute/report),`WorkFlowTransition` 对应 graph 中的 `Transition`。外部(CLI/server)负责 `WorkflowPayload``WorkFlowSteps` 的转换。 workflow-dashboard 使用 `WorkFlowSteps` 格式作为交换数据,其中 `WorkFlowRole` 的字段与 `RoleDefinition` 对齐(description/identity/prepare/execute/report),`WorkFlowTransition` 对应 graph 中的 `Target`。外部(CLI/server)负责 `WorkflowPayload``WorkFlowSteps` 的转换。
## 11. 当前状态与待完善项 ## 11. 当前状态与待完善项
+11 -10
View File
@@ -1,12 +1,12 @@
# @uncaged/workflow-moderator # @uncaged/workflow-moderator
JSONata-based graph evaluator — determines the next role or `$END` with zero LLM cost. Status-based graph evaluator — determines the next role or `$END` with zero LLM cost.
## Overview ## Overview
The moderator (Layer 1) walks the workflow graph from the current role. For each outgoing transition it evaluates an optional JSONata condition against `ModeratorContext` (start prompt + prior step outputs). The first truthy transition wins; its target role and edge prompt are returned. When no transition matches, the workflow ends (`$END`). The moderator (Layer 1) performs a status-based map lookup on the workflow graph. Given the last role and its output, it looks up `graph[lastRole][lastOutput.status]` to find the next `Target` (role + prompt template). The prompt is rendered via Mustache with `lastOutput` as the template context. For `$START`, the unit status `_` is used.
**Dependencies:** `@uncaged/workflow-protocol`, `jsonata` **Dependencies:** `@uncaged/workflow-protocol`, `mustache`
## Installation ## Installation
@@ -20,12 +20,13 @@ bun add @uncaged/workflow-moderator
```typescript ```typescript
function evaluate( function evaluate(
workflow: WorkflowPayload, graph: Record<string, Record<string, Target>>,
context: ModeratorContext, lastRole: string,
): Promise<Result<EvaluateResult, Error>> lastOutput: Record<string, unknown> & { status: string },
): Result<EvaluateResult, Error>
``` ```
Returns `{ ok: true, value: { role, prompt } }` where `role` is the next role name or `"$END"`, and `prompt` is the edge instruction for the agent. Returns `{ ok: true, value: { role, prompt } }` where `role` is the next role name or `"$END"`, and `prompt` is the rendered edge instruction for the agent.
### Types ### Types
@@ -42,9 +43,9 @@ The `Result<T, E>` type is local to this package (`{ ok: true; value: T } | { ok
```typescript ```typescript
import { evaluate } from "@uncaged/workflow-moderator"; import { evaluate } from "@uncaged/workflow-moderator";
import type { ModeratorContext, WorkflowPayload } from "@uncaged/workflow-protocol"; import type { Target } from "@uncaged/workflow-protocol";
const result = await evaluate(workflow, context); const result = evaluate(graph, lastRole, lastOutput);
if (result.ok && result.value.role !== "$END") { if (result.ok && result.value.role !== "$END") {
console.log(`Next role: ${result.value.role}, prompt: ${result.value.prompt}`); console.log(`Next role: ${result.value.role}, prompt: ${result.value.prompt}`);
} }
@@ -55,6 +56,6 @@ if (result.ok && result.value.role !== "$END") {
``` ```
src/ src/
├── index.ts Public exports ├── index.ts Public exports
├── evaluate.ts Graph walk + JSONata condition evaluation ├── evaluate.ts Status-based map lookup + Mustache prompt rendering
└── types.ts EvaluateResult, Result └── types.ts EvaluateResult, Result
``` ```
+2 -9
View File
@@ -47,23 +47,16 @@ type RoleDefinition = {
frontmatter: CasRef; frontmatter: CasRef;
}; };
type Transition = { type Target = {
role: string; role: string;
condition: string | null;
prompt: string; prompt: string;
}; };
type ConditionDefinition = {
description: string;
expression: string;
};
type WorkflowPayload = { type WorkflowPayload = {
name: string; name: string;
description: string; description: string;
roles: Record<string, RoleDefinition>; roles: Record<string, RoleDefinition>;
conditions: Record<string, ConditionDefinition>; graph: Record<string, Record<string, Target>>;
graph: Record<string, Transition[]>;
}; };
``` ```