1.3.0: answers and trust#
Status: proposed · Base: 1.2.0
1.3.0 is the release where Modelly stops being “a faster Mesa” and becomes the place a researcher goes to build, calibrate, explain, scale and publish an agent-based model, whatever their field. It is the largest release so far: 16 epics, 8 sprints, three public pre-releases. Everything in it works on Windows, macOS and Linux (see Platforms).
It stays backward compatible. Everything a 1.2 model uses keeps working; what 1.3 wants to change is deprecated with a warning and removed only in 2.0 (see Compatibility). That is why it is numbered 1.3.0 and not 2.0.0, even though it is a major release in scope.
Where we start#
What 1.2.0 already gives us, and what each piece unlocks for 1.3.0:
1.2.0 has |
1.3.0 builds on it with |
|---|---|
|
Experiments, calibration and sensitivity analysis that are reproducible by construction |
Checkpoints ( |
Time travel, resumable sweeps, run bundles for publication |
Columns, |
Free-threaded parallel phases, compiled |
|
|
Solara, Altair and Matplotlib backends |
Modelly Studio, publication figures, animations |
Eight Mesa-vs-Modelly playbooks with agreement and determinism checks |
A model zoo of validated reference models across research fields |
Versioned docs ( |
Domain tutorials, a course pack, a gallery |
Wheels for Linux, macOS and Windows x86-64 |
Windows ARM64, free-threaded wheels |
What 1.2.0 does not yet have, and researchers keep asking for:
A way to fit a model to data and say how good the fit is.
A way to say which parameters matter before spending a cluster budget.
A way to run large sweeps on a laptop, a workstation or a cluster with the same code.
A way to make a published result checkable by a reviewer in one command.
Real-world inputs: maps, census tables, road networks, contact matrices.
Agents that reason in language, without giving up reproducibility.
Ready-made, validated models for their own field, not only for the classic toy models.
Who 1.3.0 is for#
1.3.0 is designed around researchers from many fields, not one. Each epic names the people it serves; this table is the overview.
Field |
Typical question |
What 1.3.0 gives them |
|---|---|---|
Epidemiology and public health |
Which intervention bends the curve, and how sure are we? |
Age-structured SEIR in the zoo, contact matrices, ABC calibration to case data, Sobol indices on intervention parameters |
Ecology and conservation |
How does a population respond to habitat loss? |
Rasters and landscapes, individual-based energy budgets, cohorts for large populations, pattern-oriented calibration |
Economics and finance |
What macro pattern emerges from these micro rules? |
Market and order-book models, wealth and inequality collectors, method-of-moments calibration |
Sociology and political science |
How do opinions polarise, and what breaks segregation? |
Opinion dynamics on multilayer networks, survey-data initialisation, sensitivity screening |
Urban studies and transport |
What happens to congestion if we change this road? |
Real road networks, geographic space, commuting flows, traffic models |
Energy and climate |
How fast do households adopt heat pumps or solar? |
Diffusion-of-innovation models, GIS of building stock, scenario sweeps |
Archaeology and anthropology |
Which land-use rules reproduce the settlement pattern? |
Rasters, long runs with checkpoints, ABC against site data |
Computational social science with LLMs |
How do language-model agents behave in groups? |
|
Teaching |
How do I teach ABM in a course? |
Course pack, |
Research software engineers |
How do I run this on our cluster and keep it reproducible? |
Executors for SLURM, Dask and Ray, run manifests, |
The five pillars#
The 16 epics group into five pillars. A pillar is a promise to a user; an epic is how we keep it.
flowchart TB
goal["1.3.0: build, calibrate, explain, scale and publish an ABM"]
goal --> P1["Answers<br/>E1 Calibration<br/>E2 Sensitivity<br/>E3 Experiments"]
goal --> P2["Trust<br/>E4 Provenance<br/>E6 Time travel<br/>E16 Quality"]
goal --> P3["Reach<br/>E8 Geospatial<br/>E9 Networks<br/>E10 Model zoo<br/>E7 LLM agents"]
goal --> P4["Scale<br/>E11 Performance<br/>E12 Data and interop"]
goal --> P5["Experience<br/>E5 Studio<br/>E13 Developer tools<br/>E14 Visualisation<br/>E15 Teaching and community"]
Pillar |
Promise |
Epics |
|---|---|---|
Answers |
A study ends with numbers you can defend: fitted parameters, their uncertainty, and which ones matter |
E1, E2, E3 |
Trust |
Every result can be traced to code, data, seed and environment, and rerun by someone else |
E4, E6, E16 |
Reach |
Whatever your field and your data, there is a starting point that already works |
E7, E8, E9, E10 |
Scale |
The same model runs ten times larger, or ten thousand times, without rewriting it |
E11, E12 |
Experience |
From |
E5, E13, E14, E15 |
Epics#
Every epic lists its goal, who it serves, the user stories, a sketch of the API, what “done” means, the tasks, and its dependencies. Sizes: S = under a sprint, M = one sprint, L = two sprints, XL = three or more. API sketches are proposals, to be settled in the epic’s first design review.
E1 · Calibration (modelly.calibrate)#
Goal: fit scenario parameters to observed data and report how good the fit is, with uncertainty, in a way that is reproducible run for run.
Platforms: pure Python and numpy; parallel runs use the E3 executors on every OS.
Serves: every applied field; epidemiology, ecology and economics first.
User stories
As an epidemiologist, I give weekly case counts and a prior on the transmission rate, and get a posterior distribution and a fit plot.
As an ecologist, I fit to several patterns at once (abundance, spatial clustering, age structure): pattern-oriented modelling.
As an economist, I fit by matching moments of simulated and observed series.
As anyone, I stop a calibration halfway and resume it tomorrow with the same result.
API sketch
from modelly.calibrate import ABCSMC, Prior, Target, distance
calibration = ABCSMC(
model=Epidemic,
priors={"beta": Prior.uniform(0.05, 0.5), "gamma": Prior.log_normal(-2, 0.5)},
targets=[Target("infected", observed=cases, distance=distance.rmse)],
steps=120,
replicates=4,
seed=2027,
)
posterior = calibration.run(budget=20_000, executor="processes")
posterior.summary() # mean, median, HDI per parameter
posterior.plot() # marginals, pairs, fit against data
posterior.to_dataframe()
calibration.save("calib.ckpt") # resumable, uses checkpoints
Methods: ABC rejection; ABC-SMC with adaptive tolerances; simulated method of moments;
Bayesian optimisation through an optional extra (modelly-abm[calibrate]); distances (RMSE,
weighted, Wasserstein, custom); summary statistics from any DataCollector column.
Done when: ABC-SMC recovers known parameters of the bundled SIR and predator-prey models within the stated tolerance on three seeds; identical posterior for the same seed with the compiled core and the pure-Python twin; resumable from a checkpoint; docs page with a worked example; zoo model calibrated end to end.
Tasks
[ ] Design review: priors, targets, distances, posterior object
[ ] ABC rejection over
run_grid[ ] ABC-SMC with adaptive tolerance schedule and effective sample size
[ ] Simulated method of moments
[ ] Bayesian optimisation adapter (optional dependency)
[ ] Posterior: summaries, HDI, plots, export
[ ] Resume from checkpoint; parallel executors from E3
[ ] Recovery tests on synthetic data; determinism tests
[ ] Tutorial: calibrating an SIR model to case data
Size: L · Depends on: E3 (executors, results store), E4 (manifests)
E2 · Sensitivity analysis (modelly.sensitivity)#
Goal: tell the modeller which parameters drive which outputs, before and after calibration.
Platforms: pure Python and numpy; the emulator extra ships wheels for all three.
Serves: everyone with more than three parameters.
User stories
As a modeller with 12 parameters, I screen them cheaply and keep the 4 that matter.
As a reviewer, I see first-order and total Sobol indices with confidence intervals.
As a policy analyst, I see how an output’s uncertainty splits across inputs.
API sketch
from modelly.sensitivity import morris, sobol
screen = morris(
Epidemic, space=scenario_space, output="peak_infected", trajectories=50, seed=1
)
screen.ranking() # mu*, sigma per parameter
indices = sobol(
Epidemic, space=scenario_space, output="peak_infected", samples=4096, seed=1
)
indices.plot() # S1, ST with bootstrap intervals
Methods: one-at-a-time; Morris elementary effects; Sobol (Saltelli sampling, Jansen estimators); PAWN for skewed outputs; an optional Gaussian-process emulator so expensive models can be analysed from a few hundred runs.
Done when: Sobol indices match analytical values on the Ishigami function within the bootstrap interval; Morris ranking stable across seeds on the zoo models; plots and tables exportable; docs page and tutorial.
Tasks
[ ] Parameter spaces shared with E1 and E3 (
ScenarioSpace)[ ] OAT and Morris
[ ] Sobol with Saltelli sampling and bootstrap intervals
[ ] PAWN
[ ] Emulator (optional extra)
[ ] Verification on Ishigami and Sobol G functions
[ ] Tutorial: which epidemic parameters matter
Size: M · Depends on: E3
E3 · Experiments and sweeps (modelly.experiments)#
Goal: one object that describes a study (parameter space, design, replicates, outputs) and runs it anywhere: a laptop, a workstation, a cluster.
Platforms: processes, threads, Dask and Ray on all three; process workers start with spawn on Windows and macOS, so models and reporters must be importable (new lint rule); SLURM and PBS submit from any OS to a Linux cluster.
Serves: research software engineers, anyone with a sweep bigger than one evening.
User stories
As a researcher, I describe a Latin hypercube over five parameters, 20 replicates each, and run it overnight; if the machine reboots, the sweep resumes where it stopped.
As an RSE, I run the same experiment on SLURM with one line changed, and get the same numbers as on my laptop.
As a student, I query results with SQL without loading everything into memory.
API sketch
from modelly.experiments import Experiment, designs
experiment = Experiment(
model=Epidemic,
space=ScenarioSpace(
beta=(0.05, 0.5), gamma=(0.05, 0.3), vaccination=[0.0, 0.2, 0.4]
),
design=designs.latin_hypercube(200),
replicates=20,
steps=365,
outputs=["infected", "recovered"],
store="runs/epidemic.parquet",
seed=7,
)
experiment.run(executor="slurm", partition="short", max_parallel=500)
experiment.results().query("SELECT beta, avg(peak) FROM runs GROUP BY beta")
Designs: full factorial, Latin hypercube, Sobol and Halton sequences, custom lists.
Executors: processes (today’s run_grid), threads (free-threaded Python, E11), Dask, Ray,
SLURM job arrays, PBS. Store: Parquet files with a DuckDB view; one row per run and step.
Done when: the same experiment gives identical results on processes, threads and a local Dask cluster; a killed sweep resumes without repeating finished runs; SLURM executor tested on a container cluster in CI-local; docs page and RSE guide.
Tasks
[ ]
ScenarioSpaceand designs[ ] Results store (Parquet plus DuckDB), schema, append-only writes
[ ] Resume logic keyed by run seed
[ ] Executors: processes, threads, Dask, Ray, SLURM, PBS
[ ] Progress reporting and failure handling (retry, skip, record)
[ ] Guide: running Modelly on a cluster
Size: XL · Depends on: E11 (threads), E12 (Parquet)
E4 · Provenance and reproducible publication#
Goal: every number in a paper can be traced to code, data, seed and environment, and re-run by a reviewer with one command.
Platforms: manifests store POSIX-style relative paths; hashes are taken over bytes; a bundle made on one OS verifies on the other two (tested).
Serves: authors, reviewers, journals, replication studies.
User stories
As an author, every run writes a manifest I can cite.
As a reviewer, I run
modelly verifyon the authors’ bundle and see “outputs match”.As a lab, we archive a study on Zenodo with everything needed to rerun it.
As a modeller, I generate the skeleton of an ODD protocol description from my code.
API sketch
modelly run model.py:Epidemic --scenario scenario.toml --out runs/ # writes runs/manifest.json
modelly verify runs/manifest.json # reruns, compares outputs
modelly bundle runs/ --out epidemic-study.zip # code, lock file, seeds, outputs
modelly odd model.py:Epidemic > ODD.md # ODD protocol skeleton
Manifest contents: code hash (git commit and dirty diff hash), Modelly version and build (compiled or pure), Python version, platform, dependency lock, scenario, seeds, input data hashes, output hashes, wall and CPU time.
Done when: a bundle made on Linux verifies on macOS and Windows; modelly verify reports
the first differing output when one is changed; ODD skeleton covers the seven ODD elements;
guide for journals and reviewers.
Tasks
[ ] Manifest schema (versioned JSON) and writer
[ ]
modelly runcommand[ ]
modelly verifywith tolerance options for floating-point outputs[ ]
modelly bundle(zip with lock file, Zenodo metadata, CITATION.cff)[ ]
modelly oddgenerator from classes, scenario and schedule[ ] Guide: publishing a reproducible Modelly study
Size: M · Depends on: checkpoints (1.2), E12 (Parquet outputs)
E5 · Modelly Studio (modelly studio)#
Goal: one browser app to run a model, change parameters live, scrub through time, and see the profiler, the linter and the plots side by side.
Platforms: runs in the default browser on all three; tested in Chromium, Firefox, Safari and Edge; no OS-specific dependency.
Serves: everyone; the most visible feature of the release; teaching and demos.
User stories
As a modeller, I open
modelly studio model.py:Epidemic, move a slider and watch the curve change.As a teacher, I project Studio and scrub back to the step where the outbreak took off.
As a performance-minded user, I see which agent method takes the time while the model runs.
As a collaborator, I export the exact run I am looking at as a bundle (E4).
Views: space (grid, network, continuous, geographic), time series, histograms, agent table, parameter panel, timeline scrubber (E6), profiler panel, lint panel, run log.
Done when: every bundled example opens in Studio without code changes; scrubbing uses checkpoints and is instant on the examples; profiler and lint panels show live data; works offline; documented with screenshots.
Tasks
[x] Architecture decision: a Solara app reusing
modelly.viz(see the decision below)[ ] Layout and parameter panel generated from
Scenario[ ] Space views reusing the existing backends
[ ] Timeline scrubber on checkpoints
[ ] Profiler and lint panels
[ ] Export: figure, data, bundle
[ ] Docs and a short video
Size: XL · Depends on: E6, E14, profiler and lint (1.1, 1.2)
Decision: Studio is a Solara app, not a separate front end. It reuses the
portrayal styles, SpaceRenderer and the Matplotlib backend of modelly.viz instead of
rewriting them in JavaScript, needs no front-end build on any OS, and keeps the model in the
same Python process as the profiler, the linter and the recorder. The guide is
Modelly Studio.
E6 · Time travel and run diff#
Goal: debugging a stochastic model without rerunning from step zero.
Platforms: the on-disk checkpoint ring writes through os.replace, atomic on all three and safe from Windows sharing violations.
User stories
As a modeller, I rewind to step 312, change one parameter, and replay from there.
As a maintainer, I diff two runs and see the first step and the first agent where they diverge.
As a researcher, I branch a run into several futures from the same state.
API sketch
from modelly.timetravel import Recorder, diff
with Recorder(model, every=10) as rec: # checkpoints in memory or on disk
model.run_for(1000)
past = rec.at(312) # a model at step 312
past.scenario.vaccination = 0.3
past.run_for(100)
report = diff(run_a, run_b) # first divergence: step, agent, attribute
Done when: rewind and replay are bit-identical with straight runs; diff finds a seeded divergence on every example; memory use bounded by a configurable budget.
Tasks
[ ] In-memory and on-disk checkpoint ring with a memory budget
[ ] Replay with modified scenario
[ ] Branching runs
[ ] Run diff on collected data, then on agent state
[ ] Integration in Studio (E5)
Size: M · Depends on: checkpoints (1.2)
E7 · Language-model agents (modelly.llm)#
Goal: agents whose decisions come from a language model, with runs that stay reproducible and costs that stay under control.
Platforms: pure Python HTTP and a SQLite cache (standard library) on all three; local models through any HTTP endpoint.
Serves: computational social science, behavioural economics, anyone exploring generative agent models.
User stories
As a social scientist, I give each agent a persona and let it decide in natural language, then rerun the study and get the same decisions.
As a PI, I set a cost budget and the run stops before it exceeds it.
As a reviewer, the cached responses travel with the bundle (E4), so I can verify the study without calling any API.
Design
Provider-agnostic client with adapters for Anthropic, OpenAI-compatible endpoints and local models; no provider is required to install Modelly.
Deterministic response cache keyed on (model, prompt, parameters, agent seed); a cached run never calls the network; the cache is part of checkpoints and bundles.
Batching and concurrency limits; token and cost budgets per run; structured outputs validated against a schema.
Lint rule: an LLM call outside the
modelly.llmclient (unreproducible) is flagged.
Done when: a 200-agent opinion model runs, caches and replays offline bit-identically; budget stops a run cleanly; bundle verifies with no network; docs with an ethics and limitations section.
Tasks
[ ] Client interface and adapters
[ ] Cache store (SQLite) and cache keys
[ ] Budgets, batching, retries
[ ] Structured decisions with schema validation
[ ] Lint rule MDL012 (network call outside the client)
[ ] Example: opinion dynamics with persona agents
[ ] Docs: reproducibility, cost, ethics
Size: L · Depends on: E4
E8 · Geospatial space (modelly.space.geo)#
Goal: start a model from real maps, census tables and road networks.
Platforms: GeoPandas, Shapely, pyproj and rasterio wheels on all three; no system GDAL; data scripts are Python, not bash.
Serves: urban studies, transport, ecology, epidemiology, archaeology, energy.
Features: vector space backed by GeoPandas (agents as points, polygons as cells); raster
layers as property layers (optional rasterio); coordinate reference systems handled once,
at the space; neighbourhoods by distance, contiguity or network; road networks from
OpenStreetMap through an optional osmnx extra; export to GeoParquet and GeoJSON; Studio map
view (E5).
Done when: a commuting model runs on a real city’s road network; a land-use model runs on a raster; both appear in the zoo with data download scripts and licences documented.
Tasks
[ ]
GeoSpace(vector) andRasterLayer[ ] CRS handling and spatial indices
[ ] Road networks and routing
[ ] Census-style initialisation helpers (synthetic populations from marginals)
[ ] Examples, data scripts, docs
Size: L · Depends on: E12 (GeoParquet), E14 (map view)
E9 · Networks: dynamic and multilayer#
Goal: social and contact structures as they really are: changing over time and on several layers at once (household, school, work).
Platforms: pure Python, numpy and scipy.
Features: temporal networks (edges with lifetimes); multilayer networks with per-layer weights; import from NetworkX, igraph and edge-list CSV; network collectors (degree distribution, clustering, component sizes) as cheap reporters; contact matrices for epidemiology.
Done when: an age-structured epidemic runs on a three-layer contact network; collectors match NetworkX on test graphs; adjacency updates are O(changed edges).
Size: M · Depends on: CellIndex adjacency (1.0)
E10 · Model zoo (modelly.zoo)#
Goal: validated reference models from many fields, each one a starting point, a teaching tool and a regression test.
Platforms: pure models; data download scripts in Python with checksums, so they run on Windows without curl.
Models planned (each with a citation, a validation check and a tutorial):
Field |
Model |
Validated against |
|---|---|---|
Epidemiology |
Age-structured SEIR with contact matrices |
Analytical final-size relation, published curves |
Epidemiology |
Vector-borne transmission on a landscape |
Published reproduction numbers |
Ecology |
Individual-based population with energy budgets |
Equilibrium densities |
Ecology |
Forest succession on a raster |
Published gap dynamics |
Economics |
Continuous double auction (order book) |
Stylised facts: fat tails, volatility clustering |
Economics |
Boltzmann and Bouchaud-Mézard wealth |
Analytical distributions |
Sociology |
Deffuant and Hegselmann-Krause opinion dynamics |
Published cluster counts |
Sociology |
Schelling with real neighbourhoods |
Original segregation results |
Political science |
Axelrod culture dissemination |
Original results |
Transport |
Nagel-Schreckenberg traffic |
Fundamental diagram |
Energy |
Diffusion of innovation (Bass on a network) |
Bass curve |
Archaeology |
Artificial Anasazi (Long House Valley) |
Published population reconstruction |
Teaching |
Wolf-sheep, sugarscape, boids, game of life (existing) |
Existing tests |
Done when: each zoo model has a validation test in CI-local, a docs page, a Studio view and
a calibration or sensitivity example where it makes sense; modelly new --from-zoo <name>
copies one into a new project.
Size: XL (spread over all sprints) · Depends on: E1, E2, E8, E9
E11 · Performance and scale#
Goal: the same model at ten times the population on the same machine, with results identical to today’s for a given seed; and real parallelism where the model allows it.
Platforms: free-threaded wheels for all three; Windows ARM64 wheels; GPU through CUDA on Linux and Windows and JAX on Metal or CPU on macOS; PGO measured per compiler.
Work items
Free-threaded Python (3.14t, 3.15t): make
fastrng,fastloopandfastcellthread-safe; ship free-threaded wheels; run independent replicates on threads (E3).Compiled
AgentSet.selectandagg: the next hot paths after cell moves, with Python twins and side-by-side tests, followingfastloop.c.GPU arrays for column phases: an array-API backend (CuPy, or JAX) for
do_where,drawandCohortTablethinning, chosen by one setting; documented differences from numpy, if any.Memory: agent memory footprint (slots for framework attributes, compact registries); target: half the bytes per agent of 1.2 on the playbooks.
Profile-guided wheels: PGO trained on the playbooks, kept only if measured faster.
modelly bench: run the playbooks locally and append to a history file; regression report before every pre-release, without paid CI runners.Windows ARM64 wheels on a native ARM runner.
Done when: free-threaded wheels pass the full suite; compiled select and agg are faster
than their twins on the micro-benchmarks and never slower on the playbooks; modelly bench
history shows no playbook regression from 1.2.0.
Size: XL · Depends on: nothing; feeds E3
E12 · Data and interoperability#
Goal: Modelly data goes where researchers already work, and models come from where they already are.
Platforms: DuckDB and pyarrow wheels on all three; the NetLogo translator is pure Python.
Work items
Parquet and Arrow output from
DataCollector(default for experiments), DuckDB queries, xarray for gridded time series.NetLogo translator (
modelly migrate --from netlogo): a skeleton Modelly model from an.nlogofile for the common primitives, with a report of what needs a human.Mesa 3 adapter: run a Mesa 3 model’s agents inside Modelly for a comparison, in addition to the existing codemod.
R and Julia guides: reading Modelly outputs, calling Modelly from R through reticulate.
Done when: every example writes Parquet; the NetLogo translator handles the NetLogo models library’s wolf-sheep and fire models to a running skeleton; guides published.
Size: L · Depends on: nothing
E13 · Developer tools#
Goal: writing a correct, fast model is the easy path.
Platforms: LSP mode in VS Code, PyCharm and Neovim on all three; python -m modelly.dev gate replaces the bash-only gate for Windows contributors.
Work items
modelly new <name> --template grid|network|continuous|cohort|geo|llm: a seeded scenario, a determinism test, lint config, a Studio page, a README with the ODD skeleton.modelly lint --fixand--difffor rules with a safe fix (MDL001, MDL002, MDL003, MDL009).modelly lint --lsp: findings in VS Code, PyCharm and Neovim as you type; pre-commit hook.New lint rules for 1.3 features: unseeded sampler in an experiment, network call outside
modelly.llm, mutable default scenario fields, wall-clock in calibration targets.Jupyter magics
%modelly profile,%modelly lint; rich display of aModel.Errors that name the agent, the step and the user’s line, not framework internals.
Type checking: the public API passes a strict type checker;
py.typedkept accurate.
Done when: a new user goes from pip install to a linted, tested, profiled model in five
minutes, timed with three volunteers; every fix is idempotent and tested.
Size: L · Depends on: lint and profile (1.1, 1.2)
E14 · Visualisation and figures#
Goal: from exploring a run to a figure in a paper without leaving Modelly.
Platforms: animations through imageio-ffmpeg, which bundles ffmpeg for all three.
Work items: publication styles for Matplotlib (journal column widths, colour-blind-safe palettes); animation export (MP4, GIF) of any space view; map view for E8; network layouts for E9; posterior and sensitivity plots for E1 and E2; one portrayal API shared by every backend and Studio.
Done when: every zoo model has a figure script producing a print-ready PDF; animations render headless in CI-local.
Size: M · Depends on: E5 decision
E15 · Teaching, docs and community#
Goal: Modelly is the easiest ABM framework to learn, teach and contribute to.
Platforms: every tutorial shows bash and PowerShell where commands differ.
Work items
Domain tutorials: one per field in Who 1.3.0 is for.
A course pack: eight sessions, slides, exercises with solutions, built on the zoo.
A gallery of community models with a submission template.
Plugin system through entry points: third-party spaces, samplers, executors, collectors and Studio views discovered at import.
Contributor guide refresh, good-first-issue labels, community call notes.
Docs translated into French and Spanish for the getting-started path.
Done when: the course pack has been taught once (pilot); two third-party plugins exist; every tutorial runs in the docs build.
Size: L · Depends on: E10, E13
E16 · Quality and release engineering#
Goal: a bigger release with fewer surprises than the smaller ones.
Platforms: the determinism suite runs on all three in CI; the release script is Python, not bash.
Work items
Property-based tests (Hypothesis) for spaces, columns, cohorts and checkpoints.
Determinism suite: every example, compiled and pure-Python, saved and loaded, on three platforms, gives identical outputs.
Deprecation policy and warnings for anything 2.0 will change.
API stability review of every new public name before 1.3.0b1.
Pelloux audit at 0 warnings, coverage at or above 95%.
Release checklist automated as
scripts/release.sh(version, changelog, tag, docs, PyPI).
Size: M (continuous) · Depends on: everything
Priorities#
Priority |
Epics |
Rule |
|---|---|---|
Must (1.3.0 does not ship without them) |
E1 Calibration, E2 Sensitivity, E3 Experiments, E4 Provenance, E6 Time travel, E16 Quality |
The “Answers” and “Trust” pillars |
Should (planned, can slip one pre-release) |
E5 Studio (core views), E7 LLM agents, E8 Geospatial, E10 Zoo (at least 8 models), E11 (free-threaded, compiled select/agg), E13 (new, lint –fix) |
The headline experience and reach |
Could (in 1.3.0 if time allows, else 1.4) |
E9 multilayer networks, E11 GPU backend, E12 NetLogo translator, E13 LSP, E14 animations, E15 translations |
Valuable, not on the critical path |
Won’t (explicitly not in 1.3.0) |
Breaking API changes, a hosted cloud service, a GUI model builder |
2.0 or never |
How the epics depend on each other:
flowchart LR
CK["Checkpoints (1.2)"] --> E6[E6 Time travel]
CK --> E4[E4 Provenance]
E11[E11 Performance] --> E3[E3 Experiments]
E12[E12 Data and interop] --> E3
E3 --> E1[E1 Calibration]
E3 --> E2[E2 Sensitivity]
E4 --> E1
E4 --> E7[E7 LLM agents]
E6 --> E5[E5 Studio]
E14[E14 Visualisation] --> E5
E12 --> E8[E8 Geospatial]
E1 --> E10[E10 Model zoo]
E2 --> E10
E8 --> E10
E9[E9 Networks] --> E10
E10 --> E15[E15 Teaching]
E13[E13 Developer tools] --> E15
The critical path is E11 / E12 → E3 → E1 → E10: experiments first, because calibration, sensitivity and the zoo’s validation all run on them.
Sprint plan#
Eight sprints, in dependency order: a sprint is a unit of work, not a period of time, and the
next one starts as soon as the previous one’s exit criteria hold. Each sprint ends with
bash scripts/ci-local.sh green, the build matrix green on Windows, macOS and Linux, the docs
building with -W, modelly bench showing no playbook regression, and a review note appended
to this file.
S1 · Foundations#
Goal: the plumbing every later sprint stands on.
[ ] E12: Parquet and Arrow output from
DataCollector; DuckDB view[ ] E4: manifest schema and writer;
modelly run[ ] E3:
ScenarioSpaceand designs (factorial, Latin hypercube, Sobol sequence)[ ] E11: free-threaded audit of the three C modules; plan for locks or per-thread state
[ ] E16: Hypothesis tests for columns and cohorts; deprecation policy written
[ ] Platforms:
python -m modelly.dev gate; unspecified-encoding rule on;spawn-safe lint rule[ ] E1, E2, E5, E7: design reviews (one page each, decisions recorded here)
Exit: every example writes Parquet; modelly run writes a manifest; design reviews signed off.
S2 · Experiments#
Goal: a sweep that runs anywhere and survives a reboot.
[ ] E3: results store, resume logic, executors (processes, threads, Dask)
[ ] E11: thread-safe C modules; free-threaded wheels built locally
[ ] E4:
modelly verify[ ] E11: compiled
AgentSet.selectwith its twin[ ] E10: zoo scaffolding; SEIR with contact matrices; Nagel-Schreckenberg
Exit: the same experiment gives identical results on processes, threads and Dask; a killed
sweep resumes; verify detects a changed output.
S3 · Calibration → 1.3.0a1#
Goal: the first headline feature in users’ hands.
[ ] E1: ABC rejection, ABC-SMC, posterior object and plots
[ ] E3: Ray and SLURM executors
[ ] E11: compiled
agg;modelly benchhistory file[ ] E13:
modelly newwith grid and network templates[ ] E10: Bass diffusion; Deffuant and Hegselmann-Krause
[ ] Release 1.3.0a1 to PyPI (pre-release), docs at
/dev/
Exit: ABC-SMC recovers SIR parameters on three seeds; a1 installs with pip install --pre.
S4 · Sensitivity and time travel#
Goal: explain the model and debug it.
[ ] E2: OAT, Morris, Sobol with bootstrap; verified on Ishigami
[ ] E6: checkpoint ring, rewind, replay, run diff
[ ] E1: method of moments; resume from checkpoint
[ ] E13:
lint --fixfor MDL001 and MDL003
Exit: Sobol matches Ishigami analytical values; replay is bit-identical with a straight run.
S5 · Reach#
Goal: real data and new kinds of models.
[ ] E8:
GeoSpace,RasterLayer, CRS handling, road networks[ ] E9: temporal and multilayer networks; network collectors
[ ] E7: client interface, cache, budgets
[ ] E4:
modelly bundle,modelly odd[ ] E10: forest succession on a raster; order-book market
Exit: a commuting model runs on a real road network; a cached LLM run replays offline.
S6 · Studio and LLM agents → 1.3.0b1#
Goal: the experience pillar visible; feature complete.
[ ] E5: Studio layout, parameter panel, space views, timeline scrubber
[ ] E5: profiler and lint panels
[ ] E7: structured decisions, MDL012, persona opinion example
[ ] E14: publication styles; posterior and sensitivity plots in Studio
[ ] E16: API stability review of every new public name
[ ] Release 1.3.0b1 (feature complete; API frozen)
Exit: every example opens in Studio; the API review closed with no open items.
S7 · Zoo and polish → 1.3.0rc1#
Goal: breadth, docs, and the long tail.
[ ] E10: remaining zoo models (Anasazi, Axelrod, vector-borne, wealth models)
[ ] E15: domain tutorials; course pack draft; plugin entry points
[ ] E13: LSP mode and pre-commit hook (Could)
[ ] E14: animation export (Could)
[ ] E12: NetLogo translator for wolf-sheep and fire (Could)
[ ] Release 1.3.0rc1; call for testing to users and course pilots
Exit: no open blocker; every tutorial runs in the docs build.
S8 · Hardening → 1.3.0#
Goal: ship with confidence.
[ ] E16: determinism suite on Linux, macOS, Windows; compiled and pure; saved and loaded
[ ] Platforms: wheels for every row of the support matrix build and import-test
[ ] E16: Pelloux audit 0 warnings; coverage at or above 95%
[ ] Performance report:
modelly benchagainst 1.2.0, published indocs/performance.md[ ] Release notes, migration notes, blog post, CITATION.cff, Zenodo archive
[ ] Release 1.3.0; versioned docs
/1.3/becomes the root
Exit: all gates in Release plan and gates pass.
Release plan and gates#
Release |
Gate (all must hold) |
|---|---|
1.3.0a1 |
Experiments, provenance basics and ABC calibration work; ci-local green; installs with |
1.3.0b1 |
Every Must and Should epic feature complete; public API frozen after review; docs for every new module |
1.3.0rc1 |
No open blocker; every tutorial and zoo validation passes; wheels for every platform build and pass the import test |
1.3.0 |
Determinism suite green on three platforms; audit 0 warnings; coverage at or above 95%; changelog and migration notes complete |
Releases follow the existing path: a commit chore(release): X.Y.Z on main, a vX.Y.Z tag,
wheels and PyPI from the tag, versioned docs from the tag. Pre-releases are tagged
v1.3.0a1, v1.3.0b1, v1.3.0rc1. The docs workflow only matches final vX.Y.Z tags, so a
pre-release publishes no docs folder of its own: its docs are the /dev/ build of the same
commit, and the root stays on 1.2 until 1.3.0.
Definition of done#
A task is done when all of these hold. It is the Pelloux guidelines plus what research software needs.
[ ] Code follows
pelloux-guidelines.md: files at most 300 lines, function bodies at most 25 lines, no magic numbers, conventional commits, no attribution trailers[ ]
bash scripts/ci-local.shpasses;MODELLY_PURE_PYTHON=1suite passes[ ] Works on Windows, macOS and Linux: the test runs in the
buildmatrix on all three[ ] A determinism test: same seed, same result; compiled and pure give the same result
[ ] Checkpoint round trip: the feature’s state survives
saveandload[ ] Public names have docstrings with an example; the docs page exists and builds with
-W[ ]
modelly lintis clean on any new example[ ] Changelog entry under the right heading
[ ] Performance: no playbook slower than 1.2.0 in
modelly bench[ ] For a scientific method: verified against an analytical result or a published one, cited
Success metrics#
Metric |
1.2.0 |
1.3.0 target |
|---|---|---|
Research fields with a validated zoo model and tutorial |
0 dedicated (examples only) |
8 or more |
Calibration and sensitivity methods |
0 |
6 or more (ABC rejection, ABC-SMC, SMM, OAT, Morris, Sobol) |
Executors for sweeps |
1 (processes) |
6 (processes, threads, Dask, Ray, SLURM, PBS) |
Time from |
not measured |
under 5 minutes, timed |
Lint rules |
16 |
20 or more, 4 or more with |
Wheel platforms |
Linux x86-64 and ARM, macOS, Windows x86-64 |
plus Windows ARM64 and free-threaded |
Bytes per agent on the playbooks |
baseline to be measured in S1 |
half of the baseline |
Coverage |
94.9% |
at or above 95% |
Pelloux audit warnings |
0 |
0 |
Reproducibility |
bit-identical resume |
bit-identical across executors and platforms, verifiable bundles |
Platforms with compiled wheels |
7 (Linux x86-64 and ARM64, musllinux x86-64 and ARM64, macOS Intel and Apple silicon, Windows x86-64) |
8 plus free-threaded builds (adds Windows ARM64) |
Risks#
Risk |
Likelihood |
Impact |
Mitigation |
|---|---|---|---|
Studio grows without bound |
High |
High |
Core views in b1; profiler and lint panels may slip to 1.4; scope frozen at b1 |
Free-threaded C modules have races |
Medium |
High |
Per-thread generator state; ThreadSanitizer runs locally; fall back to processes if not ready |
Calibration numerics are subtly wrong |
Medium |
High |
Verify on synthetic data with known parameters; compare with an established ABC library on the same problems |
Floating-point results differ across executors or platforms |
Medium |
Medium |
Reproducibility defined per executor; |
LLM provider APIs change or cost too much |
Medium |
Medium |
Provider-agnostic client; offline cached replay; local models supported |
Geospatial dependencies are heavy to install |
High |
Medium |
Optional extra |
The zoo is too ambitious |
High |
Low |
At least 8 models for 1.3.0; the rest move to 1.3.x patch releases |
One maintainer, many epics |
High |
High |
Must list protected; parallel agent tracks per sprint as in 1.1 and 1.2; Could items dropped first |
CI costs |
Low |
Medium |
Public repo runners; release builds on tags only; |
Open questions#
[x] Studio: a Solara app (reuses today’s viz) or a separate web front end (more freedom, more work)? Decided: a Solara app (see E5).
[ ] Calibration: build ABC-SMC in house, or wrap an established library behind our API?
[ ] Results store: Parquet plus DuckDB only, or also a SQLite option for small studies?
[ ] LLM agents: which providers at launch, and is the cache per model or per project?
[ ] Geospatial: GeoPandas as an optional extra only, or part of
rec?[ ] Pre-release docs:
/dev/only, or a/1.3-pre/folder?[ ] Course pack: which university or lab pilots it first?
[ ] Translations: French and Spanish first, or the languages users ask for?
Compatibility and the road to 2.0#
1.3.0 is backward compatible with 1.2: every public name keeps working.
Checkpoints from 1.2 do not load in 1.3 (the format records the minor version). 1.3 adds a
modelly checkpoint upgradecommand for 1.2 files where the state maps cleanly.Anything 1.3 wants to change raises a
DeprecationWarningthat names the replacement and the version that removes it (2.0).2.0 is where removals happen, after at least one minor release of warnings.
Beyond 1.3.0#
What comes next is planned on the 1.4.0, 1.5.0 and 2.0.0 pages.
Sprint review log#
Each sprint appends a short review here: what shipped, what slipped and why, the modelly bench
summary, and decisions taken.
Sprint |
Shipped |
Slipped |
Notes |
|---|---|---|---|
S1 |
|||
S2 |
|||
S3 |
|||
S4 |
|||
S5 |
|||
S6 |
|||
S7 |
|||
S8 |