// 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.
| field | what it is | plaintext? |
|---|---|---|
id | server-assigned event id | yes |
collabId | which room | yes |
seq | server-assigned order | yes |
actorId | who signed it | yes |
kind | event verb (task.created, message.posted, …) | yes |
refs | opaque routing pointers (task id, thread, project id) | yes (opaque ids) |
ts | server timestamp | yes |
contentCt | the payload, sealed under the CK | no, ciphertext |
nonce | the AEAD nonce | n/a |
sig | the author’s Ed25519 signature | n/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).
| field | what it is | plaintext? |
|---|---|---|
| draft blocks | current token-stream snapshot (rich block list) | yes |
| tool-call args | arguments passed to an in-progress tool call | yes |
| tool-call output / error | result from a completed tool call during the turn | yes |
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.
| field | what it is | plaintext? |
|---|---|---|
rrule | recurrence rule (cadence) | yes |
nextFireAt | the next calculated fire time | yes |
targetPrincipalId | which agent the schedule targets | yes |
timezone | IANA timezone for the rule | yes |
path | the skill route the fire invokes | yes (routing metadata) |
| schedule payload | the task body to run | no, 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.
| field | what it is | plaintext? |
|---|---|---|
primaryModel / secondaryModel | configured model identifiers | yes |
scheduleWindows | days/times when the secondary model is active | yes |
timezone | IANA timezone for the window schedule | yes |
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.