Cross-connector identity linking
Mesh can recognize one person across separate connector accounts within one agent’s perspective. Verification associates existing actors, private memories and conversation history while preserving their original attribution. A name claim never establishes identity or reveals whether the agent knows that person.Using the flow
- The first account a person uses with an agent is that person. Nobody has to mark it: a connector-authenticated private session is the root by virtue of being first. An owner can additionally provision an exact participant ID onto a person before its first message, for example a phone number.
- When the same person turns up on another channel, they ask the agent in that
new private conversation to link their accounts, or send
link accounts. The agent gives generic instructions and a code. - Within ten minutes, the person sends
link CODEin their private conversation with the same agent on the account they already use. The person must carry the code between sessions themselves; the agent never forwards it. - The runtime verifies the persisted, connector-authenticated command before model inference. On success, both accounts resolve to the established actor. Earlier private memories and requester history become available across the linked accounts, subject to their original restrictions.
request_identity_link tool only starts the flow. It accepts no
name, target, code or approval argument. There is no model tool for approving a
link, and a bare “yes” cannot complete one.
Privacy and possession requirements
The implemented direction is claimant → anchor: show a code on the new account and receive it on the established one. There is no name lookup, candidate list, outgoing challenge notification, or contact-directory response. The claimant receives the same instructions regardless of whom they claim to be. This avoids both contact enumeration and unsolicited challenge notifications to real people. Starting and confirming require persisted inbound messages in direct conversations, with source evidence and an authenticated single-reader audience matching that connector identity. A group message cannot establish a link even if its audience happens to list only one reader. Connector bot messages bypass command handling. Any private account the agent already talks to can anchor. The proof is the echoed code: it demonstrates control of two sessions, and that is the whole claim being made. An owner assertion would add nothing about the second session, so none is required. A new observation, name match, mention or model inference still MUST NOT link accounts; only the echo does. The proof is not a legal identity check. People must use it only for their own accounts, as the challenge instructions state. Someone persuaded to copy another person’s linking code could authorize an unwanted association, just as with another account-possession ceremony.Proof data and resolution
Verification provenance lives on theconnector_identities row being verified:
verification_method:code_echofor a completed possession challenge,operatorfor an owner-provisioned participant ID, null for an observation.verified_via_identity_id: the anchor identity for a code-echo proof.verification_challenge_id: the successful challenge supporting that edge.verified_at,verified_by_user_id, andconfidence: timestamp, owner audit identity for provisioning, and proof strength (50 operator, 100 code echo).verification_revoked_at: severs the proof without erasing its provenance.
runtime_agent_id. Check constraints require complete proof records.
identity_challenges retains the SHA-256 code digest, claimant and eventual
anchor, request/response message IDs, expiry, attempts and disposition.
identity_link_attempts records confirmation attempts, and
identity_revocations records recovery actions.
Original actor IDs, message authors, participation and memory subjects are never
rewritten. Roots are implicit: an identity is a root unless it carries an active
code-echo proof pointing at another identity. mesh_verified_identity_actor
follows active proof edges to that root; mesh_canonical_actor resolves an actor
to it for reads, falling back to the original actor when its proof is inactive.
The roster, actor timeline, memory subject relevance, display labels and
search_history(speaker=requester) use that live association. Raw IDs remain
authoritative historical attribution.
A linked child may anchor another account. Resolution is bounded to 32
identities per chain; confirmation refuses cycles and proofs beyond that bound.
An actor with multiple pre-provisioned identities cannot become a code-echo child
in this version: start the challenge from the separate account and confirm it on
one of the pre-provisioned actor’s accounts instead. Provisioning is serialized
with confirmation so it cannot bypass that restriction.
Private continuity
Audience snapshots remain immutable and connector-scoped. The additional read path requires both single-reader identities to resolve through live proof chains to the same actor in the same perspective. The destination must be a direct conversation with that linked person, backed by persisted inbound evidence. Only single-reader private source evidence qualifies. Multi-reader rooms, workspace audiences, externally shared audiences, unknown evidence, explicit conversation restrictions and external-tool grants are not widened by a link. Existing permissions within a connector continue to apply. Every inherited message and memory restriction still applies, including evidence used in an agent reply that is later retrieved as history. Memory and retrieved-history permissions are checked again before delivery. A revoked link cannot authorize a reply still awaiting that check. Information already delivered cannot be retracted.Limits and failure behavior
Codes have 80 random bits, are single-use, and expire after ten minutes. They are stored as digests in the challenge table, although the plaintext necessarily appears in the two private transcripts. A new accepted request abandons any previous pending code for that claimant. The limits are three starts per claimant account per fifteen minutes, ten confirmation attempts per responding account per fifteen minutes, and five failed confirmations against a known challenge. A throttled start returns the same question with an unusable code. All unsuccessful confirmations use the same public response. Budgets are bound to authenticated senders, never to a name claimed by a stranger. A worker retry of the exact successful response message may repeat the success result while that same proof remains active. A new message replaying the code cannot consume it again; retrying after revocation cannot restore the link. Anunlink accounts retry likewise reuses its recorded outcome and cannot revoke
a newer link. Confirmation, provisioning and revocation serialize under a
per-perspective transaction lock.
Recovery and owner controls
Sendunlink accounts privately to sever the current account’s proof and every
proof chained through it. An install owner can use Unlink this account on a
linked account on the actor page. The severed accounts are separate people again,
exactly as before linking, and their private continuity is removed. Nothing
restores a severed subtree silently: each account re-links with a fresh code from
its own private conversation. There is no owner “verify” step, because there is
nothing for it to assert that the echo does not already prove.
Owner APIs under /api/agents/{slug}/actors/{actorID}/identities are:
POSTto provision a new exact participant ID onto this person.DELETE /{identityID}to sever an account’s proof while retaining the account.
Scope and delivery
Links belong to one agent’s perspective. Two agents may independently link the same person; neither inherits the other’s links or memories. This human identity flow does not change agent-owned acting identities (repository). See identity-delivery.md (repository) for the three-PR sequence: proof storage, conversational verification and recovery, then memory/history continuity. The owner-verified-root requirement those PRs shipped with was removed afterwards (migration0097); the possession proof was always the load-bearing part.