docs: plan remote workspace execution
This commit is contained in:
parent
4a7f760d25
commit
09903e120f
1 changed files with 492 additions and 0 deletions
492
specs/v2/remote-workspace-execution.md
Normal file
492
specs/v2/remote-workspace-execution.md
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
# Remote Workspace Execution
|
||||
|
||||
Status: implementation plan
|
||||
|
||||
Tracking: Organizer item `2ff92129`
|
||||
|
||||
## Goal
|
||||
|
||||
Prove that one OpenCode V2 Session can operate on a real hosted development
|
||||
environment whose checkout does not exist on the OpenCode server host.
|
||||
|
||||
The first slice must support:
|
||||
|
||||
- one stable Workspace identity referenced by a Session Location;
|
||||
- one provider-backed sandbox for that Workspace;
|
||||
- reconnecting to the existing provider sandbox;
|
||||
- reading, writing, editing, globbing, and grepping Workspace files;
|
||||
- running and interrupting foreground shell commands;
|
||||
- retaining Workspace files across provider suspension and reconnection;
|
||||
- local Locations continuing to behave exactly as they do today.
|
||||
|
||||
The design must permit Modal, Daytona, and E2B implementations without placing
|
||||
provider concepts in Session, Location, or tool APIs.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
The first slice does not include:
|
||||
|
||||
- Slack integration or other product surfaces;
|
||||
- background or detached commands;
|
||||
- PTY sessions;
|
||||
- filesystem watches;
|
||||
- user-visible snapshots, Session revert, or Workspace forks;
|
||||
- concurrent mutating Sessions in one Workspace;
|
||||
- provider migration;
|
||||
- arbitrary plugin code transparently using the remote filesystem;
|
||||
- post-crash retry of ambiguous provider or tool work;
|
||||
- running the Session runner inside the sandbox;
|
||||
- clustering Session execution.
|
||||
|
||||
## Domain Model
|
||||
|
||||
### Session
|
||||
|
||||
A Session owns conversation and agent execution history. The Session runner,
|
||||
LLM calls, tool registry, permissions, durable events, and tool settlement stay
|
||||
in the central OpenCode server.
|
||||
|
||||
### Workspace
|
||||
|
||||
A Workspace is the stable project environment referenced by
|
||||
`Location.workspaceID`. It may be the primary local checkout, a local git
|
||||
worktree, or a provider-backed hosted environment.
|
||||
|
||||
Workspace identity is orthogonal to Session identity. The first product slice
|
||||
may create one primary Session per Workspace, but this is policy rather than an
|
||||
identity invariant.
|
||||
|
||||
The Workspace record stores placement metadata. It does not mirror or
|
||||
synchronize files, dependencies, or Git state. The environment filesystem is
|
||||
authoritative.
|
||||
|
||||
### Provider Sandbox
|
||||
|
||||
A provider sandbox is implementation machinery backing a hosted Workspace. Its
|
||||
provider identifier and lifecycle state belong to the Workspace placement, not
|
||||
to Session or Location.
|
||||
|
||||
The Workspace ID remains stable when the provider reconnects, resumes, or
|
||||
recreates its compute from retained state.
|
||||
|
||||
## API Shape
|
||||
|
||||
Keep provider lifecycle separate from environment capabilities.
|
||||
|
||||
```ts
|
||||
interface SandboxProvider {
|
||||
readonly create: (input: CreateInput) => Effect.Effect<SandboxBinding, CreateError>
|
||||
readonly connect: (binding: SandboxBinding) => Effect.Effect<SandboxConnection, ConnectError, Scope.Scope>
|
||||
readonly suspend: (connection: SandboxConnection) => Effect.Effect<SandboxBinding, SuspendError>
|
||||
readonly reconcile: (binding: SandboxBinding) => Effect.Effect<SandboxBinding, ReconcileError>
|
||||
readonly delete: (binding: SandboxBinding) => Effect.Effect<void, DeleteError>
|
||||
}
|
||||
|
||||
interface SandboxConnection {
|
||||
readonly binding: SandboxBinding
|
||||
readonly environment: SandboxEnvironment
|
||||
}
|
||||
```
|
||||
|
||||
`SandboxBinding` is opaque provider state persisted with the Workspace. Core
|
||||
does not inspect it beyond selecting the provider that owns it. Persisted
|
||||
binding data uses provider-specific Effect Schemas whose encoded form is
|
||||
`Schema.Json`, not `unknown`. Both `connect` and `suspend` may replace the
|
||||
binding. This is necessary for providers such as Modal where restoring a
|
||||
filesystem snapshot creates a new provider Sandbox ID.
|
||||
|
||||
A replacement binding must remain reconnectable and include enough state to
|
||||
identify any retired provider resources. Workspace persists it before exposing
|
||||
a new connection or destroying the previous durable resource, then calls the
|
||||
provider's idempotent `reconcile` and persists the simplified binding it
|
||||
returns. A crash at either boundary therefore leaves a valid binding whose
|
||||
cleanup can resume on the next lifecycle operation rather than an
|
||||
undiscoverable sandbox or snapshot. Creation compensates by deleting provider
|
||||
state if the initial Workspace write fails. `CreateInput` carries a
|
||||
caller-generated stable provider identity derived from the Workspace ID, and
|
||||
provider creation is idempotent or discoverable by that identity. Workspace
|
||||
uses interruption-masked acquire/use/release around allocation and initial
|
||||
persistence, so interruption after the binding becomes known either commits
|
||||
the Workspace or deletes the allocation. Automatic retry of ambiguous command
|
||||
work remains out of scope.
|
||||
|
||||
```ts
|
||||
interface SandboxEnvironment {
|
||||
readonly platform: "linux"
|
||||
readonly directory: string
|
||||
readonly files: WorkspaceFileBackend
|
||||
readonly process: ChildProcessSpawner["Service"]
|
||||
readonly shell: WorkspaceShell
|
||||
readonly ripgrep: Effect.Effect<string, UnsupportedCapabilityError>
|
||||
}
|
||||
|
||||
interface WorkspaceShell {
|
||||
readonly executable: string
|
||||
readonly args: (command: string) => readonly string[]
|
||||
readonly environmentOverrides: Readonly<Record<string, string>>
|
||||
readonly detached: boolean
|
||||
}
|
||||
```
|
||||
|
||||
Core exposes these capabilities through a Location-scoped
|
||||
`WorkspaceEnvironment.Service`. The process-global Workspace service owns one
|
||||
connection manager per Workspace ID. Its Effect `RcRef` acquires a scoped
|
||||
`SandboxConnection` lazily, so constructing or inspecting a Location does not
|
||||
wake provider compute. Each Workspace operation borrows the connection in a
|
||||
scope; concurrent operations share it, and an idle TTL releases the provider
|
||||
client after the final borrower without suspending compute or discarding the
|
||||
durable binding.
|
||||
|
||||
The manager also owns a lifecycle gate and active-lease count. Suspension
|
||||
blocks new leases, waits for current leases to close, performs and persists the
|
||||
provider transition, invalidates the `RcRef`, then reopens the gate. Thus a
|
||||
suspend cannot run under an active command or file operation, and an old
|
||||
connection cannot be handed to a new operation after a binding transition.
|
||||
|
||||
The provider filesystem boundary contains only primitives needed beneath the
|
||||
existing Core policy services:
|
||||
|
||||
```ts
|
||||
interface WorkspaceFileBackend {
|
||||
readonly inspect: (path: string) => Effect.Effect<FileInfo, FileError>
|
||||
readonly realPath: (path: string) => Effect.Effect<string, FileError>
|
||||
readonly read: (path: string) => Effect.Effect<Uint8Array, FileError>
|
||||
readonly list: (path: string) => Effect.Effect<readonly DirectoryEntry[], FileError>
|
||||
readonly ensureDirectory: (path: string) => Effect.Effect<void, FileError>
|
||||
readonly createExclusive: (path: string, content: Uint8Array) => Effect.Effect<void, FileError>
|
||||
readonly write: (path: string, content: Uint8Array) => Effect.Effect<void, FileError>
|
||||
readonly writeIfUnchanged: (
|
||||
path: string,
|
||||
expected: Uint8Array,
|
||||
content: Uint8Array,
|
||||
) => Effect.Effect<void, FileError | StaleContentError>
|
||||
readonly remove: (path: string) => Effect.Effect<void, FileError>
|
||||
}
|
||||
```
|
||||
|
||||
`writeIfUnchanged` is one provider-side operation. A remote implementation must
|
||||
not emulate it with an unlocked client-side read followed by write.
|
||||
`FileMutation` remains the owner of create/write/remove semantics, stale-edit
|
||||
errors, parent-directory creation, result metadata, BOM handling, and
|
||||
OpenCode-side mutation ordering. `LocationMutation` retains symlink-safe path
|
||||
containment by composing `realPath` and `inspect`, including its nearest
|
||||
existing ancestor resolution for new targets.
|
||||
|
||||
Provider process transports adapt native command handles to Effect's scoped
|
||||
`ChildProcessSpawner` contract. Scope finalization interrupts an unfinished
|
||||
provider process. Existing `AppProcess` and `Shell` continue to own timeout,
|
||||
abort, bounded output, streaming, command IDs, and settlement, but `Shell` must
|
||||
take executable, arguments, environment, and detached policy from
|
||||
`WorkspaceShell` instead of `process.env` or `process.platform`. Hosted policy
|
||||
preserves the provider's baseline environment and adds only explicit safe
|
||||
OpenCode overrides; it never copies the server environment.
|
||||
|
||||
Foreground `Shell` acquisition is scoped. It uses `Effect.acquireRelease`
|
||||
around process creation so the interrupt finalizer is installed atomically
|
||||
before acquisition completes, closing the current create-to-`onInterrupt`
|
||||
window. The first hosted slice rejects background requests. Existing `Ripgrep`
|
||||
continues to own command construction, bounded JSON parsing, result schemas,
|
||||
and invalid-pattern behavior; the environment supplies process transport and
|
||||
the remote ripgrep executable path.
|
||||
|
||||
OpenCode services expose three distinct lifecycle operations:
|
||||
|
||||
```ts
|
||||
Workspace.get(id) // Read metadata without waking compute.
|
||||
Workspace.connect(id) // Ensure usable compute through a short-lived lease.
|
||||
Workspace.remove(id) // Delete provider state and the Workspace record.
|
||||
```
|
||||
|
||||
Core also has an internal
|
||||
`Workspace.borrow(id): Effect<SandboxEnvironment, WorkspaceError, Scope.Scope>`
|
||||
operation used by the Location environment. File operations scope the borrow
|
||||
to one operation. A spawned process holds its borrow in the caller's process
|
||||
scope until the handle exits or is finalized. Raw provider connections and
|
||||
bindings are not public API.
|
||||
|
||||
## Placement
|
||||
|
||||
`Location.workspaceID` remains the only Session-to-Workspace reference.
|
||||
|
||||
```text
|
||||
Location without workspaceID
|
||||
-> current implicit-local Location graph
|
||||
|
||||
Location with workspaceID
|
||||
-> load Workspace
|
||||
-> lexically normalize its directory against Workspace root metadata
|
||||
-> build a lazy Location graph
|
||||
-> borrow the Workspace environment only when an operation runs
|
||||
```
|
||||
|
||||
The provider sandbox ID never appears in a Session row or durable Session
|
||||
event.
|
||||
|
||||
The existing experimental Workspace adapter API is not preserved. It mixes
|
||||
configuration, provisioning, discovery, lifecycle, and request routing. The
|
||||
replacement should be derived from the lifecycle and environment interfaces
|
||||
above.
|
||||
|
||||
## Host And Workspace Authority
|
||||
|
||||
Remote execution forces a split that is currently implicit in `FSUtil`:
|
||||
|
||||
- host authority owns OpenCode global configuration, caches, credentials,
|
||||
durable events, and managed tool output;
|
||||
- Workspace authority owns repository files, project instructions, project
|
||||
configuration, project skills, Git state, search, and commands.
|
||||
|
||||
Do not implement the permanent remote boundary by emulating Effect's complete
|
||||
low-level `FileSystem.FileSystem` interface. Migrate Location-owned consumers
|
||||
to the Workspace file backend, or compose existing policy services over that
|
||||
backend and the environment's `ChildProcessSpawner`. Host-global consumers
|
||||
continue using the host filesystem.
|
||||
|
||||
## Provider Strategy
|
||||
|
||||
Build one provider-neutral contract and one real reference provider.
|
||||
|
||||
Vercel is the recommended first adapter because the tracer has verified the
|
||||
complete lifecycle against an available account. Its stable persistent name,
|
||||
automatic snapshot-on-stop, direct filesystem API, and reconnectable command
|
||||
handles map closely to the proposed contract.
|
||||
|
||||
Daytona is the next comparison because its public API directly supports
|
||||
filesystem operations, command sessions, interruption, and durable stop/pause
|
||||
semantics. Modal follows with an intentionally different binding transition:
|
||||
suspension snapshots the filesystem and reconnection creates a new Sandbox.
|
||||
E2B can provide a fourth validation through pause/resume if the first three
|
||||
leave meaningful ambiguity.
|
||||
|
||||
No in-sandbox OpenCode worker is required for the first Daytona/E2B/Modal
|
||||
slice. Their SDKs can implement the small environment contract directly. Add a
|
||||
provider-neutral worker later only when supporting providers such as exe.dev,
|
||||
or when detached process reconnection and richer runtime semantics justify it.
|
||||
|
||||
## Implementation Sequence
|
||||
|
||||
The first integration PR is a narrow vertical slice across stages 1 through 4:
|
||||
|
||||
- add the provider-neutral file, process, environment, and lifecycle contracts;
|
||||
- add the local environment and a test-only fake hosted provider;
|
||||
- replace the experimental Workspace storage shape with public metadata plus
|
||||
private placement state;
|
||||
- add the Workspace connection manager and Location-scoped
|
||||
`WorkspaceEnvironment.Service`;
|
||||
- make `Location.boundNode` resolve hosted project/root metadata without
|
||||
`Project.resolve` or any host-path access; and
|
||||
- prove lazy connection and scoped release using a hosted directory that does
|
||||
not exist on the test host.
|
||||
|
||||
It does not add Vercel or migrate every tool. Its purpose is to establish the
|
||||
placement seam and prevent cloud-provider details from shaping later Core
|
||||
changes.
|
||||
|
||||
### 1. Define Behavioral Contracts
|
||||
|
||||
- Add internal Sandbox provider, binding, file-backend, process-transport, and
|
||||
environment services in Core.
|
||||
- Include provider-side real-path and directory primitives plus explicit
|
||||
Workspace shell/environment policy in the contract.
|
||||
- Keep provider registration process-global and provider implementations out
|
||||
of tool modules.
|
||||
- Define typed errors for unavailable Workspace, connection failure, file
|
||||
operations, stale content, command failure, and unsupported capability.
|
||||
- Add a contract test suite that can run against any environment
|
||||
implementation.
|
||||
- Cover create, reconnect, file retention, conditional write, command output,
|
||||
timeout, caller interruption, and cleanup when a caller fails before wait.
|
||||
- Observe a long command running before interruption, then assert promptly that
|
||||
its provider process no longer exists.
|
||||
- Inject failures around replacement-binding persistence and verify that the
|
||||
durable binding can reconnect and reconcile retired resources.
|
||||
- Interrupt creation around allocation and initial persistence, then verify
|
||||
that the stable provider identity resolves to either one committed Workspace
|
||||
or no retained provider resource.
|
||||
|
||||
Result: contracts and reusable tests are ready for the local and fake
|
||||
implementations in the same first integration PR.
|
||||
|
||||
### 2. Establish Local Parity
|
||||
|
||||
- Implement the file backend against the current local filesystem and use the
|
||||
current `ChildProcessSpawner` plus ripgrep resolver.
|
||||
- Compose the existing `FileMutation`, `AppProcess`, `Shell`, and `Ripgrep`
|
||||
services over those local primitives.
|
||||
- Run the contract suite against the local implementation.
|
||||
- Do not route Sessions through it yet.
|
||||
|
||||
Result: the contract describes existing semantics rather than only one cloud
|
||||
provider's API.
|
||||
|
||||
### 3. Replace The Experimental Workspace Adapter Core
|
||||
|
||||
- Define a safe Workspace public metadata model in Schema instead of exposing
|
||||
only its ID and events.
|
||||
- Replace arbitrary adapter `type` plus `extra: unknown` with an explicit local
|
||||
or sandbox placement in Core-owned storage. Sandbox placement carries a
|
||||
provider key and provider-schema-validated JSON binding; it is not included
|
||||
in browser-facing Workspace metadata.
|
||||
- Add a Core Workspace service for create, get, connect, suspend, and remove.
|
||||
- Keep provider lookup behind a process-global provider registry.
|
||||
- Key one scoped connection manager by Workspace ID. Fence suspend/remove
|
||||
against new leases and wait for active leases before changing placement.
|
||||
- Persist replacement bindings before destructive cleanup and reconcile
|
||||
interrupted transitions on the next lifecycle operation.
|
||||
- Preserve `Workspace.ID` and existing Session `workspace_id` references.
|
||||
- Migrate or reset only experimental Workspace rows; do not add compatibility
|
||||
machinery without a concrete persisted-data requirement.
|
||||
|
||||
Result: Workspace owns stable identity and placement without owning files.
|
||||
|
||||
### 4. Introduce The Location Environment Seam
|
||||
|
||||
- Add a Location-scoped `WorkspaceEnvironment.Service` whose local
|
||||
implementation wraps the current filesystem and process services.
|
||||
- Give hosted Workspaces an implementation whose operations borrow the
|
||||
process-global Workspace connection manager.
|
||||
- Change `Location.boundNode` so a Location with `workspaceID` gets project and
|
||||
root metadata from the Workspace record instead of calling host-local
|
||||
`Project.resolve(directory)`.
|
||||
- Treat Workspace metadata as the canonical root. Interpret Location directory
|
||||
as a working directory within that root and lexically reject paths outside
|
||||
it before looking up the Location graph. Resolve symlinks only when an
|
||||
operation borrows the environment.
|
||||
- Prove this with a fake hosted Workspace whose directory does not exist on the
|
||||
test host. Do not add a cloud SDK in this change.
|
||||
|
||||
Result: Core has one placement seam and remote Location identity no longer
|
||||
requires a host checkout.
|
||||
|
||||
### 5. Separate Host Files From Workspace Files
|
||||
|
||||
- Inventory every Location-scoped `FSUtil`, `Ripgrep`, `AppProcess`, and direct
|
||||
Node filesystem use.
|
||||
- Adapt `FileMutation` to the Workspace file backend while preserving its
|
||||
current policy and result surface.
|
||||
- Adapt `LocationMutation` to provider-side `realPath` and `inspect` so existing
|
||||
targets, missing-target ancestors, and symlink escapes retain current
|
||||
containment behavior.
|
||||
- Adapt hosted process transport once to `ChildProcessSpawner`, then reuse
|
||||
`AppProcess`, `Shell`, and `Ripgrep` rather than adding provider command or
|
||||
search state machines.
|
||||
- Change `Shell` to consume Workspace shell/environment policy and add scoped
|
||||
foreground acquisition with an atomic interrupt finalizer. Reject background
|
||||
shell requests for hosted Workspaces in this slice.
|
||||
- Move read, edit, patch, path resolution, project instruction discovery, and
|
||||
project configuration onto those environment-aware services.
|
||||
- Keep global config, global skills, caches, credentials, and managed tool
|
||||
output on host services.
|
||||
- Report the environment platform in built-in instructions instead of
|
||||
`process.platform`.
|
||||
- Make unsupported remote snapshot, watcher, VCS, and PTY services explicit for
|
||||
the first slice rather than accidentally acting on the server host.
|
||||
|
||||
Result: no supported Workspace operation can silently touch the wrong host.
|
||||
|
||||
### 6. Make The Full Location Graph Environment-Aware
|
||||
|
||||
- Migrate each supported Location service to `WorkspaceEnvironment.Service`
|
||||
rather than selecting services through dynamic `LayerNode` replacements.
|
||||
- Preserve the current implicit-local path when `workspaceID` is absent.
|
||||
- Cache the connection manager by Workspace ID. Cache Location graphs by the
|
||||
canonical `{ workspaceID, directoryWithinWorkspace }` identity.
|
||||
- Keep Location graphs valid across binding replacement: their environment
|
||||
service borrows the current Workspace connection for each operation rather
|
||||
than retaining provider state.
|
||||
- Add an end-to-end fake-provider test that materializes the full Location graph
|
||||
without reading, searching, spawning, or watching the host path.
|
||||
|
||||
Result: a remote Location can boot when its directory does not exist locally.
|
||||
|
||||
### 7. Implement One Real Provider
|
||||
|
||||
- Add the Vercel provider behind a narrow optional package or lazy import so
|
||||
its SDK does not affect local startup.
|
||||
- Provision a named persistent Sandbox from provider configuration.
|
||||
- Persist only the binding required to reconnect.
|
||||
- Use the Vercel SDK directly for lifecycle and files. Adapt detached SDK
|
||||
command handles to scoped `ChildProcessSpawner` handles; do not shell through
|
||||
the Vercel CLI or expose detached commands to tools.
|
||||
- Implement atomic conditional write inside the sandbox, plus suspend,
|
||||
reconnect, and delete semantics.
|
||||
- Never inject OpenCode model-provider credentials into the sandbox.
|
||||
- Run the generic environment contract suite against a real provider when
|
||||
credentials are available.
|
||||
|
||||
Result: a provider-backed Workspace can be created and reconnected directly.
|
||||
|
||||
### 8. Prove The Hosted Coding Slice
|
||||
|
||||
- Create a provider-backed Workspace containing a fixture repository.
|
||||
- Create a V2 Session whose Location references that Workspace.
|
||||
- Verify that the central server has no copy of the checkout.
|
||||
- Prompt the Session to read a file, edit it, search it, and run `git status`.
|
||||
- Interrupt a long-running foreground command.
|
||||
- Suspend the Workspace, reconnect it, and verify that edited files remain.
|
||||
- Confirm that a normal local Session still passes its existing test suite.
|
||||
|
||||
Result: the milestone goal is complete.
|
||||
|
||||
### 9. Add Additional Providers
|
||||
|
||||
- Run Daytona and Modal implementations through the same contract suite, then
|
||||
add E2B if it reveals another required lifecycle shape.
|
||||
- Document where each lifecycle maps differently while preserving OpenCode's
|
||||
observable contract.
|
||||
- Add capabilities only when a required operation cannot be represented by the
|
||||
common contract.
|
||||
|
||||
Result: provider genericity is demonstrated rather than assumed.
|
||||
|
||||
## Acceptance Tests
|
||||
|
||||
The first milestone is accepted when all of the following hold:
|
||||
|
||||
- A Session with no `workspaceID` behaves exactly as before.
|
||||
- A Session with a hosted Workspace boots without its directory on the server.
|
||||
- `read`, `write`, `edit`, `patch`, `glob`, `grep`, and foreground `shell`
|
||||
operate only in the hosted environment.
|
||||
- Permission checks and tool settlement remain central.
|
||||
- Shell interruption reaches the provider process.
|
||||
- Interrupting a waiting tool fiber, timing it out, or failing before wait does
|
||||
not leave its provider process running.
|
||||
- A stale conditional edit fails rather than overwriting changed content.
|
||||
- Workspace metadata can be read without resuming compute.
|
||||
- Reconnection targets the existing provider resource when available.
|
||||
- A crash after persisting a replacement binding leaves placement reconnectable
|
||||
and its retired resource eligible for reconciliation.
|
||||
- Suspension followed by reconnection retains files.
|
||||
- Suspension waits for active Workspace leases and admits no new operations
|
||||
until its binding transition is durable.
|
||||
- Deletion removes retained provider state.
|
||||
- No model-provider credential is present in sandbox environment variables.
|
||||
- Hosted commands do not inherit the OpenCode server's environment, shell, or
|
||||
platform decisions.
|
||||
- Existing and newly created paths cannot escape the hosted Workspace through
|
||||
symlinks or missing parent directories.
|
||||
- The same environment contract tests pass against local and hosted
|
||||
implementations.
|
||||
|
||||
## Deferred Design Pressure
|
||||
|
||||
The following concerns are real but do not expand the first milestone:
|
||||
|
||||
- Mutating remote requests need idempotency before automatic network retries.
|
||||
- Background jobs need durable ownership and reconnection semantics.
|
||||
- Workspace sharing needs a mutation lease or another concurrency policy.
|
||||
- Provider migration needs an explicit file-transfer or snapshot contract.
|
||||
- Portable plugins need explicit Workspace capabilities; ambient `fs` and
|
||||
process imports remain local-only.
|
||||
- A resident OpenCode worker becomes useful for SSH-only providers, richer Git
|
||||
operations, PTY, LSP, watches, and detached commands.
|
||||
- Clustered Session execution and stale-runner fencing remain separate from
|
||||
sandbox placement.
|
||||
|
||||
## Research
|
||||
|
||||
Disposable Daytona, Modal, and Vercel tracer experiments informed this plan and
|
||||
were removed after review. Vercel was exercised live through create, reconnect,
|
||||
file I/O, foreground command interruption, stop, resume with retained files,
|
||||
and deletion. Daytona and Modal adapters were type-checked but not run because
|
||||
credentials were unavailable.
|
||||
Loading…
Add table
Add a link
Reference in a new issue