Rendered from docs/ops/comms.md, which is the source of truth and the markdown twin of this page. Rendered at site v0.1.13.
The comms channel: from the reader's column to the build agent
secrets.sgit.ai · operations · written at v0.1.7 (2026-10-06) · CC BY 4.0
The site is read by a person and built by an agent that only exists during a session. This page is how the one talks to the other without memos: a column on every page that keeps a log of what the reader marks, and a one-way encrypted lane into a vault only the agent can open.
The reader's column
Every page has a tab on its right edge, Reader. It opens a drawer with three views:
- Log. Mark the page read, star it, vote it up or down, leave a note (typed, or dictated with the browser's own speech recognition). Every action is one event in an append-only log in this browser's
localStorage(sgit.secrets.reader.log.v1), keyed by the page's path and a hash of its text, so a note written against an older version of a page says page changed since. Undo is another event; nothing is deleted. Send to the agent seals the unsent events to the agent's public key and writes them to the lane below. Copy for Claude puts the same on the clipboard as markdown with the JSON inside it; Paste to merge takes it back on another device. Nothing leaves the page without a click. - Chat. A chat over the site. Without a key it is an offline search over
data/search-index.json, generated from every page's markdown twin at each release. With the reader's own OpenRouter key it is Claude Sonnet (anthropic/claude-sonnet-5.5) throughopenrouter.ai, with tools that search the site, open a page, list the pages, say what to read next, read the reader's log and file a note into it, so a conversation turns into feedback the agent receives. The key is held in memory, or in this tab'ssessionStoragewhen the reader ticks keep for this tab; it is never written tolocalStorageand is sent toopenrouter.aiand nowhere else. Replies can be spoken with the browser's speech synthesis; questions can be dictated. - Graph. The intent nodes the page names (every content page and mockup links the nodes it realises) and their neighbours, drawn by the navigator's
review-graph; a click moves the focus, and a link opens the same place in/review/ui/.
The column is three web components under components/ (reader-panel, reader-log, reader-chat) on the site's base sg-base, three files each, no framework, no colour of their own: they follow the theme. Where the page's Content-Security-Policy does not allow a connection to openrouter.ai or to the vault endpoint, the Chat and Send parts say so and point at the page that does (docs/design/brief-corrections.md, C19).
The lane into the agent's vault
The agent has a comms vault on SG/Send (https://dev.send.sgraph.ai) and a key pair (RSA-OAEP 4096 for encryption, ECDSA P-256 for signing). The public bundle, the vault id and the lanes are published in /.well-known/sgit-agents.json, the Agent Contact v0.1 shape every sgit.ai site uses. A lane is a write-only address on the vault, gated by an append token: a sender who holds the token can write, and cannot list, fetch or read. The token is not published. The lead makes it once (configure_lane.py --new prints it), keeps it in SGIT_COMMS_LANE_TOKENS, and pastes the readers one into the column's Append token field on the devices that should write (for this tab, or kept on the device when ticked); so, for now, the comments that reach the agent are the lead's own, and opening the lane to others is a decision, not a default. The agents token goes to other agents by hand. The site's column writes reader-message/v1, the sgit PKI envelope made with WebCrypto (the same bytes sgit pki encrypt would make), to the readers lane. Other agents write signed agent-message/v1 mail to the agents lane.
The agent reads the lanes at the start of a session with tools/comms/drain.py: list, fetch, decrypt with its private key, keep ciphertext and plaintext under the vault's inbox/, print a digest it acts on, then mark-processed. What cannot be opened goes to inbox/quarantine/ with the reason.
What only the project lead can do, once
The session that built this could create the vault and the key pair, but its permission policy stopped it short of two things: registering the lanes on the vault (an account-level change on SG/Send) and pushing the private keys into the vault. So the vault key, the read key and the key pair are where that session left them, and the lead finishes the channel:
- Take the vault key from the build session's scratchpad (
vaults/create.log, the line Vault key) and the key pair from~/.sg-send/keys/of that session, with the passphrase invaults/pki-passphrase.txt. Store the vault key asSGIT_COMMS_VAULT_KEYand the passphrase asSGIT_COMMS_PKI_PASSPHRASEin the Claude Code environment this repository runs in, so every later session can open the vault. Put the key folder in the vault (agent/keys/) and push it. - Publish the public half:
python3 tools/comms/publish_contact.py --fingerprint sha256:6d9fe81fd718ab0bfills the contact file with the bundle, the vault id and the lane names (no token); commit it. - Register the lanes:
python3 tools/comms/configure_lane.py --new(needsSGIT_COMMS_VAULT_KEYandSG_SEND_ACCESS_TOKEN) makes the two append tokens, prints them once, and registers their hashes on the vault. Store the printed line asSGIT_COMMS_LANE_TOKENSin the environment (the drainer andsend_test.pyread it there). On success it sets the contact file's status toopen; commit it. Then open the column on/reader/, press Append token, paste the readers token, tick keep on this device if you want it to stay, and Send is on for that device. - Prove the round trip:
python3 tools/comms/send_test.py, thenpython3 tools/comms/drain.py --vault-dir <the vault clone>.
Until step 3 the column says the lane is pending and offers Copy for Claude, which works today. Send runs only on /reader/ (the one page whose policy allows the connection); on every other page the button becomes a link there, and the log is the same one.
The tools
| File | Does |
|---|---|
tools/comms/comms_contact.py | reads and writes the contact file |
tools/comms/comms_lane.py | the append-lane calls: write (no key), list, fetch, mark-processed (enum key), configure (write key and the SG/Send token) |
tools/comms/comms_envelope.py | the sgit PKI envelope: seal to a public key, open with the agent's private key |
tools/comms/publish_contact.py | fills the contact file from sgit pki export and the vault key |
tools/comms/configure_lane.py | registers the lanes on the vault |
tools/comms/send_test.py | writes one test message to the readers lane |
tools/comms/drain.py | reads the lanes into the vault's inbox/ and prints the digest |
They run on a person's or an agent's machine, never in CI, and use the sgit_ai package for the key derivation and the envelope; nothing in them is a secret.