@tangle-network/agent-runtime 0.60.0 → 0.61.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.
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -2
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/{chunk-IN7WHMGZ.js → chunk-5V343QPB.js} +47 -7
- package/dist/chunk-5V343QPB.js.map +1 -0
- package/dist/chunk-7IXF3VUJ.js +59 -0
- package/dist/chunk-7IXF3VUJ.js.map +1 -0
- package/dist/{chunk-ZWGEA722.js → chunk-AU5MCNHO.js} +2 -2
- package/dist/chunk-IBRJTG7O.js +475 -0
- package/dist/chunk-IBRJTG7O.js.map +1 -0
- package/dist/{chunk-MMDIORZY.js → chunk-Q5R33I7Y.js} +4 -23
- package/dist/chunk-Q5R33I7Y.js.map +1 -0
- package/dist/{chunk-45D64J7B.js → chunk-VCOT7XEQ.js} +190 -99
- package/dist/chunk-VCOT7XEQ.js.map +1 -0
- package/dist/coder-DD5J5Onk.d.ts +52 -0
- package/dist/coordination-k29badiX.d.ts +381 -0
- package/dist/{delegates-C94qchkz.d.ts → delegates-CWMv_rKL.d.ts} +649 -21
- package/dist/index.d.ts +9 -6
- package/dist/index.js +9 -9
- package/dist/intelligence.d.ts +1 -1
- package/dist/{loop-runner-bin-Noz7P-mS.d.ts → loop-runner-bin-Bqt0hiNY.d.ts} +46 -12
- package/dist/loop-runner-bin.d.ts +7 -4
- package/dist/loop-runner-bin.js +4 -4
- package/dist/loops.d.ts +7 -6
- package/dist/loops.js +17 -6
- package/dist/mcp/bin.js +10 -10
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +12 -12
- package/dist/mcp/index.js +7 -7
- package/dist/{openai-tools-d4GKwgya.d.ts → openai-tools-CA2N3-Ak.d.ts} +1 -1
- package/dist/profiles.d.ts +7 -9
- package/dist/profiles.js +5 -7
- package/dist/profiles.js.map +1 -1
- package/dist/{run-loop-CcqfR_gy.d.ts → run-loop-D3PwlG7J.d.ts} +1 -1
- package/dist/runtime.d.ts +30 -1046
- package/dist/runtime.js +17 -6
- package/dist/{types-CUzjRFZ3.d.ts → types-Crxftafi.d.ts} +2 -2
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +1 -2
- package/dist/workflow.js.map +1 -1
- package/dist/worktree-fanout-CBULEoVe.d.ts +1098 -0
- package/package.json +1 -1
- package/dist/chunk-45D64J7B.js.map +0 -1
- package/dist/chunk-4FEUFYOY.js +0 -282
- package/dist/chunk-4FEUFYOY.js.map +0 -1
- package/dist/chunk-7QYOXFCD.js +0 -293
- package/dist/chunk-7QYOXFCD.js.map +0 -1
- package/dist/chunk-IN7WHMGZ.js.map +0 -1
- package/dist/chunk-MMDIORZY.js.map +0 -1
- package/dist/coder-CybltHEm.d.ts +0 -163
- package/dist/coordination-Biw19JzN.d.ts +0 -944
- /package/dist/{chunk-ZWGEA722.js.map → chunk-AU5MCNHO.js.map} +0 -0
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
import { AgentProfile, BackendType } from '@tangle-network/sandbox';
|
|
2
|
+
import { AnalystFinding, DefaultVerdict } from '@tangle-network/agent-eval';
|
|
3
|
+
import { h as AgentSpec, E as ExecutorRegistry, B as Budget, A as Agent, i as SpawnJournal, g as ResultBlobStore, j as RootHandle, k as SupervisedResult, N as NodeId, l as Settled, m as Spend, S as Scope, n as Executor, G as GitRunner } from './delegates-CWMv_rKL.js';
|
|
4
|
+
import { I as Iteration } from './types-Crxftafi.js';
|
|
5
|
+
import { R as RuntimeHooks } from './runtime-hooks-C7JwKb9E.js';
|
|
6
|
+
import { L as LocalHarness, r as runLocalHarness } from './local-harness-BE_h8szs.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @experimental
|
|
10
|
+
*
|
|
11
|
+
* The personify layer — the "act like X" knob on top of the recursive keystone.
|
|
12
|
+
*
|
|
13
|
+
* The keystone (`src/loops/supervise/`) is pure STRUCTURE: a recursive `Agent` atom inside
|
|
14
|
+
* a budget-conserving `Scope`, an `ExecutorRegistry` mapping an `AgentSpec` to a runtime,
|
|
15
|
+
* and a `Supervisor` that runs a root agent to a typed `SupervisedResult`. It carries no
|
|
16
|
+
* CONTENT — no model, no prompt, no goal framing, no notion of "who this loop is".
|
|
17
|
+
*
|
|
18
|
+
* This layer adds exactly that content seam without inventing a second engine:
|
|
19
|
+
* - A `Persona` is a thin record: the root `AgentSpec` (profile + harness + optional BYO
|
|
20
|
+
* executor), a root `directive` (the goal framing handed to the chosen shape), a
|
|
21
|
+
* `context` blob (who the loop is acting as), and the executor seams the registry needs.
|
|
22
|
+
* `definePersona` builds it; it is data, not behavior.
|
|
23
|
+
* - A `LoopShape` is a reusable act-body FACTORY: `(ctx: ShapeContext) => Agent`. The shape
|
|
24
|
+
* owns the STRUCTURE (how to decompose / fan out / verify / synthesize); the persona's
|
|
25
|
+
* content parameterizes it. A new shape is ONE file + one `registerShape` call.
|
|
26
|
+
* - `Outcome<D>` is the contract every shape synthesizes into: a finished deliverable OR a
|
|
27
|
+
* list of concrete blockers — "100% done or 100%-defined blockers", never a vague middle.
|
|
28
|
+
*
|
|
29
|
+
* Layering: this module imports ONLY keystone runtime types (`./supervise/types`) and the
|
|
30
|
+
* substrate `AgentProfile`/`BackendType`. It typechecks standalone — no impl, no engine.
|
|
31
|
+
* Extensibility is structural: `Persona` carries an open `extensions` bag so a later
|
|
32
|
+
* world-model / memory field is additive (a new optional key), never a breaking change.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The terminal contract Drew wants: a loop returns a FINISHED deliverable, or the concrete
|
|
37
|
+
* list of blockers that stopped it — never a half-done best-effort coercion. A `blocked`
|
|
38
|
+
* outcome with an empty `blockers` list is a contract violation (a shape that can't finish
|
|
39
|
+
* MUST name why); impls fail loud on it rather than emitting a vacuous block.
|
|
40
|
+
*
|
|
41
|
+
* `Outcome` is the `Out` type a personified `Agent`/`Supervisor` is parameterized by, so the
|
|
42
|
+
* keystone's typed `SupervisedResult<Outcome<D>>` carries it end to end with no coercion.
|
|
43
|
+
*/
|
|
44
|
+
type Outcome<D> = {
|
|
45
|
+
kind: 'done';
|
|
46
|
+
deliverable: D;
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'blocked';
|
|
49
|
+
blockers: string[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The "act like X" record. A thin composition over the keystone's `AgentSpec`: it pairs the
|
|
53
|
+
* root spec (the executor mapping for the root agent the shape builds) with the CONTENT a
|
|
54
|
+
* shape consumes — the goal framing (`directive`) and who the loop is acting as (`context`).
|
|
55
|
+
*
|
|
56
|
+
* The framework never reads `directive`/`context` semantically; it threads them to the shape
|
|
57
|
+
* verbatim through `ShapeContext`. This is the rule the mandate names: the FRAMEWORK is
|
|
58
|
+
* structure, the PERSONA carries model/prompt/tools/directive. No model name, prompt, or
|
|
59
|
+
* persona string is ever hardcoded in a shape or the engine.
|
|
60
|
+
*
|
|
61
|
+
* `D` is the deliverable type this persona's loops produce; it flows into `Outcome<D>`.
|
|
62
|
+
*/
|
|
63
|
+
interface Persona<D = unknown> {
|
|
64
|
+
/** Stable persona name — used as the trace/journal label root, never as content. */
|
|
65
|
+
readonly name: string;
|
|
66
|
+
/**
|
|
67
|
+
* The root agent's executor mapping (profile + harness + optional BYO executor). The
|
|
68
|
+
* shape's root `Agent` carries THIS as its `executorSpec`; child specs the shape spawns
|
|
69
|
+
* are derived from / resolved against the same persona registry (see `ShapeContext`).
|
|
70
|
+
*/
|
|
71
|
+
readonly root: AgentSpec;
|
|
72
|
+
/** The goal framing handed to the shape — the "what to achieve", not "how". */
|
|
73
|
+
readonly directive: string;
|
|
74
|
+
/** Who the loop is acting as — the opaque persona context blob the shape may inject into
|
|
75
|
+
* child tasks. Opaque to the framework; only the persona's profiles/prompts interpret it. */
|
|
76
|
+
readonly context: PersonaContext;
|
|
77
|
+
/**
|
|
78
|
+
* The executor seams (router endpoint+key, sandbox client, cli bin) the built-in runtimes
|
|
79
|
+
* read off `ExecutorContext.seams`, OR a fully pre-configured registry. The supervisor
|
|
80
|
+
* threads an EMPTY seam bag to the root scope, so a persona that uses built-in metered
|
|
81
|
+
* runtimes MUST supply a registry whose factories close over their seams (or BYO executors
|
|
82
|
+
* on each `AgentSpec`). Carried here so `runPersonified` can build `SupervisorOpts.executors`.
|
|
83
|
+
*/
|
|
84
|
+
readonly executors: PersonaExecutors;
|
|
85
|
+
/**
|
|
86
|
+
* Forward-compatible extension bag — a later world-model / memory / tool-budget field is an
|
|
87
|
+
* additive key here, never a breaking change to the `Persona` shape. Opaque to the engine.
|
|
88
|
+
*/
|
|
89
|
+
readonly extensions?: Readonly<Record<string, unknown>>;
|
|
90
|
+
/** Phantom: binds the persona to its deliverable type so `runPersonified` infers `D` from
|
|
91
|
+
* the persona and the chosen shape must agree. Type-only — never present at runtime. */
|
|
92
|
+
readonly __deliverable?: D;
|
|
93
|
+
}
|
|
94
|
+
/** The persona context blob — who the loop is acting as. Open by intent: a persona names its
|
|
95
|
+
* own role/audience/constraints; the framework treats it as opaque content. */
|
|
96
|
+
interface PersonaContext {
|
|
97
|
+
/** The role the loop embodies ("senior staff engineer", "equity research analyst", …). */
|
|
98
|
+
readonly role: string;
|
|
99
|
+
/** Optional freeform framing the persona's prompts/profiles consume. */
|
|
100
|
+
readonly notes?: string;
|
|
101
|
+
/** Open content bag — persona-specific fields a shape's child tasks may carry. */
|
|
102
|
+
readonly [key: string]: unknown;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* How a persona supplies executor resolution. Either a pre-built registry (factories already
|
|
106
|
+
* closed over their seams) OR the raw seam bag the engine uses to construct a registry +
|
|
107
|
+
* thread the seams onto each spawn. Exactly one is required — fail loud if neither is set.
|
|
108
|
+
*/
|
|
109
|
+
interface PersonaExecutors {
|
|
110
|
+
/** A registry whose factories already capture their seams. Highest precedence. */
|
|
111
|
+
readonly registry?: ExecutorRegistry;
|
|
112
|
+
/** Raw seams to thread onto built-in runtimes (`router`/`sandbox`/`cli` keys). */
|
|
113
|
+
readonly seams?: Readonly<Record<string, unknown>>;
|
|
114
|
+
}
|
|
115
|
+
/** The minimal input to build a `Persona`. Mirrors `Persona` but lets the builder default
|
|
116
|
+
* the executors-supplied invariant check and freeze the record. */
|
|
117
|
+
interface DefinePersonaInput<D = unknown> {
|
|
118
|
+
readonly name: string;
|
|
119
|
+
readonly root: AgentSpec;
|
|
120
|
+
readonly directive: string;
|
|
121
|
+
readonly context: PersonaContext;
|
|
122
|
+
readonly executors: PersonaExecutors;
|
|
123
|
+
readonly extensions?: Readonly<Record<string, unknown>>;
|
|
124
|
+
/** Phantom: pins the input's deliverable type so `definePersona<D>` returns a `Persona<D>`
|
|
125
|
+
* the caller's shape must agree with. Type-only — never supplied at a call site. */
|
|
126
|
+
readonly __deliverable?: D;
|
|
127
|
+
}
|
|
128
|
+
/** Builds a frozen `Persona`, failing loud on the executors-supplied invariant (neither a
|
|
129
|
+
* registry nor seams = an unresolvable persona). Pure — no I/O, no engine. */
|
|
130
|
+
type DefinePersona = <D = unknown>(input: DefinePersonaInput<D>) => Persona<D>;
|
|
131
|
+
/**
|
|
132
|
+
* Budget knobs a shape reads to size its fanout/children WITHOUT owning the conserved pool.
|
|
133
|
+
* The root budget lives on `SupervisorOpts.budget`; the shape only needs the per-child
|
|
134
|
+
* sizing hints + the fanout width it is allowed to open. All ceilings — the pool reserves
|
|
135
|
+
* against them and fails closed, so an over-eager shape can never overspend.
|
|
136
|
+
*/
|
|
137
|
+
interface ShapeBudget {
|
|
138
|
+
/** Per-child spawn budget the shape reserves for each leaf/sub-loop it opens. */
|
|
139
|
+
readonly perChild: Budget;
|
|
140
|
+
/** Max children a fanout step may open in one round (the shape's structural width). */
|
|
141
|
+
readonly fanout: number;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The construction context a `LoopShape` factory receives. Carries the persona's resolved
|
|
145
|
+
* executor seams + the budget knobs, plus the ONE helper a shape needs to spawn a child
|
|
146
|
+
* through the keystone: `spawnChild` resolves an `AgentSpec` (or a persona-derived child
|
|
147
|
+
* profile) into an `Agent` the shape hands to `scope.spawn`. The shape never touches the
|
|
148
|
+
* registry directly — it asks the context, keeping resolution single-sourced.
|
|
149
|
+
*/
|
|
150
|
+
interface ShapeContext<D = unknown> {
|
|
151
|
+
readonly persona: Persona<D>;
|
|
152
|
+
readonly budget: ShapeBudget;
|
|
153
|
+
/**
|
|
154
|
+
* Wrap an `AgentSpec` into a leaf `Agent` carrying it as `executorSpec`, so the shape can
|
|
155
|
+
* `scope.spawn(spawnChild(spec), task, opts)`. `name` labels the child for traces. The
|
|
156
|
+
* returned agent's `act` is never invoked by the keystone (it is spawned, not run) — the
|
|
157
|
+
* spec drives the resolved `Executor`; `act` exists only to satisfy the `Agent` shape.
|
|
158
|
+
*/
|
|
159
|
+
spawnChild(name: string, spec: AgentSpec): Agent<unknown, Outcome<D>>;
|
|
160
|
+
/** Derive a child `AgentSpec` from the persona's root spec with an overridden profile —
|
|
161
|
+
* the seam a shape uses to give a worker a narrower role/prompt than the root persona. */
|
|
162
|
+
childSpec(profile: AgentProfile, harness?: BackendType | null): AgentSpec;
|
|
163
|
+
/** The scope analyst (selector≠judge firewall) the combinator steers from. Absent ⇒ the
|
|
164
|
+
* dormant default (empty findings → gates read deliverables/state only). */
|
|
165
|
+
readonly analyst?: ScopeAnalyst<D>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A reusable act-body factory. Given the persona's content + seams (`ShapeContext`), it
|
|
169
|
+
* returns the root `Agent<Task, Outcome<D>>` whose `act` decomposes the task, fans out
|
|
170
|
+
* children through `scope.spawn`, verifies/selects across their settlements (selector≠judge:
|
|
171
|
+
* via `settledToIteration` + `defaultSelectWinner`, never re-ranking behind the driver), and
|
|
172
|
+
* synthesizes the terminal `Outcome<D>`. The shape is STRUCTURE; the persona is CONTENT.
|
|
173
|
+
*/
|
|
174
|
+
type LoopShape<Task, D> = (ctx: ShapeContext<D>) => Agent<Task, Outcome<D>>;
|
|
175
|
+
/**
|
|
176
|
+
* The open shape registry — the extension point that makes a new loop-shape ONE file + one
|
|
177
|
+
* `registerShape` call with zero edits elsewhere. `resolve` returns a typed outcome (inspect
|
|
178
|
+
* `succeeded` before `value`); `register` fails loud on a duplicate name.
|
|
179
|
+
*/
|
|
180
|
+
interface ShapeRegistry {
|
|
181
|
+
register<Task, D>(name: string, factory: LoopShape<Task, D>): void;
|
|
182
|
+
resolve<Task, D>(name: string): {
|
|
183
|
+
succeeded: true;
|
|
184
|
+
value: LoopShape<Task, D>;
|
|
185
|
+
} | {
|
|
186
|
+
succeeded: false;
|
|
187
|
+
error: string;
|
|
188
|
+
};
|
|
189
|
+
/** The registered shape names — for diagnostics + a fail-loud "unknown shape" message. */
|
|
190
|
+
names(): string[];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The end-to-end entrypoint. Builds the persona's root `Agent` from the chosen shape, then
|
|
194
|
+
* runs it through a fresh `createSupervisor` over the persona's executors + the supplied
|
|
195
|
+
* budget/journal/blobs. Returns the keystone's typed `SupervisedResult<Outcome<D>>` — a
|
|
196
|
+
* `winner` carries the synthesized `Outcome<D>`; a `no-winner` is never coerced into one.
|
|
197
|
+
*
|
|
198
|
+
* `shape` is either a resolved `LoopShape` or a registered shape NAME (resolved through the
|
|
199
|
+
* default registry). The journal/blobs default to in-memory impls in the engine when omitted
|
|
200
|
+
* (durable FS impls are passed explicitly for a persisted run).
|
|
201
|
+
*/
|
|
202
|
+
interface RunPersonifiedOptions<Task, D> {
|
|
203
|
+
readonly persona: Persona<D>;
|
|
204
|
+
/** A resolved shape factory OR a registered shape name. */
|
|
205
|
+
readonly shape: LoopShape<Task, D> | string;
|
|
206
|
+
readonly task: Task;
|
|
207
|
+
readonly budget: Budget;
|
|
208
|
+
/** Per-child sizing + fanout width handed to the shape. Defaults derive from `budget`. */
|
|
209
|
+
readonly shapeBudget?: Partial<ShapeBudget>;
|
|
210
|
+
/** Trace/journal root key. Defaults to the persona name + a run discriminator in the engine. */
|
|
211
|
+
readonly runId?: string;
|
|
212
|
+
readonly journal?: SpawnJournal;
|
|
213
|
+
readonly blobs?: ResultBlobStore;
|
|
214
|
+
/** Runtime recursion-depth ceiling, paired with the conserved pool. */
|
|
215
|
+
readonly maxDepth?: number;
|
|
216
|
+
/** OTP intensity breaker bounds, forwarded to the supervisor verbatim. */
|
|
217
|
+
readonly maxRestarts?: number;
|
|
218
|
+
readonly withinMs?: number;
|
|
219
|
+
/** A live root handle to attach (view/signal/abort) before the run starts. */
|
|
220
|
+
readonly handle?: RootHandle<Outcome<D>>;
|
|
221
|
+
readonly now?: () => number;
|
|
222
|
+
readonly signal?: AbortSignal;
|
|
223
|
+
/** Optional scope analyst threaded into the shape's ShapeContext so loopUntil/widen steer
|
|
224
|
+
* on trace-derived findings instead of the dormant empty default. */
|
|
225
|
+
readonly analyst?: ScopeAnalyst<D>;
|
|
226
|
+
/**
|
|
227
|
+
* Lifecycle stream sink, forwarded to `SupervisorOpts.hooks` so the root `Scope`'s
|
|
228
|
+
* `agent.spawn`/`agent.child` events flow to an observer (e.g. the Intelligence SDK's
|
|
229
|
+
* trace export). Absent ⇒ no stream (the run is silent, as today).
|
|
230
|
+
*/
|
|
231
|
+
readonly hooks?: RuntimeHooks;
|
|
232
|
+
}
|
|
233
|
+
/** The composed run signature. */
|
|
234
|
+
type RunPersonified = <Task, D>(options: RunPersonifiedOptions<Task, D>) => Promise<SupervisedResult<Outcome<D>>>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @experimental
|
|
238
|
+
*
|
|
239
|
+
* The RSI-wave type surface — the FROZEN contracts the wave's Core + Compose build to.
|
|
240
|
+
*
|
|
241
|
+
* The keystone (`../supervise/`) is pure execution structure: a recursive `Agent` atom in a
|
|
242
|
+
* budget-conserving `Scope`, run to a typed `SupervisedResult` by a `Supervisor`. The persona
|
|
243
|
+
* layer (`./types`, `./persona`) adds the "act like X" content seam (`Persona` = `AgentSpec` +
|
|
244
|
+
* `directive` + `context`, `LoopShape = (ctx) => Agent`, `Outcome<D>`). This module freezes the
|
|
245
|
+
* remaining four wave seams ON TOP of those — and nothing more:
|
|
246
|
+
*
|
|
247
|
+
* 1. GENERIC COMBINATORS — the content-free act-library. Five composable shapes
|
|
248
|
+
* (`pipeline`/`fanout`/`loopUntil`/`panel`/`verify`) plus the streaming widener (G5). Each
|
|
249
|
+
* is a `CombinatorShape` (a `LoopShape` whose `Agent.act` runs the combinator over `Scope`),
|
|
250
|
+
* so a combinator IS just a `LoopShape` — no new engine type. The SHAPE is here; the DOMAIN
|
|
251
|
+
* (model, prompt, role) stays on the `Persona`. There is no "research" or "code" combinator:
|
|
252
|
+
* a research sweep is `fanout` under a research persona; a build is `pipeline` under a coder.
|
|
253
|
+
* 2. ANALYST-ON-SCOPE (G1, a PORT) — `ScopeAnalyst` carries the round-synchronous driver's
|
|
254
|
+
* analyze→findings→steer wire (dynamic.ts) across to the reactive `Scope`, behind
|
|
255
|
+
* the same trace-derived firewall (`assertTraceDerivedFindings` semantics): a reactive
|
|
256
|
+
* combinator steers from trace FINDINGS, never a child's raw `verdict`.
|
|
257
|
+
* 3. CROSS-RUN CORPUS (G2) — `Corpus` is the DURABLE accreted-fact store, DISTINCT from the
|
|
258
|
+
* per-run `SpawnJournal`/`ResultBlobStore`. `renderCorpusToInstructions` is the read-back:
|
|
259
|
+
* it projects accreted facts into `AgentProfile.prompt.instructions` / `resources.instructions`
|
|
260
|
+
* for the next run's persona (the learning-flywheel READ side).
|
|
261
|
+
* 4. TRAJECTORY TRACE + COST LEDGER — `trajectoryReport(journal, blobs)` reconstructs the whole
|
|
262
|
+
* spawn tree with per-node + rolled-up `Spend`; `equalKOnCost` compares arms on conserved
|
|
263
|
+
* COST (tokens/usd), NOT raw iteration count — closing the leaf-fanout confound.
|
|
264
|
+
*
|
|
265
|
+
* Layering: imports ONLY keystone runtime types (`../supervise/types`), persona types
|
|
266
|
+
* (`./types`), the substrate `AnalystFinding`/`AgentProfile`, and the durable-store interfaces.
|
|
267
|
+
* Pure types/interfaces — this module typechecks standalone, owns no impl, invents no engine.
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* A combinator is just a `LoopShape`: a factory `(ShapeContext) => Agent` whose `Agent.act`
|
|
272
|
+
* runs the combinator's structure over the `Scope` (spawn children, drain `next()`, select via
|
|
273
|
+
* the single-sourced `settledToIteration`+`defaultSelectWinner`, synthesize an `Outcome<D>`).
|
|
274
|
+
* Aliased — NOT a new type — so a combinator stays a first-class shape the persona layer's
|
|
275
|
+
* `runPersonified`/`ShapeRegistry` resolve with zero new machinery. The SHAPE is content-free;
|
|
276
|
+
* the persona carries the domain.
|
|
277
|
+
*/
|
|
278
|
+
type CombinatorShape<Task, D> = LoopShape<Task, D>;
|
|
279
|
+
/**
|
|
280
|
+
* `pipeline(stages)` — sequential composition: each stage's `Outcome.deliverable` feeds the next
|
|
281
|
+
* stage's task (via `feed`). The first `blocked` stage short-circuits the whole pipeline (its
|
|
282
|
+
* blockers ARE the pipeline's blockers — never coerced past a failed stage). The terminal
|
|
283
|
+
* stage's `done` deliverable is the pipeline's deliverable. Spawns one child per stage in order;
|
|
284
|
+
* a stage that the conserved pool cannot admit is a concrete blocker.
|
|
285
|
+
*
|
|
286
|
+
* No domain: "code build test" is `pipeline([plan, implement, integrate])` under a coder persona,
|
|
287
|
+
* not a named shape. A stage names only its label + how to derive its task from the prior output.
|
|
288
|
+
*/
|
|
289
|
+
interface PipelineStage<Task, StepIn, StepOut> {
|
|
290
|
+
/** Trace/journal label for this stage's spawned child. */
|
|
291
|
+
readonly label: string;
|
|
292
|
+
/** Derive this stage's task from the prior stage's deliverable (or the root task for stage 0).
|
|
293
|
+
* Pure projection — the framework never interprets the result; the resolved leaf does. */
|
|
294
|
+
feed(prior: StepIn, ctx: ShapeContext<unknown>, rootTask: Task): unknown;
|
|
295
|
+
/** Read this stage's settled child output into the typed `StepOut` the next stage feeds on.
|
|
296
|
+
* Fail loud (return a `blocked`) when the child produced nothing usable for the next stage. */
|
|
297
|
+
collect(settled: Settled<Outcome<StepOut>>): Outcome<StepOut>;
|
|
298
|
+
}
|
|
299
|
+
/** `pipeline(stages)` — build the sequential combinator from an ordered stage list. The first
|
|
300
|
+
* stage's `StepIn` is the root `Task`; the last stage's `StepOut` is the deliverable `D`. */
|
|
301
|
+
type Pipeline = <Task, D>(stages: ReadonlyArray<PipelineStage<Task, unknown, unknown>>) => CombinatorShape<Task, D>;
|
|
302
|
+
/**
|
|
303
|
+
* `fanout(items, { synthesize? })` — N children spawned in one round (one per item, bounded by
|
|
304
|
+
* the conserved pool's fail-closed admission), drained via `scope.next()`, then optionally a
|
|
305
|
+
* single SYNTHESIS child over the gathered results. Without `synthesize`, the combinator returns
|
|
306
|
+
* the best-valid child via the single-sourced selector (selector≠judge). A round that admitted
|
|
307
|
+
* zero children, or whose synthesis child could not be admitted, is a concrete blocker.
|
|
308
|
+
*
|
|
309
|
+
* No domain: a "research sweep over angles" is `fanout(angles, { synthesize: cite })` under a
|
|
310
|
+
* research persona; a "fanout-vote" is `fanout(copies)` with the default selector. The item list
|
|
311
|
+
* + the synthesis posture are the SHAPE's args; the prompt that turns an item into work is the
|
|
312
|
+
* persona's.
|
|
313
|
+
*/
|
|
314
|
+
interface FanoutOptions<Item, D> {
|
|
315
|
+
/** One child task per item: `item` + the index discriminator. The persona's directive/context
|
|
316
|
+
* is threaded in by the combinator; this only supplies the per-item discriminator. */
|
|
317
|
+
itemTask(item: Item, index: number, ctx: ShapeContext<D>): unknown;
|
|
318
|
+
/** Per-item child label (defaults to `item:<index>` in the impl). */
|
|
319
|
+
label?(item: Item, index: number): string;
|
|
320
|
+
/**
|
|
321
|
+
* Optional per-item `AgentSpec` override. When set, each item's child is spawned against the
|
|
322
|
+
* returned spec instead of `persona.root` — the seam a heterogeneous fanout uses to give each
|
|
323
|
+
* item a DISTINCT executor (e.g. N authored harness profiles, each on its own worktree-CLI
|
|
324
|
+
* leaf). Absent ⇒ every item runs against the persona's root spec (the homogeneous default).
|
|
325
|
+
*/
|
|
326
|
+
itemSpec?(item: Item, index: number, ctx: ShapeContext<D>): AgentSpec;
|
|
327
|
+
/**
|
|
328
|
+
* Optional synthesis over the gathered child results: when present, the combinator spawns ONE
|
|
329
|
+
* synthesis child whose task is built from the drained settlements, and its `done` output is
|
|
330
|
+
* the deliverable. When absent, the deliverable is the best-valid child via `defaultSelectWinner`.
|
|
331
|
+
* The synthesis child is a SEPARATE keystone agent (not a re-rank behind the driver).
|
|
332
|
+
*/
|
|
333
|
+
synthesize?: FanoutSynthesis<D>;
|
|
334
|
+
/**
|
|
335
|
+
* Winner-selection strategy among the gathered `done` children when there is no `synthesize`.
|
|
336
|
+
* Receives the SAME `Iteration[]` the default selector reads (each child's output is its
|
|
337
|
+
* `Outcome<D>`), so a strategy is a thin re-sort (smallest-diff, highest-readiness, first-valid
|
|
338
|
+
* …) over the candidates — NEVER a re-rank behind a judge. Default = `defaultSelectWinner`
|
|
339
|
+
* semantics (best-valid-score, ties→earliest). Mutually exclusive with `synthesize` (a
|
|
340
|
+
* synthesis child IS the selection); supplying both is a config error.
|
|
341
|
+
*/
|
|
342
|
+
selectWinner?: FanoutWinnerSelector<D>;
|
|
343
|
+
}
|
|
344
|
+
/** A winner-selection strategy: argmax/sort over the gathered child iterations (each output is the
|
|
345
|
+
* child's `Outcome<D>`), returning the chosen iteration or `undefined` when none qualifies. */
|
|
346
|
+
type FanoutWinnerSelector<D> = (iterations: Iteration<unknown, Outcome<D>>[]) => {
|
|
347
|
+
readonly output?: Outcome<D> | undefined;
|
|
348
|
+
} | undefined;
|
|
349
|
+
/** Built-in valid-only winner strategies for `selectValidWinner` (selector≠judge): best gated-valid
|
|
350
|
+
* score, the smallest delivered artifact (via a `sizeOf` extractor), or the earliest valid. */
|
|
351
|
+
type WinnerStrategy = 'highest-score' | 'smallest-artifact' | 'first-valid';
|
|
352
|
+
/** How a fanout's synthesis child is built + read. `synthesisTask` projects the drained child
|
|
353
|
+
* settlements into the synthesis child's task; `collect` reads its settled output into the
|
|
354
|
+
* deliverable `Outcome<D>`. */
|
|
355
|
+
interface FanoutSynthesis<D> {
|
|
356
|
+
synthesisTask(gathered: ReadonlyArray<Settled<Outcome<D>>>, ctx: ShapeContext<D>): unknown;
|
|
357
|
+
collect(settled: Settled<Outcome<D>>): Outcome<D>;
|
|
358
|
+
}
|
|
359
|
+
/** `fanout(items, opts)` — build the fanout combinator over a static item list. */
|
|
360
|
+
type Fanout = <Task, Item, D>(items: ReadonlyArray<Item>, opts: FanoutOptions<Item, D>) => CombinatorShape<Task, D>;
|
|
361
|
+
/**
|
|
362
|
+
* `loopUntil({ until, step })` — iterative deepening inside the conserved pool: spawn one `step`
|
|
363
|
+
* child per round, ask `until` whether the accumulated state satisfies the goal, and stop when it
|
|
364
|
+
* does OR when the pool can no longer admit a step (budget IS the loop bound — no unbounded
|
|
365
|
+
* while). The deployable, non-oracle stop: `until` is the satisfiability gate, read from trace
|
|
366
|
+
* findings + accumulated deliverables, never a fresh raw verdict the loop minted to stop itself.
|
|
367
|
+
*
|
|
368
|
+
* No domain: "refine until tests pass" is `loopUntil` with a coder persona + a `step` that edits
|
|
369
|
+
* and an `until` that reads the test-finding; the combinator owns only the round/stop wiring.
|
|
370
|
+
*/
|
|
371
|
+
interface LoopUntilSpec<Task, State, D> {
|
|
372
|
+
/** Build the next step child's task from the root task + the state accumulated so far. */
|
|
373
|
+
step(rootTask: Task, state: LoopUntilState<State>, ctx: ShapeContext<D>): unknown;
|
|
374
|
+
/** Fold one settled step into the accumulated state (the loop's running deliverable candidate). */
|
|
375
|
+
fold(prior: LoopUntilState<State>, settled: Settled<Outcome<D>>): LoopUntilState<State>;
|
|
376
|
+
/**
|
|
377
|
+
* The satisfiability gate: given the accumulated state + the round's trace findings, has the
|
|
378
|
+
* goal been reached? Returns the terminal deliverable when satisfied, or `null` to keep going.
|
|
379
|
+
* Reads `findings` (trace-derived), NOT a raw verdict score — the deployable-stop discipline.
|
|
380
|
+
*/
|
|
381
|
+
until(state: LoopUntilState<State>, findings: ReadonlyArray<AnalystFinding>): Outcome<D> | null;
|
|
382
|
+
/** Per-round step label (defaults to `step:<round>` in the impl). */
|
|
383
|
+
label?(round: number): string;
|
|
384
|
+
}
|
|
385
|
+
/** The accumulated state `loopUntil` threads across rounds — the running candidate + the round
|
|
386
|
+
* index, so `step`/`fold`/`until` are pure functions of it (replay-safe, no wall-clock). */
|
|
387
|
+
interface LoopUntilState<State> {
|
|
388
|
+
readonly round: number;
|
|
389
|
+
readonly value: State;
|
|
390
|
+
}
|
|
391
|
+
/** `loopUntil(spec)` — build the iterative-deepening combinator. `seed` is the initial state. */
|
|
392
|
+
type LoopUntil = <Task, State, D>(seed: State, spec: LoopUntilSpec<Task, State, D>) => CombinatorShape<Task, D>;
|
|
393
|
+
/**
|
|
394
|
+
* `panel(judges)` — M judges over ONE artifact, merged WRITE-ONLY (selector≠judge taken to its
|
|
395
|
+
* limit). The combinator spawns the M judge children over the same input artifact, drains their
|
|
396
|
+
* settlements, and MERGES their findings into a panel verdict via `merge` — a pure WRITE-ONLY
|
|
397
|
+
* fold (a judge's output is never fed back to steer another judge, and the merge never re-ranks
|
|
398
|
+
* the children behind the driver). The merged verdict gates the deliverable.
|
|
399
|
+
*
|
|
400
|
+
* No domain: a "code review panel" and an "essay rubric panel" are the same `panel` shape under
|
|
401
|
+
* different personas; the rubric lives in each judge persona's profile, not the combinator.
|
|
402
|
+
*/
|
|
403
|
+
interface PanelSpec<Artifact, D> {
|
|
404
|
+
/** The M judge child specs: each is a persona-derived child (a narrower judge profile). The
|
|
405
|
+
* combinator spawns one child per entry over the SAME `artifact` and never lets one judge's
|
|
406
|
+
* output reach another's task (write-only). */
|
|
407
|
+
readonly judges: ReadonlyArray<PanelJudge>;
|
|
408
|
+
/** Build one judge child's task from the shared artifact under review + the judge descriptor. */
|
|
409
|
+
judgeTask(artifact: Artifact, judge: PanelJudge, ctx: ShapeContext<D>): unknown;
|
|
410
|
+
/**
|
|
411
|
+
* Write-only merge: fold the M settled judge verdicts into the panel's terminal `Outcome<D>`.
|
|
412
|
+
* Pure over the drained settlements — it MUST NOT spawn, re-judge, or feed one verdict into
|
|
413
|
+
* another. A panel that reached no quorum is a concrete blocker (fail loud, never a vacuous done).
|
|
414
|
+
*/
|
|
415
|
+
merge(verdicts: ReadonlyArray<PanelVerdict>, artifact: Artifact): Outcome<D>;
|
|
416
|
+
}
|
|
417
|
+
/** One judge in a panel — a labeled persona-derived judge child. Content (the rubric) lives in
|
|
418
|
+
* the judge's profile; this carries only the label + the optional weight the merge may read. */
|
|
419
|
+
interface PanelJudge {
|
|
420
|
+
readonly label: string;
|
|
421
|
+
/** Optional merge weight (a write-only hint the `merge` fold may use; default-equal in the impl). */
|
|
422
|
+
readonly weight?: number;
|
|
423
|
+
}
|
|
424
|
+
/** One judge child's settled verdict, surfaced to the write-only `merge`. `down` judges carry no
|
|
425
|
+
* verdict (excluded from the merge `n`, like an infra-errored cell). */
|
|
426
|
+
interface PanelVerdict {
|
|
427
|
+
readonly judge: PanelJudge;
|
|
428
|
+
readonly verdict?: DefaultVerdict;
|
|
429
|
+
/** The judge child's raw output — what it was asked to assess, for a merge that quotes it. */
|
|
430
|
+
readonly output?: unknown;
|
|
431
|
+
/** True when the judge child went `down` (no usable verdict — kept out of the merge denominator). */
|
|
432
|
+
readonly down: boolean;
|
|
433
|
+
}
|
|
434
|
+
/** `panel(spec)` — build the M-judge write-only-merge combinator. */
|
|
435
|
+
type Panel = <Task, Artifact, D>(spec: PanelSpec<Artifact, D>) => CombinatorShape<Task, D>;
|
|
436
|
+
/**
|
|
437
|
+
* `verify({ implement, verifier })` — the 2-node sequential gate: an IMPLEMENT child produces a
|
|
438
|
+
* candidate, then a SEPARATE VERIFIER child's verdict GATES shippability. A `valid` verifier
|
|
439
|
+
* verdict ships the implement deliverable; any other outcome (implement down, verifier down,
|
|
440
|
+
* invalid verdict) becomes a concrete blocker carrying the failure verbatim — never a coerced
|
|
441
|
+
* "done". The verifier is a distinct keystone agent (selector≠judge: the implement child does
|
|
442
|
+
* not grade itself).
|
|
443
|
+
*
|
|
444
|
+
* No domain: "write code then run the test gate" and "draft then fact-check" are the same `verify`
|
|
445
|
+
* shape under different personas; the gate rubric is the verifier persona's, not the combinator's.
|
|
446
|
+
*/
|
|
447
|
+
interface VerifySpec<Task, Candidate, D> {
|
|
448
|
+
/** Build the implement child's task from the root task. */
|
|
449
|
+
implement(rootTask: Task, ctx: ShapeContext<D>): unknown;
|
|
450
|
+
/** Build the verifier child's task from the implement child's settled candidate. */
|
|
451
|
+
verifier(candidate: Settled<Outcome<Candidate>>, ctx: ShapeContext<D>): unknown;
|
|
452
|
+
/** Project the gated (verifier-`valid`) candidate into the terminal deliverable. */
|
|
453
|
+
collect(candidate: Settled<Outcome<Candidate>>, verdict: DefaultVerdict): Outcome<D>;
|
|
454
|
+
/** Implement / verifier child labels (default `implement` / `verify` in the impl). */
|
|
455
|
+
readonly implementLabel?: string;
|
|
456
|
+
readonly verifierLabel?: string;
|
|
457
|
+
}
|
|
458
|
+
/** `verify(spec)` — build the 2-node implement→verifier-gate combinator. */
|
|
459
|
+
type Verify = <Task, Candidate, D>(spec: VerifySpec<Task, Candidate, D>) => CombinatorShape<Task, D>;
|
|
460
|
+
/**
|
|
461
|
+
* `widen({ gate })` (G5) — the STREAMING spawn-on-completion driver. Unlike the static-fanout
|
|
462
|
+
* combinators above, the widener REACTS to each `scope.next()`: as each child settles it consults
|
|
463
|
+
* the `WidenGate` and, when a lineage is `promising`, widens by AT MOST ONE child toward it under
|
|
464
|
+
* the remaining conserved pool. Defaults to FLAT (the gate never widens) so a gate run stays
|
|
465
|
+
* non-widening and the R2 selector≠judge collision is dormant. `promising` is derived from the
|
|
466
|
+
* round's analyst FINDINGS (via `ScopeAnalyst`, §2), NOT a child's raw `verdict` — the firewall.
|
|
467
|
+
*
|
|
468
|
+
* This is the progressive-widening (MCTS-PW) combinator: the one shape whose breadth is decided
|
|
469
|
+
* at runtime from the diagnosis, not fixed at spawn. It is the mechanism the diverse-strategy-vs-
|
|
470
|
+
* blind GATE is run with — kept FLAT by default until that gate returns positive (don't build
|
|
471
|
+
* mechanism ahead of the gate).
|
|
472
|
+
*/
|
|
473
|
+
interface WidenSpec<Seed, D> {
|
|
474
|
+
/** The initial children to spawn before any widening — the seed lineages the gate widens from.
|
|
475
|
+
* One child task per seed; bounded by the conserved pool's fail-closed admission. */
|
|
476
|
+
readonly seeds: ReadonlyArray<Seed>;
|
|
477
|
+
seedTask(seed: Seed, index: number, ctx: ShapeContext<D>): unknown;
|
|
478
|
+
/**
|
|
479
|
+
* The progressive-widening gate. Consulted on EVERY settled child with the round's
|
|
480
|
+
* trace-derived `findings`; returns a widen decision (spawn one more toward a lineage) or a
|
|
481
|
+
* stop. DEFAULTS to flat via `flatWidenGate` — never widens, so the firewall stays dormant.
|
|
482
|
+
*/
|
|
483
|
+
readonly gate: ScopeWidenGate<D>;
|
|
484
|
+
/** Build the widened child's task from the lineage the gate chose to extend. */
|
|
485
|
+
widenTask(toward: WidenLineage<D>, ctx: ShapeContext<D>): unknown;
|
|
486
|
+
/** Synthesize the terminal deliverable from every settled lineage (selector≠judge: the
|
|
487
|
+
* single-sourced selector over the gathered children, never a re-judge). */
|
|
488
|
+
synthesize(gathered: ReadonlyArray<Settled<Outcome<D>>>, ctx: ShapeContext<D>): Outcome<D>;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* The runtime widening gate (the reactive analogue of the keystone's `WidenGate`, lifted to read
|
|
492
|
+
* trace FINDINGS instead of a raw verdict). `decide` is consulted per settled child; it MUST
|
|
493
|
+
* derive `promising` from `findings`, never from `settled.verdict`, unless `judgeExempt` is
|
|
494
|
+
* explicitly argued (the documented off-by-default escape hatch). Flat default never widens.
|
|
495
|
+
*/
|
|
496
|
+
interface ScopeWidenGate<D> {
|
|
497
|
+
decide(settled: Settled<Outcome<D>>, findings: ReadonlyArray<AnalystFinding>, budget: Scope<Outcome<D>>['budget']): WidenDecision<D>;
|
|
498
|
+
/** When true, `decide` may read `settled.verdict` directly — collides with the steer firewall,
|
|
499
|
+
* so it must be argued per cell, never defaulted on (mirrors the keystone `WidenGate`). */
|
|
500
|
+
readonly judgeExempt?: boolean;
|
|
501
|
+
}
|
|
502
|
+
/** A widening decision: extend one lineage by one child, or stop widening. `flatWidenGate`
|
|
503
|
+
* always returns `{ kind: 'stop' }`. */
|
|
504
|
+
type WidenDecision<D> = {
|
|
505
|
+
kind: 'widen';
|
|
506
|
+
toward: WidenLineage<D>;
|
|
507
|
+
} | {
|
|
508
|
+
kind: 'stop';
|
|
509
|
+
rationale?: string;
|
|
510
|
+
};
|
|
511
|
+
/** A lineage the gate may widen toward — the settled child that looked promising + the findings
|
|
512
|
+
* that justified it (the trace-derived provenance the firewall requires). */
|
|
513
|
+
interface WidenLineage<D> {
|
|
514
|
+
readonly settled: Extract<Settled<Outcome<D>>, {
|
|
515
|
+
kind: 'done';
|
|
516
|
+
}>;
|
|
517
|
+
readonly findings: ReadonlyArray<AnalystFinding>;
|
|
518
|
+
}
|
|
519
|
+
/** `widen(spec)` — build the streaming progressive-widening combinator. */
|
|
520
|
+
type Widen = <Task, Seed, D>(spec: WidenSpec<Seed, D>) => CombinatorShape<Task, D>;
|
|
521
|
+
/** The flat default `ScopeWidenGate` factory contract — never widens, keeping the R2 firewall
|
|
522
|
+
* conflict dormant. Exported so a gate run can pass it explicitly and a test can assert the
|
|
523
|
+
* default is flat. */
|
|
524
|
+
type FlatWidenGate = <D>() => ScopeWidenGate<D>;
|
|
525
|
+
/**
|
|
526
|
+
* The reactive analyst seam — the PORT of the round-synchronous driver's `analyze` hook
|
|
527
|
+
* (dynamic.ts) onto the reactive `Scope`. The old driver wired the analyst at round
|
|
528
|
+
* boundaries (`plan` ran the analyst over `history` BEFORE the planner); the reactive `Scope` has
|
|
529
|
+
* no rounds, so this carries the wire across: a combinator's `act` asks the `ScopeAnalyst` to turn
|
|
530
|
+
* the settled children SO FAR into `AnalystFinding[]`, and steers from THOSE findings.
|
|
531
|
+
*
|
|
532
|
+
* The firewall is preserved (selector≠judge): `analyze` runs the trace-derived analyst and the
|
|
533
|
+
* impl asserts `assertTraceDerivedFindings` semantics — a finding citing judge/verdict/score
|
|
534
|
+
* `metric` evidence aborts the round. The steer decision reads `findings`, NEVER the children's
|
|
535
|
+
* raw `verdict`. Fail loud — a throwing or non-array analyst aborts (no silent empty findings).
|
|
536
|
+
*/
|
|
537
|
+
interface ScopeAnalyst<D> {
|
|
538
|
+
/**
|
|
539
|
+
* Turn the children settled so far into trace-derived findings. `settledSoFar` is the cursor-
|
|
540
|
+
* ordered settlement list a combinator has drained (the reactive analogue of the old driver's
|
|
541
|
+
* `history`). The impl runs the analyst, then enforces the trace-derived firewall before
|
|
542
|
+
* returning — a judge-derived finding is rejected, not filtered.
|
|
543
|
+
*/
|
|
544
|
+
analyze(input: ScopeAnalyzeInput<D>): Promise<ReadonlyArray<AnalystFinding>>;
|
|
545
|
+
}
|
|
546
|
+
/** Input to a `ScopeAnalyst.analyze` — the root task framing + the children settled so far. */
|
|
547
|
+
interface ScopeAnalyzeInput<D> {
|
|
548
|
+
/** Opaque root-task framing (whatever the combinator was invoked with). */
|
|
549
|
+
readonly task: unknown;
|
|
550
|
+
/** The children this combinator has drained off `scope.next()`, in cursor order. */
|
|
551
|
+
readonly settledSoFar: ReadonlyArray<Settled<Outcome<D>>>;
|
|
552
|
+
/** This combinator's scope id (the trace-correlation root for the analyst). */
|
|
553
|
+
readonly nodeId: NodeId;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* How a combinator's `act` consumes findings to steer — the SINGLE firewalled steer surface a
|
|
557
|
+
* reactive combinator reads. `loopUntil.until`, `widen` gate, and any future steer all funnel
|
|
558
|
+
* through a `SteerContext` so the firewall is enforced in one place: `findings` is trace-derived
|
|
559
|
+
* (the analyst already asserted it), and a combinator MUST NOT reach back to `settled.verdict`
|
|
560
|
+
* for the steer decision. `lastValidScore` is provided for OBSERVABILITY only (rendering/traces),
|
|
561
|
+
* explicitly NOT for steering — reading it to steer is the coupling the architecture forbids.
|
|
562
|
+
*/
|
|
563
|
+
interface SteerContext<D> {
|
|
564
|
+
readonly findings: ReadonlyArray<AnalystFinding>;
|
|
565
|
+
readonly settledSoFar: ReadonlyArray<Settled<Outcome<D>>>;
|
|
566
|
+
/** Observability-only: the best valid score seen so far. Rendering/trace use ONLY — steering
|
|
567
|
+
* off this re-introduces selector=judge. Marked so a reviewer catches a misuse. */
|
|
568
|
+
readonly lastValidScore?: number;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* The firewall assertion contract, re-stated for the reactive seam (PORT of
|
|
572
|
+
* `assertTraceDerivedFindings`). A PROVENANCE check, not a content check: span/event/artifact/
|
|
573
|
+
* finding refs and empty-evidence findings pass; only a `metric` ref whose uri is a
|
|
574
|
+
* judge/verdict/score scheme is rejected. Fail loud — a tainted finding aborts. The impl lives in
|
|
575
|
+
* `analyst.ts`; this type pins its signature so callers depend on the contract, not the impl.
|
|
576
|
+
*/
|
|
577
|
+
type AssertTraceDerivedFindings = (findings: ReadonlyArray<AnalystFinding>) => void;
|
|
578
|
+
/**
|
|
579
|
+
* One accreted fact in the cross-run corpus — the learning-flywheel's durable unit. DISTINCT from
|
|
580
|
+
* a `SpawnEvent` (a per-run decision record): a `CorpusRecord` is a fact a run LEARNED that a
|
|
581
|
+
* FUTURE run should read back (the world-model for story 5). It is content the next persona reads,
|
|
582
|
+
* not a replay input. Tagged + scored so `query`/`renderCorpusToInstructions` can project the
|
|
583
|
+
* relevant, high-confidence subset.
|
|
584
|
+
*/
|
|
585
|
+
interface CorpusRecord {
|
|
586
|
+
readonly schemaVersion: '1.0.0';
|
|
587
|
+
/** Stable id over identity-defining fields (claim + tags) so a re-learned fact dedups. */
|
|
588
|
+
readonly id: string;
|
|
589
|
+
/** The run that produced this fact (the journal `runId`/`root`) — provenance back to the trace. */
|
|
590
|
+
readonly runId: NodeId;
|
|
591
|
+
readonly producedAt: string;
|
|
592
|
+
/** Coarse classification the query/render filters on (free-form, mirrors `AnalystFinding.area`). */
|
|
593
|
+
readonly area: string;
|
|
594
|
+
/** The accreted fact — the instruction-shaped statement the next run reads back. */
|
|
595
|
+
readonly claim: string;
|
|
596
|
+
/** Optional supporting detail the renderer may include under the claim. */
|
|
597
|
+
readonly rationale?: string;
|
|
598
|
+
/** Free-form tags for `query` filtering (domain, persona, surface). */
|
|
599
|
+
readonly tags: ReadonlyArray<string>;
|
|
600
|
+
/** 0..1 — the producing run's confidence in this fact (the render threshold reads it). */
|
|
601
|
+
readonly confidence: number;
|
|
602
|
+
/** Optional provenance back into the run that learned it (a finding id / outRef / span). */
|
|
603
|
+
readonly evidence?: ReadonlyArray<{
|
|
604
|
+
readonly kind: string;
|
|
605
|
+
readonly uri: string;
|
|
606
|
+
}>;
|
|
607
|
+
}
|
|
608
|
+
/** A corpus query filter — every field is an AND-narrowing; an omitted field does not constrain. */
|
|
609
|
+
interface CorpusFilter {
|
|
610
|
+
readonly area?: string;
|
|
611
|
+
/** Match records carrying ALL of these tags. */
|
|
612
|
+
readonly tags?: ReadonlyArray<string>;
|
|
613
|
+
/** Minimum confidence a record must clear to be returned (the render gate). */
|
|
614
|
+
readonly minConfidence?: number;
|
|
615
|
+
/** Only records from this run (rare — usually a cross-run read). */
|
|
616
|
+
readonly runId?: NodeId;
|
|
617
|
+
/** Cap the result count (most-confident first in the impl). */
|
|
618
|
+
readonly limit?: number;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* The durable cross-run corpus — the learning-flywheel store. DISTINCT from `SpawnJournal`
|
|
622
|
+
* (per-run decisions, replay) and `ResultBlobStore` (per-run payloads): `Corpus` holds accreted
|
|
623
|
+
* FACTS across runs that the next run reads back. `InMemoryCorpus` + `FileCorpus` (JSONL) impls
|
|
624
|
+
* live in `corpus.ts` and MAY share a storage spine with the JSONL journal, but the INTERFACE is
|
|
625
|
+
* separate so a consumer never confuses a replay record with a learned fact.
|
|
626
|
+
*
|
|
627
|
+
* Fail-loud, typed-outcome boundary: `append` is idempotent on an identical record (same `id` +
|
|
628
|
+
* `claim`); a conflicting re-append under the same `id` is a typed error, never a silent overwrite.
|
|
629
|
+
*/
|
|
630
|
+
interface Corpus {
|
|
631
|
+
/** Append one accreted fact. Idempotent on an identical record; returns a typed outcome —
|
|
632
|
+
* inspect `succeeded` before treating it as durable (no silent write-through on conflict). */
|
|
633
|
+
append(record: CorpusRecord): Promise<{
|
|
634
|
+
succeeded: true;
|
|
635
|
+
} | {
|
|
636
|
+
succeeded: false;
|
|
637
|
+
error: string;
|
|
638
|
+
}>;
|
|
639
|
+
/** Query accreted facts by filter — most-confident first. Returns the matching records (an
|
|
640
|
+
* empty array when none match is a valid result, NOT an error). */
|
|
641
|
+
query(filter: CorpusFilter): Promise<ReadonlyArray<CorpusRecord>>;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Project accreted corpus facts into an `AgentProfile`'s instruction seams — the learning-flywheel
|
|
645
|
+
* READ side. Reads the corpus through `filter`, renders the matching facts into instruction lines,
|
|
646
|
+
* and returns a NEW profile with them merged into `prompt.instructions` (the append-line seam) so
|
|
647
|
+
* the next run's persona reads the accreted world-model. Pure projection over the queried records;
|
|
648
|
+
* never mutates the input profile (returns a fresh one). The impl lives in `corpus.ts`.
|
|
649
|
+
*
|
|
650
|
+
* `resources.instructions` is `string | AgentProfileResourceRef`; `prompt.instructions` is
|
|
651
|
+
* `string[]`. The render targets `prompt.instructions` (additive lines) by default; a caller that
|
|
652
|
+
* wants the single-blob `resources.instructions` form passes `target: 'resources'`.
|
|
653
|
+
*/
|
|
654
|
+
interface RenderCorpusToInstructionsOptions {
|
|
655
|
+
readonly corpus: Corpus;
|
|
656
|
+
readonly filter: CorpusFilter;
|
|
657
|
+
/** The profile to project the facts into. The result is a fresh profile — the input is unchanged. */
|
|
658
|
+
readonly profile: AgentProfile;
|
|
659
|
+
/** Where the rendered facts land: appended to `prompt.instructions[]` (default) or folded into
|
|
660
|
+
* the single-blob `resources.instructions` string. */
|
|
661
|
+
readonly target?: 'prompt' | 'resources';
|
|
662
|
+
/** Optional cap on rendered lines (most-confident first), independent of the query `limit`. */
|
|
663
|
+
readonly maxLines?: number;
|
|
664
|
+
}
|
|
665
|
+
/** `renderCorpusToInstructions(opts)` — the flywheel read-back projection. Async (queries the
|
|
666
|
+
* durable corpus); returns a fresh `AgentProfile` with the accreted facts merged in. */
|
|
667
|
+
type RenderCorpusToInstructions = (opts: RenderCorpusToInstructionsOptions) => Promise<AgentProfile>;
|
|
668
|
+
/**
|
|
669
|
+
* One node in the reconstructed trajectory tree — a driver OR a leaf, with its OWN spend and the
|
|
670
|
+
* spend ROLLED UP over its subtree. Reconstructed from the `SpawnJournal` (structure + per-node
|
|
671
|
+
* `Spend`) + the `ResultBlobStore` (the `out` artifact, rehydrated by `outRef`). The realized tree
|
|
672
|
+
* shape: `parent`/`children` are the actual spawn edges the run took, not a planned topology.
|
|
673
|
+
*/
|
|
674
|
+
interface TrajectoryNode {
|
|
675
|
+
readonly id: NodeId;
|
|
676
|
+
readonly parent?: NodeId;
|
|
677
|
+
readonly children: ReadonlyArray<NodeId>;
|
|
678
|
+
readonly label: string;
|
|
679
|
+
readonly runtime: string;
|
|
680
|
+
/** Terminal status the journal recorded for this node. */
|
|
681
|
+
readonly status: 'done' | 'failed' | 'cancelled' | 'pending';
|
|
682
|
+
/** This node's OWN conserved spend (from its `settled` event). */
|
|
683
|
+
readonly ownSpend: Spend;
|
|
684
|
+
/** This node's spend PLUS every descendant's — the rolled-up subtree cost. The cost a parent
|
|
685
|
+
* "really" consumed inclusive of its children's fanout (the equal-k-on-cost basis). */
|
|
686
|
+
readonly rolledUpSpend: Spend;
|
|
687
|
+
/** The node's verdict, when its settlement carried one (observability — NOT a steer input). */
|
|
688
|
+
readonly verdict?: DefaultVerdict;
|
|
689
|
+
/** The rehydrated output artifact, when `withOutputs` was requested + the blob resolved. */
|
|
690
|
+
readonly output?: unknown;
|
|
691
|
+
readonly outRef?: string;
|
|
692
|
+
}
|
|
693
|
+
/** The whole reconstructed trajectory — the realized tree + its root-rolled-up total. The
|
|
694
|
+
* per-node + rolled-up `Spend` is the evidence both the trace viewer and `equalKOnCost` read. */
|
|
695
|
+
interface TrajectoryReport {
|
|
696
|
+
readonly root: NodeId;
|
|
697
|
+
/** Every node, in cursor/spawn order — the realized tree (`parent`/`children` are the real edges). */
|
|
698
|
+
readonly nodes: ReadonlyArray<TrajectoryNode>;
|
|
699
|
+
/** The root's rolled-up spend — the whole run's conserved total (tokens + usd + iterations + ms). */
|
|
700
|
+
readonly total: Spend;
|
|
701
|
+
/** Count of nodes by terminal status — a quick "how did the tree end" readout. */
|
|
702
|
+
readonly statusCounts: Readonly<Record<TrajectoryNode['status'], number>>;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* `trajectoryReport(journal, blobs, root, { withOutputs? })` — reconstruct the whole tree with
|
|
706
|
+
* per-node + rolled-up `Spend`. Reads the journal for structure + spend and (when `withOutputs`)
|
|
707
|
+
* the blob store for each `done` node's artifact. Fail loud on a tree that was never journaled or
|
|
708
|
+
* a `done` node whose blob the store cannot rehydrate (a silent gap would mis-cost the tree). The
|
|
709
|
+
* impl lives in `trajectory.ts`.
|
|
710
|
+
*/
|
|
711
|
+
interface TrajectoryReportOptions {
|
|
712
|
+
/** Rehydrate each `done` node's `output` from the blob store. Off by default (cost-only report). */
|
|
713
|
+
readonly withOutputs?: boolean;
|
|
714
|
+
}
|
|
715
|
+
/** `trajectoryReport(...)` — the tree+cost reconstructor. Async (reads journal + optionally blobs). */
|
|
716
|
+
type TrajectoryReportFn = (journal: SpawnJournal, blobs: ResultBlobStore, root: NodeId, options?: TrajectoryReportOptions) => Promise<TrajectoryReport>;
|
|
717
|
+
/**
|
|
718
|
+
* One arm of an equal-k comparison — a labeled trajectory (a `TrajectoryReport` is one arm's whole
|
|
719
|
+
* run). The arm's conserved COST is `report.total` (tokens + usd), which the sandbox executor
|
|
720
|
+
* already reports INCLUSIVE of a leaf's internal sub-agent fanout — so comparing arms on this cost
|
|
721
|
+
* (not raw `iterations`) closes the leaf-fanout confound: a treatment arm whose leaf fanned out
|
|
722
|
+
* internally is charged for that fanout in `total.tokens`/`total.usd`, not hidden behind one
|
|
723
|
+
* iteration count.
|
|
724
|
+
*/
|
|
725
|
+
interface EqualKArm {
|
|
726
|
+
readonly label: string;
|
|
727
|
+
readonly report: TrajectoryReport;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* The equal-k-on-cost verdict: whether every arm spent within `tolerance` of the others on the
|
|
731
|
+
* CONSERVED cost channels (tokens + usd), so a downstream metric comparison is "at equal k". Per-
|
|
732
|
+
* arm cost is surfaced so a caller can see HOW close. `withinTolerance: false` means the arms are
|
|
733
|
+
* NOT comparable at equal compute — a confound to report, not a result to publish.
|
|
734
|
+
*/
|
|
735
|
+
interface EqualKVerdict {
|
|
736
|
+
readonly withinTolerance: boolean;
|
|
737
|
+
/** Per-arm conserved cost (the basis: tokens total + usd). */
|
|
738
|
+
readonly arms: ReadonlyArray<{
|
|
739
|
+
readonly label: string;
|
|
740
|
+
readonly tokens: number;
|
|
741
|
+
readonly usd: number;
|
|
742
|
+
readonly iterations: number;
|
|
743
|
+
}>;
|
|
744
|
+
/** The realized spread on each channel (max − min across arms), for the report. */
|
|
745
|
+
readonly spread: {
|
|
746
|
+
readonly tokens: number;
|
|
747
|
+
readonly usd: number;
|
|
748
|
+
};
|
|
749
|
+
/** The fractional tolerance the check used (spread / median ≤ tolerance per channel). */
|
|
750
|
+
readonly tolerance: number;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* `equalKOnCost(arms, { tolerance? })` — assert arms are comparable at EQUAL conserved COST
|
|
754
|
+
* (tokens + usd), NOT raw iteration count. The conserved-pool guarantees `Σk` equal by
|
|
755
|
+
* construction WITHIN one supervised run; this checks it ACROSS arms (separate runs) where the
|
|
756
|
+
* pool cannot, so a cross-arm gate comparison can prove equal compute before claiming a win. The
|
|
757
|
+
* impl lives in `trajectory.ts`. Pure over the reports — no I/O.
|
|
758
|
+
*/
|
|
759
|
+
interface EqualKOnCostOptions {
|
|
760
|
+
/** Max fractional spread (spread/median) per channel for arms to count as equal-k. Default in
|
|
761
|
+
* the impl (e.g. 0.05). A tighter tolerance = a stricter equal-compute claim. */
|
|
762
|
+
readonly tolerance?: number;
|
|
763
|
+
}
|
|
764
|
+
/** `equalKOnCost(arms, opts)` — the cross-arm equal-compute check on conserved cost. */
|
|
765
|
+
type EqualKOnCost = (arms: ReadonlyArray<EqualKArm>, options?: EqualKOnCostOptions) => EqualKVerdict;
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* @experimental
|
|
769
|
+
*
|
|
770
|
+
* The completion-oracle: **settled ⟺ DELIVERED.**
|
|
771
|
+
*
|
|
772
|
+
* Foreman's one hard lesson (0/18 self-improvement deliverables) — "done" must mean a check
|
|
773
|
+
* PASSED, not the agent's say-so. `gateOnDeliverable` wraps an `Executor` so its settlement
|
|
774
|
+
* is `valid` ONLY when the deliverable check passes. The child still RUNS and settles (its
|
|
775
|
+
* spend is conserved into the pool either way), but a child that ran WITHOUT delivering
|
|
776
|
+
* settles `valid:false` — so a keep-best driver never counts it as done, and a gate never
|
|
777
|
+
* inflates with self-judged wins.
|
|
778
|
+
*
|
|
779
|
+
* Dual-purpose by construction:
|
|
780
|
+
* - product: the agent fleet only advances on real, checked deliverables.
|
|
781
|
+
* - proof: the gate's `valid` is the honest settle — equal-k comparisons can't be gamed by an
|
|
782
|
+
* arm that "ran" without producing the artifact.
|
|
783
|
+
*
|
|
784
|
+
* The check is a DEPLOYABLE oracle — a test command, a state verifier, the commit0 judge —
|
|
785
|
+
* read off the child's output, never the model judging itself. A throwing check is
|
|
786
|
+
* fail-closed (not delivered), never a crash.
|
|
787
|
+
*/
|
|
788
|
+
|
|
789
|
+
interface DeliverableSpec<Out = unknown> {
|
|
790
|
+
/** The deployable check that decides DELIVERED. `settled.valid ⟺ this resolves true`. */
|
|
791
|
+
check: (out: Out) => boolean | Promise<boolean>;
|
|
792
|
+
/** What the spawn was supposed to produce — surfaced in traces/reports. */
|
|
793
|
+
describe?: string;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Wrap an `Executor` so its settlement `valid` reflects the deliverable check, not the
|
|
797
|
+
* inner verdict. Handles both `execute` shapes (one-shot `Promise<ExecutorResult>` and
|
|
798
|
+
* streaming `AsyncIterable<UsageEvent>` + `resultArtifact()`); the check runs once the inner
|
|
799
|
+
* executor has produced its output. The inner `score` is preserved; only `valid` is gated.
|
|
800
|
+
*/
|
|
801
|
+
declare function gateOnDeliverable<Out>(inner: Executor<Out>, deliverable: DeliverableSpec<Out>): Executor<Out>;
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* @experimental
|
|
805
|
+
*
|
|
806
|
+
* The mechanical patch gate — the SINGLE source of the no-op / always-on secret-path floor /
|
|
807
|
+
* forbidden-path / diff-size / test / typecheck checks over a captured diff. No I/O: it scores a
|
|
808
|
+
* patch plus its already-derived test/typecheck PASS signals. The generic worktree-CLI deliverable
|
|
809
|
+
* (`patchDelivered`, which runs the commands itself) calls this, so the gate logic never forks.
|
|
810
|
+
*
|
|
811
|
+
* The always-on safety floors (no-op rejection + secret-path) are also exported standalone
|
|
812
|
+
* (`isNonEmptyPatch` / `touchesSecretPath`) for reuse outside the full scorer.
|
|
813
|
+
*/
|
|
814
|
+
|
|
815
|
+
/** @experimental The patch + its derived PASS signals the mechanical gate decides on. */
|
|
816
|
+
interface CoderCheckInput {
|
|
817
|
+
/** The unified diff produced by the run. */
|
|
818
|
+
patch: string;
|
|
819
|
+
/** Did `testCmd` exit clean? */
|
|
820
|
+
testsPassed: boolean;
|
|
821
|
+
/** Did `typecheckCmd` exit clean? */
|
|
822
|
+
typecheckPassed: boolean;
|
|
823
|
+
}
|
|
824
|
+
/** @experimental The per-task constraints the mechanical gate enforces. */
|
|
825
|
+
interface CoderCheckConstraints {
|
|
826
|
+
/** Default 400. Hard cap; gate fails when exceeded. */
|
|
827
|
+
maxDiffLines?: number;
|
|
828
|
+
/** Literal path prefixes the patch must not touch. */
|
|
829
|
+
forbiddenPaths?: string[];
|
|
830
|
+
}
|
|
831
|
+
/** The unified-diff paths the patch touches — the `+++`/`---` headers, de-`a/`/`b/`-prefixed,
|
|
832
|
+
* with `/dev/null` (a delete's other side) dropped. */
|
|
833
|
+
declare function touchedPathsFromPatch(patch: string): string[];
|
|
834
|
+
/** Count the added/removed content lines in a unified diff (excludes the `+++`/`---` headers). */
|
|
835
|
+
declare function countDiffLines(patch: string): number;
|
|
836
|
+
/** True when the patch actually changes something — at least one touched path AND non-blank body.
|
|
837
|
+
* An empty patch can trivially "pass" tests/typecheck (nothing changed) yet does no work. */
|
|
838
|
+
declare function isNonEmptyPatch(patch: string): boolean;
|
|
839
|
+
/** The credential-shaped paths the patch touches (always-on floor, independent of `forbiddenPaths`). */
|
|
840
|
+
declare function touchesSecretPath(patch: string): string[];
|
|
841
|
+
/**
|
|
842
|
+
* @experimental
|
|
843
|
+
*
|
|
844
|
+
* The pure mechanical gate — the SINGLE source of the no-op / always-on secret-path floor /
|
|
845
|
+
* diff-size / forbidden-path / test / typecheck checks. No I/O: it scores a patch + its
|
|
846
|
+
* already-derived pass signals.
|
|
847
|
+
*
|
|
848
|
+
* Checks in order: (1) no-op rejection, (2) always-on secret-path floor (independent of
|
|
849
|
+
* `forbiddenPaths`), (3) forbidden-path, (4) diff-size cap, (5) tests, (6) typecheck.
|
|
850
|
+
* Aggregate score: `0.5*tests + 0.3*typecheck + 0.2*(1 - diffLines/maxDiff)`; `valid` is the
|
|
851
|
+
* conjunction of all six.
|
|
852
|
+
*/
|
|
853
|
+
declare function runCoderChecks(input: CoderCheckInput, constraints?: CoderCheckConstraints): DefaultVerdict;
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* @experimental
|
|
857
|
+
*
|
|
858
|
+
* The ONE worktree-harness execution core. The physical act — run a supervisor-authored
|
|
859
|
+
* `AgentProfile` on a local coding-harness CLI (claude / codex / opencode) against a fresh git
|
|
860
|
+
* worktree off `repoRoot`, capture the diff, derive the test/typecheck PASS signals, then clean
|
|
861
|
+
* up — lives here ONCE. Two executors adapt it to two ports without re-implementing it:
|
|
862
|
+
* - `createWorktreeCliExecutor` — the `Scope`/`Supervisor` leaf `Executor`.
|
|
863
|
+
* - `createInProcessExecutor` — the `runLoop` `SandboxClient` / coder-delegate path.
|
|
864
|
+
*
|
|
865
|
+
* §1.5 by construction: the authored `profile.prompt.systemPrompt` + `profile.model.default`
|
|
866
|
+
* reach the harness through `harnessInvocation` HERE, so neither port can drop them — the exact
|
|
867
|
+
* bug that existed while the in-process path called `runLocalHarness` with only the task prompt.
|
|
868
|
+
*
|
|
869
|
+
* Lifecycle: `createWorktree` → `harnessInvocation` + `runLocalHarness` → `captureWorktreeDiff`
|
|
870
|
+
* (BEFORE checks, so the patch is the harness's output, not polluted by files a test run writes)
|
|
871
|
+
* → the configured test/typecheck commands in the live worktree → return the result + a `cleanup`
|
|
872
|
+
* the caller invokes at its own teardown point. A throw cleans up before propagating, so a failed
|
|
873
|
+
* run never leaks a worktree.
|
|
874
|
+
*/
|
|
875
|
+
|
|
876
|
+
/** Outcome of one verification command run in the worktree (test or typecheck). */
|
|
877
|
+
interface WorktreeCommandResult {
|
|
878
|
+
/** The shell command line that was run. */
|
|
879
|
+
command: string;
|
|
880
|
+
/** Did the command exit 0? The PASS signal a deliverable gate / coder output reads. */
|
|
881
|
+
passed: boolean;
|
|
882
|
+
/** OS exit code, or `null` when killed before exit. */
|
|
883
|
+
exitCode: number | null;
|
|
884
|
+
/** Combined stdout+stderr (capped) — surfaced in traces for diagnosis. */
|
|
885
|
+
output: string;
|
|
886
|
+
}
|
|
887
|
+
/** The canonical result of one worktree-harness run, projected by each port to its own shape. */
|
|
888
|
+
interface WorktreeHarnessResult {
|
|
889
|
+
/** The branch the worktree was cut on (`delegate/<runId>`). */
|
|
890
|
+
branch: string;
|
|
891
|
+
/** `git diff` of the worktree against its base — the unified patch the harness produced. */
|
|
892
|
+
patch: string;
|
|
893
|
+
/** Shortstat-derived change counts. */
|
|
894
|
+
stats: {
|
|
895
|
+
filesChanged: number;
|
|
896
|
+
insertions: number;
|
|
897
|
+
deletions: number;
|
|
898
|
+
};
|
|
899
|
+
/** The harness subprocess outcome. */
|
|
900
|
+
harness: {
|
|
901
|
+
name: LocalHarness;
|
|
902
|
+
exitCode: number | null;
|
|
903
|
+
timedOut: boolean;
|
|
904
|
+
killedBySignal: NodeJS.Signals | null;
|
|
905
|
+
durationMs: number;
|
|
906
|
+
stdout: string;
|
|
907
|
+
stderr: string;
|
|
908
|
+
};
|
|
909
|
+
/** Verification signals derived in the live worktree (present only when commands were given). */
|
|
910
|
+
checks?: {
|
|
911
|
+
tests?: WorktreeCommandResult;
|
|
912
|
+
typecheck?: WorktreeCommandResult;
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
/** The single shell-command-in-worktree runner seam (replaces the per-executor copies). */
|
|
916
|
+
type WorktreeCheckRunner = (opts: {
|
|
917
|
+
command: string;
|
|
918
|
+
cwd: string;
|
|
919
|
+
timeoutMs: number;
|
|
920
|
+
signal?: AbortSignal;
|
|
921
|
+
}) => Promise<{
|
|
922
|
+
exitCode: number | null;
|
|
923
|
+
output: string;
|
|
924
|
+
}>;
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* @experimental
|
|
928
|
+
*
|
|
929
|
+
* The worktree-CLI leaf executor — a supervisor-authored `AgentProfile` driving a local
|
|
930
|
+
* coding-harness CLI (claude / codex / opencode) on its OWN git worktree, surfaced as the open
|
|
931
|
+
* `Executor<Out>` port (`./types`). It is a LEAF executor: it plugs straight into the
|
|
932
|
+
* `Scope`/`Supervisor` recursion and `gateOnDeliverable`, so it IS the canonical recursive path
|
|
933
|
+
* (no `runLoop`/virtual-SandboxInstance shim in between).
|
|
934
|
+
*
|
|
935
|
+
* This is a THIN adapter: the physical act (worktree → profile-aware harness invocation → diff →
|
|
936
|
+
* checks → cleanup) lives ONCE in `runWorktreeHarness` (`../../mcp/worktree-harness`), shared with
|
|
937
|
+
* the `runLoop`/coder-delegate `createInProcessExecutor`. This executor only projects that core's
|
|
938
|
+
* result onto the `Executor` port (artifact + spend) and owns the teardown point. The §1.5 payload
|
|
939
|
+
* (authored systemPrompt + model) reaches the harness inside the core, not here.
|
|
940
|
+
*
|
|
941
|
+
* Token accounting: a harness CLI does not surface usage, so this executor is `budgetExempt: true`
|
|
942
|
+
* — its spend is NOT metered against the conserved pool and its iterations are EXCLUDED from the
|
|
943
|
+
* equal-k arms by construction (mirrors `cliExecutor`).
|
|
944
|
+
*/
|
|
945
|
+
|
|
946
|
+
/** Terminal artifact of one worktree-CLI run — the canonical worktree-harness result (the captured
|
|
947
|
+
* diff + the harness's run record + the derived checks). */
|
|
948
|
+
type WorktreePatchArtifact = WorktreeHarnessResult;
|
|
949
|
+
/** @experimental */
|
|
950
|
+
interface WorktreeCliExecutorOptions {
|
|
951
|
+
/** Absolute path to the git checkout the worktree is cut from. */
|
|
952
|
+
repoRoot: string;
|
|
953
|
+
/** The SUPERVISOR-AUTHORED profile (the §1.5 payload: systemPrompt + model). */
|
|
954
|
+
profile: AgentProfile;
|
|
955
|
+
/** Which local harness CLI drives this leaf (`claude` | `codex` | `opencode`). */
|
|
956
|
+
harness: LocalHarness;
|
|
957
|
+
/** The per-task instruction handed to the harness (composed under the system prompt). */
|
|
958
|
+
taskPrompt: string;
|
|
959
|
+
/** Unique id for the worktree path + branch. Defaults to a fresh UUID. */
|
|
960
|
+
runId?: string;
|
|
961
|
+
/** Override the base ref the worktree is cut from (default `HEAD`). */
|
|
962
|
+
baseRef?: string;
|
|
963
|
+
/** Wall-clock cap per harness subprocess (ms). Default 5 min (the `runLocalHarness` default). */
|
|
964
|
+
harnessTimeoutMs?: number;
|
|
965
|
+
/**
|
|
966
|
+
* Shell command run in the live worktree to derive the tests-PASS signal (e.g. `pnpm test`).
|
|
967
|
+
* Its exit code becomes `artifact.checks.tests.passed`. Omit to skip (no signal derived).
|
|
968
|
+
*/
|
|
969
|
+
testCmd?: string;
|
|
970
|
+
/** Shell command run in the live worktree to derive the typecheck-PASS signal (e.g. `pnpm typecheck`). */
|
|
971
|
+
typecheckCmd?: string;
|
|
972
|
+
/** Wall-clock cap per verification command (ms). Default = `harnessTimeoutMs` or 5 min. */
|
|
973
|
+
checkTimeoutMs?: number;
|
|
974
|
+
/** Test seam — inject a git runner so unit tests drive the worktree helpers without git. */
|
|
975
|
+
runGit?: GitRunner;
|
|
976
|
+
/** Test seam — inject the harness runner so unit tests script a `LocalHarnessResult`. */
|
|
977
|
+
runHarness?: typeof runLocalHarness;
|
|
978
|
+
/** Test seam — inject the verification-command runner so unit tests script test/typecheck
|
|
979
|
+
* outcomes without spawning a real shell. Defaults to a `/bin/sh -c` spawn in the worktree. */
|
|
980
|
+
runCommand?: WorktreeCheckRunner;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Build a worktree-CLI leaf `Executor`. Per-spawn (a fresh worktree + abort + teardown each), so a
|
|
984
|
+
* fanout of N profiles = N parallel worktrees that never clobber each other.
|
|
985
|
+
*
|
|
986
|
+
* Fail-loud: an empty `repoRoot`/`harness`/`taskPrompt` throws at construction. `resultArtifact()`
|
|
987
|
+
* before `execute()` resolves throws.
|
|
988
|
+
*
|
|
989
|
+
* @experimental
|
|
990
|
+
*/
|
|
991
|
+
declare function createWorktreeCliExecutor(options: WorktreeCliExecutorOptions): Executor<WorktreePatchArtifact>;
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* @experimental
|
|
995
|
+
*
|
|
996
|
+
* `patchDelivered` — the mechanical patch gate as a generic `DeliverableSpec` over the worktree-CLI
|
|
997
|
+
* artifact. It is one construction of a `DeliverableSpec` (a plain `check(artifact) => boolean`
|
|
998
|
+
* function); a domain customizes "is it done" by building its own spec, not by extending this one.
|
|
999
|
+
* The canonical use: a `fanout(createWorktreeCliExecutor)` of authored harness profiles, each
|
|
1000
|
+
* `gateOnDeliverable(patchDelivered(...))`.
|
|
1001
|
+
*
|
|
1002
|
+
* The checks themselves are NOT re-implemented here — `runCoderChecks` (`./patch-checks`) is the
|
|
1003
|
+
* single source of the no-op / always-on secret-path floor / forbidden-path / diff-size / test /
|
|
1004
|
+
* typecheck logic. This factory only adapts the `WorktreePatchArtifact` shape (its `checks` carry
|
|
1005
|
+
* the test/typecheck PASS signals the executor derived in the live worktree) into the check inputs
|
|
1006
|
+
* and returns the boolean the gate consumes.
|
|
1007
|
+
*
|
|
1008
|
+
* Test/typecheck enforcement is OPT-IN per `require`: when a command was not run in the worktree
|
|
1009
|
+
* (the executor's `testCmd`/`typecheckCmd` were omitted) the corresponding signal is treated as
|
|
1010
|
+
* passed UNLESS `require` lists it — so a gate that demands a tests-pass on an artifact that never
|
|
1011
|
+
* ran tests fails closed (the honest outcome) rather than passing on a missing signal.
|
|
1012
|
+
*/
|
|
1013
|
+
|
|
1014
|
+
/** @experimental */
|
|
1015
|
+
interface PatchDeliverableOptions extends CoderCheckConstraints {
|
|
1016
|
+
/**
|
|
1017
|
+
* Which verification signals the gate REQUIRES to be present-and-passing. A required signal
|
|
1018
|
+
* that the artifact never derived (the command was not configured on the executor) fails the
|
|
1019
|
+
* gate closed. Unlisted signals default to passed-when-absent (the executor simply didn't run
|
|
1020
|
+
* that command). Default `[]` — gate on no-op / secret / forbidden / diff-size only.
|
|
1021
|
+
*/
|
|
1022
|
+
require?: ReadonlyArray<'tests' | 'typecheck'>;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Build the `DeliverableSpec<WorktreePatchArtifact>`: `check(artifact)` runs the shared mechanical
|
|
1026
|
+
* gate (`runCoderChecks`) over the captured patch + the worktree-derived pass signals and returns
|
|
1027
|
+
* whether the patch is DELIVERED (the `valid` conjunction).
|
|
1028
|
+
*
|
|
1029
|
+
* @experimental
|
|
1030
|
+
*/
|
|
1031
|
+
declare function patchDelivered(options?: PatchDeliverableOptions): DeliverableSpec<WorktreePatchArtifact>;
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* @experimental
|
|
1035
|
+
*
|
|
1036
|
+
* `worktreeFanout` — the GENERIC coding combinator: a `fanout` of N supervisor-authored harness
|
|
1037
|
+
* profiles, each on its OWN worktree-CLI leaf, each `gateOnDeliverable(deliverable)`, winner via the
|
|
1038
|
+
* shared `selectValidWinner` (valid-only — an ungated patch never wins, the deliverable gate is the
|
|
1039
|
+
* point). The `deliverable` is passed as DATA: it defaults to `patchDelivered(opts)` but any
|
|
1040
|
+
* `DeliverableSpec<WorktreePatchArtifact>` a domain authors slots in unchanged.
|
|
1041
|
+
*
|
|
1042
|
+
* The shape is content-free at the `fanout` layer; this builder only assembles the pieces (the
|
|
1043
|
+
* worktree-CLI executor + the injected deliverable + the diff-size selector) into the generic
|
|
1044
|
+
* combinator's `itemSpec`/`selectWinner` seams. Nothing here re-implements selection, gating, or
|
|
1045
|
+
* fanout — it composes the existing primitives.
|
|
1046
|
+
*/
|
|
1047
|
+
|
|
1048
|
+
/** @experimental One authored harness profile in a worktree fanout: the §1.5 profile + which local
|
|
1049
|
+
* harness CLI drives it. The supervisor authors `profile` per sub-task; `harness` chooses the leaf. */
|
|
1050
|
+
interface AuthoredHarness {
|
|
1051
|
+
/** A short label for the worktree branch + trace node. */
|
|
1052
|
+
name: string;
|
|
1053
|
+
/** The supervisor-authored `AgentProfile` (systemPrompt + model reach the harness via §1.5). */
|
|
1054
|
+
profile: AgentProfile;
|
|
1055
|
+
/** Which local harness CLI drives this leaf. */
|
|
1056
|
+
harness: 'claude' | 'codex' | 'opencode';
|
|
1057
|
+
/** Per-harness model/runId/baseRef overrides flow through the profile + these. */
|
|
1058
|
+
runId?: string;
|
|
1059
|
+
baseRef?: string;
|
|
1060
|
+
}
|
|
1061
|
+
/** @experimental */
|
|
1062
|
+
interface WorktreeFanoutOptions extends PatchDeliverableOptions {
|
|
1063
|
+
/** Absolute path to the git checkout each worktree is cut from. */
|
|
1064
|
+
repoRoot: string;
|
|
1065
|
+
/** The per-task instruction handed to every harness (composed under each profile's systemPrompt). */
|
|
1066
|
+
taskPrompt: string;
|
|
1067
|
+
/** The authored harness profiles — one fanout item (and one worktree-CLI leaf) each. */
|
|
1068
|
+
harnesses: ReadonlyArray<AuthoredHarness>;
|
|
1069
|
+
/**
|
|
1070
|
+
* The completion check each leaf is gated on. Defaults to `patchDelivered(opts)` (the mechanical
|
|
1071
|
+
* no-op/secret/forbidden/diff-size + required test/typecheck gate). Pass any
|
|
1072
|
+
* `DeliverableSpec<WorktreePatchArtifact>` to customize "is it delivered" as DATA.
|
|
1073
|
+
*/
|
|
1074
|
+
deliverable?: DeliverableSpec<WorktreePatchArtifact>;
|
|
1075
|
+
/** Shell command run in each worktree to derive the tests-PASS signal. */
|
|
1076
|
+
testCmd?: string;
|
|
1077
|
+
/** Shell command run in each worktree to derive the typecheck-PASS signal. */
|
|
1078
|
+
typecheckCmd?: string;
|
|
1079
|
+
/** Wall-clock cap per harness subprocess (ms). */
|
|
1080
|
+
harnessTimeoutMs?: number;
|
|
1081
|
+
/** Winner-selection strategy. Default `highest-score`. */
|
|
1082
|
+
winnerStrategy?: WinnerStrategy;
|
|
1083
|
+
/** Test seams forwarded to every worktree-CLI leaf (inject git/harness/command runners so the
|
|
1084
|
+
* whole fanout runs offline). Production callers leave these unset. */
|
|
1085
|
+
runGit?: WorktreeCliExecutorOptions['runGit'];
|
|
1086
|
+
runHarness?: WorktreeCliExecutorOptions['runHarness'];
|
|
1087
|
+
runCommand?: WorktreeCliExecutorOptions['runCommand'];
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Build the worktree fanout combinator. Run it with `runPersonified({ persona, shape, task, budget })`
|
|
1091
|
+
* — equal-k holds by construction (the conserved budget pool bounds the N leaves), and selection is
|
|
1092
|
+
* the shared valid-only `selectValidWinner` (never a judge).
|
|
1093
|
+
*
|
|
1094
|
+
* @experimental
|
|
1095
|
+
*/
|
|
1096
|
+
declare function worktreeFanout<Task>(options: WorktreeFanoutOptions): CombinatorShape<Task, WorktreePatchArtifact>;
|
|
1097
|
+
|
|
1098
|
+
export { type TrajectoryNode as $, type AuthoredHarness as A, type FlatWidenGate as B, type CorpusRecord as C, type DefinePersonaInput as D, type EqualKArm as E, type FanoutOptions as F, type LoopUntil as G, type LoopUntilState as H, type Panel as I, type PanelJudge as J, type PanelVerdict as K, type LoopUntilSpec as L, type PatchDeliverableOptions as M, type PersonaContext as N, type Outcome as O, type PanelSpec as P, type PersonaExecutors as Q, type RenderCorpusToInstructionsOptions as R, type ScopeAnalyzeInput as S, type TrajectoryReportOptions as T, type Pipeline as U, type VerifySpec as V, type WinnerStrategy as W, type RenderCorpusToInstructions as X, type RunPersonified as Y, type ShapeBudget as Z, type ShapeContext as _, type WorktreeFanoutOptions as a, type TrajectoryReportFn as a0, type Verify as a1, type Widen as a2, type WidenDecision as a3, type WidenLineage as a4, type WorktreeCliExecutorOptions as a5, type WorktreeCommandResult as a6, countDiffLines as a7, createWorktreeCliExecutor as a8, gateOnDeliverable as a9, isNonEmptyPatch as aa, patchDelivered as ab, runCoderChecks as ac, touchedPathsFromPatch as ad, touchesSecretPath as ae, worktreeFanout as af, type WorktreePatchArtifact as b, type Corpus as c, type AssertTraceDerivedFindings as d, type SteerContext as e, type ScopeAnalyst as f, type CombinatorShape as g, type ScopeWidenGate as h, type PipelineStage as i, type FanoutWinnerSelector as j, type WidenSpec as k, type CorpusFilter as l, type Persona as m, type RunPersonifiedOptions as n, type ShapeRegistry as o, type LoopShape as p, type EqualKOnCostOptions as q, type EqualKVerdict as r, type TrajectoryReport as s, type DeliverableSpec as t, type CoderCheckConstraints as u, type CoderCheckInput as v, type DefinePersona as w, type EqualKOnCost as x, type Fanout as y, type FanoutSynthesis as z };
|