Skip to main content
One Monad daemon can delegate a self-contained task to a peer Monad daemon’s agent (compute federation). The caller’s agent invokes the agent_peer_delegate tool; the peer runs the subtask on its own filesystem, tools, and credentials, and streams the answer back as the tool’s result. This is the inverse of ACP delegation: there Monad drives an external ACP agent over stdio (and serves its files/terminal); here Monad drives a networked Monad peer that is fully self-contained — there is no filesystem/terminal bridge-back.
Ownership boundary. This mechanism assumes the two daemons share a single owner (same person). Cross-owner collaboration (different people, independent trust/billing) is not this feature — that is A2A / Monadix territory and has its own design.

Architecture — the tool is an OpenAI-compat client

P0 reuses the daemon’s existing OpenAI-compatible HTTP endpoint (/openai/v1/chat/completions, see runtime.md) as the transport. The delegating daemon (A) is just another OpenAI client pointed at the peer (B); B drives a normal agent session and streams tokens back. Both daemons run their own agent loop. A’s outbound call carries the peer’s bearer token; B authenticates it like any OpenAI-compat caller and runs the instruction as an surface:'api' session. Key files:
  • apps/monad/src/services/delegation/peer-delegate.ts — the agent_peer_delegate tool (high-risk; streams SSE; the model supplies a peer name, never a URL/token). Composed through agent/execution.ts with the enabled, token-resolved peer tools.
  • packages/environment/src/config/mesh.ts — peers and their direct credentials in mesh.json.
  • apps/monad/src/handlers/settings/peer/ + transports/http/settings/peer.ts — settings CRUD (/v1/settings/peers), driven by monad peer ….
  • apps/monad/src/services/inbound-approval.ts — the inbound approval gate wrapper (see below).
  • packages/protocol/src/peer.ts — the settings wire DTOs (no secrets cross the wire).

Configuration

A peer is infra/security config (a delegation target + its credential), so it lives in mesh.json, alongside ACP agents and managed native agents. The daemon watches that file, so an edit hot-applies without a restart. The token is stored directly beside its peer.
CLI (mirrors monad channel):
Trust is manual: the operator enables openaiCompat on B, shares B’s token out-of-band, and adds a peer entry on A with that token.

Inbound approval — openaiCompat.approval

When B’s delegated agent hits a high-risk tool (shell, write, network), it needs an approval decision. The OpenAI-compat stream has no interactive approval channel, so the request cannot be forwarded back to A’s user (that arrives with PeerLink — see roadmap). Without handling, the call would hang. B resolves it via a per-daemon policy on its OpenAI-compat surface: The policy is applied by createInboundApprovalGate (services/inbound-approval.ts), which wraps the oversight gate and keys off session.origin.client === 'openai-compat'. The daemon’s own sessions (Web/TUI) are unaffected. The setting is hot-reloaded: openaiCompat.approval lives in config.json, so an edit or a settings change applies without a restart.
Note on breadth. P0 inbound requests don’t carry a peer identity, so the policy applies to all OpenAI-compat callers, not only peers. auto flips the prior effective-deny-after-timeout to auto-allow for high-risk tools — appropriate under the same-owner + token-as-trust-boundary assumption, and configurable to local/deny.

Security model

  • The model names a peer, never a URL/token — preventing SSRF and credential leakage (same rule as agent_acp_delegate forbidding raw commands). Only operator-configured, enabled peers are reachable.
  • High-risk gate on A — agent_peer_delegate is high-risk, so each delegation passes A’s oversight gate once before any network call.
  • Native secret ownership — the token lives in mesh.json; settings responses never echo it back, and the user is responsible for the file’s security.
  • Same-owner assumption — auto inbound approval is sound only because A and B are the same person. Cross-owner setups must use local/deny, or A2A/Monadix instead.
See the runtime security model for the general agent-containment boundary. P0 requires the peer to be network-reachable and supports only local/auto approval. The next phase is PeerLink: a bidirectional JSON-RPC link over one WebSocket (/v1/peer) that adds
  • NAT traversal via reverse tunnel — the NAT’d daemon dials out; the link is symmetric, so either side can initiate a delegation once connected (connect direction ⟂ request direction).
  • forward approval — B’s approval request streams back to A over the link (peer.delegate.approve reverse-RPC), so the initiating daemon’s human can decide.
  • Peer-scoped isolation — inbound delegations use a restricted per-peer handler facade and a 'peer' session transport/surface, with capabilities and approval policy enforced at that boundary.
The agent_peer_delegate tool would then become transport-pluggable (direct = OpenAI-compat | link = PeerLink). The full design lives in the team’s planning notes.

Testing

  • apps/monad/test/integration/services/peer-delegate.test.ts — the tool’s HTTP/SSE client against a capture server: request construction, agent override, trailing-slash, multi-chunk/[DONE]/malformed-frame parsing, JSON-vs-non-JSON errors, no-token-leak.
  • apps/monad/test/unit/approvals/inbound-approval.test.ts — the gate policy (auto/local/deny + non-delegation / missing-session edges).
  • apps/monad/test/e2e/peer-settings.test.ts — settings CRUD over a real temporary home (secret stays in mesh.json and is redacted from responses).
  • apps/monad/test/e2e/peer-delegate.test.ts — a real B daemon over loopback: tool-level closed loop + a true two-daemon test where A’s real agent loop emits the tool call and answers from B’s result.
  • packages/environment/test/unit/peer-secret.test.ts — direct peer credential validation and removed-reference rejection.