Skip to content

Add opt-in cross-repo agent messaging (Agents settings toggle)#698

Merged
selfcontained merged 5 commits into
mainfrom
feat/cross-repo-messaging
Jul 6, 2026
Merged

Add opt-in cross-repo agent messaging (Agents settings toggle)#698
selfcontained merged 5 commits into
mainfrom
feat/cross-repo-messaging

Conversation

@niiyeboah

@niiyeboah niiyeboah commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

cross-repo-on

Agent-to-agent messaging (dispatch_send_message and list_agents) scopes the addressable peer set to the sender's git repo root — an agent in one repo can't message or even see an agent in another. That's the right default, but it blocks local multi-repo workflows (e.g. an agent in neo coordinating with an agent in brane-uis).

This adds an opt-in toggle that lifts the scoping. When enabled, senderRepoRoot is no longer required and every other agent is addressable. Default behavior is unchanged (off).

Supersedes the original DISPATCH_CROSS_REPO_MESSAGING env-var approach. Per review feedback, the control is now a setting in the Agents settings page instead of an environment variable.

How it works

  • Server is the source of truth. The gate is a single server-wide setting (cross_repo_messaging_enabled) in the existing settings table, read by the MCP handler for every agent. It is not per-device.
  • UI toggle lives in Settings → Agents. It's backed by a jotai atom for reactive state / instant first paint, but the atom is a cached view: the component GETs the value on mount to hydrate it and only POSTs on an explicit user toggle. There is no mount-time write, so opening settings on one device never clobbers the global gate.

Changes

  • apps/server/src/cross-repo-messaging-settings.ts (new): accessor module owning the settings key and the "true"/"false" ↔ boolean encoding — isCrossRepoMessagingEnabled(pool) / setCrossRepoMessagingEnabled(pool, bool). Mirrors agent-type-settings.ts, so the encoding lives in exactly one place.
  • apps/server/src/server/mcp-handlers.ts: the addressable-peer gate reads the setting via the accessor; the boolean is resolved once per call and threaded into a single addressableAgents() helper shared by both message delivery and agent listing.
  • apps/server/src/routes/system.ts: GET/POST /api/v1/app/settings/cross-repo-messaging (mirrors the existing agent-types / IDE settings routes; validates enabled is a boolean).
  • apps/web: crossRepoMessagingEnabledAtom + a CrossRepoMessagingSettings component rendered in the Agents settings section. Server-authoritative hydration, role="alert" on errors, and a request-sequence guard against out-of-order responses.
  • Removed the env var from .env.example; updated unit tests to drive the setting; added an e2e regression test.

⚠️ Name-based targeting caveat (unchanged from before): when enabled, a name can resolve to a same-named agent in a different repo — use the agent ID (agt_…) to address one unambiguously. This is called out in the toggle's helper text.

Testing

  • pnpm run check (tsc, server + web) — clean
  • Backend unit tests (mcp-handlers.test.ts) — 73 pass, incl. the gate logic driven by the setting
  • Settings e2e suite — 5/5 pass, incl. a new regression test for the toggle
  • Manual browser validation: default off; enabling persists to the server; a fresh second browser context hydrates from the server and does not flip the gate just by viewing settings

Review

Reviewed by three persona agents (backend-security, architecture, frontend-ux) — all approved after one round of changes that moved the toggle to a server-authoritative model.

🤖 Generated with Claude Code

dispatch_send_message and list_agents scope the addressable peer set to the
sender's git repo root, so an agent in one repo can't message or see an agent
in another. Add an opt-in DISPATCH_CROSS_REPO_MESSAGING env flag that lifts the
scoping for local multi-repo workflows; when set, senderRepoRoot is no longer
required and every other agent is addressable. Default behavior is unchanged.

Adds tests covering both handlers with the flag on, and documents the flag in
.env.example.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@niiyeboah
niiyeboah force-pushed the feat/cross-repo-messaging branch from 284cef9 to 6701fac Compare June 26, 2026 18:26
niiyeboah and others added 2 commits June 26, 2026 14:35
Per security review: when DISPATCH_CROSS_REPO_MESSAGING is enabled, name-based
targeting matches across all repos, so recommend agt_ IDs for unambiguous
addressing.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Extract addressableAgents() so dispatch_send_message and list_agents share
  one definition of the visibility boundary (flag check + self-exclusion +
  repo-root scoping), preventing drift (architecture review #113).
- Parse DISPATCH_CROSS_REPO_MESSAGING with strict === "true" to match the
  codebase convention instead of a third 1/true/yes form (review #115).
- Update .env.example and tests accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@niiyeboah
niiyeboah marked this pull request as ready for review July 1, 2026 00:04
@niiyeboah niiyeboah changed the title Add opt-in cross-repo agent messaging (DISPATCH_CROSS_REPO_MESSAGING) Add opt-in cross-repo agent messaging (Agents settings toggle) Jul 1, 2026
Per review feedback (Brad), swap the DISPATCH_CROSS_REPO_MESSAGING env var
for a setting controlled from the Agents settings page (default off).

- cross-repo-messaging-settings.ts: a dedicated accessor module owning the
  settings key and the "true"/"false" <-> boolean encoding, mirroring
  agent-type-settings.ts. Both the system route and the MCP handler call
  isCrossRepoMessagingEnabled / setCrossRepoMessagingEnabled, so the encoding
  lives in exactly one place (no bare key in db/settings.ts).
- mcp-handlers.ts: the gate reads the setting instead of the env var.
- system.ts: GET/POST /api/v1/app/settings/cross-repo-messaging.
- Frontend: a jotai atom backs the toggle, but the server is authoritative —
  the component GETs the value on mount to hydrate the atom and only POSTs on
  an explicit user toggle. This avoids a mount-time write that would otherwise
  let one device clobber the server-wide gate just by opening settings. Copy
  reflects the server-wide effect; errors use role="alert"; in-flight requests
  are sequence-guarded so a stale response can't override newer intent.
- Drop the env var from .env.example; update unit tests to drive the setting;
  add an e2e regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@niiyeboah
niiyeboah force-pushed the feat/cross-repo-messaging branch from 16759e1 to 04617a6 Compare July 1, 2026 00:08
Track last server-confirmed value in a ref so the optimistic revert on
POST failure restores the correct state instead of blindly using !next
(which desyncs after a double-toggle where both POSTs fail).

Remove three dead self-exclusion checks in sendMessage — addressableAgents
already excludes self at line 129.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@selfcontained
selfcontained merged commit 25b4303 into main Jul 6, 2026
1 check passed
@selfcontained
selfcontained deleted the feat/cross-repo-messaging branch July 6, 2026 15:52
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.

2 participants