@tangle-network/agent-runtime 0.73.0 → 0.74.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.
Files changed (44) hide show
  1. package/dist/agent.d.ts +34 -2
  2. package/dist/agent.js +3 -2
  3. package/dist/agent.js.map +1 -1
  4. package/dist/analyst-loop.d.ts +1 -1
  5. package/dist/chunk-JPURCA2O.js +266 -0
  6. package/dist/chunk-JPURCA2O.js.map +1 -0
  7. package/dist/{chunk-7ODB76J5.js → chunk-MRWXCFV5.js} +2 -2
  8. package/dist/chunk-O2UPHN7X.js +114 -0
  9. package/dist/chunk-O2UPHN7X.js.map +1 -0
  10. package/dist/{chunk-U56XGKVY.js → chunk-QKNBYHMK.js} +3 -3
  11. package/dist/{chunk-HPYWEFVY.js → chunk-SA5GCF2X.js} +2 -2
  12. package/dist/{chunk-NCH4XUZ7.js → chunk-ZKMOIEOB.js} +13 -119
  13. package/dist/chunk-ZKMOIEOB.js.map +1 -0
  14. package/dist/{coordination-DU0saWeg.d.ts → coordination-DEVknvQo.d.ts} +3 -2
  15. package/dist/generator-YkAQrOoD.d.ts +382 -0
  16. package/dist/index.d.ts +13 -167
  17. package/dist/index.js +15 -262
  18. package/dist/index.js.map +1 -1
  19. package/dist/intelligence.d.ts +1 -1
  20. package/dist/lifecycle.d.ts +787 -200
  21. package/dist/lifecycle.js +814 -9
  22. package/dist/lifecycle.js.map +1 -1
  23. package/dist/local-harness-BE_h8szs.d.ts +93 -0
  24. package/dist/{loop-runner-bin-eD3m0rHW.d.ts → loop-runner-bin-BPthX22l.d.ts} +2 -2
  25. package/dist/loop-runner-bin.d.ts +6 -5
  26. package/dist/loop-runner-bin.js +4 -3
  27. package/dist/loops.d.ts +10 -9
  28. package/dist/loops.js +3 -2
  29. package/dist/mcp/bin.js +2 -1
  30. package/dist/mcp/bin.js.map +1 -1
  31. package/dist/mcp/index.d.ts +8 -6
  32. package/dist/mcp/index.js +6 -4
  33. package/dist/mcp/index.js.map +1 -1
  34. package/dist/mcp-serve-verifier-CT1KLTG_.d.ts +162 -0
  35. package/dist/{openai-tools-CBurv8Cu.d.ts → openai-tools-D-VCAEmw.d.ts} +1 -1
  36. package/dist/profiles.d.ts +1 -1
  37. package/dist/{types-JufmXF2a.d.ts → types-YimN9PQP.d.ts} +1 -1
  38. package/dist/{worktree-DaxOvw-C.d.ts → worktree-CtuEQ7bZ.d.ts} +2 -93
  39. package/dist/{worktree-fanout-DIffZohV.d.ts → worktree-fanout-Cdez8GR7.d.ts} +3 -2
  40. package/package.json +1 -1
  41. package/dist/chunk-NCH4XUZ7.js.map +0 -1
  42. /package/dist/{chunk-7ODB76J5.js.map → chunk-MRWXCFV5.js.map} +0 -0
  43. /package/dist/{chunk-U56XGKVY.js.map → chunk-QKNBYHMK.js.map} +0 -0
  44. /package/dist/{chunk-HPYWEFVY.js.map → chunk-SA5GCF2X.js.map} +0 -0
@@ -1,105 +1,11 @@
1
- import { AgentProfileResourceRef, AgentProfileMcpServer, AgentProfileHookCommand, AgentSubagentProfile, AgentProfile } from '@tangle-network/agent-interface';
2
-
3
- /**
4
- * `@tangle-network/agent-runtime/lifecycle` — artifact-lifecycle FOUNDATION.
5
- *
6
- * The §1.5 law says an agent IS its `AgentProfile`, and the profile is the WHOLE
7
- * agent: prompt + skills + tools + mcp + hooks + subagents. This module names the
8
- * discrete, individually-promotable PIECES of that profile — "artifacts" — and
9
- * gives them stable ids so the rest of a self-improvement lifecycle (propose →
10
- * measure → promote → ship) has something concrete to hang off.
11
- *
12
- * This is PHASE 1: just the two primitives the rest hangs off.
13
- * 1. `ArtifactRegistry` — a typed catalog of profile artifacts with stable ids.
14
- * 2. `measureMarginalLift` — the with-vs-without ablation: how much score/cost a
15
- * single artifact adds on top of a baseline profile.
16
- *
17
- * The per-surface lifecycles, the `BuildableSurface` author contract, and the
18
- * promotion-gate wiring are deferred to later phases.
19
- */
20
-
21
- /**
22
- * The profile levers an artifact can target. One-to-one with the §1.5 profile
23
- * surface (`prompt + skills + tools + mcp + hooks + subagents`). Each kind maps to
24
- * exactly one field of `AgentProfile`, so an artifact can be applied onto a
25
- * baseline profile deterministically (see `applyArtifact`).
26
- */
27
- type ArtifactKind = 'skill' | 'tool' | 'mcp' | 'hook' | 'subagent' | 'prompt';
28
- /**
29
- * The payload for each `ArtifactKind`. The shapes are the SAME types the
30
- * `AgentProfile` field carries, so applying an artifact is a structural merge
31
- * onto the profile — never a bespoke per-kind transform.
32
- *
33
- * - `prompt` — an instruction line appended to `profile.prompt.instructions`.
34
- * - `skill` — a `SKILL.md`-style resource ref added to `profile.resources.skills`.
35
- * - `tool` — a tool grant: `{ enabled }` set under `profile.tools[name]`.
36
- * - `mcp` — one MCP server added under `profile.mcp[name]`.
37
- * - `hook` — one or more hook commands added under `profile.hooks[event]`.
38
- * - `subagent` — one subagent profile added under `profile.subagents[name]`.
39
- */
40
- interface ArtifactPayloads {
41
- prompt: {
42
- instruction: string;
43
- };
44
- skill: {
45
- resource: AgentProfileResourceRef;
46
- };
47
- tool: {
48
- enabled: boolean;
49
- };
50
- mcp: {
51
- server: AgentProfileMcpServer;
52
- };
53
- hook: {
54
- event: string;
55
- commands: AgentProfileHookCommand[];
56
- };
57
- subagent: {
58
- profile: AgentSubagentProfile;
59
- };
60
- }
61
- /**
62
- * A discrete, individually-promotable piece of an agent profile.
63
- *
64
- * `kind` selects the profile lever; `payload` is the kind-specific value; `key`
65
- * is the profile-field key the payload lands under (the tool name, the MCP server
66
- * name, the subagent name — unused for `prompt`, which appends). `id` is stable:
67
- * once registered, it never changes, so a marginal-lift measurement, a promotion
68
- * decision, and a ship record all reference the same artifact.
69
- */
70
- interface ProfileArtifact<K extends ArtifactKind = ArtifactKind> {
71
- /** Stable id. Assigned by the registry at register time; never reassigned. */
72
- id: string;
73
- kind: K;
74
- /**
75
- * The profile-field key this artifact lands under (e.g. the tool name, the MCP
76
- * server name, the subagent name, the hook event). Optional for `prompt`
77
- * (instructions append, they have no key). Defaults to `id` when applying a
78
- * keyed artifact without an explicit key.
79
- */
80
- key?: string;
81
- /** Human-facing label for review surfaces. */
82
- name: string;
83
- /** Optional one-line description of what this artifact does. */
84
- description?: string;
85
- payload: ArtifactPayloads[K];
86
- /**
87
- * Lifecycle status. Phase 1 tracks only `candidate` (registered, not yet
88
- * promoted) and `promoted` (passed whatever gate the caller ran). The
89
- * registry never auto-promotes; promotion is an explicit `promote(id)` call.
90
- */
91
- status: ArtifactStatus;
92
- /** Free-form metadata (provenance, generation id, the measured lift, …). */
93
- metadata?: Record<string, unknown>;
94
- }
95
- type ArtifactStatus = 'candidate' | 'promoted';
96
- /** The input to `register` — everything on `ProfileArtifact` except the
97
- * registry-owned `id` and `status`. An explicit `id` may be supplied for
98
- * deterministic/idempotent registration; otherwise the registry assigns one. */
99
- type ArtifactInput<K extends ArtifactKind = ArtifactKind> = Omit<ProfileArtifact<K>, 'id' | 'status'> & {
100
- id?: string;
101
- status?: ArtifactStatus;
102
- };
1
+ import { AgentProfile } from '@tangle-network/agent-interface';
2
+ import { a as ProfileArtifact, A as ArtifactKind, b as ArtifactStatus, c as ArtifactInput, E as EvalRunner, d as EvalResult, G as GenerateContext, C as CandidateGenerator, e as PromotionVerdict, P as PromotionGate } from './generator-YkAQrOoD.js';
3
+ export { f as ArtifactPayloads, H as HeldOutPromotionGateOptions, M as MarginalLift, g as MeasureMarginalLiftOptions, h as heldOutPromotionGate, m as measureMarginalLift, t as thresholdPromotionGate } from './generator-YkAQrOoD.js';
4
+ import { LlmClientOptions, AnalystFinding } from '@tangle-network/agent-eval';
5
+ import { M as McpServeSpec } from './mcp-serve-verifier-CT1KLTG_.js';
6
+ import { L as LocalHarness } from './local-harness-BE_h8szs.js';
7
+ import '@tangle-network/agent-eval/campaign';
8
+ import 'node:child_process';
103
9
 
104
10
  /**
105
11
  * `applyArtifact` — merge one `ProfileArtifact` onto an `AgentProfile`.
@@ -123,98 +29,6 @@ declare function applyArtifact(base: AgentProfile, artifact: ProfileArtifact): A
123
29
  /** Apply many artifacts left-to-right; later artifacts win on key conflicts. */
124
30
  declare function applyArtifacts(base: AgentProfile, artifacts: readonly ProfileArtifact[]): AgentProfile;
125
31
 
126
- /**
127
- * `measureMarginalLift` — the with-vs-without ablation for one artifact.
128
- *
129
- * "What does THIS one piece add?" is the question a lifecycle has to answer
130
- * before it can promote anything. The answer is an ablation: score the baseline
131
- * profile, score the baseline-plus-candidate profile, and report the delta. This
132
- * is the marginal contribution of the artifact — the same shape the project's
133
- * `OutcomeMeasurement` reports for an apply pass, but isolated to ONE artifact so
134
- * a registry can rank candidates by what they individually earn.
135
- *
136
- * The measurement is selector-agnostic: it takes an `EvalRunner` the caller
137
- * supplies (any function that scores a profile and reports cost), runs it twice,
138
- * and subtracts. It never judges, never picks a winner, never re-scores on a
139
- * holdout — those are the gate's job. It only quantifies the ablation so the gate
140
- * has a real number to decide on.
141
- */
142
-
143
- /**
144
- * The result of running an eval over ONE profile: a composite score and the cost
145
- * to obtain it. This mirrors the project's score/cost convention (`composite`
146
- * from `OutcomeMeasurement`, `costUsd` from `LoopResult`), so a caller can pass a
147
- * thin wrapper over `runLoop` / `runBenchmark` / `runAgentEval` directly.
148
- */
149
- interface EvalResult {
150
- /** Composite score in `[0, 1]` (higher is better) for the profile under test. */
151
- composite: number;
152
- /** USD cost to produce this result. */
153
- costUsd: number;
154
- /** Optional opaque passthrough (per-task cells, the raw report, …). */
155
- details?: unknown;
156
- }
157
- /**
158
- * Scores a profile. The caller wires this to whatever eval they run — a
159
- * `runLoop` rollout, a `runBenchmark` campaign, a `runAgentEval` cohort — and
160
- * returns the composite + cost. `signal` is forwarded for cancellation.
161
- */
162
- type EvalRunner = (profile: AgentProfile, signal?: AbortSignal) => Promise<EvalResult>;
163
- interface MeasureMarginalLiftOptions {
164
- /** The profile the artifact is measured ON TOP OF (the "without" arm). */
165
- baseline: AgentProfile;
166
- /** The single artifact whose marginal contribution we want. */
167
- candidate: ProfileArtifact;
168
- /** The eval that scores a profile. Run once per arm (twice total). */
169
- evalRunner: EvalRunner;
170
- /**
171
- * A pre-computed baseline result, to skip the "without" run when the caller
172
- * already scored the baseline (e.g. measuring several candidates against the
173
- * same baseline). When set, the baseline arm is NOT re-run.
174
- */
175
- baselineResult?: EvalResult;
176
- /** Forwarded to both `evalRunner` invocations for cancellation. */
177
- signal?: AbortSignal;
178
- }
179
- /**
180
- * The marginal lift of one artifact: the with/without ablation.
181
- *
182
- * `scoreDelta = with.composite − without.composite`. A positive `scoreDelta` is
183
- * the evidence a gate needs to promote; a negative one is the signal to drop the
184
- * artifact. `costDelta` is the extra USD the artifact costs (often positive — a
185
- * new tool/MCP adds calls) and lets the gate weigh lift against spend.
186
- */
187
- interface MarginalLift {
188
- /** The artifact id this measurement is for (stable, from the registry). */
189
- artifactId: string;
190
- /** Eval of `applyArtifact(baseline, candidate)`. */
191
- withArtifact: EvalResult;
192
- /** Eval of `baseline` alone. */
193
- withoutArtifact: EvalResult;
194
- /** `withArtifact.composite − withoutArtifact.composite`. */
195
- scoreDelta: number;
196
- /** `withArtifact.costUsd − withoutArtifact.costUsd`. */
197
- costDelta: number;
198
- }
199
- /**
200
- * Run the with/without ablation for `candidate` over `baseline` and return its
201
- * marginal score/cost contribution.
202
- *
203
- * The "without" arm scores the baseline profile unchanged; the "with" arm scores
204
- * `applyArtifact(baseline, candidate)`. Both use the same `evalRunner`, so the
205
- * delta isolates the artifact's effect (eval method held constant). The baseline
206
- * arm is skipped when `baselineResult` is supplied.
207
- *
208
- * @example
209
- * const lift = await measureMarginalLift({
210
- * baseline,
211
- * candidate: registry.get(id)!,
212
- * evalRunner: (profile) => scoreProfileOnCohort(profile),
213
- * })
214
- * if (lift.scoreDelta > 0) registry.promote(lift.artifactId)
215
- */
216
- declare function measureMarginalLift(opts: MeasureMarginalLiftOptions): Promise<MarginalLift>;
217
-
218
32
  /**
219
33
  * `ArtifactRegistry` — a typed catalog of profile artifacts with stable ids.
220
34
  *
@@ -230,6 +44,21 @@ interface ArtifactQuery {
230
44
  kind?: ArtifactKind;
231
45
  status?: ArtifactStatus;
232
46
  }
47
+ /**
48
+ * The metadata key under which the registry stores an artifact's measured held-
49
+ * back lift. This is the registry INVARIANT's anchor: an artifact is `active`
50
+ * IFF this key holds a finite number — see `promoteWithLift` and `liftOf`. The
51
+ * lifecycle never promotes by status flag alone; the lift score is the receipt.
52
+ * `driftWatch` overwrites it with the latest re-measure so `liftOf` always
53
+ * reflects the most recent evidence.
54
+ */
55
+ declare const liftMetadataKey = "measuredLift";
56
+ /**
57
+ * The metadata key under which the registry records WHY an artifact left the
58
+ * active set — the human-readable reason a `demote` (→ decayed) or `retire`
59
+ * (→ retired) carried. Kept so a demotion/retirement is auditable, not silent.
60
+ */
61
+ declare const lifecycleReasonKey = "lifecycleReason";
233
62
  /**
234
63
  * A typed, in-memory registry of `ProfileArtifact`s with stable ids.
235
64
  *
@@ -256,18 +85,62 @@ declare class ArtifactRegistry {
256
85
  */
257
86
  list(query?: ArtifactQuery): ProfileArtifact[];
258
87
  /**
259
- * Mark an artifact `promoted`. Fails loud on an unknown id — promoting a
88
+ * Mark an artifact `active`. Fails loud on an unknown id — promoting a
260
89
  * non-existent artifact is a caller bug, not a no-op. Returns the updated
261
- * record. Idempotent: promoting an already-promoted artifact is a no-op
262
- * return.
90
+ * record. Idempotent: promoting an already-active artifact is a no-op return.
91
+ *
92
+ * NOTE: the artifact-lifecycle INVARIANT (no measured lift ⇒ not active) is
93
+ * enforced by `promoteWithLift`, the path the closed loop uses. This bare
94
+ * `promote` exists for callers that gate elsewhere and just flip the flag; it
95
+ * does NOT record a lift score, so `liftOf` returns `undefined` and a
96
+ * lift-ranked `composeProfile` will skip it. Prefer `promoteWithLift`.
263
97
  */
264
98
  promote(id: string): ProfileArtifact;
99
+ /**
100
+ * Promote an artifact AND record the measured held-back lift that earned it.
101
+ * This is the closed loop's promotion path and the enforcement point of the
102
+ * lifecycle invariant: an artifact becomes `active` only WITH a finite lift
103
+ * number stamped under `liftMetadataKey`. A non-finite `lift` (NaN/Infinity)
104
+ * fails loud — promoting on a broken measurement is exactly the silent-zero the
105
+ * doctrine forbids. Re-promotes a `decayed` artifact whose lift recovered.
106
+ * Returns the updated record.
107
+ */
108
+ promoteWithLift(id: string, lift: number): ProfileArtifact;
109
+ /**
110
+ * Demote an `active` artifact to `decayed`: it was promoted, but a later
111
+ * re-measure (`driftWatch`) found its held-back lift fell below the keep-bar.
112
+ * Records the latest re-measured `lift` (so `liftOf` reflects current evidence)
113
+ * and the `reason` (so the demotion is auditable). The artifact stays in the
114
+ * registry — `decayed`, not deleted — so it can be re-promoted if a future
115
+ * re-measure recovers the lift. Fails loud on an unknown id. Demoting a
116
+ * non-`active` artifact fails loud too: only the active set decays.
117
+ */
118
+ demote(id: string, reason: string, lift?: number): ProfileArtifact;
119
+ /**
120
+ * Retire an artifact to the terminal `retired` state: it is permanently out of
121
+ * the active set (`dedupeArtifacts` retires the weaker half of a non-stacking
122
+ * pair). Records the `reason` for the audit trail. Unlike `demote`, this is
123
+ * terminal — a retired artifact is never re-promoted by the loop. Idempotent on
124
+ * an already-retired artifact; fails loud on an unknown id.
125
+ */
126
+ retire(id: string, reason: string): ProfileArtifact;
127
+ /**
128
+ * The measured held-back lift recorded at promotion time (and overwritten by
129
+ * the latest `driftWatch` re-measure), or `undefined` when the artifact was
130
+ * never promoted WITH a lift (a fresh candidate, or one promoted via the bare
131
+ * `promote`). The lifecycle invariant in one accessor: `liftOf(id) ===
132
+ * undefined` ⇒ the artifact has no measured lift ⇒ it is not eligible for a
133
+ * lift-ranked compose. Note this returns the recorded lift regardless of status
134
+ * — `composeProfile` separately filters to `active`, so a `decayed` artifact's
135
+ * stale lift is visible for audit but never folded into a profile.
136
+ */
137
+ liftOf(id: string): number | undefined;
265
138
  /**
266
139
  * Compose a set of registered artifacts onto a baseline profile. With no ids
267
- * given, composes every `promoted` artifact (the "ship the passing set"
140
+ * given, composes every `active` artifact (the "ship the passing set"
268
141
  * default). With explicit ids, composes exactly those (in id order given),
269
142
  * failing loud on any unknown id. The applied order is the order passed (or
270
- * registration order for the promoted-default), and later artifacts win on key
143
+ * registration order for the active-default), and later artifacts win on key
271
144
  * conflicts — same semantics as `applyArtifacts`.
272
145
  */
273
146
  compose(base: AgentProfile, ids?: readonly string[]): AgentProfile;
@@ -278,4 +151,718 @@ declare class ArtifactRegistry {
278
151
  /** Construct an empty `ArtifactRegistry`. */
279
152
  declare function createArtifactRegistry(): ArtifactRegistry;
280
153
 
281
- export { type ArtifactInput, type ArtifactKind, type ArtifactPayloads, type ArtifactQuery, ArtifactRegistry, type ArtifactStatus, type EvalResult, type EvalRunner, type MarginalLift, type MeasureMarginalLiftOptions, type ProfileArtifact, applyArtifact, applyArtifacts, createArtifactRegistry, measureMarginalLift };
154
+ /**
155
+ * `composeProfile` — fold the top-k active artifacts back into a profile.
156
+ *
157
+ * This is the "give it all the passing options at once" step. After the loop has
158
+ * generated, measured, and promoted artifacts, `composeProfile` selects the
159
+ * highest-lift ACTIVE ones (those promoted WITH a measured held-back lift) and
160
+ * applies them onto a baseline profile via the single `applyArtifact` bridge.
161
+ *
162
+ * It differs from the registry's raw `compose` in two ways the lifecycle needs:
163
+ * 1. It ranks by MEASURED LIFT and takes the top `k`, rather than applying
164
+ * every promoted artifact in registration order. The best pieces go in
165
+ * first; a `k` budget keeps the composed profile from accreting marginal
166
+ * wins without bound.
167
+ * 2. It enforces the lifecycle INVARIANT — only artifacts with a finite
168
+ * `liftOf` are eligible. An artifact flipped to `promoted` without a lift
169
+ * receipt is invisible here, by construction.
170
+ */
171
+
172
+ interface ComposeProfileOptions {
173
+ /** Cap on how many artifacts to fold in. Default: all eligible. */
174
+ k?: number;
175
+ /** Restrict to one surface (e.g. only fold in skills). Default: all kinds. */
176
+ kind?: ArtifactKind;
177
+ }
178
+ /**
179
+ * Return a new profile with the top-`k` active artifacts (highest measured lift
180
+ * first) applied onto `base`.
181
+ *
182
+ * "Active" = promoted WITH a finite measured lift (`registry.liftOf` returns a
183
+ * number) — the lifecycle invariant. Ties in lift fall back to registration
184
+ * order (stable). With no `k`, every eligible artifact is folded in.
185
+ *
186
+ * @param registry the catalog the loop populated
187
+ * @param base the baseline profile to fold artifacts onto (the empty profile
188
+ * on a cold start)
189
+ * @param opts `k` (top-k budget) and an optional `kind` filter
190
+ *
191
+ * @example Cold start — fold the single best distilled skill back in:
192
+ * const composed = composeProfile(registry, emptyProfile, { kind: 'skill', k: 1 })
193
+ */
194
+ declare function composeProfile(registry: ArtifactRegistry, base: AgentProfile, opts?: ComposeProfileOptions): AgentProfile;
195
+
196
+ /**
197
+ * `dedupeArtifacts` — retire the redundant half of a non-stacking pair.
198
+ *
199
+ * The lifecycle promotes each artifact on its OWN marginal lift, in isolation.
200
+ * But two artifacts can each earn lift alone yet teach the agent the SAME thing —
201
+ * a distilled "check state first" skill and a "verify before acting" prompt line,
202
+ * say. Composed together they don't add up: the agent already learned the tactic
203
+ * from the first, so the second buys little. Keeping both is wasted profile
204
+ * surface (more context, more cost, more ways to conflict) for no extra score.
205
+ *
206
+ * `dedupeArtifacts` is the judge that catches this. For each pair of `active`
207
+ * artifacts it measures whether their lifts STACK: it scores the baseline, each
208
+ * artifact alone, and BOTH together, then compares the combined lift against the
209
+ * sum of the individual lifts. When `combined < (a + b) − tolerance`, the pair
210
+ * overlaps — they do not stack — and the weaker member (lower individual lift) is
211
+ * `retire`d. Retirement is terminal: the kept member already delivers the shared
212
+ * value, so the loop should not re-promote the redundant one.
213
+ *
214
+ * The "judge" is a MEASUREMENT, not an LLM verdict — it reuses the same ablation
215
+ * machinery (`measureMarginalLift` + the caller's `EvalRunner`) the rest of the
216
+ * lifecycle runs on, so the selector≠judge firewall holds and there is no new
217
+ * execution model. It is surface-agnostic: any two artifacts (skill+prompt,
218
+ * tool+tool, …) are compared identically, because stacking is measured on the
219
+ * composed profile via the one `applyArtifacts` bridge.
220
+ */
221
+
222
+ interface DedupeOptions {
223
+ /** The registry whose `active` artifacts are pairwise stack-tested. Mutated in
224
+ * place: the weaker member of each non-stacking pair is retired. */
225
+ registry: ArtifactRegistry;
226
+ /** The baseline profile the stacking ablation runs on top of. The "without"
227
+ * arm is scored once and shared across every pair. */
228
+ baseline: AgentProfile;
229
+ /** Scores a profile on the held-back split. Called for the baseline, each
230
+ * artifact alone, and each candidate pair together. */
231
+ evalRunner: EvalRunner;
232
+ /**
233
+ * The stacking tolerance. A pair is judged non-stacking (redundant) when the
234
+ * combined lift falls SHORT of the sum of individual lifts by more than this:
235
+ * `combined < a + b − tolerance`. A small positive tolerance absorbs eval
236
+ * noise so only a real overlap retires an artifact. Default 0 — any shortfall
237
+ * counts as non-stacking (use a positive value on noisy live evals).
238
+ */
239
+ tolerance?: number;
240
+ /** Restrict dedupe to one surface (only compare skills against skills, …).
241
+ * Default: every `active` artifact is a candidate, across kinds. */
242
+ kind?: ProfileArtifact['kind'];
243
+ /** A pre-computed baseline result, to skip the shared "without" run. */
244
+ baselineResult?: EvalResult;
245
+ /** Cooperative cancellation, forwarded to every `evalRunner` call. */
246
+ signal?: AbortSignal;
247
+ }
248
+ /** The stacking verdict for one pair of active artifacts. */
249
+ interface PairStackCheck {
250
+ /** Ids of the two artifacts compared, in (a, b) order as examined. */
251
+ pair: [string, string];
252
+ /** Individual held-back lift of the first artifact alone (re-measured). */
253
+ liftA: number;
254
+ /** Individual held-back lift of the second artifact alone (re-measured). */
255
+ liftB: number;
256
+ /** Held-back lift of BOTH artifacts composed together. */
257
+ combinedLift: number;
258
+ /** `combinedLift − (liftA + liftB)`: ≥ −tolerance ⇒ they stack; below ⇒ they
259
+ * overlap (redundant). */
260
+ stackGap: number;
261
+ /** Whether the pair was judged non-stacking (redundant). */
262
+ redundant: boolean;
263
+ /** The id retired when redundant (the weaker member), else `undefined`. */
264
+ retiredId?: string;
265
+ }
266
+ interface DedupeResult {
267
+ /** One verdict per examined pair, in iteration order. Pairs where one member
268
+ * was already retired by an earlier pair this cycle are skipped. */
269
+ checks: PairStackCheck[];
270
+ /** Ids retired this cycle (the weaker member of each non-stacking pair). */
271
+ retired: string[];
272
+ /** The shared baseline eval (the "without" arm, measured once). */
273
+ baselineResult: EvalResult;
274
+ }
275
+ /**
276
+ * Pairwise stack-test the `active` artifacts and retire the redundant half of
277
+ * each non-stacking pair.
278
+ *
279
+ * For every unordered pair of active artifacts (optionally within one `kind`),
280
+ * the combined lift is compared against the sum of individual lifts. A pair is
281
+ * non-stacking when `combinedLift < liftA + liftB − tolerance`; the lower-lift
282
+ * member is retired (ties retire the second-examined). An artifact retired by one
283
+ * pair is removed from the remaining comparisons that cycle, so a cluster of
284
+ * three mutually-redundant artifacts collapses to its single strongest member.
285
+ *
286
+ * Cost is one shared baseline run, one re-measure per active artifact (cached
287
+ * across the pairs it appears in), and one combined run per still-eligible pair.
288
+ *
289
+ * @example Retire skills that teach the same tactic as a stronger one:
290
+ * const out = await dedupeArtifacts({ registry, baseline, evalRunner, kind: 'skill' })
291
+ * if (out.retired.length) report(`retired ${out.retired.length} redundant skills`)
292
+ */
293
+ declare function dedupeArtifacts(opts: DedupeOptions): Promise<DedupeResult>;
294
+
295
+ /**
296
+ * `driftWatch` — the scheduled re-measure that DEMOTES decayed artifacts.
297
+ *
298
+ * Promotion is a one-time decision on the evidence available THEN; the world
299
+ * moves on. A skill that earned its lift against last month's task mix, a tool
300
+ * grant the underlying model has since internalized, a prompt line another active
301
+ * artifact now subsumes — each can quietly stop pulling its weight. An artifact
302
+ * that no longer earns its keep but stays `active` is dead weight in every
303
+ * composed profile and a lie in the registry's lift receipt.
304
+ *
305
+ * `driftWatch` closes that gap. It re-runs the SAME `measureMarginalLift`
306
+ * ablation the loop used to promote each `active` artifact — over the CURRENT
307
+ * baseline and the CURRENT eval — and demotes any whose re-measured held-back
308
+ * lift fell below the keep-bar. Demotion moves the artifact `active` → `decayed`
309
+ * (reversible: a later re-measure that recovers the lift can re-promote it), so
310
+ * it drops out of `composeProfile` immediately while staying in the registry as
311
+ * an auditable record.
312
+ *
313
+ * This is NOT a new execution model — it is the existing ablation, run on a
314
+ * schedule, against the active set. The schedule itself is the caller's cron;
315
+ * this module is the one re-measure-and-demote step that cron invokes. It is
316
+ * surface-agnostic (skills, tools, prompts, MCP all re-measure identically) — the
317
+ * only per-surface logic lives, as everywhere in the lifecycle, behind the
318
+ * artifact's own `applyArtifact` bridge, which `measureMarginalLift` already uses.
319
+ */
320
+
321
+ interface DriftWatchOptions {
322
+ /** The registry whose `active` artifacts are re-measured. Mutated in place:
323
+ * artifacts that fail the keep-bar are demoted to `decayed`. */
324
+ registry: ArtifactRegistry;
325
+ /** The baseline profile each artifact is re-measured ON TOP OF — the SAME
326
+ * ablation shape as promotion. Pass the CURRENT baseline (the world the
327
+ * artifact lives in now), which may differ from the one it was promoted on. */
328
+ baseline: AgentProfile;
329
+ /** Scores a profile on the held-back split. The shared baseline arm is run
330
+ * once and reused across every artifact's ablation (the "without" arm). */
331
+ evalRunner: EvalRunner;
332
+ /**
333
+ * The absolute keep-bar: an artifact stays `active` only while its re-measured
334
+ * held-back lift is STRICTLY ABOVE this floor. Default 0 — an artifact that no
335
+ * longer adds anything (or now subtracts) decays. Mirrors `thresholdPromotionGate`'s
336
+ * `minDelta` so the keep-bar and the promote-bar can be set consistently.
337
+ */
338
+ minLift?: number;
339
+ /**
340
+ * The relative keep-bar: an artifact also decays if its re-measured lift fell
341
+ * to below this FRACTION of the lift recorded at promotion (`registry.liftOf`).
342
+ * E.g. `0.5` demotes an artifact that lost more than half its original lift,
343
+ * even if it still clears `minLift`. Default: unset (no relative check — only
344
+ * the absolute `minLift` floor applies). Ignored for an artifact with no
345
+ * recorded prior lift (a bare `promote`), which is judged on `minLift` alone.
346
+ */
347
+ maxRelativeDecay?: number;
348
+ /** Restrict the watch to one surface (e.g. re-measure only skills). Default:
349
+ * every `active` artifact, regardless of kind. */
350
+ kind?: ProfileArtifact['kind'];
351
+ /** A pre-computed baseline result, to skip the shared "without" run when the
352
+ * caller already scored the current baseline this cycle. */
353
+ baselineResult?: EvalResult;
354
+ /** Cooperative cancellation, forwarded to every `evalRunner` call. */
355
+ signal?: AbortSignal;
356
+ }
357
+ /** Per-artifact record of what the re-measure found and decided. */
358
+ interface DriftCheck {
359
+ /** The re-measured artifact (status reflects the decision: still `active`, or
360
+ * now `decayed`). */
361
+ artifact: ProfileArtifact;
362
+ /** The lift recorded at promotion time (`registry.liftOf` before the check),
363
+ * or `undefined` if it was promoted without a lift receipt. */
364
+ priorLift: number | undefined;
365
+ /** The freshly re-measured held-back lift (with − without composite). */
366
+ currentLift: number;
367
+ /** Whether this re-measure demoted the artifact (`active` → `decayed`). */
368
+ demoted: boolean;
369
+ /** Human-readable reason the artifact was kept or demoted (the same string
370
+ * recorded under `lifecycleReasonKey` on a demotion). */
371
+ reason: string;
372
+ }
373
+ interface DriftWatchResult {
374
+ /** One check per `active` artifact examined, in registry order. */
375
+ checks: DriftCheck[];
376
+ /** Ids of the artifacts demoted to `decayed` this cycle. */
377
+ demoted: string[];
378
+ /** The shared baseline eval (the "without" arm, measured once). */
379
+ baselineResult: EvalResult;
380
+ }
381
+ /**
382
+ * Re-measure every `active` artifact and demote those whose held-back lift
383
+ * decayed below the keep-bar.
384
+ *
385
+ * For each `active` artifact (optionally filtered to one `kind`), this re-runs
386
+ * `measureMarginalLift` over the supplied `baseline`, then applies the keep-bar:
387
+ *
388
+ * - ABSOLUTE: `currentLift > minLift` (default `> 0`), and
389
+ * - RELATIVE (when `maxRelativeDecay` is set AND a prior lift exists):
390
+ * `currentLift >= priorLift * (1 − maxRelativeDecay)`.
391
+ *
392
+ * An artifact failing EITHER bar is demoted to `decayed` (recording the
393
+ * re-measured lift + reason) and drops out of `composeProfile`. An artifact that
394
+ * passes both bars stays `active`, with its recorded lift refreshed to the latest
395
+ * measurement so `liftOf` never reports stale evidence.
396
+ *
397
+ * The baseline "without" arm is scored ONCE and shared across all artifacts, so
398
+ * the cost is `1 + (number of active artifacts)` eval runs.
399
+ *
400
+ * @example A nightly cron that demotes any active artifact that lost its lift:
401
+ * const out = await driftWatch({ registry, baseline, evalRunner, minLift: 0 })
402
+ * if (out.demoted.length) report(`demoted ${out.demoted.length} decayed artifacts`)
403
+ */
404
+ declare function driftWatch(opts: DriftWatchOptions): Promise<DriftWatchResult>;
405
+
406
+ /**
407
+ * `promptGenerator` — the `CandidateGenerator` for the PROMPT surface.
408
+ *
409
+ * It is to the prompt surface what `skillGenerator` is to skills: the thin
410
+ * per-surface adapter that turns the agent's history into fresh, unmeasured
411
+ * prompt-instruction candidates the surface-agnostic `runLifecycle` loop then
412
+ * measures, gates, and promotes. Each candidate is a `prompt` artifact carrying
413
+ * one `{ instruction }` line that `applyArtifact` appends to
414
+ * `profile.prompt.instructions`.
415
+ *
416
+ * Two candidate sources, run together each generation:
417
+ *
418
+ * 1. REFINE (incumbent-grounded) — drive agent-eval's `gepaProposer`, the
419
+ * reflective prompt-tier proposer. It reflects on the incumbent prompt +
420
+ * the trace findings and proposes TARGETED rewrites. This is the exploit
421
+ * arm: it polishes the framing the agent already has.
422
+ *
423
+ * 2. SEED (basin escape) — author N genuinely DIVERSE fresh instruction
424
+ * lines from the TASK SPEC, NOT from the incumbent. A reflective proposer
425
+ * only ever perturbs the current surface, so on its own it can polish a
426
+ * local minimum forever — it never tries a fundamentally different framing.
427
+ * The seed arm authors several rewrites that each take a DIFFERENT stance
428
+ * (imperative vs. checklist vs. failure-mode-first, …) so the search can
429
+ * JUMP basins instead of only descending the one it starts in. This is the
430
+ * explore arm, and it is the piece `gepaProposer` structurally cannot do.
431
+ *
432
+ * Both seams are INJECTED (per the §1.5 law: the generator AUTHORS profile
433
+ * pieces, it does not embed a specific LLM loop). `refine` wraps `gepaProposer`;
434
+ * `authorDiverseSeeds` wraps an LLM author. A test injects pure stubs for both,
435
+ * keeping the closed loop deterministically testable; production wires the real
436
+ * router-backed engines (see `productionPromptGenerator`).
437
+ */
438
+
439
+ /** A proposed prompt instruction line plus the WHY behind it. The `rationale`
440
+ * rides into the artifact metadata so a promotion decision is auditable. */
441
+ interface PromptDraft {
442
+ /** The instruction line appended to `profile.prompt.instructions`. */
443
+ instruction: string;
444
+ /** Short human label for review surfaces. */
445
+ label: string;
446
+ /** Why this line was proposed — which failure / framing it targets. */
447
+ rationale: string;
448
+ }
449
+ /**
450
+ * REFINE — incumbent-grounded rewrites. Given the lifecycle context, return
451
+ * targeted edits OF the current prompt framing. The production implementation
452
+ * drives `gepaProposer`; a test injects a pure function. Returns zero or more
453
+ * drafts.
454
+ */
455
+ type RefinePrompt = (ctx: GenerateContext) => Promise<PromptDraft[]> | PromptDraft[];
456
+ /**
457
+ * SEED — author N genuinely DIVERSE fresh instruction lines from the task spec,
458
+ * NOT mutations of the incumbent. This is the local-minimum escape: each seed
459
+ * MUST take a different framing so the population spans multiple basins. The
460
+ * production implementation makes one LLM call at a non-trivial temperature; a
461
+ * test injects a pure function. Returns up to `count` drafts.
462
+ */
463
+ type AuthorDiverseSeeds = (ctx: GenerateContext, count: number) => Promise<PromptDraft[]> | PromptDraft[];
464
+ interface PromptGeneratorOptions {
465
+ /** OPTIONAL — the exploit arm (incumbent-grounded rewrites via `gepaProposer`).
466
+ * Omit to run seeds-only. */
467
+ refine?: RefinePrompt;
468
+ /** OPTIONAL — the explore arm (diverse fresh seeds). Omit to run refine-only. */
469
+ authorDiverseSeeds?: AuthorDiverseSeeds;
470
+ /** How many diverse seeds the explore arm authors each generation. Default 3.
471
+ * Zero disables seeding even when `authorDiverseSeeds` is set. */
472
+ diverseSeedCount?: number;
473
+ }
474
+ /**
475
+ * Build a `CandidateGenerator` for the prompt surface. Each generation it pools
476
+ * the refine arm (incumbent rewrites) and the seed arm (diverse fresh framings),
477
+ * de-duplicates by instruction text, and emits each as a `prompt` artifact.
478
+ *
479
+ * At least one of `refine` / `authorDiverseSeeds` MUST be provided — a generator
480
+ * with neither has no way to produce a candidate and is a wiring bug, so it
481
+ * throws at construction rather than silently returning `[]` every round.
482
+ *
483
+ * @example Production wiring (refine = gepaProposer, seed = LLM author):
484
+ * promptGenerator({
485
+ * refine: gepaRefine(llm, model),
486
+ * authorDiverseSeeds: routerSeedAuthor(llm, model),
487
+ * })
488
+ */
489
+ declare function promptGenerator(opts: PromptGeneratorOptions): CandidateGenerator<'prompt'>;
490
+ interface ProductionPromptGeneratorOptions {
491
+ /** Router transport (baseUrl/apiKey) for both the refine and seed arms. */
492
+ llm: LlmClientOptions;
493
+ /** Model that performs reflection + seed authoring. Default `deepseek-v4-flash`. */
494
+ model?: string;
495
+ /** Population size handed to `gepaProposer`'s `propose`. Default 3. */
496
+ refinePopulation?: number;
497
+ /** Diverse-seed count. Default 3. */
498
+ diverseSeedCount?: number;
499
+ /** Seed-authoring temperature — high on purpose so the seeds spread across
500
+ * framings rather than collapsing onto one. Default 1.0. */
501
+ seedTemperature?: number;
502
+ }
503
+ /**
504
+ * Production `promptGenerator`: refine via `gepaProposer`, seed via a
505
+ * router-backed diverse author. The one call a consumer makes to grow prompt
506
+ * artifacts with the real engines.
507
+ */
508
+ declare function productionPromptGenerator(opts: ProductionPromptGeneratorOptions): CandidateGenerator<'prompt'>;
509
+ /**
510
+ * Wrap `gepaProposer` as a `RefinePrompt`. The proposer reflects on the
511
+ * incumbent prompt (`ctx.baseline.prompt.systemPrompt`) and the trace findings
512
+ * to propose `population` targeted rewrites. We feed it the generation-0
513
+ * `ProposeContext` (no scored history yet inside one lifecycle round), so it
514
+ * reflects on the current surface against its mutation primitives — exactly the
515
+ * "polish the framing you already have" arm.
516
+ */
517
+ declare function gepaRefine(llm: LlmClientOptions, model: string, population: number): RefinePrompt;
518
+ /**
519
+ * A router-backed `AuthorDiverseSeeds`: one structured LLM call that authors
520
+ * `count` instruction lines, each REQUIRED to take a distinct framing. The
521
+ * prompt is grounded in the task spec (the incumbent system prompt as the spec
522
+ * of WHAT the agent must do) plus the trace findings (what it gets wrong) — but
523
+ * the model is told to author FRESH lines, not edits, so the output spans
524
+ * basins the incumbent's neighborhood never reaches.
525
+ */
526
+ declare function routerSeedAuthor(llm: LlmClientOptions, model: string, temperature: number): AuthorDiverseSeeds;
527
+
528
+ /**
529
+ * `runLifecycle` — the ONE closed-loop orchestrator: generate → measure →
530
+ * promote → store.
531
+ *
532
+ * It is surface-agnostic: it knows nothing about skills vs tools vs prompts. All
533
+ * per-surface logic lives behind the `CandidateGenerator` seam. The loop:
534
+ *
535
+ * 1. GENERATE — ask each generator for candidate artifacts from the agent's
536
+ * history (traces/findings). Register them as `candidate`s.
537
+ * 2. MEASURE — for each candidate, run `measureMarginalLift` on the held-
538
+ * back split (the with/without ablation, baseline shared across
539
+ * candidates so the "without" arm runs once).
540
+ * 3. PROMOTE — run the `PromotionGate` (default: the held-back exam). On a
541
+ * pass, `promoteWithLift` records the measured lift — the
542
+ * registry invariant: no measured lift ⇒ never active.
543
+ * 4. STORE — every candidate (promoted or not) lands in the registry with
544
+ * provenance (domain, generation, generator kind, gate verdict)
545
+ * + its lift in metadata, so the decision is auditable.
546
+ *
547
+ * Compose is intentionally NOT part of this loop — folding the promoted set back
548
+ * into a profile is a separate, explicit `composeProfile` call the caller makes
549
+ * when it wants a deployable profile. Keeping them separate means a caller can
550
+ * run the loop to grow the catalog without committing a profile change.
551
+ */
552
+
553
+ interface RunLifecycleOptions {
554
+ /** The baseline profile candidates are proposed and measured on top of. On a
555
+ * cold start this is the empty (or near-empty) profile. */
556
+ baseline: AgentProfile;
557
+ /** The agent/domain id — namespaces provenance + scopes generators. */
558
+ domain: string;
559
+ /** The per-surface candidate generators. One per surface the loop grows; the
560
+ * loop runs them in order and pools their candidates. */
561
+ generators: ReadonlyArray<CandidateGenerator>;
562
+ /** Scores a profile on the HELD-BACK split. Run by `measureMarginalLift` —
563
+ * once for the shared baseline, once per candidate. */
564
+ evalRunner: EvalRunner;
565
+ /** The promotion gate (the held-back exam). Default-free on purpose: the
566
+ * caller chooses the policy (`thresholdPromotionGate` / `heldOutPromotionGate`). */
567
+ gate: PromotionGate;
568
+ /** Trace-analyst findings the generators distill/propose from. */
569
+ findings?: ReadonlyArray<AnalystFinding>;
570
+ /** Raw captured traces for generators (e.g. a skill `distill`). Opaque. */
571
+ traces?: unknown;
572
+ /** Generation counter for provenance. Default 0. */
573
+ generation?: number;
574
+ /** An existing registry to grow across generations. Default: a fresh one. */
575
+ registry?: ArtifactRegistry;
576
+ /** Cooperative cancellation. */
577
+ signal?: AbortSignal;
578
+ }
579
+ /** The per-candidate record of what the loop decided and why. */
580
+ interface CandidateOutcome {
581
+ /** The stored artifact (status reflects the gate verdict). */
582
+ artifact: ProfileArtifact;
583
+ /** The surface this candidate targeted. */
584
+ kind: ArtifactKind;
585
+ /** Measured held-back lift (with − without composite). */
586
+ scoreDelta: number;
587
+ /** Measured extra USD cost the artifact adds. */
588
+ costDelta: number;
589
+ /** The gate's verdict. */
590
+ verdict: PromotionVerdict;
591
+ /** Whether the candidate was promoted into the registry as active. */
592
+ promoted: boolean;
593
+ }
594
+ interface RunLifecycleResult {
595
+ /** The registry, grown with this generation's candidates + promotions. */
596
+ registry: ArtifactRegistry;
597
+ /** One outcome per candidate the generators produced, in generation order. */
598
+ outcomes: CandidateOutcome[];
599
+ /** Ids of the artifacts promoted this generation. */
600
+ promoted: string[];
601
+ /** The shared baseline eval (the "without" arm, measured once). */
602
+ baselineResult: EvalResult;
603
+ }
604
+ /**
605
+ * Run ONE generation of the artifact lifecycle.
606
+ *
607
+ * @example Cold start on a fixture domain (the closed loop in one call):
608
+ * const out = await runLifecycle({
609
+ * baseline: emptyProfile,
610
+ * domain: 'support-bot',
611
+ * generators: [skillGenerator({ distill, refine })],
612
+ * evalRunner: scoreOnHeldBackSplit,
613
+ * gate: thresholdPromotionGate(),
614
+ * traces: seededTraces,
615
+ * })
616
+ * const composed = composeProfile(out.registry, emptyProfile, { kind: 'skill' })
617
+ */
618
+ declare function runLifecycle(opts: RunLifecycleOptions): Promise<RunLifecycleResult>;
619
+
620
+ /**
621
+ * `skillGenerator` — the reference `CandidateGenerator` for the SKILL surface,
622
+ * and the literal answer to "an empty profile has no skills, so what creates
623
+ * one?".
624
+ *
625
+ * It is a two-step pipeline:
626
+ *
627
+ * 1. DISTILL — read the agent's traces/findings and WRITE a new skill document
628
+ * (a reusable how-to note: "check state before acting", "verify after every
629
+ * edit"). This is the CREATE step. It is the piece `skillOpt` cannot do —
630
+ * an optimizer refines an existing skill, it cannot conjure one from
631
+ * nothing. The production `distill` is `reflectiveGenerator`-style: an LLM
632
+ * reflection over the trace produces the first draft.
633
+ *
634
+ * 2. REFINE — take the distilled draft and improve its wording/structure. The
635
+ * production `refine` wraps agent-eval's skill optimizer (`runSkillOpt`).
636
+ * Refinement is optional: with no `refine`, the distilled draft IS the
637
+ * candidate.
638
+ *
639
+ * Both steps are INJECTED seams, not hardcoded engines — per the §1.5 law, the
640
+ * generator AUTHORS a profile piece; it does not embed a specific LLM loop. That
641
+ * keeps the closed loop deterministically testable (inject pure stubs) while
642
+ * production wires the real distill + skillOpt. The generator emits a `skill`
643
+ * artifact (an inline `SKILL.md` resource ref) the orchestrator measures + gates.
644
+ */
645
+
646
+ /** A distilled skill draft: a name + the `SKILL.md` body. */
647
+ interface SkillDraft {
648
+ /** Skill name — becomes the inline resource ref name + the artifact name. */
649
+ name: string;
650
+ /** The `SKILL.md` document body (markdown). */
651
+ content: string;
652
+ /** Optional one-line description for review surfaces. */
653
+ description?: string;
654
+ }
655
+ /**
656
+ * DISTILL — create new skill drafts from the agent's history. Returns zero or
657
+ * more drafts (zero is valid: nothing worth distilling this round). The
658
+ * production implementation reflects over `ctx.traces` / `ctx.findings` with an
659
+ * LLM; a test injects a pure function.
660
+ */
661
+ type DistillSkills = (ctx: GenerateContext) => Promise<SkillDraft[]> | SkillDraft[];
662
+ /**
663
+ * REFINE — improve ONE distilled draft (wording, structure, examples). The
664
+ * production implementation wraps `runSkillOpt`. Returns the refined draft; when
665
+ * omitted from `skillGenerator`, the distilled draft is used as-is.
666
+ */
667
+ type RefineSkill = (draft: SkillDraft) => Promise<SkillDraft> | SkillDraft;
668
+ interface SkillGeneratorOptions {
669
+ /** REQUIRED — the create step. Without it there is no skill to optimize. */
670
+ distill: DistillSkills;
671
+ /** OPTIONAL — the optimize step. Omit to ship distilled drafts unrefined. */
672
+ refine?: RefineSkill;
673
+ }
674
+ /**
675
+ * Build a `CandidateGenerator` for the skill surface that distills new skills
676
+ * from history, then (optionally) refines them, and emits each as a `skill`
677
+ * artifact carrying an inline `SKILL.md` resource ref.
678
+ *
679
+ * @example Production wiring (distill = LLM reflection, refine = skillOpt):
680
+ * skillGenerator({
681
+ * distill: reflectiveDistill, // creates the draft from traces
682
+ * refine: skillOptRefine, // optimizes the draft
683
+ * })
684
+ */
685
+ declare function skillGenerator(opts: SkillGeneratorOptions): CandidateGenerator<'skill'>;
686
+
687
+ /**
688
+ * `buildableGenerator` — the `CandidateGenerator` for the BUILDABLE surfaces
689
+ * (`tool` and `mcp`), and the answer to "a profile can't *write* a new tool from
690
+ * a prompt rewrite — who builds it?".
691
+ *
692
+ * Unlike the prompt and skill surfaces, where a candidate is a piece of TEXT an
693
+ * LLM authors in one call, a tool or an MCP server is CODE that must compile,
694
+ * pass its tests, and — for an MCP server — actually boot and serve. You cannot
695
+ * one-shot that reliably. So this generator is a SUPERVISOR DISPATCH, not a
696
+ * single author:
697
+ *
698
+ * 1. FAN OUT — spawn N parallel candidate implementations, each built in its
699
+ * OWN git worktree by a real coding harness (research → implement
700
+ * → test → prove it compiles / actually serves). The per-candidate
701
+ * build is the `buildCandidate` seam; production wires it to the
702
+ * shipped `improvementDriver` + `agenticGenerator` + a verifier
703
+ * (`commandVerifier` for a tool, `mcpServeVerifier` for an MCP).
704
+ * 2. FILTER — keep only the VERIFIED builds. An unverified worktree is never a
705
+ * candidate (the verifier is the gate — same valid-only discipline
706
+ * as `worktreeFanout`'s `selectValidWinner`). A build that never
707
+ * compiles/serves is discarded, never ranked.
708
+ * 3. RANK — score each verified survivor by `measureMarginalLift` against
709
+ * `ctx.baseline` (the with/without held-back ablation — the SAME
710
+ * selector the rest of the lifecycle uses; never a judge).
711
+ * 4. EMIT — return the single best survivor as ONE `tool` / `mcp` artifact,
712
+ * carrying the measured lift + the winning worktree ref as
713
+ * auditable provenance in metadata.
714
+ *
715
+ * Steps 2–4 (filter, lift-rank, emit) are surface-agnostic and live here; only
716
+ * `buildCandidate` (the per-candidate worktree build) varies, and it is INJECTED
717
+ * — production wires the real harness fan-out, a test injects pure stubs so the
718
+ * dispatch is deterministically exercisable without spawning processes or models.
719
+ *
720
+ * NB the lifecycle orchestrator (`runLifecycle`) STILL measures + gates whatever
721
+ * this returns. The rank here is an INTERNAL selection — "which of the N parallel
722
+ * builds is the best candidate to put forward" — not the promotion gate. A
723
+ * generator that puts forward a measured-best candidate and an orchestrator that
724
+ * re-measures it on the held-back exam are not redundant: the first picks among
725
+ * siblings, the second decides whether the winner clears the bar to ship.
726
+ */
727
+
728
+ /** The buildable surfaces — the kinds whose candidate IS code that must compile
729
+ * / serve, so building one is a fan-out-and-verify dispatch, not a one-shot. */
730
+ type BuildableKind = 'tool' | 'mcp';
731
+ /**
732
+ * The result of building ONE candidate in its own worktree. A build either
733
+ * verified (compiled + tests passed, or — for an MCP — booted and served) or it
734
+ * did not; an unverified build is dropped before ranking, so `verified:false`
735
+ * carries the reason for the audit trail but never becomes a candidate.
736
+ */
737
+ interface BuiltCandidate {
738
+ /** A short label for this candidate (worktree branch / trace node). */
739
+ label: string;
740
+ /** Did the build compile + pass its verifier? Only verified builds rank. */
741
+ verified: boolean;
742
+ /** The worktree path / git ref holding the built change (provenance). */
743
+ worktreeRef: string;
744
+ /**
745
+ * For an `mcp` candidate: how to START the built server (stdio transport).
746
+ * Becomes the `AgentProfileMcpServer` the artifact carries. REQUIRED for an
747
+ * `mcp` build; ignored for a `tool` build.
748
+ */
749
+ serve?: {
750
+ command: string;
751
+ args?: string[];
752
+ cwd?: string;
753
+ env?: Record<string, string>;
754
+ };
755
+ /**
756
+ * For a `tool` candidate: the tool name the grant lands under (the profile
757
+ * `tools` key). REQUIRED for a `tool` build; ignored for an `mcp` build.
758
+ */
759
+ toolName?: string;
760
+ /** Why the build failed verification, when `verified` is false (audit only). */
761
+ failureReason?: string;
762
+ /** Free-form provenance to ride into the artifact metadata (build summary, …). */
763
+ metadata?: Record<string, unknown>;
764
+ }
765
+ /**
766
+ * BUILD ONE candidate. Given the lifecycle context and the index in the fan-out,
767
+ * produce a fresh worktree implementation and report whether it verified. The
768
+ * production implementation drives a real coding harness in a fresh worktree
769
+ * (`worktreeBuildCandidate`); a test injects a pure function.
770
+ *
771
+ * MUST NOT measure lift, gate, or register — that is the dispatch's / the
772
+ * orchestrator's job. It only builds + verifies ONE sibling.
773
+ */
774
+ type BuildCandidate = (ctx: GenerateContext, index: number, signal: AbortSignal) => Promise<BuiltCandidate>;
775
+ interface BuildableGeneratorOptions {
776
+ /** The buildable surface this generator targets (`tool` or `mcp`). */
777
+ kind: BuildableKind;
778
+ /** The per-candidate build seam (the fan-out leaf). REQUIRED. */
779
+ buildCandidate: BuildCandidate;
780
+ /** How many candidate implementations to build in parallel each generation.
781
+ * Default 3. Must be >= 1. The conserved-budget / live-worker caps that bound
782
+ * the real fan-out live in the production `buildCandidate` wiring. */
783
+ fanout?: number;
784
+ /** Scores a profile on the held-back split — used to RANK the verified
785
+ * siblings by `measureMarginalLift`. REQUIRED: without it there is no way to
786
+ * pick the best of N, and "best" is the whole point of a fan-out. */
787
+ evalRunner: EvalRunner;
788
+ }
789
+ /**
790
+ * Build a `CandidateGenerator` for a buildable surface (`tool` / `mcp`). Each
791
+ * generation it fans out `fanout` parallel worktree builds, keeps the verified
792
+ * ones, ranks them by held-back marginal lift, and emits the single best as one
793
+ * artifact. Returns `[]` when no build verifies — the surface had nothing
794
+ * shippable to contribute this round (a valid, common outcome for hard builds).
795
+ *
796
+ * @example Production wiring (build = real harness fan-out, rank = held-back eval):
797
+ * buildableGenerator({
798
+ * kind: 'mcp',
799
+ * buildCandidate: worktreeBuildCandidate({ repoRoot, harness: 'claude' }),
800
+ * evalRunner: scoreOnHeldBackSplit,
801
+ * fanout: 4,
802
+ * })
803
+ */
804
+ declare function buildableGenerator(opts: BuildableGeneratorOptions): CandidateGenerator<BuildableKind>;
805
+
806
+ /**
807
+ * `worktreeBuildCandidate` — the PRODUCTION `BuildCandidate`: one fan-out leaf
808
+ * that builds a real tool / MCP server in a fresh git worktree with a real
809
+ * coding harness, and verifies it by the surface's intrinsic check.
810
+ *
811
+ * It is the wiring that makes `buildableGenerator`'s dispatch real, composed
812
+ * entirely from shipped engines — NO new execution model:
813
+ *
814
+ * - `gitWorktreeAdapter` (agent-eval/campaign) cuts the isolated worktree.
815
+ * - `improvementDriver` (this repo) owns the worktree lifecycle (create →
816
+ * generate → finalize/discard) and drives ONE candidate.
817
+ * - `agenticGenerator` (this repo) runs the coding harness IN the worktree
818
+ * with the surface's build-prompt (`toolBuildPrompt` / `mcpBuildPrompt`):
819
+ * research → implement → test, multi-shot resume-on-failure.
820
+ * - the surface's verifier proves the result: `commandVerifier('pnpm', ['test'])`
821
+ * for a tool (compiles + tests pass), `mcpServeVerifier(serve)` for an MCP
822
+ * (boots over stdio + answers `tools/list`). A build that never verifies is
823
+ * dropped by `agenticGenerator` (no `CodeSurface` finalized) → `verified:false`.
824
+ *
825
+ * Kept out of `tool-generator.ts` so the dispatch core stays process-free and
826
+ * unit-testable; this file is the one call a consumer makes to wire the real
827
+ * harness fan-out.
828
+ */
829
+
830
+ interface WorktreeBuildOptions {
831
+ /** The buildable surface to build (`tool` / `mcp`). */
832
+ kind: BuildableKind;
833
+ /** Absolute path to the git checkout each candidate worktree is cut from. */
834
+ repoRoot: string;
835
+ /** Which local coding harness drives the build. Default `claude`. */
836
+ harness?: LocalHarness;
837
+ /** Base ref each worktree forks from. Default `main`. */
838
+ baseRef?: string;
839
+ /** Max harness shots per candidate (the depth dial — resume-on-failure). Default 3. */
840
+ maxShots?: number;
841
+ /** Per-shot wall-clock timeout (ms). Forwarded to `agenticGenerator`. */
842
+ timeoutMs?: number;
843
+ /**
844
+ * For a `tool` build: the verify command (run in the worktree, exit 0 = pass)
845
+ * and the tool name the resulting grant lands under. Default command
846
+ * `pnpm test`.
847
+ */
848
+ tool?: {
849
+ verifyCommand?: string;
850
+ verifyArgs?: string[];
851
+ toolName: string;
852
+ };
853
+ /**
854
+ * For an `mcp` build: how to BOOT the built server (the boot-and-probe spec)
855
+ * — used both to verify it serves AND as the artifact's start command. The
856
+ * `cwd` defaults to the candidate worktree.
857
+ */
858
+ mcp?: McpServeSpec;
859
+ }
860
+ /**
861
+ * Build the production per-candidate seam for `buildableGenerator`. Each call to
862
+ * the returned `BuildCandidate` cuts a fresh worktree, drives the harness to
863
+ * implement + verify the surface, and reports the verified worktree (or
864
+ * `verified:false` with the reason) back to the dispatch for ranking.
865
+ */
866
+ declare function worktreeBuildCandidate(opts: WorktreeBuildOptions): BuildCandidate;
867
+
868
+ export { ArtifactInput, ArtifactKind, type ArtifactQuery, ArtifactRegistry, ArtifactStatus, type AuthorDiverseSeeds, type BuildCandidate, type BuildableGeneratorOptions, type BuildableKind, type BuiltCandidate, CandidateGenerator, type CandidateOutcome, type ComposeProfileOptions, type DedupeOptions, type DedupeResult, type DistillSkills, type DriftCheck, type DriftWatchOptions, type DriftWatchResult, EvalResult, EvalRunner, GenerateContext, type PairStackCheck, type ProductionPromptGeneratorOptions, ProfileArtifact, PromotionGate, PromotionVerdict, type PromptDraft, type PromptGeneratorOptions, type RefinePrompt, type RefineSkill, type RunLifecycleOptions, type RunLifecycleResult, type SkillDraft, type SkillGeneratorOptions, type WorktreeBuildOptions, applyArtifact, applyArtifacts, buildableGenerator, composeProfile, createArtifactRegistry, dedupeArtifacts, driftWatch, gepaRefine, lifecycleReasonKey, liftMetadataKey, productionPromptGenerator, promptGenerator, routerSeedAuthor, runLifecycle, skillGenerator, worktreeBuildCandidate };