0005 — The Matrix↔A2A Bridge is a mautrix/go Appservice (not bridgev2)¶
Status: Accepted
Context¶
The one novel component is the bridge that stitches Matrix to A2A. mautrix/go (v0.29.0, import maunium.net/go/mautrix; the framework behind Beeper and every mautrix bridge) offers two framings, and the choice is load-bearing:
bridgev2— models a foreign network mirrored into Matrix portals (logins, portals, remote-user puppets). Purpose-built for "bring Slack/Telegram into Matrix."- The plain
appservicepackage — namespace registration, event transport, and multi-ghost puppeting, with no assumption of a foreign network.
For native human+agent rooms there is no foreign network to portal — the agents live inside this platform. Choosing bridgev2 would force inventing portals and logins for a network that does not exist: the wrong shape.
Decision¶
Build the bridge as a single Go binary on the mautrix/go appservice package (v0.29.0), not bridgev2. It:
- Owns the exclusive
@agent-.*ghost namespace plus the@a2a-bridge:fgentic.fmind.aibot, declared in the appserviceregistration.yaml. - Receives events via
PUT /_matrix/app/v1/transactions; theEventProcessordispatchesevent.EventMessage, readsevt.Content.AsMessage().Mentions.UserIDs, and matches the@agent-.*regex — with a plaintext-body fallback for clients that omitm.mentions. - Maps
@agent-k8s → (namespace=kagent, name=k8s-agent)via an allowlist (this is the who-may-invoke-which-agent authorization), optionally validating the target's AgentCard, then calls A2ASendMessage(ADR 0004) — routed through agentgateway (ADR 0006). - Posts the reply as the ghost via
as.Intent(@agent-k8s)→EnsureRegistered→EnsureJoined→ send with a typedm.mentionsand anm.relates_toreply pointing at the original event (multi-ghost puppeting). - Backs its StateStore with the shared Postgres
bridgedatabase (ADR 0007) so ghost registrations survive pod restarts.
Off-the-shelf bridgev2 bridges are reserved for the external-network interop phase (Slack/Telegram/… as separate appservices).
Consequences¶
- The code owned is small and honest: a registration file, an
@agent → (namespace,name)map, aSendMessageResult → stringextractor, and the reply wiring — everything else is mautrix/go and a2a-go. - A push-based Go appservice idles at ~15–40 MiB and runs no per-ghost
/sync— negligible footprint. - Typed
m.mentions(MSC3952) is the primary trigger; the body fallback keeps non-conforming clients working. - Multi-turn context is preserved by storing
roomID → contextIdfor the A2A call. - Adding external-network interop later does not touch this bridge — those are independent
bridgev2appservices with disjoint namespaces (e.g.@telegram_.*).