code_execute
Run JavaScript, Python, or Bash in a sandboxed Docker container. No network access.
"No network" is the SANDBOX only — you (the agent) can fetch the web
The network isolation applies inside the sandbox container, not to you. Your own environment has full outbound network: WebSearch, WebFetch, and curl/wget in Bash all work. So the pattern for anything that needs an external asset or data is fetch first, then feed the sandbox — never conclude "I can't get that, no network."
- Downloading assets (sound effects, textures, sprite sheets, fonts, datasets, model weights):
curl/wgetthem straight into the project tree. They auto-sync to Gipity Storage/CDN and ship with adeploy. Then, if they need processing (trim, transcode, resize), run the sandbox on the already-local files (gipity sandbox run --input asset.ogg ...). - Example — grab a short CC0 dig sound, trim it, keep it in the app:
curl -sL -o src/audio/dig.ogg "https://<cc0-pack>/dig.ogg" # agent has network gipity sandbox run bash --input src/audio/dig.ogg \ "ffmpeg -y -i src/audio/dig.ogg -t 0.4 -af 'afade=t=out:st=0.3:d=0.1' src/audio/dig_trim.ogg" - Licensing: prefer CC0 / CC-BY / public-domain sources (e.g. Kenney.nl, freesound CC0, OpenGameArt, Minetest/Luanti's CC-licensed packs). Do not copy proprietary assets (real Minecraft/Mojang sounds, ripped game art). When no clean-licensed asset exists, synthesizing (WebAudio SFX, procedural textures) or generating with
gipity generateis the right fallback — but reach for it because the license is bad, not because you think you can't fetch.
File I/O Contract - "sandbox = your project"
/work/mirrors your project files. By default the whole project is auto-mirrored into/work/(up to 1 GB); for a larger project, setinput_files(CLI:--input) to narrow to just the files you need. Input paths are preserved: aninput_filesentry ofdata/foo.csvlands at/work/data/foo.csv.- Your code runs from the
/work/root (entry is placed at/work/_run.<ext>, not at its repo path), so write imports/opens relative to/work/, not to the source file's original folder. A module mirrored atsrc/js/engine.jsis./src/js/engine.js(or/work/src/js/engine.js) - a repo-relative../src/js/engine.jsresolves outside/work/and fails withERR_MODULE_NOT_FOUND. - Write files at any relative path under /work/ (or the cwd, which is /work/). Anything you create or modify is written back to the project at the same path.
- Scratch that should NOT be saved goes in a scratch namespace:
.gipityscratch/,tmp/, or any*_tmp/dir.tmp/,.tmp/, and.gipityscratch/already exist in every run - writetmp/chart.pngdirectly, nomkdirneeded. Build/conversion intermediates written by your code inside a run are never persisted to your project, so they don't clutter your tree or show up as phantom server-only files on later syncs. (These are the same dirsgipity sync/deploy ignores, so scratch is treated as throwaway everywhere - sandbox, sync, and deploy.) You still get to LOOK at them:gipity sandbox runwrites scratch outputs to your localtmp/etc. after the run (local-only, capped at 5MB per file) - sotmp/chart.pngis inspectable on disk without ever entering the project. Well-known build dirs (node_modules/,__pycache__/,.cache/,.venv/,.git/) are auto-excluded too. Everything else you write is saved - so place real outputs deliberately: deployable assets at their app path (src/images/logo.png), reference material you want to keep but not ship indocs/(docs/report.pdf, diagrams, decks), and working intermediates intmp/or.gipityscratch/. - Scratch is write-only, never read-in: a scratch dir is one-way. Because the mirror is built from the synced project and scratch dirs are never synced, a file you create locally at
tmp/frame.pngdoes not exist at/work/tmp/frame.png- the sandbox will fail with "no such file". Scratch works inside a run (create it and consume it in the same command), not as a place to stage inputs for a run. Stage sandbox inputs at a real project path (src/…,assets/…,docs/…), convert there, and delete the intermediate afterward if you don't want to keep it:gipity generate image "monster sprite" -o src/images/monster.png # staged at a synced path gipity sandbox run bash --input src/images/monster.png \ "convert src/images/monster.png -resize 128x128 src/images/monster.webp" rm src/images/monster.png # drop the intermediate; the delete syncs - If you write to a path that already exists, the project file is versioned (not overwritten destructively). Users and agents can roll back with
file_version_restore, so it's safe to transform files in place. - Unchanged input files are NOT round-tripped - we compare sha256, so you only pay the file write when you actually changed a file.
- Python users:
os.makedirs('thumbnails', exist_ok=True)before writing to a new subdir.ffmpegand most CLI tools auto-create parent dirs. - When you set
input_filesexplicitly (to narrow what's mirrored in): up to 5 GB per file, 6 GB total, 50 files per run - large media (video, audio) is fine. No network. 30s default / 600s max timeout. output_filesis an optional assertion: list paths you expect to produce, and you'll get a warning if any are missing. It does not filter - all new/modified files are returned regardless.
Running it (gipity sandbox run)
Code is the final positional arg. --language js|python|bash (required), --timeout <sec> (default 30, max 600), --input <path> (repeatable; narrows the /work/ mirror). The CLI pushes your local working tree up before running, so inputs staged any way - a Bash cp/ffmpeg/redirect, not just files written through an editor - are mirrored in; no manual gipity sync first. The one exception is the scratch namespaces (tmp/, .gipityscratch/, *_tmp/): they are never synced, so they are never mirrored in either - stage inputs at a real project path (see the File I/O Contract above). Files written under cwd (/work/) auto-sync back to the project at the same path. Add --json for machine output.
--discard-output <glob> (repeatable) drops matching run outputs entirely - not saved, not returned; you cannot look at a discarded file. For byproducts you want to LOOK at but never keep, don't discard - write them under tmp/ (comes back to local disk only, never enters the project). Globs support * (one segment), **, and dir/ prefixes; the CLI lists what was dropped.
To inspect a produced PDF, Read the synced file directly - the Read tool renders PDF pages when the host has poppler. If it fails ("pdftoppm is not installed"), rasterize in the sandbox instead (it always has poppler):
gipity sandbox run bash "pdftoppm -png -r 80 docs/report.pdf tmp/preview" # pages land in local tmp/, never persisted
The language is always explicit. There is no default: pin it with --language python (or py/bash/js), an interpreter token as the first arg (gipity sandbox run bash "<cmd>", gipity sandbox run python <file-or-code>), or --file script.py (inferred from the extension). A bare gipity sandbox run "<code>" fails immediately, locally, and prints these three forms - it never guesses an interpreter for you.
gipity sandbox run --language python "print(2 ** 10)"
gipity sandbox run --language python --timeout 120 "$(cat build_report.py)" # multi-line code, no shell-quoting pain
gipity sandbox run --language bash --input clip.mp4 "ffmpeg -y -i clip.mp4 clip.webm"
gipity sandbox run bash "echo hi; ffmpeg -version" # interpreter shorthand pins the language
gipity sandbox run --file build_report.py # language inferred from .py
Deterministic text questions - use text_analyze / gipity text, not the sandbox
For letter/word counts, substring occurrences and positions, nth word/char, anagram checks, or letter frequency, don't spin up the sandbox (and never answer from an LLM's head): the text_analyze tool (agent surface) and gipity text analyze "<text>" (CLI) run the same exact-arithmetic engine and answer instantly. CLI: pipe via stdin or use --file <path> for longer text. Targeted flags short-circuit the full profile: --count <substr>, --find <substr> (1-indexed positions), --word <n> / --char <n> (negative counts from the end), --anagram <other>; modifiers --whole-word, --case-sensitive, --json.
gipity text analyze "strawberry" --count r # "r" (case-insensitive): 3 non-overlapping ...
gipity text analyze --file notes.md # full profile: counts, words, frequency, palindrome
Pre-installed Runtimes
- Node.js 20 - full standard library
- Python 3 - pandas, numpy, matplotlib, scipy, sympy, pillow, openpyxl, xlsxwriter, xlrd, python-docx, python-pptx, reportlab, cairosvg, seaborn, qrcode, python-barcode, requests, bs4, lxml, pyyaml, Jinja2, tabulate, csvkit (+ agate-dbf, agate-excel, agate-sql, dbfread), pyfiglet, pytesseract, python-dateutil, python-slugify, Pygments, faker, wordcloud, networkx, SQLAlchemy, Markdown, babel, fonttools
- Perl 5.36 - full install
- GCC/G++, mingw-w64 (cross-compile to Windows .exe via
x86_64-w64-mingw32-gcc/-g++/-windres)
CLI Tools (prefer one-liners over scripts)
- FFmpeg - transcode, trim, extract frames/audio, generate thumbnails (
ffmpeg,ffprobe,ffplay) - ImageMagick (
convert/mogrify/identify/composite) - resize, crop, composite, format-convert - Graphviz (
dot,neato,sfdp,fdp,twopi,circo) - diagrams from DOT - gnuplot - CLI plotting
- sox - audio processing (convert, trim, mix, effects);
play/recalso available - jq - JSON query/transform
- sqlite3 - ad-hoc queries on .sqlite/.db
- PDF toolkit -
pdftotext,pdfimages,pdfinfo,pdfhtml,pdftoppm(PDF→PNG/PPM),pdftocairo(PDF→PNG/SVG),pdfunite(merge),pdfseparate(split),pdfattach/pdfdetach,pdfsig,pdffonts - ghostscript (
gs),ps2pdf/pdf2ps/ps2epsi/eps2eps/dvipdf- PostScript ↔ PDF - qpdf - merge, split, encrypt, linearize PDFs
- pandoc - convert markdown/HTML/docx/epub/rst/latex
- LibreOffice (
soffice --headless,lowriter,localc,loimpress,lodraw,loweb) - docx/xlsx/pptx → PDF or HTML - wkhtmltopdf / wkhtmltoimage - render HTML files to PDF or PNG
- exiftool, mediainfo - read/write file metadata
- potrace, mkbitmap - bitmap → SVG (
mkbitmappreprocesses forpotrace) - optipng, gifsicle - image optimization; gifdiff, gifview
- WebP toolkit -
cwebp(encode),dwebp(decode),gif2webp,img2webp,webpmux,webpinfo - diffimg - pixel-diff two images
- xmlstarlet - query/edit XML
- datamash, miller (
mlr) - tabular stats and CSV/JSON transforms - tesseract - OCR (Python:
pytesseract.image_to_string) - qrencode - generate QR code images
- fc-list, fc-match - fontconfig: list/match fonts on the system
- lp_solve - linear programming solver (LP/MIP)
- openssl - hashes, x509, keygen, encrypt/decrypt
- gpg (+
gpgsm) - sign, encrypt, verify - bc - arbitrary-precision calculator for bash
- tree - recursive directory listing
- p7zip (
7z), unzip/zip (zipinfo,zipsplit,zipcloak), unrar - hunspell, figlet/toilet, dos2unix/unix2dos, sed/awk/mawk/perl
Discovering tools
The toolset is fixed and there is no network, so pip install / npm install / apt-get will fail - never reach for them. Use only what's preinstalled, and if unsure whether a tool or package is available, probe before using it (a fast check is cheaper than a failed run):
- CLI:
command -v ffmpeg(exits 0 with path, 1 if missing) - Python:
python3 -c "import foo; print(foo.__version__)"orpip show foo - Node:
node -e "console.log(require.resolve('foo'))"
Worked Examples
1. FFmpeg - transcode MP4 → WebM
input_files: ["videos/clip.mp4"]
language: bash
code: ffmpeg -y -i videos/clip.mp4 -c:v libvpx-vp9 -b:v 1M -c:a libopus videos/clip.webm
Returns: project gains videos/clip.webm. The .mp4 is unchanged, so it is not re-written.
2. FFmpeg - extract 10 frames from a video
input_files: ["videos/clip.mp4"]
language: bash
code: |
mkdir -p frames
ffmpeg -y -i videos/clip.mp4 -vf "fps=10/$(ffprobe -v error -show_entries format=duration -of csv=p=0 videos/clip.mp4)" frames/frame_%03d.jpg
Returns: frames/frame_001.jpg … frames/frame_010.jpg.
3. ImageMagick - resize + convert format
input_files: ["images/photo.jpg"]
language: bash
code: |
mkdir -p thumbnails
convert images/photo.jpg -resize 400x400 -quality 85 thumbnails/photo.webp
Returns: thumbnails/photo.webp.
4. Python pandas - filter/aggregate a CSV
input_files: ["data/sales.csv"]
language: python
code: |
import pandas as pd
df = pd.read_csv('data/sales.csv')
q4 = df[df['quarter'] == 'Q4'].groupby('region')['amount'].sum().reset_index()
q4.to_csv('data/sales_q4.csv', index=False)
print(q4.to_string(index=False))
Returns: data/sales_q4.csv plus a printed preview on stdout.
5. Python matplotlib - chart a JSON metric series → PNG
input_files: ["data/metrics.json"]
language: python
code: |
import json, os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
d = json.load(open('data/metrics.json'))
os.makedirs('charts', exist_ok=True)
plt.plot(d['labels'], d['values'])
plt.xticks(rotation=45); plt.tight_layout()
plt.savefig('charts/metrics.png', dpi=120)
Returns: charts/metrics.png (inline in the reply).
6. Bash + poppler - PDF → plain text
input_files: ["docs/manual.pdf"]
language: bash
code: |
mkdir -p docs
pdftotext -layout docs/manual.pdf docs/manual.txt
wc -l docs/manual.txt
Returns: docs/manual.txt plus the line count on stdout.
7. qrencode - QR code for a deployed URL → PNG in the app
When the user asks for a QR code to a deployed app/URL (e.g. "stick a QR code on the front desk"), actually generate the image - don't hand back the URL and tell them to make one themselves.
language: bash
code: |
mkdir -p src/images
qrencode -o src/images/qr.png -s 10 -m 2 "https://app.gipity.ai/acme/front-desk/"
Returns: src/images/qr.png, saved into the app's src/ so it deploys with the site and is ready to print. Embed it on the page (<img src="images/qr.png">) if they want it shown in-app, and tell the user the file path. (-s sets pixel size per module, -m the quiet-zone margin.)
Audio/Music Generation - use dedicated tools
Do NOT use code_execute with sox/ffmpeg to generate music or sound effects. Use:
music_generate- AI-generated musicsound_generate- AI-generated sound effectsspeech_generate- AI-generated voice narration
These produce higher-quality results and handle file saving automatically.