Skip to content

feat(automation): structured loop container (ADR-0031) - #1482

Merged
os-zhuang merged 3 commits into
mainfrom
claude/amazing-rubin-d8Xmr
Jun 1, 2026
Merged

feat(automation): structured loop container (ADR-0031)#1482
os-zhuang merged 3 commits into
mainfrom
claude/amazing-rubin-d8Xmr

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Implements the foundation of ADR-0031 — structured control-flow constructs — covering issue #1479tasks 1 & 2 (spec-define the constructs + the loop container engine execution). Parallel/try-catch execution and BPMN mapping (tasks 3–5) are deliberately left as their own follow-up PRs, per the multi-agent discipline in AGENTS.md.

Representation decision: (B) nested sub-structure

ADR-0031 left the representation open (A: marker-delimited scoped regions vs B: nested mini-flow in config). This PR picks (B) — each container carries its body as a self-contained single-entry/single-exit region in config (config.body for loop, config.branches[] for parallel, config.try/config.catch for try_catch). Why:

  1. Well-formed by construction — a nested region is its own graph, so single-entry is intrinsic and there are no scope markers to balance/leak across; validation is local.
  2. Shared engine.ts traversal stays untouched — the container executor runs its body via a scoped runRegion(); the main DAG traverseNext never learns about scope markers (important given the multi-agent discipline around engine.ts). The container's ordinary out-edges remain the after-loop continuation, so the DAG invariant for ordinary edges holds.
  3. Cleaner AST for AI — the design center (ADR-0010/0011); ADR-0031 itself calls (B) "the cleaner long-term AST."

The ADR has been updated to record this decision.

What's in this PR

@objectstack/specautomation/control-flow.zod.ts (new)

  • LoopConfigSchema, ParallelConfigSchema, TryCatchConfigSchema (+ RetryPolicySchema), FlowRegionSchema.
  • Well-formedness: analyzeRegion / findRegionEntry (single-entry, single-exit, acyclic, edge integrity) and validateControlFlow(flow) — rejects malformed regions before a flow runs.
  • Canonical construct type ids (loop / parallel / try_catch) kept distinct from the BPMN interop node types.

@objectstack/service-automation — engine + loop executor

  • registerFlow() now calls validateControlFlow() after DAG cycle detection.
  • New AutomationEngine.runRegion() — executes a body region in the enclosing variable scope via a synthetic flow view (iterator var + body mutations are visible to the surrounding flow; not a subflow). Durable pause inside a region → clear error (follow-up).
  • Loop executor moved to builtin/loop-node.ts: replaces the no-op stub with a real iteration container — binds iteratorVariable/indexVariable, runs the body once per item, hard max-iteration guard (clamped to a ceiling). Back-compat: a loop with no config.body keeps legacy flat-graph behavior — the construct is additive.

Showcase / docs

  • BatchRemindersFlow demonstrates the loop container.
  • ADR-0031 records the representation decision; changeset added.

Tests

  • packages/spec/src/automation/control-flow.test.ts — 21 tests (schemas + region analysis + validateControlFlow).
  • packages/services/service-automation/src/builtin/loop-node.test.ts — 8 tests (iteration, multi-node body, empty collection, non-array failure, max-iteration guard, body-failure propagation, malformed-body rejection at register, legacy fallback).
  • Full suites pass: spec automation 284, service-automation 126, showcase 8. Spec tsc --noEmit clean.

Roadmap (#1479)

  • 1. Spec-define the constructs + pick representation (A vs B → B)
  • 2. Loop container — engine execution + e2e + showcase
  • 3. Parallel block — implicit-join execution
  • 4. Try/catch/retry — structured construct execution
  • 5. BPMN mapping ↔ structured constructs

Closes part of #1479.

https://claude.ai/code/session_012ti8cx3TkdiQdjCnZXZg2Q


Generated by Claude Code

Implement structured control-flow constructs per ADR-0031, tasks 1–2:
spec-define the constructs (picking representation B — nested sub-structure)
and ship the loop container engine execution that replaces the no-op stub.
spec (@objectstack/spec):
- new automation/control-flow.zod.ts: LoopConfig (config.body), ParallelConfig
(config.branches[], implicit join) and TryCatchConfig (try/catch/retry)
schemas; FlowRegion schema; region well-formedness analysis (analyzeRegion,
findRegionEntry) and validateControlFlow (single-entry/single-exit, acyclic;
bounded loop). Canonical construct type ids kept distinct from BPMN interop.
engine (@objectstack/service-automation):
- registerFlow() now rejects malformed control-flow regions before run
(validateControlFlow), preserving the DAG invariant for ordinary edges.
- new AutomationEngine.runRegion(): executes a body region in the enclosing
variable scope via a synthetic flow view, leaving the shared DAG traversal
untouched. Durable pause inside a region is a clear error (follow-up).
- loop executor moved to builtin/loop-node.ts: a real iteration container that
binds the iterator/index variables and runs the body once per item under a
hard max-iteration guard. Legacy flat-graph loops (no config.body) keep
working — the construct is additive.
showcase: BatchRemindersFlow demonstrates the loop container.
docs: ADR-0031 records the representation decision (B).
Parallel/try-catch engine execution and BPMN interop mapping are follow-ups
(#1479 tasks 3–5).
https://claude.ai/code/session_012ti8cx3TkdiQdjCnZXZg2Q
@vercel

vercelBot commented Jun 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specErrorErrorJun 1, 2026 7:17pm

Request Review

Mark the per-iteration region step-log merge as a tracked follow-up (#1479)
and re-run CI after the unrelated flaky redis contract-test timeout.
https://claude.ai/code/session_012ti8cx3TkdiQdjCnZXZg2Q
@os-zhuang
os-zhuangforce-pushed the claude/amazing-rubin-d8Xmr branch from e1fcb1d to ae8bf94CompareJune 1, 2026 19:02
@os-zhuang
os-zhuang marked this pull request as ready for review June 1, 2026 19:08
…d8Xmr
# Conflicts:
#	packages/services/service-automation/src/engine.ts
@os-zhuang
os-zhuang merged commit 60f9c45 into mainJun 1, 2026
2 of 3 checks passed
os-zhuang added a commit that referenced this pull request Jun 2, 2026
The structured control-flow constructs (loop/parallel/try-catch) and the BPMN
interop mapping are implemented and merged (#1482, #1489, #1499, #1500; docs
#1497). Flip ADR-0031 from Proposed to Accepted and record the implementing PRs
and the deferred follow-ups (#1504 BPMN XML plugin, #1505 region step logs).
https://claude.ai/code/session_012ti8cx3TkdiQdjCnZXZg2Q
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude