From 5a16ef77c0f76b5fc0cfae837dbc6f8ab1da2df9 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 10 Oct 2026 10:21:55 -0500 Subject: [PATCH] Isolate persistent computer runtime assets by agent session Root runner binaries, harness installs, provider packs, and session state under the trusted persistent agent home and normalized session. Keep the shared project working directory unchanged and preserve existing sandbox paths. Co-Authored-By: Paperclip --- .../native-session-executor.test.ts | 26 +++++++++++++- .../native-runtime/native-session-executor.ts | 34 +++++++++++-------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 8d85ab20b7..350cbbb3b1 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -373,6 +373,7 @@ import { syncRemoteRunnerDirectoryOut, verifyNativeHarnessBackup, shouldRestoreNativeHarnessBackupIntoSandbox, + remoteRunnerStorageRoots, } from "./native-session-executor.js"; beforeEach(() => { @@ -710,6 +711,28 @@ describe("remote runner launch fingerprint compatibility", () => { }); }); +describe("persistent computer runtime storage", () => { + it("isolates agents and sessions sharing one project while keeping warm roots stable", () => { + const target = (agent: string) => ({ kind: "remote", transport: "computer", remoteCwd: "/shared/project", + fileAuthority: { kind: "remote-persistent", root: "/shared/project", placementId: "shared", agentHome: `/home/user/paperclip/company/agents/${agent}` }, + } as never); + const a = remoteRunnerStorageRoots(target("a"), "session-1"); + const b = remoteRunnerStorageRoots(target("b"), "session-1"); + expect(a.runtimeRoot).not.toBe(b.runtimeRoot); + expect(a.runtimeRoot).toContain("/agents/a/.paperclip-runtime/"); + expect(a.sessionRoot).toBe(a.runtimeRoot); + expect(remoteRunnerStorageRoots(target("a"), "session-1")).toEqual(a); + expect(remoteRunnerStorageRoots(target("a"), "session-2").runtimeRoot).not.toBe(a.runtimeRoot); + expect((target("a") as { remoteCwd: string }).remoteCwd).toBe("/shared/project"); + expect(() => remoteRunnerStorageRoots({ kind: "remote", transport: "computer", remoteCwd: "/shared/project" } as never, "session")).toThrow("runner_remote_storage_home_missing"); + }); + it("preserves existing sandbox runtime and session paths", () => { + const roots = remoteRunnerStorageRoots({ kind: "remote", transport: "sandbox", remoteCwd: "/workspace" } as never, "session-1"); + expect(roots.runtimeRoot).toBe("/workspace/.paperclip-runtime/paperclip-runner"); + expect(roots.sessionRoot).toBe(`${roots.runtimeRoot}/sessions/${createHash("sha256").update("session-1").digest("hex")}`); + }); +}); + describe("remote runner process supervision", () => { it("admits a valid computer identity when the supervised RPC takes longer than two seconds", async () => { vi.useFakeTimers(); @@ -13439,6 +13462,7 @@ describe("runnerd provider runtime wiring", () => { runnerIngressAuthorized: true, runnerRemoteCodexNpmSpec: override, runnerExecutionTarget: { kind: "remote", transport: "computer", remoteCwd: "/workspace", + fileAuthority: { kind: "remote-persistent", root: "/workspace", placementId: "workspace", agentHome: "/home/user/agent" }, environmentId: "environment", leaseId: "lease", providerKey: "boat", effectiveCapabilities: { runnerWebSocketIngress: true }, runner: { execute: remoteExecute }, processRunner: { execute: remoteExecute }, @@ -13452,7 +13476,7 @@ describe("runnerd provider runtime wiring", () => { await expect(transport.controlPlaneRegistration({})).rejects.toThrow("reached-private-codex-verification"); expect(remoteExecute).toHaveBeenCalledWith(expect.objectContaining({ command: "npm", - args: ["install", "--prefix", "/workspace/.paperclip-runtime/paperclip-runner/harnesses/codex", "--no-audit", "--no-fund", expected], + args: ["install", "--prefix", `/home/user/agent/.paperclip-runtime/paperclip-runner/sessions/${createHash("sha256").update(execution.session.normalizedSessionId ?? `session-${execution.binding.runId}`).digest("hex")}/harnesses/codex`, "--no-audit", "--no-fund", expected], })); expect(remoteExecute.mock.calls.filter(([command]) => command.command === "npm")).toHaveLength(1); expect(remoteExecute.mock.calls.some(([command]) => command.args?.includes("--global"))).toBe(false); diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 7bfe19a9ad..ecd9244fe8 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -1697,6 +1697,18 @@ function nativeSessionKey(execution: NativeExecutionInput): string { ); } +/** Persistent computer artifacts belong to an agent session, independent of a shared repo cwd. */ +export function remoteRunnerStorageRoots( + target: Extract, + sessionKey: string, +) { + const home = target.transport === "computer" ? target.fileAuthority?.agentHome : target.remoteCwd; + if (!home || !posix.isAbsolute(home)) throw new Error("runner_remote_storage_home_missing"); + const base = posix.join(home, ".paperclip-runtime", "paperclip-runner"); + const sessionRoot = posix.join(base, "sessions", createHash("sha256").update(sessionKey).digest("hex")); + return { runtimeRoot: target.transport === "computer" ? sessionRoot : base, sessionRoot }; +} + export function nativeSessionWorkspaceScope(execution: { binding: Pick; workspace: NativeExecutionInput["workspace"] }) { if ("access" in execution.workspace) return { kind: "none" as const }; // Projectless local runs use the heartbeat run id as a durable placeholder @@ -11417,10 +11429,10 @@ async function prepareRemoteDotRunner({ input, target, runner, root, identity }: identity: NonNullable[1]>["dotRunnerOptions"]>["identity"]; }) { const runnerBinary = input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary(); - const remoteRoot = posix.join(target.remoteCwd, ".paperclip-runtime", "paperclip-runner"); + const storage = remoteRunnerStorageRoots(target, identity.normalizedSessionId); + const remoteRoot = storage.runtimeRoot; const remoteBinary = posix.join(remoteRoot, "bin", "paperclip-runnerd"); - const runnerStateDirectory = posix.join(remoteRoot, "sessions", - createHash("sha256").update(identity.normalizedSessionId).digest("hex"), "runner"); + const runnerStateDirectory = posix.join(storage.sessionRoot, "runner"); const requiredMode = resolveRemoteRunnerTransportMode({ target, runnerIngressAuthorized: input.runnerIngressAuthorized === true }); const artifact = readRunnerdArtifactBinding(runnerBinary); const prepare = async () => { @@ -11687,13 +11699,10 @@ async function createRunnerdBackendWithinSessionClaim( return { ...backend, descriptor: () => backend.descriptor(), openSession: input => backend.openSession(input), recoverSession: (snapshot, options) => backend.recoverSession!(snapshot, options), bindManagedSession: session => session }; } - const remoteRuntimeRoot = remoteTarget - ? posix.join( - remoteTarget.remoteCwd, - ".paperclip-runtime", - "paperclip-runner", - ) + const remoteStorage = remoteTarget + ? remoteRunnerStorageRoots(remoteTarget, nativeSessionKey(input.execution)) : null; + const remoteRuntimeRoot = remoteStorage?.runtimeRoot ?? null; const requiresRemoteProviderPack = remoteTarget !== null && (input.execution.provider.kind === "opencode" || @@ -11764,12 +11773,7 @@ async function createRunnerdBackendWithinSessionClaim( ) : posix.join(remoteRuntimeRoot, "bin", "codex") : null; - const remoteSessionDigest = createHash("sha256") - .update(nativeSessionKey(input.execution)) - .digest("hex"); - const remoteSessionRoot = remoteRuntimeRoot - ? posix.join(remoteRuntimeRoot, "sessions", remoteSessionDigest) - : null; + const remoteSessionRoot = remoteStorage?.sessionRoot ?? null; const remoteStateDirectory = remoteSessionRoot ? posix.join(remoteSessionRoot, "runner") : undefined;