Skip to Content
DocsExecution Isolation

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

ModeBoundaryUse case
simplenone beyond daemon-user permissionstrusted personal installation
sandboxfail-closed bubblewrap filesystem mountsshared Linux host
delegatedoperator-supplied external substratehosted/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: sandbox

Board 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_branch

The 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: false

Delegated 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:

RoleMeaning
ViewerView the branch and its conversations
CollaboratorView, create Sessions, and prompt branch-home Sessions
ManagerCollaborator 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:

  1. While still on 0.24.7, drain work, stop every daemon connected to the database, and take tested database, configuration, and storage backups.
  2. Install the 0.25.1 software without starting its daemon, then run agor db migrate --offline-cutover from 0.25.1.
  3. Obtain the 0.25.1 sandbox-home-migration-preflight.sh and strict-to-sandbox-migration.sh. Review the full operator runbook .
  4. Run both scripts’ read-only checks, then use --prepare-only to create and inspect the ownership manifest, config backup, ownership plan, and progress journal. Apply with --apply --resume only after that checkpoint is preserved.
  5. 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.

Last updated on