Skip to main content

Workspaces and Isolation

A workspace is the isolated execution environment in which one agent run operates. It bundles together a filesystem directory, a reachable harnessd HTTP endpoint, and (for some backends) managed git state. Workspaces are set up and torn down by the orchestration layer — the agent loop itself never manages its own workspace.

Choosing a workspace backend lets you control what each run can see and modify: its own branch of a repo, a fresh Docker container, a remote Hetzner VM, or simply a directory on the current host.

Workspace choice is separate from the permission model. Even a worktree or container workspace runs with the default {sandbox: unrestricted, approval: none} unless you also set a permissions block in your run request or profile. Picking an isolated workspace backend does not automatically sandbox the agent's filesystem or approval behavior. See Tools and Permissions for details.


Selecting a workspace for a run

Pass workspace_type in the POST /v1/runs request body:

{
"prompt": "refactor the auth module",
"workspace_type": "worktree"
}

Valid values are "local", "worktree", "container", and "vm". An empty string means no workspace is provisioned — the run executes in the calling process.

If workspace_type is absent from the request, the runner falls back to the isolation_mode field of the named profile (if one is set). Profile isolation_mode accepts "none", "worktree", "container", and "vm". Only "worktree", "container", and "vm" actually trigger provisioning — "none" and an empty string both mean no provisioning. Note that "local" is not a valid profile isolation mode: setting it in a profile is treated as no preference and results in no provisioning.


The four backends


Workspace lifecycle and events

When a workspace_type is set, the runner goes through a three-phase lifecycle before the agent loop starts:

  1. Provision — calls workspace.New(ctx, wsType, opts), which internally calls Provision. If provisioning fails, the run ends immediately.
  2. System prompt injection — after provisioning, the system prompt is re-resolved with the workspace path so that any AGENTS.md file in the workspace directory is picked up.
  3. Destroy — on run completion, failure, or cancellation, ws.Destroy is called and the workspace is torn down.

Each phase emits a corresponding SSE event on GET /v1/runs/{id}/events:

Event typeString valuePayload fields
EventWorkspaceProvisionedworkspace.provisionedworkspace_type, workspace_path
EventWorkspaceDestroyedworkspace.destroyedworkspace_type, workspace_path, error (only on destroy failure)
EventWorkspaceProvisionFailedworkspace.provision_failedworkspace_type, error

These events are only emitted when workspace_type is non-empty. A workspace.provision_failed event is always followed immediately by run.failed.

// Example workspace.provisioned payload
{
"workspace_type": "worktree",
"workspace_path": "/home/user/myrepo-subagents/run-abc123"
}

Passing configuration and secrets

Every workspace backend accepts two optional configuration surfaces:

FieldWhat it isSecurity note
opts.EnvMap of environment variables injected into the workspaceUse this for secrets (API keys, tokens)
opts.ConfigTOMLTOML string written to harness.toml in the workspace root (mode 0600)Never put secrets here — the file persists on disk

API keys such as OPENAI_API_KEY and ANTHROPIC_API_KEY are always passed via opts.Env, never written to harness.toml. The orchestration layer (symphd) enforces this pattern automatically.

harness.toml is written to disk inside the workspace. Secrets placed in ConfigTOML will appear on the filesystem and may be captured in logs or git history. Always use opts.Env for credentials.


Warm workspace pools

For workloads that provision many short-lived workspaces (such as benchmark suites), the Pool type pre-provisions a configurable number of slots in the background and hands them out via lease/return semantics. This eliminates cold-start latency on each run.

The symphd orchestrator exposes this as workspace_type: pool with a pool_size setting (default: 3) and pool_workspace_type specifying the inner backend (default: "container").

Pool workspaces are not registered in the default workspace registry — they require a configured Pool instance.


Quick reference: choosing a backend

NeedRecommended backend
Local dev, single agent, harnessd already runninglocal
Parallel agents on the same repo, no Docker requiredworktree
Full filesystem isolation, reproducible environmentcontainer
Long-running work on a cloud VMvm (with awareness of the tool routing limitation)
High-throughput benchmark or eval suitepool (via symphd)

Next steps

  • Tools and Permissions — configure sandbox behavior and approval policies that apply inside any workspace.
  • Events — subscribe to workspace.provisioned and workspace.destroyed to observe workspace lifecycle in real time.
  • Skills, Profiles, and Subagents — set isolation_mode in a profile to select a workspace backend per task type without changing caller code.