Workspace Resolution and Isolated Execution Environment in Paperclip AI: A Technical Deep Dive

Paperclip AI isolates every agent run inside a dedicated workspace that combines monorepo root discovery, type-safe workspace contracts, and Git worktree-based process isolation to prevent cross-contamination between executions.

The Paperclip AI platform orchestrates AI agents within complex monorepo structures, requiring robust workspace resolution and isolated execution environment capabilities to ensure deterministic, secure runs. According to the paperclipai/paperclip source code, the system implements a three-layer architecture—discovery, definition, and isolation—that materializes ephemeral execution contexts and tears them down cleanly after each run.

How Paperclip AI Discovers and Validates Workspace Roots

Every request begins with repository root resolution to ensure the server sees the exact source tree the UI and CLI are using. In server/src/services/workspace-runtime.ts, the findWorkspaceRoot() function walks upward from the current working directory until it locates pnpm-workspace.yaml, establishing the monorepo boundary.

Once the root is identified, ensureServerWorkspaceLinksCurrent() (lines 29-45) performs critical validation:

  1. Verifies the server package is a linked Git worktree via isLinkedGitWorktreeCheckout
  2. Scans all workspace packages using discoverWorkspacePackagePaths
  3. Validates that each workspace: dependency in server/package.json resolves to the correct on-disk location
  4. Recreates stale symlinks atomically using fs.symlink if any path is outdated

This prevents "missing-module" runtime crashes that occur when developers swap branches and node_modules links become stale.

// Resolve the repository root from the current working directory
const repoRoot = findWorkspaceRoot(process.cwd());

// Verify that the server's workspace symlinks are up-to-date
await ensureServerWorkspaceLinksCurrent(process.cwd(), {
  onLog: async (stream, chunk) => {
    await logActivity(db, {
      companyId,
      actorType: "system",
      actorId: "workspace_runtime",
      action: "workspace.link.repair",
      details: { stream, message: chunk },
    });
  },
});

Defining Execution Workspaces with Type-Safe Contracts

After discovery, the runtime transforms high-level requests into concrete workspace configurations using the type definitions in packages/shared/src/types/workspace-runtime.ts. The system distinguishes between input strategies and realized workspaces:

  • ExecutionWorkspaceInput: The initial request specifying desired strategy and mode
  • RealizedExecutionWorkspace: The concrete configuration containing absolute cwd, resolved branchName, worktree path, and validation warnings (lines 9-19)

Key enumerations define the isolation semantics:

  • ExecutionWorkspaceStrategyType: project_primary, git_worktree, adapter_managed, or cloud_sandbox
  • ExecutionWorkspaceMode: shared_workspace, isolated_workspace, operator_branch, reuse_existing, or agent_default
  • WorkspaceRuntimeDesiredState: running, stopped, or manual

Each workspace configuration includes lifecycle hooks defined in ExecutionWorkspaceConfig: provisionCommand, runtimeProvisionCommand, teardownCommand, and cleanupCommand.

import { ExecutionWorkspaceInput, ExecutionWorkspaceStrategy } from "@paperclipai/shared";

async function realiseWorkspace(
  input: ExecutionWorkspaceInput,
  strategy: ExecutionWorkspaceStrategy,
) {
  const cwd = resolveConfiguredPath(input.baseCwd, "/tmp/paperclip/workspaces");
  const branch = sanitizeBranchName(input.repoRef ?? "main");

  const realised: RealizedExecutionWorkspace = {
    ...input,
    strategy: "git_worktree",
    cwd,
    branchName: branch,
    worktreePath: `${cwd}/.git-worktree`,
    warnings: [],
    created: true,
  };

  if (strategy.provisionCommand) {
    await executeProcess({
      command: strategy.provisionCommand,
      args: [],
      cwd,
      maxStdoutBytes: 1024 * 1024,
    });
  }

  return realised;
}

Enforcing Process Isolation in Runtime Services

Once materialized, the workspace enforces isolation through the Workspace Runtime subsystem. When ExecutionWorkspaceMode is set to isolated_workspace, the runtime creates a fresh Git worktree using git worktree add under .paperclip/workspaces, ensuring each run receives a clean filesystem that no other process can observe.

The executeProcess() function (lines 31-44 in workspace-runtime.ts) spawns services with strict resource limits:

const { stdout, stderr, code } = await executeProcess({
  command: "node",
  args: ["my-agent.js"],
  cwd: realisedWorkspace.cwd,
  env: sanitizeRuntimeServiceBaseEnv(process.env),
  maxStdoutBytes: 256 * 1024, // DEFAULT_EXECUTE_PROCESS_OUTPUT_BYTES limit
});

if (code !== 0) {
  throw new Error(`Agent exited with ${code}\n${stderr}`);
}

Environment sanitization occurs via sanitizeRuntimeServiceBaseEnv() (lines 52-62), which strips all PAPERCLIP_* variables and secrets before spawning child processes, preventing credential leakage to agent code.

Service lifecycle is tracked in in-memory maps (runtimeServicesById and runtimeServicesByReuseKey) and persisted to the database (workspaceRuntimeServices). The local-service-supervisor.ts module handles process termination and port management, while idle timers (clearIdleTimer) enforce cleanup policies for unused workspaces.

Validation and Safety Mechanisms

Before handing a workspace to an agent, the runtime executes validation chains to detect unsafe states. The inspectGitWorktreeBranchIncoherence() function (referenced around lines 511-531) detects:

  • Mismatched branches between expected and actual state
  • In-progress Git operations or detached HEAD scenarios
  • Dirty working trees that could contaminate results

If a dirty state is detected on a clean worktree where the branch is an ancestor, the system automatically quarantines the dirty state by creating a rescue branch via buildDirtyQuarantineRescueBranch. This allows the agent to run against a clean checkout while preserving uncommitted changes safely.

Validation failures throw WorkspaceRuntimeValidationFailure with a JSON payload (resultJson) that surfaces user-friendly warnings in the UI.

Lifecycle Hooks and Cleanup Policies

The runtime manages workspace birth and death through configurable commands:

  • Provision: provisionCommand runs once when created === true, setting up language runtimes or Docker images
  • Runtime provisioning: runtimeProvisionCommand executes after the workspace is ready but before agent code runs, typically for dependency installation
  • Teardown: terminationLocalService stops stray processes and executes teardownCommand when runs complete
  • Cleanup: For isolated workspaces, the entire worktree directory is deleted after cleanupCommand finishes

These hooks ensure that even if an agent crashes, the system returns to a clean state without orphaned processes or disk pollution.

Summary

  • Three-layer architecture: Workspace resolution and isolated execution environment in Paperclip AI operate through discovery (monorepo root finding), definition (type-safe contract realization), and isolation (Git worktree-based execution).
  • Git worktree isolation: Each isolated workspace receives a unique worktree path under .paperclip/workspaces, guaranteeing filesystem separation between concurrent runs.
  • Automatic link repair: ensureServerWorkspaceLinksCurrent() validates workspace: symlinks on every request, eliminating module resolution errors during branch switches.
  • Environment sanitization: sanitizeRuntimeServiceBaseEnv() removes internal PAPERCLIP_* variables and secrets before spawning agent processes.
  • Dirty state quarantine: The runtime detects incoherent Git states and can automatically create rescue branches to isolate uncommitted changes without blocking execution.

Frequently Asked Questions

How does Paperclip AI isolate agent executions from each other?

According to the source code in server/src/services/workspace-runtime.ts, Paperclip AI achieves isolation through Git worktrees and process-level separation. When using isolated_workspace mode, each run invokes git worktree add to create a distinct working directory under .paperclip/workspaces. The runtime spawns processes via executeProcess() with sanitized environments and tracks them in runtimeServicesById, ensuring no shared state exists between concurrent agent runs.

What happens when a Git worktree is in a dirty state?

The runtime calls inspectGitWorktreeBranchIncoherence() to detect uncommitted changes or branch mismatches. If the worktree is dirty but the branch is an ancestor of the expected state, the system automatically quarantines the dirty state by creating a rescue branch using buildDirtyQuarantineRescueBranch. This preserves the developer's uncommitted work while allowing the agent to execute against a clean checkout. If the state cannot be safely repaired, a WorkspaceRuntimeValidationFailure is thrown.

How does the workspace runtime prevent module resolution errors in the monorepo?

The ensureServerWorkspaceLinksCurrent() function validates that all workspace: dependencies in server/package.json point to the correct on-disk locations within the monorepo. It detects stale symlinks by comparing the current Git worktree state against the resolved package paths, and atomically recreates any broken links using fs.symlink. This ensures the Node.js module resolver always finds the correct source packages regardless of branch changes.

What limits apply to process output in isolated execution environments?

The executeProcess() function enforces a default byte limit of DEFAULT_EXECUTE_PROCESS_OUTPUT_BYTES (256KB in standard configurations) on stdout and stderr streams. This prevents runaway agents from consuming excessive memory or disk space when capturing output. The limit is configurable per execution via the maxStdoutBytes parameter in the process options.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →