Skip to content

Commit 9ceec2f

Browse files
Merge branch 'main' into hybrid-session-mode
2 parents ceb87fe + 514cf68 commit 9ceec2f

16 files changed

Lines changed: 1586 additions & 46 deletions

File tree

.github/agents/release-manager.agent.md

Lines changed: 254 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Monitoring
2+
3+
Two things in this process are easy to hand off passively and should not be: the release PR after
4+
it is opened, and the draft release after it is created. In both cases the agent has the context
5+
needed to interpret what happens next, and the user should not have to come back and report an
6+
outcome the agent could have observed.
7+
8+
Monitoring is **automatic and read-only**. It never merges, never pushes, and never publishes.
9+
Watching does not require permission; acting on what you see always does.
10+
11+
## Monitoring the release PR
12+
13+
Opening the release PR ends stage 1 and immediately begins stage 2, which owns the watch: it runs
14+
until every check reaches a terminal state. Reporting the PR URL and stopping leaves the user to
15+
discover failures themselves, which is exactly backwards.
16+
17+
Record the time accordingly. Stage 1 ends when the PR is created, and the CI watch that follows --
18+
including any red checks, corrective pushes, and re-runs -- belongs to stage 2. Attributing that
19+
time to stage 1 makes preparation look expensive and review look cheap, which is the opposite of
20+
what the summary should reveal.
21+
22+
### When to start a watch
23+
24+
Start, or restart, monitoring:
25+
26+
- Immediately after the release PR is created (prepare-release Step 13).
27+
- After **every** push to the release branch that follows -- CI fixes, release-note corrections,
28+
review feedback, rebases. Each push produces a new head SHA with its own set of runs.
29+
- When resuming a release in a later session, before reporting stage 2 status.
30+
31+
A restart is a fresh watch against the **new head SHA**. Runs from the previous SHA are stale;
32+
do not report them as current, and do not let a green run from an earlier commit stand in for the
33+
one now at the head of the branch.
34+
35+
### Running the watch
36+
37+
1. Resolve the current head SHA of the release branch.
38+
2. List every check for it, not just the ones you expect:
39+
```sh
40+
gh pr checks {pr-number} --watch
41+
```
42+
`--watch` blocks until all checks reach a terminal state. Where blocking is not appropriate,
43+
poll with `gh pr checks {pr-number} --json name,state,bucket,link` and report progress.
44+
3. Wait for **terminal** completion. A check that is queued, in progress, or pending is not a
45+
result. Do not summarize a partially-complete run as passing.
46+
4. Confirm the run set is complete. A workflow that never started -- because of a path filter, a
47+
skipped job, or a queue backlog -- is not the same as a workflow that passed. Compare against
48+
the checks seen on previous release PRs when something looks absent.
49+
50+
### Reporting
51+
52+
Report a compact per-check table plus a single overall verdict:
53+
54+
| Check | Result |
55+
|---|---|
56+
| Build / build (ubuntu-latest, net10.0) ||
57+
| Pack / APICompat ||
58+
| CodeQL / csharp ||
59+
| markdown-link-check ||
60+
61+
**Verdict: blocked** -- Pack / APICompat failed.
62+
63+
Use three states and name them explicitly: **green**, **running**, **blocked**. "Blocked" covers
64+
any non-green terminal state, including cancelled and timed-out runs.
65+
66+
### On failure
67+
68+
Diagnose before proposing anything. A retry suggested without a diagnosis is a guess, and rerunning
69+
a deterministic product failure wastes a full CI cycle to arrive at the same red.
70+
71+
1. **Retrieve the logs automatically.** Do not ask the user to paste them.
72+
```sh
73+
gh run view {run-id} --log-failed
74+
```
75+
2. **Classify the failure**, because the two classes call for opposite responses:
76+
77+
| Class | Signals | Response |
78+
|---|---|---|
79+
| **Product / API validation** | ApiCompat or package validation errors, compile errors, assertion failures, behavior differences | Real. Diagnose it. Never rerun to make it go away |
80+
| **Infrastructure / tooling** | Runner allocation, network or feed timeouts, artifact upload, rate limits, cancelled by concurrency | A rerun is reasonable, once, with the reason stated |
81+
82+
Flaky tests sit between the two. Treat a failure as flaky only with evidence -- a known issue, a
83+
prior occurrence, or a pass on rerun of the identical SHA -- never because rerunning is easier
84+
than reading the log.
85+
86+
3. **For ApiCompat and package validation failures specifically**, apply the interpretation rules in
87+
[apicompat-apidiff.md](../../../skills/prepare-release/references/apicompat-apidiff.md) before
88+
concluding the release is breaking. `Unnecessary suppressions found` and a stale baseline
89+
produce large, convincing, and entirely phantom break listings.
90+
91+
4. **Present the diagnosis with a proposed fix, and stop.** Applying the fix means a commit and a
92+
push to the release branch, which requires explicit user approval like any other push. Delegate
93+
the fix to the child session on the release worktree; never commit in the orchestrator session.
94+
95+
5. After an approved fix is pushed, **restart the watch** for the new SHA without being asked.
96+
97+
### Stage 2 handoff
98+
99+
Stage 2 stays **blocked** until the checks are green, or until the user explicitly decides to
100+
proceed anyway. Record that decision and who made it.
101+
102+
When handing off, lead with CI status rather than only inviting review:
103+
104+
> **CI: green** -- all {n} checks passed on `{sha}`. PR #{number} is ready for your review and merge.
105+
106+
or
107+
108+
> **CI: blocked** -- {check name} failed on `{sha}`. Diagnosis below. PR #{number} is not ready
109+
> to merge yet.
110+
111+
or
112+
113+
> **CI: running** -- {done} of {n} checks complete, none failed. I am still watching and will report when
114+
> they finish.
115+
116+
Never say only "the PR is up, please review and merge." Without a CI verdict the user has to go
117+
find out for themselves whether that invitation is even actionable.
118+
119+
## Monitoring the draft release
120+
121+
Creating the draft release ends stage 3. Stage 4 is a human action in the GitHub UI, and the
122+
temptation is to hand off and wait to be told it happened. Do not. Publishing is the moment the
123+
release becomes irreversible and the moment two workflows start, so it is the least useful point in
124+
the process to be uninformed about.
125+
126+
Watch the release until it is no longer a draft:
127+
128+
```sh
129+
gh release view v{version} --json isDraft,publishedAt,tagName,isPrerelease
130+
```
131+
132+
Poll at a modest interval. This gate is human-paced and may sit for hours or span a session, so
133+
prefer periodic checks over a tight loop, and say that you are watching rather than going silent.
134+
135+
**`isDraft: false` is the trigger.** The moment it flips:
136+
137+
1. Record the stage 4 end time from `publishedAt`, not from when you noticed. The user published
138+
when they published; polling latency is yours, not theirs, and it should not inflate the stage
139+
duration in the closing summary.
140+
2. Confirm the details that were the user's to choose and cannot be inferred: the tag actually
141+
created, and whether the release was marked as a prerelease. A stable release mistakenly left
142+
unflagged, or a prerelease flagged as stable, changes what consumers receive.
143+
3. **Begin stage 5 immediately** via the verify-release skill. Publishing starts the Release and
144+
Publish Docs workflows in parallel right away; waiting to be told to verify means arriving after
145+
the interesting part. Announce the transition rather than asking permission -- stage 5 is
146+
read-only, and the irreversible act has already occurred.
147+
148+
### What else the watch can find
149+
150+
Not every change to the draft means it was published, and the difference matters:
151+
152+
| Observation | Meaning | Response |
153+
|---|---|---|
154+
| `isDraft: false` | Published | Start stage 5 |
155+
| Still a draft, body changed | The user is editing the notes, possibly removing the AI disclosure | Nothing. Do not re-add anything they removed |
156+
| Draft no longer exists | Deleted, or published under a different tag | Check for a published release before assuming it was abandoned; ask |
157+
| Published with an unexpected tag | The tag differs from the prepared version | Stop and confirm before verifying. Verifying the wrong version is worse than not verifying |
158+
159+
If the user says they published but the API still reports a draft, trust the API and say so plainly
160+
-- an unsaved draft or a failed publish looks identical to success from the browser.
161+
162+
### Stage 4 handoff
163+
164+
Hand off with the action and the watch, so the user knows they do not need to come back and report:
165+
166+
> The draft release for **v2.1.0** is ready. Review the notes line by line, set the prerelease flag
167+
> if applicable, and click **Publish release**. Once you have signed off you may remove the AI
168+
> disclosure from the notes.
169+
>
170+
> I am watching for publication and will start verification automatically when it happens.

0 commit comments

Comments
 (0)