How Paperclip Isolates Agent Execution with Workspaces and Runtime Sandboxes
Paperclip enforces strict workspace and runtime isolation for agents through short-lived Git worktrees, sandboxed runtime slots, and a heartbeat controller that provisions and tears down resources for every run.
Every agent execution in the paperclipai/paperclip repository operates inside an isolated execution workspace—a dedicated Git worktree combined with a confined runtime environment. This architecture prevents code modifications, secrets, and side effects from leaking across runs while allowing controlled reuse of stateful workspaces when needed.
Execution Workspace Architecture
The execution workspace is the foundational isolation primitive. Each workspace receives a unique UUID (executionWorkspaceId) and maps to a Git worktree stored at data/workspaces/<id>. Agents interact with these workspaces exclusively through scoped API endpoints rather than direct filesystem access.
Workspace Realization
In server/src/services/workspace-realization.ts, the system either creates a fresh worktree or reattaches to an existing one:
- Fresh workspace: Created when
currentExecutionWorkspaceis absent from the issue's heartbeat context - Reusable workspace: Reused when an issue is explicitly linked to a durable workspace for stateful operations
// Conceptual flow from workspace-realization.ts
const workspace = await realizeExecutionWorkspace({
issueId,
reusable: issue.linkedWorkspaceId != null,
baseBranch: 'main'
});
// Returns: { id: UUID, cwd: '/data/workspaces/abc123', gitWorktreePath: '...' }
Worktrees are automatically cleaned up after the heartbeat lease expires unless marked reusable.
Heartbeat Controller and Temporal Isolation
The HeartbeatController in server/src/services/heartbeat.ts enforces temporal isolation—agents only run while an active lease exists.
Six-Stage Execution Flow
- Wakeup — Trigger sources:
timer,assignment,on_demand, orautomation - Workspace resolution — Controller reads
heartbeat-contextto locate or createcurrentExecutionWorkspace - Runtime slot acquisition — MCP runtime manager allocates a matching transport slot
- Adapter launch — Selected slot executes the configured adapter with workspace-scoped environment
- Result collection — Logs, tokens, and status persist to the run row; UI receives SSE/WebSocket updates
- Cleanup — Slot termination, worktree disposal (or retention), and lease release
# Trigger on-demand heartbeat manually
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_URL/api/agents/$AGENT_ID/wake" \
-d '{"wakeReason":"on_demand"}'
The response includes a runId for status polling or cancellation.
Runtime Slot Sandboxing
The MCP runtime slot abstraction provides process-level isolation through two transport modes:
| Slot Type | Use Case | Isolation Mechanism |
|---|---|---|
remote_http |
Gateway adapters (e.g., hermes_gateway) |
Proxy to remote service with request-level boundaries |
local_stdio |
Local adapters (e.g., claude_local, codex_local) |
Containerized process with CPU/memory caps and timeout guards |
Slot health is monitored via /api/companies/:companyId/tools/runtime-health with automated alerts and recovery procedures.
Low-Trust Runtime Containment
server/src/services/low-trust-runtime-containment.ts enforces mandatory sandboxing for untrusted adapters. The default adapter trust level is untrusted, requiring an isolated workspace and runtime slot. Only explicitly privileged adapters bypass these constraints.
// From low-trust-runtime-containment.ts
if (adapter.requiresSandbox && !executionWorkspace.isSandboxed) {
throw new LowTrustViolationError(
`Adapter ${adapter.id} requires sandboxed execution workspace`
);
}
Workspace Runtime Services
Execution workspaces can expose runtime services such as preview web servers. These are managed through server/src/services/workspace-runtime.ts and exposed via the workspace-scoped API:
# Discover current workspace and services
curl -sS -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_URL/api/issues/$PAPERCLIP_TASK_ID/heartbeat-context"
Response includes currentExecutionWorkspace.id, cwd, and runtimeServices[] array.
Service Control Operations
# Start all configured services
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/start" \
-d '{}'
# Restart specific service (e.g., web preview)
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/restart" \
-d '{"workspaceCommandId":"web"}'
# Stop all services before teardown
curl -sS -X POST \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
"$PAPERCLIP_API_URL/api/execution-workspaces/<workspace-id>/runtime-services/stop" \
-d '{}'
Service URLs and state persist in the database with company-scoped access control enforced on every request.
Isolation Guarantees
| Guarantee | Implementation | Verification |
|---|---|---|
| Temporal | Heartbeat lease with automatic teardown | server/src/services/heartbeat.ts |
| Filesystem | Git worktree per run, no host path exposure | server/src/services/workspace-realization.ts |
| Runtime | Slot-based sandboxing with trust levels | server/src/services/low-trust-runtime-containment.ts |
| Network | Workspace-scoped API routes, no direct service access | server/src/routes/execution-workspaces.ts |
Key Source Files
server/src/services/heartbeat.ts— Heartbeat lifecycle orchestrationserver/src/services/workspace-realization.ts— Git worktree provisioningserver/src/services/low-trust-runtime-containment.ts— Sandbox enforcementserver/src/services/workspace-runtime.ts— Runtime service managementserver/src/routes/execution-workspaces.ts— Workspace API endpointsdocs/agents-runtime.md— Agent runtime policies and adapter configurationdoc/MCP-RUNTIME-OPERATIONS.md— Runtime slot health and operations
Summary
- Execution workspaces are isolated Git worktrees provisioned per run via
workspace-realization.ts - Heartbeat controller enforces temporal boundaries with automatic resource cleanup
- MCP runtime slots provide sandboxed process or HTTP transport isolation
- Low-trust containment requires sandboxing for all adapters by default
- Workspace runtime services expose controlled network endpoints without breaking isolation boundaries
- All operations route through workspace-scoped APIs in
execution-workspaces.tsrather than direct filesystem access
Frequently Asked Questions
What happens if an agent exceeds its heartbeat timeout?
The HeartbeatController terminates the runtime slot, releases the lease, and records the timeout status in the run row. Any incomplete file operations within the worktree are abandoned; the worktree is deleted unless explicitly marked reusable. According to heartbeat.ts, cleanup runs regardless of success, failure, or cancellation.
Can multiple agents share the same execution workspace?
Only sequentially. A workspace can be reused across heartbeats for the same issue if configured as durable, but concurrent access is prevented by lease-based locking. The currentExecutionWorkspace field in the heartbeat context ensures single-tenancy per run.
How do I verify that an adapter is running in a sandbox?
Query the runtime health endpoint and inspect the adapter's trust classification. Adapters with requiresSandbox: true that bypass containment trigger an error from low-trust-runtime-containment.ts. Privileged adapters like hermes_gateway explicitly opt out of sandbox requirements through allowlist configuration.
What is the difference between remote_http and local_stdio runtime slots?
remote_http slots proxy adapter invocations to external services without local process creation, suitable for gateway adapters that manage their own isolation. local_stdio slots spawn sandboxed subprocesses with resource caps, used for local LLM adapters like claude_local. The MCP runtime manager selects slots based on adapter transport requirements and availability.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →