feat(opencode): coordinator-messaging — sibling/coordinator subagent communication#38943
Draft
iceteaSA wants to merge 4 commits into
Draft
feat(opencode): coordinator-messaging — sibling/coordinator subagent communication#38943iceteaSA wants to merge 4 commits into
iceteaSA wants to merge 4 commits into
Conversation
6 tasks
Draft
6 tasks
Contributor
|
The following comment was made by an LLM, it may be inaccurate: No duplicate PRs found The searches show that PR #38943 (current PR) is properly declared as stacked on its dependencies (#38942 and #32425) and related to the agent-team coordination feature (#19215). The only related open PR found is #38944 (session-to-session messaging), which addresses a different layer of communication. All other results are either the current PR itself or its explicitly declared dependencies/references. |
iceteaSA
force-pushed
the
coordinator-messaging
branch
from
July 26, 2026 10:13
b713708 to
c78a116
Compare
Lets a parent agent (via tools) or the human (via TUI esc) steer, gracefully cancel, or hard-abort a single running Task subagent mid-run, without affecting the parent or sibling subagents. A per-instance Interrupt service holds one pending interrupt per child plus a terminal record; the child consumes it at its own runLoop turn boundary. Steer injects a frame and the child adapts and continues; cancel injects a frame, records a terminal, and force-breaks within a grace window; abort cancels the BackgroundJob immediately. Interrupt tools reach any descendant through a bounded ancestry walk that fails closed off the caller's subtree. Off by default behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT.
iceteaSA
force-pushed
the
coordinator-messaging
branch
from
July 26, 2026 10:38
c78a116 to
890d43f
Compare
Lets a subagent message the agent that spawned it — ask a question and block for the answer, or send a fire-and-forget update — instead of only being able to ask the human. The parent replies with the same tool. A send wakes the parent's parked task wait through a background-job channel and parks on a reply Deferred held by a per-instance Messaging service, so no new session state is needed. Replies are bounded by a timeout, the channel is separate from the promotion channel, and a missing parent fails fast. Off by default behind OPENCODE_EXPERIMENTAL_AGENT_MESSAGING. Injected parent messages carry the parent session model and variant explicitly, so an injected message cannot fall through to the agent default and switch the session model.
…communication Lets the sibling subagents one parent spawns message each other directly instead of routing everything through the parent. The surface is one composable primitive — a per-child allow-list (`message_allow`) — so the parent builds whatever graph it wants: hub, mesh, or chain. Peer sends are fire-and-forget; the synchronous round-trip stays parent-only, which is what keeps siblings deadlock-free. Slugs are parent-owned handles resolved through a registry, authorized at send time against both the allow-list and true sibling-hood. Recipients drain a FIFO inbox at their own turn boundary, batched so an M-member graph costs O(1) turns per drain. The interrupt tools also accept a slug task_id, resolved through the same registry and still subject to the full ancestry check. Off by default behind OPENCODE_EXPERIMENTAL_AGENT_MESSAGING.
iceteaSA
force-pushed
the
coordinator-messaging
branch
from
July 26, 2026 12:22
890d43f to
ba58f63
Compare
3 tasks
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.
Issue for this PR
Closes #38964
Related: #12711 and #19999 (both describe a team messaging layer; this settles peer authorization and delivery-to-an-idle-agent, which neither specifies).
Related to #19215 (agent-team coordination) — this adds the sibling/coordinator layer that proposal needs.
Re-submission of #32517, closed by the automated cleanup bot on 2026-07-16 while still active. The branch has been force-pushed since (rebased onto current
dev), so GitHub refuses an author-side reopen — same situation as #35195. No maintainer decision was involved in the closure.Type of change
What does this PR do?
Lets the sibling subagents one parent spawns message each other directly, instead of routing every message back through the parent. Same experimental flag as #38942 (
OPENCODE_EXPERIMENTAL_AGENT_MESSAGING); off by default.The surface is one composable primitive — a per-child allow-list — so the parent builds whatever communication graph it wants (hub/coordinator, mesh, chain) with no special-casing:
message_allow: string[]— the sibling slugs (theirtask_ids) a spawned child may message. Empty/omitted → parent-only (today's behavior, backward compatible). Child-immutable; only the parent sets it, at spawn.messagetarget — a peer slug:message({ target: "<slug>", body }). Fire-and-forget only;expect_replyis rejected for any non-parenttarget (the synchronous round-trip stays parent-only — that's what keeps siblings deadlock-free). Theparentandsubagenttargets are unchanged.slug→SessionIDregistry in theMessagingservice resolves them. A send is authorized at send time iff the target is on the sender's allow-list andtarget.parentID === sender.parentID(true sibling-hood — closes a grandchild-escalation hole). Unknown-but-allow-listed slug →has not spawned yet.enqueues and returns immediately. The recipient drains its inbox non-blocking at its own runLoop turn boundary — reusing theinterrupt.consumesite pattern from feat(opencode): interrupt a running subagent — steer / cancel / abort #32425 (the site, not the single-slot storage). A drain pulls all queued messages and injects them as one synthetic user message with 2N parts (a model-readableagent_messageframe + a visible✉ Inbox from <slug>marker per message), so an M-member graph costs O(1) turns per drain, not O(N). The drain is skipped on the turn that consumes a cancel interrupt.(sender, body), and a tree-wide message cap as a backstop.awaitInboxfor a coordinator to fan results in without busy-looping.Why it can't deadlock: every peer send is fire-and-forget (returns at once) and every drain is non-blocking at a turn boundary, so no agent ever waits on a specific inbound message — no wait-cycle can form, even in a mesh with simultaneous A↔B sends. With the flag off the
messagetool is unregistered and the drain block is dead code.Since the original submission, the interrupt tools also accept a slug
task_id: an id starting withses_resolves directly, anything else goes through the samemessaging.resolveSlugregistry. A resolved slug still passes the full ancestry check, so a process-global slug naming a session outside the caller's subtree is rejected rather than reachable.How did you verify your code works?
New tests (no mocks): the slug registry + allow-list, the FIFO inbox with budget/cap/dedup, bounded
awaitInbox, peer-send authorization (allow-list +parentIDsibling check +expect_replyrejection), the runLoop drain (batched 2N-part injection, skip-on-cancel, flag-off no-op), and an end-to-end collaborative-sibling flow + gating.cd packages/opencode && bun test→ 3287 pass / 0 fail;bun typecheckclean inpackages/opencodeandpackages/tuiLive-tested end-to-end on a real build across every topology the allow-list expresses, plus the refusal paths and cross-feature interaction:
has not spawned yetexpect_replyon a peerper-agent outbound budget (20) reached(sender, body)×2 → recipient receives onetask_steerduring messagingScreenshots / recordings
UI surface is small and reuses #38942's marker render path: inbox deliveries show a muted
✉ Inbox from <slug>row in the recipient's transcript (same mechanism/styling as✉ Message from subagent). No new components.Checklist