@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
package/dist/lifecycle.js CHANGED
@@ -1,3 +1,11 @@
1
+ import {
2
+ agenticGenerator,
3
+ commandVerifier,
4
+ mcpBuildPrompt,
5
+ mcpServeVerifier,
6
+ toolBuildPrompt
7
+ } from "./chunk-JPURCA2O.js";
8
+ import "./chunk-O2UPHN7X.js";
1
9
  import {
2
10
  ValidationError
3
11
  } from "./chunk-NBV35BR6.js";
@@ -48,6 +56,20 @@ function keyOf(artifact) {
48
56
  return artifact.key ?? artifact.id;
49
57
  }
50
58
 
59
+ // src/lifecycle/compose.ts
60
+ function composeProfile(registry, base, opts = {}) {
61
+ const eligible = [];
62
+ for (const artifact of registry.list({ status: "active", kind: opts.kind })) {
63
+ const lift = registry.liftOf(artifact.id);
64
+ if (lift === void 0) continue;
65
+ eligible.push({ artifact, lift });
66
+ }
67
+ eligible.sort((a, b) => b.lift - a.lift);
68
+ const k = opts.k ?? eligible.length;
69
+ const selected = eligible.slice(0, Math.max(0, k)).map((e) => e.artifact);
70
+ return applyArtifacts(base, selected);
71
+ }
72
+
51
73
  // src/lifecycle/marginal-lift.ts
52
74
  async function measureMarginalLift(opts) {
53
75
  const { baseline, candidate, evalRunner, baselineResult, signal } = opts;
@@ -63,7 +85,364 @@ async function measureMarginalLift(opts) {
63
85
  };
64
86
  }
65
87
 
88
+ // src/lifecycle/dedupe.ts
89
+ async function dedupeArtifacts(opts) {
90
+ const tolerance = opts.tolerance ?? 0;
91
+ if (!Number.isFinite(tolerance) || tolerance < 0) {
92
+ throw new ValidationError(
93
+ `dedupeArtifacts: tolerance must be a finite, non-negative number (got ${tolerance})`
94
+ );
95
+ }
96
+ const active = opts.registry.list({ status: "active", kind: opts.kind });
97
+ const baselineResult = opts.baselineResult ?? await opts.evalRunner(opts.baseline, opts.signal);
98
+ const liftCache = /* @__PURE__ */ new Map();
99
+ const liftOf = async (artifact) => {
100
+ const cached = liftCache.get(artifact.id);
101
+ if (cached !== void 0) return cached;
102
+ const lift = await measureMarginalLift({
103
+ baseline: opts.baseline,
104
+ candidate: artifact,
105
+ evalRunner: opts.evalRunner,
106
+ baselineResult,
107
+ signal: opts.signal
108
+ });
109
+ liftCache.set(artifact.id, lift.scoreDelta);
110
+ return lift.scoreDelta;
111
+ };
112
+ const retired = /* @__PURE__ */ new Set();
113
+ const checks = [];
114
+ for (let i = 0; i < active.length; i += 1) {
115
+ if (opts.signal?.aborted) break;
116
+ const a = active[i];
117
+ if (retired.has(a.id)) continue;
118
+ for (let j = i + 1; j < active.length; j += 1) {
119
+ if (opts.signal?.aborted) break;
120
+ const b = active[j];
121
+ if (retired.has(b.id)) continue;
122
+ const liftA = await liftOf(a);
123
+ const liftB = await liftOf(b);
124
+ const combined = await opts.evalRunner(applyArtifacts(opts.baseline, [a, b]), opts.signal);
125
+ const combinedLift = combined.composite - baselineResult.composite;
126
+ const stackGap = combinedLift - (liftA + liftB);
127
+ const redundant = stackGap < -tolerance;
128
+ const check = {
129
+ pair: [a.id, b.id],
130
+ liftA,
131
+ liftB,
132
+ combinedLift,
133
+ stackGap,
134
+ redundant
135
+ };
136
+ if (redundant) {
137
+ const weaker = liftA < liftB ? a : b;
138
+ const reason = `lifts do not stack: combined \u0394=${combinedLift.toFixed(4)} < ${a.id} (\u0394=${liftA.toFixed(4)}) + ${b.id} (\u0394=${liftB.toFixed(4)}) \u2212 tol ${tolerance} \u2014 redundant with ${(weaker.id === a.id ? b : a).id}`;
139
+ opts.registry.retire(weaker.id, reason);
140
+ retired.add(weaker.id);
141
+ check.retiredId = weaker.id;
142
+ checks.push(check);
143
+ if (weaker.id === a.id) break;
144
+ } else {
145
+ checks.push(check);
146
+ }
147
+ }
148
+ }
149
+ return { checks, retired: [...retired], baselineResult };
150
+ }
151
+
152
+ // src/lifecycle/drift-watch.ts
153
+ async function driftWatch(opts) {
154
+ if (opts.maxRelativeDecay !== void 0) {
155
+ if (!Number.isFinite(opts.maxRelativeDecay) || opts.maxRelativeDecay < 0) {
156
+ throw new ValidationError(
157
+ `driftWatch: maxRelativeDecay must be a finite, non-negative fraction (got ${opts.maxRelativeDecay})`
158
+ );
159
+ }
160
+ }
161
+ const minLift = opts.minLift ?? 0;
162
+ if (!Number.isFinite(minLift)) {
163
+ throw new ValidationError(`driftWatch: minLift must be a finite number (got ${minLift})`);
164
+ }
165
+ const active = opts.registry.list({ status: "active", kind: opts.kind });
166
+ const baselineResult = opts.baselineResult ?? await opts.evalRunner(opts.baseline, opts.signal);
167
+ const checks = [];
168
+ const demoted = [];
169
+ for (const artifact of active) {
170
+ if (opts.signal?.aborted) break;
171
+ const priorLift = opts.registry.liftOf(artifact.id);
172
+ const lift = await measureMarginalLift({
173
+ baseline: opts.baseline,
174
+ candidate: artifact,
175
+ evalRunner: opts.evalRunner,
176
+ baselineResult,
177
+ signal: opts.signal
178
+ });
179
+ const currentLift = lift.scoreDelta;
180
+ const decision = keepDecision(currentLift, priorLift, minLift, opts.maxRelativeDecay);
181
+ if (decision.keep) {
182
+ const refreshed = opts.registry.promoteWithLift(artifact.id, currentLift);
183
+ checks.push({
184
+ artifact: refreshed,
185
+ priorLift,
186
+ currentLift,
187
+ demoted: false,
188
+ reason: decision.reason
189
+ });
190
+ } else {
191
+ const decayed = opts.registry.demote(artifact.id, decision.reason, currentLift);
192
+ demoted.push(artifact.id);
193
+ checks.push({
194
+ artifact: decayed,
195
+ priorLift,
196
+ currentLift,
197
+ demoted: true,
198
+ reason: decision.reason
199
+ });
200
+ }
201
+ }
202
+ return { checks, demoted, baselineResult };
203
+ }
204
+ function keepDecision(currentLift, priorLift, minLift, maxRelativeDecay) {
205
+ if (!(currentLift > minLift)) {
206
+ return {
207
+ keep: false,
208
+ reason: `re-measured lift \u0394=${currentLift.toFixed(4)} \u2264 keep-bar ${minLift} \u2014 decayed`
209
+ };
210
+ }
211
+ if (maxRelativeDecay !== void 0 && priorLift !== void 0 && priorLift > 0) {
212
+ const floor = priorLift * (1 - maxRelativeDecay);
213
+ if (currentLift < floor) {
214
+ return {
215
+ keep: false,
216
+ reason: `re-measured lift \u0394=${currentLift.toFixed(4)} fell below ${(maxRelativeDecay * 100).toFixed(0)}%-decay floor ${floor.toFixed(4)} of prior ${priorLift.toFixed(4)} \u2014 decayed`
217
+ };
218
+ }
219
+ }
220
+ return {
221
+ keep: true,
222
+ reason: `re-measured lift \u0394=${currentLift.toFixed(4)} clears the keep-bar \u2014 still active`
223
+ };
224
+ }
225
+
226
+ // src/lifecycle/gate.ts
227
+ import { HeldOutGate } from "@tangle-network/agent-eval";
228
+ function thresholdPromotionGate(minDelta = 0) {
229
+ return {
230
+ kind: `threshold:${minDelta}`,
231
+ decide(lift) {
232
+ if (lift.scoreDelta > minDelta) {
233
+ return {
234
+ promote: true,
235
+ reason: `held-back lift \u0394=${lift.scoreDelta.toFixed(4)} > ${minDelta}`,
236
+ rejectionCode: null
237
+ };
238
+ }
239
+ return {
240
+ promote: false,
241
+ reason: `held-back lift \u0394=${lift.scoreDelta.toFixed(4)} \u2264 ${minDelta}`,
242
+ rejectionCode: "negative_delta"
243
+ };
244
+ }
245
+ };
246
+ }
247
+ function heldOutPromotionGate(opts) {
248
+ const gate = new HeldOutGate({
249
+ baselineKey: opts.baselineKey,
250
+ minProductiveRuns: opts.minProductiveRuns,
251
+ pairedDeltaThreshold: opts.pairedDeltaThreshold,
252
+ overfitGapThreshold: opts.overfitGapThreshold,
253
+ seed: opts.seed,
254
+ costPerTaskCeiling: opts.costPerTaskCeiling
255
+ });
256
+ return {
257
+ kind: "heldout",
258
+ decide(lift) {
259
+ const candidateRuns = recordsOf(lift.withArtifact.runs, "withArtifact", lift.artifactId);
260
+ const baselineRuns = recordsOf(lift.withoutArtifact.runs, "withoutArtifact", lift.artifactId);
261
+ const decision = gate.evaluate(candidateRuns, baselineRuns);
262
+ return {
263
+ promote: decision.promote,
264
+ reason: decision.reason,
265
+ rejectionCode: decision.rejectionCode
266
+ };
267
+ }
268
+ };
269
+ }
270
+ function recordsOf(runs, arm, artifactId) {
271
+ if (runs === void 0 || runs.length === 0) {
272
+ throw new Error(
273
+ `heldOutPromotionGate: the EvalRunner produced no per-task records on the '${arm}' arm for artifact ${JSON.stringify(artifactId)}. The held-out gate runs a paired-bootstrap on per-task holdout records \u2014 it cannot decide from a scalar composite. Either surface EvalResult.runs from your runner, or use thresholdPromotionGate.`
274
+ );
275
+ }
276
+ return runs;
277
+ }
278
+
279
+ // src/lifecycle/prompt-generator.ts
280
+ import { callLlmJson } from "@tangle-network/agent-eval";
281
+ import {
282
+ isProposedCandidate
283
+ } from "@tangle-network/agent-eval/campaign";
284
+ import { gepaProposer } from "@tangle-network/agent-eval/contract";
285
+ function promptGenerator(opts) {
286
+ if (!opts.refine && !opts.authorDiverseSeeds) {
287
+ throw new TypeError(
288
+ "promptGenerator: at least one of `refine` or `authorDiverseSeeds` is required \u2014 a generator with neither can never produce a candidate"
289
+ );
290
+ }
291
+ const seedCount = opts.diverseSeedCount ?? 3;
292
+ return {
293
+ kind: "prompt",
294
+ async generate(ctx) {
295
+ const drafts = [];
296
+ if (opts.authorDiverseSeeds && seedCount > 0) {
297
+ drafts.push(...await opts.authorDiverseSeeds(ctx, seedCount));
298
+ }
299
+ if (opts.refine) {
300
+ drafts.push(...await opts.refine(ctx));
301
+ }
302
+ return dedupeByInstruction(drafts).map(toPromptArtifact);
303
+ }
304
+ };
305
+ }
306
+ function dedupeByInstruction(drafts) {
307
+ const seen = /* @__PURE__ */ new Set();
308
+ const out = [];
309
+ for (const draft of drafts) {
310
+ const key = draft.instruction.trim();
311
+ if (key.length === 0 || seen.has(key)) continue;
312
+ seen.add(key);
313
+ out.push(draft);
314
+ }
315
+ return out;
316
+ }
317
+ function toPromptArtifact(draft) {
318
+ return {
319
+ kind: "prompt",
320
+ name: draft.label,
321
+ description: draft.rationale,
322
+ payload: { instruction: draft.instruction },
323
+ metadata: { rationale: draft.rationale }
324
+ };
325
+ }
326
+ var defaultPromptModel = "deepseek-v4-flash";
327
+ function productionPromptGenerator(opts) {
328
+ const model = opts.model ?? defaultPromptModel;
329
+ return promptGenerator({
330
+ refine: gepaRefine(opts.llm, model, opts.refinePopulation ?? 3),
331
+ authorDiverseSeeds: routerSeedAuthor(opts.llm, model, opts.seedTemperature ?? 1),
332
+ diverseSeedCount: opts.diverseSeedCount ?? 3
333
+ });
334
+ }
335
+ function gepaRefine(llm, model, population) {
336
+ const proposer = gepaProposer({
337
+ llm,
338
+ model,
339
+ target: "agent system prompt"
340
+ });
341
+ return async (ctx) => {
342
+ const baseline = baselinePromptSurface(ctx.baseline);
343
+ const proposeCtx = {
344
+ currentSurface: baseline,
345
+ history: [],
346
+ findings: [...ctx.findings],
347
+ populationSize: population,
348
+ generation: 0,
349
+ signal: ctx.signal ?? new AbortController().signal
350
+ };
351
+ const proposed = await proposer.propose(proposeCtx);
352
+ return proposed.flatMap((p) => promptDraftFromProposal(p, baseline));
353
+ };
354
+ }
355
+ function routerSeedAuthor(llm, model, temperature) {
356
+ return async (ctx, count) => {
357
+ const spec = baselinePromptSurface(ctx.baseline);
358
+ const findingLines = ctx.findings.map(
359
+ (f) => `- [${f.area}] ${f.claim}${f.recommended_action ? ` \u2192 ${f.recommended_action}` : ""}`
360
+ ).join("\n");
361
+ const system = "You author standing instructions for an autonomous agent. Given the agent task spec and its observed mistakes, write DIVERSE candidate instruction lines. CRITICAL: the candidates must NOT be paraphrases of each other or of the existing spec \u2014 each must take a GENUINELY DIFFERENT framing (e.g. one imperative directive, one as a pre-flight checklist, one written failure-mode-first, one as a principle, \u2026). Different framings let a search escape a local optimum instead of polishing one. Return JSON only.";
362
+ const user = `Agent task spec (what it must do):
363
+ ${spec || "(no explicit spec \u2014 infer from the domain)"}
364
+
365
+ Domain: ${ctx.domain}
366
+
367
+ Observed mistakes from traces:
368
+ ${findingLines || "(none captured this round)"}
369
+
370
+ Author exactly ${count} instruction-line candidates, each a single standing instruction, each with a distinct framing.`;
371
+ const { value } = await callLlmJson(
372
+ {
373
+ model,
374
+ messages: [
375
+ { role: "system", content: system },
376
+ { role: "user", content: user }
377
+ ],
378
+ temperature,
379
+ jsonSchema: {
380
+ name: "diverse_prompt_seeds",
381
+ schema: {
382
+ type: "object",
383
+ properties: {
384
+ candidates: {
385
+ type: "array",
386
+ items: {
387
+ type: "object",
388
+ properties: {
389
+ instruction: { type: "string" },
390
+ framing: { type: "string" },
391
+ rationale: { type: "string" }
392
+ },
393
+ required: ["instruction", "framing"]
394
+ }
395
+ }
396
+ },
397
+ required: ["candidates"]
398
+ }
399
+ }
400
+ },
401
+ llm
402
+ );
403
+ const wire = value.candidates ?? [];
404
+ return wire.slice(0, count).map((c, i) => ({
405
+ instruction: c.instruction,
406
+ label: c.framing?.trim() ? `seed:${c.framing.trim()}` : `seed-${i + 1}`,
407
+ rationale: c.rationale?.trim() || `diverse seed (${c.framing?.trim() || "fresh framing"}) authored from the task spec to escape a local minimum`
408
+ }));
409
+ };
410
+ }
411
+ function baselinePromptSurface(profile) {
412
+ return profile.prompt?.systemPrompt ?? "";
413
+ }
414
+ function promptDraftFromProposal(proposal, baseline) {
415
+ if (isProposedCandidate(proposal)) {
416
+ const surface = proposal.surface;
417
+ if (typeof surface !== "string") return [];
418
+ const instruction2 = surfaceDelta(baseline, surface);
419
+ if (!instruction2) return [];
420
+ return [
421
+ {
422
+ instruction: instruction2,
423
+ label: proposal.label?.trim() || "refine",
424
+ rationale: proposal.rationale?.trim() || "incumbent-grounded rewrite (gepaProposer)"
425
+ }
426
+ ];
427
+ }
428
+ if (typeof proposal !== "string") return [];
429
+ const instruction = surfaceDelta(baseline, proposal);
430
+ if (!instruction) return [];
431
+ return [{ instruction, label: "refine", rationale: "incumbent-grounded rewrite (gepaProposer)" }];
432
+ }
433
+ function surfaceDelta(baseline, proposed) {
434
+ const trimmed = proposed.trim();
435
+ const base = baseline.trim();
436
+ if (trimmed === base) return "";
437
+ if (base && trimmed.startsWith(base)) {
438
+ return trimmed.slice(base.length).trim();
439
+ }
440
+ return trimmed;
441
+ }
442
+
66
443
  // src/lifecycle/registry.ts
444
+ var liftMetadataKey = "measuredLift";
445
+ var lifecycleReasonKey = "lifecycleReason";
67
446
  var ArtifactRegistry = class {
68
447
  artifacts = /* @__PURE__ */ new Map();
69
448
  counter = 0;
@@ -111,10 +490,15 @@ var ArtifactRegistry = class {
111
490
  return out;
112
491
  }
113
492
  /**
114
- * Mark an artifact `promoted`. Fails loud on an unknown id — promoting a
493
+ * Mark an artifact `active`. Fails loud on an unknown id — promoting a
115
494
  * non-existent artifact is a caller bug, not a no-op. Returns the updated
116
- * record. Idempotent: promoting an already-promoted artifact is a no-op
117
- * return.
495
+ * record. Idempotent: promoting an already-active artifact is a no-op return.
496
+ *
497
+ * NOTE: the artifact-lifecycle INVARIANT (no measured lift ⇒ not active) is
498
+ * enforced by `promoteWithLift`, the path the closed loop uses. This bare
499
+ * `promote` exists for callers that gate elsewhere and just flip the flag; it
500
+ * does NOT record a lift score, so `liftOf` returns `undefined` and a
501
+ * lift-ranked `composeProfile` will skip it. Prefer `promoteWithLift`.
118
502
  */
119
503
  promote(id) {
120
504
  const artifact = this.artifacts.get(id);
@@ -123,21 +507,125 @@ var ArtifactRegistry = class {
123
507
  `ArtifactRegistry.promote: no artifact with id ${JSON.stringify(id)} is registered`
124
508
  );
125
509
  }
126
- if (artifact.status === "promoted") return artifact;
127
- const promoted = { ...artifact, status: "promoted" };
510
+ if (artifact.status === "active") return artifact;
511
+ const promoted = { ...artifact, status: "active" };
128
512
  this.artifacts.set(id, promoted);
129
513
  return promoted;
130
514
  }
515
+ /**
516
+ * Promote an artifact AND record the measured held-back lift that earned it.
517
+ * This is the closed loop's promotion path and the enforcement point of the
518
+ * lifecycle invariant: an artifact becomes `active` only WITH a finite lift
519
+ * number stamped under `liftMetadataKey`. A non-finite `lift` (NaN/Infinity)
520
+ * fails loud — promoting on a broken measurement is exactly the silent-zero the
521
+ * doctrine forbids. Re-promotes a `decayed` artifact whose lift recovered.
522
+ * Returns the updated record.
523
+ */
524
+ promoteWithLift(id, lift) {
525
+ if (!Number.isFinite(lift)) {
526
+ throw new ValidationError(
527
+ `ArtifactRegistry.promoteWithLift: lift for ${JSON.stringify(id)} must be a finite number (got ${lift})`
528
+ );
529
+ }
530
+ const artifact = this.artifacts.get(id);
531
+ if (!artifact) {
532
+ throw new ValidationError(
533
+ `ArtifactRegistry.promoteWithLift: no artifact with id ${JSON.stringify(id)} is registered`
534
+ );
535
+ }
536
+ const promoted = {
537
+ ...artifact,
538
+ status: "active",
539
+ metadata: { ...artifact.metadata, [liftMetadataKey]: lift }
540
+ };
541
+ this.artifacts.set(id, promoted);
542
+ return promoted;
543
+ }
544
+ /**
545
+ * Demote an `active` artifact to `decayed`: it was promoted, but a later
546
+ * re-measure (`driftWatch`) found its held-back lift fell below the keep-bar.
547
+ * Records the latest re-measured `lift` (so `liftOf` reflects current evidence)
548
+ * and the `reason` (so the demotion is auditable). The artifact stays in the
549
+ * registry — `decayed`, not deleted — so it can be re-promoted if a future
550
+ * re-measure recovers the lift. Fails loud on an unknown id. Demoting a
551
+ * non-`active` artifact fails loud too: only the active set decays.
552
+ */
553
+ demote(id, reason, lift) {
554
+ const artifact = this.artifacts.get(id);
555
+ if (!artifact) {
556
+ throw new ValidationError(
557
+ `ArtifactRegistry.demote: no artifact with id ${JSON.stringify(id)} is registered`
558
+ );
559
+ }
560
+ if (artifact.status !== "active") {
561
+ throw new ValidationError(
562
+ `ArtifactRegistry.demote: artifact ${JSON.stringify(id)} is '${artifact.status}', not 'active' \u2014 only an active artifact can decay`
563
+ );
564
+ }
565
+ if (lift !== void 0 && !Number.isFinite(lift)) {
566
+ throw new ValidationError(
567
+ `ArtifactRegistry.demote: re-measured lift for ${JSON.stringify(id)} must be a finite number (got ${lift})`
568
+ );
569
+ }
570
+ const demoted = {
571
+ ...artifact,
572
+ status: "decayed",
573
+ metadata: {
574
+ ...artifact.metadata,
575
+ [lifecycleReasonKey]: reason,
576
+ ...lift !== void 0 ? { [liftMetadataKey]: lift } : {}
577
+ }
578
+ };
579
+ this.artifacts.set(id, demoted);
580
+ return demoted;
581
+ }
582
+ /**
583
+ * Retire an artifact to the terminal `retired` state: it is permanently out of
584
+ * the active set (`dedupeArtifacts` retires the weaker half of a non-stacking
585
+ * pair). Records the `reason` for the audit trail. Unlike `demote`, this is
586
+ * terminal — a retired artifact is never re-promoted by the loop. Idempotent on
587
+ * an already-retired artifact; fails loud on an unknown id.
588
+ */
589
+ retire(id, reason) {
590
+ const artifact = this.artifacts.get(id);
591
+ if (!artifact) {
592
+ throw new ValidationError(
593
+ `ArtifactRegistry.retire: no artifact with id ${JSON.stringify(id)} is registered`
594
+ );
595
+ }
596
+ if (artifact.status === "retired") return artifact;
597
+ const retired = {
598
+ ...artifact,
599
+ status: "retired",
600
+ metadata: { ...artifact.metadata, [lifecycleReasonKey]: reason }
601
+ };
602
+ this.artifacts.set(id, retired);
603
+ return retired;
604
+ }
605
+ /**
606
+ * The measured held-back lift recorded at promotion time (and overwritten by
607
+ * the latest `driftWatch` re-measure), or `undefined` when the artifact was
608
+ * never promoted WITH a lift (a fresh candidate, or one promoted via the bare
609
+ * `promote`). The lifecycle invariant in one accessor: `liftOf(id) ===
610
+ * undefined` ⇒ the artifact has no measured lift ⇒ it is not eligible for a
611
+ * lift-ranked compose. Note this returns the recorded lift regardless of status
612
+ * — `composeProfile` separately filters to `active`, so a `decayed` artifact's
613
+ * stale lift is visible for audit but never folded into a profile.
614
+ */
615
+ liftOf(id) {
616
+ const value = this.artifacts.get(id)?.metadata?.[liftMetadataKey];
617
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
618
+ }
131
619
  /**
132
620
  * Compose a set of registered artifacts onto a baseline profile. With no ids
133
- * given, composes every `promoted` artifact (the "ship the passing set"
621
+ * given, composes every `active` artifact (the "ship the passing set"
134
622
  * default). With explicit ids, composes exactly those (in id order given),
135
623
  * failing loud on any unknown id. The applied order is the order passed (or
136
- * registration order for the promoted-default), and later artifacts win on key
624
+ * registration order for the active-default), and later artifacts win on key
137
625
  * conflicts — same semantics as `applyArtifacts`.
138
626
  */
139
627
  compose(base, ids) {
140
- const selected = ids === void 0 ? this.list({ status: "promoted" }) : ids.map((id) => {
628
+ const selected = ids === void 0 ? this.list({ status: "active" }) : ids.map((id) => {
141
629
  const artifact = this.artifacts.get(id);
142
630
  if (!artifact) {
143
631
  throw new ValidationError(
@@ -164,11 +652,328 @@ var ArtifactRegistry = class {
164
652
  function createArtifactRegistry() {
165
653
  return new ArtifactRegistry();
166
654
  }
655
+
656
+ // src/lifecycle/run-lifecycle.ts
657
+ async function runLifecycle(opts) {
658
+ if (opts.generators.length === 0) {
659
+ throw new ValidationError("runLifecycle: at least one CandidateGenerator is required");
660
+ }
661
+ const registry = opts.registry ?? new ArtifactRegistry();
662
+ const generation = opts.generation ?? 0;
663
+ const findings = opts.findings ?? [];
664
+ const ctx = {
665
+ baseline: opts.baseline,
666
+ domain: opts.domain,
667
+ findings,
668
+ traces: opts.traces,
669
+ signal: opts.signal
670
+ };
671
+ const candidates = [];
672
+ for (const generator of opts.generators) {
673
+ if (opts.signal?.aborted) break;
674
+ const inputs = await generator.generate(ctx);
675
+ for (const input of inputs) {
676
+ const stored = registry.register({
677
+ ...input,
678
+ // Provenance: who proposed this, for which domain, in which generation.
679
+ metadata: {
680
+ ...input.metadata,
681
+ domain: opts.domain,
682
+ generation,
683
+ generatorKind: generator.kind
684
+ }
685
+ });
686
+ candidates.push(stored);
687
+ }
688
+ }
689
+ const baselineResult = await opts.evalRunner(opts.baseline, opts.signal);
690
+ const outcomes = [];
691
+ const promoted = [];
692
+ for (const candidate of candidates) {
693
+ if (opts.signal?.aborted) break;
694
+ const lift = await measureMarginalLift({
695
+ baseline: opts.baseline,
696
+ candidate,
697
+ evalRunner: opts.evalRunner,
698
+ baselineResult,
699
+ signal: opts.signal
700
+ });
701
+ const verdict = opts.gate.decide(lift);
702
+ let artifact = candidate;
703
+ if (verdict.promote) {
704
+ artifact = registry.promoteWithLift(candidate.id, lift.scoreDelta);
705
+ promoted.push(candidate.id);
706
+ } else {
707
+ artifact = registry.register({
708
+ ...toInput(candidate),
709
+ metadata: {
710
+ ...candidate.metadata,
711
+ gateReason: verdict.reason,
712
+ gateRejectionCode: verdict.rejectionCode,
713
+ scoreDelta: lift.scoreDelta
714
+ }
715
+ });
716
+ }
717
+ outcomes.push({
718
+ artifact,
719
+ kind: candidate.kind,
720
+ scoreDelta: lift.scoreDelta,
721
+ costDelta: lift.costDelta,
722
+ verdict,
723
+ promoted: verdict.promote
724
+ });
725
+ }
726
+ return { registry, outcomes, promoted, baselineResult };
727
+ }
728
+ function toInput(artifact) {
729
+ return {
730
+ id: artifact.id,
731
+ kind: artifact.kind,
732
+ key: artifact.key,
733
+ name: artifact.name,
734
+ description: artifact.description,
735
+ payload: artifact.payload
736
+ };
737
+ }
738
+
739
+ // src/lifecycle/skill-generator.ts
740
+ function skillGenerator(opts) {
741
+ return {
742
+ kind: "skill",
743
+ async generate(ctx) {
744
+ const drafts = await opts.distill(ctx);
745
+ const out = [];
746
+ for (const draft of drafts) {
747
+ const refined = opts.refine ? await opts.refine(draft) : draft;
748
+ out.push(toSkillArtifact(refined));
749
+ }
750
+ return out;
751
+ }
752
+ };
753
+ }
754
+ function toSkillArtifact(draft) {
755
+ const resource = {
756
+ kind: "inline",
757
+ name: draft.name,
758
+ content: draft.content
759
+ };
760
+ return {
761
+ kind: "skill",
762
+ name: draft.name,
763
+ description: draft.description,
764
+ payload: { resource }
765
+ };
766
+ }
767
+
768
+ // src/lifecycle/tool-build.ts
769
+ import {
770
+ gitWorktreeAdapter,
771
+ resolveWorktreePath
772
+ } from "@tangle-network/agent-eval/campaign";
773
+ function worktreeBuildCandidate(opts) {
774
+ const harness = opts.harness ?? "claude";
775
+ const baseRef = opts.baseRef ?? "main";
776
+ const maxShots = opts.maxShots ?? 3;
777
+ const worktree = gitWorktreeAdapter({
778
+ repoRoot: opts.repoRoot,
779
+ branchPrefix: `build-${opts.kind}`
780
+ });
781
+ const generator = agenticGenerator({
782
+ harness,
783
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
784
+ buildPrompt: opts.kind === "mcp" ? mcpBuildPrompt : toolBuildPrompt,
785
+ verify: buildVerifier(opts)
786
+ });
787
+ return async (ctx, index, signal) => {
788
+ const label = `${opts.kind}-cand${index}`;
789
+ const wt = await worktree.create({ baseRef, label });
790
+ try {
791
+ const { applied, summary } = await generator.generate({
792
+ worktreePath: wt.path,
793
+ report: void 0,
794
+ findings: [...ctx.findings],
795
+ maxShots,
796
+ signal
797
+ });
798
+ if (!applied) {
799
+ await worktree.discard(wt).catch(() => {
800
+ });
801
+ return {
802
+ label,
803
+ verified: false,
804
+ worktreeRef: wt.path,
805
+ failureReason: "no verified candidate within maxShots"
806
+ };
807
+ }
808
+ const surface = await worktree.finalize(wt, summary);
809
+ return verifiedBuild(opts, label, surface);
810
+ } catch (err) {
811
+ await worktree.discard(wt).catch(() => {
812
+ });
813
+ throw err;
814
+ }
815
+ };
816
+ }
817
+ function buildVerifier(opts) {
818
+ if (opts.kind === "mcp") {
819
+ if (!opts.mcp) {
820
+ throw new ValidationError(
821
+ "worktreeBuildCandidate: an mcp build requires opts.mcp (the boot-and-probe serve spec)"
822
+ );
823
+ }
824
+ return mcpServeVerifier(opts.mcp);
825
+ }
826
+ const command = opts.tool?.verifyCommand ?? "pnpm";
827
+ const args = opts.tool?.verifyArgs ?? (opts.tool?.verifyCommand ? [] : ["test"]);
828
+ return commandVerifier(command, args);
829
+ }
830
+ function verifiedBuild(opts, label, surface) {
831
+ const worktreeRef = resolveWorktreePath(surface);
832
+ if (opts.kind === "tool") {
833
+ const toolName = opts.tool?.toolName;
834
+ if (!toolName || toolName.trim().length === 0) {
835
+ throw new ValidationError(
836
+ "worktreeBuildCandidate: a tool build requires opts.tool.toolName for the grant key"
837
+ );
838
+ }
839
+ return {
840
+ label,
841
+ verified: true,
842
+ worktreeRef,
843
+ toolName,
844
+ metadata: { summary: surface.summary, baseRef: surface.baseRef }
845
+ };
846
+ }
847
+ const mcp = opts.mcp;
848
+ return {
849
+ label,
850
+ verified: true,
851
+ worktreeRef,
852
+ serve: {
853
+ command: mcp.command,
854
+ ...mcp.args ? { args: mcp.args } : {},
855
+ cwd: worktreeRef,
856
+ ...mcp.env ? { env: mcp.env } : {}
857
+ },
858
+ metadata: { summary: surface.summary, baseRef: surface.baseRef }
859
+ };
860
+ }
861
+
862
+ // src/lifecycle/tool-generator.ts
863
+ function buildableGenerator(opts) {
864
+ const fanout = opts.fanout ?? 3;
865
+ if (fanout < 1) {
866
+ throw new ValidationError(
867
+ `buildableGenerator: fanout must be >= 1 (got ${fanout}) \u2014 a dispatch with no candidates can never build anything`
868
+ );
869
+ }
870
+ return {
871
+ kind: opts.kind,
872
+ async generate(ctx) {
873
+ const signal = ctx.signal ?? new AbortController().signal;
874
+ const settled = await Promise.all(
875
+ Array.from({ length: fanout }, (_unused, i) => opts.buildCandidate(ctx, i, signal))
876
+ );
877
+ const verified = settled.filter((b) => b.verified);
878
+ if (verified.length === 0) return [];
879
+ const baselineResult = await opts.evalRunner(ctx.baseline, signal);
880
+ const ranked = [];
881
+ for (const built of verified) {
882
+ if (signal.aborted) break;
883
+ const probe = stageProbeArtifact(opts.kind, built);
884
+ const lift = await measureMarginalLift({
885
+ baseline: ctx.baseline,
886
+ candidate: probe,
887
+ evalRunner: opts.evalRunner,
888
+ baselineResult,
889
+ signal
890
+ });
891
+ ranked.push({ built, scoreDelta: lift.scoreDelta, costDelta: lift.costDelta });
892
+ }
893
+ if (ranked.length === 0) return [];
894
+ ranked.sort((a, b) => b.scoreDelta - a.scoreDelta);
895
+ const winner = ranked[0];
896
+ return [toArtifact(opts.kind, winner.built, winner.scoreDelta, winner.costDelta, fanout)];
897
+ }
898
+ };
899
+ }
900
+ var probeRegistry = new ArtifactRegistry();
901
+ function stageProbeArtifact(kind, built) {
902
+ return probeRegistry.register(bareArtifact(kind, built));
903
+ }
904
+ function bareArtifact(kind, built) {
905
+ if (kind === "tool") {
906
+ const toolName = built.toolName;
907
+ if (!toolName || toolName.trim().length === 0) {
908
+ throw new ValidationError(
909
+ `buildableGenerator: a verified 'tool' build must report a toolName (label=${built.label})`
910
+ );
911
+ }
912
+ return {
913
+ kind: "tool",
914
+ key: toolName,
915
+ name: toolName,
916
+ payload: { enabled: true }
917
+ };
918
+ }
919
+ const serve = built.serve;
920
+ if (!serve?.command || serve.command.trim().length === 0) {
921
+ throw new ValidationError(
922
+ `buildableGenerator: a verified 'mcp' build must report a serve command (label=${built.label})`
923
+ );
924
+ }
925
+ const server = {
926
+ transport: "stdio",
927
+ command: serve.command,
928
+ ...serve.args ? { args: serve.args } : {},
929
+ ...serve.cwd ? { cwd: serve.cwd } : { cwd: built.worktreeRef },
930
+ ...serve.env ? { env: serve.env } : {},
931
+ enabled: true
932
+ };
933
+ return {
934
+ kind: "mcp",
935
+ key: built.label,
936
+ name: built.label,
937
+ payload: { server }
938
+ };
939
+ }
940
+ function toArtifact(kind, built, scoreDelta, costDelta, fanout) {
941
+ const bare = bareArtifact(kind, built);
942
+ return {
943
+ ...bare,
944
+ description: `built via ${fanout}-way fan-out; won on +${scoreDelta.toFixed(4)} held-back lift`,
945
+ metadata: {
946
+ ...built.metadata,
947
+ worktreeRef: built.worktreeRef,
948
+ buildLabel: built.label,
949
+ fanout,
950
+ // The INTERNAL rank lift (best-of-N selection), distinct from the
951
+ // orchestrator's promotion-gate measurement.
952
+ siblingScoreDelta: scoreDelta,
953
+ siblingCostDelta: costDelta
954
+ }
955
+ };
956
+ }
167
957
  export {
168
958
  ArtifactRegistry,
169
959
  applyArtifact,
170
960
  applyArtifacts,
961
+ buildableGenerator,
962
+ composeProfile,
171
963
  createArtifactRegistry,
172
- measureMarginalLift
964
+ dedupeArtifacts,
965
+ driftWatch,
966
+ gepaRefine,
967
+ heldOutPromotionGate,
968
+ lifecycleReasonKey,
969
+ liftMetadataKey,
970
+ measureMarginalLift,
971
+ productionPromptGenerator,
972
+ promptGenerator,
973
+ routerSeedAuthor,
974
+ runLifecycle,
975
+ skillGenerator,
976
+ thresholdPromotionGate,
977
+ worktreeBuildCandidate
173
978
  };
174
979
  //# sourceMappingURL=lifecycle.js.map