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

Scenario, seeded generators, run_batch / run_grid with one SeedSequence seed per run

Experiments, calibration and sensitivity analysis that are reproducible by construction

Checkpoints (model.save, Model.load, checkpoint_every), bit-identical resume

Time travel, resumable sweeps, run bundles for publication

Columns, CohortTable, CellIndex, three compiled modules (fastrng, fastloop, fastcell)

Free-threaded parallel phases, compiled select / agg, GPU arrays

modelly lint (16 rules), modelly profile (with --sizes), modelly migrate

lint --fix, editor integration, profile panels in Studio, modelly new

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 (/dev/, /1.0/, /1.1/, /1.2/)

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?

modelly.llm with cached, reproducible responses and cost budgets

Teaching

How do I teach ABM in a course?

Course pack, modelly new templates, Studio for live demos

Research software engineers

How do I run this on our cluster and keep it reproducible?

Executors for SLURM, Dask and Ray, run manifests, modelly verify, run bundles


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 pip install to a linted, tested, profiled, visualised model in five minutes

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

  • [ ] ScenarioSpace and 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 verify on 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 run command

  • [ ] modelly verify with tolerance options for floating-point outputs

  • [ ] modelly bundle (zip with lock file, Zenodo metadata, CITATION.cff)

  • [ ] modelly odd generator 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.llm client (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) and RasterLayer

  • [ ] 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, fastloop and fastcell thread-safe; ship free-threaded wheels; run independent replicates on threads (E3).

  • Compiled AgentSet.select and agg: the next hot paths after cell moves, with Python twins and side-by-side tests, following fastloop.c.

  • GPU arrays for column phases: an array-API backend (CuPy, or JAX) for do_where, draw and CohortTable thinning, 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 .nlogo file 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 --fix and --diff for 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 a Model.

  • 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.typed kept 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: ScenarioSpace and 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.select with 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 bench history file

  • [ ] E13: modelly new with 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 --fix for 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 bench against 1.2.0, published in docs/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 --pre; no playbook regression

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.sh passes; MODELLY_PURE_PYTHON=1 suite passes

  • [ ] Works on Windows, macOS and Linux: the test runs in the build matrix 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 save and load

  • [ ] Public names have docstrings with an example; the docs page exists and builds with -W

  • [ ] modelly lint is 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 pip install to a linted, tested model

not measured

under 5 minutes, timed

Lint rules

16

20 or more, 4 or more with --fix

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; verify has explicit tolerances; documented

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 modelly-abm[geo]; the core never imports them

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; modelly bench local


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 upgrade command for 1.2 files where the state maps cleanly.

  • Anything 1.3 wants to change raises a DeprecationWarning that 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