Skip to content

Let a plugin leave a note for an agent's next turn - #1466

Merged
johnmatthewtennant merged 6 commits into
mainfrom
jtennant/plugin-turn-context
Oct 21, 2026
Merged

johnmatthewtennant merged 6 commits into
mainfrom
jtennant/plugin-turn-context

Conversation

@johnmatthewtennant

@johnmatthewtennant johnmatthewtennant commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Why

Plugins can reach an owned agent only by posting a visible message, or by waking it with ctx.agents.wake. Sometimes a plugin acting for the owner just needs the agent to know something on its next turn, with no reason to wake it and nothing for the channel to see. The Berd Call plugin needs this. When a call starts, it tells the agent it is on a voice call, which shouldn't wake anyone. When you talk over an agent's spoken reply, it tells the agent how much you heard; today it posts "(I interrupted your reply after …)" in the chat, where everyone sees it.

What

Agents.note(pubkey, channelId, text, root?) holds context for an agent’s next turn without waking it. A thread-scoped session consumes only notes addressed to that thread root. A channel-scoped session, DM or Solo chat consumes notes across its channel. Omitting the root targets the channel conversation, so a turn in another thread cannot take those notes. It returns false when this copy of Buzz doesn't run the agent, so the plugin can fall back to posting. Notes live in memory only, so any still waiting when the app quits are lost.

  • The note is appended to that turn's prompt just before the Buzz tools' attention note (turnNote). This works in Claude Code, goose and Codex, including a message that steers a running Codex turn.
  • A turn takes the channel's notes only once its prompt is ready for the harness. A turn whose reads fail, or that is cancelled first, leaves them for the next one.
  • A turn that takes notes but definitely never begins puts them back, ahead of any newer ones. That covers:
    • a goose or Claude session that can't open, or whose runtime stops first
    • a lost Claude session that can't start over
    • goose giving up on waiting for its session
    • Codex refusing a steer or a new turn
  • A transport failure or timeout leaves the notes taken, because the turn may have begun.

AgentHandle.notes(channelId, root?) and AgentHandle.restoreNotes(channelId, notes, root?) are optional, so agent types that don't take notes still compile.

Validation

These checks exercise two boundaries.

Production service and runner. These tests use a fake relay, a fake native publisher and a stub agent type.

  • A note is refused before its agent runs, and accepted once it does.
  • A note reaches one turn in its target conversation, only once.
  • Notes queued for two threads stay isolated, including when an undelivered turn restores its notes ahead of newer ones; channel-scoped consumption retains channel-wide ordering.

Each production runtime against a fake harness. Goose uses a fake ACP process, Claude Code a fake stream-json process, and Codex a fake app-server.

  • Prompts end with the note, harnesses take notes using their existing conversation scope, and notes come back ahead of newer ones when a session or turn never begins.

Thread-isolation coverage verifies that each thread consumes only its addressed notes.

No test runs the runner and a real runtime together, and nothing ran against a live model or in the desktop app.

@johnmatthewtennant
johnmatthewtennant marked this pull request as ready for review October 21, 2026 04:05
@johnmatthewtennant
johnmatthewtennant requested a review from a team as a code owner October 21, 2026 04:05

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

Reviewed head 99884931cd8057dc47997e612e3e0082f315c601 against base/merge-base b2a272bd705350ba514ea6c512f6deb2c842818c. 8/10: one concrete recovery defect, detailed inline. The small runner-owned API and captured-note reuse otherwise fit the stated goal.

Reviewed the changed source/tests, runtime delivery and retry callers, plugin trust model, public PR description and feature commit messages. No public-material disclosure finding; the description has no image attachments.

Source-only: no tests, builds or app execution. One hosted-check snapshot showed CI required, JavaScript, Rust and browser checks successful; Windows validation and the conditional browser flake check were skipped. Live plugin-to-agent delivery, native timing, error/retry interaction and human acceptance remain unverified. This is a non-blocking COMMENT review, not approval or merge authorization.

Comment thread src/bundled/goose/runtime.ts Outdated

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

Reviewed head ffe3033fb8c8be904933912d451eeac787b6da27 against base b2a272bd705350ba514ea6c512f6deb2c842818c, concentrating on the changes since reviewed head 99884931cd8057dc47997e612e3e0082f315c601.

The explicit goose startup/expiry and Codex refusal paths now restore notes. One remaining Claude recovery path still loses them before a replacement turn starts; details and the smallest repair are inline. Source assessment: 8/10 pending that repair.

Validation: immutable-source inspection, callers and regression-test review, and git diff --check; no PR code, tests, builds or app executed. The public description has no attached images; no disclosure issue found in the inspected description and new commit messages. A single hosted-check snapshot still had JavaScript, Rust and browser jobs running; Windows validation was skipped. Live provider behavior, native acceptance, rendered focus/accessibility and performance remain unverified. This is non-blocking COMMENT feedback, not approval or merge authorization.

Comment thread src/bundled/claude-code/runtime.ts Outdated

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

Reviewed head d9f52febf0112a2607c22977352b2f0cfb5be1d1 against base b2a272bd705350ba514ea6c512f6deb2c842818c, focusing on the four-file follow-up since ffe3033fb8c8be904933912d451eeac787b6da27.

No actionable findings in this follow-up; source assessment: 9/10. The previously reported lost-note recovery path is fixed: failed saved-session deletion or fresh-prompt construction now returns an undelivered result, restoring the captured notes for a later turn and reporting the failure to the conversation. The new tests cover both failure points and next-turn note delivery.

Validation: source/caller and regression-test inspection plus git diff --check; no tests, builds or app execution. The public PR description has no image attachments, and it and the new commit message showed no disclosure issue. One hosted-check snapshot still had JavaScript, Rust and browser jobs running; Windows native validation was skipped. Live Claude recovery, native persistence failures, performance and rendered accessibility/focus behavior remain unverified. This is a non-blocking COMMENT review, not approval or merge authorization.

@johnmatthewtennant johnmatthewtennant changed the title Let a plugin leave private context for an agent's next turn Let a plugin note or privately message an agent Oct 21, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

Reviewed head 274f436b70eb57afbd6980cae4fcdb210e8be508 against base/merge-base b2a272bd705350ba514ea6c512f6deb2c842818c, focusing on the private-message addition since reviewed d9f52febf0112a2607c22977352b2f0cfb5be1d1 and its interaction with note delivery.

One actionable P2 is detailed inline. Source assessment: 8/10 until private-message timestamps preserve the precision required by Codex history. The earlier note-restoration fixes remain in place. No additional source-demonstrated defects found in queue admission, channel-session routing, private activity/typing/resume/title suppression or visible-turn answer ownership. Channel-only routing, in-memory lifetime and queue-acceptance returns are documented limits. No disclosure issue found in the public description or new commit; the description has no attached images.

Validation limits: source review only; no PR code, tests, builds or apps executed. The one hosted-check snapshot still had JavaScript, Rust and browser jobs running; Windows native validation was skipped. Real provider delivery, desktop behavior, performance, rendered accessibility/focus and human acceptance remain unverified. This COMMENT is not approval or merge authorization.

Comment thread src/features/agents/runner.ts Outdated

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot review lanes.

No new actionable findings in the two-file follow-up from 274f436. The prior same-second history-cutoff finding (discussion_r4336242381) is addressed: runner.ts derives both created_at and the ms tag from one clock sample, preserving the earlier public message in the reported T+500ms/T+700ms example. The added service regression checks the dispatched timestamp and shared ordering helpers. Source assessment for this bounded repair: 9/10.

Reviewed head: a6f6e51. Base/merge-base: b2a272b. Checked the changed source and callers, hidden success/failure behavior, public PR description (no attached images), and new commit metadata. Existing clock-skew behavior is outside this follow-up’s scope.

Source-only: no tests, builds, app, live model or harness execution. The regression tests the runner and ordering helpers, not an end-to-end runtime history request. A single hosted-check snapshot showed JavaScript, Rust/tool integration, browser measurements and Vitest flake checks successful, with several browser journeys still running and Windows native validation skipped. Desktop behavior, accessibility, live-provider integration and human acceptance remain unverified. This COMMENT is not approval or merge authorization.

johnmatthewtennant and others added 5 commits October 21, 2026 10:42
A plugin that acts for the owner outside the chat, such as a voice
call where the owner talked over an agent's reply, had no way to tell
the agent what happened except by posting a visible message. Add
`Agents.note(pubkey, channelId, text)`: while this copy runs the
agent, the text is held for its next turn in that channel and appended
to that turn's prompt, before the Buzz tools' attention note, in the
Claude Code, goose and Codex runtimes. It returns false otherwise, so
the plugin can say it another way. Notes live in memory only.

Signed-off-by: John Tennant <jtennant@block.xyz>
Co-authored-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each runtime took an agent's notes before preparing its turn, so a
turn whose reads failed, or whose delivery was cancelled or ran past
its deadline while it was prepared, lost notes `Agents.note` had
accepted. Take them only once the prompt is ready to hand to the
harness: Claude Code and goose just before giving it to the session,
keeping them for a fresh session's rebuilt prompt, and Codex when the
turn's input is first built, keeping them with the request if it is
retried.

Signed-off-by: John Tennant <jtennant@block.xyz>
Co-authored-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mention takes its channel's notes when it is handed over, but goose
can still give up opening its session, and Codex can refuse a steer.
Either way no turn took the notes in, and the next mention had none.
Put them back, ahead of any left since, when delivery definitely never
happened: goose's expired opening, and Codex's refusal answer. A
transport failure or timeout leaves them taken, since the turn may
have begun.

Signed-off-by: John Tennant <jtennant@block.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A message whose goose or Claude session could not open, or whose
runtime stopped before a turn took it in, never reached a turn, and
neither did one Codex refused to start. Their notes were lost all the
same. Mark these failures before the message is handed to a session as
undelivered, apart from failures after a turn took it in, and put the
notes back for the next turn.

Signed-off-by: John Tennant <jtennant@block.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A saved Claude session that no longer loads starts over: its saved
entry is forgotten and the conversation rebuilt for a new one. If
either fails, delivery rejected after the turn had taken its channel's
notes, and nothing put them back. Nothing has taken the message in at
that point, so report it as a failure that was never delivered, which
restores the notes and tells the thread.

Signed-off-by: John Tennant <jtennant@block.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnmatthewtennant
johnmatthewtennant force-pushed the jtennant/plugin-turn-context branch from 1d80e02 to 8efcc5a Compare October 21, 2026 14:43
@johnmatthewtennant johnmatthewtennant changed the title Let a plugin note or privately message an agent Let a plugin leave a note for an agent's next turn Oct 21, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot lanes.

9/10 — no new actionable findings in this rebase and plugin-wake integration. Range-diff against the previously reviewed note commits shows three recovery patches unchanged and two adjusted for the new base’s plugin-wake trigger. The two later private-message commits were removed; the current description correctly scopes this PR to notes alongside the base’s ctx.agents.wake.

Checked lazy note consumption, cancelled/ignored wake handling, Claude lost-session restoration, Goose startup/expiry restoration, Codex refused start/steer handling and retry ordering. The existing policy remains: restore notes ahead of newer notes when delivery definitely never began; do not restore after ambiguous transport failures. No new timer, process or pool owner is introduced. Public description and five commit messages inspected; no attached images or disclosure finding on those surfaces.

Reviewed head 8efcc5a9aa72215788d21b7e701b603287f99bdf, base d9793a8dcf9052271b0e87365a9c7b189c512fa5. All independent lanes completed and were reconciled against the pinned source. This is a follow-up review of the changed integration and prior repairs, not an exhaustive re-review of unrelated new-base changes.

Source-only: no PR code, tests, builds, app or live harness executed. Diff check passed. One hosted snapshot showed CI required and completed validation checks successful; Windows native validation and browser flake checks were skipped. Native timing/performance, live-provider recovery, end-to-end plugin-to-runtime delivery, rendered error/retry/focus behavior and human acceptance remain unverified. This COMMENT is not approval or merge authorization.

Co-authored-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
Signed-off-by: John Tennant <jtennant@block.xyz>
@johnmatthewtennant
johnmatthewtennant marked this pull request as draft October 21, 2026 17:11
@johnmatthewtennant
johnmatthewtennant marked this pull request as ready for review October 21, 2026 17:34
@johnmatthewtennant

Copy link
Copy Markdown
Contributor Author

🤖 Please refresh the automated review of current head a08d14ad164a6231fdb1d5ee8ef6f023c5bf1fde. The last favorable hosted review covered 8efcc5a9; the subsequent change isolates plugin notes by the harness conversation scope and preserves note targeting when restoring undelivered turns. CI is green, and all prior inline threads are resolved. The PR description describes the current scope and validation. John has authorized progressing this PR through review and merge gates.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot lanes.

9/10 — no required changes in the 10-file follow-up from 8efcc5a9aa72215788d21b7e701b603287f99bdf. Thread note consumption and restoration use the same captured conversation root in Claude, Goose and Codex. The runner keeps thread queues separate, preserves their order, and retains the existing definite-undelivered recovery policy without adding a lifecycle owner.

Optional recovery improvement: src/features/agents/runner.ts:2127–2137 restores every entry with the consuming session’s root. When a channel-scoped turn takes thread-targeted notes and never starts, restoring with undefined loses those original targets. If scope changes to thread before another turn, those notes remain unconsumed. Preserving the original per-note roots would cover this edge; this is optional and no change is required for this review. It is separate from the documented rule that deliberately rootless notes are channel-only.

Reviewed head a08d14ad164a6231fdb1d5ee8ef6f023c5bf1fde, base d9793a8dcf9052271b0e87365a9c7b189c512fa5. All three independent lanes completed and were reconciled. Public description and the follow-up commit inspected; no attached images or disclosure findings on those surfaces.

Source-only follow-up: no PR code, tests, builds, app or live harness executed; CI was not assessed this cycle. Diff check passed. The source tests cover runner isolation and runtime root arguments, while restoration mocks remain channel-keyed; this does not prove end-to-end plugin-to-runtime restoration. Native performance, live-provider failure/retry, rendered focus/accessibility and human acceptance remain unverified. This COMMENT is not approval or merge authorization.

@johnmatthewtennant

Copy link
Copy Markdown
Contributor Author

🤖 Aaron: Disposition of the optional recovery suggestion: defer to a separate follow-up. The current contract restores to the consuming conversation; preserving each original target would extend the text-only runtime note handoff. The edge requires a channel-scoped turn to fail before starting and then a scope change before retry. Channel-scoped retry still consumes the restored notes. I have tracked a follow-up covering original-target retention and mixed-root ordering across failure/scope change, including deliberate rootless notes remaining channel-only. This limitation remains in the release; no code change or new validation is claimed. The current-head 9/10 review has no required changes. Required team approval is still outstanding.

@loganj loganj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Scope concern addressed at a08d14a: thread sessions pass their root and take only that root's notes; channel sessions take channel-wide notes. Covered by the isolation and restore-order test in service.test.ts. Local-only delivery is a known limit; plugins get false and can fall back to a message.

@johnmatthewtennant
johnmatthewtennant merged commit 1a7e5bc into main Oct 21, 2026
37 checks passed
@johnmatthewtennant
johnmatthewtennant deleted the jtennant/plugin-turn-context branch October 21, 2026 18:31
johnmatthewtennant pushed a commit that referenced this pull request Oct 21, 2026
Co-authored-by: John Tennant <jtennant@block.xyz>

* origin/main:
  test(settings): match the Usage plugin name in scroll regression (#1488)
  Let a plugin leave a note for an agent's next turn (#1466)
  Read goose memory through the shared connection-scoped MemoryReads (#1476)
  Make Claude's standalone turns honour the runner's deadline signal (#1477)
  feat(plugins): simplify manual plugin management in settings (#1397)
  feat(relay-notes): add a folder-based shared notes reader (#1395)
  feat(buzz-mcp): create and list system channels (#1465)
  Unify profile headers and polish agent activity controls (#1405)
  Polish video timestamp chips and open videos with context (#1474)
  GitHub integration: gh-backed panel with checklist and merge, Solo PR link, and watch_pr (#1325)
  Estimate Codex costs in Usage with genai-prices (#1458)
  improve agent activity hierarchy and transcript spacing (#1421)

Signed-off-by: Gigi <65cc00ce5da00abcd27be4a87ad29d7ef8431766d769a24d94f004fb8fe07066@buzz.block.builderlab.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants