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 #1479 tasks 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

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Error Error Jun 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-zhuang force-pushed the claude/amazing-rubin-d8Xmr branch from e1fcb1d to ae8bf94 Compare June 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 main Jun 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants