refactor: status-based graph routing + mustache prompt templates #490

Closed
opened 2026-05-25 04:34:18 +00:00 by xiaoju · 2 comments
Owner

Summary

Replace the current JSONata condition system with status-based map routing and mustache prompt templates.

Motivation

Running workflows revealed that moderator rules almost never inspect history — they only check the current role's key output (e.g. approved = false). The full JSONata expression engine is overkill. Simplifying to a status enum field makes the graph self-describing and the evaluator trivial.

Design

Graph: ordered list → status map

Before:

conditions:
  notApproved:
    description: "Reviewer rejected"
    expression: "$last(
eviewer\). approved = false"

graph:
  reviewer:
    - role: developer
      condition: notApproved
      prompt: "Fix the issues."
    - role: $END
      condition: null
      prompt: "Done."

After:

graph:
  $START:
    _: { role: planner, prompt: "Analyze the issue and create a plan." }
  planner:
    _: { role: developer, prompt: "Implement the plan: {{plan}}" }
  developer:
    _: { role: reviewer, prompt: "Review the changes: {{summary}}" }
  reviewer:
    approved: { role: $END, prompt: "Done." }
    rejected: { role: developer, prompt: "Fix: {{comments}}" }
  • graph[role] is Record<Status, Target> (map lookup, not ordered list)
  • _ is the unit status for roles with a single exit
  • conditions block is deleted entirely

Frontmatter status field

  • Every role's frontmatter schema must include a status field with enum constraint
  • Unit roles: status: { enum: ["_"] }
  • $START implicitly has status _
  • Invalid status values are caught by schema validation before routing

Prompt templates: mustache

  • Prompt strings are standard mustache templates
  • Template variables come from the current role's frontmatter object
  • Supports dot paths ({{review.summary}}), sections ({{#items}}...{{/items}}), etc.
  • LLMs natively know mustache syntax — zero prompt engineering overhead

Evaluator

The entire evaluate() function becomes a map lookup + mustache render:

function evaluate(graph, lastRole, lastOutput) {
  const target = graph[lastRole][lastOutput.status]
  return { role: target.role, prompt: mustache.render(target.prompt, lastOutput) }
}

Type Changes

// NEW
type Target = { role: string; prompt: string }

// CHANGED
type WorkflowPayload = {
  name: string
  description: string
  roles: Record<string, RoleDefinition>
  graph: Record<string, Record<string, Target>>  // role → status → target
}

// DELETED
// - ConditionDefinition
// - Transition (replaced by Target)

Dependency Changes

Remove Add
jsonata mustache

Affected Packages

  • workflow-protocol: delete ConditionDefinition, change WorkflowPayload and remove Transition type
  • workflow-moderator: rewrite evaluate.ts to map lookup + mustache render, delete jsonata dependency
  • workflow-moderator/__tests__: rewrite tests
  • cli-workflow: YAML schema validation update
  • examples/*.yaml: migrate all to new format
  • workflow-dashboard: edge visualization from condition to status label

—— 小橘 🍊(NEKO Team)

## Summary Replace the current JSONata condition system with status-based map routing and mustache prompt templates. ## Motivation Running workflows revealed that moderator rules almost never inspect history — they only check the current role's key output (e.g. `approved = false`). The full JSONata expression engine is overkill. Simplifying to a `status` enum field makes the graph self-describing and the evaluator trivial. ## Design ### Graph: ordered list → status map **Before:** ```yaml conditions: notApproved: description: "Reviewer rejected" expression: "$last( eviewer\). approved = false" graph: reviewer: - role: developer condition: notApproved prompt: "Fix the issues." - role: $END condition: null prompt: "Done." ``` **After:** ```yaml graph: $START: _: { role: planner, prompt: "Analyze the issue and create a plan." } planner: _: { role: developer, prompt: "Implement the plan: {{plan}}" } developer: _: { role: reviewer, prompt: "Review the changes: {{summary}}" } reviewer: approved: { role: $END, prompt: "Done." } rejected: { role: developer, prompt: "Fix: {{comments}}" } ``` - `graph[role]` is `Record<Status, Target>` (map lookup, not ordered list) - `_` is the unit status for roles with a single exit - `conditions` block is deleted entirely ### Frontmatter `status` field - Every role's frontmatter schema **must** include a `status` field with `enum` constraint - Unit roles: `status: { enum: ["_"] }` - `$START` implicitly has status `_` - Invalid status values are caught by schema validation before routing ### Prompt templates: mustache - Prompt strings are standard [mustache](https://mustache.github.io/) templates - Template variables come from the current role's frontmatter object - Supports dot paths (`{{review.summary}}`), sections (`{{#items}}...{{/items}}`), etc. - LLMs natively know mustache syntax — zero prompt engineering overhead ### Evaluator The entire `evaluate()` function becomes a map lookup + mustache render: ```typescript function evaluate(graph, lastRole, lastOutput) { const target = graph[lastRole][lastOutput.status] return { role: target.role, prompt: mustache.render(target.prompt, lastOutput) } } ``` ## Type Changes ```typescript // NEW type Target = { role: string; prompt: string } // CHANGED type WorkflowPayload = { name: string description: string roles: Record<string, RoleDefinition> graph: Record<string, Record<string, Target>> // role → status → target } // DELETED // - ConditionDefinition // - Transition (replaced by Target) ``` ## Dependency Changes | Remove | Add | |--------|-----| | `jsonata` | `mustache` | ## Affected Packages - `workflow-protocol`: delete `ConditionDefinition`, change `WorkflowPayload` and remove `Transition` type - `workflow-moderator`: rewrite `evaluate.ts` to map lookup + mustache render, delete jsonata dependency - `workflow-moderator/__tests__`: rewrite tests - `cli-workflow`: YAML schema validation update - `examples/*.yaml`: migrate all to new format - `workflow-dashboard`: edge visualization from condition to status label —— 小橘 🍊(NEKO Team)
Author
Owner

Phase 拆分

  • Phase 1: protocol types + moderator rewrite → #491
  • Phase 2: examples YAML migration + CLI validation → #492
  • Phase 3: dashboard adaptation → #493

—— 小橘 🍊(NEKO Team)

## Phase 拆分 - Phase 1: protocol types + moderator rewrite → #491 - Phase 2: examples YAML migration + CLI validation → #492 - Phase 3: dashboard adaptation → #493 —— 小橘 🍊(NEKO Team)
Author
Owner

验证结果汇总

  • Phase 1: protocol types + moderator rewrite (#491) → PR #494 merged
  • Phase 2: examples YAML migration + mustache escape fix (#492) → PR #495 merged
  • Phase 3: dashboard adaptation (#493) → PR #496 submitted

变更统计

Metric Value
删除 jsonata 依赖, ConditionDefinition, Transition 类型, conditions 块, ConditionalEdge
新增 mustache 依赖, Target 类型, status enum, StatusEdge
净减代码 ~360 行
测试 298 pass, 0 fail

架构变化

Before: evaluate(workflow, context) → JSONata 遍历 + 表达式求值
After:  evaluate(graph, lastRole, lastOutput) → graph[role][status] + mustache.render()

等 PR #496 merge 后 close。

—— 小橘 🍊(NEKO Team)

## 验证结果汇总 - ✅ Phase 1: protocol types + moderator rewrite (#491) → PR #494 merged - ✅ Phase 2: examples YAML migration + mustache escape fix (#492) → PR #495 merged - ✅ Phase 3: dashboard adaptation (#493) → PR #496 submitted ## 变更统计 | Metric | Value | |--------|-------| | 删除 | jsonata 依赖, ConditionDefinition, Transition 类型, conditions 块, ConditionalEdge | | 新增 | mustache 依赖, Target 类型, status enum, StatusEdge | | 净减代码 | ~360 行 | | 测试 | 298 pass, 0 fail | ## 架构变化 ``` Before: evaluate(workflow, context) → JSONata 遍历 + 表达式求值 After: evaluate(graph, lastRole, lastOutput) → graph[role][status] + mustache.render() ``` 等 PR #496 merge 后 close。 —— 小橘 🍊(NEKO Team)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#490