Skip to content

feat(campaign): cross-window succession and a durable per-arm frontier record #703

Description

@drewstone

What is missing

A campaign that runs out of budget dies instead of resuming, and nothing carries a campaign's stopping point into the next round's input. Two related capabilities:

1. Cross-window succession. When a campaign question settles without closing its objective, mint exactly ONE successor branched from the just-settled run, carrying a monotonic generation counter, so a sharded or multi-round campaign resumes across budget windows. With a hard stop-guard: N consecutive successors that move nothing — the run banked no result AND the objective did not advance — pause the campaign and surface it, so the N+1th is never burned. A run that produced no branchable state is deferred, never emitted as a row that would refuse every cycle forever.

2. A per-arm frontier record. One durable record per campaign arm that survives budget windows, process restarts, and machine crashes: the current objective, what is banked, what is ruled out, the generation counter, the unmoved streak, and the snapshot each successor is measured against. Movement on it is the round's grade, on an ordered scale: closed, advanced, ruled-out, unmoved. Two rules make it honest — the record is a SIBLING file, never a field inside the queue; and every banked or ruled-out claim must RESOLVE to a real artifact reference, so a campaign cannot self-grade its own movement.

Where it lives today

  • discovery-lab/tools/campaign-succession.mjs 343 lines.
  • discovery-lab/tools/frontier.mjs 212 lines.

Where it belongs

  • src/campaign/run-campaign.ts:225 — export async function runCampaign(, which owns a campaign's lifecycle within one window and has no successor concept.
  • src/campaign/parent-selection.ts:49 — export function crowdedFrontierParent(, which selects a parent WITHIN a generation. Succession is the same selection across windows.
  • src/campaign/search-ledger.ts — SearchCandidateLineage (:83), SearchPlannedOperation (:108), and SearchCandidateSlot (:113) already model lineage and planned work; the durable per-arm frontier and the generation counter are the missing records beside them.

Grepped across src/campaign/*.ts: successor and barren return nothing, and every resume hit belongs to the external-optimizer subprocess path (external-optimizer-run-budget.ts:21, external-optimizer-runtime.ts:76-89), which resumes one optimizer process, not a campaign question.

Measured motive

From the 2026-08-30 continuity audit recorded in frontier.mjs:6-13: the branch primitive is real and was exercised 276 times, but 0 of 1,170 question runs ever used it. One round ended with "smallest counterexample now 11 <= n <= 14" and no successor question exists anywhere — an honest scoped partial terminated its own lineage with no next round. Stopping points died in the results file because nothing carried them into the next round's input.

Acceptance

Both files can be deleted when a test settles a campaign question without closing its objective, asserts exactly one successor is minted with an incremented generation counter and a resolvable link to its parent's state, asserts three successors that move nothing pause the campaign and a fourth is never dispatched, and asserts a banked claim whose reference does not resolve is refused.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions