paperclip/doc/sandbox-work-folders.md

42 KiB
Raw Permalink Blame History

Sandbox work folders

Shared folders can contain saved files from several sandboxes. The cached-file inspector keeps earlier run failures visible with a View failed run link, separately from the last successful save time and direct file-operation errors.

Finalization runs once per execution, including its file flush and session-state publication. Repeated error cleanup keeps the original failure visible. A later authorized run recovers unsaved edits from the retained sandbox before loading incoming shared files; it does not rewrite the failed run as successful.

Reusable sandbox resume resolves provider configuration from the recorded lease, as workspace operations and cleanup do. This preserves provider-selected defaults such as Daytona's region when the environment leaves them unspecified. Existing configuration and identity checks still reject incompatible reuse; stopping and resuming a compatible lease must reopen the same account-scoped provider handle.

Task workspace binding follows the sandbox's reuseLease setting independently of the native runner's process lifecycle. Both warm and per_turn retain the task's execution workspace and compatible provider session; restarting the provider process is not a request for a fresh sandbox. Local execution and sandboxes with reuse disabled retain their existing behavior.

Session compatibility compares effective workspace settings. A project title or description edit, and startup pinning an inherited workspace mode to the same already-effective mode, do not invalidate the session. Actual project policy, network settings, model, and identity changes remain compatibility boundaries.

Periodic saves are best effort for files that continue changing during a scan; the required final flush must still save the settled working copy or visibly retain it for recovery. A continuously rewritten file missing one periodic save and the existing file-preview polling lag are accepted limitations. Startup latency should be compared with the legacy folder path on an equivalent workload, rather than treating isolated cold-start timings as an independent acceptance gate.

The deployed acceptance entry point is pnpm test:e2e:work-folders:deployed. Set PAPERCLIP_DEPLOYED_STACK_MANIFEST to a JSON manifest matching tests/runner-e2e/deployed-stack.ts, PAPERCLIP_DEPLOYED_STACK_AUTH to a private 0600 JSON file containing baseURL and a normally authorized boardApiToken, and PAPERCLIP_DEPLOYED_STACK_EVIDENCE to an absolute output directory. The token must belong to the manifest's userId and have company access. The harness verifies that identity before file operations or paid agent execution. The harness never launches a local server. It checks the deployed commit and adapter inventory, exercises scoped file APIs, and starts real sandbox tasks for every configured profile, including cold/warm Git state and real timed saves. Cold and warm checks verify actual file bytes in all four scopes, including nested empty executable files. Disposable Git commits use an explicit author and committer environment because sandbox launches clear inherited Git identity. Engine coverage is derived from each live agent's configuration. A warm pass requires the same host-recorded physical sandbox identity and a surviving cache marker; restoring durable files into a replacement is tested separately. The maintainer-approved matrix is Codex, Claude, OpenCode, and Pi in both runner generations (11 CLI/ACP profiles). Only Cursor, Gemini, Grok, and Kimi are deferred for this campaign. Missing required profiles fail the inventory gate. Credentials are not recorded in Playwright reports. These API checks supplement the required browser walkthrough, two real 180-second intervals, and recovery scenarios; passing them alone is not staging acceptance.

The separate three-turn Daytona runner qualification writes into PAPERCLIP_TASK_DIR when the host supplies scoped folders. After each turn it checks that run's final save and reads the exact bytes through the task file API. It still checks the same sandbox, provider session, and ordered turn markers. Runs without a scoped manifest retain the existing host-workspace assertions. The test does not treat a missing host mirror as proof that a scoped file was lost.

The staging matrix covers legacy Codex and Claude with both CLI and ACP, legacy OpenCode and Pi, and native Codex, OpenCode, and ACPX Claude/Codex/Pi. Cursor, Gemini, Grok, and Kimi are excluded from this acceptance campaign by explicit user instruction. Other required profiles must not be silently skipped.

Native Pi uses pi-acp@0.0.33 with the official Linux x64 Pi 0.84.2 standalone executable. The image build verifies the archive and executable SHA-256, then starts an ACP session through the runner's descriptor-based launcher. It does not make a model request. Live staging must still verify the qualified OpenRouter model, work folders, saves, and recovery. The same image exposes this Pi executable to the legacy adapter. Provider-pack shims resolve links before locating their runtime so task-local launch paths remain valid. Because this pinned Pi ACP adapter does not forward MCP tools, the native runner stages a private Pi extension that reads the authenticated run-owned tool catalog. It preserves tool schemas, idempotent call IDs, cancellation, and the bridge's private-tool boundary. The bridge credential is injected at launch and excluded from persisted environment records; project extensions remain untrusted.

Existing tasks and upgrade compatibility

Session fingerprint normalization accepts exact fingerprints produced by the previous algorithm for the same effective configuration, including an inherited workspace mode that startup subsequently pinned. It does not waive model, secret version, or workspace-policy changes. Subsequent session publication writes the normalized fingerprint through the existing persistence path.

If project metadata changed before the first upgraded run, the original timestamp cannot be reconstructed from the old hash. The host can instead verify the exact execution-workspace fingerprint recorded by that session's last successful run. This requires the same company, task, agent, responsible user, conversation, and reused workspace, and matching fingerprints for every other session category. Missing or conflicting evidence still requests a fresh conversation while retaining working files. After normalization, the session is marked so this one-time compatibility path cannot mask later configuration changes.

Legacy Codex and Claude session codecs retain their remote execution identity. Sandbox conversations bind to the physical provider sandbox and environment, so creating a new host lease record for another turn does not discard the conversation. Older records missing this metadata can be repaired from their last successful host run only when the company, agent, responsible user, task, workspace, environment, provider sandbox, working directory, and conversation all match. An explicit conflicting identity is never overwritten. Local and SSH session matching remain separate; a replacement sandbox cannot inherit a conversation merely because its working-directory path is the same. Old codecs that recorded the host checkout path are translated to the sandbox path only when both the saved workspace and its local realization prove that exact host path and project-workspace ID. Arbitrary path changes still reset. Codex configuration refresh replaces only the managed auth/config/skills entries; it preserves the sandbox's rollout files and SQLite state, including WAL files. Those provider-session files stay outside shared work-folder collections. Claude also retains the MCP server identity used by its conversation. A host-verified old record missing that identity may migrate with only the built-in Paperclip server; unknown external MCP server sets do not bypass the existing session compatibility check. Failed sandbox acquisition retains its provisional resume claim immediately. Startup also recovers provisional claims left active by older terminal runs; it does not reclaim an executing run or a pending provider release. A temporary provider startup failure therefore cannot permanently block the task's next run. If folder preparation fails before publishing a new manifest, heartbeat explicitly retains the lease even when the previous run completed a successful final save.

An intentional native session reset (for example, changing the model) can start a new conversation inside the task's retained sandbox. The host authorizes this only when it mints a new logical session ID; the runner atomically claims an absent session directory. Existing task files and previous conversation state remain in place. A normal continuation, restart recovery, existing partial session directory, or saved checkpoint cannot take this fresh-session path.

Older reusable per-turn sandboxes may have a task-owned execution workspace with no explicit reuse preference. Startup can recover that default binding only for the same company, project, and source task. Explicit workspace preferences remain authoritative, and workspace freshness and lease identity checks still apply.

Tasks that have already completed a sandbox run without work-folder persistence keep their original workspace, adapter file-sync/restore behavior, and provider session directories. Upgrading does not move, clean, or replace those files. This compatibility mode persists across turns and sandbox expiry; it does not claim the new scoped-folder or repository-checkpoint durability guarantee for old tasks. New tasks enter the scoped lifecycle below. Automatic migration of an old task's working tree into scoped folders is not performed.

Retained PRP v1 runners continue ordinary native turns without session goals. The host checks the authenticated protocol version before probing or changing a goal, so an optional v2 request cannot disconnect an older runner or block its final suspension and checkpoint. Unauthenticated connections do not provide capability evidence.

Version-1 reusable leases obtain their missing task and responsible-user identity from company-scoped host run records. Reuse still requires matching agent, task, user, environment, workspace, provider, and configuration fingerprint. Missing or conflicting identity records, configuration drift, or a failed resume retain the old sandbox and report a recovery error instead of destroying its only copy. The provider cannot opt a new task into this compatibility mode.

Some older releases retained a sandbox without recording a workspace binding on the task. When that binding and any explicit workspace preference are absent, startup recovers the retained workspace from the matching task, project, agent, responsible user, and sandbox environment. Local execution, explicit workspace choices, and tasks that have entered scoped persistence do not use this fallback. The normal workspace freshness and provider identity checks still apply. After a validated legacy resume, both runner generations adopt the existing sandbox working copy without uploading a replacement Git directory or host overlay. This preserves its index and repository-local state. The legacy outbound merge still saves working files to the host. A missing native sync stamp on a pre-change lease does not make the host copy authoritative.

When an older sandbox image lacks a required runner capability, startup stages the server-resolved runner artifact, including the vendored binary in packaged server builds. The uploaded artifact and the controller's runner identity use the same file. Replacement is atomic and preserves the previous launcher if staging fails; it does not reset the task's working files or provider session.

A follow-up task run waits up to 30 seconds for a terminal predecessor to release its reusable sandbox. While that handoff is pending, startup cannot allocate a second workspace or resume the sandbox concurrently. An active predecessor or an incomplete release reports a retryable resume error and preserves the lease. The next run claims the reusable lease in Postgres before resuming the provider; competing server processes cannot both resume the same released sandbox. An incomplete resume keeps a provisional lease marker until provider verification succeeds. Failed-run cleanup retains that exact lease without stopping or deleting its sandbox, so a retry cannot silently create a replacement. Daytona refreshes the live sandbox state on explicit resume, including externally stopped resources whose cached handles still say running. Terminal sandbox release claims ownership in Postgres before provider calls; duplicate completion paths and an old run's stale lease snapshot cannot stop a newer owner. Native runs persist their selected resource disposition independently of workspace copy-back, so recovery retains a successful warm sandbox consistently.

An uncertain provider stop leaves a durable release claim and reports sandbox_release_recovery_required. Startup cannot resume that resource or create a replacement while its stop may still be in flight. Recovery requires verifying that the original provider operation settled before resolving the matching claim; time passing or an application restart never clears it automatically. The working copy remains retained, and deletion or orphan cleanup is not a fallback. This applies to both runner generations and leaves distinct task/user bindings isolated. If Daytona rejects a command because its cached shell session no longer exists, the provider creates one replacement session and retries within the original command deadline. This applies only to a confirmed rejection before dispatch; errors while polling or reading output never replay a potentially executed command. Concurrent callers share the replacement session. Native ACPX recovery also admits a provider started lazily by model selection. Selection waits for verified process ownership before accepting the configured model; cleanup and out-of-band process launches remain fenced. This matters when reopening a persisted Codex session after stopping its sandbox. The host retains the verified command snapshot and its pinned descriptors until runtime cleanup, so reconnecting for the first turn after model selection can launch again without reopening a mutable executable path. A failed turn-start signal remains observable without crashing a sidecar that consumes only the turn's event stream and result. The new scoped-shell startup setting is not added to an existing unscoped Codex session's protected launch arguments. Its durable provider profile remains unchanged during attachment.

For an active native sandbox run, the app keeps the control-plane journal while the sandbox keeps the runner journal. An app restart must verify the exact remote run, session, runner, and lease identity; a missing controller-side runner file is not evidence of corruption. Recovery checks the remote process marker, Linux process fingerprint, and command-line binding before adopting a live runner. The runner must then authenticate to its existing durable PRP authority. Adoption preserves the provider attempt and does not launch another runner or replace its artifacts. A dead runner can restart only from verified suspended state. Missing or conflicting evidence preserves the controller journal and fails recovery. Verification failures release the claimed execution lease and persist a recovery disposition. Temporary connection failures retry after 30 seconds through the same remote verifier and consume the bounded attempt budget; missing or conflicting authority blocks automatic replacement. The retry keeps remote process identifiers and cannot launch a provider before verification. Host PID checks do not establish remote process ownership. Remote runners have a five-minute reconnect grace period for app replacement; liveness probes share one request in flight and run at most once per second.

Acceptance must resume representative pre-upgrade legacy and native tasks with committed, staged, unstaged, and untracked work, verify their original paths and usable continuation, and exercise their existing restore mechanism after a sandbox restart. A newly created task passing the scoped-folder matrix does not establish upgrade compatibility.

New sandbox tasks

Sandbox runs use the operating-system user's home directory. Both legacy adapters and the native runner enter the same host-owned lifecycle before dispatch. Local execution keeps its existing workspace and home behavior. Legacy ACP proxies use a private host staging directory while agent sessions start in the sandbox home. After login-shell initialization, remote agents restore the managed Git PATH ahead of paths added by the profile. This preserves both the Git launcher and custom runtime initialization. For API-key Codex ACP runs, the adapter writes the explicit key to an owner-only login file in the staging copy; host credentials stay unchanged. Per-run GitHub launchers declare their own CommonJS package scope so warm runs inside ES-module repositories can still execute Git and GitHub CLI commands. Native runner launches carry the validated scoped paths through both runnerds Rust sidecar filter and the ACPX JavaScript launch filter, including the explicitly controller-projected GitHub broker environment. ACPX does not inherit ambient host GitHub credentials or shell startup hooks. CLI configuration remains in its private runtime directories. Native OpenCode preserves the same explicit GitHub binding through both its runner proxy and provider process. It does not inherit repository credentials or shell startup hooks from the host; provider diagnostics redact the projected capabilities and credential configuration values. Warm sandbox task bindings persist independently of the experimental isolated workspace setting. Only the active host run can establish that binding; the setting still controls user-configurable worktree operations.

$HOME/
  task/      current issue's working files
  agent/     current agent's durable files
  user/      responsible user's private Paperclip files
  project/   current project's shared files
  repos/     task-specific project repository checkouts
  .codex/    CLI configuration and provider session state
  .cache/    disposable caches

An absent task, user, or project produces an empty, unbound directory. The user/ collection never copies a person's computer home directory. Existing managed agent workspace files are imported once, excluding CLI homes, caches, Git metadata, and conventional credential directories. Task attachments become editable working copies whose filenames include attachment IDs; original uploads remain unchanged. Task plans and documents are not materialized.

The agent starts in $HOME. PAPERCLIP_PRIMARY_REPO and the workspace context identify the repository for project commands. AGENT_HOME and PAPERCLIP_{TASK,AGENT,USER,PROJECT,REPOS}_DIR expose the bound directories. Legacy adapters use the host-bound sandbox home for both CLI launch and skill discovery; a private per-run runtime directory must not override it. CLI-specific configuration remains separately staged beneath that home. Local and SSH homes are unchanged. Sandbox Codex tool commands preserve the environment initialized by the adapter: login-shell execution and shell snapshots are disabled so image profiles cannot replace the managed Git PATH. This applies to CLI and ACP execution in both runner generations; local execution keeps its existing settings. Legacy and native Codex ACP select the provider's agent-full-access initial mode only inside a validated external work-folder sandbox with an explicit approve-all binding. This avoids an inner network namespace that prevents tools from reaching the sandbox's loopback API and Git-credential callback bridge. Local execution and the approve-reads / deny-all modes retain their existing policy. CLI state is separate from the four shared collections. A change of task, agent, responsible user, or project cannot reuse a sandbox with another binding.

The cache inspector shows “Waiting for first save” until an active run has a completed checkpoint. Missing or unavailable sync status is never labeled saved.

Storage and synchronization

Postgres stores company/owner bindings, paths, executable bits, current object references, trash entries, retry receipts, and each sandbox's sync baseline. Contents use the configured StorageProvider: S3 or self-hosted local_disk. The sandbox disk is a working copy, not the durability authority. With local disk object storage, operators must persist and back up that storage directory alongside Postgres. S3 recovery needs the database and bucket; it does not need the original sandbox or application workspace volume.

Startup hydrates only the four bound collections. A warm startup first saves uncheckpointed local changes and then downloads changed incoming files. There is no background incoming refresh while an agent edits. Explicit refresh is queued until the run stops, after a successful final flush. If another writer changes a shared file between listing and download, incoming transfer uses the downloaded version's size, hash, and executable bit. Its sync baseline records those same bytes, so an unchanged copy cannot overwrite a later shared edit.

Providers with native file synchronization or explicit streaming-stdin support hydrate files through bounded stdin batches instead of one remote command per small chunk. A batch carries at most 4 MiB of file bytes and 256 operations; each file still uses confined paths, SHA-256 validation, and atomic publication. Before publishing an incoming batch or applying incoming deletions, the host persists the intended versions in Postgres. After a failed or lost response, the next run reconciles those intents against observed disk contents before saving outgoing changes. Imported bytes therefore cannot overwrite a newer shared version by being mistaken for an agent edit; actual subsequent edits still synchronize normally. Failed explicit refreshes retain the same intent. Other providers keep the small-argument transport. Incoming storage responses are prefetched sixteen at a time without consuming queued response bodies, and closed if transfer fails. The transport keeps its separate bounded batch buffer. Repository restores use the same path, then recreate confined repository links.

Read-only sandbox commands retry transient connection failures and HTTP 502/503/504 responses up to three attempts within one 120-second deadline. Script failures, invalid responses, and authorization failures are not retried. An incoming bulk batch has a unique ID and a signed receipt in its private staging directory. If the provider loses the response, the host checks that receipt: a completed batch is acknowledged without publishing its files again; only a missing claim permits resubmitting the same batch ID. An atomic claim prevents two concurrent submissions from applying the same batch twice. A running or interrupted batch is never replayed. Its bounded outcome check either observes completion or fails visibly and retains the working copy for the next run's existing intent reconciliation. This does not make arbitrary sandbox commands or repository mutations retryable.

Host-owned GitHub launcher staging also retries transient transport failures up to three times within a single 15-second deadline per file or permission step. The same run-specific file is locked, hash-checked, and atomically replaced, so a lost reply after a successful upload does not rewrite the file on retry. Cancellation, script failures, and invalid responses stop setup. This retry is limited to launcher preparation; it never replays an agent or Git command.

Repository checkpoints transfer up to sixteen distinct batch-readable blobs of at most 1 MiB concurrently, plus at most four larger streaming blobs. Transports without batched reads retain the four-stream limit. Identical files share one content-addressed upload. Small-file reads are grouped into at most 1 MiB and 64 files per remote command, with at most four read batches cached per checkpoint and sixteen additional batches held by active readers. Retries bypass that cache and reopen the actual file. All active transfers must settle, and a second filesystem scan must match, before the complete checkpoint reference can advance. Scoped-file retry receipts and last-write-wins publication remain ordered.

Outgoing checkpoints run every 180 seconds, with at most one in flight, and a final flush when execution stops. File signatures include content and executable state. Unchanged stale working copies do not overwrite newer shared files. Changed files use server-accepted last-write-wins. Operation IDs persist before transfer so a lost response retries the same operation rather than overwriting a later writer. A failed save remains visible and prevents lease cleanup from destroying the working copy. Providers with resume support can recover a retained lease on the next run with the same identity/configuration, including a lease originally configured as ephemeral.

S3 uploads observe source completion and cancel failed requests before reporting success. Optional SDK streaming checksums are disabled to avoid an unhandled digest rejection when a source file changes during transfer. Work-folder SHA-256 verification and complete-checkpoint publication remain required. A changing source must fail its save without stopping the application or another run. Scoped-file and repository-blob uploads retry transient network errors and retryable HTTP responses up to three attempts, using the same object key. Each attempt opens a fresh source and verifies its complete size and SHA-256; the previous request and reader must settle before another attempt starts. Changed content, ownership errors, and authentication failures are not retried. Exhausted retries retain the previous complete checkpoint and the recoverable working copy.

Deletion moves files to recoverable trash. Restore rejects path collisions. Explicit purge and permanent owner deletion schedule object cleanup through a durable deletion journal. Overwritten scoped-file content is not versioned. The current complete repository checkpoint remains retained while its task binding exists. Superseded manifests and unused blobs enter a 24-hour deletion queue; shared blobs in the current checkpoint stay protected. A save that takes more than one hour fails visibly and must retry before its old references expire. The scheduler retries object cleanup every three minutes; disabled heartbeat scheduling also disables this cleanup sweep.

Every file API checks company and owner authorization. User collections are available only to the current user and their bound, authorized agent run. Generic company access does not grant user-file access. Background transfers also recheck the responsible user's active membership before saving.

Paths reject traversal, control characters and reserved runtime segments. Scoped files cannot be symbolic links or hard links. Linux transport pins parent directory descriptors and uses no-follow opens. Repository symlinks must stay inside their checkout, outside .git. Transfers stream in bounded chunks; individual files are limited to 1 GiB, scans to 100,000 entries, and UI previews to 8 MiB.

Repositories

Each task owns independent clones of all project workspaces with a repository URL. Names derive from repository names, with stable workspace-ID suffixes on collisions. Initial clones use existing Git credentials and starting-ref policy; the primary clone also honors the task's configured branch. Warm starts never reset branches, clean edits, or rerun completed setup. Added repositories are prepared at the next startup; removed bindings retain saved work. Replacement sandboxes rerun project setup to restore excluded dependencies; an existing warm checkout keeps both its completed setup and reusable caches.

A complete repository checkpoint includes Git objects, refs, HEAD and index, tracked working files, and nonignored untracked files. It excludes dependencies and generated ignored caches, Git credentials/configuration, hooks, and private runtime state, including nested plugin repositories inside private runner caches. Checkpoints reject in-progress Git locks and a tree that changes during scanning. The database pointer advances only after all required objects and the manifest have been saved. Restores verify ownership and hashes, then publish the restored directory atomically. Git origin configuration is recreated from the host binding. Linked worktrees and submodules using external .git directories are not supported by this checkpoint format.

API and UI

Cached-file inspection is an opt-in development tool. Enable Allow viewing cached task files in Experimental settings under Paperclip Developer Mode. Task properties then show Files → View cached files beneath Execution in the Workspace section. The dialog previews and downloads the task, project, assigned agent, and responsible user's saved collections. Missing bindings are empty; private user files are available only to that user. Existing server ownership checks apply independently of the visibility setting.

The inspector clearly identifies saved copies that can lag behind agent edits. Checkboxes select files; the move-to-trash action appears only for a nonempty selection. Each scope has Files and Trash tabs, with restoration from retained trash. Upload, folder creation, sandbox refresh, and permanent purge controls remain outside this inspector. It does not list repositories or the live sandbox disk. Agent, project, and profile pages have no standalone stored-file entry points. Live sandbox filesystem inspection remains a separate future feature. The editable stored-file browser remains in Storybook for design reference.

All routes start at /api/companies/:companyId/work-folders/:scope/:ownerId:

  • GET /: paginated active files or trash (trash=true).
  • GET /content?path=...: confined download/preview stream.
  • PUT /content?path=...: raw application/octet-stream upload; supports Idempotency-Key, X-File-Content-Type, and X-File-Executable.
  • POST /operations: idempotent mkdir, delete, restore, or purge.
  • GET /sync: save state, last successful save, errors, and refresh state.
  • POST /refresh: request refresh at the active run's safe boundary.

Acceptance gate

The Cloud app image includes a build-owned remote provider pack at /opt/paperclip-runner/provider-pack and configures PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH to that directory. Native OpenCode and ACPX runs verify the sandbox's installed pack against this manifest. Reuse requires a valid full manifest digest and matching content, including artifact hashes, the distribution tree, dependency pins, platform, and Node requirements. The source revision remains provenance; a revision-only difference does not require retransferring identical contents. App and sandbox builds omit the redundant dist/bin/paperclip-runnerd from the pack because that executable is shipped and verified separately. If content differs, the host stages its complete pack before launch. The pack is built from the app revision, includes the production lockfile and artifact hashes, and must pass its provider-launch checks during the image build. It belongs to the app image, not the workspace volume or a scoped file collection. Ordinary local execution is unchanged.

When diagnosing startup delays, distinguish scoped-file hydration from native runtime preparation. work_folder.prepared records the intended layout before hydration completes. provider_pack.verify_preinstalled reports installed-pack verification; a fallback within runner.artifact.prepare can transfer gigabytes independently of task files. Acceptance must prove that a matching image uses its installed pack instead of silently relying on that fallback.

Both provider-pack build stages install from the immutable docker/daytona-runner/provider-dependencies.lock.yaml using pnpm 9.15.4 and a frozen install. This deployment input is separate from the CI-owned app lockfile. Each stage checks its SHA-256 before installing, builds the provider entrypoints under that graph, and records the installed lock in the pack. Refresh this lock from a reviewed CI-resolved artifact when provider manifests or patches change, update both expected hashes, and qualify a new sandbox image. Matching source files alone does not prove matching dependencies: acceptance also compares the actual app and sandbox production-lock hashes.

The qualification entry build-provider-pack.mjs (also exposed as pnpm --filter @paperclipai/paperclip-runner build:provider-pack) requires Docker with BuildKit and builds the canonical linux/amd64 provider stage. It uses the same digest-pinned Node interpreter, dedicated dependency graph, and fresh TypeScript compilation as the sandbox image; host node_modules, CI's root lock and previously compiled outputs are not assembly inputs. Both Docker stages call the low-level assembler directly, avoiding recursion. The entry verifies exported manifest and artifact bytes before atomically replacing its output. Failed builds or validation preserve the previous pack. This produces a local artifact and does not publish an image or release.

Native and legacy Git credential callbacks honor the same experimental duplex setting and provider capability gates. When streaming is disabled or unavailable, the file bridge remains supported. Credential acquisition allows 35 seconds per request so the bridge can return its response within its 30-second window. Transient transport failures receive at most three attempts within a 75-second overall budget; authorization denials and invalid responses are not retried. Only credential acquisition is retried, before starting Git or gh; repository operations are never replayed. Acceptance must exercise both transport paths and record which one was actually selected.

Controller-requested bridge shutdown marks the transport complete before closing its provider channel. A connection loss observed earlier remains latched; closing the native Git bridge after execution must not invent a transport failure.

Legacy sandbox cancellation stops the owned remote CLI process group or ACP process session before waiting for run teardown and the final file flush. The host sends a command-scoped cancellation marker for CLI execution; remote PIDs are never passed to the host process killer. Cancellation is persisted before stopping execution so its exit cannot admit an automatic retry. Scope registration rechecks durable run status, and cancellation rechecks newly registered scopes before acknowledgement, preventing cancelled startup work from dispatching. The supervisor preserves externally delivered child termination signals. Failed stop requests remain visible and can be retried explicitly. Local and native runner cancellation retain their existing authorities. Immediate recovery honors the same operator-cancellation attribution as periodic recovery, so cancelling a run does not synthesize a continuation that restarts its sandbox. Explicitly queued work can still run through normal promotion. Native failure recovery also checks the durable cancellation intent under the run lock before scheduling a retry. An interrupted turn without a semantic result must preserve cancellation instead of reporting a provider failure. Terminal cancellation clears stale retry retention flags so final flushing and sandbox release still run.

Automated tests do not qualify a deployed runner image. Before merging, use a new pinned staging stack with the branch's Cloud image and matching migrator. The deployed harness must target that tenant URL without launching a local server. Enumerate every sandbox-capable adapter/engine and native profile exposed by the stack; missing credentials or skipped required profiles block acceptance. Verify that standalone stored-file entry points are absent from the UI, then enable experimental cached-file inspection and test the task dialog's four scopes, previews, downloads, checkbox selection, trash, restoration, and save feedback in the deployed browser. Record API/runner operations, two actual 180-second intervals, short-run flushes, independent task checkouts, identity/privacy boundaries, interrupted saves, and recovery without the original sandbox or app volume.

Repository acceptance must also read the private repository through the managed Git launcher, for example with git ls-remote origin HEAD. Do not count a model's PATH changes or credential workarounds as a pass. Native Git credential callbacks use the internal API origin, as legacy callbacks do; the public Cloud tenant origin requires a browser session.

Native continuation validates the full control-plane journal with the same 64 MiB bound as the runner transport. Tool output in that journal can exceed 2 MiB without invalidating its identity. Oversized, malformed, foreign-session, and unverifiable state still fail closed and remain recoverable in quarantine. The cached-file routes initialize storage once per router after authorization; every request still checks current owner access. If a run ends while its save is starting or in progress, the inspector reports the interruption and keeps the previous successful save time visible, including across sandbox replacement.

Passing acceptance does not authorize a merge or mainline release. Both require the user's explicit sign-off.

Cloud's deploy:stack tool can deploy an unpublished branch commit to an explicit staging tenant. Run it from the Cloud checkout with the staging tenant URL and full commit SHA. Pin the target stack first. Require the tool's migration and readiness gates plus an authenticated tenant health response with the exact SHA. This does not authorize a mainline release or a fleet-default promotion.

Staging migrator artifacts use an immutable object-storage prefix. The Docker workflow's optional staging_artifact_base_url input builds DB/shared tarballs and an integrity manifest as a GitHub Actions artifact; it has no release-write permission. Supply staging_lock_sha256 with the reviewed SHA-256 of the resolved pnpm 9.15.4 lockfile. The migrator and both app-image builds verify that digest before installing dependencies, failing if registry resolution has changed. Transfer those artifacts to the staging bucket using conditional creates, publishing the manifest last. Do not create GitHub releases or publish npm packages for this flow. The reusable Cloud build workflow receives those staging inputs explicitly; splitting the workflow must not drop the app/migrator dependency-integrity gate.

Cloud enables this lane only in staging through CLOUD_HARNESS_STAGING_ARTIFACT_BASE_URL. Resolve preview:<full SHA> through its authenticated deployment API, then target the dedicated pinned stack. The configured origin, commit identity, artifact integrity, migration coverage, dependency lockfile and tenant readiness are checked before acceptance. Preview artifacts cannot become the fleet default. Record both the build workflow and the resulting object identities with the acceptance evidence.

On graceful app shutdown, idle native sandbox sessions are parked and checkpointed before application services and the database close. The drain runs with bounded concurrency and a 30-second deadline; failed or timed-out checkpoints are logged as incomplete. Active native turns keep their existing restart/reattach behavior. Local and SSH session shutdown behavior is unchanged. A hard process kill cannot guarantee a provider-session checkpoint; scoped files and repository durability remain limited to the last successfully published work-folder checkpoint.

Deployments must allow enough graceful shutdown time for that drain and other server cleanup. On Railway, configure at least 60 seconds of draining time for the service before testing an app redeployment; the platform default is zero. This is a deployment prerequisite, not a fleet-default promotion. When upgrading from a release without the idle-session drain, park warm native sessions and verify their completed harness checkpoints before stopping the old app.

Native OpenCode binds each validated completion result to the current provider process and turn before the controller can interrupt it. This matches the semantic-tool response path. A shutdown interruption must preserve that exact completed-turn authority so the session can be suspended and checkpointed. Invalid results and conflicting identities still fail validation.

Warm native Codex attachment drains bounded informational deprecation notices that arrive after the prior turn and its readiness probe. Notices naming another turn, new work, and provider requests still block attachment.

The app checks the runners passive-notice capability before reusing an image binary. An older binary is replaced with the apps compatible artifact through a temporary file and atomic rename, preserving image symlink targets and the previous launcher on interrupted uploads. The durable checkpoint contract stays at version 2 so existing native session backups remain restorable.

Warm continuity qualification reads the full run record before selecting the persistence contract; company run listings omit the scoped-folder manifest. Each completed scoped turn must have its own successful final save before the harness reads cached bytes. The sync API exposes finalCheckpointAt from the run manifest, distinct from periodic lastSavedAt. Qualification requires a successful terminal run and finalization/save timestamps within that run; a periodic save cannot substitute for completed finalization. Legacy host-workspace fallback is used only when the full run record has no scoped manifest.

The three-turn fixture supplies a shell script that compares exact bytes before appending and after writing. Missing or changed prior content fails without repair; repeating the first turn cannot truncate existing work. This avoids model-generated byte-count arithmetic while retaining independent per-turn persistence checks and the same-sandbox, same-provider continuity requirements.

Native continuation retains its sandbox, runner-instance identity, provider conversation, and files across runs. The existing GitHub identity contract rotates the provider process for each run-scoped capability. Qualification requires an explicit controller rotation event for that exact transition before accepting a changed PID/process fingerprint; other process changes still fail. See GitHub execution identity and the run-log contract.

Process metadata updates apply only while their run is active and unfinished. Late callbacks from warm-session inspection or maintenance cannot rewrite a completed run's process identity. Remote native process timestamps come from the validated remote marker; an unrelated host process with the same PID must not replace them. Active local execution retains its host process lookup.