def run_py_in_process(code):
"""What most products do: exec in the same process. No limits."""
out = io.StringIO()
t0 = time.perf_counter()
try:
with contextlib.redirect_stdout(out):
exec(code, {"__name__": "__main__"})
return True, out.getvalue(), round((time.perf_counter() - t0) * 1000)
except Exception as e:
return False, f"{type(e).__name__}: {e}\n{out.getvalue()}", round((time.perf_counter() - t0) * 1000)← All cases
Case 2 · Measured on 2026-10-16 · Synsema 0.6.44 · same data, same model on both sides
Running the code an LLM wrote
When an LLM writes the code that answers a question about your data, what can that code do once you run it?
58×
faster sandbox: 34 ms in Synsema, 1,987 ms in Docker
An LLM writes the analysis, Synsema runs it sandboxed in 34 ms. Docker needs 2 s.
The problem
Most AI analytics products work the same way: the model writes Python or SQL, and something runs it. That code was written by a model that can make mistakes or be steered by the data it reads.
Teams either run it in-process with every permission, or pay for a container sandbox on every question.
What we built
- Ten questions over ten scattered files (CSV, Parquet, JSONL), with the correct answers computed separately.
- The same model and the same schema for three variants: the model writes Synsema (run with run_program and a permission ceiling), pandas (run with exec, and again inside Docker) or DuckDB SQL.
- Four escape tests against every executor: read .env, reach the network, overwrite the data, run forever.
Results
| Synsema | Python / pandas | SQL / DuckDB | |
|---|---|---|---|
| Correct answers | ✓10 / 10 | ✓10 / 10 | ✓10 / 10 |
| Attempts needed (minimum 10) | ✗11 | ✓10 | ✓10 |
| Run time, median | ✓34 ms, sandboxed | ·29 ms unsandboxed · 1,987 ms in Docker | ·29 ms unsandboxed |
| Test | Python exec | DuckDB default | DuckDB restricted | Synsema run_program |
|---|---|---|---|---|
| Read .env (your API keys) | ✗allowed | ✗allowed | ✓blocked | ✓blocked |
| Reach the network | ✗allowed | ✗allowed | ✓blocked | ✓blocked |
| Overwrite the data | ✗allowed | ✗allowed | ✗allowed | ✓blocked |
| Infinite loop | ✗cannot be stopped | ·not tested | ·not tested | ✓killed at 3 s |
The code, side by side
The usual way
Synsema
let CEILING be "stdout,file.read=./cases/02-llm-written-analytics/data/*"
-- Fixed preamble: the generated code may ask for more, the ceiling trims it anyway
let PREAMBLE be "require file.read(\"./cases/02-llm-written-analytics/data/*\")\n" let run be run_program(PREAMBLE + code, {"ceiling": CEILING, "profile": "native", "timeout": 20})Why Synsema
- run_program starts a child process of the same binary under a ceiling that is the intersection with the parent's permissions. The child can never exceed it.
- The generated code even asked for the network and for .env with require. The ceiling trimmed them: declared but above the host ceiling.
- No container, no image, no daemon. A sandboxed answer costs about as much as an unsandboxed one.
What it does not solve
- Models know Synsema less than Python or SQL: 1 extra attempt across 10 questions and higher LLM latency, because the language guide travels with every request.
- Restricted DuckDB is a good choice when SQL is enough, but allowed_directories also allows writing, and it does not stop endless queries by itself.
- SQL covers aggregations well. For multi-step logic, Python or Synsema read more naturally.
Reproduce it
git clone https://github.com/synsema/why-synsema && cd why-synsema # cases/02-llm-written-analytics synsema run cases/02-llm-written-analytics/ask.syn -- "Which city had the most rain?" uv run --with polars python cases/02-llm-written-analytics/comparison/truth.py uv run --with pandas --with duckdb --with pyarrow python cases/02-llm-written-analytics/comparison/compare.py uv run --with duckdb --with pandas python cases/02-llm-written-analytics/comparison/security.py
github.com/synsema/why-synsema/tree/main/cases/02-llm-written-analytics