* fix(deps): depend on renamed tenki package instead of tenki-sandbox tenki-sandbox has been removed from PyPI and republished as tenki. Its old wheel URL still resolves, so existing lockfiles keep installing and the breakage is invisible to anyone with a warm lock; any fresh resolution fails with 'tenki-sandbox was not found in the package registry'. tenki 1.0.2 still ships the tenki_sandbox module, so the imports in community/tenki/provider.py and sandbox.py are unchanged. Fixes #5081 * fix(tenki): point install guidance at the renamed distribution The rename to `tenki` left the user-facing remediation still naming the removed package. `_import_client` raised "pip install tenki-sandbox" on the missing-extra path — the exact instruction this change proves now 404s on PyPI, handed to the user at the exact moment they need it to work. Update that message and the remaining `tenki-sandbox` references in the provider, sandbox adapter, README, sandbox AGENTS.md and the test docstring. The imported module stays `tenki_sandbox`, so the distribution and module names now differ; each mention says so rather than just swapping the string. No behavior change beyond the error text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tenki): migrate the provider to the 1.x workspace-only API Renaming the dependency was not enough. tenki 1.0.2 keeps the tenki_sandbox module name but not its contract: Client.create dropped project_id and has no **kwargs to absorb it, and IdentityWorkspace no longer carries `projects` (the attribute is gone from the package entirely). Both configuration paths therefore failed before a sandbox could be created — explicit project scope raised TypeError, and automatic scope raised AttributeError walking workspace.projects. Scope is now the workspace alone. _resolve_scope returns a single workspace id, auto-selecting when the account has exactly one, and project_id is gone from create_kwargs and from the documented config surface. A stale project_id in config.yaml warns rather than fails. SandboxConfig is extra="allow", so simply not reading the key would leave it scoping nothing with no signal; it also used to short-circuit the identity lookup, so operators with more than one workspace need to know they must now set workspace_id. The suite passed against the broken provider because the fake client took **kwargs and swallowed the project_id the real SDK rejects. The double now mirrors 1.0.2 — keyword-only, no **kwargs — so an unexpected argument is a TypeError in tests exactly as it is against the SDK. Reintroducing the old create call fails 20 tests; before this change it failed none. Verified against the exact locked wheels: every other kwarg the provider passes (name, workspace_id, sticky, wait, max_duration, image, cpu_cores, memory_mb, env) and every SDK surface it touches (who_am_i, Identity.workspaces, wait_ready, exec, close, the fs API, the four terminal exception classes) is unchanged in 1.0.2. Reported by willem-bd in review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(config): drop sandbox.project_id from the Tenki example The canonical example still documented project_id as a supported optional key after the provider stopped honouring it, so an operator following it could set the key, get no scope from it, and hit a workspace-resolution failure with nothing in the example to explain why. Replaced with a migration note rather than a silent deletion: someone upgrading already has the key in their config.yaml and needs to know it is inert now and that workspace_id is what scopes a sandbox on Tenki 1.x. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Aniket Wagh <aniketwaghh@users.noreply.github.com>
5.0 KiB
Tenki backend
Runs each DeerFlow sandbox as a Tenki cloud sandbox — an isolated microVM created from a stock base image, with no daemon or local virtualization to manage. A cloud-hosted alternative to the container-based AIO sandbox and the local-virtualization BoxLite backend.
Configuration
sandbox:
use: deerflow.community.tenki:TenkiSandboxProvider
api_key: $TENKI_API_KEY # falls back to TENKI_API_KEY / TENKI_AUTH_TOKEN env var
base_url: https://tenki.cloud # optional; SDK default when omitted
image: my-base-image # optional; Tenki account default base image when omitted
workspace_id: ws_... # optional; auto-selected if the account has exactly one
cpu_cores: 2 # optional per-sandbox vCPUs
memory_mb: 2048 # optional per-sandbox memory
replicas: 3 # active + warm microVM cap per gateway process (default: 3)
idle_timeout: 600 # warm microVM idle seconds before terminate; 0 disables
max_duration: 14400 # Tenki sandbox lifetime in seconds (default: 4h); 0 uses the account default
sticky: false # pin the microVM to its host (only matters with pause/resume)
home_dir: /home/tenki # writable dir backing /mnt/user-data (default: /home/tenki)
environment: # injected into every command (and as create-time env)
PYTHONUNBUFFERED: "1"
Install the optional SDK before selecting this provider:
pip install "deerflow-harness[tenki]"
The tenki package (which provides the tenki_sandbox module) is an optional
DeerFlow harness extra, not part of the default install. Get an API key from
https://tenki.cloud/docs/sandbox/sdk.
Design
Tenki's Python SDK is synchronous, so — unlike BoxLite — the adapter calls the
SDK directly with no event-loop bridge. Sandboxes are named deterministically
from user_id:thread_id, released into an in-process warm pool after each agent
turn, and reclaimed (after a liveness health check) by the same thread on the
next acquire. A terminal session error evicts the sandbox and the next acquire
rebuilds it; other transport errors surface to the caller (exec is never
auto-retried — it is not idempotent, so re-running could double a command's side
effects). Sandboxes are created with wait=False and awaited via wait_ready()
so a readiness failure still leaves this provider holding the handle to
terminate, and with an explicit max_duration so a long-lived thread does not
lose its sandbox to Tenki's default lifetime mid-conversation.
| File | Role |
|---|---|
provider.py |
SandboxProvider lifecycle, warm pool, scope resolution |
sandbox.py |
Sandbox adapter; execute_command + file ops |
Contract coverage
The full Sandbox surface is implemented. File transport uses Tenki's native sandbox.fs
API; directory and content search shell out and reuse deerflow.sandbox.search,
mirroring e2b_sandbox:
execute_command—sh -lc, with per-call env and timeout.read_file/write_file/update_file— nativefs.read_text/fs.mkdir/fs.write_stream(binary-safe, streamed).download_file— nativefs.read_stream, restricted to the/mnt/user-dataprefix; the 100 MB cap is enforced on bytes actually received, so a file growing mid-transfer cannot slip past it.list_dir/glob/grep—find/grepwith busybox-portable flags (the fs API is single-level and has no content search); results filtered/capped in Python and reported back under/mnt/user-data.
Tenki sandboxes run as the unprivileged tenki user with /mnt root-owned, so
DeerFlow's /mnt/user-data virtual prefix is remapped under the writable
home_dir (like e2b_sandbox). The provider also best-effort sudo-symlinks
/mnt/user-data → home_dir at create time so agent shell commands using the
literal /mnt/... path still work; if sudo is unavailable the file APIs keep
working via the remap.
Warm-pool capacity is governed by sandbox.replicas across active + warm
sandboxes. sandbox.idle_timeout controls how long released warm sandboxes stay
running; 0 disables idle reaping. Active sandboxes are never evicted to satisfy
the cap.
Scope: stable features only
Only the stable Tenki surface is used — sandbox create/terminate plus exec/shell/filesystem over shell commands. Volumes, snapshots, and template builds are intentionally not used, so no prebaked image or unstable Tenki feature is required and any stock base image works.
Not yet implemented (follow-ups): cross-process orphan reconciliation (adopting sandboxes left by a previous gateway process) and a preview-URL surface.
Status
Verified end-to-end against live Tenki sandboxes: provider resolution →
execute_command → full file-op surface (read/write/update/download,
list_dir/glob/grep) → warm-pool reclaim → terminate, plus the
/mnt/user-data sudo symlink. Unit tests run in CI without tenki
installed; test_integration_real_sandbox exercises a real microVM when
TENKI_API_KEY is set.