Discourse Simulator
LLM-augmented Agent-Based Social Simulation for modelling public discourse dynamics following a critical real-world event.
Agents observe live news, reason about it, post to a simulated social network, and update multidimensional beliefs through peer influence and news salience, all powered by a local Ollama LLM.
Installation
1. Install discourse-sim
pip install discourse-sim
2. Install Ollama
discourse_sim runs LLMs locally via Ollama. You need to install it separately.
macOS
brew install ollama
Or download the desktop app from ollama.com/download.
Linux
curl -fsSL https://ollama.com/install.sh | sh
Windows
Download and run the installer from ollama.com/download.
3. Start the Ollama server
ollama serve
On macOS with the desktop app, this starts automatically. On Linux/Windows you may need to run this in a separate terminal or set it up as a service.
4. Pull the default model
ollama pull mistral:7b-instruct-q4_0
This is the default model (~4 GB). You can use any other Ollama model by passing ollama_model="..." to DiscourseSimulation.
Lighter alternatives (if disk space is limited):
ollama pull mistral # ~4 GB - good balance
ollama pull llama3.2 # ~2 GB - faster, slightly less capable
ollama pull phi3 # ~2 GB - Microsoft, very efficient
5. Verify everything works
ollama run mistral:7b-instruct-q4_0 "Hello"
You should see a response. If you do, you are ready to run simulations.
All dependencies: (make sure if everything is installed correctly)
pip install langchain-core langchain-community langchain-ollama \
duckduckgo-search networkx numpy tqdm pandas openpyxl
Quick Start
from discourse_sim import DiscourseSimulation
sim = DiscourseSimulation(
critical_event=(
"In late April 2025, Dublin saw a large anti-immigration march "
"from the Garden of Remembrance to the Custom House, with thousands "
"protesting immigration levels and housing pressure..."
),
event_date="2025-04-26",
topic="immigration in Ireland",
n_agents=100,
n_days=15,
)
sim.run()
df = sim.to_dataframe() # one row per agent per day
df.to_excel("output.xlsx", index=False)
All Parameters
Required
| Parameter | Type | Description |
|---|---|---|
critical_event |
str |
Plain-text description of the event injected into every agent prompt on every day |
event_date |
str |
ISO date "YYYY-MM-DD" - this becomes Day 0 |
topic |
str |
Subject domain, e.g. "immigration in Ireland". Anchors search queries and scoring prompts |
Agent Population
| Parameter | Type | Default | Description |
|---|---|---|---|
n_agents |
int |
100 |
Number of synthetic agents |
n_days |
int |
15 |
Simulation duration in days |
agent_distribution |
dict |
Ireland 2025 empirical split | Proportions of each agent kind - must sum to 1.0 |
kind_priors |
dict |
DEFAULT_KIND_PRIORS |
Prior distributions for belief/attitude initialisation per kind |
Default distribution (Ireland 2025):
{"centrist": 0.45, "far_right": 0.20, "pro_imm": 0.25, "media": 0.10}
Timeline
| Parameter | Type | Default | Description |
|---|---|---|---|
timeline |
dict[int, str] |
None |
Optional explicit daily news entries keyed by 0-based day index. Missing days auto-generated. If None, all days use live search. |
Example:
timeline={
0: "[VERIFIED] The march took place at the Garden of Remembrance...",
4: "[VERIFIED] Government announced a deportation flight...",
}
Any day not in the dict falls back to an auto-generated entry. Agents search live news on every day regardless of timeline.
LLM & Network
| Parameter | Type | Default | Description |
|---|---|---|---|
ollama_model |
str |
"mistral:7b-instruct-q4_0" |
Any locally pulled Ollama model |
temperature |
float |
0.75 |
Post generation temperature |
use_llm_scoring |
bool |
True |
If False, uses keyword heuristic for attitude scoring |
network_k |
int |
6 |
Watts-Strogatz nearest-neighbour count |
network_p |
float |
0.3 |
Watts-Strogatz rewiring probability |
network_seed |
int |
42 |
Reproducibility seed for network and agents |
Belief Update Keywords
| Parameter | Type | Default | Description |
|---|---|---|---|
threat_keywords |
list[str] |
See config.py | Words in daily news that trigger security_threat_belief increase and exposure accumulation |
humanitarian_keywords |
list[str] |
See config.py | Words that trigger humanitarian_belief increase |
Override these when simulating non-immigration topics:
threat_keywords=["eviction", "homeless", "unaffordable", "crisis"],
humanitarian_keywords=["social housing", "rights", "family", "support"],
Outputs
sim.run()
# Single DataFrame - one row per agent per day (recommended for analysis)
df = sim.to_dataframe()
# All three DataFrames
dfs = sim.to_dataframes()
dfs["history"] # one row per timestep - aggregate metrics
dfs["agents"] # one row per agent per timestep - full belief state
dfs["messages"] # one row per agent per timestep - posts + beliefs merged
Column reference - df_messages
| Column | Description |
|---|---|
t / date |
Day index and calendar date |
agent_id / kind / quirk |
Agent identity |
message |
The generated social media post |
interpreted_score |
LLM-scored stance [-1, +1] |
attitude |
Agent attitude at end of day t |
mood |
Agent mood at end of day t |
exposure |
Cumulative threat narrative exposure |
economic_threat_belief |
Economic threat sub-belief |
cultural_threat_belief |
Cultural threat sub-belief |
security_threat_belief |
Security threat sub-belief |
humanitarian_belief |
Humanitarian weight sub-belief |
avg_attitude |
Population mean attitude that day |
polarization |
Mean edge-level attitude divergence |
news_summary |
First 120 chars of daily news entry |
Custom Agent Kinds
Add any agent kind by extending DEFAULT_KIND_PRIORS:
from discourse_sim import DiscourseSimulation
from discourse_sim.config import DEFAULT_KIND_PRIORS
custom_priors = {
**DEFAULT_KIND_PRIORS,
"nationalist": {
"attitude": (0.55, 0.95),
"economic_threat": (0.2, 0.6),
"cultural_threat": (0.7, 1.0),
"humanitarian": (-0.6, 0.0),
"openness": (0.1, 0.35),
"emotional_react": (0.5, 0.85),
},
}
sim = DiscourseSimulation(
...,
agent_distribution={"centrist": 0.35, "nationalist": 0.30, "pro_imm": 0.35},
kind_priors=custom_priors,
)
Architecture
discourse_sim/
├── __init__.py # Public API: DiscourseSimulation
├── core.py # DiscourseSimulation class - orchestrator
├── config.py # SimConfig dataclass - all parameters + validation
├── agents/
│ └── agent.py # Agent dataclass + make_agents() factory
├── tools/
│ └── search_tools.py # make_tools() - search, memory, sentiment
├── timeline/
│ └── timeline.py # Timeline class - user-supplied + auto-generated
├── simulation/
│ └── engine.py # generate_message(), score_message(), update_beliefs(), run_loop()
└── utils/
└── dataframes.py # build_dataframes() - history/agents/messages
Per-day flow for each agent:
OBSERVE → search_event_news(query) # live DuckDuckGo
→ recall_agent_memory(posts_json) # last 5 posts
THINK → build prompt: event + profile + memory + search result + today's news
ACT → LLM call → post text (max 40 words)
SCORE → LLM call (temp=0.0) → float in [-1, +1]
UPDATE → news salience → sub-beliefs
→ peer pull → attitude pressure
→ mood shock → affective state
→ inertia → resistance to change
→ composite → attitude(t)
Examples
| File | Description |
|---|---|
examples/dublin_march.py |
Full reproduction of the original Dublin April 2025 experiment |
examples/minimal_usage.py |
Minimal usage - no timeline, live search only |
examples/custom_agent_kinds.py |
Adding a custom nationalist kind with its own priors |
Citation
If you use this package in research, please cite:
@software{discourse_sim,
title = {discourse\_sim: LLM-Augmented Agent-Based Social Simulation of Discourse Dynamics},
author = {dreji18},
year = {2026},
note = {Python package}
}
Metadata
Release files for discourse-sim 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| discourse_sim-0.1.1.tar.gz | 26.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| discourse_sim-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.7 kB
Release files / discourse_sim-0.1.1.tar.gz
| Download URL | discourse_sim-0.1.1.tar.gz |
|---|---|
| Size | 26.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
da51c607483d0178e263400f9fdebcca10cce2f286d303f6d42e0b646f59c74b
|
|
BLAKE2b-256 checksum How to use checksums |
2511a237d15e44503878c941f7b2bf15cdc34633d2acc5f549a54dc51e25449d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|
Release files / discourse_sim-0.1.1-py3-none-any.whl
| Download URL | discourse_sim-0.1.1-py3-none-any.whl |
|---|---|
| Size | 24.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88bb66bd267a530881e9c335c3aadaf4fdaa7b5d1e2526c13e7a3b2a68cd1af1
|
|
BLAKE2b-256 checksum How to use checksums |
d05d05975c9eab98e15e966c86f88d7272c83e201c7c3bc32dc56f5b675ad335
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|
Security for discourse-sim 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.