Skip to content

Commit 9c7bac8

Browse files
committed
Add docs
1 parent 153ca96 commit 9c7bac8

4 files changed

Lines changed: 507 additions & 10 deletions

File tree

docs/mkdocs/en/code_executor.md

Lines changed: 242 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ When this feature is enabled, if the LLM returns text containing code snippets,
66

77
## Code Executor Types
88

9-
Two types of code executors are currently available:
9+
Three types of code executors are currently available:
1010

1111
### UnsafeLocalCodeExecutor
1212

@@ -34,6 +34,21 @@ Two types of code executors are currently available:
3434
- Scenarios requiring execution of untrusted code
3535
- Scenarios requiring environment isolation
3636

37+
### CubeCodeExecutor
38+
39+
**Features:**
40+
- Agent dispatches code snippets to a remote Cube/E2B sandbox for execution; supports `Python/Bash`
41+
- Strong sandboxed environment running on a remote host, suitable for executing untrusted code at scale
42+
- Decoupled lifecycle: the same sandbox can be re-attached across processes via `sandbox_id` (`create` / `attach` / `create_or_recreate` factories)
43+
- Ships an optional `CubeWorkspaceRuntime` that adds per-execution workspace directories, file upload/download (single files or whole directories via tar), and structured program runs — useful for the Skill subsystem
44+
- Requires the optional `[cube]` extra (`pip install 'trpc-agent-py[cube]'`, which installs `e2b-code-interpreter`) and access to a Cube/E2B-compatible gateway
45+
46+
**Use Cases:**
47+
- Production environments where Docker is not available on the agent host
48+
- Scenarios requiring strong remote isolation for untrusted code
49+
- Long-lived skill/code execution that needs a persistent workspace surviving across multiple `execute_code` calls
50+
- Multi-tenant agent platforms that share a remote sandbox fleet
51+
3752
## Usage Examples
3853

3954
When creating an LlmAgent, build a CodeExecutor and configure the `code_executor` parameter to enable code execution functionality.
@@ -48,15 +63,19 @@ from trpc_agent_sdk.models import OpenAIModel
4863
from trpc_agent_sdk.code_executors import BaseCodeExecutor
4964
from trpc_agent_sdk.code_executors import UnsafeLocalCodeExecutor
5065
from trpc_agent_sdk.code_executors import ContainerCodeExecutor
66+
# Cube is an optional extra (`pip install 'trpc-agent-py[cube]'`)
67+
from trpc_agent_sdk.code_executors.cube import CubeCodeExecutor
68+
from trpc_agent_sdk.code_executors.cube import CubeCodeExecutorConfig
5169
from trpc_agent_sdk.log import logger
5270

53-
def _create_code_executor(code_executor_type: str = "unsafe_local") -> BaseCodeExecutor:
71+
async def _create_code_executor(code_executor_type: str = "unsafe_local") -> BaseCodeExecutor:
5472
"""Create a code executor.
5573
5674
Args:
5775
code_executor_type: Type of code executor to use. Options:
5876
- "unsafe_local": Use UnsafeLocalCodeExecutor (default, no Docker required)
5977
- "container": Use ContainerCodeExecutor (requires Docker)
78+
- "cube": Use CubeCodeExecutor (requires the [cube] extra and a Cube/E2B gateway)
6079
- None: Auto-detect from environment variable CODE_EXECUTOR_TYPE,
6180
or default to "unsafe_local"
6281
@@ -76,9 +95,18 @@ def _create_code_executor(code_executor_type: str = "unsafe_local") -> BaseCodeE
7695
executor = ContainerCodeExecutor(image="python:3-slim", error_retry_attempts=1)
7796
logger.info("ContainerCodeExecutor initialized successfully")
7897
return executor
98+
elif code_executor_type == "cube":
99+
# CubeCodeExecutor reads E2B_API_URL / E2B_API_KEY / CUBE_TEMPLATE_ID
100+
# from the environment when the corresponding cfg fields are unset.
101+
# `create()` opens a fresh remote sandbox; pass `sandbox_id=...` in
102+
# the cfg to attach to an existing one instead.
103+
cfg = CubeCodeExecutorConfig(execute_timeout=30.0, idle_timeout=600)
104+
executor = await CubeCodeExecutor.create(cfg)
105+
logger.info("CubeCodeExecutor initialized: sandbox_id=%s", executor.sandbox_id)
106+
return executor
79107
else:
80108
raise ValueError(f"Invalid code executor type: {code_executor_type}. "
81-
"Valid options are: 'unsafe_local', 'container'")
109+
"Valid options are: 'unsafe_local', 'container', 'cube'")
82110

83111
```
84112

@@ -154,6 +182,68 @@ def create_agent() -> LlmAgent:
154182
![ContainerCodeExecutor Execution Result](../assets/imgs/container0.png)
155183
![ContainerCodeExecutor Execution Result 1](../assets/imgs/container1.png)
156184

185+
### Using CubeCodeExecutor
186+
187+
```python
188+
# ...
189+
async def create_agent() -> LlmAgent:
190+
"""Create an agent backed by a remote Cube/E2B sandbox.
191+
192+
Required environment (read by CubeCodeExecutorConfig.resolve_*):
193+
- E2B_API_URL: Cube/E2B-compatible gateway URL
194+
- E2B_API_KEY: API key for the gateway
195+
- CUBE_TEMPLATE_ID: Cube template id (e.g. `std-XXXXXXXX`)
196+
197+
Note: `_create_code_executor` is async because `CubeCodeExecutor.create`
198+
opens the remote sandbox over the network. The executor owns the
199+
sandbox; call `await executor.destroy()` when the agent shuts down to
200+
free the remote resource. `executor.close()` only drops the local
201+
handle and lets the sandbox idle out on its own.
202+
"""
203+
# Select cube
204+
executor = await _create_code_executor(code_executor_type="cube")
205+
agent = LlmAgent(
206+
name="code_assistant",
207+
description="Code execution assistant",
208+
model=_create_model(), # You can change this to your preferred model
209+
instruction=INSTRUCTION,
210+
code_executor=executor, # Enables code execution functionality
211+
)
212+
return agent
213+
214+
# Install the optional extra before use:
215+
# pip install 'trpc-agent-py[cube]'
216+
# And export the gateway credentials:
217+
# export E2B_API_URL=...
218+
# export E2B_API_KEY=...
219+
# export CUBE_TEMPLATE_ID=...
220+
```
221+
222+
#### Attaching to an existing sandbox
223+
224+
`CubeCodeExecutor` exposes three async factories so callers can choose the
225+
lifecycle policy explicitly. All three read the bound sandbox id from
226+
`cfg.sandbox_id` so it is the single source of truth:
227+
228+
```python
229+
# 1. Strict create-or-attach: when cfg.sandbox_id is set, attach and assert
230+
# the sandbox is RUNNING; otherwise create a fresh one.
231+
executor = await CubeCodeExecutor.create(cfg)
232+
233+
# 2. Attach-only: requires cfg.sandbox_id to be set; never creates fresh.
234+
executor = await CubeCodeExecutor.attach(cfg)
235+
236+
# 3. Attach-or-recreate: invokes `on_recreate` when the sandbox is gone,
237+
# then transparently provisions a new one. Useful for long-lived agents
238+
# whose external locator state must be cleared on recreate.
239+
executor = await CubeCodeExecutor.create_or_recreate(
240+
cfg, on_recreate=lambda old_id: clear_locator(old_id),
241+
)
242+
```
243+
244+
`close()` is a no-op for the remote sandbox (it just drops the local
245+
handle); `destroy()` explicitly kills the remote sandbox.
246+
157247
## Configuration Parameters
158248

159249
### UnsafeLocalCodeExecutor Parameters
@@ -208,6 +298,117 @@ code_executor = ContainerCodeExecutor(
208298
)
209299
```
210300

301+
### CubeCodeExecutor Parameters
302+
303+
`CubeCodeExecutor` is configured via two dataclasses split by ISP:
304+
`CubeCodeExecutorConfig` carries only sandbox-lifecycle / command-execution
305+
settings, and `CubeWorkspaceRuntimeConfig` carries only workspace settings
306+
(see the next section).
307+
308+
```python
309+
from trpc_agent_sdk.code_executors.cube import (
310+
CubeCodeExecutor,
311+
CubeCodeExecutorConfig,
312+
)
313+
314+
cfg = CubeCodeExecutorConfig(
315+
# Cube template id for new sandboxes; falls back to env CUBE_TEMPLATE_ID.
316+
template=None,
317+
318+
# E2B-compatible Cube API URL; falls back to env E2B_API_URL.
319+
api_url=None,
320+
321+
# E2B API key; falls back to env E2B_API_KEY.
322+
api_key=None,
323+
324+
# Existing remote sandbox id. When set, factories attach instead of
325+
# creating a fresh sandbox.
326+
sandbox_id=None,
327+
328+
# Default per-command timeout in seconds (float). Shared by the bare
329+
# executor and the workspace runtime. Default: 60.0.
330+
execute_timeout=60.0,
331+
332+
# Sandbox idle lifetime in seconds (int >= 1); renewed on every
333+
# command. Default: 3600 (1 hour). The underlying e2b API takes
334+
# integer seconds — sub-second values are rejected at construction.
335+
idle_timeout=3600,
336+
)
337+
338+
executor = await CubeCodeExecutor.create(cfg)
339+
```
340+
341+
`CubeCodeExecutor` accepts the same `code_block_delimiters` as the other
342+
executors; by default it adds a `bash` delimiter on top of the default
343+
`python` and `tool_code` delimiters so plain `\`\`\`bash\n ... \n\`\`\``
344+
fences are also picked up.
345+
346+
## CubeWorkspaceRuntime
347+
348+
For skill execution and other use cases that need a per-execution
349+
workspace (input staging, structured program runs, output collection),
350+
the Cube package additionally ships `CubeWorkspaceRuntime`. It composes
351+
`CubeWorkspaceManager` (workspace directory lifecycle), `CubeWorkspaceFS`
352+
(file/directory upload, download and glob-based collection), and
353+
`CubeProgramRunner` (structured `cmd` + `args` execution) on top of the
354+
same `CubeSandboxClient`.
355+
356+
```python
357+
from trpc_agent_sdk.code_executors._types import (
358+
WorkspaceOutputSpec,
359+
WorkspacePutFileInfo,
360+
WorkspaceRunProgramSpec,
361+
)
362+
from trpc_agent_sdk.code_executors.cube import (
363+
CubeCodeExecutor,
364+
CubeCodeExecutorConfig,
365+
CubeWorkspaceRuntimeConfig,
366+
create_cube_workspace_runtime,
367+
)
368+
369+
executor = await CubeCodeExecutor.create(CubeCodeExecutorConfig())
370+
371+
# `workspace_cfg` is optional. When omitted the runtime uses
372+
# DEFAULT_REMOTE_WORKSPACE = "/workspace/cube_agent" as the root.
373+
runtime = create_cube_workspace_runtime(
374+
executor,
375+
workspace_cfg=CubeWorkspaceRuntimeConfig(
376+
# Remote root under which the manager creates per-execution
377+
# `ws_<exec_id>_<suffix>` subtrees.
378+
remote_workspace="/workspace/cube_agent",
379+
),
380+
)
381+
382+
manager = runtime.manager()
383+
fs = runtime.fs()
384+
runner = runtime.runner()
385+
386+
ws = await manager.create_workspace("demo-1") # /workspace/cube_agent/ws_demo-1_<ts>
387+
388+
await fs.put_files(ws, [
389+
WorkspacePutFileInfo(path="work/script.py",
390+
content=b"print('script ran')\n"),
391+
])
392+
393+
run_result = await runner.run_program(
394+
ws,
395+
WorkspaceRunProgramSpec(cmd="python3", args=["work/script.py"], timeout=15.0),
396+
)
397+
print(run_result.exit_code, run_result.stdout)
398+
399+
outputs = await fs.collect_outputs(
400+
ws, WorkspaceOutputSpec(globs=["work/*.py"], inline=True),
401+
)
402+
for ref in outputs.files:
403+
print(ref.name, len(ref.content))
404+
405+
await manager.cleanup("demo-1")
406+
```
407+
408+
The runtime plugs straight into the Skill subsystem — pass it as
409+
`workspace_runtime` when constructing a skill repository (see
410+
[skill.md](skill.md) for details).
411+
211412
## Code Block Format
212413

213414
The Agent automatically identifies and executes code blocks in LLM responses. Supported code block formats:
@@ -245,6 +446,10 @@ After code execution, the results are returned to the LLM in the following forma
245446
- Python (`python`, `py`, `python3`, empty string defaults to Python)
246447
- Bash (`bash`, `sh`)
247448

449+
### CubeCodeExecutor
450+
- Python (`python`, `py`, `python3`, empty string defaults to Python)
451+
- Bash (`bash`, `sh`)
452+
248453
## Workflow
249454

250455
1. **User Query** → Agent receives the user query
@@ -291,10 +496,42 @@ code_executor = UnsafeLocalCodeExecutor(timeout=30) # 30-second timeout
291496
- Review the log output; the framework logs detailed error information
292497
- For ContainerCodeExecutor, check the container logs
293498

499+
### 4. CubeCodeExecutor Cannot Connect / Authenticates as Wrong Tenant
500+
501+
**Problem:** `CubeCodeExecutor.create` raises with messages like
502+
`Cube sandbox requires \`api_url\` or E2B_API_URL env`, `... api_key ...`,
503+
or `... template ... CUBE_TEMPLATE_ID ...`.
504+
505+
**Solution:**
506+
- Install the optional extra: `pip install 'trpc-agent-py[cube]'`
507+
- Export the three required env vars (or pass them on
508+
`CubeCodeExecutorConfig`): `E2B_API_URL`, `E2B_API_KEY`, `CUBE_TEMPLATE_ID`
509+
- For multi-tenant deployments, prefer setting the cfg fields explicitly so
510+
each agent instance uses its own credentials instead of falling back to
511+
the process-wide environment
512+
513+
### 5. CubeCodeExecutor Sandbox Disappears Between Calls
514+
515+
**Problem:** A sandbox attached via `cfg.sandbox_id` raises
516+
`SandboxNotFoundException` (gone) or `SandboxException` (PAUSED) on the
517+
next command.
518+
519+
**Solution:**
520+
- For long-lived agents, use `CubeCodeExecutor.create_or_recreate(cfg, on_recreate=...)`
521+
so the executor transparently provisions a new sandbox and notifies the
522+
caller to clear any external locator state
523+
- Tune `idle_timeout` (default 3600s) upward if you legitimately need a
524+
longer idle window between commands; every command renews the lease
525+
- Use `CubeWorkspaceManager.cleanup(exec_id)` instead of `executor.destroy()`
526+
if you only want to drop one workspace while keeping the sandbox alive
527+
294528
## Complete Example
295529

296530
See the complete example code: [examples/code_executors/agent/agent.py](../../../examples/code_executors/agent/agent.py)
297531

532+
End-to-end Cube example (executor + workspace runtime):
533+
[examples/code_executors/cube_demo.py](../../../examples/code_executors/cube_demo.py)
534+
298535
## Security Recommendations
299536

300537
1. **Production Environment**: It is strongly recommended to use `ContainerCodeExecutor` for sandbox isolation
@@ -307,4 +544,5 @@ See the complete example code: [examples/code_executors/agent/agent.py](../../..
307544

308545
- **UnsafeLocalCodeExecutor**: Fast execution speed, suitable for rapid iteration
309546
- **ContainerCodeExecutor**: The initial startup requires pulling the image; subsequent executions are relatively fast
310-
- It is recommended to use ContainerCodeExecutor in production environments and UnsafeLocalCodeExecutor in development environments
547+
- **CubeCodeExecutor**: Adds network round-trips to a remote sandbox per command, but amortizes well for long-lived sessions because the sandbox is reused across calls (and across processes via `sandbox_id`); workspace file transfers use a tar-based protocol so directory uploads/downloads stay a single round-trip
548+
- It is recommended to use ContainerCodeExecutor or CubeCodeExecutor in production environments and UnsafeLocalCodeExecutor in development environments

docs/mkdocs/en/skill.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@ from trpc_agent_sdk.skills import SkillToolSet
102102
from trpc_agent_sdk.skills import create_default_skill_repository
103103
from trpc_agent_sdk.code_executors import create_local_workspace_runtime
104104
from trpc_agent_sdk.code_executors import create_container_workspace_runtime
105+
# Cube is an optional extra (`pip install 'trpc-agent-py[cube]'`); import lazily.
106+
# from trpc_agent_sdk.code_executors.cube import CubeCodeExecutor, CubeCodeExecutorConfig
107+
# from trpc_agent_sdk.code_executors.cube import create_cube_workspace_runtime
105108

106-
# Create workspace runtime (local or container)
109+
# Create workspace runtime (local, container, or cube)
107110
workspace_runtime = create_local_workspace_runtime()
108111
# Or use container: workspace_runtime = create_container_workspace_runtime()
112+
# Or use a remote Cube/E2B sandbox:
113+
# executor = await CubeCodeExecutor.create(CubeCodeExecutorConfig())
114+
# workspace_runtime = create_cube_workspace_runtime(executor)
109115

110116
# Create skill repository
111117
repository = create_default_skill_repository("./skills", workspace_runtime=workspace_runtime)
@@ -1073,11 +1079,20 @@ LLM calls skill_run(skill="python-math", command="python3 scripts/fib.py 10")
10731079
- Executes commands directly on the local system, suitable for development and testing
10741080
- **Container executor** (Docker): [trpc_agent_sdk/code_executors/container/_container_ws_runtime.py](../../../trpc_agent_sdk/code_executors/container/_container_ws_runtime.py)
10751081
- Executes in Docker containers, providing better isolation
1082+
- **Cube executor** (remote E2B sandbox): [trpc_agent_sdk/code_executors/cube/_runtime.py](../../../trpc_agent_sdk/code_executors/cube/_runtime.py)
1083+
- Executes inside a remote Cube/E2B sandbox; suitable for environments without local Docker, or when strong remote isolation is required
1084+
- Construct via `create_cube_workspace_runtime(executor, workspace_cfg=...)`; see [code_executor.md](code_executor.md#cubeworkspaceruntime) for details
1085+
- Requires the optional `[cube]` extra (`pip install 'trpc-agent-py[cube]'`) and the `E2B_API_URL` / `E2B_API_KEY` / `CUBE_TEMPLATE_ID` environment variables (or equivalent cfg fields)
10761086

10771087
**Container executor notes**:
10781088
- The run base directory is writable; when `$SKILLS_ROOT` is set, it is mounted in read-only mode
10791089
- Network access is disabled by default for reproducibility and security
10801090

1091+
**Cube executor notes**:
1092+
- File and directory transfers use a tar-based protocol so directory upload/download stays a single round-trip and preserves symlinks/permissions
1093+
- The remote workspace root defaults to `/workspace/cube_agent`; per-execution subtrees follow the `ws_<exec_id>_<suffix>` naming convention and are recreated lazily on every `create_workspace` call (so external sandbox cleanup heals transparently)
1094+
- The same Cube sandbox can back both the bare `CubeCodeExecutor` and the workspace runtime; commands share `execute_timeout` from `CubeCodeExecutorConfig`
1095+
10811096
**Security and resource limits**:
10821097
- **Workspace isolation**: All read/write operations are confined within the workspace
10831098
- **Risk control**: Reduces security risks through timeout mechanisms and read-only skill trees

0 commit comments

Comments
 (0)