Sandbox Execution
Overview
Section titled “Overview”When you call run_model, Bridge Town executes your model’s run.py entry point—or the explicitly named file in path—inside an isolated Docker container. Synchronous calls return the results inline. The sandbox blocks network access and constrains filesystem access to mounted runtime paths only.
Security constraints
Section titled “Security constraints”| Constraint | Value |
|---|---|
| Network access | --network none — no outbound or inbound connections |
| Root filesystem | Read-only container root filesystem |
| Mounted paths | /repo (read-only), /data (read-only), /outputs (writable tmpfs), /tmp (writable tmpfs), /upstream (writable tmpfs, model runs only) |
| Timeout | 5 minutes (hard cap) |
| Memory | Capped per container |
| Packages | Standard library + numpy, pandas, openpyxl pre-installed |
Execution flow
Section titled “Execution flow”- Pull code — the model archive is fetched at the specified commit and mounted read-only at
/repo/ - Mount data — every connected data source’s latest snapshot is mounted read-only at
/data/ - Prepare writable scratch/output paths —
/outputs/and/tmp/are provided as writable tmpfs mounts; forrun_model(mode='sync')calls,/upstream/is also mounted as a writable tmpfs so pipeline models can exchange intermediate results - Run — the sandbox executes
run.pywhenpathis omitted, or the explicitly named Python file whenpathis provided - Capture — stdout, stderr, and files written to
/outputs/are collected - Record — a
ModelRunrecord is written with status, duration, and results - Return — all terminal results are returned inline to the MCP client
/data/ layout with multiple data sources
Section titled “/data/ layout with multiple data sources”Every connected data source gets its own collision-free canonical location:
/data/_sources/<data-source-id>/<file> # always present, one root per source/data/_sources.json # manifest: name, id, type, opaque # snapshot ID, safe handle, metadata, # canonical root, and files/data/<file> # short path when the file name is uniqueWhenever exactly one connected source produces a given relative path, that path
is also available directly under /data/ as a zero-copy short path to the
canonical file, not a duplicate download. A single-source model can therefore
read /data/<tab>.csv directly.
If two connected sources happen to produce the same relative path — e.g. two
CSV uploads that both contain a data.csv — the flat alias for that path is
omitted rather than one source silently winning or the run failing. Both
sources’ files are still mounted at their canonical /data/_sources/<id>/
paths, and the run proceeds normally. Read /data/_sources.json to resolve
the ambiguity explicitly, or rename one of the source tabs so the flat alias
comes back. In short: adding a second, even unrelated, data source can never
make an otherwise-runnable model unrunnable.
For browser-uploaded CSVs, prefer the stable runtime API instead of depending on the filesystem layout:
from bridge_town.data import read_csv
forecast = read_csv("spv_forecasts_v2")The API resolves only the immutable manifest reserved for this run. It never
looks up the latest source by name, exposes storage prefixes, or permits source
UUID/path guessing. Use source_metadata(name) when a model needs the opaque
snapshot ID, schema, row count, or safe runtime handle.
Output
Section titled “Output”run_model(mode='sync') and run_model(mode='sync', path='<name>.py') return results synchronously:
{ "run_id": "uuid", "model_name": "my-model", "branch": "main", "commit_sha": "abc123", "status": "success", "exit_code": 0, "stdout": "...", "stderr": "...", "stdout_truncated": false, "stderr_truncated": false, "outputs": {"forecast.json": {"q1": 1200000}}, "duration_seconds": 3.2, "data_snapshot_ref": "s3://..."}stdout and stderr are capped at 4 KB each in the inline response. When you need one named output from a completed run, call get_run_output with the returned run_id and output_name; it returns that output inline up to 10 MiB. Use get_run when you need the full run envelope or status details.
Synchronous vs. asynchronous execution
Section titled “Synchronous vs. asynchronous execution”Primary path — synchronous (run_model with mode='sync'):
run_model(mode='sync') executes the model’s run.py entrypoint and waits for completion, returning all results inline. run_model(mode='sync', path='<name>.py') runs a single <name>.py directly. No follow-up get_run call is needed.
Background path — asynchronous (run_model with mode='async' / get_run / list_runs):
run_model(mode='async') dispatches execution via Celery and returns a run_id immediately without waiting for the container to finish. Poll get_run until the status reaches a terminal state (success, failed, timed_out, or cancelled). Use this path when you need to queue many runs concurrently or want to submit work without blocking.
To review run history or locate a previous run’s run_id, use list_runs — it returns run summaries for a model ordered most-recent-first, with optional status filtering. Pass the run_id from list_runs to get_run_output for one named output or to get_run for the full run envelope.
Related guides
Section titled “Related guides”- Multi-File Pipelines — chaining files with
PIPELINEand/upstreamtransport