Repository navigation
feat(orchestration): a wrap-up turn when a worker stops without reporting - #88
Merged
Merged
Conversation
…ting When a worker's turn ends with no report and nothing pending, or its orchestrator stops it with stop_thread, Gram sends it one turn in the same session asking it to stop and report what is done, what it tried and ruled out, and what remains. The digest waits for that report instead of saying the worker ended its turn without reporting. Fires once per stop, and never for a worker that is paused, user-held, archived, at a usage limit, errored, cut off by a restart, or waiting on a wake, monitor, merge, dependency, background task, open question or pending request.
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 changes
When a worker ends a turn with no
reportand nothing pending, or its orchestrator stops it withstop_thread, Gram sends the worker one more turn in the same session: stop working and report. The orchestrator's digest then carries that report instead of "ended its turn without reporting".How a silent stop was found, and where this hooks in
onTurnEnd(service/observers.ts) already marks a worker's memoquiet: turnEndedwhen a turn ends with an empty queue, no report in the turn and no armed automation. The ledger (quietOfWorker) turns that intoquiet.kind === "turnEnded"unless something else explains the stop: parked, user hold, pending request, background work, a finished, done, blocked or question state, or a report that still stands.digestDiffthen writes "ended its turn without reporting".The wrap-up hooks in right after that memo write. The decision is a pure function,
wrapUpDueinrules.ts. The service sends the turn withsendNotice(as server-authored, so it never counts as the user typing), then marks the memowrapUp: true, clears itsquietand skips the turn end's wake. While the wrap-up is pending,digestDiffdoesn't call the stop silent (wrappingUp). The next turn end clears the mark:That stop never gets a second wrap-up.
When it fires (
wrapUpDue)quiet.kind === "turnEnded") whose turn ended with an empty queue and no report in it.completed,maxTurns), or an interrupt the worker's own orchestrator asked for.stop_threadon its own worker records the stop through the newOrchestration.noteStop. An interrupt from anyone else (the user's stop button, a restart's interrupt) is treated as theirs and gets no wrap-up.userIntervened);limitWait; that path returns earlier and is unchanged).depends_on);ask_question;New text the worker receives (quoted exactly)
When its turn ended silently:
When its orchestrator stopped it:
No role prompt or tool description changes in this PR.
Tests
wrap-up.test.ts, 29 tests:wrapUpDue: the two firing cases, one case per exclusion, and the notice text.stop_threadsends the "stopped" text;ask_questiongets none.stopSilently(session)ends the turn, waits for the wrap-up and requires its exact text (otherwise the test dies), then ends that turn silently too.last tool none, because the stop the orchestrator hears of is the wrap-up turn's. It no longer sees the original turn'sBash.completeTurnused to poll for the thread'sidlestatus and then wait for the service to count the turn end. For a worker the service follows, the wrap-up turn can start before any poll seesidle, so it failed every time. For those workers it now waits only for the service to count the turn end. The service counts it only after the runtime has ended the turn, so I don't think the wait is any looser. Threads the service doesn't follow still wait foridle.Commands
bun run test packages/server/src/orchestration packages/server/src/settings packages/server/src/app-tools packages/server/src/git/merge-after-local-check.test.ts: 74 files, 1333 passed and 5 failed. All 5 were scenarios ofan armed worker PRinpr-tools-merge-launch.test.ts. After fixing them, that file passes on its own (32/32).bun run test packages/server/src/orchestration/wrap-up.test.ts: 29 passedGOMEMLIMIT=2GiB bun run typecheck --singleThreaded: passbun run check:scaffold: passbun run fmt:check: passKnown limits