Skip to content

docs(skills/swamp-workflow): document when to use nested workflows#1165

Merged
stack72 merged 1 commit intomainfrom
worktree-sequential-tinkering-umbrella
Apr 11, 2026
Merged

docs(skills/swamp-workflow): document when to use nested workflows#1165
stack72 merged 1 commit intomainfrom
worktree-sequential-tinkering-umbrella

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented Apr 11, 2026

Summary

Adds a "When to Use Nested Workflows" section to the swamp-workflow skill covering the three cases where a child workflow is the right shape — most importantly, the one that's invisible until you hit it:

forEach over an async list. forEach.in is evaluated synchronously at expansion time (src/domain/workflows/execution_service.ts:1543), so CEL expressions that return a Promise — data.latest(), data.findByTag(), data.findBySpec() — never resolve in that position, and the step fails with a misleading forEach.in must evaluate to an array or object, got: object (the "object" is the unresolved Promise).

Task inputs: ARE awaited for both model_method and workflow tasks (execution_service.ts:329 and :1964, both using evalService.evaluateData which calls celEvaluator.evaluateAsync). So the canonical fix is to resolve the async call in the parent's task.inputs and let the child iterate over a plain inputs.<name>:

# parent — task.inputs awaits data.latest()
- name: download
  task:
    type: workflow
    workflowIdOrName: download-episodes
    inputs:
      episodes: ${{ data.latest("dedup", "current").attributes.episodes }}
# child — forEach over pre-resolved inputs array
- name: download-${{ self.ep.show }}
  forEach:
    item: ep
    in: ${{ inputs.episodes }}
  task: { ... }

Changes

  • references/nested-workflows.md — new top section covering three "when to nest" cases (async list → forEach, reusable sub-process, independent cadence) and an explicit "when NOT to nest" list (use dependsOn for ordering, direct CEL for shared values, 10-level nesting cap).
  • references/expressions-and-foreach.md — new "forEach.in Cannot Await Promises" subsection right after the forEach variables table, with a pointer to nested-workflows.md#when-to-use-nested-workflows so readers landing on the forEach doc find the escape hatch.
  • SKILL.md — reference index pointer updated to surface "when to split a workflow into parent + child (including the async-list-into-forEach pattern)".

Follow-up issue filed for the misleading got: object error message itself (the docs fix the user-facing workaround; the error message fix is orthogonal).

Test Plan

  • deno fmt --check passes
  • deno lint passes
  • CI verifies deno run test
  • Skill content renders correctly in future sessions (verified by re-loading the swamp-workflow skill)

🤖 Generated with Claude Code

Add a "When to Use Nested Workflows" section to the swamp-workflow skill
covering the three cases where a child workflow is the right shape —
most importantly, forEach over an async list (`data.latest()`,
`data.findByTag()`, etc.). `forEach.in` is evaluated synchronously so
promise-returning CEL functions never resolve in that position and the
step fails with a misleading "got: object" error. Task `inputs:` ARE
awaited for both model_method and workflow tasks, so the canonical fix
is to resolve the async call in the parent's task.inputs and let the
child iterate over a plain `inputs.<name>`.

Also cross-references the new section from expressions-and-foreach.md
so readers landing there find the escape hatch, and updates the
SKILL.md reference pointer so the topic is discoverable from the index.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Code Review

Blocking Issues

None.

Suggestions

  1. Pre-existing: SKILL.md exceeds 500-line limit — The file is currently 557 lines (558 after this PR), above the 500-line cap in CLAUDE.md. This is not introduced by this PR (net +1 line), but worth a future cleanup pass to move more content into references/ files.

Notes

  • Technical accuracy verified: The core claims about forEach.in synchronous evaluation (celEvaluator.evaluate() at execution_service.ts:1553) and async task.inputs evaluation (evalService.evaluateData() at lines 334 and 1974) are confirmed against the source code.
  • Cross-references are well-placed: The pointer from expressions-and-foreach.md to nested-workflows.md#when-to-use-nested-workflows ensures readers landing on the forEach doc find the escape hatch for the async limitation.
  • "When NOT to nest" section is a good addition — it prevents over-use of the pattern.
  • No code changes, no import boundary concerns, no security issues. DDD review not applicable (docs only).

@stack72 stack72 merged commit 50b5a0d into main Apr 11, 2026
10 checks passed
@stack72 stack72 deleted the worktree-sequential-tinkering-umbrella branch April 11, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant