Cloud agentic workspaces
Provisioning agentic workspaces in the cloud
One installation on your laptop is the desktop app. A fleet of them - one per person, per project, or per tenant - running on machines nobody sits at, reached from a browser, is this page.
It is the same program either way. What changes is that you are now the person who gets paged.
Read The architecture first. This page is what to build out of it, in what order, and what to watch afterwards.
The unit
An agentic workspace is one mechabedo installation on one beam slot with one volume. It is the smallest thing worth provisioning and the largest thing worth treating as disposable-with-a-backup. The unit may carry its own beam host: with MECHABEDO_BEAM_EMBEDDED=on the container runs one inside the server on loopback, keeps its Bedos on it, and enrolls your desktop onto it with a code (Joining with a code); then nothing below about the host applies to it.
Drawing the diagram…
Four things, and they are not interchangeable. Confusing the credentials is the most common way to get this wrong, so:
| Secret | Holder | Opens |
|---|---|---|
BEAM_TOKEN | You, the operator, and nowhere else | Any slot, the slot listing, the dataset routes |
A BEAM_SLOTS entry | One installation | That slot's tunnel, and the sync tenant slot:<name> |
An enrollment code (MECHABEDO_BEAM_ENROLL) | One installation, once | Redeemed at its first start for a slot the host mints and that slot's token; spent after that |
| That installation's own login | Whoever signs in to it | That installation, past its own auth gate |
Why it is shaped this way
The alternative - give every workspace a public hostname, a certificate and an ingress - is what most of this replaces.
| Inbound per workspace | One beam host, outbound dials | |
|---|---|---|
| Public surface | One per workspace | One, total |
| Certificates | One per workspace | One |
| DNS | A record per workspace | One |
| Adding a workspace | Ingress, cert, DNS, firewall | Issue a slot token |
| A workspace behind NAT, or on a laptop | Impossible without a tunnel | The same as any other |
| Blast radius of the public thing | The workspace and its volume | A stateless relay |
The last row is the one that matters at three in the morning. The exposed component holds no database, no credential of yours beyond the tokens it validates, and nothing a restart loses.
Capacity, and where it actually goes
The server is small. What costs is what it spawns: every harness session is a child process with its own model client, its own toolchain, and sometimes a build.
Drawing the diagram…
| Sessions at once | Sensible shape |
|---|---|
| 1-2 | 2 vCPU, 4 GB |
| 3-5 | 4 vCPU, 8 GB |
| More | Split across slots rather than growing one box |
Split rather than grow. Two workspaces of four vCPU each fail independently, back up independently and upgrade independently. One of eight does none of that, and a runaway build in it takes the tunnel with it.
Set the two limits explicitly, because a harness can spawn a compiler:
--pids-limit 512 --memory 8gWithout them one runaway build takes the whole container, the tunnel included, and the failure looks like the beam rather than like a build.
For the beam host, bandwidth is what binds rather than CPU: every byte a remote browser sees crosses it twice.
| Slots | Beam host shape |
|---|---|
| 1-5 | The smallest VPS you can rent. 1 vCPU, 512 MB |
| 5-25 | 1-2 vCPU, 1 GB |
| More, or with the dataset on | 2 vCPU, 2 GB, and put blobs on object storage |
Provisioning one, end to end
Drawing the diagram…
1 and 2, the slot
BEAM_TOKEN=$(openssl rand -hex 32) # yours, and only yours
BEAM_SLOTS="kdlab=$(openssl rand -hex 32),studio=$(openssl rand -hex 32)"A slot name is lowercase letters, digits and hyphens, at most 31 characters. An installation naming none lands in default.
The tunnel token rides in the dial's query string
A WebSocket client cannot set an Authorization header, so the token lands in the proxy's own access log. Either keep that log private, or drop the query from it.
3, the configuration
Vendor mode makes one document the deployment's interface. A missing or invalid auth section is the one thing that exits rather than degrading: a deployment that came up unauthenticated would be worse than one that is down.
Drawing the diagram…
The environment variable exists for a platform that gives you secrets and environment but no way to seed a volume before the first start - a Kubernetes secret, for instance.
Durable deployment settings go in the document, and only there. A setting changed in the UI that the document also names is overwritten the next time the container restarts. That is the point: a container that drifted heals on restart.
| Section | Scope |
|---|---|
auth | Mandatory. basic or oidc, validated strictly |
llm | The gateway the orchestrator and the Refinery run on, and that /api/llm proxies for the browser |
workspaces | Where agents are allowed to work |
harnesses.env | Set on the process; every spawned harness inherits it. The server's own secrets (MECHABEDO_LLM_API_KEY, MECHABEDO_BEAM_TOKEN and the rest) may each be a file instead, named by <VAR>_FILE |
harnesses.files | Paths relative to $HOME, written on every start |
platforms | Forgejo, YouTrack, and the write gate |
loadouts | Seeded once onto the volume, then the settings page owns the list |
kits | Seeded once the same way, before the loadouts; copyOf copies plus, lean or an earlier entry, and enhance.defaultKit picks the default among them |
loadouts and kits are the sections that are handed over rather than held. A malformed entry in either is a refusal rather than a silent drop, and a save the document refuses says so rather than appearing to work.
4, the container
Three shapes, in increasing order of how little of the host they can see. All of them are in Beam, server setup with full commands.
| Shape | When |
|---|---|
| Bind mounts of host directories | The agents are meant to work on code that already exists on that machine |
| Its own named volume | Self-contained. Nothing of the host is visible |
Rootless, --cap-drop=ALL, --pids-limit, --memory | The same, with the restrictions that hold without breaking the harnesses |
--mount /srv/projects at the same path inside and out is worth doing deliberately, so a path in a transcript means the same thing on both sides.
This is not a sandbox for the agents
The harnesses run as the container's user with whatever is mounted. Everything not mounted does not exist to them - which is the actual boundary. A read-only root filesystem is not supported: mise and the harnesses install at runtime.
7, the volume becomes the installation
The image is disposable. /data is not.
| Path | Contents | Losing it costs |
|---|---|---|
/data/config | The deployment's interface | Nothing; you wrote it |
/data/settings | The signing secret, the installation id, the enhance catalog | Every session gets a new login, and the beam slot reads this as a different machine |
/data/home/.mechabedo/mainy/data/pglite | The database | The archive |
/data/home | $HOME: mise, the harnesses, their config, the workspaces | Everything not in /data/settings, but rebuildable by re-running bootstrap |
The installation id in /data/settings is what tells the host this machine restarting from a second machine arriving. A settings directory restored onto a different machine will reclaim that machine's slot; a lost one will be refused it. Both are correct, and both are worth knowing before they surprise you.
Patterns
One workspace per person
The obvious one. Each person gets a slot, a container and a login. They reach it at https://beam.example.com/__beam/go/<their slot>.
Drawing the diagram…
Isolation is per container and per volume. Nobody's archive, harness credentials or workspaces are visible to anybody else, and one runaway build takes down one workspace.
A desktop and a server, one person
The pattern that gets underrated. Your laptop runs the desktop app on one slot; a server runs a container on another. Both sync their logbooks to the host, so the archive converges: what you did on the laptop is searchable from the server and the reverse.
Drawing the diagram…
And the agents in either reach the agents in the other: peer_list_instances names them, and the peering tools take a slot name as instance. Nothing is configured for that beyond the beam itself.
A fleet working one repository
Several workspaces on the same codebase, each with its own volume, each running its own agents.
Two things to decide up front:
- Where the code comes from. Either each workspace clones it, or they mount a shared directory. Sharing a checkout across containers means agents editing one tree at once, which is a workflow decision rather than a deployment one.
- What lands. Workspaces and sessions covers the working copy an agent can be given of its own, which nothing sees until it is landed deliberately.
What to watch
Four signals, in the order they will actually page you.
| Signal | Source | Meaning |
|---|---|---|
live below what you issued | /__beam/health | An installation is down or cannot reach the host |
channels stuck above zero on an idle slot | /__beam/slots | A leaked channel. Restarting that installation clears it |
Slot x refused to y: held by z | The host's log | Two machines want one slot. Expected during a migration, not otherwise |
vitals.uptimeSeconds resetting repeatedly | /__beam/slots | That installation is crash-looping. Its own log says why |
curl -s https://beam.example.com/__beam/health | jq .live
curl -s -H "Authorization: Bearer $BEAM_TOKEN" \
https://beam.example.com/__beam/slots | jq '.slots[] | {slot, device, channels, agent: .agent.version, up: .vitals.uptimeSeconds}'/__beam/health needs no credential and names nothing - an unauthenticated endpoint handing out machine names and version strings would be a list of targets. The slot detail needs a tunnel credential.
Up, versus working
Three things separate them, and only the third needs a person:
- The container is healthy. The image carries a
HEALTHCHECKon/api/health, with a long start period because PGlite opening a cold volume the first time is slower than every start after it. - The log carries
tunnel open to <host> on slot <name>, and the slot is in/__beam/slots. - A chat session opens and stays open while idle. The remote leg has no heartbeat of its own, so an idle session staying connected is the one thing only a real session proves.
Backup, per workspace
The unit is the database and the settings directory together.
Drawing the diagram…
Prefer the first. It is consistent and it stops nothing, because the database exports itself rather than being copied out from under its own write-ahead log. A restore or a wipe asked for while a server holds the profile is parked and applied at the next start.
From outside, the whole volume is the safe unit and the container must be stopped. A tar of a running PGlite is a tar of a database mid-write, and a tar of settings alone does not contain the database at all.
Do not restore one workspace's settings onto a second running one
They will share an installation id and fight over one slot. Restore it onto the machine it came from, or onto its replacement.
Rolling the fleet
Host first, then the installations, one at a time. A newer host serving older installations is the state that works while you roll. The reverse leaves upgraded installations parked until you reach the host.
Drawing the diagram…
There is no quorum and nothing to drain. The interlock is the tunnel's protocol version, and it is announced in the release notes when it moves; everything else
- a new
hellofield, a new control frame, a new feature - is negotiated by name and needs no coordination at all.
The last step is the cheap one that catches a half-finished roll: a slot still reporting the old version is an installation you have not restarted yet.
Full procedures, including mise run docker update and rollback, are in Upgrading.
Next
- The architecture - the hops these procedures move through
- Reference deployments - two shapes of the same tandem, drawn
- Running a beam host - the public end, in full
- Running mechabedo as a server - one workspace, in full
- Upgrading - the order, and getting back