Wave Summary

Wave 9 made every reviewed repository a factory citizen. Wave 10 changed what happens before execution: a raw thought is no longer treated as approved work, planning is no longer a transient agent convention, and an approved plan no longer depends on a mayor or ephemeral supervisor remembering to enqueue it.

The production lifecycle now has three independent axes:

  • lifecycle says where the work is;
  • disposition says whether an orthogonal veto applies; and
  • automation policy says whether the user wanted capture, planning, or delivery.

The main path is:

captured → planning → planned → open → claimed → in_progress
         → awaiting_verification or awaiting_merge → merging → done

open has one narrow meaning: the current revision is planned, approved, and eligible for deterministic dispatch when its remaining readiness predicates pass. It is not an inbox. Ordinary feature and bug creation is therefore safe capture; --plan asks for a validated plan and stops; --auto-plan asks the factory to deliver under standing policy.

The rollout is active in production at lifecycle control revision 21. The writer, planner, and scheduler are enforced; the implementation backlog has zero legacy notes; the factory’s own canary reached done; its exact candidate passed on eight distinct MicroVMs; a formal rollback and replay were proved; and a fresh 15-check acceptance authorized activation. The deterministic refinery remains the only merge authority.

Boundary Active result
source release 046b44f0486acfc715a7bacae18471c283c90594
lifecycle control revision 21, active, enforced
canary ys-yes-1sqs, root yes-jx4e9, terminal done/closed
coding run run-7cb684da37d1 on yesod-runner-1
source commit c00035ef147e2f410719b1eef4d97a478e89e59e
refinery attempt attempt-intake-v1-4ca8d68b0af8b5e4e35dcf96c0de95c8
gate gate-exec-v1-6d067ba8337686a26db55f663aeb246c1d15bd2a
gate proof eight distinct MicroVMs, green, exit 0, 6,471 passed, 28 skipped
deployment effect effect-deploy-v1-24eafa5372a556d6d608c286a65a86b4, confirmed applied
final acceptance lifecycle-acceptance-v1-156730450ade8ba19e089976, 15/15
final refinery snapshot 26 idle healthy lanes, no queue, no operator items, zero running MicroVMs

The Problem Was That open Meant Too Many Things

Before this wave, a feature request or bug report normally arrived as open. That single word might mean “I just wanted to remember this,” “please plan this,” “a planner created some Beads,” “I approve this,” or “a runner may claim this now.” The system had useful pieces—planner agents, Beads, dispatch lanes, runner claims, refinery intake—but no durable contract connected them.

This created two opposing risks. Capture-only ideas could look runnable, while work the user actually wanted delivered could sit outside the queue until a mayor, dispatcher agent, or ephemeral refinery supervisor noticed it. The first problem was excess authority. The second was missing mechanism.

The design separated the questions instead of adding another overloaded status:

Axis Question Examples
lifecycle where is the work? captured, planning, planned, open, in_progress, awaiting_merge, done
disposition may automation act? active, held, blocked, needs_rework, quarantined, deferred
automation policy how far did the user authorize it? capture, plan, deliver

A dispatch target remains routing metadata. It does not authorize planning, execution, credentials, deployment, or destructive side effects. Capacity, backoff, and dependency waits remain derived explanations rather than new states.

The CLI Became Explicit Without Becoming Fussy

The common cases are now intentionally small:

# Record intent. Do not plan or dispatch it.
yesod tool <tool> feature-request "..."
yesod tool <tool> bug-report "..."

# Produce a grounded, validated plan, then wait for approval.
yesod tool <tool> feature-request "..." --plan

# Plan, validate, approve under standing policy, and deliver.
yesod tool <tool> feature-request "..." --auto-plan

All three begin with durable capture. --plan and --auto-plan write policy; they do not synchronously ask one process to perform the entire pipeline. This is why the workflow survives restarts. The planning reconciler, promoter, dispatch reconciler, runner, and refinery can each stop after one auditable step and resume from PostgreSQL.

Plans Became Revision-Bound Durable Objects

A root Bead is useful grounding, but its mere existence is not proof that a planner processed the current request. Wave 10 binds planning to immutable input:

  1. material intent edits increment note_revision;
  2. one planning job exists for (note_id, note_revision);
  3. the artifact records plan identity, provenance, validation, and a fingerprint;
  4. planned_revision must equal note_revision before promotion or claim;
  5. an edit invalidates the old plan rather than silently changing live scope; and
  6. a dispatch arm is bound to the note revision, plan fingerprint, project lane, repository, target branch, and model target.

Planner output is stored durably, including bodies too large for bounded relational fields. Large payloads are hydrated through content-addressed Blob references, so retry and restart do not depend on a vanished provider response or one agent’s context window.

The planner controller uses stable provider-job identity, a lease, bounded retry state, and a deadline. A provider that still reports running after the deadline is not allowed to keep the job pending forever: the controller marks the deadline condition and cancels or escalates through durable state.

Deterministic Promotion and Dispatch Replaced Remembering

The lifecycle controller owns state movement; agents own semantic judgment.

  • The planning reconciler turns eligible captured notes into one durable job.
  • The validator accepts only an artifact for the exact current revision.
  • The promoter moves planned to open only after explicit approval or the deliver policy passes every risk and readiness guard.
  • The dispatch reconciler turns open + active into one lane-scoped arm and claim when onboarding, dependencies, budget, routing, and capacity agree.
  • The runner establishes the run/worktree/provider effect and produces a verified candidate branch.
  • The refinery alone gates, pushes, deploys, and terminalizes it.

This is the answer to whether an ephemeral refinery supervisor is still required: no agent is required to keep the pipeline moving. A mayor remains a useful product and coordination role. A planner remains necessary for semantic decomposition. Neither is a liveness mechanism or merge authority.

Implementation Was a Program, Not an Enum Patch

The active implementation includes:

  • additive schema and compatibility projections;
  • centralized audited compare-and-swap writers;
  • durable lifecycle control revisions and enable-token fencing;
  • capture/plan/deliver CLI and API semantics;
  • planning jobs, leases, provider recovery, retries, deadlines, and artifacts;
  • validator and auto-promoter policy;
  • revision- and lane-bound dispatch arms;
  • deterministic intake from approved open work;
  • recovery and invariant diagnostics;
  • lifecycle API and all-notes state-diagram filters;
  • contextual help explaining state, authority, blocking impact, and operator action;
  • activation acceptance bound to a specific control revision; and
  • a non-destructive rollback/replay procedure.

Focused lifecycle, broker, scheduler, runner, runtime, and UI tests established internal coherence before the production canary. The complete refinery gate then tested the exact constructed candidate, not merely the worker’s source branch.

Production Found the Bugs Unit Tests Could Not

The staged cutover was deliberately allowed to discover boundary failures before full authority was granted.

Source branch and target branch were conflated

The canary initially exposed a resolver that treated the feature source branch as though it were the integration target. The durable arm now carries both identities, and resolution rejects ambiguity. A note can explain exactly which branch supplies work and which branch the refinery may update.

Identity had to include the project lane

Two repositories may contain the same Git SHA and gate profile without being the same operation. Global idempotency rejected the second lane. Gate and dispatch identity now includes the repository/target lane; a SHA is never treated as a globally unique project identity.

Recovery needed an atomic claim fence

Planner and dispatch recovery could otherwise observe a durable object and race to own it. The claim path now binds the current revision, artifact, lane, and owner under one fenced transition. Diagnostics distinguish “no work,” “not ready,” “already owned,” and “contradictory evidence.”

Retry publication and Blob hydration were part of correctness

A retry that only changes a private provider row is invisible to the lifecycle projection. Retry state is now published durably, and artifact bodies are rehydrated before validation or dispatch. Restart behavior therefore matches the ordinary path.

A stale historical branch could poison a newly armed note

Arming a new revision must not reuse an earlier branch merely because the note id is stable. New arms discard historical branch assumptions and bind only the current plan and source facts.

Acceptance needed exact gate invariants

The first production implementation verified that a gate existed and was green. The strengthened acceptance requires the expected provider, terminal green, exit code zero, and eight distinct MicroVM instance identities. This turned the user’s throughput and isolation expectation into an activation condition.

Closed Beads could disappear behind a large open backlog

The terminal-close acceptance initially failed even though the canary root was closed. The standing collector requested only 100 issues, and more than 100 open Yesod issues sorted ahead of the newly closed root. Production currently overrides yesod-beads-collect.service to use --issue-limit 1000. The source-level follow-up is to fetch linked terminal roots completely by construction, then remove the host override only after that fix is deployed.

Public URL stability was an operational invariant

The application page existed at /yesod/viz/refineries.html, while activation acceptance required a stable /yesod/refineries surface. dertog now serves the stable alias through its Nginx router and retains the historical path for compatibility.

The Cutover Was a Sequence of Capabilities

The lifecycle enable token is separate from service liveness. A running controller without the token cannot acquire write, planning, or scheduling authority. The token lives on yesod-dispatch at /home/stephen/.config/yesod/lifecycle-v2-enable-token, mode 0600; its contents are never part of an evidence bundle or documentation.

Activation proceeded through durable control revisions:

  1. establish schema, readers, projections, and invariant reports;
  2. enable audited writers;
  3. enable planner/validator behavior;
  4. restrict scheduling to one exact canary;
  5. prove source/target resolution and revision-bound arm identity;
  6. run the worker and enroll its verified branch;
  7. construct and gate the exact candidate on eight MicroVMs;
  8. confirm push, deployment effect, note terminalization, and root closure;
  9. migrate the remaining legacy terminal implementation notes without dispatch;
  10. pass the first activation acceptance;
  11. perform a formal rollback with all writers disabled;
  12. prove the heartbeat advances while durable lifecycle facts do not change;
  13. replay migration and restore readers, writers, planner, and canary in order;
  14. reject the old acceptance because it was bound to a stale revision;
  15. pass a fresh 15-check acceptance; and
  16. enter active revision 21.

The formal rollback was revision 15 to revision 16. Its durable fact digest remained exactly unchanged while the service continued to tick. Recovery advanced through revisions 17–20. The revision-15 acceptance was correctly rejected after recovery; activation used only the fresh revision-20 report.

The Canary Went Through the Real Factory

The canary was not an administrative state edit. It used the same boundaries as ordinary work:

ys-yes-1sqs revision 10
  → planjob-f6e9470c80dfa6a928b1c793
  → dispatch-arm-f7fda887a5241f8a3506276b
  → run-7cb684da37d1 on yesod-runner-1
  → source c00035ef147e...
  → attempt-intake-v1-4ca8d68b0af8b5e4e35dcf96c0de95c8
  → candidate 046b44f0486a...
  → gate-exec-v1-6d067ba8337686a26db55f663aeb246c1d15bd2a
  → eight distinct MicroVMs, green, exit 0
  → origin/main 046b44f0486a...
  → deployment effect confirmed applied
  → note done; root yes-jx4e9 closed

The gate reported 6,499 collected tests: 6,471 passed and 28 skipped, with zero failures. After terminalization the fleet reaped to zero running MicroVMs.

Legacy Migration Was Conservative

The controller found seven post-bootstrap implementation notes already known to be terminal through legacy evidence. It classified them through the v2 capture-terminal path, recorded a plan before applying it, and replayed the migration to prove idempotency. No legacy note became new dispatch work. The final inventory reports zero legacy implementation notes.

Historical bootstrap alerts required a similar evidence rule. Fourteen open lane_not_configured_for_recovery items belonged to lanes that were later configured. Every linked attempt had subsequently reached succeeded, every lane was enabled and idle, and no current attempt or queue row existed. Only then were the items resolved with structured immutable explanations. The final operator inbox is empty; the history remains queryable.

What Lives Where Now

The placement model did not change during lifecycle activation; it became more strictly documented and re-audited.

Place What lives there What does not live there
pompom interactive CLI, source checkouts, operator sessions, unloaded rollback plists no standing Yesod, refinery, dispatcher, poller, bridge, dashboard, sjbis-gog, or OpenCode service
seykhl Proxmox and VM 112’s virtual hardware no Yesod application process or application credential on the hypervisor
yesod-dispatch lifecycle controller, deterministic refinery, gate proxy, fleet poller, operator-only dispatch maintenance, collectors/timers, bridges, release and merge checkouts no public visualization and no coding claims
yesod-runner-1 active Linux coding-runner service and governed worktrees no merge or deployment authority
yesod-runner-2/3 provisioned coding-runner tier when enabled by fleet policy no merge authority
dertog Yesod read APIs, dashboards, WebSocket watcher, Nginx routing, and sjbis no planning claim, coding claim, gate launch authority, push, or merge authority
homestar content-addressed NFS Blob storage no interpretation of state
AWS MicroVMs eight disposable isolated pytest guests while the Yesod gate is active no durable catalog credential, state, or merge key

The deployment uses immutable source releases:

/srv/yesod/releases/<commit>
/srv/yesod/current       → active controller/dispatcher release
/srv/yesod/ops-current   → active auxiliary-service release

At the final snapshot both pointers, the coding runner, and the visualization deployment identify 046b44f0486acfc715a7bacae18471c283c90594.

pompom was audited after its reboot. No matching launchd job was loaded and no standing lifecycle/refinery/dispatch process existed. Preserved plist files are rollback material, not running infrastructure.

The Operator Surface Shows Real Capacity

The Refineries page at https://dertog.tailb4b58.ts.net/yesod/refineries reads one coherent PostgreSQL projection. It separately displays:

  • configured scheduler capacity;
  • active, waiting, degraded, and operator-required lanes;
  • queued and current attempt identity;
  • gate progress and assigned provider instances;
  • actual provider-wide AWS RUNNING count;
  • fleet snapshot source time, age, TTL, proxy state, and consistency;
  • a launch-storm tripwire; and
  • contextual question-mark help for the operational vocabulary.

This distinction matters. One active lane may own eight MicroVMs, and eight assigned gate identities are not proof that all eight guests remain running. The dashboard exposes both facts without requiring browser-side AWS credentials.

Final State

The final production audit reported:

  • lifecycle control revision 21, active and enforced;
  • writer, planner, and scheduler enabled;
  • zero legacy implementation notes;
  • admissions active at revision 140;
  • controller and runner heartbeats fresh;
  • canonical and deployed release 046b44f0 everywhere in authority;
  • 26 configured project lanes, all idle and healthy;
  • zero queued attempts and no current attempt;
  • zero open operator items;
  • zero running MicroVMs, healthy idle proxy, consistent fleet snapshot;
  • public refinery and legacy dashboard paths returning HTTP 200; and
  • no standing Yesod infrastructure on pompom or directly on seykhl.

The rollout evidence is retained privately on yesod-dispatch under /home/stephen/.local/state/yesod/lifecycle-v2-evidence/20260811T094900Z. The directory and files are permission-restricted and hash-manifested; no enable token or database credential is part of the bundle.

The Exit Criterion

Wave 10 is complete when a user can choose intent explicitly and the factory can carry that intent without a resident agent remembering the next step:

  1. ordinary creation captures safely;
  2. --plan produces a validated revision-bound plan and stops;
  3. --auto-plan progresses only through standing policy and deterministic guards;
  4. open is the sole approved handoff to dispatch;
  5. one lane-scoped arm and one runner claim own execution;
  6. the refinery gates and merges only the exact candidate;
  7. terminal evidence closes the note and governing root;
  8. rollback disables authority without deleting history; and
  9. restart/replay resumes from durable identity without duplication.

That criterion is now met. Semantic agents remain essential, but factory liveness no longer depends on an ephemeral supervisor, mayor, or workstation session. The system can explain who authorized each transition, which revision it used, what it is waiting for, which external effect occurred, and why the exact candidate was allowed onto main.