Skip to content

Models & Files

A model is a versioned workspace managed by Bridge Town. Each model belongs to a tenant (organization) and contains:

  • Files — Python model files/scripts, README, configuration, dashboards, data artifacts, and other support files
  • Data sources — Parquet snapshots uploaded from CSV, Excel, or Google Sheets
  • Output files — Results from model execution runs

Models maintain full version history. You can list branches, diff versions, and roll back to any previous commit.

Each user has a role on each model they can access:

RoleRead filesWrite filesDelete modelManage users
ViewerYesNoNoNo
EditorYesYesNoNo
OwnerYesYesYesYes

A file is anything stored inside a model: a Python model file/script, README, configuration, dashboard, or data artifact. A model file (or script) specifically means a Python file stored at model/<name>.py. Model file names must be valid Python identifiers (letters, digits, underscores; max 128 characters).

  1. Createcreate_file writes a new file and commits it
  2. Readread_file returns the source code
  3. Patchpatch_file applies targeted edits from an instruction
  4. Updatecommit_files overwrites the file and commits
  5. Runrun_model(mode='sync') executes run.py synchronously and returns results inline; run_model(mode='sync', path='<name>.py') runs a single file/script directly; run_model(mode='async')/get_run for background async execution; get_run_output fetches one completed run output by name
  6. Deletecommit_files removes the file and commits

Bridge Town’s supported default workflow is:

  1. create_file to scaffold the model file
  2. patch_file for small iterative changes
  3. commit_files for a full-file rewrite/deletion or when several files should land in one commit
  4. Branch-based scenario analysis via create_branch + compare_branches for model-level comparisons, optionally focusing the returned diff on one output; use compare_runs when both completed run IDs already exist

A model can chain multiple files in execution order by defining a PIPELINE list in run.py. Each file’s runtime output dict (its module-level result dict or supported outputs dict) is written to /upstream/<file_name>/outputs.json before the next file runs, allowing downstream files to read it:

# run.py — define execution order
PIPELINE = ["revenue", "expenses", "summary"]
# model/expenses.py — read from the upstream revenue file
import json, pathlib
_upstream = pathlib.Path("/upstream/revenue/outputs.json")
if _upstream.exists():
rev = json.loads(_upstream.read_text())
monthly_revenue = rev.get("monthly_revenue", [100_000] * 12)
else:
# Standalone fallback when /upstream is not mounted.
monthly_revenue = [100_000] * 12

/upstream/ is a run-scoped, branch-scoped tmpfs: it exists only for the duration of the run_model call and is never persisted. It is distinct from /data/, which holds immutable Google Sheet and CSV snapshots that serve as external inputs.

See Multi-File Pipelines for a complete walkthrough, including the recommended /upstream first, /data fallback read pattern and scenario-analysis integration.

Model files can declare module-level inputs, outputs, and dependencies as lists of strings to expose a static contract. These declarations are read by the describe_model MCP tool without executing the code, making pipelines easier to reason about and maintain:

model/summary.py
inputs = ["monthly_revenue", "monthly_expenses"]
outputs = ["total_revenue", "total_expenses", "net_income"]
dependencies = ["revenue", "expenses"]

Pair contract metadata with a module-level result dict that holds the actual runtime values. result is what the run pipeline returns and what gets written to /upstream/ for downstream files. This avoids any clash between the outputs contract list and the runtime values:

result = {
"total_revenue": 1_440_000,
"total_expenses": 960_000,
"net_income": 480_000,
}

Rules:

  • Use a list, tuple, or set of strings for contract metadata.
  • Use a dict for runtime values, assigned to result.
  • outputs = {...} (dict) is also accepted as a runtime output pattern when result is absent, but new model files should prefer the outputs = [...] + result = {...} pairing.
  • All three contract variables are optional; omitting them produces warnings in describe_model but does not break execution.

Repeated Python logic — cohort waterfalls, driver parsing, period helpers, output formatting — belongs in lib/, not copied across model files.

Convention: place shared helpers at lib/<module>.py and import them from any model file with package-style paths:

model/pnl.py
from lib.cohort import simulate_cohort
from lib.periods import quarter_labels
result = simulate_cohort(1_000_000)

This works because the model root (/repo) is always on sys.path inside the sandbox, so from lib.<module> import ... resolves as a regular Python package import. No configuration is required.

Managing lib/ files: use the same generic file tools as for model files:

create_file(path="lib/cohort.py", content="...")
commit_files(files=[{"action": "update", "path": "lib/cohort.py", "content": "..."}])
read_file(path="lib/cohort.py")

New models seeded with the auto-discovery scaffold include an empty lib/__init__.py to mark the directory as a Python package.

Rules:

  • lib/ files are never auto-executed as model files. Only model/*.py files are auto-discovered by run.py.
  • Do not use file-to-file imports (from model.customer_cohort import ...). Model files are executable entry points, not importable modules. Shared logic belongs in lib/.
  • lib/ is model-local. There is no supported mechanism for sharing code across models.

For supported platform helpers, use the immutable public releases rather than reconstructing source from an example. See Read Native Sheet Inputs in a Model and Output & Input Lineage Authoring for version-pinned downloads, SHA-256 verification, and runnable snippets.

"""Revenue forecast — 12-month projection with three product lines."""
MONTHS = 12
LINES = {
"SaaS": {"base": 50_000, "growth": 0.08},
"Services": {"base": 30_000, "growth": 0.03},
"Marketplace": {"base": 15_000, "growth": 0.12},
}
results = {}
for name, params in LINES.items():
monthly = []
revenue = params["base"]
for m in range(MONTHS):
monthly.append(round(revenue, 2))
revenue *= 1 + params["growth"]
results[name] = monthly
inputs = ["base_assumptions"]
outputs = ["monthly_revenue"]
dependencies = []
result = {"monthly_revenue": results}