mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-23 13:06:20 +00:00
* 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>
98 lines
5.0 KiB
Markdown
98 lines
5.0 KiB
Markdown
# Tenki backend
|
|
|
|
Runs each DeerFlow sandbox as a [Tenki](https://tenki.cloud) 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
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```bash
|
|
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` — native `fs.read_text` / `fs.mkdir` / `fs.write_stream` (binary-safe, streamed).
|
|
- `download_file` — native `fs.read_stream`, restricted to the `/mnt/user-data` prefix; 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` / `grep` with 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.
|