Models & Files
Models
Section titled “Models”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.
Access control
Section titled “Access control”Each user has a role on each model they can access:
| Role | Read files | Write files | Delete model | Manage users |
|---|---|---|---|---|
| Viewer | Yes | No | No | No |
| Editor | Yes | Yes | No | No |
| Owner | Yes | Yes | Yes | Yes |
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).
Lifecycle
Section titled “Lifecycle”- Create —
create_filewrites a new file and commits it - Read —
read_filereturns the source code - Patch —
patch_fileapplies targeted edits from an instruction - Update —
update_fileoverwrites the file and commits - Run —
run(scope='model', mode='sync')executesrun.pysynchronously and returns results inline;run(scope='file', mode='sync')runs a single file/script directly;run(mode='async')/get_runfor background async execution;get_run_outputfetches one completed run output by name - Delete —
delete_fileremoves the file and commits
Supported Authoring Path
Section titled “Supported Authoring Path”Bridge Town’s supported default workflow is:
create_fileto scaffold the model filepatch_fileorupdate_filefor iterative changescommit_fileswhen a change spans multiple files in a model and should land in one commit- Branch-based scenario analysis via
create_branch+compare_branchesfor model-level comparisons, optionally focusing the returned diff on one output; usecompare_runswhen both completed run IDs already exist
Legacy generation/refinement tools may still exist in some deployments, but they are not part of the supported default path.
Pipelines within a model
Section titled “Pipelines within a model”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 a legacy 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 orderPIPELINE = ["revenue", "expenses", "summary"]# model/expenses.py — read from the upstream revenue fileimport 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 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 file metadata convention
Section titled “Model file metadata convention”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:
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 still recognised as a legacy runtime output pattern whenresultis absent, but new model files should prefer theoutputs = [...]+result = {...}pairing.- All three contract variables are optional; omitting them produces warnings
in
describe_modelbut does not break execution.
Shared helper code (lib/)
Section titled “Shared helper code (lib/)”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:
from lib.cohort import simulate_cohortfrom 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="...")update_file(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. Onlymodel/*.pyfiles are auto-discovered byrun.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 inlib/. lib/is model-local. There is no supported mechanism for sharing code across models.
Example model file
Section titled “Example model file”"""Revenue forecast — 12-month projection with three product lines."""MONTHS = 12LINES = { "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}