revive — the fleet comes back on its own
What this is not for
This skill owns one thing nothing else owns: what restarts a fleet that has stopped, and what
proves it should be restarted at all. Everything around it already has an owner. Route there.
| The task |
Its owner |
Why not this skill |
| Provisioning, launching, integrating, tearing down; the six-element handoff bar |
successor |
It owns the five phases. This skill launches exactly one session — a campaign's coordinator — and that coordinator then runs Phase 1 for its own workers. The bar is cited here, never restated. |
| What is true of one delegated session right now |
successor-manager |
It owns the ownership register and the five verdicts. This skill asks a narrower question — present or absent — and asks it of the same channels. |
| What a campaign contains, and whether it may close |
campaign |
The revival manifest references a campaign by slug and copies nothing from it. Whether the work is finished is that engine's answer, forwarded. |
| Handing this session forward, once |
handoff / /relay |
A deliberate one-off with a human present. Revival is what happens when nobody is. |
| Whether to keep going at all |
endless |
Continuation doctrine. This skill restarts what was already decided; it never decides to start something new. |
| One bounded cycle and its ledger |
work-loop |
A loop asks whether to run again inside a session. This asks whether a session should exist. |
| Deleting the residue afterwards |
tidy |
This engine issues no delete verdicts, prunes nothing, and retires no row. |
The stance
A schedule may not decide what to do; it may only decide when to look.
The cadence is dumb — two crontab entries, always the same, gated on nothing. Every decision lives
in a committed manifest and in facts measured at the moment the tick fires. That split is the whole
design, and it is what makes the mechanism survive the two-day gap it exists for: a handoff written
on Saturday and fired on Monday describes a fleet that has moved, and a status written into a file
reads identically whether it is current or six hours old.
So the manifest stores the assignment and a not-before instant, and never a status. campaign
refuses a stored status for the same reason (ADR-0113); this is that rule applied to the second file
that could carry one, and revival-manifest-check.sh refuses status, state and progress at any
depth rather than at the top level, because the field people actually add is a note inside a role row.
The five preconditions, in order
A launch happens only when all five hold. The order is the substance: each channel after the first
cannot report its own absence.
digraph revive {
rankdir=LR;
bridge [label="1. RC bridge up\n(pid + procStart + socket)", shape=box];
health [label="2. fleet-health.sh", shape=box];
when [label="3. not_before passed", shape=box];
work [label="4. campaign has open items", shape=box];
absent [label="5. coordinator absent\nfrom the daemon roster", shape=box];
launch [label="relaunch the coordinator", shape=doublecircle];
refuse [label="refuse, naming which\nprecondition failed", shape=box, style=filled, fillcolor="#ffcccc"];
bridge -> health -> when -> work -> absent -> launch;
bridge -> refuse [label="exit 4"];
health -> refuse [label="exit 2 / 3"];
when -> refuse [label="exit 0"];
work -> refuse [label="exit 0"];
absent -> refuse [label="exit 0 / 1"];
}
- The bridge, because a session launched without it is unreachable, unlistable and unstoppable.
Checked as pid plus
procStart plus a control socket — a pid alone is reused within hours on
a busy host, and a liveness test that reads only the number passes forever.
- The daemon, via
fleet-health.sh, whose 2 and 3 are forwarded unchanged. Without this, step 5
is meaningless: a registry outlives the daemon that served it, and on 2026-08-19 five dead
sessions rendered as ordinary for eight hours (M-0014, ADR-0072).
- The not-before, read from the manifest. A weekly limit, a maintenance window or a deliberate
pause is a fact about the work, so it lives with the work.
- Open items, from the
campaign engine. Its undetermined is forwarded, never collapsed into
"nothing to do" — those are different sentences.
- Absence, from the daemon's roster. Never from the session's own
state: a wedged session
reports running, and a dead one reports whatever the registry last knew.
Why only the coordinator
A cron job that also launched workers would be a second actor provisioning worktrees and reserving
identifiers for the same items — the collision successor Phase 1 exists to prevent, arriving from a
new direction. So an absent worker is a finding, reported for the coordinator to act on, and the
coordinator revives its own fleet the way it always would.
The commands
bash .claude/scripts/fleet-revive.sh --dry-run # what would happen, launching nothing
bash .claude/scripts/fleet-revive.sh # the decision, for real
bash .claude/scripts/fleet-heartbeat.sh --once # one line appended; decides nothing
bash .claude/scripts/revival-manifest-check.sh # the manifest is a plan, not a status
bash .claude/scripts/revival-cron.sh status # armed? and what would a tick do?
bash .claude/scripts/revival-cron.sh install # two entries; idempotent
bash .claude/scripts/revival-cron.sh remove # exactly what install wrote, and nothing else
Exit codes are the interface, so a caller greps nothing:
| Code |
Means |
0 |
nothing to do, or a launch was decided and performed |
1 |
findings — a missing worktree, an absent worker under a live coordinator |
2 |
the session daemon is down (forwarded) |
3 |
a channel could not be read (forwarded, or this engine's own fail-closed) |
4 |
the remote-control bridge is down |
64 |
usage |
The record
One JSON manifest per campaign under .claude/docs/revival/<slug>.json, committed. What it holds,
field by field, and what it deliberately does not:
revival-record.md.
What the heartbeat is for
It decides nothing and launches nothing. It appends one line per tick — the instant, the bridge
verdict, the live session ids, the open-item count — because after the 12h36m gap that produced this
skill, nobody could say when the fleet went quiet. Every channel that could have answered was a
snapshot of the present, and a snapshot cannot date an absence. A count it could not obtain is
written ?, never 0: an invented zero reads as a finished campaign.
Red flags
| Thought |
Reality |
| "The brief says it is an endless run" |
A brief is not a mechanism. That is the exact finding this skill remedies (harness:RM-0395). |
| "I'll write the handoffs now so they are ready" |
They will describe a fleet that has moved. Generate at fire time, from measurements. |
| "The manifest can note which wave we reached" |
That is a status, and revival-manifest-check.sh refuses it. Ask the campaign engine. |
| "The roster is empty, so nothing is running" |
An unreadable roster is not an empty one. Exit 3, and launch nothing. |
| "The supervisor pid is alive, so the bridge is up" |
Pids are reused. Check procStart too. |
| "A systemd user timer is the right tool" |
It is — on a host with linger enabled. Linger=no means user timers die at logout, which is the case this exists for. |
| "One cron line for Monday at 21:00" |
Cron has no year field, a powered-off host misses the minute, and the line must then delete itself. |
| "It is stalled, so relaunch it" |
That is successor-manager's verdict and successor's amended handoff, not this. Revival is for absent, not for stuck. |
| "Revive everything at once, it is faster" |
Two actors then provision the same worktrees. Coordinator only. |
Quick reference
| Need |
Where |
| What the manifest holds, field by field |
revival-record.md |
| Delegating one worker, and integrating it |
successor |
| Is that worker stalled, dead, or landed |
successor-manager |
| Whether the campaign may close |
campaign |
| Why landedness is content, never ancestry |
.claude/docs/adr/0093-branch-landedness-is-a-question-about-content.md |
| Why a session's own report is never evidence |
.claude/docs/adr/0072-liveness-is-never-read-from-the-subject.md |
| The matrix |
.claude/tests/fleet-revive.test.sh |
1---2name: revive3description: Bring a stopped fleet back without a human at the keyboard — a committed revival manifest, a scheduled engine that decides from channels no session controls, and a handoff generated at fire time. Use when a campaign must survive a shutdown, a weekly limit, or a turn that ends with work remaining.4---56# revive — the fleet comes back on its own78## What this is not for910This skill owns one thing nothing else owns: **what restarts a fleet that has stopped, and what11proves it should be restarted at all.** Everything around it already has an owner. Route there.1213| The task | Its owner | Why not this skill |14|---|---|---|15| Provisioning, launching, integrating, tearing down; the six-element handoff bar | `successor` | It owns the five phases. This skill launches exactly one session — a campaign's coordinator — and that coordinator then runs Phase 1 for its own workers. The bar is cited here, never restated. |16| What is true of one delegated session right now | `successor-manager` | It owns the ownership register and the five verdicts. This skill asks a narrower question — present or absent — and asks it of the same channels. |17| What a campaign contains, and whether it may close | `campaign` | The revival manifest **references** a campaign by slug and copies nothing from it. Whether the work is finished is that engine's answer, forwarded. |18| Handing this session forward, once | `handoff` / `/relay` | A deliberate one-off with a human present. Revival is what happens when nobody is. |19| Whether to keep going at all | `endless` | Continuation doctrine. This skill restarts what was already decided; it never decides to start something new. |20| One bounded cycle and its ledger | `work-loop` | A loop asks whether to run again inside a session. This asks whether a session should exist. |21| Deleting the residue afterwards | `tidy` | This engine issues no delete verdicts, prunes nothing, and retires no row. |2223## The stance2425**A schedule may not decide what to do; it may only decide when to look.**2627The cadence is dumb — two crontab entries, always the same, gated on nothing. Every decision lives28in a committed manifest and in facts measured at the moment the tick fires. That split is the whole29design, and it is what makes the mechanism survive the two-day gap it exists for: a handoff written30on Saturday and fired on Monday describes a fleet that has moved, and a status written into a file31reads identically whether it is current or six hours old.3233So the manifest stores **the assignment and a not-before instant**, and never a status. `campaign`34refuses a stored status for the same reason (ADR-0113); this is that rule applied to the second file35that could carry one, and `revival-manifest-check.sh` refuses `status`, `state` and `progress` at any36depth rather than at the top level, because the field people actually add is a note inside a role row.3738## The five preconditions, in order3940A launch happens only when all five hold. The order is the substance: each channel after the first41cannot report its own absence.4243```dot44digraph revive {45 rankdir=LR;46 bridge [label="1. RC bridge up\n(pid + procStart + socket)", shape=box];47 health [label="2. fleet-health.sh", shape=box];48 when [label="3. not_before passed", shape=box];49 work [label="4. campaign has open items", shape=box];50 absent [label="5. coordinator absent\nfrom the daemon roster", shape=box];51 launch [label="relaunch the coordinator", shape=doublecircle];52 refuse [label="refuse, naming which\nprecondition failed", shape=box, style=filled, fillcolor="#ffcccc"];5354 bridge -> health -> when -> work -> absent -> launch;55 bridge -> refuse [label="exit 4"];56 health -> refuse [label="exit 2 / 3"];57 when -> refuse [label="exit 0"];58 work -> refuse [label="exit 0"];59 absent -> refuse [label="exit 0 / 1"];60}61```62631. **The bridge**, because a session launched without it is unreachable, unlistable and unstoppable.64 Checked as pid **plus `procStart`** plus a control socket — a pid alone is reused within hours on65 a busy host, and a liveness test that reads only the number passes forever.662. **The daemon**, via `fleet-health.sh`, whose 2 and 3 are forwarded unchanged. Without this, step 567 is meaningless: a registry outlives the daemon that served it, and on 2026-08-19 five dead68 sessions rendered as ordinary for eight hours (M-0014, ADR-0072).693. **The not-before**, read from the manifest. A weekly limit, a maintenance window or a deliberate70 pause is a fact about the work, so it lives with the work.714. **Open items**, from the `campaign` engine. Its `undetermined` is forwarded, never collapsed into72 "nothing to do" — those are different sentences.735. **Absence**, from the daemon's roster. Never from the session's own `state`: a wedged session74 reports `running`, and a dead one reports whatever the registry last knew.7576## Why only the coordinator7778A cron job that also launched workers would be a second actor provisioning worktrees and reserving79identifiers for the same items — the collision `successor` Phase 1 exists to prevent, arriving from a80new direction. So an absent worker is a **finding**, reported for the coordinator to act on, and the81coordinator revives its own fleet the way it always would.8283## The commands8485```sh86bash .claude/scripts/fleet-revive.sh --dry-run # what would happen, launching nothing87bash .claude/scripts/fleet-revive.sh # the decision, for real88bash .claude/scripts/fleet-heartbeat.sh --once # one line appended; decides nothing89bash .claude/scripts/revival-manifest-check.sh # the manifest is a plan, not a status90bash .claude/scripts/revival-cron.sh status # armed? and what would a tick do?91bash .claude/scripts/revival-cron.sh install # two entries; idempotent92bash .claude/scripts/revival-cron.sh remove # exactly what install wrote, and nothing else93```9495Exit codes are the interface, so a caller greps nothing:9697| Code | Means |98|---|---|99| `0` | nothing to do, or a launch was decided and performed |100| `1` | findings — a missing worktree, an absent worker under a live coordinator |101| `2` | the session daemon is down (forwarded) |102| `3` | a channel could not be read (forwarded, or this engine's own fail-closed) |103| `4` | the remote-control bridge is down |104| `64` | usage |105106## The record107108One JSON manifest per campaign under `.claude/docs/revival/<slug>.json`, committed. What it holds,109field by field, and what it deliberately does not:110[revival-record.md](references/revival-record.md).111112## What the heartbeat is for113114It decides nothing and launches nothing. It appends one line per tick — the instant, the bridge115verdict, the live session ids, the open-item count — because after the 12h36m gap that produced this116skill, **nobody could say when the fleet went quiet.** Every channel that could have answered was a117snapshot of the present, and a snapshot cannot date an absence. A count it could not obtain is118written `?`, never `0`: an invented zero reads as a finished campaign.119120## Red flags121122| Thought | Reality |123|---|---|124| "The brief says it is an endless run" | A brief is not a mechanism. That is the exact finding this skill remedies (`harness:RM-0395`). |125| "I'll write the handoffs now so they are ready" | They will describe a fleet that has moved. Generate at fire time, from measurements. |126| "The manifest can note which wave we reached" | That is a status, and `revival-manifest-check.sh` refuses it. Ask the campaign engine. |127| "The roster is empty, so nothing is running" | An unreadable roster is not an empty one. Exit 3, and launch nothing. |128| "The supervisor pid is alive, so the bridge is up" | Pids are reused. Check `procStart` too. |129| "A systemd user timer is the right tool" | It is — on a host with linger enabled. `Linger=no` means user timers die at logout, which is the case this exists for. |130| "One cron line for Monday at 21:00" | Cron has no year field, a powered-off host misses the minute, and the line must then delete itself. |131| "It is stalled, so relaunch it" | That is `successor-manager`'s verdict and `successor`'s amended handoff, not this. Revival is for **absent**, not for stuck. |132| "Revive everything at once, it is faster" | Two actors then provision the same worktrees. Coordinator only. |133134## Quick reference135136| Need | Where |137|---|---|138| What the manifest holds, field by field | [revival-record.md](references/revival-record.md) |139| Delegating one worker, and integrating it | `successor` |140| Is that worker stalled, dead, or landed | `successor-manager` |141| Whether the campaign may close | `campaign` |142| Why landedness is content, never ancestry | `.claude/docs/adr/0093-branch-landedness-is-a-question-about-content.md` |143| Why a session's own report is never evidence | `.claude/docs/adr/0072-liveness-is-never-read-from-the-subject.md` |144| The matrix | `.claude/tests/fleet-revive.test.sh` |