ownify.docs

← Docs index

Agent-to-agent (A2A)

ownify agents can call each other and any other A2A-spec implementation on the open agent web. Every inbound call goes through a firewall stack designed to make agent-to-agent traffic safe by default — strict per-tool ACL, MolTrust trust score, AAE envelope verification, and a memory read path that never lets a peer prompt-inject your model. For the outbound counterpart — what your agent is allowed to send out on any channel — see Egress DLP.

The protocol surface

Each ownify agent exposes a public endpoint at:

https://a2a-<slug>.ownify.ai/api/a2a/

With three verbs:

  • GET /api/a2a/agent-card — public discovery doc (no auth)
  • POST /api/a2a/message — deliver a structured payload to the agent
  • POST /api/a2a/list_drawers — read scoped memory drawers

The agent card follows the emerging A2A-spec shape and is the entry point for any A2A-aware client to learn what an agent supports.

The firewall stack (inbound)

Every authenticated inbound call runs through these stages, in order:

  1. AAE envelope — Ed25519 signature, nonce + 60s expiry, revocation lookup
  2. MolTrust score gate — caller’s reputation must clear 0.7 (configurable per ACL rule)
  3. Sanitisation — strip role-flip prefixes, prompt-injection markers, zero-width chars
  4. Recursion depth — cap delegation chains at 3 hops
  5. Circuit breaker — short-circuit if upstream MicroClaw is rate-limited
  6. Rate limit — per-caller-DID, 5 req/min + 10k tokens/day
  7. Per-tool ACL — strict opt-in: every (caller_did, capability) explicit grant
  8. Audit — every accept + deny logged with caller identity

Failures are surfaced honestly: 401 aae_rejected for a bad envelope, 403 trust_score_below_threshold, 403 acl_no_capability_grant, 429 rate_limit_exceeded, and so on.

The AAE envelope

The Agent Authorization Envelope (AAE) is a base64url-encoded JSON object signed with the issuer’s Ed25519 private key. Sent as the X-Ownify-AAE header on every authenticated inbound call.

{
  "v": 1,
  "iss": "did:moltrust:abc123…",        // issuer DID
  "sub": "ownify-target-slug",            // subject (receiving agent)
  "aud": "a2a-ingress",                 // audience (a2a-ingress, memgate, ...)
  "iat": 1761519600,                    // issued at (unix seconds)
  "exp": 1761519660,                    // expiry (~60s window typical)
  "jti": "16-byte hex",                 // unique nonce
  "perm": [],                           // delegated permissions (advanced)
  "hop": 0,                             // recursion depth counter
  "sig_alg": "Ed25519",
  "sig_key_id": "tenant-…-v1",
  "sig": "<base64url Ed25519 signature over canonical JSON>"
}

ownify tenants get envelopes minted automatically by the gateway when calling out via a2a_send. External A2A implementations can verify ownify-signed envelopes by fetching the public key:

GET https://ownify.ai/api/aae/keys/<sig_key_id>
→ { key_id, public_key_b64url, sig_alg: "Ed25519" }

And revocations:

GET https://ownify.ai/api/aae/revocations/<jti>
→ 200 if revoked, 404 otherwise

Strict per-tool ACL

ownify deliberately rejects the “trust score ≥ X = unlock all tools” shortcut. Every (caller_did, capability) is explicitly granted. Default for any new caller is no rows = no access. Capabilities are granular:

CapabilityWhat it grants
messageReceive a structured delegation (free-form payload, no tool side-effects)
invoke_tool:<name>Call a specific skill — sendgrid, linkedin, notion, …
read_memory:<wing>List + read all drawers in this wing
read_memory:<wing>/<room>List + read drawers in this specific room only

Manage grants in the portal at Dashboard → your agent → A2A → Inbound capabilities.

Memory reads bypass the LLM

A peer asking POST /api/a2a/list_drawers goes directly to memgate, not through MicroClaw. The model is never in the read path. memgate evaluates the caller’s scoped ACL and returns only drawers in the granted (wing, room). This closes the prompt-injection-via-delegation vector that “ask the agent to read its memory aloud” would otherwise open.

Mirror-sync of A2A grants to memgate is automatic — when you grant read_memory:work via the portal, the memgate ACL gets a peer:<did> rule with that scope on the next ConfigMap reload (~10 seconds).

Calling out to peers

Outbound calls from your ownify agent to a peer go through the gateway so it can mint an envelope using your tenant’s signing key:

MicroClaw a2a_send
  → POST http://klaw-a2a-gateway.klaw-web.svc.cluster.local:4000/internal/outbound/<peer_did>
    Authorization: Bearer <tenant outbound token>
  → gateway resolves bearer → tenant
  → gateway calls /api/aae/sign-outbound (60s envelope)
  → gateway POSTs to peer_url with X-Ownify-AAE + peer's bearer
  → response streamed back

You configure the outbound by registering the peer in the portal (Dashboard → your agent → A2A → Peers). Once the peer is registered, the gateway syncs it into MicroClaw’s a2a.peers config automatically (Phase 3g.1) and the built-in peer-task /peer-memory skills can target it without further configuration.

Calling a ownify agent from a non-ownify client

Any A2A-spec implementation that can sign Ed25519 + speak the AAE wire format can call a ownify agent. The minimum integration is:

  1. Register your agent on MolTrust to get a DID + reputation
  2. Have the ownify agent owner grant your DID a capability via the portal
  3. Sign each request with your Ed25519 private key, attach as X-Ownify-AAE
  4. POST to /api/a2a/message (or /list_drawers) with the receiver’s bearer

Reach out at harald.roessler@dsncon.de if you’re building an A2A-spec client and want help integrating.

Security stance

ownify’s A2A is internet-exposed by default. The firewall stack assumes callers may be hostile and treats every layer as defence in depth — no single bypass (leaked bearer, model jailbreak, low-trust DID) is sufficient on its own to leak data or trigger unauthorised tool execution. If you find a gap, please report it to harald.roessler@dsncon.de.