7f9137fcb6
The lead rollover handover file now lives inside the workspace, at the relative path fleetd.yaml's leadRollover.handoverPath names. It is a snapshot of one moment's live state, so it must never enter git history. The handover skill also now says the handoverPath fleetd hands back is always absolute, even when the configured value is relative — a lead that resolves it itself can pick a different file from the one the daemon checks.
215 lines
9.9 KiB
Markdown
215 lines
9.9 KiB
Markdown
---
|
||
name: handover
|
||
description: Procedure for an outgoing lead to write the handover file that a fresh lead session inherits. Load this when your context is filling up and you are about to be replaced, whether you hand off by hand or fleetd does it for you. The file is the new lead's only inheritance — follow it exactly.
|
||
---
|
||
|
||
# Handover — write the file the next lead depends on
|
||
|
||
A lead session fills up its context and has to be replaced by a fresh one. The outgoing lead
|
||
writes a handover file, and the new session reads that file and carries on.
|
||
|
||
**There are two ways to hand off, and the file is the same either way.**
|
||
|
||
- **By hand.** You write the file, then tell the operator where it is. The operator starts the new
|
||
session and points it at the file. This always works.
|
||
- **With `fleet_handover`** (fleetd #480, merged 2026-09-11). You ask fleetd to do the swap: it
|
||
checks the file, clears your pane, and tells the fresh session to read it. This needs
|
||
`leadRollover:` in `fleetd.yaml`; without it every action answers a clean refusal naming
|
||
`NOT_CONFIGURED`, and you fall back to the manual path. Section 11 below is the procedure.
|
||
|
||
Nothing else in this skill changes between the two. Only who performs the swap changes.
|
||
|
||
**The new lead's only inheritance is that file.** It does not see your conversation, your plan,
|
||
or your screen. If the file is thin or wrong, the new lead re-derives what you already knew, and
|
||
that wastes hours. Writing a good handover file is real work. It is not paperwork you rush
|
||
through at the end of a session.
|
||
|
||
This skill is the procedure for writing it. Every rule below earned its place because a past
|
||
handover got it wrong.
|
||
|
||
## 1. Confirm you are the right session to write this
|
||
|
||
Run `fleet_whoami` first. It must answer `primary`. Only a primary (lead) session writes a
|
||
handover file. A worker's job ends with its own pull request, not a fleet-wide handoff.
|
||
|
||
## 2. Every number needs a command, run in this turn
|
||
|
||
A number is a claim: a count, a commit hash, a process id, a percentage, a queue depth. Before
|
||
you write one, run the command that produces it — now, in this turn, against the live state.
|
||
|
||
Never take a number from:
|
||
|
||
- earlier in your own conversation — the state has moved since then,
|
||
- a peer lead's report — that is their measurement, not yours,
|
||
- your own memory of an earlier session.
|
||
|
||
Put the command, or its real output, next to the number. That lets the next lead re-run it and
|
||
check it still matches. If you cannot measure something yourself, say so instead of guessing:
|
||
"the fleet01 lead reports 91 commits behind; I have not checked this myself."
|
||
|
||
## 3. Say what you measured and what you did not
|
||
|
||
Mark every claim as one of two things:
|
||
|
||
- **"I checked this myself, in the code or on this host, at `<time>`."**
|
||
- **"I did not check this myself; `<who>` reported it."**
|
||
|
||
Never present someone else's measurement as your own. This matters most for cross-host claims —
|
||
a peer lead's daemon, a worker's report, or something the operator said earlier that you cannot
|
||
re-verify from here.
|
||
|
||
## 4. Record open decisions, and who owns them
|
||
|
||
List three things:
|
||
|
||
- what the operator actually asked for, in their own words where you have them,
|
||
- what is still unanswered,
|
||
- any question you decided yourself instead of asking, with your reason.
|
||
|
||
Write the decision so it cannot be mistaken for the operator's instruction. Say plainly: "the
|
||
operator never answered X; I decided Y, because Z." Without this, the next lead either silently
|
||
reopens a closed question or assumes the operator chose something they never did.
|
||
|
||
## 5. Record live hazards
|
||
|
||
List anything that will break if the next lead does the obvious thing next. This includes:
|
||
|
||
- unpushed commits or unmerged branches,
|
||
- a build, a spawn, or a redeploy still running,
|
||
- code merged to `main` but not yet redeployed to the live daemon,
|
||
- any trap that looks safe and is not — say what goes wrong and why, not only that something is
|
||
"tricky."
|
||
|
||
## 6. Record what is explicitly not owed
|
||
|
||
List work that is finished, and work that another party has said they do not want touched. Name
|
||
who said so and when. Without this line, the next lead re-does closed work or reopens a question
|
||
a peer already declined to revisit.
|
||
|
||
## 7. Open the file with three re-measurement commands
|
||
|
||
The file's own first section must give the next lead three concrete commands to run before
|
||
acting on anything else in the file:
|
||
|
||
1. confirm role — for example `fleet_whoami`,
|
||
2. confirm the state of the working tree — for example `git status` and
|
||
`git rev-list origin/main..HEAD`,
|
||
3. read the live fleet — for example `fleet_list`.
|
||
|
||
Record what each command answered when you wrote the file, and tell the reader to run it again
|
||
rather than trust your answer. The point of this section is that the reader checks live state
|
||
before acting on any claim in the rest of the file, including yours.
|
||
|
||
## 8. Stamp the file with time and commit
|
||
|
||
Near the top of the file, write:
|
||
|
||
- the date and time you wrote it,
|
||
- the commit the tree was on (`git rev-parse HEAD`),
|
||
- whether the tree was clean (`git status`).
|
||
|
||
Without this, nobody can tell how old the file is, or which code it describes.
|
||
|
||
## 9. State plainly that the file goes stale fast
|
||
|
||
Say near the top: **re-measure anything you act on.** The file goes stale the moment anyone
|
||
merges a branch, spawns a member, or restarts the daemon. Everything in the file is a snapshot
|
||
of one moment, not a live fact.
|
||
|
||
## 10. What to leave out
|
||
|
||
Do not include:
|
||
|
||
- narration of how the session felt, or how hard something was,
|
||
- anything the repo already records — code structure, git history, or a rule already written in
|
||
`CLAUDE.md`. Point at it instead of repeating it,
|
||
- advice that is only true for the session that is ending — a half-open terminal, a local
|
||
variable, a train of thought with no state behind it.
|
||
|
||
A handover file is a record of state and decisions. It is not a diary.
|
||
|
||
## 11. Using `fleet_handover` (only if `leadRollover:` is configured)
|
||
|
||
**Run the three steps in this order. The order is not a style choice — the wrong order is
|
||
refused.**
|
||
|
||
1. **`fleet_handover{action: "open", reason: "<why now>"}`.** It returns a `token` and the
|
||
`handoverPath` you must write to. Nothing has happened to your pane yet.
|
||
|
||
**Write to exactly that path, and do not resolve it yourself.** It is always absolute, even when
|
||
the operator configured a relative `handoverPath`: fleetd resolves a relative one against your
|
||
own workspace before it hands it to you. The daemon and your pane can run in different
|
||
directories, so a path you resolve yourself can point at a different file from the one the daemon
|
||
will check.
|
||
2. **Write the handover file at that path**, following sections 1–10 above.
|
||
3. **Ask the operator, then `fleet_handover{action: "confirm", token, operatorConfirmed: true}`.**
|
||
|
||
Why that order: `confirm` refuses with `HANDOVER_STALE` unless the file was modified **after** the
|
||
`open` request. That check stops a leftover file from an earlier session being accepted as this
|
||
one's handover. So writing the file first and then calling `open` — the obvious order — always
|
||
fails.
|
||
|
||
`{action: "cancel", token}` drops a pending request without rolling.
|
||
|
||
**Things that will surprise you:**
|
||
|
||
- **`accepted` does not mean your pane has been cleared.** It means every gate passed and the roll
|
||
is scheduled to run once your current turn ends. Say your goodbye in the same turn — you will not
|
||
get another one.
|
||
- **There is no terminal or session parameter, on purpose.** The pane is always your own, resolved
|
||
from your connection, so you can only ever roll yourself.
|
||
- **`operatorConfirmed` is your report of what a human told you.** Do not pass `true` because you
|
||
are confident. Ask, wait for the answer, then pass what they said. `requireOperatorConfirm`
|
||
defaults to `true` and this is the only thing standing between a judgement call and a wiped
|
||
session.
|
||
- **The roll can still refuse after `confirm` returns**, and by then there is no caller to tell.
|
||
Those outcomes are logged only, as `lead-rollover:` lines in the daemon log.
|
||
- **If the bootstrap prompt never lands, your context is gone and no fresh session starts.** This
|
||
has not yet been proven end-to-end (see fleetd #480). The recovery is the manual path: the file
|
||
is already written, so the operator starts a session and points it at the file. That is why you
|
||
write the file before you confirm, and never the other way round.
|
||
|
||
## Writing style
|
||
|
||
Write in plain English. Use everyday words, one idea per sentence, and active voice. Keep every
|
||
class, method, file, flag, and config key exactly as it appears in the code — replacing a
|
||
precise term with a vague one makes the sentence wrong, not simpler. Explain an abbreviation the
|
||
first time you use it.
|
||
|
||
If a diagram genuinely helps, put it in the `.md` file as a fenced ` ```mermaid ` block with no
|
||
hardcoded colors, so it stays readable on light and dark backgrounds. Quote any label that has
|
||
brackets, colons, or slashes.
|
||
|
||
## Template
|
||
|
||
```markdown
|
||
# Handover — <fleet name> lead session, <date and time>
|
||
|
||
Written at commit `<output of git rev-parse HEAD>`. Tree was <clean, or dirty: `<git status
|
||
summary>`>. Re-measure anything you act on — this file goes stale the moment anyone merges,
|
||
spawns, or restarts.
|
||
|
||
## 0. Do these three things first
|
||
|
||
1. Confirm your role: `fleet_whoami` — must answer `primary`. (Answered `<result>` at `<time>`.)
|
||
2. Confirm tree state: `git status`, `git rev-list origin/main..HEAD`. (`<result>` at `<time>`.)
|
||
3. Read the live fleet: `fleet_list`. (`<result>` at `<time>`.)
|
||
|
||
## 1. What the operator asked for
|
||
|
||
<the live instructions, in their words where you have them; what is still open; any decision
|
||
you made yourself, and why>
|
||
|
||
## 2. Open decisions, and who owns them
|
||
|
||
<one line per decision: who owns it, what is unanswered>
|
||
|
||
## 3. Live hazards
|
||
|
||
<one entry per hazard: what breaks, and why, if the next lead does the obvious thing>
|
||
|
||
## 4. What is not owed
|
||
|
||
<finished work, and work another party has declined; name who said so and when>
|
||
```
|