| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
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.
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.
…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.
…ions Lets two separate top-level sessions — different windows or OS processes on the same machine — send each other messages. There is no parent/child relationship, so both sides opt in with an explicit mutual-consent handshake: invite mints a one-time token, accept consumes it and writes durable bidirectional consent, then either side can msg. Peers are addressed by session_id, never by slug, because slugs carry no uniqueness guarantee. Send persists a capsule row to a shared table; receive has two halves — a session taking a turn drains its inbox at the turn boundary, and an idle session is woken by a per-instance poller that claims its rows. Delivered rows are hard-deleted and a reaper reopens rows claimed by a crashed process. Incoming messages render as an external-context frame and are treated as untrusted input. The poller forks lazily from inside the run loop so it captures the live fiber's instance context; forking at layer-build time leaves that context absent and the receive path dies silently while unit tests still pass. Off by default behind OPENCODE_EXPERIMENTAL_S2S.
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Issue for this PR
Closes #38965
Related to #19215 (agent-to-agent communication primitives). Not "Closes" — this is an experimental, flag-gated primitive.
Re-submission of #32693, closed by the automated cleanup bot on 2026-07-17 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 two separate top-level sessions — different windows / OS processes on the same machine — send each other messages. This is distinct from the subagent primitives in the base PRs: there is no parent/child relationship, so the two sides opt in with an explicit mutual-consent handshake first. Gated behind OPENCODE_EXPERIMENTAL_S2S (off by default).
A new s2s tool drives it: invite mints a one-time 10-minute token; the peer runs accept with that token (shared out-of-band), which writes a durable bidirectional consent record; then either side runs msg to send. Peers are addressed by their globally-unique session_id (ses_…), never by slug — slugs are not unique. leave tears the pair down.
How a message actually gets from one process to the other:
Why the receive path is built the way it is (the non-obvious part): the messaging/poller services are per-instance — they resolve through a request-scoped context reference that is only present inside a live request/run fiber. An earlier version forked the poller at layer-build time, where that reference is absent, so it died on its first tick and the entire recipient path was silently dead in a real process while every unit test passed (the tests inject the context). The fix is to (a) drain in-context at the runLoop boundary, and (b) lazily fork the per-instance wake-poller from inside SessionPrompt.loop, capturing the live fiber's context — plus making the S2SStore service a direct member of the prompt-serving layer groups so a serviceOption lookup in the forked fiber actually finds it. No service was made process-global.
Addressing by session_id (not slug) is deliberate: session.slug has no uniqueness guarantee — an earlier draft added a UNIQUE index on it and that broke new-session creation on a real DB once the small random-slug space saturated. Slugs stay as parent-owned handles for the subagent primitives only.
How did you verify your code works?
Screenshots / recordings
Minor TUI surface (an inbox marker line in the transcript + a session-list tweak), reusing the marker render path from #38942.
Checklist