Add Tensorlake as a sandbox provider - #173
Open
diptanu wants to merge 3 commits into
Open
Conversation
diptanu
force-pushed
the
tensorlake-provider
branch
from
July 27, 2026 06:06
09979cd to
f66cea4
Compare
Collaborator
|
Looks good. Thanks for doing this. |
Lifecycle goes through the Tensorlake platform API (create/get/suspend/ resume/delete/snapshot); commands go through the per-sandbox proxy, using `/api/v1/processes/run` for exec and `/api/v1/processes` plus the stdin and follow endpoints for streaming processes. Cross-process resume uses a deterministic *named* sandbox derived from SandboxKey + spec hash. Tensorlake creates carry no label or metadata field, and only named sandboxes support suspend/resume, so the name is the only resume handle. A request without an idle TTL gets an ephemeral sandbox. SnapshotKind::TensorlakeSnapshot carries a manifest pointing at a platform snapshot id; restore creates a sandbox from it. Registered on --sandbox-provider and `exo provider configure`, with optional --cpus / --memory-mb. Credentials resolve lazily from TENSORLAKE_API_KEY. Scoped deliberately to the provider: no changes to ManagedSandboxBackend or ManagedSandboxHandle, so this does not collide with the attach/detach lifecycle in exoharness#161. Sandbox reclamation on delete and copying sandboxes into forked conversations both need trait changes and are held back until that lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Wait for a snapshot to become restorable before persisting its id.
`POST /sandboxes/{id}/snapshot` returns 202 while the snapshot is still
being written, so snapshotting and immediately rewinding could reference
something create would reject. `local_ready` and `replicating` both count as
restorable; waiting for full durability would stall a rewind for no benefit.
A failed snapshot now surfaces its error instead of yielding a manifest.
- Reject a snapshot manifest captured from a different sandbox. The name was
recorded but never checked, so restoring across identities would silently
hand back someone else's filesystem.
- Reject `--cpus` / `--memory-mb` for providers that ignore them, matching the
existing guard on `--session-storage-mount-path`.
- Fold the proxy-liveness flag into the mutex that already guarded it. The
target and its liveness are only ever read and written together, so an
atomic alongside the lock bought nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
diptanu
force-pushed
the
tensorlake-provider
branch
from
July 31, 2026 05:50
f66cea4 to
c87d0a2
Compare
ankrgyl
reviewed
Aug 1, 2026
Comment on lines
+745
to
+750
| /// Tensorlake: whole CPU cores per sandbox. | ||
| #[arg(long)] | ||
| cpus: Option<u32>, | ||
| /// Tensorlake: memory per sandbox in MiB. | ||
| #[arg(long = "memory-mb")] | ||
| memory_mb: Option<u64>, |
Collaborator
There was a problem hiding this comment.
if we add these we should propagate them to each sandbox provider
Comment on lines
+211
to
+212
| Ok(false) | ||
| } |
Collaborator
There was a problem hiding this comment.
generally prefer not introducing default implementations. also if we have a default implementation, shuoldn't it do snapshot + acquire_from_snapshot?
| } | ||
|
|
||
| async fn terminate(&self, request: SandboxRequest) -> Result<()> { | ||
| let spec_hash = sandbox_spec_hash(&request.spec); |
Collaborator
There was a problem hiding this comment.
did you run a test for all the sandbox providers you added terminate for? it's a little tricky because it requires API keys for all of them
| /// a later acquire, termination gives up the provider resource and all of | ||
| /// its retained state. Implementations must be idempotent and must not | ||
| /// create or resume a sandbox while looking for it. | ||
| async fn terminate(&self, request: SandboxRequest) -> Result<()>; |
Collaborator
There was a problem hiding this comment.
if we add these methods we need to add generic tests for them that work across all sandbox providers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
tensorlakealongside Daytona, E2B, Sprites, Vercel, and AgentCore. Tensorlake sandboxes are Firecracker MicroVMs that can suspend and resume with their filesystem intact, which lines up with how Exo conversation sandboxes behave across sessions.Trying it
Point Exo at a Tensorlake key and configure the provider:
Nothing is provisioned until something first needs the sandbox. A quick test without a model call:
Streaming processes work too, so
--shell-program /bin/bashin the REPL behaves as it does on other providers.Lifecycle
This PR now builds on the attach/detach lifecycle from #161:
ManagedSandboxBackendgains idempotentterminateand optionalfork_sandboxAPIs.Every existing backend implements termination. Remote providers use their permanent delete/stop APIs, container backends remove their Exo-owned container, and local-process termination is a no-op.
How Tensorlake resume and fork work
Tensorlake creates have no label or metadata field, and only named sandboxes can suspend and resume. Each sandbox therefore gets a deterministic
exo-<hash>name derived from itsSandboxKeyand spec hash. Agent-scoped and conversation-scoped sandboxes hash differently; changing the image or mounts yields a different name.stopsuspends named sandboxes so filesystem state survives. Snapshots are stored by reference and are not persisted until Tensorlake reports them as restorable. Conversation forks copy a live named sandbox directly to the deterministic name derived for the target conversation, leaving the parent untouched.Configuration lives on the binding, including optional
--cpusand--memory-mb. Credentials resolve lazily fromTENSORLAKE_API_KEY, so the provider can be registered before a key is set.Validation
cargo test -p exo --test tensorlake_backend— 28 passedcargo test -p exoharness --features basic-backend -- --test-threads=1— 67 passed, 7 real-provider tests ignoredcargo check --workspace --all-targetscargo fmt --all -- --checkKnown gaps
execoutput always ends with one.