Skip to content

One computer per person, with Isolated Bots on request - #871

Merged
davidmckayv merged 19 commits into
mainfrom
david/per-person-computers
Oct 29, 2026
Merged

davidmckayv merged 19 commits into
mainfrom
david/per-person-computers

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

What this changes

Every Bot used to have a computer of its own. With the supervisor (laptop, Desktop, Compose) that meant one container per Bot, running all the time. And because computers were keyed by Bot alone, everyone who used a public Bot, including every built-in Bot, shared one browser and its logins.

Now all the Bots a person uses run on one computer per person, as Grok Bot does (Cursor docs: one persistent computer per user, shared by that user's Bots). Inside it, each Bot keeps its own browser profile and folder, so a login belongs to the person and the Bot, never to everyone who uses the Bot. That is stricter than Grok, whose Bots share a person's sign-ins.

  • Isolated Bots. An owner can tick Isolated when making a Bot, or turn it on in its Setup. That gives the Bot a computer of its own, which keeps its logins; everyone who uses that Bot shares it. The tooltip reads: "Runs in its own computer. Nothing it does can reach your other Bots' files or logins. Costs more to run."
  • Admin control. A new enterprise switch, Isolated Bots (isolatedComputers), sits beside the existing ones. It is on by default. It can be turned off for everyone, or allowed for chosen roles and groups.
  • When the switch is withdrawn, the person's isolated Bots run on their own computer from the next run, and the Bot's Setup says "Your admin turned off Isolated Bots, so this Bot runs on your computer."
  • Where Isolated isn't possible. A deployment with one shared computer (Helm shared, or no supervisor) can't give a Bot its own. The checkbox says so instead of hiding.

How

  • Computer keys (server/src/computer/computer-key.ts).
    • A Bot's computer is person:<actor> unless runsIsolated (server/src/agents/isolation.ts) says the Bot is isolated and its owner still holds the switch; then it is bot:<id>.
    • The actor is the person in the conversation, or the owner of a background turn.
    • Every gateway call carries a seat, meaning a computer key plus the Bot.
    • Server state (snapshots, page frames, sessions, the culler) is keyed by seat, so two people on one public Bot never collide.
  • Supervisor.
    • Containers and volumes are named <ns>-computer-person-<id> and -bot-<id>.
    • A container is only treated as ours when its kind and id labels match.
    • SPIRE ids are /<kind>/<id>.
    • Old per-Bot containers are stopped at start, and their volumes are kept.
  • Helm.
    • sandbox mode: one Sandbox per person, plus one per isolated Bot, still suspended when idle.
    • shared mode: still one computer, now with a profile and folder per person and Bot.
  • Logins on upgrade.
    • A private Bot's existing profile is copied once into its owner's computer, on the supervisor and in Helm shared.
    • Public Bots sign in again once, because their old logins were shared by everyone.
    • Helm sandbox doesn't copy. Each Sandbox's volume is fixed at creation, so private Bots there sign in again once.
  • API and UI.
    • Bots gain isolated, isolationActive and isolationOffReason.
    • Turning Isolated on needs the switch: a 403 refusal with an audit row (bot.isolation_refused). Turning it off is always allowed. Package Bots can't be isolated.
    • /api/agents/capabilities says whether this deployment can isolate.
  • Migration 0066_isolated_bots, generated by drizzle-kit, adds agent_profiles.isolated (off).
  • Wording. The README, docs, .env.example, the Helm chart, onboarding and the admin Computers screen now describe one computer per person. They previously claimed shared mode shares logins between Bots, and that external gives each Bot its own computer; both are fixed.
  • Carried CI fixes. This also carries Fix the three red CI checks: Anthropic 2.x in the agent tests, the Chromium enter-text launches, and a React teardown #863's three test fixes, so CI is green; those commits drop out once Fix the three red CI checks: Anthropic 2.x in the agent tests, the Chromium enter-text launches, and a React teardown #863 merges.

Checks

  • Server suite (sharded, real Postgres): 5865 pass. The 17 failures are exactly the same set as on main (5824 pass, same 17).
  • App suite: 1554 pass. Typechecks, lint and format are clean.
  • New tests cover:
    • two people on one public Bot getting different computers and logins;
    • one person's two Bots sharing a computer but not a profile or folder;
    • an isolated Bot getting its own computer;
    • revoking falling back to the person's computer;
    • login carry-over running once;
    • API enforcement, its refusal and audit row;
    • the checkbox, tooltip, Setup row and revoked note;
    • onboarding's wording.
  • Live, on a real Docker supervisor in Chrome:
    • General Assistant opened a page on …-computer-person-<me>.
    • An Isolated Bot made through the sheet got …-computer-bot-<id>.
    • Turning Isolated Bots off in Admin → Enterprise disabled the checkbox with a reason and showed the Setup note, and the Bot's next run landed on the person's computer.
    • Turning it back on restored isolation.
    • Two people and login carry-over were proven with server and Docker integration tests, because single-user mode has only one person.

Design: https://app.notion.com/p/4073aa3818528115ab5ed1ea1c2ac41d (internal).

Where it runs

  • New state that outlives a request? agent_profiles.isolated and the isolatedComputers capability rows, in Postgres. The carry-over marker lives in the person's computer volume.
  • What happens on the second replica? The computer key is derived from Postgres and the actor on every call, so every replica picks the same computer. The supervisor's ensure is idempotent per key.
  • Anything serialised? The carry-over runs once per (person, Bot) and leaves a marker in the destination volume; a second run sees the marker and does nothing. Container creation goes through the supervisor's existing ensure.
  • Anything fanned out to a browser? Capability changes reach the app through the existing enterprise invalidation.
  • New listener, port, or schedule? No.

Boundary and audit

  • Every acting call still goes through the gateway: resolve, decide, audit, act. The seat is resolved before locate.
  • New refusals and new failures each write a row (bot.isolation_refused).
  • Nothing new is trusted from the client. The person and the isolation decision come from the session and the database.

Changelog

  • An entry and a "Before upgrading" bullet under Unreleased.

The starting point for moving Bots onto one computer per person: a Bot's
isolated flag (off by default, migration 0066 generated by drizzle-kit) and the
isolatedComputers enterprise switch, on by default so an untouched deployment
keeps letting people ask for a computer of its own.
The supervisor now makes one computer per person (<ns>-computer-person-<id>)
and one per isolated Bot (<ns>-computer-bot-<id>), each labelled with whose it
is so a computer from before cannot be adopted under a name the new scheme also
derives. Every computer it makes keeps a folder per Bot.

agent-computer keys a Bot's profile, folder and session by a seat: the Bot on a
computer of one person's own, or the person and the Bot (person~bot) on the
shared computer, where the server now names the person too. Network policy and
proxies stay per Bot.

A private Bot's old profile is given to its owner once: the supervisor copies
the old profile volume into the owner's computer through a throwaway container
that mounts both, leaving a marker and the old volume in place; the shared
computer moves /profiles/<bot> to the owner's seat. Computers from before are
stopped when the supervisor starts, keeping their volumes.
Bots read and write `isolated`, and the Bot DTO says whether that computer is in
effect now (`isolationActive`) and, when it is not, why (`isolationOffReason`:
the owner lost "Isolated Bots", or this deployment cannot make a second
computer). Turning it on needs the isolatedComputers switch for whoever asks,
checked in the store so every caller meets it; turning it off never does, an
edit that leaves the field out leaves it alone, a Bot that belongs to no person
is refused, and a copy keeps it only for a duplicator who holds the switch.
Each refusal answers in the gate's shape and leaves a bot.isolation_refused row.

The New Bot sheet gets an Isolated checkbox with its tooltip, shown disabled
with the reason where the deployment or an admin rules it out, and Setup gets
an Isolated switch that says when an admin has turned it off. Changing a switch
in Admin, Enterprise now refreshes Bots too.

`runsIsolated` is the verdict the computer key reads.
Every layer keyed a computer by the Bot alone, so a public Bot (every built-in
one included) had one computer and one set of logins that everybody in the
deployment shared. A computer is now chosen by a seat: the Bot, on the computer
of the person it acts for (the person in the conversation, or the owner of a
background turn), or on its own computer when it is isolated and its owner still
holds Isolated Bots. Whether it is isolated comes from runsIsolated in
agents/isolation.ts; a provider that cannot make a computer per Bot (the shared
one) seats every Bot for the person.

The supervisor and the sandbox are asked for /computers/person/<id> or
/computers/bot/<id>; the shared computer is sent the person with the Bot.
Snapshots and page frames are filed under the seat, audit rows name the
computer, and the idle culler, the inactive stop and offboarding act per
computer. A Bot's own stop and reset reach only its profile on a person's
computer; the admin list says whose each computer is and acts on it by key.
A private Bot's old login is carried to its owner's computer on first use.

Also fixes the wording that said shared mode shares logins between Bots, and
turns on a folder per Bot in the sandbox pod template.
…ed Bot has its own

The README, docs, .env.example and the Helm chart described one computer per
Bot. They now describe one per person, with a profile and folder per Bot, and
a computer of its own for a Bot marked Isolated. The changelog says what an
upgrade does to existing computers and logins.
The second slide read "Each Bot has its own computer". Bots now share one
computer per person, each with its own browser and folder there; only an
Isolated Bot has a computer of its own.
…raph AG-UI test

anthropic 2.x sends the key as Authorization: Bearer instead of x-api-key and
dates its Messages API version later than 2023-06-01. The provider-boundary
test asserted both by their 1.x values, so it failed once the unpinned
requirements resolved 2.x. It now reads the key from either header and checks
the version is a date; the endpoint, key, model and body it asserts are
unchanged.

(cherry picked from commit 99685ee)
CI installs Chromium with --no-shell since #829, and enter-text launched
Playwright's default, the separate headless shell, so it failed to start.
It now launches channel "chromium", the build the computer itself launches.

(cherry picked from commit f220198)
# Conflicts:
#	agent-computer/tests/enter-text.test.ts
#	agent-langgraph-agui/tests/test_provider_boundaries.py
#	app/src/components/agents/new-bot-sheet.tsx
#	app/tests/new-bot-sheet.test.tsx
#861 turned onboarding into a looping introduction whose computer scene is
labelled "Each Bot has its own computer". Label and heading now say what
every Bot gets, and the check reads the route's words, since the looping
scene cannot be reached under happy-dom.

@guidovizoso guidovizoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core is careful:

  • Server state is keyed by seat.
  • The supervisor only reuses a container whose kind and id labels match.
  • The profile carry-over runs once, in a no-network container with capabilities dropped.
  • The Isolated switch is enforced in the store as well as the route, with 403s and bot.isolation_refused rows.
  • The migration is additive only.

Requesting changes for one blocker.

Blocker

Another author's Bot now runs inside your computer, next to your private Bots' logins. seatOf (computer/computer-key.ts) puts every non-isolated Bot into the person's seat, public Bots included. Commands are bash -c in that container, and agent-computer/src/workspaces.ts:17-20 says a command "can read any path the container can". So a public Bot whose instructions cat /profiles/*/Default/Cookies reads the person's private Bots' sessions. Before this PR, a public Bot had a container of its own. The person using it can't tick Isolated, and the per-person copy on the admin Computers screen (admin/computers.tsx:174) doesn't say this. Options I'd accept:

  • run Bots the person doesn't own outside their computer, or isolate them by default;
  • give each seat its own uid;
  • at minimum, refuse computer_run_command for Bots the person doesn't own, and say it in the copy.

Should fix

  • Isolated plus public brings shared logins back. canUseComputer lets anyone use a public Bot's computer, and password-manager sign-in takes that same seat (passwords/service.ts:423). So one person's bank login lands in a browser everyone uses. Making an isolated Bot public gives no warning (setup.tsx:170), and the server doesn't check the combination (profile-store.ts:652). Please refuse it, or warn and reset the profile on that change.
  • Upgrades hide files:
    • Helm sandbox: an isolated Bot keeps its Sandbox name, but replaceTemplate swaps in a template with COMPUTER_SEPARATE_WORKSPACES=on (_helpers.tpl:749). That moves its workspace root, which workspaces.ts warns against.
    • Supervisor: legacyNamesFor (supervisor/src/names.ts:150) doesn't carry over <ns>-workspace-<botId>, and the CHANGELOG doesn't say those files become unreachable.
  • Memory. One person's computer can run COMPUTER_MAX_BROWSERS (8) browsers inside a 4Gi limit sized for one (values.yaml:446). An out-of-memory kill takes down every Bot's browser for that person. Please set new defaults or add sizing guidance.
  • requireMayIsolate(actor) checks the person editing, not the Bot's owner (profile-store.ts:477,552). At run time, runsIsolated checks the owner's switch. An admin can tick Isolated on a Bot whose owner lacks the switch: the save succeeds and the Bot never isolates.
  • No cleanup on delete. Removing a person leaves their profile volume (their logins), and deleting an isolated Bot leaves its computer. A follow-up issue is fine.
  • Tests: nothing covers a command crossing seats, isolated plus public, or the Sandbox template swap moving the workspace.

Nits

  • 68e44ca6, 3ad22cdc and the typecheck-baseline.txt edits came from #863. Please rebase them out.
  • mayIsolate turns a read error into "no" without logging (isolation.ts:41), so an isolated Bot silently runs on the person's computer.
  • The comment in agents/computer-access.ts:5-11 still says "one per Bot and carries the owner's browser".
  • A reset leaves frames stored under the bare Bot id (computer/routes.ts:706).
  • Every computer request now resolves the seat with 2–3 queries. Consider caching it per run.

Brings in the Recorder and the OpenBot Browser (#858). The migration is renumbered
0069_isolated_bots, after #858's 0066 to 0068. A person's own browser is always a
computer of its own, never a seat on the person's computer beside their Bots: the
seat resolver says so, the browser routes locate it by its own key, and the
computer refuses it by the id named rather than by the seat made from it. A
Sandbox is cut by computer key: an isolated Bot's names its Bot and keeps its files
at the root of the volume it had, and a person's names no Bot and gives each Bot
a folder.
…st of the review

A person's computer follows the industry standard for an always-on agent's
computer: every Bot the person uses runs on it, a public Bot somebody else made
included, and a command any of them runs can reach anything there. The admin
Computers screen and the provider's description now say so, and an isolated
Bot's Setup says before it is made public, and while it is, that everybody who
uses it shares its computer and what is signed in to there.

Also from the review:
- Ticking Isolated asks the Bot owner's switch, the one runs are decided by,
  not the editor's, so an administrator can no longer save a tick the Bot never
  runs with.
- A Sandbox is cut by computer key: an isolated Bot keeps its files at the root
  of the volume it had, instead of having them hidden by a new workspace root.
- With the supervisor, a private Bot's old files come to its folder on its
  owner's computer, once, the same way its old profile does.
- A reset also forgets snapshots and page pictures filed under the Bot alone
  from before computers were per person.
- Helm's computer requests 2Gi with an 8Gi limit, sized for a person's
  browsers, with sizing guidance in values.yaml.
- An unreadable Isolated switch is logged, not silently read as off.
Takes #872's allow-list format, which matches an entry by its test and
expression rather than its line, with the expressions this branch changed
(seats instead of bare Bot ids) and the cleared enterprise-controls entry
removed.
@davidmckayv

Copy link
Copy Markdown
Contributor Author

@guidovizoso thanks for the review. Main is merged in (with #858 and #872), and the migration is renumbered 0069_isolated_bots. Fixes are in c299092 and 989970e; each bug fix has a regression test that fails before it and passes after.

Blocker: another author's Bot on your computer. This is the model we've chosen, and it is the industry standard for an always-on agent's computer: one computer per user, shared by every Bot that user runs, with Bots isolating personalities and workspaces, not compute. The standard goes further than we do and shares sign-ins across a user's Bots; we keep a browser profile per Bot. So rather than separate Bots inside the computer, we say it plainly:

  • The admin Computers screen and the provider's own description now say that every Bot a person uses runs on their computer, public Bots made by other people included, and that a command one Bot runs can reach anything else there.
  • A Bot that must be kept apart is made Isolated, which admins grant per person, role or group.

Should fix

  • Isolated plus public. An isolated Bot's Setup now says, before it is made public and while it is, that everyone who uses it shares its computer and whatever is signed in there. That is how a shared Bot's computer works across the industry, so it is said rather than refused.
  • Upgrades hiding files.
    • Helm sandbox: podTemplateFor now cuts the template by computer key. An isolated Bot's computer names its Bot and keeps COMPUTER_SEPARATE_WORKSPACES=off, so its files stay at the root of the volume it had. A person's computer names no Bot and gives each Bot a folder. Tested in computer-sandbox.test.ts.
    • Supervisor: legacyNamesFor now carries <ns>-workspace-<botId>. A private Bot's old files are copied once into its folder on its owner's computer, alongside its profile, by the same no-network copy. A new real-Docker case covers it. The CHANGELOG says a public Bot's old files stay in their old volume.
  • Memory. Helm's computer now requests 2Gi with an 8Gi limit. values.yaml explains the sizing: browsers per person, maxBrowsers, and the idle close.
  • requireMayIsolate. Updates now ask the Bot owner's switch (requireOwnerMayIsolate), with its own sentence when an admin edits someone else's Bot. The test reproduces your case: the admin holds the switch, the owner does not.
  • Cleanup on delete. Kept as an explicit admin action, which is also the standard. Admin → Computers lists every person's computer and each isolated Bot's, and Reset signs every Bot on it out. The CHANGELOG says so.
  • Tests: the Sandbox swap, the isolated plus public copy, the owner's switch, the file carry-over and the reset of old state are covered. A command reaching another Bot's files on the same person's computer is the model, so it's stated in the copy rather than tested against.

Found while merging #858: a person's own browser now always takes a computer of its own. The seat resolver and the browser routes both enforce it. The computer also refuses it by the id named rather than by the seat, which closes a case where a person's browser id sent with a person header slipped past on a shared computer.

Nits

  • 68e44ca and 3ad22cd: after merging main, those two files are identical to main's. The typecheck-baseline.txt removals are this PR's own: it fixed those four test errors, and the gate requires removing them.
  • mayIsolate: it now logs a switch it can't read before refusing.
  • agents/computer-access.ts: the comment is rewritten for per-person seats.
  • Reset: it now also forgets snapshots and page pictures filed under the Bot alone (resetComputer and resetTarget). Tested.
  • Seat caching: left as is, at two or three indexed lookups per call. I'm happy to cache per run if it shows up in profiles.

End to end on the laptop shape (supervisor, real Docker, real browser):

  • The admin copy renders as intended.
  • An isolated Bot's Setup reads correctly, private and public.
  • An ordinary Bot ran on openbot-871-computer-person-<id>.
  • The isolated Bot ran on -computer-bot-<id>, told its id.
  • The person's own browser opened on -computer-bot-person-<hex>, never on their shared computer.

guidovizoso
guidovizoso previously approved these changes Oct 29, 2026

@guidovizoso guidovizoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 989970e5. Approving.

On the blocker: with one computer per person, a shared Bot runs on the computer of the person using it, so nothing crosses between people. What's left is narrower: a Bot someone else wrote can run commands next to your other Bots' profiles. Prompt injection already gives every Bot, your own included, the same reach. The new copy on the admin Computers screen, the provider description and the isolated Bot's Setup says this plainly, so I'm fine with the model.

Verified fixed:

  • requireOwnerMayIsolate asks the owner's switch, and names the owner when an admin edits someone else's Bot. Tested.
  • podTemplateFor keeps COMPUTER_SEPARATE_WORKSPACES=off for an isolated Bot's computer, so its files stay at the root, and on for a person's computer.
  • The supervisor carries a private Bot's old workspace volume over once, in the same no-network, CapDrop: ALL container as the profile, with a real-Docker test.
  • Helm requests 2Gi with an 8Gi limit, with per-person sizing guidance. #879 adds the closing under pressure.
  • An isolated Bot's Visibility row warns before and while it's public. An unreadable switch is logged. Reset forgets frames filed under the bare Bot id. The computer-access.ts comment is rewritten.
  • A person's own browser always takes a computer of its own, and the computer checks the id. That's a good catch from the #858 merge.

Follow-ups (not blocking):

  • Consider turning computer_run_command off by default for Bots the person doesn't own (or asking first). That's cheap hardening against an author's instructions, while the model stays as is.
  • About 200 of the 261 files in c299092a only reorder imports. Next time, please keep that kind of churn out of a review-fix commit.
  • requireOwnerMayIsolate reads the owner before the transaction. A Bot changing owner in between is checked against the old owner. Low risk.

Brings in a Bot's own browser with Record (#868) and closing browsers when
memory runs high (#879). Conflicts are resolved here; the Bot's browser is
moved onto per-person seats in the next commit.
… a person's own computer

A Bot's browser (#868) is now a seat's, like everything else on a computer: each
route resolves the asker's seat for the Bot and reaches that browser, on their
own computer or on an isolated Bot's computer of its own, with the person named
to the computer where people share one. Recorder connections are still filed
under the Bot, but a seat on a person's computer reads, hands back and clears
only that person's: before, Ben opening his own Bot screen ended Ada's live
recording on her computer as left behind. The Bot's network policy is pushed to
the seat's computer.

Record is refused only on the computer every person shares. A person's computer
is now told whose it is (COMPUTER_PERSON_ID, from the supervisor and the Sandbox
template), so it records in its Bots' browsers there and still refuses a
person's own browser.
@davidmckayv
davidmckayv merged commit 9d80da0 into main Oct 29, 2026
19 checks passed
@davidmckayv
davidmckayv deleted the david/per-person-computers branch October 29, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants