Workflow editor: connection model, loop support, model escalation, and dashboard cleanup #4

Open
opened 2026-04-17 08:21:36 +00:00 by timur · 0 comments
Owner

Several design issues surfaced while reviewing the service_agent workflow in the editor.


1. Remove Examples section from dashboard

The dashboard (index.html) shows a full Examples section (table with Name/Workflow/Description/Updated columns) that takes significant vertical space. This was never requested and clutters the main view. There's also a separate /examples page.

Action: Remove the Examples section from the dashboard index page. Keep the /examples route and the per-workflow example sidebar — those are useful in context.


2. Connection visual style is misleading

The editor currently shows conditional edges as red dashed lines (on_failure → red, conditional → amber dashed) vs solid grey for on_success. This creates a false impression that connections are fundamentally different types.

In reality, every connection is the same type — it's just a pipe with a condition expression that gates whether data flows through. The visual distinction should communicate what condition is set, not imply different connection kinds.

Action: Unify the visual style. All connections are solid lines of the same base style. The condition label (shown on hover or always) communicates the gate. A small icon or badge on the edge can indicate it has a condition, but the line itself shouldn't change shape/color in a way that suggests different pipe types. Reserve dashed style only for "being drawn" (drag preview).


3. Script execution needs structured output with exit code

Currently, when hero_proc runs a script:

  • Exit code 0 → node status Success, stdout captured as output_data
  • Exit code != 0 → node status Failed, stderr/stdout goes to error_message, output_data is empty

This means downstream conditional edges can only branch on Success/Failed (via on_success/on_failure), not on the actual exit code or stderr content. The error output isn't available as structured data for downstream nodes.

Action: For script-type actions, produce structured output that includes exit_code, stdout, and stderr as separate fields. When output_format is text (current default for scripts), wrap the result:

{"exit_code": 0, "stdout": "...", "stderr": "..."}

This lets downstream edges use condition expressions like outputs.exit_code == 0 and data mappings can pull stdout or stderr independently.

For the service_agent template specifically:

  • script_execution → result_summary: condition outputs.exit_code == 0, map stdout to execution_output
  • script_execution → error_debug: condition outputs.exit_code != 0, map stderr to error_output, also carry stdout for context

4. Error debug should loop back to code_generation, not a separate retry node

The v3 template has a separate retry_execution node that's essentially a duplicate of script_execution. This is wrong. The error_debug node should produce feedback and feed it back to code_generation as additional context, which regenerates the script, which goes back to script_execution. This creates a natural loop:

code_generation → script_execution → (exit_code != 0) → error_debug → code_generation → ...

The error_debug node's output becomes a feedback input on code_generation's next iteration — containing the error, the failed script, and the AI's analysis of what went wrong.

Problem: The current executor explicitly rejects cycles:

if has_cycle(workflow) {
    return Err("Workflow contains a cycle".to_string());
}

And topological_sort() also fails on cycles. Supporting loops requires executor changes — see next section.


5. Loop support in the executor

Loops are a natural pattern (retry with feedback, iterative refinement, polling). The question is how to support them without adding special-case complexity.

Proposed approach: keep it uniform

Rather than introducing a special "loop box" construct, treat loops as regular graph cycles with a loop counter tracked per-node:

  • Each node tracks an iteration counter (starts at 0, incremented each time the node runs again)
  • Condition expressions can reference iteration — e.g., iteration < 3 on the back-edge from error_debug to code_generation
  • An exit condition on the forward edge from error_debug caps the retries — e.g., iteration >= 3 → skip to result_summary with the error

This keeps the model uniform: no new node types, no special constructs. A loop is just a cycle in the graph where at least one edge has an iteration-based condition.

Executor changes needed:

  1. Remove cycle rejectionvalidate_workflow() should allow cycles, but verify that every cycle has at least one conditional edge (prevents infinite loops)
  2. Replace topological sort with a ready-queue approach — the executor already has a ready-queue loop (lines 183-295 in executor.rs). It just needs to:
    • Allow nodes to re-enter the ready queue when a back-edge fires
    • Clear a node's previous status/output when it re-runs
    • Track iteration count per node
  3. Global max iteration guard — hard cap (e.g., 10) to prevent runaway loops regardless of condition expressions

What the service_agent template looks like with loops:

fetch_catalog → service_selection → compile_stubs → code_generation → script_execution
                                                         ↑                    |
                                                         |          (exit_code == 0) → result_summary
                                                         |          (exit_code != 0)
                                                         |                    ↓
                                                         └──── error_debug ───┘
                                                              (iteration < 3)

No retry_execution node needed. Code_generation gets a feedback input that's empty on first run and populated by error_debug on retries.


6. Model escalation on loop iterations

A very common pattern: start with a cheap/fast model, escalate to a more capable one on retry. For the service_agent: first attempt uses gpt-4o-mini, after 1 failure escalate to gpt-4o, after 2 failures try claude-sonnet.

This should NOT be a special engine feature. It follows naturally from the uniform loop model:

  • The model field in ai_config is already a template variable ({{model}})
  • The error_debug node can output a next_model field based on the iteration count
  • code_generation's model input gets overridden by the mapped next_model from error_debug
  • On first run (no error_debug output), it falls back to the workflow input's model value

Alternatively, the condition expression itself can drive this:

  • Edge from error_debug to code_generation with condition iteration < 1 maps next_model: "gpt-4o"
  • A second back-edge with condition iteration >= 1 && iteration < 3 maps next_model: "claude-sonnet-4-6"
  • The exit edge with iteration >= 3 goes to result_summary with the final error

This keeps escalation as data flow, not engine magic.


7. Future: visual aids for loops in the editor

Once loops work at the engine level, the editor can add optional visual grouping — detecting cycles and drawing a subtle bounding box around the loop body with iteration settings shown as a badge. This is purely a UI convenience, not a model change. The underlying representation stays the same: nodes + edges + conditions.

Possible visual aids:

  • Auto-detect cycles and highlight back-edges with a loop icon
  • Show current iteration count during play execution
  • A "loop settings" popover when clicking the back-edge that shows max iterations and escalation config
  • Collapse/expand a loop group to reduce visual noise

This is low priority — the engine and condition model come first.


Summary of changes by priority

  1. Dashboard cleanup — remove Examples from index (trivial)
  2. Connection visuals — unify edge styling (UI-only)
  3. Structured script output — exit_code/stdout/stderr in node output (engine + hero_proc coordination)
  4. Loop support — allow cycles, iteration counter, re-entry (engine)
  5. Update service_agent template — remove retry_execution, add back-edge with feedback (template)
  6. Model escalation — template-level, no engine changes needed once loops work
  7. Editor visual aids for loops — future enhancement
Several design issues surfaced while reviewing the service_agent workflow in the editor. --- ## 1. Remove Examples section from dashboard The dashboard (`index.html`) shows a full Examples section (table with Name/Workflow/Description/Updated columns) that takes significant vertical space. This was never requested and clutters the main view. There's also a separate `/examples` page. **Action:** Remove the Examples section from the dashboard index page. Keep the `/examples` route and the per-workflow example sidebar — those are useful in context. --- ## 2. Connection visual style is misleading The editor currently shows conditional edges as **red dashed lines** (`on_failure` → red, `conditional` → amber dashed) vs solid grey for `on_success`. This creates a false impression that connections are fundamentally different types. In reality, every connection is the same type — it's just a pipe with a **condition expression** that gates whether data flows through. The visual distinction should communicate *what condition* is set, not imply different connection kinds. **Action:** Unify the visual style. All connections are solid lines of the same base style. The condition label (shown on hover or always) communicates the gate. A small icon or badge on the edge can indicate it has a condition, but the line itself shouldn't change shape/color in a way that suggests different pipe types. Reserve dashed style only for "being drawn" (drag preview). --- ## 3. Script execution needs structured output with exit code Currently, when hero_proc runs a script: - Exit code 0 → node status `Success`, stdout captured as `output_data` - Exit code != 0 → node status `Failed`, stderr/stdout goes to `error_message`, `output_data` is empty This means downstream conditional edges can only branch on `Success`/`Failed` (via `on_success`/`on_failure`), not on the actual exit code or stderr content. The error output isn't available as structured data for downstream nodes. **Action:** For script-type actions, produce structured output that includes `exit_code`, `stdout`, and `stderr` as separate fields. When `output_format` is `text` (current default for scripts), wrap the result: ```json {"exit_code": 0, "stdout": "...", "stderr": "..."} ``` This lets downstream edges use condition expressions like `outputs.exit_code == 0` and data mappings can pull `stdout` or `stderr` independently. For the service_agent template specifically: - `script_execution → result_summary`: condition `outputs.exit_code == 0`, map `stdout` to `execution_output` - `script_execution → error_debug`: condition `outputs.exit_code != 0`, map `stderr` to `error_output`, also carry `stdout` for context --- ## 4. Error debug should loop back to code_generation, not a separate retry node The v3 template has a separate `retry_execution` node that's essentially a duplicate of `script_execution`. This is wrong. The error_debug node should produce **feedback** and feed it back to `code_generation` as additional context, which regenerates the script, which goes back to `script_execution`. This creates a natural loop: ``` code_generation → script_execution → (exit_code != 0) → error_debug → code_generation → ... ``` The `error_debug` node's output becomes a `feedback` input on `code_generation`'s next iteration — containing the error, the failed script, and the AI's analysis of what went wrong. **Problem:** The current executor explicitly rejects cycles: ```rust if has_cycle(workflow) { return Err("Workflow contains a cycle".to_string()); } ``` And `topological_sort()` also fails on cycles. Supporting loops requires executor changes — see next section. --- ## 5. Loop support in the executor Loops are a natural pattern (retry with feedback, iterative refinement, polling). The question is how to support them without adding special-case complexity. ### Proposed approach: keep it uniform Rather than introducing a special "loop box" construct, treat loops as regular graph cycles with a **loop counter** tracked per-node: - Each node tracks an `iteration` counter (starts at 0, incremented each time the node runs again) - Condition expressions can reference `iteration` — e.g., `iteration < 3` on the back-edge from error_debug to code_generation - An exit condition on the forward edge from error_debug caps the retries — e.g., `iteration >= 3` → skip to result_summary with the error This keeps the model uniform: no new node types, no special constructs. A loop is just a cycle in the graph where at least one edge has an iteration-based condition. ### Executor changes needed: 1. **Remove cycle rejection** — `validate_workflow()` should allow cycles, but verify that every cycle has at least one conditional edge (prevents infinite loops) 2. **Replace topological sort with a ready-queue approach** — the executor already has a ready-queue loop (lines 183-295 in `executor.rs`). It just needs to: - Allow nodes to re-enter the ready queue when a back-edge fires - Clear a node's previous status/output when it re-runs - Track iteration count per node 3. **Global max iteration guard** — hard cap (e.g., 10) to prevent runaway loops regardless of condition expressions ### What the service_agent template looks like with loops: ``` fetch_catalog → service_selection → compile_stubs → code_generation → script_execution ↑ | | (exit_code == 0) → result_summary | (exit_code != 0) | ↓ └──── error_debug ───┘ (iteration < 3) ``` No `retry_execution` node needed. Code_generation gets a `feedback` input that's empty on first run and populated by error_debug on retries. --- ## 6. Model escalation on loop iterations A very common pattern: start with a cheap/fast model, escalate to a more capable one on retry. For the service_agent: first attempt uses `gpt-4o-mini`, after 1 failure escalate to `gpt-4o`, after 2 failures try `claude-sonnet`. This should NOT be a special engine feature. It follows naturally from the uniform loop model: - The `model` field in `ai_config` is already a template variable (`{{model}}`) - The `error_debug` node can output a `next_model` field based on the iteration count - `code_generation`'s `model` input gets overridden by the mapped `next_model` from error_debug - On first run (no error_debug output), it falls back to the workflow input's `model` value Alternatively, the condition expression itself can drive this: - Edge from error_debug to code_generation with condition `iteration < 1` maps `next_model: "gpt-4o"` - A second back-edge with condition `iteration >= 1 && iteration < 3` maps `next_model: "claude-sonnet-4-6"` - The exit edge with `iteration >= 3` goes to result_summary with the final error This keeps escalation as data flow, not engine magic. --- ## 7. Future: visual aids for loops in the editor Once loops work at the engine level, the editor can add **optional visual grouping** — detecting cycles and drawing a subtle bounding box around the loop body with iteration settings shown as a badge. This is purely a UI convenience, not a model change. The underlying representation stays the same: nodes + edges + conditions. Possible visual aids: - Auto-detect cycles and highlight back-edges with a loop icon - Show current iteration count during play execution - A "loop settings" popover when clicking the back-edge that shows max iterations and escalation config - Collapse/expand a loop group to reduce visual noise This is low priority — the engine and condition model come first. --- ## Summary of changes by priority 1. **Dashboard cleanup** — remove Examples from index (trivial) 2. **Connection visuals** — unify edge styling (UI-only) 3. **Structured script output** — exit_code/stdout/stderr in node output (engine + hero_proc coordination) 4. **Loop support** — allow cycles, iteration counter, re-entry (engine) 5. **Update service_agent template** — remove retry_execution, add back-edge with feedback (template) 6. **Model escalation** — template-level, no engine changes needed once loops work 7. **Editor visual aids for loops** — future enhancement
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_logic#4
No description provided.