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:
- Verifies the server package is a linked Git worktree via
isLinkedGitWorktreeCheckout - Scans all workspace packages using
discoverWorkspacePackagePaths - Validates that each
workspace:dependency inserver/package.jsonresolves to the correct on-disk location - Recreates stale symlinks atomically using
fs.symlinkif 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 modeRealizedExecutionWorkspace: The concrete configuration containing absolutecwd, resolvedbranchName, worktree path, and validation warnings (lines 9-19)
Key enumerations define the isolation semantics:
ExecutionWorkspaceStrategyType:project_primary,git_worktree,adapter_managed, orcloud_sandboxExecutionWorkspaceMode:shared_workspace,isolated_workspace,operator_branch,reuse_existing, oragent_defaultWorkspaceRuntimeDesiredState:running,stopped, ormanual
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:
provisionCommandruns once whencreated === true, setting up language runtimes or Docker images - Runtime provisioning:
runtimeProvisionCommandexecutes after the workspace is ready but before agent code runs, typically for dependency installation - Teardown:
terminationLocalServicestops stray processes and executesteardownCommandwhen runs complete - Cleanup: For isolated workspaces, the entire worktree directory is deleted after
cleanupCommandfinishes
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()validatesworkspace:symlinks on every request, eliminating module resolution errors during branch switches. - Environment sanitization:
sanitizeRuntimeServiceBaseEnv()removes internalPAPERCLIP_*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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →