feat(loops): the Program op-set + runProgram tree executor (worker- AND loop-layer parallelism) - #141
Merged
Merged
Conversation
…le topology language
architecture.md §1 describes an Agent atom whose act() emits a Program over
{sample, steer, fork, select, seq, stop}; the runtime shipped only the flat one-move-
per-round TopologyMove. This lands the Program as a real, composable, emittable artifact.
- Program<Task> ADT (sample/steer/fork/select/seq/stop), seq + fork compose.
- compileProgram(program) → TopologyPlanner: executes a composable program over the
EXISTING runLoop kernel (seq runs steps across rounds; fork/sample → a parallel
fanout round; select declares the winner; short-circuits on the first valid attempt).
- agentProgramPlanner(agent) — the recursive-atom bridge: act() emits a Program, the
kernel executes it.
- flattenProgram fails loud on empty fork/seq and a non-positive sample n.
Honest limit (encoded + documented): fork branches are LEAF tasks, so parallelism is
at the WORKER layer (N concurrent worker attempts per round, heterogeneous across
agentRuns). Parallel SUB-LOOPS / sub-agents (a fork branch that is itself a multi-round
program — the doc's "loop whose step contains a loop") need a tree-walking executor —
the next increment.
tsc + biome clean; tests/loops/program.test.ts (flattenProgram unit + 4 kernel
integration cases); 36/36 loops tests.
…lelism + dynamic workflows)
The Program op-set landed worker-layer parallelism (fork/sample → N concurrent
attempts in one fanout round of a single loop). This adds the LOOP layer: a new
parallel{branches: Program[]} op + a recursive executor that runs concurrent
SUB-LOOPS, so an agent can drive multiple loops / a dynamic workflow tree, not just
a per-round move queue.
- parallel{branches: Program[]}: N concurrent sub-loops, each its own multi-round
runLoop; a branch may itself be a seq/fork/parallel tree.
- runProgram(program, opts): runs the largest STRAIGHT-LINE subtree as ONE runLoop
(keeping the kernel's free history threading across seq rounds) and forks into
concurrent runLoops only at a parallel node — merging iterations (re-indexed),
cost, and tokens, then selecting a global winner with the kernel's OWN selector.
- runAgent(agent, rootTask, opts): drives the recursive Agent atom over the tree
executor (act() emits the program, runProgram executes the tree).
- isStraightLine / ProgramResult / RunProgramOptions exported; maxParallel bounds the
concurrent-branch count (caps multiplicative sandbox load).
- defaultSelectWinner exported from run-loop so merge-point selection is single-sourced,
not a forked copy of the kernel's argmax.
Honest seams (encoded + documented, fail-loud): compileProgram FAILS LOUD on parallel
(a per-round move queue can't express a sub-loop); a select placed after a parallel
boundary FAILS LOUD (cross-branch selection is the merge's job). Per-branch ADAPTIVE
re-planning (a branch that re-emits its own program from its own context each round)
is the next generalization; today a branch is a fixed sub-program.
Concurrency review: mapPool index/firstError mutations span no await (atomic); a
failing branch stops new scheduling but drains in-flight sub-loops and aborts siblings
via the shared ctx.signal (mirrors the kernel's runBatch); winner index is correct by
construction (recomputed over the merged set, never remapped).
Tests: a tracking sandbox client asserts maxActive===2 — PROVING branches overlap, not
just that the merge picks the right winner; merge re-indexing, the seq-of-fanout-then-
parallel workflow, maxParallel bound, and both fail-loud cases. 599/599 package tests,
tsc + biome clean.
drewstone
approved these changes
Jun 3, 2026
drewstone
left a comment
Contributor
There was a problem hiding this comment.
✅ Auto-approved tangletools PR — fc5f1243
This PR was opened by the trusted tangletools automation account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: tangletools_author · 2026-06-03T17:50:33Z
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.
Lands the
Programmodel fromarchitecture.md §1AND the recursiverunProgramexecutor — the runtime now expresses parallelism at both layers the architecture describes.Worker layer (one loop, one fanout round)
fork{branches: Task[]}/sample{n}→ N concurrent worker attempts in a singlerunLoop, round-robined acrossagentRuns(heterogeneous).compileProgram(program) → TopologyPlannerexecutes a straight-line program over the existing per-round kernel (free history threading acrossseq).Loop layer (concurrent sub-loops / dynamic workflows)
parallel{branches: Program[]}→ N concurrent sub-loops, each its own multi-roundrunLoop; a branch may itself be aseq/fork/paralleltree.runProgram(program, opts)runs the largest straight-line subtree as one loop (preserving threading) and forks into concurrent loops only at aparallelnode — merging iterations (re-indexed), cost, and tokens, selecting a global winner with the kernel's owndefaultSelectWinner(single-sourced).runAgentdrives the recursive Agent atom over the tree.Honest seams (encoded + fail-loud)
compileProgramthrows onparallel(a per-round move queue can't express a sub-loop).selectafter aparallelboundary throws (cross-branch selection is the merge's job).Verification
maxActive === 2— proving the sub-loops actually overlap, not just that the merge picks the right winner. Plus merge re-indexing, the seq-of-fanout-then-parallel workflow, themaxParallelbound, and both fail-loud cases.mapPoolindex/firstErrormutations span noawait(atomic); a failing branch stops new scheduling but drains in-flight sub-loops and aborts siblings via the sharedctx.signal(mirrors the kernel'srunBatch); winner index correct by construction.