Skip to content

fix(park): wake a session off the roster when its wait settles - #1844

Merged
teemow merged 7 commits into
mainfrom
fix/1818-off-roster-wakes
Nov 1, 2026
Merged

teemow merged 7 commits into
mainfrom
fix/1818-off-roster-wakes

Conversation

@teemow

@teemow teemow commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Problem

A session waiting on something beekeeper settles is woken only if it is on the roster. Off the roster, nothing reaches it: payOwed drops the outcome of a queued merge (the gate's exit 76) or of a CI wait that left its call ("is no registered agent, nobody is woken"). agents park refuses the session, and agents wake resolves roster names only. A park on a pull request also never settles on a review or comment, only on a merge or close.

Change: one wake path, by session id

agents wake stays the only way to deliver a wake. When no roster agent matches, it now resolves a session off the roster (cmd/offroster.go):

  • a role holder, by name or ids. This covers a supervisor started by hand.
  • a session parked off the roster.
  • any session by its own id: a CLI session id, a desktop local_ id, or an omp_/agy_/codex_ id.

Delivery then runs exactly as for a roster agent: by name to a running CLI, a desktop turn, a background claude --bg --resume (so a stopped background session resumes from its transcript too), a headless resume, or the harness inbox. A session that no longer exists (no transcript to resume, no harness process reading its inbox) is refused with the new errGone.

Every waiter is recorded by its session (state.Party: session id, desktop or harness id), so the wake needs no roster entry:

  • Gate exit 76 and CI waits (tellOwner → payOwed): the owner is woken by its session id, on the roster or off it. If its session is gone, the outcome is no longer owed and the log gets one devctl.unheard line. An off-roster owner's session records are cleared, so the issues it served go back to the board.
  • Parks: agents park from a session off the roster is kept in the new state.Parks (one per session); joining the roster (agents register) ends it, as a new task ends an agent's park, and a resume of a session that joined the roster otherwise wakes nobody. It settles in settledParks/markResumable like a roster agent's park and is resumed by autoResume or agents resume <id|name> through the same wake. If the session is gone, it gets one line and its records go back to the board.
  • Park on a pull request also settles on the PR's newest review or comment by anyone but its author, if it is newer than the park. RefStates reads the author and the last 10 reviews and comments of each pull request in the same GraphQL request. Pending reviews are left out. A review under the author's own login settles nothing (the park help says so): where every session posts under one login, the desk's own review does not wake a park.
  • Note answers to a filer off the roster reach it by its session id too (tellFiler).

I chose this path over registering the caller at its first wait. Registering would list a supervisor or guide session as spare worker capacity, and the loop could staff it. The roster stays the worker roster, and every waiter is still woken through agents wake.

What this settles

Proof

  • Unit tests for each wait kind: TestAQueuedMergeWakesItsOwnerOffTheRoster (exit 76), TestAnUnheardRunWakesItsOwner (CI wait, roster and off the roster), TestAReviewResumesASessionParkedOffTheRoster, TestAReviewMakesAnAgentsParkResumable, TestResumeOffTheRoster and TestAGoneOwnerGetsOneLine (gone session: no wake, one line, records back to the board), TestJoiningTheRosterEndsAParkOffIt, TestWakePartyReachesSessionsOffTheRoster, TestRefStatesReadsAPullRequestsNewestReply.
  • Base 6403fef (main, including the stop of parked background sessions from fix(agents): stop a parked background session once its turn ends #1825, which this wake resumes from its transcript), head f2996c9 (the later heads add a gofmt fix and the review's fix, head 86aa488, proven by CI): go vet ./cmd ./internal/github ./internal/state clean; go test ./internal/github ./internal/state ok; go test ./cmd -run 'Park|Resum|Owner|Owed|OffTheRoster|WakeParty|Gone|Unheard|Waiter|Queued|Background|Gate|Wake|Note|Filer|Answer' ok; golangci-lint run on the changed packages reports only one errcheck, in cmd/agentstartcodex_test.go:512, which is on main and untouched here.
  • Live proof with a session off the roster follows the release.

Closes #1818
Closes #1777
Closes #1154

A queued merge, a CI wait that left its call, a park and a note answer now
wake their waiter by its session id through agents wake, on the roster or
off it. agents wake resolves a role holder or any session by its own id
when no roster agent matches; a session that is gone gets no wake and one
line, and the issues it served go back to the board. Parks of sessions off
the roster live in state.Parks. A park on a pull request also settles on a
review or comment by anyone but its author since the park.
…akes

* origin/main:
  fix(agents): stop a parked background session once its turn ends (#1825)
  fix(capacity): treat an unopened five-hour window as unused (#1808)
  fix(supervisor): keep the loop's inner-loop states out of the person's decisions (#1828)
  feat(run): add opt-in worktree builder Jobs (#1811)

# Conflicts:
#	cmd/agentpark.go
@teemow
teemow requested a review from a team as a code owner November 1, 2026 00:56

@teemow teemow left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: changes requested
Head reviewed: 4b6c67bfdcbf8271f63efe4d3912fc6cac79d8ea

The PR does what #1818 asks. Gate exit 76 and CI-wait owners, parks, and note filers off the roster are now woken by session id through agents wake. A gone session gets one line, and its records go back to the board. Each wait kind has a unit test. At this head, go vet ./cmd ./internal/github ./internal/state is clean, go test ./internal/github ./internal/state passes, and the PR's go test ./cmd -run '…' selection (plus Register) passes. One blocker:

Blocking

  1. An off-roster park outlives the session joining the roster, then wakes the agent with a stale "your park is over" (cmd/agents.go:401 registerAgent, cmd/agentpark.go:179, cmd/agentpark.go:665).
    Only a new agents park by the same session (agentpark.go:179) or a resume removes an entry from state.Parks. registerAgent never touches st.Parks, and neither does anything that ends a roster park (reportIdle, a new task). Here is the failure, reproduced at this head with a throwaway test built on the PR's own helpers (parkingWatch, replyingRefs, stubWakes):

    1. offSession parks on o/r#1.
    2. It registers (registerAgent) and takes a new task. st.Parks still has 1 entry.
    3. A comment lands on o/r#1.
    4. w.pending wakes the now-busy roster agent with: Your park on o/r#1 (the desk review) is over: o/r#1 has a new comment by a-reviewer … Re-query the live state of your task and continue it.

    The cause is that wakeParty resolves the session id to the roster agent first, so resumeOffRoster delivers to it. The same stale park also stays visible to offRosterParty/offRosterPark after the session registers.
    Fix: registering takes the session's entry out of st.Parks, either dropping it or carrying it over as reg.park when the new entry has none. That gives the "a new task or agents idle ends the park" rule an off-roster equivalent. Add a regression test for: park off the roster → register → a settle wakes nobody.

Not blocking

  • Reply settling and this desk's single login. RefStates leaves out replies by the PR author (internal/github/refs.go:210), as #1154 specifies. On this desk, every session posts as the same GitHub login as the PR author, including the loop's review of a worker's PR. So a park on our own PR settles on a teammate's or an outside review, never on the desk's own review. The recent PRs (#1825, #1828, #1808, #1811) only have reviews and comments by that one login. Worth one sentence in the park help, so nobody parks on a PR expecting the loop's review to wake them.
  • Bot comments count as replies (anyone but the author). That is fine for now, but a CI or bot comment on a parked PR will resume the parker.
  • The PR description's proof names head f2996c9. The head is 4b6c67b, a gofmt-only commit on top, so the proof still holds. Update the description when you next push.

A session that parked off the roster and then registered kept its entry
in state.Parks, so a later settle woke the busy agent with a stale end of
park. registerAgent drops the entry, and a resume of a session that joined
the roster by any other way wakes nobody. The park help says a review
under the author's own login settles nothing.
@teemow
teemow merged commit c5c9188 into main Nov 1, 2026
16 checks passed
@teemow
teemow deleted the fix/1818-off-roster-wakes branch November 1, 2026 03:45
teemow added a commit that referenced this pull request Nov 1, 2026
* origin/main:
  fix(park): wake a session off the roster when its wait settles (#1844)
teemow added a commit that referenced this pull request Nov 1, 2026
…lert-state

* origin/main:
  fix(park): wake a session off the roster when its wait settles (#1844)
teemow added a commit that referenced this pull request Nov 1, 2026
* origin/main:
  fix(watch): stop sandbox scopes adopted by bubblewrap (#1862)
  fix(park): wake a session off the roster when its wait settles (#1844)
teemow added a commit that referenced this pull request Nov 1, 2026
* origin/main:
  fix(watch): stop sandbox scopes adopted by bubblewrap (#1862)
  fix(park): wake a session off the roster when its wait settles (#1844)
  chore(deps): update gsoci.azurecr.io/giantswarm/architect docker tag to v8.6.32 (#1854)
  feat(capacity): read Claude plan windows from its local usage command (#1829)
  fix(agents): resolve the worker contract against the host installation (#1851)
  fix(relay): allow roles to start in their configured folders (#1835)
  fix(deps): update module github.com/giantswarm/go-selfupdate to v1.7.2 (#1816)
  fix(note): recover notes after a failed initial status write (#1836)

# Conflicts:
#	internal/config/config_test.go
teemow added a commit that referenced this pull request Nov 1, 2026
* origin/main:
  fix(watch): stop sandbox scopes adopted by bubblewrap (#1862)
  fix(park): wake a session off the roster when its wait settles (#1844)
  chore(deps): update gsoci.azurecr.io/giantswarm/architect docker tag to v8.6.32 (#1854)
  feat(capacity): read Claude plan windows from its local usage command (#1829)
  fix(agents): resolve the worker contract against the host installation (#1851)
  fix(relay): allow roles to start in their configured folders (#1835)
  fix(deps): update module github.com/giantswarm/go-selfupdate to v1.7.2 (#1816)
  fix(note): recover notes after a failed initial status write (#1836)

# Conflicts:
#	CHANGELOG.md
teemow added a commit that referenced this pull request Nov 1, 2026
…uard

* origin/main:
  fix(park): wake a session off the roster when its wait settles (#1844)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant