For Savi Comms 2026.11.5 on one machine of your own: a Mac, or a Linux machine with systemd. The download carries the same steps in docs/QUICKSTART.md. For a Linux server in Azure, reachable over the internet, use the server install guide.
For: the administrator installing Savi Comms for their company on one machine: a Mac, or a Linux machine or virtual machine with systemd, used from its own screen or from another computer over SSH.
To install on a Linux server in Microsoft Azure, reachable over the
internet with Microsoft Teams connected, use
docs/INSTALL-SERVER.md instead. The two guides agree on
everything they share.
Savi Comms runs on one machine you choose. It gives your AI agents a mailbox each, wakes an agent when mail arrives, checks every action an agent takes against your rules, and sends the decisions that need a person to you, on a page called the Decision Desk.
Three things are worth knowing before you start.
127.0.0.1. From
another computer you reach it through an SSH tunnel (Step 5).| What | Needed | How to check |
|---|---|---|
| Operating system | macOS, or Linux with systemd. The Linux distributions tested are listed on savicomms.com, under Downloads, then Linux. | Linux: systemctl --version prints a version |
| Python | 3.12 or 3.13 on Linux; 3.12 on a Mac. Each release carries compiled code built for exactly these, and an update refuses, before it changes anything, a Python it has no build for. The container image uses 3.12. | python3 --version |
| Disk encryption | The disk that holds the store must be encrypted, or the broker refuses to start. Linux: LUKS (dm-crypt), including LVM on LUKS, or an Azure virtual machine's managed disks. macOS: FileVault, or an encrypted APFS volume. | See "The encrypted disk" below |
| Claude Code | Installed for the same user that runs Savi Comms. No version is refused. This release's console sign-in is tested with Claude Code 2.1.282 to 2.1.309; with any other version, a sign-in that fails names the version that ran. | claude --version |
| Ports | 8421 for the broker, on 127.0.0.1 only. A chat port,
8431, opens only once a chat surface such as Microsoft Teams is set up.
Nothing to open in a firewall. SAVI_COMMS_PORT moves the
broker's port. |
The health check in Step 3 |
| Network | Outbound https to Anthropic while agents work, and to the Savi Comms services to start a trial or use a license and to check it each day. Nothing inbound. | |
| Memory and disk | Savi Comms states no minimum memory. Two agent sessions run at once by default. The server guide sizes its server at 2 processors and 8 GB of memory, and asks for 16 GB of free disk. | free -h and df -h on Linux |
| Time | About 30 minutes. |
In the examples below, the company is Acme Robotics, the administrator is Dana Reyes, and the agents are Sentry (security), Orchid (ways of working) and Ada (a first working agent). Use your own names.
Savi Comms checks, every time it starts, that the disk holding its store is encrypted. If it cannot confirm it, the broker does not start, writes this to its log, and emails you once if this installation can send email:
Savi Comms will not start: the disk holding its store (...) is not encrypted, or its encryption could not be confirmed (...).
When the check passes, the log reads
disk encryption confirmed for the store. The check looks
at the store only, so choose where the store lives before Step 2. Keep
your agents' folders on an encrypted disk as well: they hold your
agents' work.
On Linux, the store's filesystem must sit on a dm-crypt device, which is what LUKS creates. A whole-disk LUKS install, a separate LUKS data volume, and LVM on LUKS all pass. An Azure virtual machine with managed disks also passes, because Azure encrypts those itself; the log then says the platform attested it rather than the machine. Anything else is refused. To check the folder you plan to use:
findmnt -no SOURCE --target ~
lsblk -s -o NAME,TYPE "$(findmnt -no SOURCE --target ~)"
How you know it will pass: the second command lists
a line whose TYPE is crypt. If the store will live
somewhere other than your home folder, name that folder in place of
~.
On a Mac, Savi Comms asks macOS whether FileVault is on for the volume that holds the store. Two ways pass:
FileVault on the startup disk. Check with
fdesetup status; it prints FileVault is On.
Turn it on in System Settings, Privacy & Security, FileVault. A Mac
with FileVault off and the store on the startup disk is refused,
Apple-silicon Macs included.
An encrypted APFS volume of its own, with
FileVault left as it is. An encrypted APFS volume that is not the
startup disk reports itself to macOS the same way, and passes. To make
one in Disk Utility: choose View, then Show All Devices; select the
APFS container that holds your startup disk; click the plus button above
the list; name the volume, for example Savi Store; choose
APFS (Encrypted) as the format and set a password.
Then use /Volumes/Savi Store/savi-comms-store as the store
in every step below, and check it:
diskutil info "/Volumes/Savi Store" | grep FileVault
How you know it will pass: the line ends in
Yes. The volume has to be unlocked and mounted for Savi
Comms to start. While it is locked the service cannot start; macOS
keeps trying, and it starts once the volume is unlocked.
With the store on LUKS, the disk needs its passphrase every time the machine starts. After a power cut nobody is there to type it, so Savi Comms cannot start until somebody does. When the whole disk is on LUKS, the machine waits at the passphrase prompt. When only the store's volume is, the system starts, the service keeps trying every five seconds, and Savi Comms starts by itself once the volume is unlocked and mounted.
If that wait is a problem, Linux offers ways to unlock without a person at the keyboard. Each one trades some of the protection a typed passphrase gives. Savi Comms checks only that the disk is encrypted, so all of these pass its check. Which one fits is your decision, and keeping the typed passphrase is a valid choice.
| Way | How | What it protects | What it costs |
|---|---|---|---|
| Typed passphrase (what you have) | Somebody types it at every start | A stolen disk and a stolen machine stay locked | Nothing starts after a power cut until a person types it |
| A key file on separate media | A LUKS key slot opened by a file on a USB stick, named in
/etc/crypttab |
A stolen disk without the stick stays locked | Anyone holding the machine and the stick together can read everything. An unattended start needs the stick attached, so in practice it stays with the machine |
| The machine's TPM2 chip | systemd-cryptenroll --tpm2-device=auto on the LUKS
device, and tpm2-device=auto in
/etc/crypttab |
A disk pulled out of the machine stays locked | A stolen machine, whole, unlocks itself when switched on; a firmware
or boot-loader update can stop the automatic unlock until the passphrase
is typed once and the key is enrolled again. Adding
--tpm2-with-pin keeps a PIN at every start, which brings
back the wait |
| clevis with a tang server | clevis luks bind against a tang server on your own
network |
A disk or machine taken off your network stays locked | The machine unlocks only while the tang server answers, so a power cut that also takes the tang server down still needs the passphrase; the unlock needs the network in the early boot stage |
In every case keep the passphrase key slot, so the disk can still be opened by hand.
Open a terminal: on macOS, Terminal is in Applications, then Utilities. Put the distribution file and its checksum file in your home folder, then check and unpack it, using the version number in the file's name:
cd ~
shasum -a 256 -c savi-comms-<version>.tar.gz.sha256
tar -xzf savi-comms-<version>.tar.gz
mv savi-comms-<version> savi-comms
On Linux, use sha256sum -c in place of
shasum -a 256 -c. The check prints OK; if it
does not, do not use the file.
Check your Python first. On Linux, python3 --version
must print 3.12 or 3.13; on a Mac, 3.12. These are the Pythons each
release is built for: its code is compiled, and compiled code runs only
under the Python version that built it. Ubuntu 24.04 ships 3.12 and
Debian 13 ships 3.13, so python3 works as written there.
Debian 12 ships 3.11 and Ubuntu 22.04 ships 3.10, which no release is
built for: use a newer version of the system. On a Mac, the
python3 that comes with Apple's command line tools is
older: install Python 3.12 from python.org and name it,
python3.12, in place of python3 on the last
line below (or use the Savi Comms Mac app, which carries its own). On
Red Hat Enterprise Linux 9 or 10, Rocky 9, AlmaLinux 9 or 10, and Amazon
Linux 2023, install the python3.12 package and name
python3.12 the same way.
Then create the Python environment:
cd ~/savi-comms
SAVI_VENV=~/savi-comms-venv sh scripts/install_dependencies.sh install ~/savi-comms python3
This installs every package from requirements.lock.txt,
which names the exact release of each package with its hash, using
pip install --require-hashes. A package that does not
match its hash stops the install. Packages that only one optional
feature uses are left out unless you choose that feature: the encrypted
store is in by default, and a Postgres database or outgoing mail through
an Azure Communication Services resource is added by putting
SAVI_EXTRAS=postgres or SAVI_EXTRAS=email (or
both, SAVI_EXTRAS=postgres,email) at the start of the same
line. The command says which features it installed and which it left
out.
How you know it worked: the last line reads
install_dependencies: verified followed by a count of
locked releases.
The Python environment, ~/savi-comms-venv, sits beside
the program rather than inside it, so an update can replace the
program's folder without rebuilding it.
Savi Comms has a short set of rules that nobody at your company can change, including you. They are what make it safe for agents to work without a person watching every step. The installer shows them to you first.
A license or a free trial comes before the rules. The package you download is encrypted, and it opens only for an installation holding one or the other. The first time you run the installer it asks you, in the terminal: type 1 to paste a license, 2 to start a free trial with a six-digit code sent to your email, or 3 for a page in a browser on this computer, which also offers signing in with Google or Microsoft. One trial is allowed per company domain, and one per address at a free email provider. This step needs the internet. It also needs an available terminal for that question: started by a tool with no terminal, the installer stops and says what it needs, and creates nothing. The Savi Comms Mac app asks the same question in its own setup window, with a box to paste a license and a free trial started with an emailed code, and needs no terminal and no browser.
If your store goes on an encrypted volume of its own, use that path
after --store here and in every later step.
Run the installer without accepting the rules:
cd ~/savi-comms
~/savi-comms-venv/bin/python installer.py --store ~/savi-comms-store --workspace-root ~/agent-workspaces \
--human-name "Dana Reyes" --human-email dana@acme.example \
--company-name "Acme Robotics" --company-short Acme \
--security-agent sentry --air-agent orchid
It prints the rules and stops. Nothing has been created except what holds your license or trial.
Read them. When you accept them, run the same command again with
--accept-fixed-rules added:
cd ~/savi-comms
~/savi-comms-venv/bin/python installer.py --store ~/savi-comms-store --workspace-root ~/agent-workspaces \
--human-name "Dana Reyes" --human-email dana@acme.example \
--company-name "Acme Robotics" --company-short Acme \
--accept-fixed-rules --security-agent sentry --air-agent orchid
How you know it worked: the last line reads:
bootstrap complete: 2 agents live, authority retired, audit chain verified=True
What the installer did:
~/savi-comms-store and the agents'
folders under ~/agent-workspaces.~/savi-comms-store/tokens/dana.token, named after the first
word of your name in lowercase: dana for Dana Reyes. Use
your own name in its place wherever this guide writes
dana.token.comms command
finds it from any folder without any setting.root.key inside the store, readable only by you.If Claude Code is not installed yet, the installer ends with the steps to install it. Follow them now:
curl -fsSL https://claude.ai/install.sh | bash
~/.local/bin/claude --version
How you know it worked: it prints a version number
followed by (Claude Code). Do not run
claude to sign in on this machine. The
installation signs itself in from its console in Step 5. A sign-in
typed in a terminal here is a second sign-in, and whichever of the two
refreshes first retires the other.
Protect the root key. Anyone who has both the store and the root key can read every secret in it, and without the root key nobody can, including you. Keep it with your backups and nowhere else.
Start the broker by hand once to see it work:
cd ~/savi-comms
SAVI_COMMS_ROOT=~/savi-comms-store ~/savi-comms-venv/bin/python main.py
Its log includes
disk encryption confirmed for the store. If it stops with
Savi Comms will not start: the disk holding its store,
see "The encrypted disk" above.
In a second terminal, check its health:
SAVI_COMMS_ROOT=~/savi-comms-store sh -c '. ~/savi-comms/scripts/broker_address.sh && broker_curl -s "$(broker_url)/health"'
How you know it worked: "status":"ok"
and "secrets_ok":true.
Why the check reads this way. A new installation
encrypts the connection to its broker, even on this computer, so the
broker answers https on port 8421 with a certificate of its
own, and a plain http:// address on that port gets no
answer. This line asks the program where its broker is and which
certificate to trust, the same way the program's own scripts ask, so it
works whether the connection is encrypted or not. A new installation
also keeps a plain way in for this computer only, on the next port up:
curl -s http://127.0.0.1:8422/health.
Then check that both agents are ready to work:
SAVI_COMMS_ROOT=~/savi-comms-store sh -c '. ~/savi-comms/scripts/broker_address.sh && broker_curl -s "$(broker_url)/agents/sentry/liveness"'
SAVI_COMMS_ROOT=~/savi-comms-store sh -c '. ~/savi-comms/scripts/broker_address.sh && broker_curl -s "$(broker_url)/agents/orchid/liveness"'
How you know it worked: each answers
"live":true with an empty failures list. If
an agent is not live, the failures list says what is
missing and what that breaks.
Stop the broker with Control-C before Step 4.
So Savi Comms keeps running after you close the terminal and starts again after a restart, install it as a service.
On macOS:
cd ~/savi-comms
scripts/install_macos_service.sh --company acme --store ~/savi-comms-store --workspaces ~/agent-workspaces
Use your own company's short name after --company, in
lowercase. The script finds ~/savi-comms-venv by itself,
checks the service definition before loading it, and links the
comms command into ~/.local/bin. If that
folder is not on your path, the script says so; add
~/.local/bin to your path in your shell profile. Its logs
go to ~/savi-comms-store/logs/.
On Linux:
cd ~/savi-comms
bash scripts/install_linux_service.sh --store ~/savi-comms-store --workspaces ~/agent-workspaces
loginctl enable-linger "$USER"
The script finds ~/savi-comms-venv by itself and links
the comms command into ~/.local/bin. Both
commands need systemd; on a Linux system without it, the service script
stops with systemctl not found. The last command keeps
Savi Comms running after you log out; if it refuses, run it with
sudo in front. To read its log, run
journalctl --user -u savi-comms.
Who the agents run as. Every agent session runs as
the same account as the service: on a Mac, the account that ran the
script above, whose LaunchAgent the service is; on Linux, the account
whose user service it is. To the operating system an agent session is
that account, so it can open the files that account can, including the
ones the broker keeps to itself. What stands between an agent and those
files is its gate, which checks every action against your rules. Woken
agent sessions run without Claude Code permission prompts. Every tool
call is checked by the Savi Comms gate, which refuses by policy. On a
Linux server, agent sessions can run under an account of their own
instead; docs/INSTALL-SERVER.md, Part 9, sets that up.
Which Claude sign-in they use. Each session uses
the sign-in you make in Step 5, which the installation keeps in its
encrypted store. Until you make one, it uses the Claude Code sign-in of
the account the service runs as: on a Mac, the item named
Claude Code-credentials in that account's login Keychain;
on Linux, ~/.claude/.credentials.json. On a Mac, Claude
Code itself may also keep a copy of a session's sign-in in that login
Keychain, in items whose names start
Claude Code-credentials-.
Where it runs from. On both, the script copies the
unpacked folder to
~/savi-comms-store/releases/<version>, points
~/savi-comms-store/releases/current at it, and the service
starts that pointer. An update unpacks the next version beside it and
moves the pointer. Leave ~/savi-comms and
~/savi-comms-venv where they are.
A machine whose service was installed from an earlier copy of this
guide may still run ~/savi-comms and its
.venv, so an update installs and does not take effect. Run
the service script once more from the newest version, with the same
options and --venv naming the Python environment you
already have.
How you know it worked: the health check from Step
3 answers "status":"ok" with no terminal open.
The console is where you run the installation, and its Home page is where the installation signs in to Claude.
On the machine that runs Savi Comms, ask for a sign-in link:
~/.local/bin/comms console --print-only
It prints a link starting with
https://localhost:8421/console?c= (http:// on
an installation whose connections are not encrypted). Open it in your
browser within ten minutes. It works once. Until company sign-in is
connected, the console then offers to set up a passkey, so you can sign
in again without a link.
If you are working from another computer, use the
two lines the same command prints after the link. The first is an
ssh -N -L line, which ends with your user name and this
machine's name. Run it on the other computer and leave it running; if
the name at the end does not work, put in the address you reach this
machine by. The second is the link to open in that computer's
browser.
On an installation that encrypts its connections, the tunnel carries the plain way in, port 8422, rather than 8421. The installation's certificate is trusted on this machine only, so the encrypted link would show a certificate warning on the other computer. The SSH tunnel encrypts the connection between the two computers by itself.
Then sign the installation in to Claude:
How you know it worked: Home says
This installation is signed in to Claude. A sign-in made
this way lasts a year. The same step offers An Anthropic API
key instead, if your company pays for Claude that way.
The Decision Desk is where agents bring you the decisions only a person may make.
pbcopy < ~/savi-comms-store/tokens/dana.token. On
Linux, run cat ~/savi-comms-store/tokens/dana.token and
copy its one line.https://127.0.0.1:8421/desk
(http:// on an installation whose connections are not
encrypted).How you know it worked: the box disappears and you see your agents across the top. The browser remembers the token, so you sign in once.
Your security and ways-of-working agents look after the installation. Add the agents that do your company's work:
SAVI_COMMS_TOKEN=$(cat ~/savi-comms-store/tokens/dana.token) ~/.local/bin/comms seats add ada --folder "Ada - Operations Analyst"
How you know it worked: the answer shows
"live": true and an empty failures list. Ada
has a mailbox, her own token, her folder, your rules and her gate, and
the broker did not need a restart.
The folder. Every agent's folder sits directly
under the workspace root you named in Step 2,
~/agent-workspaces. Its name is the agent's name, then
-, then its role: Ada - Operations Analyst
makes an agent called Ada whose role is Operations Analyst, and the
agent is told that role. Choose the folder name with that in mind.
Savi Comms writes two things into each agent's folder.
SAVI_COMMS.md is the agent's guide to the mail system, and
it is written again whenever the agent is set up again.
CLAUDE.md gets a block between the lines
<!-- savi-comms --> and
<!-- /savi-comms -->. A folder with no
CLAUDE.md gets a new one. A folder that already has one
keeps all of its own text, and the block is added at the end; setting
the agent up again replaces only that block.
Your token goes on the same line as the command, and only for that
command. If you set it for the whole terminal instead, every later
comms command in that terminal acts as you, including ones
you run from an agent's folder.
Only you can add, remove or reassign an agent. An agent that tries gets refused.
To see how many seats you are using, run
SAVI_COMMS_TOKEN=$(cat ~/savi-comms-store/tokens/dana.token) ~/.local/bin/comms seats.
Your first two agents (security and ways of working) are listed as
included and are not in the count.
Use a separate terminal window for each agent. A terminal remembers the first agent it acts as and refuses to act as a different one, so one agent cannot be made to act as another by changing folders.
Open a new terminal window for Sentry, and send a message from Sentry's folder:
cd "$HOME/agent-workspaces/Sentry - AI Security"
~/.local/bin/comms send --to ada --subject "Welcome to Acme" \
--body "Ada, this is Sentry. Confirm you can read your mailbox."
Open another new terminal window for Ada, and read her mailbox from her folder:
cd "$HOME/agent-workspaces/Ada - Operations Analyst"
~/.local/bin/comms inbox
How you know it worked: the send answers
"state": "delivered", and Ada's inbox lists the message.
Notice that you never told comms who was sending. It knows
from the folder it runs in, and an agent cannot send as anyone else.
This message does not wake Ada. It is sent at normal priority and
asks nothing of her, so the send also prints a line starting
comms send: warning: that says it waits. Reading her
inbox by hand is how this step checks the mailbox without waiting.
"When your agents wake", below, gives the whole rule.
When an agent needs something only you may approve, it files an ask on the Desk. File one as Sentry, in Sentry's terminal window from Step 8, to see the whole path:
cd "$HOME/agent-workspaces/Sentry - AI Security"
~/.local/bin/comms desk-ask \
--context "Ada joined Acme today as an operations analyst. She needs read access to the shared reporting folder to build the weekly summary." \
--question "Should Ada get read-only access to the reporting folder?" \
--option "Grant read-only for 30 days | covers the first month, then reviewed" \
--option "Grant read-only with no end date | less review work, wider standing access" \
--option "Hold access | Ada drafts templates only" \
--recommend 1
Refresh the Desk. The ask is waiting, with the agent's recommendation marked. Every ask gives you a last option, None of these are right, where you type what you want instead.
Click the option you choose. The page offers you an optional note to send along with your decision; type one or leave it blank and confirm. The ask leaves your queue, the agent receives your decision, and the decision is recorded in the Change Log.
When you approve access for a limited time, register it so it is
tracked until it is removed. Run this in Sentry's terminal window,
since Sentry filed the ask and removes the access when the time is up.
The ask id is the "id" value comms desk-ask
printed when Sentry filed the ask, and the broker refuses a grant whose
ask you have not decided:
~/.local/bin/comms grant add --principal ada --scope "read-only, shared reporting folder" \
--expiry 2026-10-16T17:00:00-05:00 --teardown-owner sentry \
--verification "ada lists the folder and is refused" \
--ask-id <the id comms desk-ask printed>
The Grants tab shows it until the access is removed.
Agents wake on their own when mail asks them for something, using Claude Code on this machine and the sign-in from Step 5.
To confirm it, send an agent a message that asks for work, from Sentry's terminal window as in Step 8:
cd "$HOME/agent-workspaces/Sentry - AI Security"
~/.local/bin/comms send --to ada --asks-work --subject "First task" \
--body "Ada, reply to Sentry with one line saying you are set up."
How you know it worked: the send prints
comms send: Wakes the recipient now. Then look in the
broker's log for a line that starts with Waking and names
that agent. On macOS the log is in
~/savi-comms-store/logs/; on Linux, run
journalctl --user -u savi-comms. If the log instead
mentions the Claude Code binary, Claude Code is missing or installed
somewhere unusual; tell Savi Comms where with the
SAVI_COMMS_CLAUDE_PATH setting.
An agent costs nothing while it waits, and Savi Comms starts a
session for it only when there is something to do. Every message's
sender sees which of these applied: comms send prints one
line saying the message wakes the recipient now, waits for the digest,
or is held, and why.
Mail.
--reply-required
or --asks-work wakes the recipient when it arrives.A message is worth a session when it is urgent, needs a reply, asks for work, comes from you, or carries a Desk decision, or when the recipient's task file holds work that is working or queued. A message that is none of these starts no session, at the digest either. It stays unread, and the agent reads it at its next wake.
A message an agent flags --requires-human goes to you
instead of waking anybody. A message from an agent is also held, and
sent to you instead, when its conversation has passed 10 hand-offs
between agents or 12 messages, or has woken agents 16 times. Your own
messages are never held this way. To reach the agent again, start a new
conversation.
Your terminal. While you work in an agent's folder
from a terminal (see "Working as an agent from a terminal"), Savi Comms
holds that agent's woken sessions until 5 minutes after the terminal was
last used, so two sessions of one agent never work at once. The
installation setting SAVI_INTERACTIVE_WINDOW_SECONDS
changes the 5 minutes, up to an hour.
Work an agent starts on its own. By default agents start only when mail arrives. In the console, Settings has a group called Working on their own: a switch, Agents start work on their own; the days and hours; and Times a day agents may start on their own, across all agents, which is 0 when you install. Set that number above 0, and during those days and hours (weekdays, 08:00 to 18:00, when you install) Savi Comms starts agents whose task files hold work, first the one the most other agents' work is waiting behind, at most 3 times a day for any one agent.
Those starts happen only while you are present. Savi Comms counts
you present for 90 minutes after you last used the console or the
Decision Desk, ran comms as yourself, or used any agent's
terminal. If it cannot tell, it counts you present. While you are
away, the clocks that remind agents about unanswered mail, and escalate
it to you, pause as well. Mail still wakes agents while you are away,
and so does a schedule you give an agent on its
Configuration tab in the console.
How long a session runs, and how many at once. A
woken session on Claude is stopped after 30 minutes. At most two
sessions on Claude run at once, across the whole installation rather
than per agent; a wake that finds both in use waits for one to finish.
Both are installation settings: SAVI_COMMS_WAKE_TIMEOUT, in
seconds, and SAVI_COMMS_CLAUDE_SLOTS, each read when the
broker starts, so restart it after changing one. More slots finish work
sooner and use your Claude allowance faster. Each agent runs one woken
session at a time, and a wake that arrives while it works joins that
session. The installation setting SAVI_WAKE_SESSION_CAPS,
a JSON object naming an agent and its number, for example
{"ada": 2}, lets that agent run up to 4 woken sessions at
once, each still waiting for a slot like any other. The agent's
Configuration tab says which applies. A session on a
model of your own (see "Models of your own") is allowed 24 hours and
waits for no slot.
You can do an agent's work yourself, from a terminal, in its folder.
comms run inside an agent's folder acts as that agent: it
reads that agent's mail and sends as that agent, as Step 8 showed. A
terminal remembers the first agent it acts as and refuses to act as
another, so use one terminal window for each agent.
While you work this way, Savi Comms holds that agent's woken sessions until 5 minutes after the terminal was last used, so you and a woken session never answer the same mail at once. Mail that arrives meanwhile waits unread, and the agent is woken for it once the terminal has been quiet that long.
A Claude session as the agent. Savi Comms places a
launcher, savi-claude, in the store's bin
folder each time the broker starts:
~/savi-comms-store/bin/savi-claude with the store this
guide uses. Put that folder on your PATH, or call the
launcher by its full path:
~/savi-comms-store/bin/savi-claude ada
It starts Claude Code in that agent's folder, working as the agent.
The session gets the agent's connector secrets from the encrypted store,
the same ones a woken session of that agent gets, so a
${NAME} in the folder's .mcp.json is filled
in. Any Claude Code option after the agent's name passes straight
through, for example savi-claude ada --continue. Before
Claude Code opens, the launcher prints the names of the secrets it
handed over, never their values, and which Claude sign-in the session
runs on:
/login
once in the session. That sign-in stays in a folder of the session's
own, inside the store, and never changes the installation's
sign-in.The launcher runs only as the account Savi Comms runs under. Run as any other account, it names the account to use and starts nothing.
What the console shows of it. Each agent's page in
the console has a Terminal tab, for an administrator:
everything the agent's sessions said and did, oldest at the top, with
earlier sessions above. Each session is headed Woken
session or Terminal session, with when it
started and, while it runs, Running now. A terminal
session shows there when it runs as the account Savi Comms runs under,
whether savi-claude started it or claude was
started in the agent's folder. The box at the bottom, Write
to and the agent's name, sends the agent a message and wakes
it; while a terminal is in use, the tab says the message waits until
that terminal is quiet. Stop ends a woken session. A
terminal session is stopped from its own terminal.
Work started in a terminal also shows where woken work does, for an administrator. The agent's card on Home carries a line naming the last command, search or file the agent worked on, and the What it is running panel at the top of the agent's Dashboard tab lists its last fifty, newest first. The agent's gate records each tool call it lets through, whichever session made it, so a terminal session's calls are on that line and in that list, and the panel reads Working now while the terminal is running them.
On a Linux server with workspace access, each person assigned to an
agent works in its folder over SSH under their own account;
docs/INSTALL-SERVER.md, Part 9.5, sets that up.
An agent you add cannot use git, and that is on purpose: most agents never touch a repository. When an agent's work is code, you turn that on for it in the console.
1. Open the console, then Agents, and click Open beside the agent. 2. Click the Configuration tab. 3. In the Writing code card, tick This agent writes code. The console asks you to confirm it is you, the same way it does for any change to an agent.
How you know it worked: the card says the agent writes code now, and the agent's next git command runs. Nothing needs restarting.
That one switch does three things together. The agent may use git in its own repositories. It is given the tier that developers hold, unless you had already given it a higher one. And every push or deploy it makes waits for your yes on the Desk, so nothing it writes reaches a repository without you.
The card then shows the next step: give the agent a GitHub App of
its own, so its work on GitHub is recorded as its own and never as
yours. The guide that came with Savi Comms,
docs/GITHUB-AGENT-IDENTITY.md, walks you through it, and
the card shows the one command its last step runs, with the agent's name
already filled in.
To undo it, untick the switch. Git closes to the agent again, its tier goes back to what it was before, and it leaves the push gate.
Only you, the owner, can change this switch. An agent that asks for git is asking you to make this change.
Closing a Desk decision with a commit. When an
agent's commit carries out a decision you made on the Desk, the agent
writes Executes: <ask id> among the commit's trailer
lines, and Savi Comms closes the ask once that commit is on the
repository's main branch. Savi Comms looks in every git
repository directly inside ~/Developer, at
origin/main as that folder last fetched it; it does not
fetch for you. If the repositories are somewhere else, name their
folder with the installation setting SAVI_COMMS_CODE_DIR,
or list them with SAVI_COMMS_WORK_REPOS, as a JSON list of
paths. An ask whose commit is not seen stays open.
comms desk-executed <ask id> records an ask carried
out only when Savi Comms can see the work itself: that commit on
origin/main, the ask's grant used or closed, a release
naming it, or, for a step only a person can take, that person's own
message. A sentence or a commit number typed by an agent is not enough,
so fetch the repository and the ask closes on its own.
An agent you let read a repository's history without writing code may
run a short list of git commands that only read, such as
git log and git status. You choose that list
under Settings, then Agents and
models, then Reading code history. Only
commands that read are offered, so nothing on it can change a
repository.
Savi Comms runs on the machine itself, so most of it carries on without the internet. What changes:
Your agents start on the generic policy pack in
packs/starter/. To write your company's rules, copy that
folder, put your rules into rules/*.md, list them in the
pack's manifest, and install it:
cd ~/savi-comms
~/savi-comms-venv/bin/python -c "from services.policy_packs import install; print(install('packs/yours'))"
Every earlier version of every rule file is kept, archived by its hash. To see which layer a rule lives in, and so who may change it, ask the broker:
SAVI_COMMS_ROOT=~/savi-comms-store sh -c '. ~/savi-comms/scripts/broker_address.sh && broker_curl -s "$(broker_url)/layers/status-vocabulary"'
Some rules belong to a job rather than to the whole installation. A
pack whose manifest names applies_to_role lands on the
agents carrying that role and on nobody else, and an agent then reads
your installation's rules first and the role's rules after them:
~/savi-comms-venv/bin/python -c "from services.policy_packs import install_role_pack; print(install_role_pack('packs/savi-maker-developer'))"
A role pack places each of its files in one of two layers, and the
manifest says which. A file in the product, fixed layer
ships with the product and cannot be changed here; an install or an edit
that would change one is refused and tells you why. A file in the
customer, tunable layer is yours:
~/savi-comms-venv/bin/python -c "from services.policy_packs import edit_role_rule; print(edit_role_rule('savi-maker-developer', 'working-with-your-person.md', open('mine.md').read()))"
An agent picks up the pack at its next wake, and gives it back when it stops carrying the role.
Your agents run on Anthropic's models until you say otherwise. An installation that would rather run models on a machine it owns adds that machine as a gateway, in Settings, under Models you bring yourself:
1. What you call the machine, such as Studio box. 2.
Its address, such as http://192.168.1.40:11434. 3. Which
API it speaks. Ollama, LM Studio and vLLM all speak the OpenAI API, and
Savi Comms translates for them with nothing else installed. An endpoint
already speaking the Anthropic API is reached directly. 4. The models
it serves, such as qwen3:32b, gemma3:27b, qwen3:4b. 5. A
model for each of the three jobs: hard work, the day to day work, and
short jobs. The same model in all three is fine when that is what the
machine serves. Every job needs one, because an agent whose short jobs
have no model on your machine would send those back to Anthropic without
telling you.
The machine is asked whether it is there as soon as you add it, and the answer is on the screen. Its models then appear on every agent's screen beside Anthropic's, and an agent set to one of them runs entirely on your machine.
Savi Comms can carry your agents into Microsoft Teams, Slack or
Google Chat, and each card for them is in the console under
Settings, then Apps and connections.
All three deliver messages to an address on the public internet, over
https, and an installation on one machine has none, since nothing on it
listens on your network. To use one of them, install on a server
instead: docs/INSTALL-SERVER.md sets one up with a public
address, then connects Teams in Part 6, and Slack and Google Chat in
Part 6.3.
To run the broker in a container instead of on the machine, the build
and run lines are at the top of the Dockerfile. A
container has no Claude Code sign-in of its own, so point
SAVI_CLAUDE_CREDENTIALS_PATH at a mounted credentials
file. Your agents' model usage bills to your own Anthropic account, and
Savi Comms never sees your credentials. Model traffic goes from the
session straight to Anthropic, except for an agent you put on a gateway
of your own that speaks the OpenAI API: that traffic passes through
this installation's own bridge, on this machine, so that it can be
translated.
Everything in Steps 2 to 9 was written to a tamper-evident audit chain. Check that it is intact at any time:
SAVI_COMMS_TOKEN=$(cat ~/savi-comms-store/tokens/dana.token) ~/.local/bin/comms admin verify
How you know it is intact: "ok": true.
The check finds an entry changed or removed anywhere inside the record.
It cannot tell whether entries were cut from the very end, or whether
the whole record was rewritten from the start, which is one more reason
to keep backups.
Back up two things together: the store folder,
~/savi-comms-store, and the root key. On macOS the root
key is the Keychain item named savi-comms-root-key. On
Linux it is root.key inside the store. A backup of the
store without the root key cannot be opened.
scripts/set_secret.py, in the folder you unpacked,
lists, sets and deletes what the encrypted secret store holds from this
machine's own command line, and its list prints names only,
never values. scripts/encrypt_store.py encrypts the mail,
the record and the index of a store made before stored data was
encrypted by default: run it with --dry-run first, then
--apply with Savi Comms stopped, and
--rollback puts the store back.
| What you see | What it means | What to do |
|---|---|---|
Savi Comms will not start: the disk holding its store ... is not encrypted |
The store is on a disk Savi Comms cannot confirm is encrypted, or the encrypted volume is locked | Unlock the volume, or move the store onto an encrypted one. See "The encrypted disk" |
| The health check does not answer | The broker is not running | On macOS, rerun Step 4. On Linux, run
systemctl --user status savi-comms. Without a service,
start it by hand as in Step 3 |
"secrets_ok": false |
The secret store could not be read, most often because the root key is missing or comes from a different backup than the store | Restore the root key from the same backup as the store |
| An agent is not live | Its failures list names the missing piece |
Fix what the list names, then check liveness again |
| The console link does not open from another computer, or shows a certificate warning | The SSH tunnel is not running, forwards a different port, or the encrypted link was opened instead of the one printed for another computer | Run ~/.local/bin/comms console --print-only again,
start the ssh -N -L line it prints, and open the link it
prints for another computer |
| The console link has expired | It lasts ten minutes and works once | Run ~/.local/bin/comms console --print-only again |
| The Desk asks for your token again | The browser lost it | Repeat Step 6 |
| The broker log mentions the Claude Code binary | Claude Code is missing or not where Savi Comms looks | Install Claude Code, or set SAVI_COMMS_CLAUDE_PATH |
DENIED by the git-access gate |
The agent is not set up to write code, so git is closed to it | If the agent's work is code, open it in the console and turn on This agent writes code (see "An agent that writes code"). Otherwise this is by design; the agent stops and asks you instead |
| An agent says it has no tier, or every action is refused | Its tier was never set | For an agent that writes code, turning on This agent writes code sets its tier. See "An agent that writes code" |
DENIED by the push gate |
Every push and deploy by an agent that writes code waits for your yes | Answer the agent's ask on the Desk; the push then runs. See "An agent that writes code" |