Skip to content

feat(campaign): runLineageLoop — Lineage DAG live seams#322

Merged
drewstone merged 1 commit into
mainfrom
feat/lineage-loop
Jul 7, 2026
Merged

feat(campaign): runLineageLoop — Lineage DAG live seams#322
drewstone merged 1 commit into
mainfrom
feat/lineage-loop

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Wires the just-merged Lineage DAG primitive (src/campaign/lineage.tsLineage, runLineage, Governor, heuristicGovernor) to the real improvement machinery so the multi-track, multi-parent improvement DAG can run live.

What this adds

src/campaign/presets/run-lineage-loop.tsrunLineageLoop, a thin additive adapter that supplies runLineage's two abstract seams:

  • step = ONE SurfaceProposer.propose from the track tip (a single GEPA reflective generation, small population) + one scoring runCampaign per candidate. Elitist: the tip stays in the pool so a step never regresses below its parent (mirrors runOptimization keeping the winner as baseline when no candidate beats it). Best-scoring candidate becomes the new DAG node.
  • merge = the same proposer driven with ctx.paretoParents set to the 2+ parent surfaces, which fires gepaProposer's GEPA combine-complementary-lessons crossover, then one scoring campaign on the merged surface.

The default governor is heuristicGovernor(); seed scores come from an initial scoring pass of each seed surface.

Integration choice

Single gepa-propose + one scoring campaign per step — not a full runImprovementLoop per DAG step. A full improvement loop (baseline campaign + optimization + two holdout campaigns + gate + optional PR) per single node would be enormously heavy; the DAG Governor is what controls breadth across steps (extend / branch / merge / prune), so the inner step is intentionally one small generation.

Injectable seams (unit-testable without a live model)

Both machinery halves are overridable:

  • proposer — defaults to gepaProposer (needs llm + model); tests inject a pure stub.
  • scoreSurface — defaults to a runCampaign pass over holdoutScenarios ?? scenarios (the "agent" = dispatchWithSurface, judges = judges); tests inject a deterministic function, or supply a stub dispatchWithSurface + judges and exercise the real runCampaign path.

Tests

src/campaign/presets/run-lineage-loop.test.ts (5 tests, all green) drives the loop with a deterministic stub agent + judge (surfaces encode their own fitness; no LLM, no sandbox) and asserts: it seeds N visioned tracks, produces a multi-node merge DAG, best() is the max score, re-running is bit-identical, the merge seam drives the crossover proposer, and required-seam validation throws. The default scoreSurface runs the real runCampaign path via an in-memory storage stub.

Observed live DAG: 3 visioned roots → a 3-parent merge node → the solve track climbs 0.11→0.31→0.51→0.71→0.9 via the real propose+campaign step seam, then plateaus and stops.

Additive: no changes to lineage.ts, run-optimization.ts, or gepa.ts. tsc --noEmit clean, biome clean, 29/29 lineage+loop tests pass.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 8cc567da

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-07T17:33:42Z

@drewstone drewstone merged commit 7927f7c into main Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants