[Loom#112] workflows: add workflow-level loop_until#4
Merged
Conversation
When a workflow declares `loop_until: <expression>`, executeWorkflow now re-runs the entire workflow as a fresh WorkflowRun (same user_message, scoped nodeOutputs) until the expression evaluates true against the just-completed iteration's node outputs, or max_iterations is reached. Pause and failure short-circuit the loop. This is the second of two archon primitives the Loom coleam00#110 PRD needs: the picker -> implement -> done cycle in loom-execute-prd.yaml leans on it for clean self-termination once no slices remain. Reuses condition-evaluator.ts unchanged (Loom coleam00#112 acceptance). Existing executeWorkflow body is factored out into runWorkflowIteration; behavior is unchanged when loop_until is unset. Refs Loom#112 (slice 2 of Loom#110) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wtaisto
added a commit
that referenced
this pull request
May 23, 2026
When a workflow declares `loop_until: <expression>`, executeWorkflow now re-runs the entire workflow as a fresh WorkflowRun (same user_message, scoped nodeOutputs) until the expression evaluates true against the just-completed iteration's node outputs, or max_iterations is reached. Pause and failure short-circuit the loop. This is the second of two archon primitives the Loom coleam00#110 PRD needs: the picker -> implement -> done cycle in loom-execute-prd.yaml leans on it for clean self-termination once no slices remain. Reuses condition-evaluator.ts unchanged (Loom coleam00#112 acceptance). Existing executeWorkflow body is factored out into runWorkflowIteration; behavior is unchanged when loop_until is unset. Refs Loom#112 (slice 2 of Loom#110) Co-authored-by: Tai <tai@claricode.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
loom-execute-prd.yaml(Loom#115) needs a way for the whole workflow (picker → implement → done) to re-run until the picker reports an empty ready set, without hand-rolling termination state in a loop node prompt.workflow:invocation node from Loom#111 to give us a small, declarative orchestrator.loop_until: <expression>andmax_iterations: <int>fields onworkflowDefinitionSchema. Whenloop_untilis set,executeWorkflowrunsrunWorkflowIterationrepeatedly — freshWorkflowRunrow per iteration, sameuser_message, scopednodeOutputs— until the expression evaluates true against the just-completed iteration's node outputs, ormax_iterationsis reached. Reusescondition-evaluator.tsunchanged.when:parser changes. No node-level loop semantics changed. Workflows withoutloop_untilbehave identically to before.UX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
executor.tscondition-evaluator.tsloop_untilevaluationexecutor.tsstore.getCompletedDagNodeOutputsexecutor.tsdag-executor.tsschemas/workflow.tsLabel Snapshot
risk: lowsize: Sworkflowsworkflows:executorChange Metadata
featureworkflowsLinked Issue
Validation Evidence (required)
executor-loop-until.test.tscovering termination-on-true, max_iterations cap, default-to-20, fresh-run isolation, condition-evaluator reuse, unparseable-expression fail-fast, pause short-circuit, failure short-circuit, and parity (no-op whenloop_untilunset). All 31 existingexecutor.test.tstests still pass — no regressions.bun run validateat repo root (not run; verified the workflows package end-to-end).Security Impact (required)
NoNoNoNoCompatibility / Migration
Yes— both new schema fields are optional; absentloop_untilpreserves the prior single-run behavior exactly.NoNoHuman Verification (required)
loop_untiltrue after 2 iterations (test).max_iterations: 3cap fails the workflow with a descriptive error (test).max_iterationsis 20 (test runs 20 iterations on an unsatisfiable expression).createWorkflowRuncall with monotonically-increasing run ids (test).{ empty, count, next }) feed condition-evaluator's dot notation (test).loop_untilhalts after the first iteration withunparseablein the error message (test).loom-execute-prd.yamlflow — that's Loom#115 and depends on this PR landing first.Side Effects / Blast Radius (required)
executor.tsis the single entry to all workflow runs. Workflows that do not declareloop_untilroute through the samerunWorkflowIterationbody as before (a thin wrapper, not a refactor of the body itself).loop_untilto an expression that always evaluates false (e.g. references a missing node), the workflow will burnmax_iterationsruns before failing. Mitigated bymax_iterationsdefaulting to 20 (bounded blast).workflow.loop_until_iteration_continuing; satisfaction logsworkflow.loop_until_satisfied; cap logsworkflow.loop_until_max_iterations_exceeded. All include workflow name and iteration count.Rollback Plan (required)
loop_untilwill fail Zod validation cleanly (unknown field) once reverted, but no in-flight runs are affected mid-execution.workflow.loop_until_unparseableorworkflow.loop_until_max_iterations_exceededlog lines; user-visible error message withloop_untilsubstring.Risks and Mitigations
loop_untilreferencing a node id that does not exist innodes:.max_iterationscap and fails with a clear message naming the expression. Future improvement (out of scope here): static validation at workflow load time that$<id>.outputreferences resolve to declared nodes.🤖 Generated with Claude Code