mentionable.dev

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.

LayerPurposeOwnsExamples
Agent Adapter LayerVerify 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:

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

TransportLibraryUsed For
ActivityPubFedifyFediverse mentions, federated profile actors.
A2A@a2a-js/sdkAgent-to-agent JSON-RPC, structured tool / artifact / streaming.
EmailGmail API + Postalsys (Nodemailer / smtp-server / ImapFlow / PostalMime)Inbound DKIM/DMARC verified mail, outbound DKIM-signed mail.
RESTBuilt-inBrowser/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):

See deployment-patterns for which package to import in which scenario.

Common Mistakes