Repository navigation
feat(orchestration): a person's PR comment wakes the worker that owns it - #86
Merged
Merged
Conversation
A new issue comment or comment-only review on a worker's monitored PR, from someone with write access, wakes that worker with the text fenced as untrusted external content. Rides the review monitor's poll: the badge lists people's comment ids at no extra cost, and only a new id costs a lookup.
- Own-comment check matches whole bodies the worker, orchestrator or a sibling worker posted (gh pr comment/review, gh api body=, review replies) or the links gh printed, never a substring of a tool call. - A high-water mark (createdAt, id) decides what is new; the id set is only a guard, so a comment sliding back after deletes isn't replayed. - The untrusted fence carries a per-fence random value in both markers, and NFKC-normalises and strips escapes, zero-width, bidi and control characters and any marker shape inside. HTML comments are marked. - A failed lookup backs off (1, 2, 4 min) and gives up as untrusted after four tries, logging one line without the comment. - Wakes are capped per PR at 1/min, 6/hour and 24/day, kept apart from the watch and in the monitor mark, so neither a restart nor a moment of ineligibility resets them.
- Links count only from the output of a call that posted (gh pr comment, gh pr review, gh issue comment, gh api POST to comments or reviews, pr_reply_to_thread), never from reads such as gh pr view --json comments, so a person's comment the worker read back is still delivered. - A body match needs the post to name this PR (or, for the worker, no PR: its branch's own), to come before the comment, and spends the post, so one post explains one comment. Sibling workers count only by link; the orchestrator's bodies only when they named this PR. - A lookup backing off gets a timer for the earliest one due, so a retry that succeeds first no longer strands a later one. - A floor five minutes behind the newest handled comment decides what is old, with every comment handled after it kept by id and time: a same-second or lagging comment GitHub lists late is still delivered, and the replay protection holds. - The fence also strips Unicode tag characters, variation selectors and the grapheme joiner; Markdown link-reference definitions such as [//]: # (...) are marked hidden, as HTML comments are. - Posts are read once per look for the whole batch.
- A posting call's links count only when it ran no gh read beside the post and printed no more comment links than it posted; only links that name a comment, review or review comment count. A post chained with gh pr view no longer hides the user's comment. - The badge says from when its comment listing is whole (personCommentsFrom). A full listing that starts after the floor means a flood may have pushed comments out: the floor rises to the listing and one info line says older comments were skipped, so nothing pushed out can be delivered later. - Comments without a time are left out of the badge, and the floor never takes one, so one can't stop delivery on its PR. - The merged or closed path clears the retry timer's recorded time. - A mark an earlier build kept as plain ids still loads.
- New comments are worked out against the floor as it stood, then the floor moves up to a full listing's start, staying below every listed comment not handled yet. A comment still listed but older than where the other full window starts is delivered; only comments out of every window are skipped, with the info line saying so. - A posting call's links count only when every command in it posts: a curl (or any non-gh command) beside the post can print others' links.
ANT1OFF
added a commit
that referenced
this pull request
Nov 2, 2026
Desktop release metadata proposal only. Merging this PR does not publish or tag Gram; follow docs/RELEASING.md to release. --- ## [1.4.0](v1.3.1...v1.4.0) (2026-11-02) ### Features * **agents:** identify Gram's Claude processes as the gram entrypoint ([#91](#91)) ([f7dd703](f7dd703)) * **desktop:** run on Linux ([#82](#82)) ([1d37deb](1d37deb)) * **desktop:** turn voice input off on Linux with one check ([#79](#79)) ([470ff8b](470ff8b)) * **orchestration:** a person's PR comment wakes the worker that owns it ([#86](#86)) ([19bdc1c](19bdc1c)) * **orchestration:** a wrap-up turn when a worker stops without reporting ([#88](#88)) ([36a07fb](36a07fb)) * **orchestration:** workers say what they ruled out and check before repeating ([#90](#90)) ([8bdce01](8bdce01)) * **release:** publish and update Linux AppImage and .deb ([#95](#95)) ([c33c464](c33c464)) * **voice:** an accurate mode with a Parakeet pass on each pause ([#83](#83)) ([2b04a52](2b04a52)) * **voice:** hold one key to talk, with a tone at each end ([#87](#87)) ([1a9b3ef](1a9b3ef)) * **voice:** start capture at the press, and an opt-in ready microphone ([#81](#81)) ([505d240](505d240)) * **web:** a Done section that keeps the last 7 days ([#65](#65)) ([8c7bd49](8c7bd49)) * **web:** Done lists every finished chat ([#67](#67)) ([c91d9ac](c91d9ac)) * **web:** one attention count, quieter rows, a sidebar and board that agree ([#92](#92)) ([f193da4](f193da4)) * **web:** orchestrator → workers in the sidebar, a board that matches ([#80](#80)) ([686e737](686e737)) ### Bug Fixes * **desktop:** harden the Linux key vault and autostart entry ([#85](#85)) ([4b35eac](4b35eac)) * **e2e:** hold the t-flaky turn while the smoke test reads its opening line ([#93](#93)) ([2edc4ac](2edc4ac)) * **e2e:** release the dictation tap as soon as a word is heard ([#94](#94)) ([7781194](7781194)) * prepare only approved local-check sources ([#32](#32)) ([af3fab7](af3fab7)) * repair main after [#32](#32) ([#68](#68)) ([ce352e6](ce352e6)) * **voice:** keep the last word after release ([#78](#78)) ([cc40a47](cc40a47)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: gram-release-please[bot] <351321620+gram-release-please[bot]@users.noreply.github.com> Co-authored-by: Johan <32440065+ANT1OFF@users.noreply.github.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
A person's comment on a worker's pull request now wakes the worker that owns it. The user can steer from GitHub, for example from their phone. Before this, only approvals and change requests woke a worker; a plain comment or a comment-only review woke nobody.
When it fires. A new issue comment, or a comment-only review with its inline comments, triggers it when the PR is:
spawnedByset, not archived);pr_review_monitor;What the worker gets. One notice, sent by Gram:
reportormessage_orchestratorand say why.The worker prompt and the
pr_review_monitortool description say the same.The trust rule, as implemented
Each check runs in order, and a comment wakes the worker only if it passes all of them:
Not a bot. The author is not
__typename: Bot, not a…[bot]login, and not a configured review bot. Bots keep going through the existing review monitor, before any permission lookup.A comment, not a verdict. It's an issue comment or a review whose state is
COMMENTED. Approvals and change requests keep their existing person-review notice.Not blank. It has a body or inline comment with text in it.
Not Gram's own (transcript match, chosen by the user in WQ11; it only applies to comments posted with the user's own
ghlogin,viewerDidAuthor). Such a comment is passed over in exactly these cases:A configured bot's re-review command.
A link match. GitHub's link to the comment, or to one of its inline comments, was printed by a call that posted:
gh pr comment,gh pr revieworgh issue comment;gh apiPOSTing to…/commentsor…/reviews;pr_reply_to_thread.Only links that name a comment, review or review comment count, and only from a call in which every command posts and that printed no more such links than it posted. Links from read calls never count:
gh pr view --json comments, agh apiGET of the comments,pr_review_threads, a web fetch, or any other command run beside the post (a chainedgh pr view, acurlof the comments).A whole-body match. Every text the comment carries is, after trimming and whitespace normalisation, equal to a body posted to this PR before the comment. The PR is read from the command's number,
#N, URL and--repo, or from an API path'sissues/Norpulls/N. Each post explains only one comment.Whose posts count, and how:
Posted bodies are read from:
--body,-band--body-file, including a file the thread wrote, a heredoc on stdin, and$(cat <<'EOF' … EOF);gh api -f/-F/--field/--raw-field body=…;pr_reply_to_threadbodies;bash -lcwrappers.The search starts from the turn already running two hours before the comment. Posts are read once per look for the whole batch.
Write access.
gh api repos/{repo}/collaborators/{login}/permissionreturnsadminorwrite: the repo owner and collaborators with write access.nonewithout a call.What counts as new. A floor (
MonitorMark.commentsFloor) sits five minutes behind the newest comment handled, and never past one still open. Every comment handled after the floor is kept by id and time (MonitorMark.comments, up to 64).PrStatus.personCommentsFrom: the oldest item of each connection that came back full,comments(last:10)orreviews(last:20)). If that is after the floor, comments between the two may have been pushed out unseen.Rate limit, per PR. Wakes are at least 1 minute apart, with at most 6 an hour and 24 a day.
MonitorMark.commentWakes), so a restart doesn't reset them either.Merged or closed PRs. A comment on a merged or closed PR does nothing: it is marked seen, and anything the rate limit was holding is dropped. Resuming finished work is out of scope for now.
The fence (shared with the check-output notice)
untrusted-fence.tsneutralises rather than detects:(end of quoted text).<!-- … -->) and Markdown link-reference definitions such as[//]: # (…).Polling cost
This adds no new loop; it rides the review monitor's existing badge poll.
comments(last:10)now also asks forid,createdAtandauthor.__typename. These are scalar fields, so GitHub's point cost doesn't change. Reviews already carried their id andsubmittedAt.gh api graphqlcall, with anode(id:)alias for each new comment (up to 30, inline comments up to 20 per review);Tests
pr-comments.test.ts(service, with fake reads and a fake publisher) checks that:gh pr view --json commentsandgh apiGET;gh pr viewin one command doesn't hide the user's "Please hold off on the merge.";curlof the comments doesn't take the user's link as the post's own;Other tests:
posted-comments.test.ts: shell parsing and every way a body is posted, and that commands which merely mention words post nothing.pr-comment-rules.test.ts: the pass-over rules, whole-body matching, quoted replies, and marking hidden HTML.untrusted-fence.test.ts: the per-fence value, every lookalike from the review (fullwidth, double space, zero-width, trailing space, bidi, ANSI, fullwidth letters), and character stripping.packages/git: badge comment ids withat, thenode(id:)query, parsing andcanWrite.Mutation checks (each change reverted after the run):
gh pr viewtest;ghcommand as harmless fails thecurltest;Commands run, all passing:
bunx vitest run packages/server/src/orchestration packages/server/src/babysit packages/server/src/git packages/contracts packages/git/src: 180 files, 2643 passed, 15 skipped.bun run typecheck,bun run lint,bun run fmt:check,bun run check:scaffold,bun run check:docs.Known limits
viewerDidAuthoron review nodes, and how real Claude and Codex transcripts store tool calls.$(git …), variables, a script) only matches through the linkgh pr commentprints;gh pr reviewprints none. A miss shows the worker its own words, fenced, and the rate limit caps any loop at 6 an hour and 24 a day.