Repository navigation
Let a plugin wake an agent in a conversation (ctx.agents.wake) - #1468
Conversation
`ctx.agents.wake(pubkey, { channel, root?, text })` queues a `plugin`
trigger in the agent's mention lane, with the mention's rate limit and
"still current" checks, and sends nothing to the relay. It throws when
this copy of the app does not run the agent or the lane is full.
Claude, Codex and goose take the wake on their mention path, in the
conversation's session, without the owner check. One shared
`<plugin-wake>` section replaces `<buzz-event>`: it names the plugin,
says neither the owner nor anyone in the channel wrote it, and that its
text is data. A local anchor in place of the event routes the turn; its
random id is never published. Reports and typing go to the thread root,
or the channel in a Solo chat, and a threadless wake elsewhere is
ignored. Wakes are not resumed after a reload and show no activity row.
Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Harnesses find a wake's thread from its anchor's `e` tag, which `threadReference` reads only as a 64-hex id. Any other `root` left them answering, reporting and defaulting tools at the anchor's own id, which must never be published. `Agents.wake` now refuses such a root and lowercases a good one. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
|
🤖 Draft, the base of the stack under #1325. An independent review found one gap: a malformed |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6e0c66d8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.runners.wakeIn(pubkey, { | ||
| type: "plugin", | ||
| plugin, |
There was a problem hiding this comment.
Cancel wakes when their source plugin unloads
When a plugin is disabled, reloaded, or removed after calling wake while the target agent is busy, this call leaves only the plugin ID in the queued trigger and registers no scope-disposal cleanup or revision check. AgentRunners treats plugin triggers as always current, so the old wake can still be handed to Codex/Claude/goose—and potentially execute commands—as though the plugin were still installed. Associate queued and handed-off wakes with the calling plugin scope/revision and cancel or reject them once that scope ends.
Useful? React with 👍 / 👎.
| if (!channel || !text.trim()) | ||
| throw new Error("A wake needs a channel and text"); |
There was a problem hiding this comment.
Bound wake text before placing it in model prompts
When a plugin forwards a large value such as complete CI output, this validation accepts text of any size, and every bundled harness subsequently interpolates the entire value into its model input. A sufficiently large log can exceed the provider context limit, fail the wake, consume excessive tokens, or allocate a very large in-memory prompt. Reject or explicitly truncate by UTF-8 size at this API boundary and tell callers what limit applies.
Useful? React with 👍 / 👎.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account.
One actionable correctness finding is recorded inline. Assessment: 8/10; preserving the wake’s precise history cutoff is needed to reach the repository’s 9/10 bar. Rocket reviewed systems/lifecycle, Mantis correctness/security/concurrency, Groot product/accessibility, and I reconciled their results against the pinned source.
Reviewed head: a6e0c66d8d1eb57c059701623d652a32ee3bfa6c. Target base: 6d0035aff265be9a3cac9a489d027e8a37b0c5ce; feature merge base: b2a272bd705350ba514ea6c512f6deb2c842818c. Inspected all 17 feature-diff files, relevant callers, queue/custody and failure/steering paths, and the public description and feature commit messages (no attached images). The documented absence of wake activity rows/resume and the Solo-only threadless behavior are retained as scope decisions. The stacked #1325 consumer is outside this review.
Source-only: no PR code, tests, builds, or app flows executed. One hosted check snapshot showed CI required, JavaScript, Rust/tool integration and browser journeys successful; Windows native validation and the browser flake placeholder were skipped. Live provider behavior, native acceptance, performance and rendered accessibility/focus remain unverified. This COMMENT is not an approval or merge authorization.
| ...(root ? { root: root.toLowerCase() } : {}), | ||
| text, | ||
| id: bytesToHex(crypto.getRandomValues(new Uint8Array(32))), | ||
| at: Math.floor(Date.now() / 1000), |
There was a problem hiding this comment.
[P2] Preserve subsecond time in the wake’s history cutoff
This truncates the wake time to the beginning of its second, and wakeAnchor emits no ms tag. Codex passes that anchor into conversationHistory, which filters with compareMessages(item, cutoff) < 0 using eventMs(anchor) (src/bundled/codex/history.ts:38–51). A wake at S+.900 therefore excludes an already-posted message at S+.100. On a fresh Codex session, a wake immediately after a thread is created can omit the root/request itself; same-second replies are also lost. Ordinary app messages carry millisecond tags, so this affects normal conversation context.
Capture the time once and preserve its milliseconds through the trigger and anchor (or provide a precise history cutoff). Add a fresh-session plugin-wake case that includes earlier same-second context and excludes later context. This finding is derived from source; no runtime reproduction was performed.
A wake's time was rounded to whole seconds, and its anchor carried no `ms` tag. Codex cuts history off at the anchor by Buzz's millisecond ordering, so everything posted earlier in the wake's second, possibly the original request, sorted after it and was left out of a fresh session's context. The trigger's `at` is now milliseconds, and the anchor carries it the way Buzz's own messages do: seconds in `created_at`, the rest in an `ms` tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Salman Mohammed <smohammed@squareup.com>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (lifecycle/performance), Mantis (correctness/security/concurrency), and Groot (product/context behavior) reviews.
No actionable changes requested in this follow-up. The prior history-cutoff finding (#1468 (comment)) is addressed: wake creation retains milliseconds, the anchor supplies canonical seconds plus an ms tag, and the displayed time uses the same units. The added Codex regression case checks earlier and later messages within the wake’s second. The existing Claude/Goose timestamp units and mention-only interruption persistence remain consistent. Assessment: 9/10 for the reviewed repair.
Reviewed head 692c3d79a3e42477dbf497d58e07267a1dc64d3c, target base 6d0035aff265be9a3cac9a489d027e8a37b0c5ce. This follow-up compares the five changed files with previously reviewed head a6e0c66d8d1eb57c059701623d652a32ee3bfa6c and traces affected consumers; it does not reopen unchanged feature areas or review stacked #1325. Public description and new commit metadata inspected; no attached images or new disclosure finding.
Source-only: no PR code, tests, builds, app flows or performance measurements executed. One hosted-check snapshot showed JavaScript, Rust/tool integration, native fixture, browser journeys and measurements still running; Windows native validation and the browser-flake placeholder were skipped. Live wake/provider behavior, native acceptance and rendered accessibility/focus remain unverified. This COMMENT is not an approval or merge authorization.
What
ctx.agents.wake(pubkey, { channel, root?, text })lets a plugin wake an agent this copy of the app runs, in one of its conversations, the way a mention there would, but with no message. Nothing goes to the relay and nobody signs anything on the owner's behalf.This is the base of a two-PR stack. #1325 (GitHub
watch_pr) sits on top of it and uses it instead of posting an owner-signed mention.How
features/agents/service.ts): a newplugintrigger (plugin,channel,root?,text, its own randomid,at).Agents.wakerefuses calls from outside a plugin, and empty text. It throws when this copy does not run the agent or its mention lane is full.features/agents/runner.ts): the wake queues in the mention lane, with the mention's rate limit. The drain checks it like a mention, so it is dropped like a queued mention when the agent stops running here or another copy takes it. It shows typing in its thread. It is not saved for resume.fromOwnercheck, because the plugin is the owner's installed code and not a message<buzz-event>block is replaced by one shared<plugin-wake plugin="…">section. It says the plugin woke the agent, that neither the owner nor anyone in the channel wrote it, and that the text is data, not instructions.features/agent-harness/plugin-wake.ts): the harnesses are built around the mention's event (channel, thread root, ordering time, failure replies). So the wake becomes a local anchor, an unsigned stand-in event with the thread'setag and the wake's random id, and each harness reuses its mention path.The anchor's id is never published
Agents.wakerefuses arootthat isn't a 64-hex event id. Harnesses only read a well-formed id as the thread, and any other would leave them answering at the anchor. An independent review found that gap, and the fix is the second commit.Every place that tags the triggering event goes through the thread root (
threadReference(event)?.rootId), neverevent.id, when the wake has aroot: failure reports, goose's fallback post, tool reply defaults, Codex'sreply_to, and typing. The runner types atrootdirectly. A wake with noroothas no thread to answer in, so:etagNo harness adds reactions or read markers for a mention, so none exist for a wake.
Tests: a service test runs two wakes to failure, one in a thread and one at the top of a non-Solo channel. It checks that nothing published as the agent contains either wake's id, and that typing only went to the thread root. Each harness has a failure test that checks its report goes to the root and contains no wake id.
Tests
Full Vitest: 711 files, 11,069 tests passed on
8ee52ba45. The Agents service tests also pass ona6e0c66d8, and the hooks passed on both pushes.root, or for an agent this copy does not run. A queued wake is dropped when the agent is removed.<plugin-wake>section and no<buzz-event>or wake id. A failed wake reports at the root. A wake with no thread is ignored outside a Solo chat and taken in one.Follow-ups (not in this PR)
rootonly works in a Solo chat. A DM or channel-scoped session would need a way to post at the top level without a thread.🤖 Generated with Claude Code