feat(tools): RunPython — isolated Python execution with code mode + agent-in-agent - #62
Open
bpb02 wants to merge 3 commits into
Open
feat(tools): RunPython — isolated Python execution with code mode + agent-in-agent#62bpb02 wants to merge 3 commits into
bpb02 wants to merge 3 commits into
Conversation
Adds RunPython, a Timbal-native tool that runs LLM-authored Python in an isolated subprocess (uv PEP 723 with venv+pip fallback), with: - Code mode: the script can call exposed Timbal tools via a Unix-socket RPC bridge, plus top-level `await` support so async agent code just works. - Dependency handling: explicit deps + auto-detection from imports, with base-name dedupe so a pinned spec (e.g. `timbal @ file://...`) is not shadowed by a bare auto-detected import. - Hardening (E2B/Monty-inspired): secrets stripped from the child env by default with an `env_passthrough` allowlist, head+tail output truncation, process-group kill on timeout, and code/return-value size limits. - Headline use case validated end-to-end: install timbal inside the sandbox and run an Agent within an Agent (real Anthropic call). Includes ported competitor test cases (pydantic monty / mcp-run-python), hardening and async tests, real integration tests, and a manual validation runner. Registers RunPython in the tools package and ignores RunPython execution artifacts. Co-authored-by: Cursor <cursoragent@cursor.com>
…r module Move child-process logic from an embedded _CHILD_PREAMBLE string into _run_python_runner.py (stdlib-only, lintable, unit-testable). The parent now writes user_code.py and passes config via env (TIMBAL_USER_CODE_PATH, TIMBAL_EXPOSED_TOOLS) instead of interpolating code into a generated script. - uv executor uses --with per dependency (no PEP 723 header codegen) - venv executor runs the static runner after pip install - Add 8 unit tests for the runner module in isolation Co-authored-by: Cursor <cursoragent@cursor.com>
Use a localhost TCP server when asyncio.start_unix_server is unavailable (Windows CI). The child runner connects via tcp://host:port or unix path. Add a regression test that simulates the Windows transport. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
RunPython, a Timbal-native tool that executes LLM-authored Python in an isolated subprocess (uvPEP 723, withvenv+pip fallback). No MCP — everything is a Timbal tool.awaitis supported, so async agent code (await inner(...).collect()) just works.timbal @ file://...) is not shadowed by a bare auto-detected import.env_passthroughallowlist.max_output_chars) to protect the context window.uv/python children).return_value-size limits.timbalinside the sandbox and run an Agent within an Agent with a real Anthropic call.Tests
monty,mcp-run-python).awaittests.-m integration): inner agent runs; outer agent delegates to inner agent.python/tests/tools/validate_run_python_agent.py.Test plan
uv run python -m pytest python/tests/tools/test_run_python.py→ 62 passed (incl. 2 real integration)uv run ruff checkon all touched files → cleanuv run python -m pytest python/tests→ 2206 passed (server-test failures are a local.envTIMBAL_LOG_LEVELissue viaload_dotenv(), unrelated to this PR)ANTHROPIC_API_KEYset:pytest python/tests/tools/test_run_python.py -m integration -o addopts=""Next steps (planned)
Three follow-up workstreams, designed to compose (the tool-result journal IS the cache IS the artifact store):
1. Token-context optimization
return_valuefor large objects (shape/columns/head) instead of dumping the full value into the LLM context.{return_value, stdout(truncated), artifacts:[{id, kind, bytes, preview}]}.read_artifact(id)), keeping intermediates inside the sandbox.2. Code & artifact persistence ("save to view later")
script.py, stdout,return_value, and files the script produced (diff the run dir).OutputEvent.metadata; companionget_run_artifact(id)tool for on-demand fetch.3. Human-in-the-loop inside the script — replay design (chosen)
Align with Timbal's existing HITL model (
suspend()raisesSuspend, the run unwinds + persists, and on resume the handler re-executes from the top with memoized resume values keyed by a stablesuspension_id).(tool_name, args, ordinal).run_context._resume_values→ return it (replay); otherwise the parent raisesSuspend(...),RunPythonunwinds, kills the subprocess, and the normalPauseRequiredmachinery bubbles up so the outer agent emits anInteractionEvent/ApprovalEventand persists for resume.RunPythonre-runs the script from the top: non-HITL tool calls return journaled results (side effects don't re-fire), the HITL call returns the human's decision, and the script continues to the next suspension or to completion.interrupt().suspend()must come before non-idempotent side effects.Made with Cursor