Architecture
How eris works, in eleven short chapters.
Here, eris means your agent, on your devices. Our servers means the controlplane and relay we run.
Architecture · 1 of 11
It starts with your account key
Your browser makes your account key, and never sends it anywhere.
Here, eris means your agent, on your devices. Our servers means the controlplane and relay we run.
Scroll to follow along ↓
Account key
Made in your browser. Never sent.
Sign-in secret
Sent when you sign in.
Our servers
Keep only a hash of it.
User key
Stays in your browser.
From it, your browser derives a sign-in secret. That's all our servers get, and they keep only a hash of it.
It also derives your user key, which stays in your browser and decides which devices are yours.
More detail
The account key. 24 characters, or 120 random bits. There's no email or password, and your browser doesn't keep it. Both other keys come from it with HKDF-SHA256, each under its own label, so the sign-in secret can't be turned back into the account key.
The user key. Your browser keeps it as WebCrypto keys that can't be exported, and forgets it when you sign out.
2 of 11
Adding your devices
Add a device with a one-line install, or by approving a link it shows you.
With a token
eris.talk/add
Makes a token with a provisioning key after the dot.
The new device
Has the token.
With a link
The new device
Makes a secret, and shows a link: /activate/CODE#secret
Your browser
Seals a token with the secret.
The new device
Opens the token.
It joins your keyring
- Makes its own key, and gets a certificate
- Opens the envelope your browser left for the token
- Signs its own add_member
The public certificate log
Anyone can check it.
eris.talk/add makes a token for the install. The part after its dot is a provisioning key, which never reaches our servers.
Or the device shows a link ending in #secret. Your browser never sends that part. It seals a token with it, and our servers pass that on unopened.
Either way, the device makes its own key and adds itself to your keyring.
Each device gets a certificate that lasts three days and renews itself. Every one goes in a public log.
More detail
Tokens. The part before the dot is an ID our servers know. Your browser signs a delegate for the provisioning key, and leaves it an envelope with your keyring's start and epoch keys. Each token adds one device.
Links. Running eris setup without a token makes a 256-bit secret and shows a link like eris.talk/activate/WDJB-MJHT#… and a QR code. The page shows the device's name, code and key fingerprint to check. Approving makes a token, good for 15 minutes, sealed with a key derived from the secret and the code. Anything our servers swap in won't open, and the device gives up, loudly.
Joining. The device gets its certificate with a request signed by its key. It opens the envelope only your browser could have made, pins your keyring's start from it, and signs its own add_member with the provisioning key.
Certificates. They're X.509, issued under an offline root. Devices need no login: they sign every request with their own key.
The public log. It's an append-only Merkle tree, like Certificate Transparency's, and renewals only work for certificates in it. So our servers can't quietly issue one for your device, and a key of their own couldn't join your keyring anyway. The Trust page shows the root and the log, to check yourself.
3 of 11
The keyring
Your keyring lists your devices, and your user key signs every change to it.
User key
In your browser.
Keyring log
Your devices, and a commitment to each epoch key.
Epoch key
Random, from your browser.
History and settings
Stored by our servers, sealed.
Envelopes
One for you, one per device.
Each device's box key
And your user key.
A new epoch key
Sealed only to the devices that are left.
Everything our servers store for you is sealed with the current epoch key, which your browser makes at random.
Each device gets the epoch keys in an envelope only its own box key opens, and only uses keys your user key has vouched for.
Unlink a device, and your browser makes a new epoch key for the rest. The unlinked device gets nothing new, and erases its eris data the next time it connects.
More detail
The log. Each statement is signed by your user key and names the hash of the one before. Our servers can't forge it, and your browser and devices check the whole chain. The kinds are genesis, delegate, add_member, remove_member, new_epoch and replace_user_key, for a new account key.
Epoch keys. The log holds only a hash of each. Envelopes hold every epoch key so far, sealed with X25519 and AES-256-GCM. A device only uses a key that matches a hash, so swapped envelopes get our servers nothing.
Unlinking. Your browser signs remove_member and new_epoch, and reseals your settings. That needs your settings unlocked, so the removal is always signed. The relay shuts the device out at once, and its certificate isn't renewed.
Erasing. Next time the device reaches our servers, they turn it away and send the keyring log up to its remove_member. The device checks it itself: the log must follow on from the one it last checked, be signed by your user key, and remove this device. Only then does it erase ~/.eris and its background service, and nothing else. Our servers can't sign a removal, so they can't make a device erase itself. A device off for longer than its certificate lasts asks for the log with a request signed by its own key, and gets nothing else.
| Key | Who holds it | What it's for |
|---|---|---|
| Account key | You | Deriving the sign-in secret and the user key |
| Sign-in secret | Your browser as you sign in. Our servers keep a hash. | Signing in |
| User key | Your browser, while you're signed in | Signing the keyring and settings. Opening your envelope. |
| Epoch key | You and every device in your keyring | Sealing history and settings with AES-256-GCM |
| Device key | That device, which never shares it | Its certificate, and signing its history, requests and relay sign-in |
| Device box key | That device | Opening its envelope |
| Provisioning key | An enrollment token | Letting one device add itself |
4 of 11
Your settings
Your settings are sealed in your browser, so our servers can't read or change them.
Your browser
Seals your settings, and signs them with your user key.
Our servers
Store them, unread.
Your devices
Check the signature and the version, then open them.
They say what your devices do: the Discord bot, who it answers, what Claude may do, and a Claude token for devices not signed in. Your browser seals them with the epoch key and signs them with your user key.
Our servers store them, and tell your devices over the relay when they change.
Each device checks the keyring and signature, refuses a version older than one it's seen, and only then opens them. It keeps only the sealed copy on disk.
5 of 11
A message arrives
Every device hears your message, and exactly one answers.
Your message, on Discord
Laptop
Online. A lower hash, so it stays quiet.
Home server
Online. The highest hash, so it answers.
Work machine
Offline, so it isn't counted.
Your chat
Answered once.
Our servers send every device the same snapshot of who's online. Of those, the device with the highest hash of the chat and its own ID answers.
Every device works that out the same way, so there's no election. When one comes or goes, only its chats move.
More detail
Snapshots. They're numbered, so a newer one always wins. Our servers send one when a device connects or leaves, when your keyring changes, and every 30 seconds. Picking by hash like this is called rendezvous hashing.
Losing the connection. A device that's lost its connection can't be sure its snapshot is current. It waits up to 10 seconds for a new one, then goes quiet until it's back. If our servers are down for everyone, every device keeps its last snapshot, so they still agree.
Not built yet. Pinning a chat to one device. Discord is the only chat app so far.
6 of 11
The shared history
Every device keeps the same history: every chat, and what eris did on every device.
Laptop
Our servers
Your other devices
- Laptop: Seals and signs entry 42.
From Laptop to Our servers: Sealed: Entry 42
- Our servers: Checks it's signed and follows 41. Stores it, unread.
From Our servers to Your other devices: Sealed: Pushed to those online
- Your other devices: Each checks and stores it.
From Your other devices to Our servers: Back online: I have up to 37
From Our servers to Your other devices: Sealed: Entries 38 to 42
A device seals and signs each new entry and sends it to our servers. They check it's signed and follows on from the last, then store it unread.
They push it over the relay, a WebSocket each device keeps open, to your other devices that are online.
A device that was offline asks for what it's missing by sequence number, and sends what it wrote meanwhile.
More detail
One feed per device. Each device only adds to its own feed, so feeds never conflict, and there's no leader. If a device signs two different entries at the same place, the others keep the first.
| Each entry has | Sealed | What it holds |
|---|---|---|
| Device | No | Which device's feed it's in |
| Sequence number | No | Its place in that feed: 0, 1, 2 and so on |
| Previous hash | No | The hash of the entry before, chaining the feed |
| Epoch | No | Which epoch key sealed the body |
| Body | Yes | The record itself, bound to its device, place and epoch |
| Signature | No | The device's, over everything above |
A copy on every device. Each device checks every entry again and keeps the whole history in a local database. One that loses it, like a cloud server after a redeploy, fetches it all back. A new device gets everything, even if no other device is on.
What's in it. Every chat message and change to core memory. Each turn of eris's Claude sessions: what Claude was sent, what it said, and which tools it called with what, but not what they returned. Notes in the journal, tasks and their results, and each device's description: its system, chats, and git repositories. Long text is cut short, and the full transcript stays in Claude Code's own files on the device that ran it.
The relay. Devices sign in by signing a challenge with their key. The relay checks the certificate, turns away unlinked devices, and passes messages between devices on the same account without looking inside. Our servers also use it to say who's online, and when your settings or keyring change.
7 of 11
Memory
Each Claude session starts knowing what's going on across your devices.
When it starts
- Your core memory: what you've told it to keep
- Which devices are online, and what each has
- The latest messages from your other chats
- The latest notes in the journal
What's new in the journal
Notes written elsewhere since its last message.
Tools, on demand
- read_messages, list_sessions, read_session
- read_journal, write_journal
- search_memory, remember, forget
- list_devices, ask_device, check_task
A new session has your core memory, knows which devices are online, and gets the latest from your other chats and the journal.
The journal is where sessions note what they did, decided or learned. Before each of your messages, a session hears what was noted elsewhere.
For anything else, it searches and reads the history with tools. Each device searches its own copy, so nothing leaves it.
More detail
Sessions and notes. Each chat has its own Claude Code session, on the device that answers it. A new one gets the latest from the last three days. A note is a sentence or two, never secrets, and a running session hears up to 8 new ones before each message. Claude Code's own memory is off, since it's a folder on one machine.
Core memory. Your standing instructions and lasting facts, like “always work in ~/code on the mac mini”. Every session has what applies to it in its prompt, as instructions. Claude saves one only when you state it in your own message, never from something it read, and another device's tasks can't change it. Entries are capped at 300 characters, and 6,000 in all, so Claude merges or forgets some rather than add more. Each is for everywhere, one device, or only your private chats.
Search, and who's listening. search_memory searches core memory, the journal, chats and Claude's turns, in any language. Each device does it on its own copy, with SQLite's full-text search (FTS5), and no embeddings. Each chat has an audience: Owner for your own messages with eris, or WithOthers where others can read, and for another device's tasks. One rule decides what may be shown where: a chat only gets what's for its audience or a wider one, and the default is the careful side. So far, that keeps private core memory, and messages and notes unasked, out of chats with others. Search over chats, the journal and turns isn't held back yet, and audiences like friends or an organization aren't built.
Data, not instructions. What's in the history was written by people, other programs, web pages, or Claude after reading those. So it's always passed on as data, never as instructions.
8 of 11
Work moves between devices
When a job needs another device, eris hands it over and brings the answer back.
Your chat
Device answering
Your laptop
From Your chat to Device answering: “Fix the test we looked at on my laptop”
- Device answering: Claude calls ask_device, carrying on from that session.
From Device answering to Your laptop: Sealed: A Task, through the history
- Your laptop: Forks the session, with the whole conversation, and does the work.
From Your laptop to Device answering: Sealed: A Done, through the history
- Device answering: Gets it within 4 minutes, or later as a notice.
From Device answering to Your chat: “Fixed, on a branch”
Claude calls ask_device, and the task goes to that device through the shared history, sealed and signed.
If you started the work there, the device forks that session, so it has the whole conversation.
eris waits up to 4 minutes for the answer. A later one still reaches the chat you asked in.
More detail
Tasks. The device asking writes a Task to its feed. The device asked does it in a Claude session of its own, then writes a Done. The session it forks is left as it was. If that session ran elsewhere, the new one reads it from the history instead. A device that's offline isn't asked, tasks can't be handed on again, and one that arrives more than 10 minutes late isn't done.
Files. Claude can send you a file from its device, like a screenshot, with send_file. It goes straight into the chat, up to 20 MB on Discord, and stays on the device.
Long replies. eris says it's still working after 2, 10 and 30 minutes, and on what. If Claude's quiet for 3 minutes in the middle of a tool call, eris says so, and on macOS checks whether a permission prompt is waiting. After 20 minutes of quiet, it stops the turn and says why.
9 of 11
What our servers can and can't see
Our servers see who, when and how much, but not what.
Our servers can see
- Your account ID, and your name if you set one
- Your devices' names, public keys and certificates
- Your keyring log, which is signed, not sealed
- Which browsers you're signed in on
- When each device is online, and its network address
- Each history entry's device, place, epoch, size and time
Our servers can't see
- Your account key, user key and epoch keys
- Your devices' private keys
- What's in your history
- Your settings, your bot's token included
- Your files, which stay on your devices
They see what they need to run the service: your account, your devices and their public keys, and when each is online.
They can't see your keys, your history, your settings or your files.
Discord sees your chats with your bot, and Anthropic sees what Claude is sent, as they would without eris.
10 of 11
Limits, honestly
Sealing protects what's said. Some trust is still needed, and some things aren't hidden.
Our servers can't read or forge your history, but they could hold it back, or show devices different keyrings. Devices don't compare notes to catch that yet.
A device trusts the root certificate our servers give it at setup. And eris.talk's code comes from our servers, so changed code could take your account key next time you enter it.
History is kept indefinitely for now, with no way to delete entries. An unlinked device keeps what it had until it next connects, when it erases it.
More detail
Metadata. Who, when and how much aren't hidden: see what our servers can see, above.
Prompt injection. Marking what's read as data makes it harder to steer Claude with planted text, but not impossible.
No recovery. Lose your account key and every signed-in browser, and the account is gone.
Not built yet. Separate spaces for work and personal, with keys of their own, the website reading your history, more chat apps, and friends or an organization as audiences.
11 of 11
The whole picture
Your browser and devices hold the keys. Our servers keep and pass on what's sealed.
- Yours, holding your keys
- Our servers, holding only sealed data
- Other services
- Sealed
Your browser, on eris.talk
Makes your account key, links and unlinks devices, and seals your settings.
Our servers: the controlplane and relay
Keep your keyring, settings and history, sealed, and pass them on.
Your devices
Each runs eris and Claude Code, and keeps a copy of the whole history.
Discord
Where you talk to eris.
Claude, by Anthropic
On your own Claude login.
Your browser makes your account key, links and unlinks devices, and seals your settings.
Our servers, the controlplane and relay, keep your keyring, settings and history sealed, and pass them on.
Each device runs eris and Claude Code, on your own Claude login, and keeps a copy of the whole history. Your files stay there.
Your chats go through Discord, and prompts go to Anthropic.
That's how eris works.