Skip to content

Document iterate-until-stable loop pattern in conductor-mechanics skill (PR #221 follow-up) #222

Description

@PolyphonyRequiem

PR #221 (closed-loop step 9, apex-driver outer loop) introduced a new conductor pattern that is not currently documented in the conductor-mechanics skill:

Pattern: iterate-until-stable loop

Conductor's only built-in loop primitive is for_each over a fixed collection. PR #221's apex-driver outer loop needed an unbounded "keep dispatching waves until either (a) the apex is satisfied, (b) we've made no progress, or (c) we've hit a configured cap" loop. Implemented as:

  1. Loop counter as a temp-file side-channel. Each script step is a fresh PowerShell process, so per-iteration state has to live on disk. apex-driver.yaml writes apex-driver-iter-{apex_id} under [System.IO.Path]::GetTempPath() from an outer_loop_init step (resets to 0 once after declare_root), and outer_loop_evaluator increments it.
  2. Loop body as a graph cycle. outer_loop_evaluator.routes includes - to: build_worklist with when: \"{{ outer_loop_evaluator.output.decision == 'continue' }}\". This is an explicit cycle in the agent graph — conductor's strict-undefined / M3 rules do not forbid cycles, but lint-strict-undefined and reachability checks must continue to pass.
  3. 4-way decision route. Strict priority order via successive when: clauses on the same step (complete > cap > blocked > continue), with M4 catch-all defaulting to a non-loop terminal (terminal_apex_blocked) so a malformed envelope can't infinite-loop.
  4. Cap configurable via env var. POLYPHONY_APEX_MAX_DISPATCH_ITERATIONS overrides the default 10. The cap is a safety net, not a performance ceiling — state is on disk + ADO so resume after cap-hit is honest.

Why it belongs in conductor-mechanics

This is a runtime/plumbing pattern (not a design principle): a future workflow author who needs the same "iterate until stable" shape needs to know about the temp-file counter idiom, the cycle-with-defensive-catch-all rule, and the priority-ordered when: decision routing. Without docs they'll either reinvent it incompatibly or assume conductor has a primitive it doesn't.

Suggested home: a new reference like references/m10-graph-cycles.md (or a section under references/m04-routing-rules.md if cycles are considered a routing concern). Cross-link from polyphony-workflow-author / polyphony-actionable.

Concrete examples to cite

  • .conductor/registry/workflows/apex-driver.yamlouter_loop_init (counter reset), outer_loop_evaluator (counter increment + decision + cycle route), terminal_apex_iteration_cap / terminal_apex_blocked terminals.
  • .conductor/registry/workflows/apex-wave-dispatch.yamlaggregate_renegotiation extension producing the per-iteration progress counters the evaluator consumes.
  • .conductor/registry/tests/e2e-apex-driver.Tests.ps1 Section 2b — the structural assertions that pin the pattern (M4 catch-all defaults to a terminal, four decisions are present, every cycle node is reachable from entry).

Out of scope for this issue

  • Whether conductor itself should grow a first-class while / until-stable primitive. That's a conductor change, not a skill update. Capture the question separately if it's worth pursuing.
  • Concurrency safety of the temp-file counter (two parallel apex-driver invocations on the same apex_id would race). Documented as a known characteristic in the PR Closed-loop step 9: apex-driver outer loop (Option α) #221 body; revisit if/when apex-driver becomes multi-tenant per apex.

cc closed-loop-state-plan §3.4 + §4 PR #9.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions