Repository navigation
Add a run_python dialog tool backed by an nsjail sandbox - #268
Conversation
The model can now run a Python script in a disposable sandbox, copy chat files into it, and send the files the script creates back to the chat. - New openplotva-sandbox crate: a supervisor binary that runs each call under nsjail (no network, uid 65534, 256 MiB, 1 CPU, 30 s, 16 pids, syscall deny list) with a per-session 100 MiB tmpfs home, plus the HTTP client the bot uses. Sessions are keyed by chat and thread, close 60 s after the last call or 5 min after opening, at most 4 at once. - sandbox/: image with nsjail built from source, CPython 3.14 and a pinned data, plotting, office and media package set installed at build time, Debian's standard ffmpeg, and the nsjail policy. - run_python tool: resolves file_ids seen in the same chat, uploads them, and after a clean exit sends send_files as photos, albums, animations, videos, audio or documents, recording the messages in history and the file metadata store. A rejected photo is resent as a document. - Stored run_python tool results render the script, so a follow-up turn edits the earlier code. The tool is offered only when SANDBOX_URL is set; SAFE virtual dialogs run scripts without delivering files. - Telegram builders for sendDocument, sendVideo and sendAnimation uploads. - Production compose runs the sandbox privileged on an internal network; the deploy workflow builds and pushes its image and the deploy script starts it and waits for health before recreating the app.
|
Health covers only the supervisor's HTTP listener; a host that cannot run nsjail would pass it and leave run_python failing on every call.
The drawing rule sends every "draw" request to draw_image, which would take a bar chart or a floor plan as an illustration prompt.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
- When Telegram rejects a photo group, each file is retried as a document on its own, and the report lists exactly the paths that reached the chat; the caption goes on the first message that gets through. - The deploy integration test passes OPENPLOTVA_SANDBOX_IMAGE, which the deploy script now requires. - Annotate the sandbox entrypoint: the supervisor needs root for tmpfs mounts and cgroups, and scripts run as uid 65534 inside nsjail.
|
PR-Agent finding "Partial album send misreported": fixed in 8a7924c. A rejected photo group is now retried file by file as documents, the delivery report lists exactly the paths that reached the chat, and the caption goes on the first message that gets through (test |
Code scanning ignores in-source Semgrep suppressions, so the missing-user-entrypoint alert stayed open. USER root states the intended identity; scripts still run as uid 65534 inside nsjail.
|
Danger warnings, handled in the PR description ("Deployment plan" and "Review notes"):
|
What
The dialog model gets a
run_pythontool. It runs a model-written Python script in a disposable nsjail sandbox, can copy chat files into it, and sends the files the script creates back to the chat. Typical uses: bar charts and plots, floor plans, tables from a CSV or a crawled page, file conversions, ffmpeg edits, exact calculations.The script is kept in the stored tool result, so a follow-up such as "make the bars blue" edits the earlier code instead of starting over.
Design notes:
docs/superpowers/specs/2026-10-20-python-sandbox-design.md.How
openplotva-sandboxcrate. A supervisor binary plus the HTTP client the bot uses.POST /v1/sessions/{key}/run,GET /v1/sessions/{key}/files/{path},DELETE /v1/sessions/{key},GET /health. File access never follows symlinks.sandbox/image. nsjail 3.6 is built from source. CPython 3.14 gets a pinned package set (requirements.in→requirements.txt) at build time, Debian's standard ffmpeg is installed, and the matplotlib font cache is prebuilt.crates/openplotva-app/src/python_sandbox.rs).file_idsonly when the file was seen in the same chat and downloads them.send_files: photos (albums of up to 10),.gifas animations,.mp4as videos,.mp3/.m4aas audio, and other files as documents. A photo Telegram rejects is resent as a document.telegram_files.python_error,python_timeout,python_killed,sandbox_busyandsandbox_unavailable.DialogToolbox::offers_toolhidesrun_pythonunlessSANDBOX_URLis set. SAFE runtime virtual dialogs run the script but withhold delivery.run_pythontool results render the call'scodenext to its output.prompts/aifarm/system.promptsends data charts, plans, tables, file conversions and exact calculations torun_pythoninstead ofdraw_image, and tells the model to edit the earlier script for a fix-up.openplotva-telegramgains builders forsendDocument,sendVideoandsendAnimationuploads.sandboxservice on an internal network shared only with the app, plusSANDBOX_URLfor the app.…/openplotva-sandbox:<sha>.Verification
cargo fmt --all -- --check,cargo clippy --locked --workspace --all-targets --all-features -- -D warnings,cargo test --locked --workspace,tools/rust-dependency-gate.sh: pass./proc,unshare/mountdenied, the 16th process fails with EAGAIN, files over 100 MiB fail with EFBIG, and the home fills at 100 MiB.killed, and an endless loop ends astimeoutafter 30 s.NameErrorcame back aspython_errorwith the traceback.python_killed.Risk
Deployment plan
openplotva-sandboximage. The first sandbox build compiles nsjail and installs the package set; later builds reuse themode=maxcache.deploy-production.shpulls both images and starts the database services as before. It then startssandbox, waits for its health check, and runs a smoke script (print(6 * 7)must return42).python_sandbox.run_pythoncall through the runtime API virtual dialog (SAFE mode runs the script without sending files);Rollback: set
SANDBOX_URL=in the production env and recreate the app. The tool then disappears from the model's tool list, and the sandbox container can be stopped.Review notes
actionlint .github/workflows/deploy-production.ymlpasses. The workflow and compose files parse as YAML.docker compose configrenders thesandboxservice, the internalsandbox-netandSANDBOX_URL.shellcheck deploy/production/deploy-production.shis clean.tools/maintenance/testspasses (203 tests).<agent_behavior>ofprompts/aifarm/system.prompt. Partials, role markers and template variables are unchanged. As with any prompt edit, the cached prefix is rebuilt once.sandbox/requirements.txtandCargo.lock. The rest is one feature (supervisor, tool, deploy) that only works as a whole.