SIMULATED. Everything on this page concerns a simulation of deliberation by AI agents. No real residents were surveyed, and nothing here measures actual public opinion. No results from the system’s one full run are published anywhere on this site — our own review found the prompts colored the deliberations, and results publish only after a redesigned re-run clears our validity bar.

Methods page preface — /assembly/methods.html opening section

Committed page source (old-estate fires dispatch Leg 1, 2026-08-12): the opening section of the methods/limits page, rendered by tools/assembly_site_gen.py ABOVE the imported METHODS and VALIDITY memos (archived-imports/phase0-pilot/). Exists as a file so page substance lives in markdown sources, never inside a generator (five-verbs law). The H1–H3 statements below are condensed from the engine's own research/assembly/phase0_pilot_import/CODE_REVIEW.md — update them only against that source. Everything above the first --- is stripped at render.


How the simulation works — and where it honestly falls short

This page publishes the engine's own methods documentation and its threats-to-validity memo, unedited, exactly as the project's internal review wrote them. Read the limits before the methods: no number this system produces is a measurement of what real Torontonians think.

The three open validity issues (H1–H3)

Our own code review found three high-severity issues that remain open. They are the core of why run-1 results are unpublished, and any future results will state how each was addressed:

What follows

The two documents below are published verbatim from the project archive: Methods (what the system does, precisely enough to reproduce) and Threats to validity (what its outputs can and cannot support, with the published failure modes of simulating people with language models).


Methods

System: a simulated deliberative mini-public ("Citizens' Assembly") in which a statistically representative population of LLM-backed agents deliberates a policy question, votes on statements, authors proposals, and (optionally) requests expert briefings. Outputs are an opinion map, ranked cross-group-agreed proposals, and a full transparency log.

Status / claim. This is a rehearsal and hypothesis-generation instrument, not a measurement of public opinion. It is designed to (a) pressure-test the design of a real assembly (agenda, framing, materials, process) cheaply and repeatably, and (b) generate testable hypotheses about where a population might find common ground. Every output is labelled SIMULATED. Threats to validity are documented separately in VALIDITY.md; this file documents what the system does, precisely enough to reproduce.

This document describes the Phase-0 reference implementation (single machine, ≤ a few hundred agents). Section 12 notes what changes at population scale.


1. Pipeline overview

A run is a deterministic function of (seed, config, agenda, model) over seven stages:

  1. Sampling — draw a roster of N agent profiles matched to target population marginals, with realistic joint structure (§3–4).
  2. Agent construction — turn each profile into a persona with values, per-issue leanings, psychographic traits, a latent opinion vector, and a one-page narrative (§5).
  3. Seeding — load the agenda: a question + balanced two-sided seed statements (§6).
  4. DeliberationR rounds of routed voting, proposal authoring, and (optional) expert-briefing requests, with inter-round memory and reflection (§7).
  5. Opinion analysis — Pol.is-style clustering and group-informed consensus over the accumulated vote matrix; semantic de-duplication of proposals (§8).
  6. Expert panel / knowledge base — balanced briefings, cached and reusable (§9).
  7. Reporting — report, opinion map, dashboard, and machine-readable artifacts (§10).

2. Precedents (what each component reproduces)

Component Precedent Borrowed mechanism
Opinion clustering & consensus Pol.is / Computational Democracy Project vote-matrix → PCA → k-means → group-informed consensus
AI-mediated consensus framing DeepMind Habermas Machine (Tessler et al., Science 2024) generate→predict-endorsement→aggregate→critique loop (adapted)
Agents grounded in individuals Park et al., Generative Agent Simulations of 1,000 People (2024); Generative Agents (UIST 2023) narrative-grounded personas; memory/reflection
Conditioning models on demographics Argyle et al., Out of One, Many (2023) "silicon sampling"
Representative selection OECD deliberative-wave; sortition / civic lottery stratification on demographic + attitudinal strata

Full citations in VALIDITY.md. The implementation is original; these are the conceptual sources.


3. Population data and provenance

Population data lives in swappable region datasheets (regions/<id>.json; default toronto.json). The reference population is the City of Toronto (CSD 3520005, 2021 Census). Each datasheet declares one-way marginals, pairwise affinities, and supplementary populations (below). Full figures, sources, and census-vs-illustrative flags are in DATA_PROVENANCE.md (not separately published on this site). region_tool.py scaffolds/validates new places; --region <id> selects one. Ontario and Canada datasheets ship as scaling examples.

Provenance discipline. Each marginal is a named distribution in one file; changing the modelled population means changing only MARGINALS (+ AFFINITIES, §4). No figure is hard-coded elsewhere. Illustrative vs census-grounded status is documented in census.py and must be re-stated in any published result.


4. Sampling: marginal-exact quota + targeted-swap raking

Goal. Produce N profiles that (i) match every target marginal exactly (up to integer rounding, eliminating small-sample drift) and (ii) exhibit realistic joint structure (e.g., seniors tend to be retired and owners), all without a full joint cross-tabulation (which Statistics Canada does not publish freely).

Step 1 — quota allocation (exact marginals). For each variable with target distribution p, integer category counts are assigned by the largest-remainder method: base_k = floor(p_k · N), then the N − Σ base_k residual seats go to the categories with the largest fractional remainders. Small mandatory cells (Indigenous, non-binary/trans) are floor-guaranteed ≥ 1 for N ≳ 50. Each variable's category list is shuffled and assigned to agents independently, so every one-way marginal is hit to the nearest integer (verified: tests.py §2, all N).

Step 2 — correlation pass (joint structure). Target conditional rates P(B = b | A = a) are declared in AFFINITIES (e.g., P(retired | 65+) = 0.70). A targeted swap exchanges the B-value between one agent with A=a, B≠b and one with A≠a, B=b: this raises the joint count of (a,b) by one while leaving the B-marginal unchanged (it is a permutation of B-values). Swaps are applied per affinity toward its target count round(target · |A=a|); several passes resolve conflicts among affinities that share a B-dimension. The procedure is deterministic given the seed and converges to within ≤ 0.03 of every target across N ∈ {50,100,400} and 5 seeds, with marginals still exact (verified: tests.py §2; cross-seed sweep in VALIDATION.md).

Modelled affinities (illustrative, editable): age↔employment (retired/student/FT), education↔income, income↔tenure, age↔tenure, household↔income, politics↔tenure, area↔commute, immigrant↔years-in-city, age↔disability.

Honest scope. This is a constrained maximum-plausibility construction, not inference from a real joint distribution. It is faithful to the marginals and to the declared pairwise targets; higher-order interactions are not modelled. The production endpoint remains true iterative proportional fitting (IPF) against a commissioned StatCan custom cross-tabulation. method="independent" reproduces naïve per-variable sampling for ablation.


5. Agent construction

For each profile, agents.py::build_agents computes, in order:

  1. Values — three ranked priorities sampled (without replacement) from 12 value tokens with profile-conditioned weights (e.g., low income ⇒ +affordability, +fairness).
  2. Per-issue leaningsissue_bias[theme] ∈ ℝ, a signed scalar per deliberation theme derived from profile (e.g., car commuter = −0.5 on mobility; renter = +0.3 on housing). Positive = leans toward the change/regenerative pole of that theme.
  3. Latent opinion vector vec = (x, y): - x (incremental/centralized ↔ bold/decentralized) = OPEN[openness] + {left:+.15, centre:0, right:−.15} + youth/low-trust adjustments + 𝒩(0, .30); - y (market ↔ collective) = LEAN_Y[politics] + 0.4·TRUST[trust] + renter/low-income/ racialized/high-income adjustments + 𝒩(0, .30); both clamped to [−1.3, 1.3].
  4. Traits ∈ [0,1]: engagement, numeracy, persuadability, risk-tolerance, optimism, verbosity — profile-conditioned with bounded noise.
  5. Narrative — a one-page persona that renders all of the above in plain language, including an explicit "at a glance" footer of trait levels. This is the system prompt in LLM mode, so behaviour is conditioned on the full, legible profile.

Fidelity note. Narratives are synthesized from demographics, not real interviews — the largest single fidelity gap (see VALIDITY.md). The architecture deliberately matches Park et al.'s narrative-grounding so that real interview transcripts can be substituted in Phase 2 with no interface change.


6. Agenda and seed statements

The agenda is a question plus balanced, two-sided seed statements grouped by theme (agenda.py). The default is a regenerative-Toronto agenda (22 statements / 11 themes); arbitrary agendas load from a human-readable Markdown file (--agenda, agenda_tool.py), with check warning on any one-sided theme. Two-sidedness is a deliberate guard against a leading instrument. In mock mode, statements missing latent coordinates receive a keyword-estimated position (estimate_xy); in LLM mode only the text is used.


7. Deliberation protocol

Each of R rounds (deliberation.py::run_round):

  1. Routing. Each agent is shown a subset of statements (default 12), preferring unseen ones — a simplified analogue of Pol.is comment routing (information-gain / consensus-potential weighting is not yet implemented; see VALIDITY.md).
  2. Decision (concurrent, bounded). Each agent returns votes (agree=+1 / disagree=−1 / pass=0) on the shown subset, optionally one authored proposal, optionally one evidence question. Concurrency is semaphore-bounded; per-agent failures are isolated (one malformed reply cannot abort the run).
  3. Proposal integration with de-duplication. Authored proposals are normalized; an identical-text proposal already on the table records the author's support rather than creating a duplicate (popularity accrues to one statement). New unique proposals are capped per round.
  4. Expert briefings. De-duplicated evidence questions are answered by the panel (§9); each agent's bounded memory receives the briefing digests, which are injected into subsequent prompts so evidence can influence later votes and proposals.
  5. Opinion analysis (§8) is computed each round; reflection (mock backend only) nudges agents toward their group centroid by their persuadability, modelling opinion movement over rounds.

8. Opinion analysis (Pol.is math)

Let M be the agents × statements vote matrix (entries in {+1,−1,0}, missing = NaN).


9. Expert panel and knowledge base

Evidence requests are cleaned (junk/bare-id removal, near-duplicate collapse, cap) and served knowledge-base-first:

The panel design mirrors a real assembly's balanced learning phase (informants vs advocates) and explicitly fixes the Habermas Machine's acknowledged lack of a fact-finding phase.


10. Backends, determinism, transparency


11. Software environment

Python 3.10+. Core: numpy, scikit-learn (PCA, k-means, silhouette, TF-IDF). Optional: openai (LLM backends), anthropic (Opus briefings), matplotlib (static map). Exact versions for any run are captured in that run's metadata.json.environment.packages. The verification suite is tests.py (python tests.py, exit 0 ⇔ all pass).


12. What changes at population scale (forward note)

Scaling toward millions of agents across languages/regions changes engineering, not the method: (i) sampling generalizes to any region given its marginals + affinities (or a real joint table + IPF); (ii) personas and prompts are language-agnostic given a localized template and a multilingual model; (iii) clustering must move from in-memory k-means to streaming/approximate methods, and routing to the real information-gain weighting; (iv) the KB becomes a shared multilingual evidence base with embedding-based lookup. The validity threats in VALIDITY.md do not shrink with scale — several (caricature of under-represented groups, language/cultural skew) grow — and must gate any scale-up. Scope for scaling is deferred until the present system is independently reviewed.

Neighbourhood-conditioned sampling

When a region datasheet declares a neighbourhoods array (each with a population and its own marginals), sampling is two-stage:

  1. Allocation. The N agents are distributed across neighbourhoods in proportion to their population by the same largest-remainder quota used for marginals, so the per-neighbourhood head-counts are exact and deterministic.
  2. Conditioned draw. Each neighbourhood's agents are drawn from its own marginals, overlaid on the citywide marginals (any variable a neighbourhood omits falls back to city). A neighbourhood's district fixes Profile.area. Blocks of ≥12 agents additionally get the targeted-swap correlation pass; smaller blocks keep exact marginals without it (too few people for a within-neighbourhood joint structure to be meaningful).

Each agent records its neighbourhood, which flows into the persona narrative, agents.json, and the dashboard. The citywide (single-stratum) path is unchanged when no neighbourhoods are declared. Construction and validity caveats: DATA_PROVENANCE.md (neighbourhood section).

Persona localization & standard geography codes

Place-coded persona prose (the place name, dwelling phrasings, commute framings, the "new here / watched it change" lines, and the Indigenous-relationship line) lives in a per-region locale block. The built-in default reproduces the Toronto wording exactly, so Toronto output is unchanged; a datasheet (e.g. ontario.json) overrides it with region-appropriate framings (rural/car-dependent, the North, smaller towns, Francophone Ontario), so a rural Ontario agent no longer references the TTC. Occupation lists are not yet locale-aware (a planned refinement).

Sub-areas follow standard nested geography. Each sub-area entry carries a level and an official code, and the geography block names the code_system — Statistics Canada's Standard Geographical Classification (Canada → province → census division → census subdivision → census tract → dissemination area), with City-administrative units (Toronto's 158 neighbourhoods) and electoral districts as parallel administrative options. Each agent records geo_level and geo_code, which is what lets regional maps nest/federate (DA→CT→CSD→CD→PR) and translate internationally (GADM admin 0–3 / OECD TL2–TL3).


Threats to validity and honest limitations

This document states, as plainly as possible, what the simulated assembly can and cannot support, and the threats that any reviewer should weigh. It is deliberately conservative. Read it before treating any number from this system as a finding.

0. What is and is not claimed

Claimed. The system is a rehearsal and hypothesis-generation instrument: it lets us (a) design and stress-test the agenda, framing, evidence, and process of a real deliberation cheaply and repeatably, and (b) produce testable hypotheses about where a population might find cross-group common ground. It is good at central tendencies and at surfacing bridging vs dividing framings.

Not claimed. It is not a measurement of public opinion; not a substitute for a real, human deliberation; and not evidence that any specific number (e.g., "80% support") would hold among real people. Simulated participants are not participants. Every artifact is labelled SIMULATED.

The deepest objection — that accurate simulation still does not deliver the representation real human-subjects deliberation exists to provide — we accept as correct (Agnew et al. 2024). The simulation informs and de-risks the real thing; it does not replace it.


1. Construct validity — do the agents represent humans?

1.1 Synthetic personas, not real interviews. Park et al. (2024) achieved high fidelity by grounding agents in two-hour interviews of the real individuals they simulate. Our personas are synthesized from demographics. This is the single largest fidelity gap. We match their architecture (narrative grounding + memory) so real qualitative material can be substituted with no interface change, but until then the agents are demographic archetypes, not people. (Roadmap §5.)

1.1b Census-missed populations are the least reliable of all. We deliberately include groups the census under-counts (homelessness, non-status migrants, precariously housed, precarious-status residents) so the simulated room is not limited to the well-counted — an inclusion improvement over census-only sampling. But their sizes are estimates with wide uncertainty, and the literature is clearest that LLMs misportray and flatten exactly such marginalized groups. These personas are crude, humble approximations that make the voices present; they are emphatically not a substitute for engaging real people from these communities ("nothing about us without us"). Provenance and ethics: DATA_PROVENANCE.md §2,5.

1.2 Algorithmic fidelity is unestablished and must be tested per model/task. "Silicon sampling" can reproduce some demographically-correlated response patterns (Argyle et al. 2023), but fidelity is not transferable across models, tasks, or time and must be demonstrated case-by-case. We have not validated fidelity against any real Toronto survey or assembly. Until we do, agreement levels are internal to the simulation.


2. Documented failure modes of simulating people with LLMs

These are established in the literature and apply directly:


3. Internal validity (run-to-run, design artifacts)


4. External validity (generalization)


5. Statistical-conclusion validity


6. Ethics and honesty safeguards


7. What would raise validity (mitigation roadmap, in priority order)

  1. Calibration against ground truth — compare simulated agreement to a real Toronto survey and/or a real mini-public on the same items; report the gap. (Highest value.)
  2. Interview-grounded personas — replace synthetic narratives with real consultation/oral-history transcripts (Park-style), closing §1.1.
  3. Multi-model ensembles — run ≥2 base models; report only robust findings (compare_runs.py already diffs runs).
  4. Within-group diversity repair — explicitly diversify personas and measure synthetic vs real variance to counter §2 variance collapse.
  5. True IPF sampling against a commissioned StatCan joint cross-tabulation.
  6. Real comment routing (information-gain) and a learned reflection model.
  7. Pre-registration of agenda, config, and analysis before any externally-reported run.

8. Claims ledger

Claim Supported? Evidence Caveat
Core math reproduces Pol.is group-informed consensus Yes tests.py §3 (equals independent recomputation)
Roster matches population marginals exactly Yes tests.py §2 (all N) marginals, not joint; some illustrative
Joint structure is realistic Partly correlations ≤0.03 of target, tests.py §2 declared pairwise only; not real IPF
Pipeline is deterministic given seeds (non-LLM) Yes tests.py §1 LLM backends stochastic
Distinct ideas are de-duplicated Yes (heuristic) tests.py §4 lexical TF-IDF, threshold-dependent
Agreement levels reflect real public opinion No not validated against ground truth
Agents represent real individuals No synthetic personas, not interviews
Within-group diversity is realistic No literature §2 variance collapse / flattening
Results generalize across model/city/topic Unknown single-model, single-city tested

References

Augment, do not replace (Part D framing)

The Pol.is team's own analysis of LLMs for deliberation (Small et al., Opportunities and Risks of LLMs for Scalable Deliberation with Polis, 2023) warns that using LLMs to replace participants "could be catastrophic for deliberation at scale." Accordingly this system is positioned as an augmentation instrument: it designs and de-risks a real Pol.is/assembly, stress-tests framings, and generates testable hypotheses about where bridges lie - and it can include census-undercounted voices the self-selected real tool misses - but it is not a substitute for a real deliberation with real people, and every artifact is labelled SIMULATED.

Part of the Toronto Assembly project · updated 2026-08-17 · corrections welcome — tell us where we’re wrong.