@tangle-network/agent-runtime 0.87.0 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,7 @@ The driver owns strategy.
30
30
  | Review from several lenses | `panel` |
31
31
  | Simulated user/product eval | `defineConversation` + `runConversation` |
32
32
  | Dynamic topology / drivers of drivers | `Scope` or sandbox driver + `createCoordinationTools` |
33
+ | **A coded multi-round loop spawned + steered like a worker** | `defineLoop` + `loopChild` (the loop atom) |
33
34
  | Mutate a shared repo | git branch/clone loop with typed merge outcomes |
34
35
 
35
36
  If a fixed combinator solves it, do not use a dynamic driver.
@@ -114,6 +115,78 @@ When the driver lives in a sandbox, expose the same verbs through
114
115
  `steer_worker`, `list_questions`, `answer_question`, `ask_parent`, `stop`, and
115
116
  optional analyst tools.
116
117
 
118
+ ## The Loop Atom — a coded loop spawned like a worker
119
+
120
+ When the loop itself is the reusable unit (a research loop, a verify loop, an
121
+ evolve loop), make it a spawnable atom instead of a hand-driver. `defineLoop`
122
+ authors the body; the runtime owns the round ceiling, the conserved budget, the
123
+ gate, and steer-between-rounds. A supervisor spawns / observes / steers it with
124
+ the SAME coordination verbs as a worker.
125
+
126
+ Two ways to author the round. **`agents`** — a MULTI-AGENT loop as a declarative
127
+ CHAIN (the common case): an ordered list of named agents piped each round,
128
+ `task -> agents[0] -> agents[1] -> ... -> out`, each agent's return feeding the next
129
+ as `prior`. "Two agents" is self-evident from the list — no bespoke `runTwoAgent...`
130
+ function. **`round`** — freeform code for any other topology (fan-out, dynamic
131
+ routing). Provide exactly one.
132
+
133
+ ```ts
134
+ // A two-agent research loop as a CHAIN: proposer drafts, verifier checks the draft.
135
+ const research = defineLoop('research', {
136
+ maxRounds: 3,
137
+ agents: [
138
+ { name: 'proposer', run: async ({ scope, steer }, _prior) => {
139
+ const w = scope.spawn(researcher, { steer }, { budget: perRound, label: 'propose' })
140
+ if (!w.ok) throw new Error(w.reason)
141
+ return await scope.next() // the draft
142
+ } },
143
+ { name: 'verifier', run: async ({ scope }, draft) => {
144
+ const w = scope.spawn(verifier, { draft }, { budget: perRound, label: 'verify' })
145
+ if (!w.ok) throw new Error(w.reason)
146
+ return await scope.next() // the verified result -> round out
147
+ } },
148
+ ],
149
+ check: (out) => readinessPasses(out), // the deployable completion oracle
150
+ })
151
+
152
+ // Spawn it exactly like a worker (role:'loop' resolves to the loop-executor).
153
+ const r = scope.spawn(loopChild(research, journal), task, { budget, label: 'research-loop' })
154
+ // Wire once at the top: createInMemoryRunContext({ withDriver: true, withLoop: true }).
155
+ ```
156
+
157
+ TOPOLOGY: `agents` is a sequential CHAIN only. It is NOT parallel and NOT a graph —
158
+ the loop's nested `scope.next()` is one shared queue, so parallel agents would steal
159
+ each other's settlements. For fan-out (a panel of critics, best-of-N), dynamic
160
+ routing, or any non-linear shape, use the freeform `round`: spawn every handle first,
161
+ then drain N times, or branch on the data yourself. `fanout`/`panel`/`pipeline` cover
162
+ the reactive-layer parallel case.
163
+
164
+ Rules: the loop is code, not the model's judgment — that is what makes maxRounds,
165
+ the budget, and the gate ENFORCED rather than hoped-for. Give the loop a real
166
+ `check` (an executable oracle, never a self-judged score). Budget nests: the pool
167
+ reserves each spawn's full ceiling until it settles, so pool > loop > per-round.
168
+
169
+ ### Codemode — an LLM authors the loop at runtime
170
+
171
+ When the supervisor should WRITE the loop for a novel goal (not pick a hand-built
172
+ one), use `authorLoop` — the codemode seam over the atom, the sibling of
173
+ `authorStrategy`. It shows the model `loopAuthorContract` (the exact `defineLoop`
174
+ module shape, exported so a skill/GEPA pass can evolve it), extracts the fenced
175
+ module, lints it with `assertStrategyContract` (only the loops import; no
176
+ require/eval/fetch/process/node builtins), writes it, dynamic-imports it, and
177
+ validates the default export is a `LoopDef` — ready for `loopChild`.
178
+
179
+ ```ts
180
+ const { loop } = await authorLoop({ chat, goal, maxRounds, outDir, fallbackModel })
181
+ const spawned = scope.spawn(loopChild(loop, journal), task, { budget, label: 'authored-loop' })
182
+ ```
183
+
184
+ Safety is structural, same as `authorStrategy`: the authored body can be WRONG
185
+ but cannot overspend (conserved pool), cannot skip the check (the runtime gates
186
+ it), and cannot reach outside the loops surface (the lint). Do not build a
187
+ per-product loop-code generator or a VM sandbox around authored loops — `authorLoop`
188
+ is that seam.
189
+
117
190
  ## Role Boundaries
118
191
 
119
192
  - **Verifier**: executable shippability gate; controls accept/reject.