Execution profiles

On this page
  1. Sealed runs
  2. Inspect the effective profile
  3. Immutable inputs and declared outputs
  4. Provider runtime state

AOP profiles define the complete execution boundary, not only where writes are allowed. The three isolated profiles build an empty bwrap mount namespace and explicitly add the selected workspace, repository view, read-only input snapshots, output, provider runtime, private state, cache, scratch, required operating-system runtime paths, and fresh /proc, /dev, and /tmp. They do not inherit the host root filesystem.

Inside the sandbox, bubblewrap's own reaper is process 1 and collects orphaned processes, and the harness runs as process 2. AOP passes bubblewrap's options through a private file descriptor, so process 1's command line, which every process in the sandbox can read, names no mount or host path. When --timeout expires, AOP sends SIGTERM to the harness itself, so it can save its session and flush its output, and stops the sandbox after a five-second grace period.

ProfilePrimary repositoryTask workspaceOther host pathsInstructionsWritable guest paths
editRead-only at /repositoryRead-write at /workspaceRuntime allowlist onlyProject and user/workspace, /output, /scratch, /state, /cache, /tmp
reviewRead-only at /repositoryRead-only at /workspaceRuntime allowlist onlyProject and user/output, /scratch, /state, /cache, /tmp
sealedNot mountedEmpty /workspace, read-only unless --writable-workspaceRuntime allowlist onlyNo inherited local instructions/output, /scratch, /state, /cache, /tmp, and /workspace with --writable-workspace
hostNativeNativeNativeNativeNative

The worktree's .git pointer and shared Git directory remain read-only under edit and review. The repository's .aop directory is masked from /repository, so a task cannot read run records, provider state, caches, or sibling worktrees. Provider permission prompts are bypassed because this outer mount boundary decides whether filesystem writes can succeed.

host skips bwrap and environment filtering. Use it only when the native host filesystem and environment are part of the intended boundary.

Two options make a host run behave like the harness started by hand in a directory:

  • --workdir DIR runs the harness in DIR itself, which need not be a Git repository, instead of a task worktree. TASK becomes optional, and AOP keeps the run's records in the .aop of the directory aop runs from, so DIR stays untouched and aop cleanup never removes it.
  • --native-state lets the harness use its own home, settings, memory, skills, and session store instead of AOP's private copy, so its sessions appear where the harness normally keeps them. Claude supports it; other harnesses refuse it.

request.json records both, and aop resume reuses them. The effective policy reports the workspace content as caller-directory and the state as harness-native. For example, from a separate records directory records directory:

aop run --agent claude --profile host --workdir /code/debate --native-state --prompt-parts turn.json

Sealed runs#

sealed does not require a Git repository. It uses an opaque agent-visible identity, canonical guest paths, a filtered environment, and minimal private provider configuration. It does not inherit project or user rules, skills, plugins, hooks, MCP configuration, memories, repository contents, controller paths, or human task labels.

Under sealed, /cache, /scratch, and /state are private to one session and reused only by its exact resumes. /output is private to one invocation. The compiled policy records these scopes explicitly.

--writable-workspace makes a sealed run's /workspace writable, so the model can write and run code where it naturally works. The workspace still starts empty, belongs to that session alone, keeps its files across the session's resumes, and is removed by aop cleanup. The request records writable_workspace, and the effective policy marks /workspace writable.

Account-level customizations arrive with a login rather than from local files, so a private home alone does not exclude them. Claude Code downloads a claude.ai account's connectors as mcp__claude_ai_* tools and its claude.ai skills into the home it runs with. Sealed Claude runs therefore pass --safe-mode, --strict-mcp-config, and an empty --mcp-config, which keep out MCP servers, connectors, skills, and other customizations. Claude Code's own built-in tools and skills remain.

A sealed run records itself in the .aop of the directory it was started from, even inside a Git repository. aop resume and aop cleanup find a run from any directory.

Without --no-web, every profile retains native host networking because the supported provider CLIs need it. Local network services and provider-side account state can therefore remain context channels. With --no-web, the sandboxed profiles get their own network namespace with only an allowlisted egress proxy, as the CLI guide describes. sealed is a filesystem and local-state boundary, not complete information isolation.

--no-web adds a separate model-capability boundary. It leaves provider inference traffic enabled but denies model-controlled external retrieval through native web tools, browsers, extensions, subagents, and shell processes. AOP records the adapter-specific enforcement and effective tool allowlist in request.json. Unsupported adapters fail before dispatch rather than receiving a prompt-only prohibition.

Inspect the effective profile#

Inspect a declared profile or preview a run without dispatching it:

aop profile explain sealed
aop profile explain sealed --agent codex --json
aop run --agent codex --profile sealed --prompt "Answer using only declared inputs" --dry-run

Every dispatched run persists its compiled boundary in request.json. It records repository and workspace access, guest paths, writable-path scope, input mode, environment and credential exposure, inherited and generated instruction policy, namespaces, network limitations, provider executable hash, and controller-owned state locations. The record also includes the requested and effective model capabilities and their enforcement mechanisms.

Credential values are redacted from recorded commands and results, but the selected provider and its shell tools can read credentials exposed through the environment or private /state. A sealed profile limits which credentials enter the process; it does not make those credentials unreadable to that process.

Immutable inputs and declared outputs#

Repeatable --input paths are copied into private, durable per-run snapshots beneath .aop/snapshots. Isolated profiles mount them read-only beneath /inputs. Their original host paths are not mounted or included in the child command, environment, or prompt. Stable guest paths such as /inputs/ledger.json do not reveal controller directory names. The host profile uses the same copies but does not enforce a read-only mount because its filesystem access is intentionally native. Sealed runs mount an opaque temporary projection of the durable snapshot so Linux mount metadata does not expose the controller path. AOP removes the projection after execution and retains the snapshot.

Each invocation receives a fresh writable /output, exposed in AOP_OUTPUT_DIR. AOP validates and archives only paths declared with --artifact; it never copies artifacts into the main worktree. See the CLI guide for declaration and validation details.

Provider runtime state#

Isolated provider state lives beneath .aop/provider-state/ for repository tasks and beneath an opaque key for sealed sessions. AOP seeds only the configuration, credentials, and extensions allowed by the selected profile. Existing provider sessions, logs, caches, and unrelated credentials are excluded. Exact resume reuses the task-private state associated with that session.

The exact seeded paths and adapter-specific exceptions are documented in Harness adapters.

Edit this page