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:
- material intent edits increment
note_revision; - one planning job exists for
(note_id, note_revision); - the artifact records plan identity, provenance, validation, and a fingerprint;
planned_revisionmust equalnote_revisionbefore promotion or claim;- an edit invalidates the old plan rather than silently changing live scope; and
- 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
plannedtoopenonly after explicit approval or thedeliverpolicy passes every risk and readiness guard. - The dispatch reconciler turns
open + activeinto 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
openwork; - 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:
- establish schema, readers, projections, and invariant reports;
- enable audited writers;
- enable planner/validator behavior;
- restrict scheduling to one exact canary;
- prove source/target resolution and revision-bound arm identity;
- run the worker and enroll its verified branch;
- construct and gate the exact candidate on eight MicroVMs;
- confirm push, deployment effect, note terminalization, and root closure;
- migrate the remaining legacy terminal implementation notes without dispatch;
- pass the first activation acceptance;
- perform a formal rollback with all writers disabled;
- prove the heartbeat advances while durable lifecycle facts do not change;
- replay migration and restore readers, writers, planner, and canary in order;
- reject the old acceptance because it was bound to a stale revision;
- pass a fresh 15-check acceptance; and
- 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
RUNNINGcount; - 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
046b44f0everywhere 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
pompomor directly onseykhl.
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:
- ordinary creation captures safely;
--planproduces a validated revision-bound plan and stops;--auto-planprogresses only through standing policy and deterministic guards;openis the sole approved handoff to dispatch;- one lane-scoped arm and one runner claim own execution;
- the refinery gates and merges only the exact candidate;
- terminal evidence closes the note and governing root;
- rollback disables authority without deleting history; and
- 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.