Upgrading
Diese Seite gibt es nur auf Englisch — sie gehört zu den Betriebsanleitungen, die wir bewusst einsprachig halten.
Migrations run by themselves: covey serve applies what is missing at startup,
under an advisory lock, so several instances against one database do not race.
An upgrade is therefore usually "pull the new binary, restart".
This page lists the upgrades where that is not enough — where something has to be built, backed up or decided beforehand. Newest first.
Ask your own installation first
covey doctor
Or, in the browser: Platform → Diagnostics — the same checks, plus which agent configurations should catch up after the upgrade.
It reads and changes nothing, and it answers the questions this page can only ask in general: which images your agents need and whether they are on this host, what your database is about to migrate, where your blocks lie and how big they are. It exits non-zero when something would stop an agent from working, so it can stand in a deploy script before the restart.
The short version of an upgrade is therefore:
make upgrade # binaries + both sandbox profiles
covey doctor # what is still in the way here
Voices per occasion (migration 0122)
Nothing to do by hand; one thing to decide. An agent's one voice moves into
its Customers and Publications slots, and its Chat slot stays
empty: the team chat used only that voice's chat tone before, never its card,
and it keeps doing exactly that — the tone (du/Sie, emoji) still comes from
the customers voice until somebody names a chat voice. If colleagues should
hear a voice of its own in the chat, name one on the agent (Settings →
Voices) or as the organisation's default (Administration → Default
voices). agents.voice_id stays in the table, unread, so a rollback past
0122 still works. See Voices.
After 0.11.0 — egress is enforced by default
COVEY_EGRESS_ENFORCE now defaults to true. With the docker provider every
sandbox reaches only the hosts on its agent's allowlist: the organisation's
base list (seeded with api.anthropic.com), the assigned templates and the
agent's own hosts. Before, the lists were kept but applied only when an
operator switched enforcement on — and neither compose file passed the
variable, so a compose installation ran open whatever its .env said.
If your agents only ever talk to hosts that are already on their lists,
there is nothing to do. Everything else is refused after the restart: a
target system (GitLab, Jira, Zendesk, a mail server — the action proxy runs
inside the sandbox, so those calls leave from there too), a package registry,
a model endpoint other than api.anthropic.com. The agent sees a refused
connection; the refusal is in the egress monitoring, not in its prompt.
1. Check the allowlists before the restart
GET /api/v1/egress— whether enforcement is on (enforced), the organisation's base list, and the additions fromCOVEY_EGRESS_ALLOW.- Per agent: Settings → Egress on the agent page — assigned templates and own hosts. That is where a target system's host belongs.
- Egress in the side menu: the monitoring. While enforcement is off it logs
nothing, so it cannot tell you what an agent used to reach; the agent's
ACCESS.mdand its target-system secrets (gitlab_url,jira_url, …) can.
Changes to the lists take effect within about 15 seconds, without a restart — they can also be made after the upgrade, while watching the monitoring.
2. Compose installations: one new port
The proxy runs inside the covey container, and a sandbox is a sibling
container: it reaches the proxy only through a port the host publishes. Both
compose files now set COVEY_EGRESS_LISTEN_ADDR=:8495 and publish
8495:8495. Take the new compose file, and make sure 8495 is free on the host.
The port answers every request without a sandbox's own token with 407; a
firewall in front of it costs nothing.
A binary started directly on the host needs nothing: the proxy binds a free
port there, and the sandboxes reach it through host.docker.internal.
covey serve warns and covey doctor reports a blocking finding when covey
runs in a container with the proxy on a free port — the one configuration in
which enforcement lets nothing out at all, not even to the model.
3. Or keep the old behaviour, explicitly
# .env of a compose installation (the compose file passes it through)
COVEY_EGRESS_ENFORCE=false
The lists stay and are not applied. covey serve says so at every start and
covey doctor reports it, so an open egress is a decision that is written
down, not a default nobody chose.
Two limits of what "enforced" means, unchanged by this release: the default
mode is cooperative (HTTP(S)_PROXY in the container — an agent that ignores
it gets past; COVEY_EGRESS_ISOLATION=network closes that), and it applies to
the sandboxes this control plane starts itself, through its built-in runner.
To 0.6.0 — three plugins move to the catalogue
zammad, vulndb and k8s are no longer compiled into the binary. They are
WebAssembly modules in the plugin catalogue
now, on the same footing as anybody else's plugin.
If you use none of the three, there is nothing to do. The other nine target systems are unchanged.
1. Install the ones you use — the agents keep their access
Store → Catalogue → the plugin → Install. Do it once per installation, after the upgrade.
What survives the move on its own: the plugin row, its enablement per
organisation, the stored secrets, and every agent's ACCESS.md. Only the code
now arrives from the catalogue. An agent whose ACCESS.md names zammad finds
it again as soon as the module is installed, with the credentials it always had.
Between the upgrade and that install, an agent that needs one of the three cannot use it. Do the install in the same maintenance window rather than the next one.
Installing needs the catalogue to be reachable. An air-gapped installation should fetch the three
.wasmfiles from the pack's v0.7.0 release beforehand and install them from file — the digests are in the release notes, and covey checks them either way.
2. Two settings are gone, and one of them silently
| Was | Now |
|---|---|
COVEY_ZAMMAD_REPLY_TYPE=web |
the reply_type parameter on the reply action |
COVEY_ZAMMAD_INTAKE_GROUPS="Support L1" |
a group condition on the Zammad trigger (ops-zammad.md) |
vulndb_token (an NVD API key) |
nothing. NVD answers at its anonymous rate limit |
The middle one is the one to act on. A module gets no process environment, so an allowlist that used to be honoured is not merely ignored — every group's tickets now arrive. If you relied on it, put the condition in the trigger before the upgrade, not after: the filter belongs there anyway, because a trigger that does not fire costs nothing while a webhook covey discards has already been sent, signed and verified.
vulndb_token is dead configuration and can be deleted. It used to raise NVD's
limit from 5 requests per 30 seconds to 50; a module is never handed the
credential for a host it merely declared, and all six of vulndb's sources are
declared ones. advisory reports a rate-limited NVD as a note beside an
otherwise complete result, the same as any source that does not answer.
3. Kubernetes: the cluster CA moves out of the action
k8s used to take the certificate as an action parameter,
{{secret:k8s_ca}} in ca_pem. That parameter is gone. Store the certificate
as the secret k8s_ca and covey builds the trust store from it.
This is a fix as much as a move: a certificate passed per action travelled through the model's context, the guard-rail subject and the recording of every single call.
4. Rolling back
Going back to 0.5.x restores the three as compiled plugins, so nothing is lost — the installed modules simply stop being reached, because a compiled plugin and a stored one share a name and the compiled one wins. Uninstall them from the store if you intend to stay on 0.5.x, or the store will show a plugin that is present and not the one being used.
An 0.5.x instance cannot install the modules in the first place: the catalogue
entries declare covey_min_version: 0.6.0 and the install is refused with that
sentence.
To the runner release (spec/16)
Six migrations (0051–0056), a split sandbox image, and a new store next to the database. Two things need action before the first agent wakes.
1. Build the second sandbox image — otherwise no agent wakes
The sandbox image used to be one image with everything in it. It is now two
profiles (spec/16, "Sandbox images per agent"):
| Profile | Contents | For whom |
|---|---|---|
base (covey-sandbox:latest) |
coveyd, Node, Python, git, chromium, ripgrep | support, mail, QA, research |
dev (covey-sandbox-dev:latest) |
+ PHP, JDK, fvm, uv, node-gyp toolchain |
developer agents |
Migration 0052 puts every existing agent on dev — deliberately, because
that is what the old image contained, and an upgrade must not pull a developer
agent's toolchain out from under it. But covey-sandbox:latest now builds the
lean base image, so the image your agents are pointed at does not exist yet:
make sandbox-images-pull # pulls both from the published package (minutes)
make sandbox-images # or builds them here: base, then dev on top of it
The project publishes every workplace as one package on every push and every
release — ghcr.io/benjaminledel/covey-sandbox, the variants as a tag prefix
(base-latest, dev-latest, dev-flutter-latest, base-v0.4.0, …). Take the tag your binary is:
the image carries the coveyd that talks to this control plane.
Without it every wake fails with sandbox image "covey-sandbox-dev:latest" is
missing. covey doctor names it before the restart, with how many agents are
waiting on it; the startup check and the agent overview say it afterwards.
Running covey as a container? Then there is no checkout and no make, and
the image has to come from somewhere else. Two ways, both a line in the
deployment's .env:
# a) the published image — nothing to build, the host pulls it on the first wake
COVEY_SANDBOX_IMAGE=ghcr.io/benjaminledel/covey-sandbox:base-latest
COVEY_SANDBOX_IMAGE_DEV=ghcr.io/benjaminledel/covey-sandbox:dev-latest
# b) the image you already have. The one from before the split carried PHP, JDK,
# fvm and uv — it IS the dev workplace, whatever it is tagged.
COVEY_SANDBOX_IMAGE_DEV=covey-sandbox:latest
# c) your own registry (the deploy pipeline pushes both images there and pins
# them; this is what an instance deployed from CI gets)
COVEY_SANDBOX_IMAGE_DEV=registry.example.com/covey/sandbox-dev:<tag>
docker compose up -d afterwards. The variable exists for every profile —
COVEY_SANDBOX_IMAGE_<PROFILE> — and the startup check names the one that fits
the missing image.
Afterwards, move the agents that do not need a toolchain to base (agent →
settings → workplace). That is what the split is for: a mail agent should not
carry a JVM. It is a decision per agent and not one a migration makes.
Since then there are role workplaces beside dev — dev-flutter,
dev-php, dev-web — for agents whose field is settled, and dev-full for an
installation that would rather not split at all. Nothing moves an agent there
either; dev keeps working. ops-workplaces.md says
what each contains and what one costs.
2. Back up the new block store
The home store is on by default. After every job an agent's home is synced into it as a whole and materialised from it on wake — that is what makes a home survive the loss of its working copy.
The blocks live under <COVEY_DATA_DIR>/blocks.
This directory needs backup like the database. For 99 % of its content that would only mean downloading toolchains and cloning repos again. For the rest it would not: of a measured 7.1 GB developer home, 48 MB exist nowhere else, and they lie scattered all over it. It is a cache in its function, not in its need for protection.
What it costs, so nothing is a surprise:
The first falling-asleep of each agent is slow. The first sync of a grown home is a full pass — 7 GB. Everything after that is a delta of megabytes.
Disk. The store holds what the runner also has. With a single agent that is close to a doubling; from the second developer agent onwards the deduplication of the identical toolchain caches turns it into a saving.
COVEY_HOME_STORE=falseturns it off. Then homes stay directories as before: no snapshots, no rollback, and unrecoverable when lost.COVEY_HOME_EXCLUDES=".dartServer,…"leaves paths out, and it now has a considered default instead of an empty one: the scrap class —__pycache__,.dartServer,*.pyc,*.tmp, the pip and Playwright caches, an agent's hand-built apt tree. Nothing but the tool reading them recreates those, so leaving them out costs nothing. Three kinds of pattern are understood: a path from the home root (repos/scratch), a NAME at any depth (__pycache__), and a glob on the file name (*.pyc).Package caches (
.npm,.gradle,.pub-cache) are deliberately NOT in the default. Leaving them out saves store and scan time and costs a re-download on the next host — a good trade for an agent that moves between runners, a bad one for an agent that always lands on the same. That decision is yours; setting the variable replaces the default entirely, andCOVEY_HOME_EXCLUDES=noneswitches it off (everything is synced, as before).COVEY_HOME_TIDY_ABOVE_GB=5is the size above which the platform asks an agent to tidy its own home — a backlog task with the figures in it, not a sweep. What is scratch and what is memory is known only by the agent that created it.0switches it off.COVEY_HOME_TIDY_ABOVE_ENTRIES=200asks the same when that many entries lie directly in the home, whatever their size.0switches it off.From this version on a run starts in
~/scratch/<task-id>/instead of the home root. Task directories no run has entered for seven days are removed; nothing outside~/scratchis touched, and the heap an agent built before the upgrade stays until the housekeeping task asks it to tidy.
Retention (the last N per agent, a maximum age) is set under Runners, with a preview before anything is removed.
3. What happens by itself
Nothing to do for these; they are listed so the log lines make sense.
- Migrations 0051–0056 are applied at startup.
- The egress proxy no longer gets the database URL. It fetches its allowlist from the control plane with a runner token now — it is an enforcement point, not a database client. The control plane sets that up itself.
- The internal network and the proxy container carry the runner's identity
(
covey-egress-internal-<runner>). The instance-wide ones from before are removed at the next start; you will see a log line saying so. This also closes a gap that existed before: with more than one organisation, all sandboxes hung off the same internal segment, and--internalcuts the way out, not the way sideways. - Every sandbox now starts through the runner protocol, on a built-in runner the control plane runs itself — one per organisation. Nothing to install, and the setup does not change.
4. Optional, and only if you want it
- A second host:
make runner, thencovey-runner registeron that machine. Seeops-runner.md. The built-in runner keeps running beside it and takes over when the new host cannot — whoever wants no compute on the control plane's machine pauses the built-in runner in the runner view. (It used to stand down by itself as soon as a runner was registered; that rule left more than one instance with a host on paper and no data plane in fact.) TLS becomes mandatory at that point: astart_sandboxcarries daemon tokens. - Blocks in an object store:
COVEY_BLOB_STORE=s3(seeops-runner.md). Switching backends does not move the blocks — copy the directory over first, or accept that every home is rebuilt from scratch on its next wake.
Rolling back past earlier releases
Everything before this release upgraded without a manual step: no environment variable was ever removed or renamed, and every migration carried its own data over. One boundary is worth knowing about anyway — v0.3.0 cannot be rolled back without loss. Migration 0048 moved credential policy from the secret key up to the runtime; going back restores the columns but not which runtime had set which cap, because two runtimes can share a key. The migration says so itself.
Rolling back
The migrations have down files, but rolling back past 0052 leaves the agents
on an image column that no longer exists while the content of
covey-sandbox:latest has changed. If you have to go back, rebuild the old
image from the previous tag as well.
The block store is unaffected by a rollback: it is content-addressed, and a version that does not know it simply does not read it. It also does not clean it up — the directory stays until a newer version sweeps it.