Multiplayer Execution Isolation
Agor separates application authorization from the execution boundary. Branch RBAC decides which principals may view, prompt, or control a branch. Execution mode decides what files and credentials agent code can reach.
Supported modes
| Mode | Boundary | Use case |
|---|---|---|
simple | none beyond daemon-user permissions | trusted personal installation |
sandbox | fail-closed bubblewrap filesystem mounts | shared Linux host |
delegated | operator-supplied external substrate | hosted/containerized execution |
The former strict and insulated modes were removed in Agor 0.25. Agor no longer creates host users or groups, changes passwords, repairs ACLs, creates per-user symlinks, or launches executors through sudo.
Sandbox mode
execution:
unix_user_mode: sandboxBoard and branch RBAC is always enabled. Sandbox mode adds the per-user filesystem sandbox. It requires Linux and bubblewrap and refuses to launch rather than silently falling back.
For each launch the daemon derives mount access from trusted tenant and branch authorization state:
- the active branch is writable, read-only, or absent according to the prompt actor’s file access;
- sibling worktrees and tenant homes are masked;
- daemon state and external data roots are hidden;
- the principal receives a persistent private home overlay;
- provider and daemon connectivity still use the host network namespace—this is filesystem isolation, not network isolation.
While a Task is running, its existing executor heartbeat rechecks the exact
user, Task, Session, Branch, prompt permission, and effective file access
against PostgreSQL authority. Losing prompt permission ends the Task through
the normal fenced shutdown path. A file-access reduction also ends a runtime
whose original mounts were broader (read/write to read or none, and
read to none). An access increase does not widen or remount a live runtime;
it applies to a later launch. Redis can accelerate disconnection but is not
required for the next heartbeat decision.
users.filesystem_home can point at a migrated home. Otherwise Agor uses its canonical tenant-scoped home store. Never derive this path from untrusted request data.
Per-branch SDK homes
Agent SDK state can be relocated from the session owner’s home into a directory shared by sessions on one branch:
execution:
unix_user_mode: sandbox
sandbox:
sdk_home_mode: per_branchThe default is inherit, which preserves the existing home layout. With
per_branch, admission of the first supported independent Session on a branch
stickily records branch intent. Its directory at
<tenantDataRoot>/branch-homes/<branchId> is still created lazily when that
Session first runs. Disabling the deployment setting later stops new adoption
but does not strand an adopted branch’s state. Archiving retains the directory;
permanently deleting the branch removes it.
Each Session also records an immutable SDK-home scope. Existing Sessions keep using their historical execution home even after their branch is adopted; a new independent Session is the deliberate migration point. Forked and spawned children inherit their parent’s scope so one native conversation lineage never moves between homes. Branch intent selects the default for future independent Sessions, while the Session stamp selects the home used when a conversation is resumed.
Agor sets each supported tool’s config-home environment variables and mounts
the branch directory at its real path. Cursor is refused because its config
home cannot currently be relocated reliably. OpenCode is also refused because
its current XDG data home combines native credentials with relocatable state.
Credentials remain caller-scoped rather than being copied into the shared
branch home. Codex API keys are injected through the environment; Codex
subscription auth uses a caller-specific file overlay in local sandbox mode.
The daemon opens that caller’s existing .codex/auth.json without following
symlinks, passes the pinned inode to bubblewrap, and mounts it read/write over
an empty auth.json mountpoint in the branch Codex home. Codex refreshes the
caller’s credential file in place, while no credential bytes are copied into
branch-owned storage. This requires home_mode: per_user, functional
--bind-fd, and bubblewrap 0.12.0 or newer; Agor fails closed without that
boundary. Importing replacement credentials or logging out durably requests
termination of that actor’s live branch-scoped Codex Tasks: their sandboxes
pin the prior inode, and the next prompt must bind the new pathname. A reviewed
delegated launcher may provide an equivalent caller-scoped overlay.
Web terminals
A branch terminal requires Collaborator or Manager access plus read or read/write file access. In sandbox mode it receives the same filesystem boundary as an agent launch. In simple mode it is a daemon-user shell and should be disabled on shared instances:
Interactive terminals do not mount or receive environment variables for a per-branch SDK home. Native tool login commands write credentials into their config home, so exposing the shared home to an arbitrary shell would violate the caller-scoped credential boundary.
Terminals do not share the Task executor heartbeat, so this continuous revalidation applies to Task executors only. Closing that live-terminal parity gap requires separate terminal lifecycle work.
execution:
allow_web_terminal: falseDelegated execution
execution:
unix_user_mode: delegated
executor_command_template: >-
launcher --tenant {tenant_id} --user {user_id} --branch-access {branch_fs_access}
--branch-sdk-home {branch_sdk_home}Agor does not claim to prove an external launcher’s isolation. The operator must ensure:
- tenant and immutable user identity are bound to the runtime;
- homes and credentials are isolated;
- branch storage matches application authorization;
- Stop, heartbeat, retry, and orphan cleanup semantics are enforced;
- terminal support is owner-affine or disabled;
- network access is explicitly controlled.
Prefer {tenant_id}, {user_id}, {branch_fs_access}, and
{branch_sdk_home}. The branch-home value is shell-escaped and is present only
for a branch-scoped Session; it is empty for execution-home Sessions even when
their branch has since been adopted. The launcher must make the same
Session-scoped mount decision and owns its tool-environment policy.
{unix_user} remains temporarily as an opaque, validated home key for
compatible hosted launchers; it is not a Unix account and is never passed to
sudo.
Board and branch permissions
Every board and branch has one primary owner. The current owner or a workspace Admin/Superadmin can use Transfer ownership in its permission settings to choose another member of the same workspace. Managers cannot transfer ownership unless they are also the owner or a workspace administrator. Viewer-only accounts cannot receive ownership. This transfer authority is independent of the configured superadmin bypass for ordinary branch operations.
Transfer changes management ownership of one resource only. A board transfer does not transfer its branches, and a branch transfer does not grant access to its board. Creators and historical authorship stay unchanged. Named access entries, groups, Others, inheritance/overrides, and session-sharing switches are preserved. The former owner loses the primary-owner shortcut but keeps any independent access or administrator privileges. The result shows their remaining resource-policy capabilities and branch file access.
This is not an assistant runtime handoff or offboarding: sessions, schedules, gateway run-as, Knowledge namespace ownership, personal MCP connections, OAuth grants and credentials do not move. Existing work keeps its execution identity; normal authorization rechecks may stop it if that identity loses access. Transfer does not pause work or guarantee immediate credential/process revocation. The successor must configure their own connections. Execution-home Sessions remain unshareable; branch-home Sessions still require both sharing gates.
The REST/Socket.IO commands are PATCH /boards/:id/ownership and
PATCH /branches/:id/ownership, with full UUIDs in the route and body:
{
"expected_owner_user_id": "<current owner UUID>",
"target_user_id": "<successor UUID>"
}Read the resource first. A stale expected owner is rejected; reload before
retrying. Ordinary resource and permission patches cannot change ownership.
MCP exposes agor_boards_transfer_ownership and
agor_branches_transfer_ownership. Bulk transfer, runtime handoff and user
soft/hard deletion are not part of these commands.
Upgrade the daemon and apply its normal database migrations before using transfer. The migration removes only the old ownership-immutability triggers; it does not rewrite existing owners. Authorization, successor eligibility and stale-owner checks run in the shared application transaction on SQLite and PostgreSQL. Existing database reference protections remain in place (SQLite owner-update eligibility is checked by the application, not a replacement trigger). Branches undergoing maintenance or permanent deletion reject transfer.
A board has two separate settings:
- Board access controls who can see and edit the canvas.
- Branch defaults are the complete permission package inherited by branches on that board.
A branch either inherits that entire package or uses one override copied from the current defaults. This includes session sharing—there is no separate inheritance switch for it.
Board roles are Viewer, Editor, and Manager. Branch roles are:
| Role | Meaning |
|---|---|
| Viewer | View the branch and its conversations |
| Collaborator | View, create Sessions, and prompt branch-home Sessions |
| Manager | Collaborator access plus branch, environment, session-lifecycle, and permission management |
Branch file access is selected separately as none, read, or read/write. Collaborator and Manager can open a terminal only when file access is not none.
Each entry names exactly one person or group. A person’s direct entry overrides their group memberships; otherwise group permissions combine and the highest file access wins. Others is a fallback only for active members of the same workspace who match no person or group entry.
Collaborators and Managers may prompt another person’s branch-home Session only when the workspace preference and effective branch permission package both enable shared session prompting. The task runs as the actual caller while the conversation and native SDK state remain branch-owned. An execution-home Session is never shareable; create a new branch-home Session instead. When a gate is disabled, Agor and connected gateway channels return an actionable, terminal denial rather than silently changing execution identity or retrying.
Capability-policy upgrade
The normalized board/branch permission migration is a coordinated offline cutover. Stop every daemon, take and test a complete database backup, then run the migration with only the new version installed.
The migration maps legacy access conservatively. Personal sharing grants are not broadened into branch-wide access: the new workspace and branch switches start off. Some users may therefore have less access after the upgrade. Review important board and branch permission screens before resuming normal work.
If a board or branch cannot be attributed to a primary owner, migration stops and lists the affected IDs. Resolve those resources manually rather than assigning an arbitrary owner. Old daemon versions cannot run against the migrated authority model. Rollback requires restoring the complete pre-migration database backup.
Migrating from 0.24
There is no published 0.24 bridge release for this conversion. Treat the 0.25.1 upgrade as an offline cutover:
- While still on 0.24.7, drain work, stop every daemon connected to the database, and take tested database, configuration, and storage backups.
- Install the 0.25.1 software without starting its daemon, then run
agor db migrate --offline-cutoverfrom 0.25.1. - Obtain the 0.25.1
sandbox-home-migration-preflight.shandstrict-to-sandbox-migration.sh. Review the full operator runbook . - Run both scripts’ read-only checks, then use
--prepare-onlyto create and inspect the ownership manifest, config backup, ownership plan, and progress journal. Apply with--apply --resumeonly after that checkpoint is preserved. - Start only 0.25.1 daemons, then verify one real task for every configured agentic tool.
The migration scripts are Linux/GNU-specific reference tooling. They only detect the configured systemd service, so independently verify that foreground and alternate-service daemons are stopped. They do not cross nested mount points, and the ownership manifest is not an automatic rollback tool. Keep host users, groups, sudoers, and all recovery artifacts through a production soak. The safest full rollback is to stop 0.25.1 and restore the complete pre-upgrade backup.
Version 0.25 refuses strict, insulated, and opportunistic configuration values; it does not downgrade them to simple. For migration help, bring the dry-run output and deployment layout to Discord before applying filesystem changes.
Historical unix_group database values remain nullable for rollback and audit but are no longer written or interpreted. unix_username remains temporarily as the delegated home key and immutable session stamp.