# How Paperclip Isolates Agent Execution with Workspaces and Runtime Sandboxes

> Discover how Paperclip isolates agent execution using worktrees and sandboxed runtimes. Learn about their resource provisioning and teardown for secure agent workflows.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: internals
- Published: 2026-08-16

---

**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](https://github.com/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`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/workspace-realization.ts), the system either creates a fresh worktree or reattaches to an existing one:

- **Fresh workspace**: Created when `currentExecutionWorkspace` is absent from the issue's heartbeat context
- **Reusable workspace**: Reused when an issue is explicitly linked to a durable workspace for stateful operations

```typescript
// 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`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/heartbeat.ts) enforces **temporal isolation**—agents only run while an active lease exists.

### Six-Stage Execution Flow

1. **Wakeup** — Trigger sources: `timer`, `assignment`, `on_demand`, or `automation`
2. **Workspace resolution** — Controller reads `heartbeat-context` to locate or create `currentExecutionWorkspace`
3. **Runtime slot acquisition** — MCP runtime manager allocates a matching transport slot
4. **Adapter launch** — Selected slot executes the configured adapter with workspace-scoped environment
5. **Result collection** — Logs, tokens, and status persist to the run row; UI receives SSE/WebSocket updates
6. **Cleanup** — Slot termination, worktree disposal (or retention), and lease release

```bash

# 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`](https://github.com/paperclipai/paperclip/blob/main/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.

```typescript
// 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`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/workspace-runtime.ts) and exposed via the workspace-scoped API:

```bash

# 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

```bash

# 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`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/heartbeat.ts) |
| **Filesystem** | Git worktree per run, no host path exposure | [`server/src/services/workspace-realization.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/workspace-realization.ts) |
| **Runtime** | Slot-based sandboxing with trust levels | [`server/src/services/low-trust-runtime-containment.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/low-trust-runtime-containment.ts) |
| **Network** | Workspace-scoped API routes, no direct service access | [`server/src/routes/execution-workspaces.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/routes/execution-workspaces.ts) |

## Key Source Files

- **[`server/src/services/heartbeat.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/heartbeat.ts)** — Heartbeat lifecycle orchestration
- **[`server/src/services/workspace-realization.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/workspace-realization.ts)** — Git worktree provisioning
- **[`server/src/services/low-trust-runtime-containment.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/low-trust-runtime-containment.ts)** — Sandbox enforcement
- **[`server/src/services/workspace-runtime.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/workspace-runtime.ts)** — Runtime service management
- **[`server/src/routes/execution-workspaces.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/routes/execution-workspaces.ts)** — Workspace API endpoints
- **[`docs/agents-runtime.md`](https://github.com/paperclipai/paperclip/blob/main/docs/agents-runtime.md)** — Agent runtime policies and adapter configuration
- **[`doc/MCP-RUNTIME-OPERATIONS.md`](https://github.com/paperclipai/paperclip/blob/main/doc/MCP-RUNTIME-OPERATIONS.md)** — Runtime slot health and operations

## Summary

- **Execution workspaces** are isolated Git worktrees provisioned per run via [`workspace-realization.ts`](https://github.com/paperclipai/paperclip/blob/main/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.ts`](https://github.com/paperclipai/paperclip/blob/main/execution-workspaces.ts) rather 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`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/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.