Show when a task is parked on a third party - #434
Open
dkrattiger wants to merge 1 commit into
Open
dkrattiger wants to merge 1 commit into
dkrattiger wants to merge 1 commit into
Conversation
A task awaiting someone else's review sits at `turn=user` and renders yellow — indistinguishable from work that is actually yours. The only way to find out was to open it, which is the round trip this removes. Of 36 live tasks, 32 were at `turn=user`; several were waiting on a reviewer. Adds `Task.waiting_on` (`WaitingOn`: external-review | ci), derived by the session service from the forge and rendered dim in the turn cell. NOT a third `Actor`, which was the obvious shape and the wrong one. `turn` is machine-driven — the container's Stop hook sets it to `user` and its UserPromptSubmit hook to `agent` on every turn boundary — so a third turn value would be clobbered the moment the agent did anything. And `Actor` is load-bearing across `turn_on_enter`, `advanced_by`, and responsibility gating (25 call sites), where a third party has no meaning: nothing external ever *advances* a task. Derived, not declared, and deliberately so. Nothing has to remember to set it and nothing has to remember to clear it: when the PR is approved or the checks go green the next pass reports None and the marker disappears. An agent skill that forgets to clear leaves a task looking parked forever — worse than no marker, because you learn to distrust it. Also distinct from `blocked`, which is the agent's own "I am stuck" and is cleared explicitly. Different lifecycles, so they stay separate fields; `blocked` still outranks this in the cell, since being stuck needs attention. The derivation refuses to guess. An unreadable PR (gh absent, rate-limited, unauthenticated) leaves the previous value alone rather than reporting None — marking a parked task actionable because a shell command failed is precisely the error this exists to prevent. CHANGES_REQUESTED and failing checks are *work*, not waits, so neither sets the marker. Reads are throttled to once a minute per task: the host wakes on the change feed, not a timer, so an unthrottled watcher would be one `gh` call per task per tick. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
Task.waiting_on— why a task is parked on someone who is neither you nor the agent — derived from the forge and rendered dim in the dashboard's turn cell.Why
A task awaiting someone else's review sits at
turn=userand renders yellow, indistinguishable from work that's actually yours. The only way to find out was to open it. On the live fleet: 32 of 36 tasks atturn=user, several parked on a reviewer.Why not a third
ActorThat was the obvious shape and it doesn't work:
turnis machine-driven. The container's Stop hook sets it touser; its UserPromptSubmit hook sets it toagent. Every turn boundary rewrites it, so a third value survives until the agent does one more thing.Actoris load-bearing acrossturn_on_enter,advanced_by, and responsibility gating — 25 call sites. A third party has no meaning there: nothing external ever advances a task.So this is a separate axis, which is what the turn can't carry.
Why derived, not declared
Nothing has to remember to set it, and — the part that matters — nothing has to remember to clear it. When the PR is approved or checks go green, the next pass reports
Noneand the marker disappears. An agent skill that forgets to clear leaves a task looking parked forever, which is worse than no marker at all, because you learn to distrust it.It's also distinct from
blocked(the agent's own "I am stuck", cleared explicitly). Different lifecycles, so separate fields — andblockedstill outranks this in the cell, since stuck needs attention while parked doesn't.The derivation refuses to guess
REVIEW_REQUIREDexternal-reviewciCHANGES_REQUESTEDThat last row is the one that matters.
ghabsent, rate-limited, or unauthenticated leaves the previous value alone rather than reportingNone— marking a parked task actionable because a shell command failed is exactly the error this feature exists to prevent.Notes
ghcall per task per tick. Review state moves on human timescales.server_defaultneeded (unlike thepausedone).Tests
11 for the watcher (full derivation table, the three gates, the throttle, and the unreadable-PR case), 3 for the API (round-trip through the store, lifecycle untouched, unknown reason → 422), 3 for the rendering (dim beats the turn colour,
blockedbeats dim, unknown reason still dims).Full suite passes (1,262). The two
test_tarot_*failures are pre-existing — they assert tarot isn't installed, and it is on this machine.🤖 Generated with Claude Code