You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
mirosociety/docs/plans/2026-03-17-mirosociety-desi...

17 KiB

MiroSociety — Design Document

Define the rules of a society. Watch what emerges.

Overview

MiroSociety is a multi-agent simulation engine where users define social rules in natural language, and the system generates a small town of AI agents who live under those rules. The simulation runs day by day, and emergent behaviors — institutions, conflicts, workarounds, cultural norms — arise organically from agent interactions.

Core Decisions

  • Simulation model: Town Square — agents interact through speech, trade, voting, forming groups, protesting, complying, defecting. Not social media.
  • Scale: Configurable, default 20-30 agents. Self-hosted can scale to 200+.
  • Agent reasoning: Round-robin with memory. Each round, a context-dependent subset of agents (5-10) get LLM calls. Others update passively. Crisis = more active agents. Quiet day = fewer.
  • No OASIS dependency: Custom lightweight simulation engine. OASIS is designed for social media platforms — we need richer action types.
  • No Zep dependency: Users define worlds from scratch via natural language. No document extraction needed.

Architecture

Frontend (Vue 3 + Vite + D3.js)
    ↕ REST + SSE (Server-Sent Events for live streaming)
Backend (Python + FastAPI)
    ├── World Generator (rules → blueprint)
    ├── Citizen Generator (blueprint → agent personas)
    ├── Simulation Engine (round-by-round loop)
    │   ├── Tension Engine (prevents equilibrium stagnation)
    │   └── Reactive Micro-Rounds (agent conversations within rounds)
    ├── Narrator (actions → readable prose)
    └── Report Generator (multi-stage pipeline → shareable summary)
State Layer (SQLite + JSON files)
    ├── World state (locations, resources, institutions)
    ├── Agent state (beliefs, relationships, tiered memory)
    └── Action log (JSONL)

Data Models

World Blueprint (generated from user rules)

{
  "name": "Truthtown",
  "description": "A coastal town where lying is physically impossible",
  "rules": ["No one can speak an untruth", "Emotions are visible to all"],
  "locations": [
    {"id": "town_square", "name": "Town Square", "type": "public", "description": "..."},
    {"id": "market", "name": "Market", "type": "commerce", "description": "..."},
    {"id": "council_hall", "name": "Council Hall", "type": "governance", "description": "..."}
  ],
  "resources": ["food", "goods", "influence", "knowledge"],
  "initial_tensions": ["Privacy vs transparency", "Commerce vs fairness"],
  "time_config": {
    "total_days": 365,
    "rounds_per_day": 3,
    "active_agents_per_round_min": 3,
    "active_agents_per_round_max": 10
  }
}

Agent Persona

{
  "id": 0,
  "name": "Elena Vasquez",
  "role": "Baker",
  "age": 34,
  "personality": {
    "honesty": 0.9,
    "ambition": 0.4,
    "empathy": 0.8,
    "confrontational": 0.2,
    "conformity": 0.5
  },
  "background": "Third-generation baker who values routine and community...",
  "goals": ["Protect her livelihood", "Maintain friendships", "Stay out of politics"],
  "core_memory": ["I co-founded the Privacy Council", "Tomás was exiled on day 89"],
  "working_memory": ["Marcus called an emergency meeting this morning"],
  "beliefs": ["Privacy should be protected", "The council has too much power"],
  "relationships": {"3": "Marcus — I respect him but fear his rigidity", "7": "Tomás — I distrust him deeply"},
  "resources": {"food": 80, "goods": 20, "influence": 30, "knowledge": 40},
  "location": "market",
  "faction": null,
  "emotional_state": "uneasy"
}

Action Types

SPEAK_PUBLIC     - Say something in a public location
SPEAK_PRIVATE    - Say something to a specific person
TRADE            - Exchange resources with another agent
FORM_GROUP       - Create or join a faction/institution
PROPOSE_RULE     - Propose a new community rule
VOTE             - Vote on a proposal
PROTEST          - Publicly oppose a rule or institution
COMPLY           - Follow a rule (even if unhappy)
DEFECT           - Break or circumvent a rule
BUILD            - Create something (shop, school, wall)
MOVE             - Go to a different location
OBSERVE          - Watch and gather information
DO_NOTHING       - Stay quiet this round

Action Log Entry (JSONL)

{
  "round": 141,
  "day": 47,
  "time_of_day": "morning",
  "agent_id": 0,
  "agent_name": "Elena Vasquez",
  "location": "council_hall",
  "action_type": "SPEAK_PUBLIC",
  "action_args": {
    "content": "I support privacy rights. Yesterday a customer asked if my bread was fresh and I had to say it was from Tuesday."
  },
  "speech": "I support privacy rights.",
  "internal_thought": "I wonder if Marcus is losing control of this council.",
  "targets": [],
  "world_state_changes": {},
  "relationship_changes": {}
}

Agent Memory Architecture

Three-tier memory, inspired by human cognition:

1. Core Memory (always in prompt, ~200 tokens)

Permanent facts the agent carries with them. Updated only when something truly significant happens (a relationship fundamentally changes, an institution is created, someone is exiled, a new rule passes). The LLM decides what "promotes" from working memory to core memory via the memory_promotion field in each decision.

2. Working Memory (recent, ~300 tokens)

Last 2-3 days of events the agent directly witnessed or participated in. Rolling window — old events drop off unless promoted to core memory.

3. Reflective Memory (periodic compression)

Every 30 simulated days, a single LLM call per agent: "Given everything that's happened, update your core beliefs, goals, and key memories." This compresses 30 days of lived experience into updated core memory. Cost: 25 agents × 1 call per 30 days = negligible.

Narrative Tension Engine

LLMs trend toward consensus and resolution. Left unchecked, agents will form a council, agree on norms, and settle into boring equilibrium by day 30. The tension engine prevents this.

The engine maintains a "tension floor" — a minimum level of unresolved conflict. If world metrics show stability > 0.85 and conflict < 0.15 for 3 consecutive rounds:

  1. Internal pressure: 2-3 agents' emotional states shift to "restless" or "dissatisfied." A latent grievance is added to their beliefs ("I've been overlooked," "the system benefits others more than me," "I'm tired of following rules that don't serve me").

  2. External pressure: The engine generates a minor environmental event — resource scarcity, a stranger arrives, a natural disaster, a rumor spreads, a neighboring community makes contact.

  3. Faction fracture: If a dominant faction has >60% of agents, the engine seeds a splinter ideology within it — a subset of members who agree with the faction's goals but disagree on methods.

This models the reality that human societies are never truly at equilibrium. There are always new pressures, generational shifts, and dissatisfied minorities.

Simulation Loop (per round)

  1. Compute context: current day, time, recent events, active disputes, world metrics
  2. Check tension engine: if equilibrium detected, generate pressure
  3. Determine active agents: base count from config, boosted by crisis/event proximity
  4. For each active agent (concurrently via asyncio): a. Build agent prompt: persona + core memory + working memory + beliefs + relationships + situation + actions b. LLM returns: feel/want/fear reasoning + action + speech + internal thought + belief updates + memory promotion c. Parse and validate action
  5. Reactive micro-rounds: If any SPEAK_PUBLIC or SPEAK_PRIVATE actions occurred, witnesses get lightweight LLM calls to react (agree, disagree, respond, stay silent). This produces conversations, not monologues.
  6. Apply all actions to world state (resolve conflicts, update resources, update relationships)
  7. Narrator LLM: convert round actions into readable prose paragraph
  8. Update agent memories (working memory window, core memory promotions)
  9. Update world metrics (stability, prosperity, trust, freedom, conflict) via exponential moving average
  10. Log everything to JSONL + SQLite
  11. Emit SSE event to frontend

Agent Decision Prompt (revised)

You are {name}, a {age}-year-old {role} in {world_name}.

THE RULES OF THIS WORLD:
{rules}

WHO YOU ARE:
{background}
{personality_description}

WHAT YOU KNOW TO BE TRUE (core memories):
{core_memory}

WHAT YOU BELIEVE RIGHT NOW:
{beliefs}

YOUR RELATIONSHIPS:
{key_relationships}

WHAT JUST HAPPENED (recent days):
{working_memory}

THE SITUATION RIGHT NOW:
You are at {location}. It is {time_of_day} on day {day}.
{immediate_context}

AVAILABLE ACTIONS:
{action_list}

Think step by step:
1. FEEL: What is your emotional reaction to the current situation?
2. WANT: Given your personality and goals, what do you want right now?
3. FEAR: What could go wrong? What worries you?
4. DECIDE: What action do you take?

Respond as JSON:
{
  "feel": "one sentence emotional reaction",
  "want": "one sentence desire",
  "fear": "one sentence concern",
  "action": "ACTION_TYPE",
  "args": { ... },
  "speech": "what you say out loud, if anything (null if silent)",
  "internal_thought": "what you think but don't say",
  "belief_updates": ["any changed beliefs"],
  "memory_promotion": "a core fact to remember long-term, or null"
}

Reactive Micro-Rounds

After primary actions resolve, if any SPEAK_PUBLIC or SPEAK_PRIVATE actions occurred:

  1. Identify agents who witnessed the speech act (same location for public, target for private)
  2. For each witness, lightweight LLM call (~50 tokens): "You just heard {speaker} say '{content}'. React: (a) respond publicly, (b) whisper to someone, (c) stay silent. Return letter + optional one-sentence response."
  3. Resolve reactive actions
  4. Include reactions in the narrator's round summary

Cost: ~3-5 extra cheap LLM calls per round. Produces natural dialogue instead of disconnected monologues.

Narrator Prompt (per round)

You are narrating events in a small town called {world_name}.
Rules of this world: {rules}

The following actions occurred during {time_of_day} on Day {day}:
{action_summaries_with_speech_and_reactions}

Previous narrative (for continuity):
{previous_narrative}

Write a 2-4 paragraph narrative of what happened, as if writing a novel.
Focus on the most dramatic or consequential events.
Include dialogue where agents spoke. Show reactions and body language.
Contrast what characters say with what they think (use italics for thoughts).
Capture the mood and tension.
Do not editorialize or explain the rules — just tell the story.

Report Generation Pipeline

Multi-stage pipeline (NOT a single LLM call):

Stage 1: Epoch Summaries

The narrator already generates round narratives. Group into 30-day epochs. One LLM call per epoch: "Summarize the key events and trends of days {N} to {N+30}." Produces ~12 epoch summaries for a year-long simulation.

Stage 2: Trend Extraction

Feed all epoch summaries + metrics timelines to LLM. Extract: key turning points, faction evolution, surprising emergent behaviors, overall narrative arc.

Stage 3: Report Composition

Feed trend extraction + the most dramatic individual narratives (selected by highest conflict/change scores) to LLM for final report:

  1. One-paragraph summary ("what happened")
  2. Key moments (5-10 most consequential events with day numbers)
  3. Metric sparklines (stability, freedom, conflict over time)
  4. "The Surprise" — the most unexpected emergent behavior
  5. Faction summary — what groups formed and why

Total: ~15 LLM calls instead of 1 impossible call.

Simulation Speed Modes

Live Mode (default at start)

Every round gets full LLM reasoning + narration. ~5 seconds per round. Good for the first 10-20 days when the user is engaged.

Fast-Forward Mode

Skip narration for "quiet" rounds. Run agent decisions but only narrate rounds with high significance scores (conflict spike, faction change, proposal, defection). Quiet periods get a one-line summary: "Days 45-52: A quiet week. Market activity normal. No disputes." ~1 second per round.

Jump Mode

"Skip to the next interesting thing." Engine runs rounds silently until a significance threshold is crossed (conflict spike, new faction, rule change, exile, death, etc.), then drops back to Live Mode. The timeline scrubber shows which days had significant events as highlighted marks.

Fork System

At any point in the timeline, users can "Fork here" on any day. This creates a copy of the simulation state at that point. The user describes what changes:

  • "What if Elena had voted against the Privacy Council?"
  • "What if the trader Tomás was never born?"
  • "What if the drought happened 3 months earlier?"

The fork runs from that point with the altered condition. The report view can show BOTH timelines side by side — original and fork — highlighting where they diverge.

Implementation: Copy the simulation's SQLite file at the fork point. Modify the specified agent/world state via LLM interpretation. Resume the engine from that round. Frontend shows split-screen comparison.

Frontend Screens

  1. Landing / Rule Editor — text input + presets + population/duration config
  2. Simulation View — generation phase streams seamlessly into simulation (NO separate loading page). Left panel: social graph (D3) + world metrics. Right panel: narrative feed. Bottom: timeline scrubber with speed controls.
  3. Agent Profile — slide-in sidebar with personality, relationships, beliefs, memory, action history, interview chat
  4. Summary Report — shareable page with narrative + key moments + sparklines + surprise + fork comparison
  5. Community Gallery — public simulations browsable by recency, views, forks

Progressive Generation (Zero Dead Time)

The entire pipeline streams from the moment the user clicks Simulate:

  • Second 0-5: "Imagining your world..." → world name + description appear
  • Second 5-15: "Populating..." → citizens appear one by one (streamed as generated)
  • Second 15-20: "Day 1 begins..." → first round narrative streams in
  • Second 20+: Simulation running, new content every 3-5 seconds

The user NEVER sees a blank loading screen. Generation smoothly transitions into simulation on the same page. Citizens are generated in two batches: first 10 immediately (enough to start the sim), remaining generated in background while first rounds run.

Tech Stack

Backend

  • Python 3.11+
  • FastAPI + uvicorn
  • OpenAI SDK (any OpenAI-compatible provider)
  • SQLite (via aiosqlite)
  • Pydantic v2

Frontend

  • Vue 3 (Composition API)
  • Vite
  • D3.js (social graph)
  • TailwindCSS

Infrastructure

  • Docker + Docker Compose
  • Single .env for config (LLM provider, model, API key)

API Endpoints

POST /api/simulate              — rules text → starts full pipeline (generate world + citizens + begin simulation), returns simulation_id
GET  /api/simulation/{id}/stream — SSE stream of all events (generation progress, citizen reveals, round narratives, metrics)
GET  /api/simulation/{id}/state  — current world state + metrics
GET  /api/simulation/{id}/agents — all agent states
GET  /api/agent/{sim_id}/{id}    — single agent detail + memory + history
POST /api/agent/{sim_id}/{id}/interview — chat with agent in character
POST /api/simulation/{id}/inject — inject "what if" event into running sim
POST /api/simulation/{id}/fork   — fork simulation at a specific day with altered conditions
POST /api/simulation/{id}/speed  — change speed mode (live / fast-forward / jump)
POST /api/simulation/{id}/pause  — pause simulation
POST /api/simulation/{id}/resume — resume simulation
POST /api/simulation/{id}/stop   — stop simulation, trigger report generation
GET  /api/simulation/{id}/report — get generated report
POST /api/simulation/{id}/publish — make simulation public in gallery
GET  /api/gallery                — list public simulations (sort: recent, views, forks)
GET  /api/gallery/{id}           — read-only view of public simulation report
POST /api/gallery/{id}/fork      — fork a public simulation with modified rules
GET  /api/presets                — list preset scenarios

Presets

  1. Nobody Can Lie — "Lying is physically impossible. Everyone speaks only truth." Teaser: "They invented silence clubs by month 3."

  2. Full Communism — "No private property. All resources are communal. No one can own more than they need." Teaser: "It worked. Then it didn't."

  3. Everyone Is Armed — "Every citizen carries a weapon. Violence is always an option." Teaser: "Politeness became a survival skill."

  4. Total Surveillance — "Everyone can see what everyone else is doing at all times. No secrets." Teaser: "The most powerful person was the one who stopped watching."

  5. Direct Democracy — "Every decision, no matter how small, must be voted on by everyone." Teaser: "They voted 47 times on day 1. By day 30, nobody showed up."

  6. No Phones, No Internet — "Communication is face-to-face only. No long-distance messaging." Teaser: "Rumors moved slower but hit harder."

  7. Absolute Meritocracy — "Your social status is strictly determined by measurable output. Nothing else." Teaser: "The artists starved. Then they revolted."

  8. Total Anonymity — "No one has a name or persistent identity. You can't be recognized." Teaser: "Trust became the scarcest resource."