Architecture Overview
Single-line summary: Mentionable is an implementation layer that ties three existing protocols (ActivityPub, A2A, Email) into one address space via WebFinger, with a single normalized message contract between transport adapters and agent code.
See also: normalized-message-guide, webfinger-agent-card-guide, transport-module-guide, identity-auth-guide, glossary
Two-Layer Architecture
Mentionable separates the network surface from the agent code surface. Confusing the two is the most common source of bugs.
| Layer | Purpose | Owns | Examples |
|---|---|---|---|
| Agent Adapter Layer | Verify wire-level credentials, normalize platform messages into NormalizedMessage, dispatch to the agent. | auth_method, verified, recipient_capabilities, history trimming via HistoryPolicy. | @mentionable/transport-activitypub, transport-a2a, transport-email, transport-rest. |
| Client Integration Layer (Connector) | Verify a native platform (Slack, Discord, Teams, IDE), mint signed identity attestations, dispatch to a Mentionable agent over a Transport. | IdentityEvidence issuance, Connector Card publication, PolicyPart UI rendering. | slack-connector, future Teams/Discord/IDE connectors. |
The two layers are decoupled. A Connector calls an agent over the same Transport surface as anyone else; the Connector is just a more privileged caller because the agent’s runtime trusts its issuer.
WebFinger Is the Hub
Mentionable does not invent a new wire protocol. It uses WebFinger as the discovery hub binding @agent@domain.tld addresses to:
- An ActivityPub actor (
rel: "self") - An Agent Card (
rel: "https://mentionable.dev/ns/rel/agent-card") - An optional profile page (
rel: "http://webfinger.net/rel/profile-page") - An optional mailto link (
rel: "mailto")
A receiver resolves @agent@domain once via WebFinger, then chooses the transport surface it wants. WebFinger is always served at the apex (/.well-known/webfinger), even when other Mentionable transports are mounted under a basePath.
Normalized Message Is the Single Contract
The single contract between adapters and agents is [[normalized-message-guide|NormalizedMessage]]. Adapters convert their wire format into it; agents implement Agent.handle(message, ctx) against it.
interface Agent {
handle(
message: NormalizedMessage,
ctx: AgentContext,
): Promise<NormalizedResponse> | AsyncIterable<NormalizedResponse>
}
Key design invariant: agents branch on business logic, never on received_via. If your agent has if (msg.received_via === 'email') ..., you are violating the contract — push that decision down into the adapter or up into a typed field on NormalizedMessage.
Four Transports
| Transport | Library | Used For |
|---|---|---|
| ActivityPub | Fedify | Fediverse mentions, federated profile actors. |
| A2A | @a2a-js/sdk | Agent-to-agent JSON-RPC, structured tool / artifact / streaming. |
| Gmail API + Postalsys (Nodemailer / smtp-server / ImapFlow / PostalMime) | Inbound DKIM/DMARC verified mail, outbound DKIM-signed mail. | |
| REST | Built-in | Browser/IDE/script clients, GET-stream + POST-message. |
All four implement the same trust contract: verify native credentials → mint IdentityEvidence → attach to sender.identities → invoke the agent.
Symmetric Addressing
Both sender.address and recipient use the same @x@domain shape. Agents and humans share the address space — @alice@example.com (a user via email) and @scheduler@bots.example.com (an agent) interoperate by the same rules.
Symmetry enables multi-agent composition: any agent can send to any other agent over A2A by resolving its WebFinger record, with AgentChain metadata bounding the depth.
Package Structure
15 published packages under @mentionable/* (pnpm-workspace.yaml):
- Foundations:
core(types, validation, helpers),runtime(router, session, agent interface),server(HTTP host,TransportMount). - Transport modules:
transport-activitypub,transport-a2a,transport-email,transport-rest. - Composition:
node(headless one-import composition for embeds and standalone),gitagent-mapping,x402-bridge,slack-rich,mcp-builtins. - Tooling:
repo-analyzer,analyze-repo-cli,tck(conformance test kit).
See deployment-patterns for which package to import in which scenario.
Common Mistakes
- Branching on
received_viafor business logic. Use typed fields instead. - Treating
Transport ModuleandConnectoras the same thing. They have different trust scopes — see glossary#transport-module and glossary#connector. - Mounting WebFinger under
basePath. WebFinger is always at the apex. - Authorizing on
sender.profile. Profile is presentation context only — see identity-auth-guide.