Skip to content

feat(sandbox): add a Tenki Sandbox VM sandbox provider - #187

Merged
earakely-scale merged 1 commit into
scaleapi:mainfrom
francoluxor:feat/tenki-sandbox-provider
Oct 14, 2026
Merged

earakely-scale merged 1 commit into
scaleapi:mainfrom
francoluxor:feat/tenki-sandbox-provider

Conversation

@francoluxor

@francoluxor francoluxor commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Closes #186.

Adds tenki as a built-in sandbox provider behind a tenki extra, so environments and agents can run on Tenki Sandbox VMs. Each sandbox is one VM with Docker inside, so agent-env's docker-in-VM flows run unchanged: the gateway's docker-compose, an agent's docker run, image loading and artifact collection.

  • Config: [sandbox.providers.tenki.config] takes api_key (required, env:/secret:), base_url, snapshot_id or image, and min_disk_gb. No new environment variables.
  • VMs: sized from the request, raised to 2 vCPU / 4 GiB and refused rather than shrunk past Tenki's limits. timeout is the VM's hard max lifetime.
  • Exec and files: commands run as root; objects go over exec stdin through push_object_over_stdin. A dropped stream is exit -1, which exec_script retries.
  • Network policy: allow-all or an allowlist of up to 256 hostnames and 64 IPv4 CIDRs, enforced outside the VM and set when it is created. Exposed ports get public HTTPS URLs; EGRESS_HOSTS is *.sb.tenki.sh and *.us.sb.tenki.sh.
  • Reconnect and cleanup: get_sandbox restores ports and the applied policy. A VM whose setup fails, whose create is cancelled, or whose create response is lost is terminated.

Core changes are the registry entry and the --sandbox help text, plus the shared test and doc touchpoints a built-in provider gets.

How it was tested

  • make unit-test: all pass, including the new tenki_* tests (offline, against SDK stand-ins).
  • check_plugin_api.py --base origin/main: no break to the plugin surface.
  • Against Tenki production:
    • tenki_sandbox_smoke_test.py: 2 passed (nested Docker, public port, upload over stdin, reconnect, allowlisted VM).
    • agent-env run hello --sandbox tenki: passed, about 8 s from a snapshot.
    • A task with deploy_env (gateway and service DB), load_artifact, deploy_agent, prompt_agent and rubrics_verifier, using a Claude agent with the env's MCP tools: 5 parallel runs passed in about 70 s, judge score 1.0.
    • From the explorer (agent-env up): the same agent task started from the Tasks hub, 5 runs at once, all passed in about 70 s; their rollouts show each step and the judge's scores.

RetriggerView in GreptileConfidence Score: 4/5

Fix cancelled-create cleanup and satisfy the named-variable requirement before merging.

Fix All in CursorFindings

  1. P1 Cancelled deploy leaves VM running ▶
  2. P2 Docs overstate hostname checks ▶
  3. P2 Upload delay has no name ▶
Fix with agent prompt
### Issue 1
src/agent_env/providers/sandbox_providers/tenki/provider.py:368-370
Cancelling a CLI deploy during `client.create` makes `_create_or_reclaim` attach a callback and immediately raise cancellation. When `asyncio.run` shuts down, it cancels the unfinished create. The callback then starts cleanup that shutdown does not wait for. The loop can close while `client.list` is still waiting, leaving the VM running until its maximum lifetime.

Wait for creation and cleanup during cancellation, with a bounded wait, rather than relying only on the callback.

### Issue 2
src/agent_env/providers/sandbox_providers/tenki/README.md:111-112
The README promises that all malformed entries raise `NetworkPolicyUnsupportedError` before creation. But `supports_network_policy` only checks CIDRs and entry counts. A malformed hostname passes those checks and reaches `client.create`, so users can receive an SDK or API error instead of the promised early rejection.

Narrow the wording to malformed CIDRs, or add hostname checks and tests.

```suggestion
  becomes a /32. A policy with IPv6 or malformed CIDRs is refused with `NetworkPolicyUnsupportedError` before
  anything is created, and a fallback chain skips to its next provider. Tenki applies the policy outside the VM, so
```

### Issue 3
src/agent_env/providers/sandbox_providers/tenki/sandbox.py:274
The new queue-drain loop uses an inline `0.005` delay. The repository requires magic numbers to be stored as class or instance variables with descriptive names. Move this delay to a named class variable so its unit and purpose are clear. This repository requirement must be satisfied before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds the optional tenki backend for Docker-capable VMs, public ports, restricted outbound access, file uploads, and reconnects.

  • Tenki VMs can run agent-env environments and agents.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Configured Tenki provider] --> B[Size VM and check policy]
  B --> C[Create Tenki VM]
  C --> D[Wait for readiness]
  D --> E[Expose public ports]
  E --> F[Prepare Docker]
  F --> G[Run gateway or agent containers]
  C --> H[Create cancelled]
  H --> I[Register late cleanup callback]
  I --> J[CLI loop may close before cleanup finishes]
  D --> K[Setup failure]
  E --> K
  F --> K
  K --> L[Await VM termination]
Loading

Reviews (1) · Last reviewed commit: "feat(sandbox): add a Tenki Sandbox VM sa..." · Reviewed by Greptile

Adds `tenki` as a built-in VM sandbox provider behind a `tenki` extra. Each
sandbox is one Tenki VM running agent-env's docker-in-VM flows: commands
run as root via privileged exec, objects go over exec stdin, and exposed
ports get public HTTPS URLs. Allowlist egress is enforced outside the VM
and set at create, so bytes the policy doesn't cover go over stdin.

Config: api_key (required), base_url, snapshot_id or image, min_disk_gb.
@francoluxor
francoluxor requested a review from a team as a code owner October 13, 2026 23:53
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​tenki@​1.4.1100100100100100

View full report

Comment on lines +368 to +370
except asyncio.CancelledError:
task.add_done_callback(terminate_orphan)
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cancelled deploy leaves VM running

Cancelling a CLI deploy during client.create makes _create_or_reclaim attach a callback and immediately raise cancellation. When asyncio.run shuts down, it cancels the unfinished create. The callback then starts cleanup that shutdown does not wait for. The loop can close while client.list is still waiting, leaving the VM running until its maximum lifetime.

Wait for creation and cleanup during cancellation, with a bounded wait, rather than relying only on the callback.

Knowledge Base Used: Stop Container Leaks During Cancelled Modal Deploys

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/tenki/provider.py
Line: 368-370

Comment:
**Cancelled deploy leaves VM running**

Cancelling a CLI deploy during `client.create` makes `_create_or_reclaim` attach a callback and immediately raise cancellation. When `asyncio.run` shuts down, it cancels the unfinished create. The callback then starts cleanup that shutdown does not wait for. The loop can close while `client.list` is still waiting, leaving the VM running until its maximum lifetime.

Wait for creation and cleanup during cancellation, with a bounded wait, rather than relying only on the callback.

**Knowledge Base Used:** [Stop Container Leaks During Cancelled Modal Deploys](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/reverts/incident-mitigation_171-20261013-cancelled-container-leaks-9ee55f4.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Comment on lines +111 to +112
becomes a /32. A policy with IPv6 or malformed entries is refused with `NetworkPolicyUnsupportedError` before
anything is created, and a fallback chain skips to its next provider. Tenki applies the policy outside the VM, so

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Docs overstate hostname checks

The README promises that all malformed entries raise NetworkPolicyUnsupportedError before creation. But supports_network_policy only checks CIDRs and entry counts. A malformed hostname passes those checks and reaches client.create, so users can receive an SDK or API error instead of the promised early rejection.

Narrow the wording to malformed CIDRs, or add hostname checks and tests.

Suggested change
becomes a /32. A policy with IPv6 or malformed entries is refused with `NetworkPolicyUnsupportedError` before
anything is created, and a fallback chain skips to its next provider. Tenki applies the policy outside the VM, so
becomes a /32. A policy with IPv6 or malformed CIDRs is refused with `NetworkPolicyUnsupportedError` before
anything is created, and a fallback chain skips to its next provider. Tenki applies the policy outside the VM, so
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/tenki/README.md
Line: 111-112

Comment:
**Docs overstate hostname checks**

The README promises that all malformed entries raise `NetworkPolicyUnsupportedError` before creation. But `supports_network_policy` only checks CIDRs and entry counts. A malformed hostname passes those checks and reaches `client.create`, so users can receive an SDK or API error instead of the promised early rejection.

Narrow the wording to malformed CIDRs, or add hostname checks and tests.

```suggestion
  becomes a /32. A policy with IPv6 or malformed CIDRs is refused with `NetworkPolicyUnsupportedError` before
  anything is created, and a fallback chain skips to its next provider. Tenki applies the policy outside the VM, so
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

if not isinstance(queue, asyncio.Queue) or not isinstance(done, asyncio.Event):
return False
while not done.is_set() and queue.qsize() > _STDIN_QUEUE_LIMIT:
await asyncio.sleep(0.005)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Upload delay has no name

The new queue-drain loop uses an inline 0.005 delay. The repository requires magic numbers to be stored as class or instance variables with descriptive names. Move this delay to a named class variable so its unit and purpose are clear. This repository requirement must be satisfied before merging.

Rule Used: Store magic numbers as class or instance variables with descriptive names rather than using them inline in the code. (source)

Learned From
scaleapi/scaleapi#126388

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/tenki/sandbox.py
Line: 274

Comment:
**Upload delay has no name**

The new queue-drain loop uses an inline `0.005` delay. The repository requires magic numbers to be stored as class or instance variables with descriptive names. Move this delay to a named class variable so its unit and purpose are clear. This repository requirement must be satisfied before merging.

**Rule Used:** Store magic numbers as class or instance variables with descriptive names rather than using them inline in the code. ([source](https://app.greptile.com/scale-ai/-/custom-context?memory=002e0051-41ad-46c1-9098-47433c580150))

**Learned From**
[scaleapi/scaleapi#126388](https://github.com/scaleapi/scaleapi/pull/126388)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Claude Code Fix in Codex

@earakely-scale
earakely-scale merged commit 230aa0f into scaleapi:main Oct 14, 2026
11 of 13 checks passed
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.

Add Tenki as an in-tree sandbox provider

2 participants