Skip to content

[RFC] Agent Contacts: cross-owner A2A conversations + decision inbox #41

Open

Description

@kvnloo

Problem

I’d like OpenDots to support a simple cross-owner workflow:

“Ask my friend’s agent for a shareable update, let the agents handle bounded clarification, and only interrupt us when a human decision is required.”

This is different from same-workspace multi-agent orchestration. The remote agent belongs to another person, with its own memory, tools, credentials, and policy.

Full downstream design: kvnloo#13

Product shape: Agent Contacts

A contact would represent an owner-confirmed remote agent endpoint.

Initial flow:

  1. Pair — both owners manually confirm the endpoint over an existing trusted channel.
  2. Propose — from a Dot conversation, stage a request to that contact.
  3. Review disclosure — show the exact outgoing text and explicit page/snapshot revisions before sending.
  4. Remote owner decides independently — their deployment controls what its agent may reveal.
  5. Agents clarify within bounds — only approved exchange context is available.
  6. Surface a compact result / blocker card — collapse routine agent chatter; show human decisions when new authority or private context is needed.
  7. Revoke — immediately prevent future local dispatch/continuation.

Initial scope: one-to-one, text-only, read-only exchanges. No computer control, external writes, payments, calendar actions, attachments, groups, public directory, or contact-of-contact forwarding.

Important architecture boundary

OpenDots should implement this independently. It should not require Hermes as a backend.

The app would own its own:

  • contacts registry
  • owner-authenticated pairing/revocation
  • peer authentication/policy
  • durable inbox/outbox
  • disclosure decisions
  • restricted exchange runner
  • delivery/result UI

Hermes can be one compatible contact, not the control plane.

The companion Hermes implementation is separately proposed as a standalone plugin:
kvnloo/hermes-agent#426

Reuse existing OpenDots / CopilotKit surfaces

OpenDots already has several pieces that fit this UX:

  • human review cards
  • Spaces / revision-aware content
  • persistent work state
  • per-Dot permissions
  • AG-UI rendering
  • explicit computer controls

CopilotKit/AG-UI also has A2A middleware support, so I’d evaluate that rather than inventing another agent transport. But A2A connectivity alone does not solve cross-owner authorization, disclosure, isolation, durable delivery, or owner identity.

The contacts policy/outbox must remain server-side and authoritative regardless of whether a request is proposed from text, voice, UI, or by the model.

Security invariants

OpenDots’ current security doc correctly describes the prototype as single-owner. I’d keep that property: two separately owned OpenDots deployments communicate through a narrow peer interface rather than turning one deployment into a shared multi-user workspace.

For the first implementation:

  • pairing grants communication, not Space/computer access
  • remote display names are not verified human identity
  • initial outgoing text is itself a disclosure requiring review
  • approvals bind to recipient + exact content/snapshot revision + expiry/policy epoch
  • the peer runner sees only approved snapshots + exchange transcript
  • no normal thread history, Automatic Learning, browser, shell, computer tools, arbitrary URL fetching, or further delegation
  • remote messages are untrusted data, never authority or UI actions
  • peer task/history operations are ownership-checked
  • delivery state, agent state, and owner-decision state remain distinct
  • revocation blocks future local work but does not claim to erase already delivered data

First mergeable slice

I would avoid starting with a full social/contact network.

The first vertical slice could be:

  • local contact + revocation model
  • owner-only control API
  • one disclosure decision card
  • deterministic fixture peer / no live model dependency
  • durable inbox/outbox state
  • narrow-screen + keyboard-accessible UI
  • clear states for queued / dispatched / peer accepted / reply received / unknown / expired / failed
  • restart + duplicate-submit tests

Then connect a real A2A peer once the policy and state machine are proven.

Interop contract

The Hermes and OpenDots tracks can share only a small versioned behavior contract / fixture set:

  • exchange request
  • approved shared-context snapshot
  • local decision record
  • delivery/result receipt

Each runtime keeps its own storage, policy, UI, and release cadence.

The useful test matrix is:

  • OpenDots ↔ OpenDots works with Hermes absent
  • Hermes ↔ Hermes works with OpenDots absent
  • OpenDots ↔ Hermes matches the same observable fixtures
  • generic A2A peers either get an explicitly approved manual exchange or a clear incompatibility; no silent downgrade to unrestricted behavior

Credit / provenance

This proposal is intended to compose with existing work, not overwrite it.

  • @whatisthis666 proposed separating permission to communicate from permission to read/act in Hermes #126407; @vanscodex has claimed implementation there. That capability distinction directly informs the cross-owner boundary here.
  • @csigelo proposed the optional peer-authentication direction in Hermes #131484; stronger identity can compose later, but this RFC does not depend on it.
  • The broader downstream OpenDots handoff RFC preserves @Chebaleomkar’s peer-tool investigation, @findigital’s file-handoff problem, @charan-rathore’s recurring-work direction, and @vaheed’s watcher direction as separate lanes. This proposal should re-check those owner threads before touching overlapping surfaces.
  • Existing OpenDots/CopilotKit/AG-UI contributors provide the application, review-card, state, and protocol foundation.

I’m happy to prototype this in small slices if the product direction fits OpenDots.

Prepared with AI assistance; this is a proposal, not a claim that cross-owner agent contacts are already implemented.

Activity

  1. maximveksler commented on Oct 3, 2026

    @maximveksler
  2. vaheed commented on Oct 3, 2026

    @vaheed

    Thanks for preserving the Watcher direction as a separate lane.

    For context, the Watcher work in #31 is intentionally a lightweight proactive layer on top of OpenDots' existing task/conversation model: a watcher detects a change, queues a normal task in an existing conversation, and surfaces the result through Inbox.

    I see this as complementary to F09 rather than overlapping with it. A future Watcher could potentially propose or trigger an Agent Contact exchange, but the cross-owner communication, disclosure, approval, and delivery policy should remain owned by the Agent Contacts flow.

    In other words, Watcher can be a trigger; it should not implicitly grant cross-owner communication or data-access authority.

  3. jibraaan commented on Oct 3, 2026

    @jibraaan
    Contributor

    I built a working first slice of this in my fork, close to the "first mergeable slice" above, in case it's useful as a reference while the design settles:

    What it does (OpenDots ↔ OpenDots only, no A2A or Hermes yet):

    • Pairing: a one-time invite code that expires in 7 days. One owner creates it and the other pastes it, so both confirm. Pairing grants messaging only, with no Space, memory, computer or tool access. Each pairing gets its own secret, which never reaches the browser.
    • Outgoing: ask_contact is a human-in-the-loop card showing the recipient and the exact text. Only the owner's approval route sends it, and only that text. Approval is bound to (thread, toolCallId, contact, text), so a retry never re-sends and the same approval can't send different text.
    • Incoming: requests wait in an owner inbox. "Draft a reply" is a tool-less model call that sees only the Dot's role instructions, this contact's earlier exchange and the owner's typed guidance. It never sees threads, memories, Learning or tools. The owner edits the draft, then sends or declines.
    • States: delivery (queued/delivered/failed) is kept separate from the owner's decision (pending/answered/declined). Failed and queued deliveries retry on demand and at startup.
    • Revocation: stops both directions and is propagated to the peer best-effort. Already-delivered data isn't claimed to be erased.
    • Peer boundaries: a narrow /peer/v1 interface outside the owner API, with per-request contact authentication. At most 20 unanswered requests per contact. Peer text is treated as untrusted data, and peer error bodies are never shown.

    Verification: 6 tests run two OpenDots servers that talk through each other's real routes. They cover pairing, duplicate submits, invite expiry, authentication, flooding limits, forged replies, delivery retry and revocation. I also ran it live between two local servers: pairing from the UI, a round trip in both directions, and revocation. A live model turn has not been tested yet.

    Not done yet (from the RFC): disclosure of explicit page snapshots, bounded agent-to-agent clarification (here, clarification goes through each owner), A2A transport, Hermes interop fixtures, and an expiry or policy epoch on approvals.

    I can open a PR for whatever slice fits the direction the maintainers prefer, or split it smaller.

    Prepared with AI assistance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions