// docs

what the relay sees

This page is the homepage’s ciphertext journey with the marketing removed: the complete inventory of what the relay stores, observes, and transiently holds in memory.

per committed event

The table below covers the durable event log only: rows written to Postgres and returned by the event-log WebSocket. Every content payload is sealed before it leaves the client; the relay stores envelope-on-ciphertext.

fieldwhat it isplaintext?
idserver-assigned event idyes
collabIdwhich roomyes
seqserver-assigned orderyes
actorIdwho signed ityes
kindevent verb (task.created, message.posted, …)yes
refsopaque routing pointers (task id, thread, project id)yes (opaque ids)
tsserver timestampyes
contentCtthe payload, sealed under the CKno, ciphertext
noncethe AEAD noncen/a
sigthe author’s Ed25519 signaturen/a (public)

The stored contentCt is additionally KMS-wrapped at rest (OpenBao Transit), so a raw database read shows envelope-on-ciphertext. Read paths unwrap the envelope and return exactly the bytes the author sealed.

in-flight live chat

While a live-chat turn is in flight, the relay process holds one plaintext draft snapshot per active session in memory and fans it to authorised subscribers over a separate WebSocket transport. This is a deliberate carve-out from SO-1 (ADR-0030): the live-session path is structurally isolated from the event-insert path and never writes to Postgres, the LISTEN/NOTIFY bus, or logs. The buffer’s own header comment states it directly: “This buffer holds ONLY ephemeral drafts; it never touches the event log” (packages/relay/src/ingest/live-session-buffer.ts).

fieldwhat it isplaintext?
draft blockscurrent token-stream snapshot (rich block list)yes
tool-call argsarguments passed to an in-progress tool callyes
tool-call output / errorresult from a completed tool call during the turnyes

The buffer is bounded: max 500 blocks per draft, max 256 KB per draft, max 200 concurrent sessions. Cap hits are logged and the frame is dropped; the prior snapshot is preserved. The buffer is cleared when the turn ends (done) or the final sealed commit lands; a relay restart mid-turn drops the draft without a Postgres trace. The clear method in packages/relay/src/ingest/live-session-buffer.ts confirms: it calls only this.snapshots.delete(key), with no database write.

DM drafts are additionally scoped: the relay fans a DM draft only to the two parties of the pair (the peer human and the agent’s owner), never to other collab members (ADR-0045).

per schedule

Schedule timing is plaintext in Postgres because the relay is the scheduler: it expands the recurrence rule and fires occurrence wakes while no browser is open. This is the same metadata grade as event seq/timing and heartbeats. The schedule payload (what the agent will do when the schedule fires) is CK-sealed in the collaboration event stream and never stored in the schedule row.

fieldwhat it isplaintext?
rrulerecurrence rule (cadence)yes
nextFireAtthe next calculated fire timeyes
targetPrincipalIdwhich agent the schedule targetsyes
timezoneIANA timezone for the ruleyes
paththe skill route the fire invokesyes (routing metadata)
schedule payloadthe task body to runno, CK-sealed in the event stream

per agent model preference

Operator model preference policy is plaintext in Postgres. The relay resolves the tier-and-model answer for a running agent; no content or credentials are stored here.

fieldwhat it isplaintext?
primaryModel / secondaryModelconfigured model identifiersyes
scheduleWindowsdays/times when the secondary model is activeyes
timezoneIANA timezone for the window scheduleyes

per account and principal

  • accounts: email, argon2id hash of the login password, and the sealed keystore blob it cannot open
  • principals: public keys (x25519Pub, ed25519Pub), type, status, owner linkage for agents
  • memberships: role, capabilities, your wrappedCk (useless without your private key), ckEpoch, status

what never arrives at all

  • private keys, passphrases, recovery codes
  • the collaboration key in the clear
  • credential secret values: those go to OpenBao KV, are never persisted in Postgres, and are never returned by any list or get surface
  • artifact bytes: encrypted client-side and moved directly between you and your object store
  • the invite secret S: it lives in the link’s URL fragment, which browsers do not send

and the logs?

Structured logging is allowlisted security telemetry only: auth outcomes, rate-limit events, that class of thing. Request bodies, ciphertext, tokens, and key material are never logged, enforced by a dedicated test (security-so1.test.ts) that fails CI if a log line ever carries them.