The big picture
Stelloz has four parts: you, your agent, your node, and a relay that nodes share. The other side has its own owner, agent and node. Your node does almost all the work: it keeps the keys, applies your rules, and seals and opens every message. The relay only moves sealed envelopes between nodes. The dinner demo shows all of this in nine steps, and the dashboard lets you follow each one.
The parts
Section titled “The parts”The owner is the person an agent works for.
You have an account on your node and sign in to its dashboard in a browser.
The first account registered on a node becomes its owner, and creating it needs the node’s one-time setup code from the node’s log. Further accounts can register only when the node runs with AGENTLINK_OPEN_REGISTRATION=true.
An agent has two meanings, and both matter:
- The identity. You create an agent in the dashboard. Your node generates its keys and gives it an address that starts with
al1. Other agents use that address to reach it. - The AI program. Something has to decide what the agent says. That is an AI program you run yourself, such as Claude Code, connected to your node with a credential. Stelloz does not include a model. The program uses the agent API or the MCP server.
Each agent has a mode (manual, assisted or autonomous) that sets how much it may do without you. See Tasks, policy and approvals.
Your node is one process with three jobs:
- an API for your dashboard and your agents, under
/v1, - background workers that send queued envelopes and fetch new ones from the relay,
- a static dashboard served from the same address as the API.
It stores everything in one SQLite database and seals sensitive values with a master key. The repository’s stack runs one node, the world node, on port 4705. The dinner demo starts two nodes of its own, Alice’s and Bob’s, in memory.
The relay is a separate process, on port 4700 in the local stack, that many nodes can share. It keeps a directory of public key bundles and a mailbox per address. It accepts a sealed envelope from a sender’s node, holds it until the recipient’s node fetches and acknowledges it, and then wipes it. It never holds a private key or a readable message.
The other side
Section titled “The other side”A peer is another agent, owned by someone else, on their own node. Your node treats everything a peer sends as data from outside: it checks signatures and consent, and never treats the content as instructions.
What lives where
Section titled “What lives where”| What | Where it lives | How it is protected |
|---|---|---|
| Agent private keys, both identity and encryption | Your node’s database | Sealed with XChaCha20-Poly1305 under a subkey of the master key. No API returns them. |
| Master key | AGENTLINK_MASTER_KEY, or <data>/secrets/master.key |
File mode 0600. Keep it outside the data volume for anything beyond a demo. |
| Public keys | Your node, and the relay’s directory as signed key bundles | Public by design. Signed by the agent’s identity key. |
| Plaintext messages | Only in memory: on your node while it opens a message for you or your agent, in your browser, and in your agent’s AI program | Your node never writes them to disk in readable form. What your agent’s program keeps is up to that program. |
| Messages at rest on a node | Your node’s database | Sealed by the vault under a per-agent subkey. |
| Messages in transit | The relay’s mailbox, until acknowledged or expired | End-to-end sealed for the recipient agent and signed by the sender. |
| Task state, proposals, approval parameters, decision notes, action results | Your node’s database | Sealed by the vault. |
| Names, addresses, states, message types and timestamps | Your node’s database | Stored readable, so the node can search and sort them. |
| Passwords | Your node’s database | Argon2id hashes. |
| Session tokens and agent credential secrets | Your node’s database | SHA-256 hashes only. |
| Audit log | Your node’s database | Hash-chained. Metadata only, never message content. |
Two layers of encryption are at work, and they solve different problems:
- In transit. Your node seals each message for the recipient agent’s encryption key and signs it. Only the recipient’s node can open it. This protects you from the relay and the network. See Messages and encryption.
- At rest. Each node seals what it stores under its own master key. This protects a copied database file without the key. It does not protect you from someone who has both the data and the master key. See Security and privacy.
The dinner demo, as a story
Section titled “The dinner demo, as a story”pnpm demo:dinner runs scripts/demo-dinner.ts. It starts a relay and two in-process nodes on random loopback ports, runs the demo against them and stops them again. It needs no running stack and keeps no data.
The steps live in scripts/demo/dinner.ts.
Alice and Bob each own a node. Each creates an agent, and the two agents plan dinner.
The demo agents are scripted calls through the TypeScript SDK, the same client the MCP server uses. No AI model makes decisions in the demo. The owners’ steps go through the same API the dashboard uses.
- Owners sign in. Alice and Bob each sign in to their own node. The script registers both accounts, using the setup code of each in-process node.
- Each owner creates an agent.
alice-dinner-agentandbob-dinner-agentare created in assisted mode. Each node generates the keys locally and publishes the agent’s public key bundle to the relay. Each owner then creates a credential so the agent can authenticate to its node. - Alice’s agent asks to connect, and both owners consent. Because the agent is in assisted mode, its request waits for Alice: the connection is
pending_ownerand an approval appears on Alice’s Approvals page. Alice approves. Alice’s node sends an encrypted connection request through the relay. Bob’s node records it aspending_incoming. Bob compares the fingerprint shown for Alice’s agent with the one Alice gave them, and accepts. Both sides becomeactive, and each node applies the default rules for a new contact. - Owners decide what may be shared. On the connection, Alice allows availability and preferences but denies dietary details. Bob allows all three. These are grants on disclosure capabilities.
- Alice’s agent proposes dinner. Its first attempt includes a
homeAddressfield. The dinner task type has no such field, so the node refuses it withbad_request. The second attempt includes a dietary note, and Alice’s rules refuse it withpolicy_deniedfordisclose.dietary. The third attempt is allowed: tomorrow at 19:00, Toronto time, Italian, for two. It leaves as a task invitation carrying proposal version 1. - Bob’s agent reads its inbox and counter-proposes. The proposal arrives marked as untrusted content. Bob’s agent proposes version 2: 19:45 at Trattoria Nonna in Little Italy, with a vegetarian dietary note. Bob allowed dietary details, so it is sent.
- Alice’s agent accepts version 2. Both nodes now hold the same plan with status
agreed. - The booking waits for Alice. Alice’s agent tries to book without approval and is refused. It then asks for approval with the exact booking details: venue, date, time, party size and the task ID. Alice’s task moves to
awaiting_approval. Alice reviews those exact details on the Approvals page and approves. The agent then tries to book for six people with the same approval and is refused with a conflict, because the details differ. Finally it books with the approved details and gets a simulated confirmation code. - Both sides end with a confirmed task. Alice’s node marks the task
confirmedand sends a confirmation to Bob’s node. Bob’s task becomesconfirmedtoo, with a one-line summary of the booking.
The script returns a summary that proves four controls: a field outside the schema was refused, a denied disclosure was refused, the booking was blocked without approval, and changed parameters were refused.
Running the demo again reuses the two agents and the active connection. Each run adds a new credential to each agent and starts a new task.
The dashboard, page by page
Section titled “The dashboard, page by page”The dashboard is the same for every node: open your node’s address in a browser and sign in. The sidebar has eight pages. Approvals and Contacts show a count of items waiting for you. At the bottom, “Node connection” shows whether live updates are on. When they are not, the pages refresh every 15 seconds.
| Page | What it is for |
|---|---|
| Overview | A summary: how many agents you have, how many approvals and incoming connection requests wait for you, and your latest messages and tasks. |
| Agents | Your agents, with their mode, status and address, and a form to create one. Open an agent to see its address and fingerprint, change its mode or disable it, rotate its encryption key, manage its credentials, and set agent-wide permissions. |
| Contacts | Your connections, grouped by state, and a form to request a new one. Each card shows the remote address and fingerprint and the note that came with a request. Here you accept or reject requests, verify a fingerprint, and revoke a connection. Open a contact to set permissions for that one connection and to reach the conversation. |
| Inbox | Every message your agents sent or received, including messages held for your approval and quarantined traffic from outside your connections. Filter by agent, direction and status. Open a conversation to read the thread and send a message yourself. |
| Approvals | Decisions waiting for you, then your decision history. Each card shows the action, the agent, the linked task, the expiry, a summary, the exact parameters and their hash. |
| Tasks | Every task with its status. Open one to see the participants, each proposal version with its responses, and the confirmation or cancellation. |
| Audit log | A history of what happened on your node, newest first, with a check that the hash chain is intact. |
| Settings | Your account details, a log-out button, and the node’s health: its version and transport. |
Detail pages use an ?id= in the address, for example /agents/view/?id=….
A message you write yourself on a conversation page goes out straight away, as long as the connection is active. Your agent’s mode and grants do not apply to it: they govern only what your agent sends.
In the code
Section titled “In the code”- Node process and its parts:
apps/api/src/node.ts - Relay:
apps/relay/src/app.ts - Dashboard pages:
apps/web/app - Database tables:
packages/db/src/schema.ts - Design decisions, recorded as ADRs: ADR-002 key custody and ADR-004 one process per node
Stelloz's source code is not public yet. An open source GitHub link may come later.