Skip to content

Setup

The question this answers: what do I install where, and where do the conversations actually live?

Every machine runs the whole product and keeps its own copy of the event log. One of them is the home; the others are clients. A client pushes what it writes to the home and listens for everything else; the home fans out.

The home is not a master. It holds the same signed log as everybody else; if it burns down, every machine still has the whole conversation, and any one of them can become the home by changing one setting. It exists because a laptop that is closed cannot be reached, and two laptops never open at the same time never converge.

A machine with no home address configured is the home. That is also the single-machine case: a fresh install talks to itself and needs no setup at all.

Four arrangements; the difference is which machine can dial which. Pick one in Settings → Relay on the home; every other machine reads the choice off the log.

What it is Needs a secret
One local network Every machine in the same building. Nothing to install; nothing works away from that network. No
Tailscale Every machine gets an address that works from anywhere, behind any NAT. No - the tailnet is private
One always-on machine at home The default. Clients dial in from wherever they are. No
Home on a public address A VPS, or a port forwarded to a box at home. Yes - the setting refuses without one

A client only ever dials out, so a machine behind carrier NAT or a hotspot works as a client today; it simply cannot be the machine everybody dials. A public address is one the internet can join - set MAVEN_TEAMS_SECRET to the same string on every machine first. It is not encryption; it buys exactly one property - a stranger cannot join. Set it everywhere in one sitting: the failure mode is silent.

Node 22.22+ (for node:sqlite), git, a POSIX shell.

Terminal window
git clone <your-repository-url> ~/maven-teams
cd ~/maven-teams
npm ci
npm run build
npm start # http://localhost:3400

Copy .env.example to .env.local and fill in the keys you have; every one is optional. The variables that decide how the machine behaves on the network:

Variable What it does
MAVEN_STATE_DIR Where the log, signing key, settings and jobs live. Defaults to ~/.maven-teams - deliberately not M.A.V.E.N’s ~/.maven, which shares seventeen filenames with it.
MAVEN_MACHINE This machine’s name on the log and in every audit line. Defaults to the hostname.
MAVEN_HOME_URL The home’s address. Set it and this machine is a client; leave it unset and it is the home. Settings → Relay can set it too, without a restart; the variable wins when both exist.
MAVEN_TEAMS_SECRET The shared workspace secret. Absent means open - right behind a router, wrong on a public address.
MAVEN_FORCE_CLOUD 1 sends every capability to a provider regardless of hardware. See Compute.
PORT / HOST Default 3000 / 127.0.0.1; the service sets 3400. A machine others must reach needs HOST=0.0.0.0.

A machine’s advertised address is read from its network interface; what it listens on is HOST. Leave the default and the machine will confidently publish http://192.168.1.42:3400 while accepting nothing but connections from itself - it works perfectly as a client, can never be the home, and every screen says it is fine.

Environment=HOST=0.0.0.0
Environment=PORT=3400

A systemd user service, so it runs as you and can read your keys:

~/.config/systemd/user/maven-teams.service
[Unit]
Description=MAVEN Teams
After=network-online.target
[Service]
WorkingDirectory=%h/maven-teams
Environment=NODE_ENV=production
Environment=PORT=3400
Environment=MAVEN_MACHINE=%H
ExecStart=/usr/bin/npm run start
Restart=always
[Install]
WantedBy=default.target
Terminal window
systemctl --user daemon-reload
systemctl --user enable --now maven-teams
loginctl enable-linger "$USER" # or it stops when you log out

On the home, open Settings → Relay. Under the first row is “reach me at” - the addresses another machine can use, port filled in. Copy one. On each client, paste it into the home field and press SET HOME. Within seconds the panel says connected and the workspace’s whole history arrives.

To move the home later, press MAKE THIS MACHINE THE HOME on the new one and set its address on the others. Nothing is lost: every machine already holds the log.

If a client cannot reach the home, check the direction that matters - from the client:

Terminal window
curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 http://<home-address>:3400/api/relay

200 means the network is fine and the problem is configuration. For networks that cannot see each other, put the machines on WireGuard or Tailscale and use that address; each machine announces every address it has.

Terminal window
curl -s localhost:3400/api/relay | jq '{me: .me.machine, role: .hub.role, connected: .hub.connected}'
curl -s localhost:3400/api/events/verify | jq

Every machine’s chain is verified separately; ok: false names the sequence number where the break is.

$MAVEN_STATE_DIR/ (defaults to ~/.maven-teams)
├── events/
│ ├── log.jsonl this machine's own chain - the one it signs
│ └── peers/<machine>.jsonl one file per machine, exactly as received
├── teams/
│ ├── identity.json this machine's keypair. BACK THIS UP.
│ ├── peers.json addresses of machines to pull from directly
│ └── hub.json the home's address, if this machine is a client
└── index.db SQLite full-text index - derived, delete it freely

identity.json is the only irreplaceable file. Lose it and the machine cannot sign new events; a new identity means a new chain under the same name. index.db is disposable - if search behaves oddly, delete it.

Conversations you clear are also written to your Obsidian vault as Markdown under memories/channels/<room>/.

The log carries what happened: messages, patches, runs, approvals, decisions. It does not carry your keys - .env.local stays on the machine it was written on, and agents run on the machine you are sitting at, with your credentials. A private channel is hidden, not encrypted: the log replicates everywhere; the setting decides what the interface lists.