|
| 1 | +# Delegation and Worktrees |
| 2 | + |
| 3 | +The release-manager session is an **orchestrator**. It stays on whatever branch it started on and |
| 4 | +never checks out or mutates a release branch. Work that creates commits happens in a **child session |
| 5 | +on its own worktree**, based on the target release branch. |
| 6 | + |
| 7 | +This mirrors how [`docs.yml`](../../../workflows/docs.yml) already works: the orchestration scripts run |
| 8 | +from a single fixed checkout, while each version's content is built from its own tag in a separate |
| 9 | +worktree. |
| 10 | + |
| 11 | +## Why |
| 12 | + |
| 13 | +- **Current orchestration.** The agent runs from the checkout it was launched in, so a servicing |
| 14 | + release for an older branch still uses the process as it exists in that checkout, not the process |
| 15 | + as it existed when the release branch forked. |
| 16 | +- **A clean working tree.** The orchestrator holds long-lived session state -- stage timings, gate |
| 17 | + interactions, the progress rail. Checking out branches underneath it risks losing that context |
| 18 | + and makes "which branch am I on?" a source of error at exactly the moment precision matters. |
| 19 | +- **Isolation of the risky part.** Only stage 1 writes to the repository. Confining it to a |
| 20 | + disposable worktree means an abandoned or failed preparation leaves the orchestrator's branch |
| 21 | + untouched. |
| 22 | +- **Concurrency.** A `2.0.0-preview.2` preparation and a `1.3.1` servicing preparation can proceed |
| 23 | + independently, each in its own worktree. |
| 24 | + |
| 25 | +## What runs where |
| 26 | + |
| 27 | +| Stage | Mutates the repo? | Runs where | |
| 28 | +|---|---|---| |
| 29 | +| 1. Prepare | **Yes** -- version bump, suppressions, docs, commit, branch, PR | **Child session** on a worktree based on the source/base branch | |
| 30 | +| 2. Review and merge | No -- reads CI and PR state | Orchestrator, in place | |
| 31 | +| 3. Publish | No -- reads merged PR, writes only a GitHub draft release | Orchestrator, in place | |
| 32 | +| 4. Release | No -- human action in the GitHub UI | Orchestrator, in place | |
| 33 | +| 5. Verify | No -- reads workflow runs and published artifacts | Orchestrator, in place | |
| 34 | + |
| 35 | +Stage 3 does edit `src/PACKAGE.md` and `README.md` when the README checklist finds issues. **The |
| 36 | +release branch is already merged by this point, so those fixes cannot land on it.** They go to the |
| 37 | +base branch the release ships from — `main` or `release/{MAJOR}.x` — which is protected, so they |
| 38 | +need their own small PR, reviewed and merged like any other change. |
| 39 | + |
| 40 | +Delegate that PR the same way as stage 1: a child session on a fresh worktree based on the base |
| 41 | +branch. Do not push directly to the base branch, and do not commit into the orchestrator's worktree. |
| 42 | + |
| 43 | +A corrective commit merged at this point **is not in the draft release's tag**, because the draft is |
| 44 | +pinned to the merge commit the user approved. After the fix merges, re-target the draft to the new |
| 45 | +head and regenerate the notes per |
| 46 | +[publish-release Step 9](../../../skills/publish-release/SKILL.md). Skipping the re-target ships a |
| 47 | +tag that predates the fix while the notes describe the fixed state. |
| 48 | + |
| 49 | +## Confirm the orchestrator's location |
| 50 | + |
| 51 | +Before starting any stage, note the branch this session started on and confirm the working tree is |
| 52 | +clean. Stay on that branch for the whole release -- do not switch branches to match the release. |
| 53 | + |
| 54 | +- **Dirty working tree** -- report the uncommitted changes and ask how to proceed. Do not stash, |
| 55 | + reset, or commit unrelated work. |
| 56 | +- **Session started on a release branch** -- that is fine; the orchestrator only reads. Still |
| 57 | + delegate stage 1 to a worktree rather than committing in place. |
| 58 | + |
| 59 | +A status assessment is read-only and is safe from anywhere; say so rather than blocking the user on |
| 60 | +a technicality. |
| 61 | + |
| 62 | +## Delegating stage 1 |
| 63 | + |
| 64 | +Create the child session with the **source/base branch** selected in prepare-release Step 1 as its |
| 65 | +base -- `main` or `release/{MAJOR}.x`. The child creates the `release-{version}` work branch itself, |
| 66 | +as part of the skill's Step 6. Do not create that branch yourself, and do not pass it as the base. |
| 67 | + |
| 68 | +The worktree must be **fresh and based on the upstream's latest state** for that branch. A worktree |
| 69 | +cut from a stale local branch, or missing tags, silently corrupts the entire release: the PR range |
| 70 | +is computed from the wrong starting point, and the ApiCompat baseline resolves to the wrong commit |
| 71 | +or fails to resolve at all. Before the child begins Step 1, it must complete prepare-release |
| 72 | +**Step 0**: identify the upstream remote, `git fetch {upstream} --prune --prune-tags --tags`, and |
| 73 | +base its work on the remote-tracking ref rather than a local branch. |
| 74 | + |
| 75 | +Reuse of an existing worktree is the common way this goes wrong. Prefer creating a new one per |
| 76 | +release. If you do reuse one, fetch and reset it to the upstream ref first, and confirm it is clean |
| 77 | +-- do not assume a worktree left over from a previous release is current. |
| 78 | + |
| 79 | +The child's kickoff prompt must carry everything it needs, because it does not share your context: |
| 80 | + |
| 81 | +1. The instruction to run the **prepare-release** skill, **starting at Step 0**. |
| 82 | +2. The source/base branch, already selected. |
| 83 | +3. The target commit or ref, if the user chose one. |
| 84 | +4. Any decisions the user has already made -- the confirmed version, breaking-change conclusions, |
| 85 | + or a chosen preamble -- so the child does not re-litigate them. |
| 86 | +5. The requirement to **stop at the skill's Step 12 gate** and report back rather than pushing or |
| 87 | + creating the PR. |
| 88 | +6. The instruction to report anything the Step 0 fetch changed, and to stop rather than proceed if |
| 89 | + the previous release tag is not an ancestor of the target. |
| 90 | +7. The requirement to **stop at the skill's Step 10b gate** and bring the categorization table and |
| 91 | + acknowledgements roster back to you, so the user reviews notes content before a PR exists. |
| 92 | + |
| 93 | +If app-native child sessions are not available in the current environment, fall back to a git |
| 94 | +worktree created from the source/base branch and run the skill there, keeping the orchestrator's |
| 95 | +own checkout untouched. The invariant is the worktree, not the mechanism. |
| 96 | + |
| 97 | +## Recording the child |
| 98 | + |
| 99 | +The moment you dispatch a child, write its identity into `release_session` -- `child_session_id`, |
| 100 | +`child_worktree_path`, and `child_branch`. A release routinely outlives the session that started |
| 101 | +it, and a worktree with no recorded owner is very hard to tell apart from the dozens of unrelated |
| 102 | +worktrees a busy repository accumulates. |
| 103 | + |
| 104 | +## Recovering an interrupted preparation |
| 105 | + |
| 106 | +A child can stop anywhere: it fails, the user closes it, or the orchestrator session ends while the |
| 107 | +child is mid-flight. Recovery starts from what the worktree actually contains, never from the fact |
| 108 | +that it exists. |
| 109 | + |
| 110 | +**Existence is not progress.** A `release-{version}` worktree proves only that a preparation was |
| 111 | +started. Read its state before deciding anything: |
| 112 | + |
| 113 | +| Evidence in the child's worktree | Where the preparation stopped | |
| 114 | +|---|---| |
| 115 | +| No `release-{version}` branch | Before Step 6; nothing to salvage | |
| 116 | +| Branch exists, working tree dirty, no commit | Mid-preparation, somewhere in Steps 6-11 | |
| 117 | +| Branch has a commit, nothing pushed | At the Step 12 gate, prepared and awaiting approval | |
| 118 | +| Branch pushed, no PR | Interrupted inside Step 13 | |
| 119 | +| PR open | Step 13 finished; this is stage 2, not stage 1 | |
| 120 | + |
| 121 | +Then apply three rules: |
| 122 | + |
| 123 | +- **Never reset or recreate a branch that has a commit on it.** It may hold work the user already |
| 124 | + reviewed and corrected -- release-note categorization, acknowledgement edits, a chosen preamble -- |
| 125 | + none of which is reproducible from the repository. Read the commit and the drafted notes and |
| 126 | + continue from there. |
| 127 | +- **Never inherit a validation result.** Build, pack, and ApiCompat outcomes leave no trace in git. |
| 128 | + A commit proves the files were written, not that anything passed. Re-run the checks rather than |
| 129 | + assuming the interrupted run got that far. |
| 130 | +- **Prefer resuming the recorded child over launching a replacement.** It still holds the context. |
| 131 | + If it is gone, dispatch a replacement pointed at the *existing* worktree and branch, and tell it |
| 132 | + to audit what is already there before continuing -- not to start over. |
| 133 | + |
| 134 | +Report the stopping point and the evidence you read, and let the user confirm before continuing. |
| 135 | + |
| 136 | +Decisions the user made at a gate are the hardest thing to recover, because session tracking does |
| 137 | +not survive the session. Their durable form is the artifact itself: the drafted release notes carry |
| 138 | +the categorization, and the acknowledgements roster carries the exclusions. On resume, re-derive the |
| 139 | +decisions by reading the drafted notes, and present them as *previously decided* for confirmation. |
| 140 | +Silently re-deriving them from scratch will quietly undo corrections the user already made once. |
| 141 | + |
| 142 | +## Gates stay with the orchestrator |
| 143 | + |
| 144 | +The human gates belong to the orchestrator session. The child prepares and reports; the user |
| 145 | +approves in the conversation they are already having with you; you relay the approval. |
| 146 | + |
| 147 | +Never let the child push a branch, open a PR, or create a release on its own initiative. When the |
| 148 | +child reaches Step 12, it reports the full release summary back to you, you present that to the |
| 149 | +user with the progress rail, and only after explicit approval do you instruct the child to proceed |
| 150 | +with Step 13. |
| 151 | + |
| 152 | +## Timing across sessions |
| 153 | + |
| 154 | +Session tracking stays in the **orchestrator**. A stage delegated to a child is still one stage on |
| 155 | +your timeline: record `started_at` when you dispatch the child, and `ended_at` when its gate is |
| 156 | +satisfied. |
| 157 | + |
| 158 | +Time the child spends working is **wait time**, not interaction time -- the user is not answering |
| 159 | +prompts while the child builds and packs. Time the user spends reviewing what the child reported |
| 160 | +**is** interaction time. See [session-tracking.md](session-tracking.md). |
| 161 | + |
| 162 | +## Cleaning up |
| 163 | + |
| 164 | +When a release is complete, offer to remove the worktrees created for it. If a preparation was |
| 165 | +abandoned, say the worktree and its `release-{version}` branch still exist and offer to remove |
| 166 | +them. Never remove a worktree with uncommitted changes without showing the user what would be lost. |
0 commit comments