Skip to Content
DocumentationCompanion MemoryAsking & sharing architecture

Asking and sharing architecture

Most interactions between people are inquiries about personal memory: availability, shared experiences, contact information, photos, or documents. Traditional cloud architectures force users to choose between two undesirable extremes:

  1. All-or-nothing data sharing: Sharing entire folders, photo albums, or calendars on commercial cloud platforms, permanently handing raw data to third parties.
  2. High-friction manual interruption: Messaging the person directly, waiting for them to search their personal devices, and receiving unverified, manual replies.

Companion Memory introduces interpersonal federated querying: Companions can ask bounded questions of other Companions and return truthful, minimally disclosed answers—without either party transferring their raw records, without centralized cloud brokers, and with the responder remaining in absolute cryptographic control of disclosure.

For a screen-by-screen walkthrough of how Asks works in the Companion Memory interface today, see Asking other Companions.


The two sharing models

Companion Memory provides two distinct sharing mechanisms designed for different threat models and operational needs:

1. Shared searches (passive, scoped egress)

A Shared Search is a pre-authorized, read-only window into a specific slice of memory.

  • Query-pinned: The search terms, date boundaries, and entity filters are fixed at creation time. The holder of the link cannot modify the query.
  • Dynamic evaluation: Unlike a static export, when new records matching the search criteria are ingested, the view updates automatically.
  • Scoped capability tokens: Access is gated by a cryptographically random capability token. The token can carry an expiration timestamp or be revoked immediately.
  • Use case: Sharing receipts from a specific trip with an accountant, or sharing family vacation photos with relatives who do not run a Companion.

2. Federated asks (active, bilateral query-answer)

An Ask is an active question sent from one person’s Companion to another person’s Companion.

  • No raw data egress: Raw database records never leave the responder’s appliance.
  • Review by default: Unless covered by an explicit, pre-existing standing grant, every incoming question queues for manual owner review.
  • Ceiling-clamped output: Answers are distilled to the lowest necessary rung of disclosure (for example, a yes/no boolean or an item count) before leaving the appliance.
  • Use case: Asking a colleague for availability, asking a friend for photos of a specific event, or asking a peer for contact recommendations.

Closed query-type grammar

To prevent arbitrary SQL or prompt-injection queries from crossing trust boundaries, Companion Memory validates every ask against a closed grammar (askGrammar.ts and peers/protocol.ts):

Query type (queryType / archetype)Composer prompt in /asksAnswer formBuilt-in ceilingAccepted on /agent/v1/ask peer wire?
relationshipDo you know…?booleanboolean (Rung 1)Yes (relationshipAskSchema)
contentDo you have any…? / Do you have photos of…?existencecount (Rung 2)Yes (contentAskSchema)
availabilityAre you free…?attribute (free/busy)summary (Rung 3)Yes (availabilityAskSchema)
attributeWhat is…?attributesummary (Rung 3)Local / DCQL only
identity(API / DCQL only)identitysummary (Rung 3)Local / DCQL only

Any request naming an unknown queryType or archetype is rejected before consent rules or search evaluation run.


Trust Over IP (ToIP) dual-stack implementation

Federated asks in Companion Memory follow the Trust Over IP (ToIP) 4-layer dual-stack model. This structure separates technical trust (cryptographic verification) from human trust (consent governance).

Layer 1: Utility and root of trust (KERI)

Companion Memory does not rely on centralized Certificate Authorities (CAs), Domain Name System (DNS) records, or distributed blockchain ledgers for identity:

  • Autonomic Identifiers (AIDs): Cryptographically self-certifying identifiers (did:keri:<aid>). The identifier is derived directly from the inception event public key.
  • Key Event Logs (KEL): An append-only, tamper-evident hash chain of key state events (icp inception, rot rotation, and ixn interaction).
  • Pre-rotation commitments: Every key rotation commits to the cryptographic hash of the next public key (nxt). If an active private key is compromised, the attacker cannot forge a rotation event because they do not control the pre-committed key pair.

Layer 2: Peer-to-peer protocols and secure interconnect

Companions communicate directly over HTTPS, Tailscale tailnets, or local networks:

  • Out-of-Band Introductions (OOBI) and peer directory: P2P discovery URLs (/.well-known/keri/oobi) allow two appliances to establish pairwise cryptographic links via QR codes or shared links without cloud mediation. When CI_PEER_DIRECTORY_URL is configured, appliances can also resolve handles through the Companion Portal directory (POST /api/peers, GET /api/peers/resolve/:handle, GET /api/peers/search, GET /api/peers/mine, and DELETE /api/peers/:handle), while inbound cross-box asks verify the responder’s audience (CI_PEER_AUDIENCE).
  • RFC 9421 HTTP Message Signatures: Every inbound ask and poll request to /agent/v1/ask must carry an RFC 9421 signature over:
    • @method (HTTP verb)
    • @target-uri (Canonical URI reconstructed from server config)
    • content-digest (SHA-256 digest of the request body)
  • Nonce tracking and replay defense: A memory-bounded sliding window cache (PeerSignatureService) tracks signature nonces to reject replayed requests.

Layer 3: Trust exchange and credentials (ACDC and TEL)

Standing permissions are represented as Authentic Chained Data Containers (ACDC):

  • Self-Addressing Identifiers (SAID): Every grant object is uniquely identified by the cryptographic hash of its canonical content (said.ts).
  • Transaction Event Logs (TEL): Revocation is tracked in an append-only event log (iss issuance and rev revocation events). Companions can immediately verify credential validity without querying a centralized revocation list.

The top layer enforces owner autonomy and privacy boundaries:

  • The six-rung disclosure ladder: Restricts the fidelity of every response.
  • Trust tiers: Sets global maximum disclosure ceilings based on human relationships.
  • Cryptographic audit logging: Every disclosure is written to an immutable audit ledger anchored by a SAID digest.

The six-rung disclosure ladder

When a question matches records in your memory, the answer is not returned as raw database rows. Instead, it is synthesized to the specific rung permitted by policy:

Code rungLabel in /asks UIWhat the asker receivesExample output
none (0)NothingNo answer at alldelivered (Waiting for them in /asks; asker sees only that the request was received)
boolean (1)Just yes or noA single verification bittrue or false
count (2)Just how manyMatch magnitude or existence3 matching items found (or existence confirmation in /asks)
summary (3)A summary onlyA short synthesized statement"You were in Lake Tahoe together in July 2024."
redacted (4)Only what I pickItem references with sensitive fields scrubbedPhoto IDs with location data and background faces stripped
full (5)Everything that matchedFull item references and metadataComplete photo references, coordinates, and participant IDs

Every peer Companion linked to your appliance is assigned a Trust Tier. Trust tiers establish a permanent, non-negotiable ceiling on what that peer may receive:

Trust TierWhere it appliesMaximum allowed rungPurpose
strangerDefault for unlinked peersbooleanRestricts untrusted callers to binary availability checks.
peerStandard acquaintancessummaryAllows high-level confirmations without item references.
trustedClose friends and colleaguesredactedPermits itemized answers with sensitive metadata redacted.
familyImmediate familyfullPermits full item disclosures when authorized by a grant.
householdSame appliance onlyfullInternal accounts on the same physical server.

The household boundary cannot cross networks. The household tier is restricted to local multi-user accounts on the same machine. It is rejected by database constraints (PEER_TRUST_TIERS) for external peers to prevent remote appliances from inheriting local administrative privileges.


The five architectural invariants

Companion Memory enforces five non-negotiable architectural invariants (documented as Architectural Decision Records ADR-0001 through ADR-0005). These invariants guarantee that bugs or adversarial requests cannot breach user privacy.

ADR-0001: The asker sees only answered or delivered

To prevent attackers from probing whether you are ignoring them, whether a grant exists, or whether an ask was declined:

  • The asker-visible status is folded down to exactly two states: answered or delivered (shown in /asks as Answered or Waiting for them).
  • Terminal outcomes such as declined, expired, or withheld are stored as delivered on the asker’s appliance (status: "pending", reason: "No answer is available" on /agent/v1/ask poll responses, folded into delivered by askerVisibleStatus).
  • The asker cannot differentiate between an ask waiting in your review queue and an ask you deliberately declined.

A major vulnerability in authorization systems is the “revocation race”: revoking a restrictive grant might cause a request to fall back to a broader policy.

  • Companion Memory stamps the active disclosure ceiling on the ask at the moment consent is granted.
  • When the answer is computed, the engine evaluates both the stamped ceiling and the current policy, and applies whichever is narrower:
    Effective Ceiling = min(Stamped Ceiling, Current Policy)
  • Revoking a grant can never broaden an answer; it can only narrow or eliminate it.

ADR-0003: Asynchronous polling via correlation IDs (no webhooks)

Companion Memory forbids webhook callbacks for federated ask replies:

  • Webhook callbacks turn receiving boxes into open egress servers, making them vulnerable to Distributed Denial of Service (DDoS) and Server-Side Request Forgery (SSRF) amplification.
  • All federated asks use a pull model: the asker mints a client-side askId (correlation ID) and polls GET /agent/v1/ask/:correlation.
  • Polling requests are rate-limited independently of the ask intake queue.

ADR-0004: Zero redirects and connect-time IP pinning

When fetching answers or dispatching asks to peer endpoints:

  • The egress fetcher (peerEgressFetch) forbids HTTP redirects (301, 302, 307, and 308). Following redirects would allow malicious peers to redirect an egress request to internal endpoints (for example, http://169.254.169.254 or http://localhost:5002).
  • DNS resolution is performed once, and the target IP is pinned at socket connection time. This neutralizes DNS rebinding attacks.

ADR-0005: Null dataClass is wildcard for block, never for auto-answer

Requests and grants can optionally specify a dataClass (for example, photos, calendar, or health):

  • In a block rule (grant-block / mode: block), an omitted or null dataClass acts as a wildcard: it blocks every data class.
  • In an auto-answer rule (grant-auto / mode: auto), an omitted or null dataClass is forbidden from acting as a wildcard: an auto-answer grant must explicitly name the specific data class it applies to.

End-to-end federated ask lifecycle

The following sequence diagram illustrates the complete execution path of a federated ask crossing between two sovereign appliances:


Verifiable audit records (SAID)

Every terminal outcome (answered, auto-answered, declined, or expired) generates an immutable audit record in ci.ask_audit.

To provide cryptographic non-repudiation between sovereign peers, Companion Memory computes a Self-Addressing Identifier (SAID) over the canonical JSON representation of each audit record:

// auditSaid.ts export function computeAuditSaid(record: AuditableRecord): string { const facts = { askId: record.askId, askerUserId: record.askerUserId, peerLinkId: record.peerLinkId, peerHandle: record.peerHandle, responderUserId: record.responderUserId, query: record.query, consentBasis: record.consentBasis, outcome: record.outcome, disclosedCount: record.disclosedCount, disclosedEntities: record.disclosedEntities, d: "############################################", // 44-char placeholder }; const canonical = canonicalStringify(facts); // RFC 8785 deterministic ordering return computeCesrSha256Digest(Buffer.from(canonical, "utf8")); // Returns 'E...' }

If either party disputes what was asked or disclosed, the SAID can be recomputed against the audit payload. Any modification to query parameters, disclosed entities, or counts produces a mismatched digest, providing mathematical proof of tampering.


Last updated on