Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/docs-web/src/content/docs/book/dag-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ The classify-and-route example uses `none_failed_min_one_success` on `implement`

## Node Types

Archon supports seven node types. Exactly one mode field is required per node:
Archon supports eight node types. Exactly one mode field is required per node:

| Type | Syntax | When to use |
|------|--------|-------------|
Expand All @@ -242,6 +242,7 @@ Archon supports seven node types. Exactly one mode field is required per node:
| **Bash** | `bash: "shell command"` | Run a shell script without AI. Stdout is captured as `$nodeId.output`. Deterministic operations only. |
| **Script** | `script: "..." ` + `runtime: bun \| uv` | Run TypeScript/JavaScript (bun) or Python (uv) without AI. Inline code or named reference to `.archon/scripts/`. Stdout captured as `$nodeId.output`. See [Script Nodes](/guides/script-nodes/). |
| **Loop** | `loop: { prompt: "...", until: SIGNAL }` | Repeat an AI prompt until a completion signal appears in the output. See [Loop Nodes](/guides/loop-nodes/). |
| **Loop Group** | `loop_group: { until: SIGNAL, nodes: [...] }` | Repeat a multi-node sub-DAG body until a completion condition is met (cross-node iteration). See [Loop Nodes](/guides/loop-nodes/). |
| **Approval** | `approval: { message: "..." }` | Pause the workflow for a human approve/reject decision. See [Approval Nodes](/guides/approval-nodes/). |
| **Cancel** | `cancel: "reason string"` | Terminate the workflow run (status: cancelled, not failed). Usually gated with `when:`. |

Expand Down
99 changes: 99 additions & 0 deletions packages/docs-web/src/content/docs/guides/loop-nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,105 @@ If you need to accumulate results across iterations, write them to files in
| Idle timeout per iteration | Iteration completes with whatever output was collected; loop continues to next iteration |
| `retry` configured on node | Rejected at parse time — workflow fails to load |

## Cross-Node Loops with `loop_group`

A `loop` node iterates a **single prompt**. A `loop_group` node iterates a
**multi-node sub-DAG** — a sealed body of nodes that re-runs in full each
iteration until a completion condition is met. Use it when "iterate until done"
needs a *pipeline* of steps (e.g. `implement → test → review`), not just one
repeated step.

```yaml
name: fix-until-green
description: Implement, test, and review until tests pass
nodes:
- id: fix-loop
loop_group:
until: TESTS_PASS # completion signal in the body's terminal output
max_iterations: 5
fresh_context: false # reset body sessions each iteration (default false)
nodes: # sealed sub-DAG body — repeats as a unit
Comment on lines +353 to +355

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix fresh_context comment — meaning is inverted.

Line 357 says fresh_context: false with the comment "reset body sessions each iteration (default false)". This is backwards: fresh_context: true resets sessions each iteration; false (the default) preserves context across iterations. Update the comment to:

-      fresh_context: false      # reset body sessions each iteration (default false)
+      fresh_context: false      # preserve body sessions across iterations (default: false)

Or if the intent was to show the reset behavior, change the value to true:

-      fresh_context: false      # reset body sessions each iteration (default false)
+      fresh_context: true       # reset body sessions each iteration
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
max_iterations: 5
fresh_context: false # reset body sessions each iteration (default false)
nodes: # sealed sub-DAG body — repeats as a unit
max_iterations: 5
fresh_context: false # preserve body sessions across iterations (default: false)
nodes: # sealed sub-DAG body — repeats as a unit
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs-web/src/content/docs/guides/loop-nodes.md` around lines 356 -
358, The `fresh_context` example in the loop-nodes guide has an inverted
explanation: in the `fresh_context` snippet, the comment currently describes
reset behavior while the value is `false`. Update the inline comment near
`fresh_context` to match the actual semantics in this section, or switch the
example to `true` if you want to demonstrate resetting body sessions each
iteration; use the `fresh_context` setting in the loop example as the anchor for
the edit.

- id: implement
prompt: Fix the failing tests. Emit TESTS_PASS only when all pass.
depends_on: []
- id: test
bash: bun test
depends_on: [implement]
- id: review
prompt: Summarize the result; echo TESTS_PASS if tests are green.
depends_on: [test]
```

### How it works

- From the **outer DAG's** perspective, `fix-loop` is one node. The cycle is
*encapsulated* inside it — the outer DAG stays acyclic.
- Each iteration runs the body's topological layers in full (concurrent nodes
within a layer run in parallel, same as a normal DAG).
- The body is **sealed**: a body node's `depends_on` may only reference sibling
body nodes, not outer-DAG nodes. Outer context is still reachable via `$nodeId.output`
refs in body prompts.
- Body node events are namespaced `{groupId}.{nodeId}` in the event log.
- `$fix-loop.output` (visible to the outer DAG) is the **final iteration's
terminal-node output**.

### Cross-iteration references: `$LOOP_PREV`

A body node can reference a sibling's output from the **previous iteration**
with `$LOOP_PREV.<nodeId>.output` (and `$LOOP_PREV.<nodeId>.output.<field>`
for structured output):

```yaml
nodes:
- id: fix-loop
loop_group:
until: TESTS_PASS
max_iterations: 5
nodes:
- id: implement
prompt: |
Previous attempt's test output:
$LOOP_PREV.test.output
Fix what failed.
depends_on: []
- id: test
bash: bun test
depends_on: [implement]
```

On iteration 1 (no prior iteration), `$LOOP_PREV.*` resolves to an empty
string. Field access uses the same strict semantics as `$nodeId.output.field`
(a field not in the producer's declared schema fails the consuming node rather
than silently degrading).

### Configuration fields

`loop_group` shares the same iteration-control fields as `loop`:
[`until`](#until), [`max_iterations`](#max_iterations),
[`fresh_context`](#fresh_context), [`until_bash`](#until_bash),
[`interactive`](#interactive-and-gate_message), and `gate_message`. The
difference is the body: `loop` takes a single `prompt`; `loop_group` takes a
`nodes` array.

### Resume

Resuming a paused `loop_group` (interactive gate, or a cancelled/restarted run)
**re-runs the current iteration's whole body** in full — the same model as a
`loop` node re-running its current iteration. Per-body-node resume granularity
is not supported in v1.

### What is NOT supported on loop_group nodes (v1)

- `retry` (the loop manages its own iteration) — rejected at parse time.
- `persist_session` for body AI nodes across iterations — body sessions reset
per iteration (governed by `fresh_context`).
- Per-body-node resume (skip-to-failed-body-node) — the whole iteration re-runs.
- `$LOOP_PREV.<id>.output[N]` history indexing — only the immediately prior
iteration is reachable.

Nested `loop_group` inside a `loop_group` body is supported by construction
(the body is a normal `nodes` array), but is not hardened in v1.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
## See Also

- [Authoring Workflows](/guides/authoring-workflows/) — full workflow reference
Expand Down
1 change: 1 addition & 0 deletions packages/paths/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ export type WorkflowNodeType =
| 'bash'
| 'script'
| 'loop'
| 'loop_group'
| 'approval'
| 'cancel';

Expand Down
Loading