Skip to content

Add Tenki Sandboxes provider#25

Open
francoluxor wants to merge 1 commit into
cased:mainfrom
francoluxor:main
Open

Add Tenki Sandboxes provider#25
francoluxor wants to merge 1 commit into
cased:mainfrom
francoluxor:main

Conversation

@francoluxor

Copy link
Copy Markdown

Summary

Adds a Tenki Sandboxes provider (managed microVM sandboxes for agentic
workloads, https://tenki.cloud) implemented as a thin adapter over the official
tenki-sandbox async SDK (AsyncClient / AsyncSandbox). Tenki's real API
is a Connect/gRPC control plane plus a data-plane gateway for command
execution — there is no plain REST surface — so the SDK handles the protocol,
data-plane priming, and auth.

Features

  • Full SandboxProvider lifecycle: create / get / list / destroy
  • Command execution (sb.shell) and stdout streaming (sb.start) over the data plane
  • File upload / download via the SDK fs API
  • Label-based reuse — labels map to Tenki session metadata
  • Auto-configuration when TENKI_API_KEY is set; Sandbox.configure(tenki_api_key=...)
  • Capabilities: persistent, snapshot, streaming, file_upload, interactive_shell

Requirements

  • TENKI_API_KEY (or TENKI_AUTH_TOKEN)
  • Optional: TENKI_PROJECT_ID / TENKI_WORKSPACE_ID for scoped listing, TENKI_API_ENDPOINT
  • pip install "cased-sandboxes[tenki]" (pulls tenki-sandbox)

Test plan

  • ruff clean on changed files
  • Unit tests — tests/test_tenki_provider.py drive the provider via an injected
    fake async client (create/get/list/exec/stream/destroy/files/timeout/auth)
  • Capability matrix updated
  • Live integration verified against prod: real session created (reached
    RUNNING, confirmed via an independent raw Connect GetSession), real shell
    commands executed on an Ubuntu 24.04 guest (echo, hostname, uname),
    then terminated (state flipped to TERMINATED, no leaked session)

* Add Tenki Sandboxes provider

Implement a SandboxProvider for Tenki (managed microVM sandboxes) as a thin
adapter over the official tenki-sandbox async SDK (AsyncClient / AsyncSandbox),
which speaks Tenki's Connect/gRPC control plane and data-plane gateway.

- Lifecycle: create / get / list / destroy sessions
- Command execution and stdout streaming over the data plane
- File upload / download via the SDK fs API
- Label-based reuse (labels map to Tenki session metadata)
- Capabilities: persistent, snapshot, streaming, file_upload, interactive_shell

Wire the provider into the registry, CLI, and auto-configuration (enabled when
TENKI_API_KEY is set). Add unit tests using an injected fake async client plus a
live integration test (skipped without TENKI_API_KEY), and register the
tenki-sandbox optional dependency.

* Address code review feedback on Tenki provider

- Map SDK command timeouts to SandboxTimeoutError in _map_error (used by
  stream_execution); execute_command keeps returning a timed_out result
- Evict cached handle before close() in destroy_sandbox so concurrent callers
  can't observe a terminating sandbox
- Guard aclose() with the provider lock
- Default unknown session states to ERROR instead of RUNNING
- stream_execution: try/finally kills the remote process if the generator is
  abandoned, and routes close_stdin/wait/stdout errors through _map_error
- Live integration test: create inside try, guard teardown, always aclose
- Add stream cleanup and error-mapping unit tests

* Honor TENKI_AUTH_TOKEN in provider auto-config and CLI status

Auto-registration and the CLI providers command only checked TENKI_API_KEY,
so a user with only TENKI_AUTH_TOKEN set (which TenkiProvider.__init__ accepts)
saw the provider unregistered / reported as not configured. Check both.

* Address second review pass on Tenki provider

- _resolve/destroy_sandbox: track in-flight destroys via a _destroying set and
  read the cache under the lock, so a handle can't be returned or re-cached
  mid-termination
- stream_execution: bound proc.wait() with asyncio.wait_for and raise
  SandboxTimeoutError on timeout; _map_error is now idempotent for library errors
- aclose(): clear state under the lock, run client.close() outside it
- create_sandbox: cpu_cores uses `is not None` so an explicit 0 is not dropped
- Add tests for the destroy guard and the stream wait-timeout
@francoluxor
francoluxor marked this pull request as ready for review July 20, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant