Repository navigation
Peer inbox wakes can preempt active tool work and reactivate completed turns #1137
Description
Symptom
Ambient peer-inbox notifications are injected into model history as user messages. During a multi-step tool workflow, repeated parked-peer wakes can become the newest user turn, pull the model away from the task it was continuing, and start additional assistant/tool cycles. A wake can also arrive immediately after attempt_completion and reactivate a session that had already completed.
The observed tool calls still had matching tool outputs, so this is not evidence that the tool processes themselves were cancelled. The failure is at the conversation/run-loop level: notification traffic is treated as task-authoritative user input.
Reproduction
- Run a root session through a multi-step inspection that uses several tool calls.
- From one or more peer sessions, send messages while the root session is working and leave them parked in its inbox.
- Let additional peer messages arrive across model step boundaries, including near completion.
- Inspect the transcript ordering.
Observed behavior:
- peer wakes appear as
role: usermessages with notification origin; - identical unread-state wakes can appear again as ordinary user messages;
- newer unread counts add further user turns rather than coalescing the existing ambient state;
- assistant reasoning/tool activity can continue after
attempt_completionin response to a peer wake.
Root cause
Agent.runTurn calls peer_channel.deliverInbound before every model request, including tool-result continuation requests. deliverInbound parks the peer bodies and appends a synthetic user message containing the unread count. Because the wake has user role and is delivered at every step boundary, the model can interpret ambient coordination as a new primary instruction instead of continuing the active task.
Clearing the parked inbox does not make already-injected wake messages non-authoritative, and the same unread state can be represented through both notification delivery and normal conversation history. Wording the wake as advisory reduces one symptom but does not fix its role, lifecycle, or deduplication semantics.
Expected behavior
Peer-room traffic should remain ambient coordination unless the user or agent explicitly opens the inbox. It must not create a new task-authoritative turn, interrupt tool-result continuation, or restart a completed turn.
Fix direction
- Represent peer availability out of band, or in a non-user context channel that cannot start a turn by itself.
- Coalesce wakes by mailbox generation/unread state so the same state is exposed at most once.
- Do not auto-run a peer wake after
attempt_completion. - Preserve the current human task as the newest authoritative turn while tools are being continued.
- When
peer_message action=inboxclears the mailbox, retire any stale wake associated with that mailbox generation.
Regression coverage
Add a run-loop test that proves:
- a peer arrival between tool batches does not add a new authoritative user turn;
- duplicate delivery of the same unread state is idempotent;
- a peer arrival after
attempt_completiondoes not start another assistant/tool cycle; - an explicit later human turn can still see that peer messages are waiting.
Activity
yxlyx commentedon Sep 21, 2026
@justrach — could you take a look at this peer-notification run-loop issue?
justrach commentedon Sep 21, 2026
Fixed in #1141.
justrach commentedon Oct 17, 2026
Seen on graff 0.302.41 (latest release checked: v0.302.41), Harness 0.2.122, Harness desktop / ACP, macOS arm64.
Additional current-version reproduction of the completed-turn reactivation problem, through Harness
send_messagerather than a demonstrated nativepeer_inboxpath:Observed: later coordination messages can refer to work that has already finished. Closure/no-reply updates still enter the recipient as new user-role turns with an unconditional reply directive in the message envelope. The recipient produces repeated no-op completion/acknowledgement turns instead of remaining idle. Delayed/stale updates are difficult to distinguish from fresh requests.
This adds a Harness
send_message/ACP surface reproduction on the versions above; it does not establish that the previously fixed native peer-inbox path is the same cause. Local trace review did not establish an exact enqueue-to-consumption delay, so no numeric delivery-latency claim is made.Relevant current source boundaries, not a proven root cause:
v0.302.41:src/acp_inbox.zig:152-162: incoming session prompts are queued behind a live turn; the active-turn hook signals a follow-up to job waiting.v0.302.41:src/acp_inbox.zig:63-71: queued input is drained in arrival order.v0.302.41:src/acp_idle.zig:1-3: documents idle peer-mail wake behavior and distinguishes it from mid-turn preemption.Suggested follow-up / regression expectations:
No conversation bodies, private project details, local paths, session identifiers or raw traces are attached.
yxlyx commentedon Oct 18, 2026
Seen on graff 0.302.44 (latest release checked: v0.302.44), Harness 0.2.127, surface Harness desktop / ACP, macOS arm64.
New reproduction: ordinary final answer followed by a duplicate completion
Correlated the saved model transcript, agent trace, and Harness journal for a completed issue-filing task. This is a native parked-peer wake, not the Harness
send_messageenvelope path described in the previous comment.phase: final_answer; the host journal records a completed Done. The requested issue was already created._graff_origin: notificationand a parked-peer unread notice started a second turn, with no intervening human request.peer_messageand read its inbox. The mail was an informational edit-scope/non-overlap update, not a request to repeat the human-facing result.attempt_completionwith a paraphrase of the result it had already delivered. The UI therefore displayed a second completion and a separate two-tool activity group.This was two generated assistant completions, not one message rendered twice. The issue-creation command was not executed twice in this sequence.
Completion-path gap to investigate
v0.302.44:src/agent_tools.zig:330-334: acceptedattempt_completionstores its result and calls rootpeer_idle.noteCompletion().v0.302.44:src/peer_idle.zig:43-47: that function setssuppress_idle_wake.v0.302.44:src/peer_idle.zig:97-115: when not busy and not suppressed, a new unread batch can produce an idle wake.v0.302.44:src/acp_idle.zig:35-61: the wake is translated into anothersession/prompt, without a new client request.v0.302.44:src/peer_idle.zig:157-177: existing regression coverage specifically tests suppression afterattempt_completion.The first observed completion used the ordinary final-answer / completed-Done path, not the completion tool. The unsolicited second turn then used that tool. Task-completion protection must also cover normal final answers, rather than depending on which completion path the model chooses. The evidence establishes that path distinction; the suppression flag itself was not instrumented at runtime.
Why it matters / expected behavior
One human request produces two near-identical final answers, suggesting a retry or double execution. Informational coordination reopens the completed task and its Working/timer state, pollutes the transcript, and adds unnecessary model work. Hiding repeated text in the renderer would not fix the extra turn.
Please revisit this closed issue and cover successful ordinary final answer + completed Done + informational peer arrival, alongside explicit
attempt_completion. Neither should replay the completed task result. Keep mail readable and preserve genuine human requests, actionable coordination, and legitimate outstanding-job continuation; do not disable all idle continuation indiscriminately.Evidence is summarized without raw prompts, peer-message bodies, screenshots, local paths, machine names, model/provider details, or session/trace identifiers.
yxlyx commentedon Oct 18, 2026
Seen on graff 0.302.44 (latest release checked: v0.302.44), Harness 0.2.127, surface Harness desktop / ACP, macOS arm64.
Additional reproduction: repeated ordinary final answers after unrelated peer mail
Reopening at the reporter's request. This adds a distinct observed sequence to the earlier current-version report: one completed human request followed by two more ordinary final answers, each immediately following a peer-inbox read, rather than an ordinary final answer followed by
attempt_completion.Evidence and sequence
Checked the saved model conversation against the chat's message/tool history. The relevant sequence was:
phase: final_answercontaining the result.peer_messagewithaction: inbox. The returned mail was an informational edit-scope/non-overlap update about another task. It did not ask the recipient to revisit the preview or repeat its answer.phase: final_answerthat paraphrased the already-delivered preview result.peer_message action: inboxreturned an informational update about a different, unrelated task. The assistant again generated a normalphase: final_answerrepeating the preview result.The two repeated preview answers are separate generated assistant messages in the saved model conversation, not one message rendered multiple times. Each corresponding chat entry has a separate one-tool activity group. The repeated-answer segments contain inbox reads, not another preview-start command; this evidence does not show the preview being started repeatedly.
The inbox results also include an unconditional instruction to reply through
peer_message, even for informational scope updates. That is a potential acknowledgement-pressure contributor, not proof that it caused the repeated human-facing answers.Why this extends the existing report
The earlier current-version reproduction described an ordinary final answer followed by an explicit completion-tool call. Here, all three result-bearing answers use the ordinary final-answer path. The session can therefore emit multiple unsolicited result repetitions without ever reaching the completion-tool suppression hook in these segments.
The demonstrated behavioral failure is completed-task reactivation/result replay around informational peer inbox consumption. The saved sequence does not, by itself, instrument the exact scheduler wake or prove the runtime value of the suppression flag. The available startup trace is not evidence for the later wake timings; no model-call counts, costs, or precise wake-latency claims are made.
Relevant tagged source / hypothesis to investigate
v0.302.44:src/agent_tools.zig:330-334: acceptedattempt_completionstores the result and calls rootpeer_idle.noteCompletion().v0.302.44:src/peer_idle.zig:43-47:noteCompletion()setssuppress_idle_wake.v0.302.44:src/peer_idle.zig:97-115: the idle path checks that flag and may create a wake for new unread mail.v0.302.44:src/acp_idle.zig:35-61: an eligible idle wake is passed into a newsession/prompt.These boundaries support investigating ordinary-final-answer completion bookkeeping and peer-mail lifecycle together. They do not establish that every observed extra answer entered through this exact runtime path. A fix limited to the explicit completion tool is insufficient for the demonstrated ordinary-final-answer sequence.
Expected behavior and focused regression coverage
attempt_completiondoes.attempt_completionandyield_turnseparately, so their existing protection is preserved.This follow-up contains no raw conversation or peer-message bodies, screenshots, local URLs/ports, local paths, chat titles, machine/user names, model/provider names, session/run/trace identifiers, or raw logs.
justrach commentedon Oct 20, 2026
#1688 (merged) addresses part of this.
peer_message sendtakesfyi: truefor a status or scope update. That mail stays readable in the inbox but never starts an idle turn on its own, and a later request still wakes the root. The inbox footer no longer tells the model to reply unconditionally.Still open: unmarked informational mail can still start a turn after an ordinary final answer, and the Harness
send_messagepath is unchanged.peer mail: fyi updates stay parked instead of starting a turn