diff --git a/docs/user-guide/workflows/configuration/context-management.md b/docs/user-guide/workflows/configuration/context-management.md index dac844e..bf4a9cb 100644 --- a/docs/user-guide/workflows/configuration/context-management.md +++ b/docs/user-guide/workflows/configuration/context-management.md @@ -233,6 +233,7 @@ task: | - ⚠️ **Simple lists**: Not added to context as variables (use output_key or wrap in dict) - ⚠️ **Plain strings**: Not added to context as variables (use output_key or output as JSON) - 💡 **Best practice**: Always output structured JSON from assistants for maximum flexibility +- 💡 **Parallel iterations**: Use `append_to_context: true` with `output_key` to collect all iteration results into a list — without it, only the last iteration's value is retained #### Accessing Context in Templates @@ -337,6 +338,40 @@ states: include_in_llm_history: false # Don't show raw IDs to LLM ``` +**append_to_context** (boolean, default: `false`) + +Controls whether the current state's output is appended to an existing list in the context store rather than overwriting it. + +- `true`: Output is accumulated — the value under `output_key` in the context store grows into a list across multiple executions (most useful with `iter_key` iterations) +- `false` (default): Output overwrites the previous value under the same key + +**When to use `true`:** + +- Collecting results from all parallel `iter_key` iterations into a single list +- Any scenario where multiple states write to the same key and all values must be preserved + +**Requirements and constraints:** + +- Use together with `output_key` to specify which context key accumulates the results +- When `append_to_context: true`, the top-level state key (set by `output_key`) is **not** written — read the accumulated list from the context store only via `{{output_key}}` +- Has no effect when `store_in_context: false` + +```yaml +states: + - id: process-item + assistant_id: processor + task: "Analyze {{task}} and return a result object" + next: + state_id: aggregate + output_key: results + append_to_context: true # Each parallel branch appends; context_store["results"] = [r1, r2, r3] + + - id: aggregate + assistant_id: aggregator + task: "Summarize all results: {{results}}" + # {{results}} contains the full list from every iteration +``` + **clear_prior_messages** (boolean, default: `false`) Clears all prior messages from the message history, creating a "fresh start" for the LLM context. @@ -543,6 +578,18 @@ next: Use case: When iterating over items, prevent context from accumulating across iterations. Each iteration gets only the current item, not data from previous iterations. +**Pattern 7: Accumulate All Iteration Results Into a List** + +```yaml +next: + state_id: aggregate + iter_key: items + output_key: all_results + append_to_context: true +``` + +Use case: When iterating in parallel and you need to collect every branch's output into a single list. Without `append_to_context: true`, only the last branch's value is kept (last-value-wins). With it, `context_store["all_results"]` contains a list with one entry per iteration. + ### 6.3 Dynamic Value Resolution Dynamic value resolution enables you to use context store values throughout your workflow configuration using template syntax. diff --git a/docs/user-guide/workflows/configuration/state-transitions.md b/docs/user-guide/workflows/configuration/state-transitions.md index 94582d9..c34b18b 100644 --- a/docs/user-guide/workflows/configuration/state-transitions.md +++ b/docs/user-guide/workflows/configuration/state-transitions.md @@ -361,6 +361,21 @@ The previous state can output various formats, and `iter_key` adapts accordingly - Two formats: dictionary key (`"items"`) or JSON Pointer (`"/data/items"`) - The extracted value must be a list or will be wrapped as single-item list +**append_to_context** (boolean, default: `false`): + +- When `true`, each iteration's output is **appended** to a list in the context store instead of overwriting the previous value +- When `false` (default), standard overwrite semantics apply — the last iteration's value wins on duplicate keys +- Use together with `output_key` to control which context key accumulates the collected results +- When `append_to_context: true` is combined with `output_key`, the top-level state key is **not** set — values are only available via the accumulated list in the context store + +```yaml +next: + state_id: collect-results + iter_key: items + output_key: processed_items + append_to_context: true # Each iteration appends its output; context_store["processed_items"] becomes a list +``` + #### Multi-Stage Iteration: For multi-stage iteration (when you have multiple sequential states processing each item), the **same `iter_key` must be present in every state** included in the iteration chain. @@ -550,6 +565,57 @@ states: 4. After all branches complete, contexts and message histories are merged 5. Merged results flow to `merge-results` +**Example 7: Accumulating Results Across Iterations** + +When all iteration results must be preserved, use `append_to_context: true` so each parallel branch contributes to a shared list rather than overwriting it. + +```yaml +states: + - id: get-tickets + tool_id: jira-api + tool_args: + jql: "project = PROJ AND status = 'Open'" + # Outputs: [{"id": "PROJ-1", "title": "Bug A"}, {"id": "PROJ-2", "title": "Bug B"}, {"id": "PROJ-3", "title": "Bug C"}] + next: + state_id: analyze-ticket + iter_key: . # Iterate over the entire list + + - id: analyze-ticket + assistant_id: analyzer + task: | + Analyze ticket {{id}}: {{title}} + Return a JSON object: {"ticket_id": "...", "severity": "low|medium|high", "summary": "..."} + # Iteration 1 returns: {"ticket_id": "PROJ-1", "severity": "high", "summary": "..."} + # Iteration 2 returns: {"ticket_id": "PROJ-2", "severity": "low", "summary": "..."} + # Iteration 3 returns: {"ticket_id": "PROJ-3", "severity": "medium", "summary": "..."} + next: + state_id: create-report + output_key: analyses + append_to_context: true # Accumulate all results; context_store["analyses"] = [{...}, {...}, {...}] + + - id: create-report + assistant_id: reporter + task: | + Create a severity report based on all ticket analyses. + Analyses: {{analyses}} + # Receives the full list of all three analyses in {{analyses}} + next: + state_id: end +``` + +**How it works:** + +1. Three parallel branches process one ticket each +2. Each branch writes its output with the `analyses` key via `append_to_context: true` +3. The reducer appends each result to the list — no overwriting occurs +4. `create-report` receives `analyses = [result_1, result_2, result_3]` in its context + +:::tip +`append_to_context: true` is the recommended way to collect results from all parallel iterations into a single list. It replaces the workaround of using unique per-iteration keys (`result_1`, `result_2`, ...). +::: + +--- + #### Context Isolation and Merging: Iterations have important context management characteristics that ensure proper isolation and aggregation: @@ -571,9 +637,17 @@ Iterations have important context management characteristics that ensure proper - When all parallel iterations complete (fan-in), their context stores are **merged** - The merge uses `add_or_replace_context_store` reducer -- For duplicate keys across iterations, the **last value wins** (last iteration overwrites previous) +- **Default (overwrite)**: for duplicate keys across iterations, the **last value wins** (last iteration overwrites previous) +- **Accumulation mode**: when `append_to_context: true` is set on the iterating state, each iteration's output is **appended** to a list under the specified key — no values are lost - The merged context is then passed to the next state after iteration +**Choosing between overwrite and accumulation:** + +| Mode | Config | Result for key `output` after 3 iterations | +| ------------------- | -------------------------- | ----------------------------------------------- | +| Overwrite (default) | `append_to_context: false` | `output = "result_3"` (only last) | +| Accumulation | `append_to_context: true` | `output = ["result_1", "result_2", "result_3"]` | + **Message History Merging:** - Similarly, message histories from all iterations are also merged @@ -611,9 +685,9 @@ states: **Important Context Merging Notes:** - Iterations are isolated during execution but merged after completion -- Context keys set by multiple iterations will have only one final value (last wins) -- To preserve all iteration results, use unique keys (e.g., `result_1`, `result_2`) or aggregate into lists -- Message histories are fully preserved from all iterations +- By default, context keys set by multiple iterations will have only one final value (last wins) +- To preserve all iteration results, set `append_to_context: true` combined with `output_key` — the context key will accumulate all values as a list +- Message histories are fully preserved from all iterations regardless of the merge mode #### Important Notes: @@ -626,5 +700,7 @@ states: - Cannot combine `iter_key` with `state_ids` (parallel transitions) or `condition`/`switch` - Each iteration branch has isolated context and message history during execution - After all iterations complete, contexts and message histories are merged using LangGraph reducers +- Use `append_to_context: true` to accumulate all iteration outputs into a list; without it, only the last iteration's value is retained for duplicate keys +- `append_to_context: true` has no effect when `store_in_context: false` --- diff --git a/faq/how-do-i-collect-all-results-from-parallel-workflow-iterations.md b/faq/how-do-i-collect-all-results-from-parallel-workflow-iterations.md new file mode 100644 index 0000000..e2b4c34 --- /dev/null +++ b/faq/how-do-i-collect-all-results-from-parallel-workflow-iterations.md @@ -0,0 +1,31 @@ +# How do I collect all results from parallel workflow iterations? + +Use `append_to_context: true` together with `output_key` in the `next:` block of the +iterating state. This tells the workflow engine to accumulate each iteration's output into +a list rather than overwriting the previous value. + +```yaml +states: + - id: process-item + assistant_id: processor + task: "Analyze {{task}} and return a result object" + next: + state_id: aggregate + iter_key: items + output_key: all_results + append_to_context: true + + - id: aggregate + assistant_id: aggregator + task: "Summarize all results: {{all_results}}" + # {{all_results}} contains every iteration's output as a list +``` + +By default (`append_to_context: false`), when multiple parallel branches write to the +same context key, only the last branch's value is kept. Setting `append_to_context: true` +preserves all values in `context_store["all_results"]` as an ordered list. + +## Sources + +- [State Transitions — Iterative Transitions](https://docs.codemie.ai/user-guide/workflows/configuration/state-transitions) +- [Context Management — Context Control Flags](https://docs.codemie.ai/user-guide/workflows/configuration/context-management) diff --git a/faq/what-happens-to-context-keys-when-multiple-iterations-write-to-the-same-key.md b/faq/what-happens-to-context-keys-when-multiple-iterations-write-to-the-same-key.md new file mode 100644 index 0000000..d8a58f1 --- /dev/null +++ b/faq/what-happens-to-context-keys-when-multiple-iterations-write-to-the-same-key.md @@ -0,0 +1,21 @@ +# What happens to context keys when multiple iterations write to the same key? + +By default, when parallel `iter_key` iterations all write to the same context key, the +**last iteration's value wins** — earlier values are silently overwritten during the +fan-in merge. + +To preserve every iteration's output, set `append_to_context: true` on the iterating +state. With this flag, each branch appends its output to a list instead of overwriting: + +| Mode | Config | Result after 3 iterations writing to `output` | +| ------------------- | -------------------------- | ----------------------------------------------- | +| Overwrite (default) | `append_to_context: false` | `output = "result_3"` (only last) | +| Accumulation | `append_to_context: true` | `output = ["result_1", "result_2", "result_3"]` | + +Use `append_to_context: true` together with `output_key` to name the accumulation key. +The accumulated list is accessible via `{{output_key}}` in subsequent states. + +## Sources + +- [State Transitions — Context Isolation and Merging](https://docs.codemie.ai/user-guide/workflows/configuration/state-transitions) +- [Context Management — Context Control Flags](https://docs.codemie.ai/user-guide/workflows/configuration/context-management)