Skip to main content
Autonomous AgentsPrediction727 lines

Multi-Agent Simulation for Prediction

Quick Summary24 lines
Multi-agent simulation spawns thousands or millions of autonomous AI agents — each with unique personalities, knowledge, and decision-making patterns — and lets them interact in simulated environments. The emergent behaviors that arise from these interactions produce predictions about social dynamics, market movements, political outcomes, and technological adoption that no single model could generate. This approach moves beyond traditional forecasting by modeling the complex adaptive systems that actually generate the events we want to predict.

## Key Points

1. Agent-based models capture nonlinear dynamics, tipping points, and emergent behaviors that equation-based models miss
2. Agent personality diversity is critical: use realistic distributions from behavioral science, not uniform random
3. Network topology dramatically affects outcomes: small-world networks produce different dynamics than scale-free ones
4. Hierarchical update scheduling and spatial partitioning enable scaling to millions of agents
5. Run multiple simulations with different random seeds and aggregate results for robust predictions
6. The MiroFish dual-platform approach (microblog + forum) captures different types of social dynamics
7. LLM-driven agents produce more realistic behaviors than rule-based agents, but require batching and caching for scale
8. Emergent behavior detection (consensus, polarization, cascades) is where the predictive value lies

## Quick Example

```
Traditional:    GDP = f(consumption, investment, government, exports)
Agent-Based:    10,000 consumer agents + 500 firm agents + 1 government agent
                → Each follows behavioral rules
                → GDP emerges from their interactions
                → Captures nonlinear dynamics, tipping points, cascades
```
skilldb get prediction-skills/multi-agent-simulationFull skill: 727 lines
Paste into your CLAUDE.md or agent config

Multi-Agent Simulation for Prediction

Overview

Multi-agent simulation spawns thousands or millions of autonomous AI agents — each with unique personalities, knowledge, and decision-making patterns — and lets them interact in simulated environments. The emergent behaviors that arise from these interactions produce predictions about social dynamics, market movements, political outcomes, and technological adoption that no single model could generate. This approach moves beyond traditional forecasting by modeling the complex adaptive systems that actually generate the events we want to predict.

Agent-Based Modeling Foundations

What Makes ABM Different

Traditional forecasting uses equations to model aggregate behavior. Agent-based modeling (ABM) starts from individual agents following simple rules, and complex macro-level patterns emerge from their interactions.

Traditional:    GDP = f(consumption, investment, government, exports)
Agent-Based:    10,000 consumer agents + 500 firm agents + 1 government agent
                → Each follows behavioral rules
                → GDP emerges from their interactions
                → Captures nonlinear dynamics, tipping points, cascades

Core Agent Architecture

from dataclasses import dataclass, field
from typing import Optional
import random
import uuid

@dataclass
class AgentPersonality:
    """Defines an agent's behavioral tendencies."""
    risk_tolerance: float       # 0 (risk-averse) to 1 (risk-seeking)
    information_sensitivity: float  # How much new info changes beliefs
    social_conformity: float    # Tendency to follow neighbors
    contrarianism: float        # Tendency to go against the crowd
    attention_span: int         # How many time steps of memory
    emotional_volatility: float # How much mood affects decisions
    expertise_level: float      # Domain knowledge (0 to 1)

    @classmethod
    def random(cls) -> 'AgentPersonality':
        """Generate a random personality from realistic distributions."""
        risk = random.betavariate(2, 5)  # Skewed toward risk-averse
        conformity = random.betavariate(3, 2)  # Skewed toward conformist
        return cls(
            risk_tolerance=risk,
            information_sensitivity=random.betavariate(2, 2),
            social_conformity=conformity,
            contrarianism=max(0, 1 - conformity + random.gauss(0, 0.1)),
            attention_span=int(random.expovariate(0.1)) + 1,
            emotional_volatility=random.betavariate(2, 3),
            expertise_level=random.betavariate(1.5, 5)
        )


@dataclass
class AgentState:
    """Mutable state of an agent at a point in time."""
    beliefs: dict = field(default_factory=dict)      # topic -> probability
    mood: float = 0.0                                 # -1 (negative) to 1 (positive)
    wealth: float = 1000.0
    social_connections: list = field(default_factory=list)
    memory: list = field(default_factory=list)
    position: tuple = (0, 0)                          # Spatial position


class Agent:
    """An autonomous agent in the simulation."""

    def __init__(self, personality: Optional[AgentPersonality] = None):
        self.id = str(uuid.uuid4())[:8]
        self.personality = personality or AgentPersonality.random()
        self.state = AgentState()

    def perceive(self, environment: dict, neighbors: list['Agent']) -> dict:
        """Gather information from environment and neighbors."""
        perceptions = {
            'environment_signals': environment.get('signals', []),
            'neighbor_beliefs': [
                n.state.beliefs for n in neighbors
            ],
            'neighbor_moods': [n.state.mood for n in neighbors],
            'neighbor_actions': [
                n.state.memory[-1] if n.state.memory else None
                for n in neighbors
            ]
        }
        return perceptions

    def decide(self, perceptions: dict) -> dict:
        """Make a decision based on personality and perceptions."""
        action = {}

        # Update beliefs based on new information
        for signal in perceptions['environment_signals']:
            topic = signal['topic']
            new_evidence = signal['value']
            current = self.state.beliefs.get(topic, 0.5)

            # Bayesian-ish update weighted by personality
            weight = self.personality.information_sensitivity
            self.state.beliefs[topic] = (
                current * (1 - weight) + new_evidence * weight
            )

        # Social influence on beliefs
        if perceptions['neighbor_beliefs']:
            for topic in self.state.beliefs:
                neighbor_avg = sum(
                    nb.get(topic, 0.5) for nb in perceptions['neighbor_beliefs']
                ) / len(perceptions['neighbor_beliefs'])

                social_pull = self.personality.social_conformity
                contrarian_push = self.personality.contrarianism

                self.state.beliefs[topic] += (
                    social_pull * (neighbor_avg - self.state.beliefs[topic])
                    - contrarian_push * (neighbor_avg - 0.5)
                )
                self.state.beliefs[topic] = max(0, min(1, self.state.beliefs[topic]))

        # Mood contagion
        if perceptions['neighbor_moods']:
            avg_mood = sum(perceptions['neighbor_moods']) / len(perceptions['neighbor_moods'])
            self.state.mood += (
                self.personality.emotional_volatility * (avg_mood - self.state.mood) * 0.1
            )

        # Decide on action based on beliefs and personality
        action['trade'] = self._trading_decision()
        action['communicate'] = self._communication_decision()

        return action

    def _trading_decision(self) -> dict:
        """Decide whether to buy, sell, or hold."""
        confidence = max(self.state.beliefs.values()) if self.state.beliefs else 0.5
        threshold = 1 - self.personality.risk_tolerance

        if confidence > threshold + 0.1:
            return {'action': 'buy', 'size': confidence * self.personality.risk_tolerance}
        elif confidence < threshold - 0.1:
            return {'action': 'sell', 'size': (1 - confidence) * self.personality.risk_tolerance}
        return {'action': 'hold', 'size': 0}

    def _communication_decision(self) -> dict:
        """Decide what to share with neighbors."""
        # Extroverted agents share more
        share_prob = self.personality.social_conformity * 0.5 + 0.2
        if random.random() < share_prob:
            return {'share': True, 'beliefs': self.state.beliefs.copy()}
        return {'share': False}

    def act(self, action: dict, environment: 'SimulationEnvironment'):
        """Execute the decided action in the environment."""
        self.state.memory.append(action)
        if len(self.state.memory) > self.personality.attention_span:
            self.state.memory = self.state.memory[-self.personality.attention_span:]

OASIS-Style Large-Scale Simulation

Architecture for Million-Agent Simulations

The OASIS (Open Agent Social Interaction Simulations) framework demonstrates how to scale agent-based simulations to millions of agents by using LLMs to drive realistic behavior.

class OASISEngine:
    """
    Large-scale social simulation engine.
    Inspired by OASIS: supports millions of agents interacting
    on simulated social platforms.
    """

    def __init__(self, config: dict):
        self.agents = []
        self.environment = SimulationEnvironment(config)
        self.social_network = SocialNetwork()
        self.time_step = 0
        self.metrics = SimulationMetrics()
        self.event_queue = []

    def spawn_population(self, n_agents: int, demographics: dict):
        """Create a diverse agent population matching demographic targets."""
        for i in range(n_agents):
            # Generate personality from demographic distribution
            demographic = self._sample_demographic(demographics)
            personality = self._personality_from_demographic(demographic)
            agent = Agent(personality)
            agent.state.beliefs = self._initial_beliefs(demographic)
            self.agents.append(agent)

        # Create social network
        self.social_network.build_network(
            self.agents,
            topology='small_world',
            avg_connections=15,
            clustering=0.3
        )

    def inject_event(self, event: dict, time_step: int):
        """Schedule an external event to hit the simulation."""
        self.event_queue.append({
            'event': event,
            'time': time_step,
            'affected_fraction': event.get('reach', 1.0)
        })

    def step(self):
        """Execute one simulation time step."""
        # Process any scheduled events
        for event in self.event_queue:
            if event['time'] == self.time_step:
                self._apply_event(event)

        # Parallelize agent updates
        environment_state = self.environment.get_state()

        for agent in self.agents:
            neighbors = self.social_network.get_neighbors(agent.id)
            neighbor_agents = [a for a in self.agents if a.id in neighbors]

            perceptions = agent.perceive(environment_state, neighbor_agents)
            action = agent.decide(perceptions)
            agent.act(action, self.environment)

        # Update environment based on aggregate agent actions
        self.environment.update(self.agents)

        # Collect metrics
        self.metrics.record(self.time_step, self.agents, self.environment)
        self.time_step += 1

    def run(self, n_steps: int, events: list = None):
        """Run the full simulation."""
        if events:
            for event in events:
                self.inject_event(event['event'], event['time'])

        for _ in range(n_steps):
            self.step()

        return self.metrics.summarize()

    def _apply_event(self, event: dict):
        """Apply an external event to affected agents."""
        affected = random.sample(
            self.agents,
            int(len(self.agents) * event['affected_fraction'])
        )
        for agent in affected:
            for topic, impact in event['event'].get('belief_impacts', {}).items():
                current = agent.state.beliefs.get(topic, 0.5)
                sensitivity = agent.personality.information_sensitivity
                agent.state.beliefs[topic] = current + impact * sensitivity
                agent.state.beliefs[topic] = max(0, min(1, agent.state.beliefs[topic]))

            mood_impact = event['event'].get('mood_impact', 0)
            agent.state.mood += mood_impact * agent.personality.emotional_volatility


class SocialNetwork:
    """Manages agent connections and network topology."""

    def __init__(self):
        self.adjacency = {}  # agent_id -> set of neighbor_ids
        self.influence_weights = {}  # (from, to) -> weight

    def build_network(self, agents: list, topology: str = 'small_world',
                      avg_connections: int = 15, clustering: float = 0.3):
        """Build a social network with specified topology."""
        n = len(agents)
        ids = [a.id for a in agents]

        if topology == 'small_world':
            # Watts-Strogatz small-world network
            k = avg_connections
            rewire_prob = 1 - clustering

            # Start with ring lattice
            for i, agent_id in enumerate(ids):
                self.adjacency[agent_id] = set()
                for j in range(1, k // 2 + 1):
                    neighbor = ids[(i + j) % n]
                    self.adjacency[agent_id].add(neighbor)
                    if neighbor not in self.adjacency:
                        self.adjacency[neighbor] = set()
                    self.adjacency[neighbor].add(agent_id)

            # Rewire edges
            for i, agent_id in enumerate(ids):
                neighbors = list(self.adjacency[agent_id])
                for neighbor in neighbors:
                    if random.random() < rewire_prob:
                        new_neighbor = random.choice(ids)
                        if new_neighbor != agent_id and new_neighbor not in self.adjacency[agent_id]:
                            self.adjacency[agent_id].discard(neighbor)
                            self.adjacency[agent_id].add(new_neighbor)

        elif topology == 'scale_free':
            # Barabasi-Albert preferential attachment
            for i, agent_id in enumerate(ids):
                self.adjacency[agent_id] = set()
                if i < avg_connections:
                    for j in range(i):
                        self.adjacency[agent_id].add(ids[j])
                        self.adjacency[ids[j]].add(agent_id)
                else:
                    degrees = [len(self.adjacency.get(aid, set())) + 1 for aid in ids[:i]]
                    total_degree = sum(degrees)
                    probs = [d / total_degree for d in degrees]
                    targets = set(random.choices(ids[:i], weights=probs, k=min(avg_connections, i)))
                    for target in targets:
                        self.adjacency[agent_id].add(target)
                        self.adjacency[target].add(agent_id)

    def get_neighbors(self, agent_id: str) -> set:
        return self.adjacency.get(agent_id, set())

Emergent Behavior Analysis

Detecting Emergent Patterns

class EmergenceDetector:
    """Detect and classify emergent behaviors in agent populations."""

    def __init__(self, agents: list, history: list):
        self.agents = agents
        self.history = history  # List of snapshots over time

    def detect_consensus_formation(self, topic: str, threshold: float = 0.8) -> dict:
        """Detect when agents converge on a shared belief."""
        consensus_timeline = []
        for snapshot in self.history:
            beliefs = [a['beliefs'].get(topic, 0.5) for a in snapshot]
            mean_belief = sum(beliefs) / len(beliefs)
            std_belief = (sum((b - mean_belief)**2 for b in beliefs) / len(beliefs)) ** 0.5

            agreement = 1 - std_belief * 2  # Normalize
            consensus_timeline.append({
                'mean': mean_belief,
                'std': std_belief,
                'agreement': agreement,
                'is_consensus': agreement > threshold
            })

        # Find consensus formation point
        for i, point in enumerate(consensus_timeline):
            if point['is_consensus']:
                return {
                    'formed': True,
                    'time_step': i,
                    'consensus_value': point['mean'],
                    'strength': point['agreement']
                }
        return {'formed': False}

    def detect_polarization(self, topic: str) -> dict:
        """Detect opinion polarization (bimodal distribution)."""
        latest = self.history[-1]
        beliefs = [a['beliefs'].get(topic, 0.5) for a in latest]

        # Check for bimodality
        low_cluster = [b for b in beliefs if b < 0.4]
        high_cluster = [b for b in beliefs if b > 0.6]
        middle = [b for b in beliefs if 0.4 <= b <= 0.6]

        polarization_index = (
            (len(low_cluster) + len(high_cluster)) / len(beliefs)
            - len(middle) / len(beliefs)
        )

        return {
            'polarization_index': polarization_index,
            'is_polarized': polarization_index > 0.5,
            'low_fraction': len(low_cluster) / len(beliefs),
            'high_fraction': len(high_cluster) / len(beliefs),
            'undecided_fraction': len(middle) / len(beliefs)
        }

    def detect_cascade(self, topic: str, window: int = 5) -> list:
        """Detect information cascades (rapid belief shifts)."""
        cascades = []
        for i in range(window, len(self.history)):
            current_beliefs = [a['beliefs'].get(topic, 0.5) for a in self.history[i]]
            past_beliefs = [a['beliefs'].get(topic, 0.5) for a in self.history[i - window]]

            current_mean = sum(current_beliefs) / len(current_beliefs)
            past_mean = sum(past_beliefs) / len(past_beliefs)

            shift = abs(current_mean - past_mean)
            if shift > 0.15:  # Significant shift
                cascades.append({
                    'time_step': i,
                    'shift_magnitude': shift,
                    'direction': 'positive' if current_mean > past_mean else 'negative',
                    'speed': shift / window
                })
        return cascades

    def detect_clustering(self) -> dict:
        """Detect spatial or network clustering of opinions."""
        latest = self.history[-1]
        # Group agents by similar beliefs
        clusters = []
        assigned = set()

        for i, agent in enumerate(latest):
            if i in assigned:
                continue
            cluster = [i]
            assigned.add(i)
            for j, other in enumerate(latest):
                if j in assigned:
                    continue
                belief_distance = sum(
                    abs(agent['beliefs'].get(k, 0.5) - other['beliefs'].get(k, 0.5))
                    for k in agent['beliefs']
                )
                if belief_distance < 0.3:
                    cluster.append(j)
                    assigned.add(j)
            if len(cluster) > 1:
                clusters.append(cluster)

        return {
            'n_clusters': len(clusters),
            'largest_cluster': max(len(c) for c in clusters) if clusters else 0,
            'singleton_fraction': len([a for a in range(len(latest)) if a not in assigned]) / len(latest)
        }

MiroFish-Style Prediction Simulation

The MiroFish Approach

MiroFish demonstrates a dual-platform approach where LLM-powered agents interact on simulated social media and discussion platforms to generate predictions about social phenomena.

class MiroFishSimulation:
    """
    Dual-platform simulation for social prediction.
    Agents interact on simulated Twitter-like and Reddit-like platforms.
    """

    def __init__(self, seed_scenario: str, n_agents: int = 1000):
        self.scenario = seed_scenario
        self.platforms = {
            'microblog': MicroblogPlatform(max_post_length=280),
            'forum': ForumPlatform(thread_based=True)
        }
        self.agents = self._create_diverse_population(n_agents)
        self.llm_cache = {}  # Cache LLM responses for efficiency

    def _create_diverse_population(self, n: int) -> list:
        """Create agents with diverse backgrounds for the scenario."""
        archetypes = [
            {'type': 'expert', 'fraction': 0.05, 'expertise': 0.9},
            {'type': 'informed_citizen', 'fraction': 0.15, 'expertise': 0.6},
            {'type': 'casual_observer', 'fraction': 0.50, 'expertise': 0.3},
            {'type': 'contrarian', 'fraction': 0.10, 'expertise': 0.5},
            {'type': 'influencer', 'fraction': 0.05, 'expertise': 0.4},
            {'type': 'bot_like', 'fraction': 0.05, 'expertise': 0.1},
            {'type': 'lurker', 'fraction': 0.10, 'expertise': 0.4}
        ]

        agents = []
        for archetype in archetypes:
            count = int(n * archetype['fraction'])
            for _ in range(count):
                agent = self._create_agent_from_archetype(archetype)
                agents.append(agent)
        return agents

    def _create_agent_from_archetype(self, archetype: dict) -> dict:
        """Create a richly defined agent from an archetype template."""
        return {
            'id': str(uuid.uuid4())[:8],
            'archetype': archetype['type'],
            'personality': AgentPersonality.random(),
            'backstory': self._generate_backstory(archetype),
            'platform_preference': random.choice(['microblog', 'forum', 'both']),
            'posting_frequency': random.expovariate(1.0),
            'follower_count': int(random.paretovariate(1.5) * 10),
            'memory': [],
            'beliefs': {}
        }

    def _generate_backstory(self, archetype: dict) -> str:
        """Generate a unique backstory for an agent."""
        # In production, this would call an LLM
        backgrounds = {
            'expert': "Professional with deep domain knowledge",
            'informed_citizen': "Engaged citizen who follows news closely",
            'casual_observer': "Occasional social media user with moderate interest",
            'contrarian': "Skeptic who questions mainstream narratives",
            'influencer': "Content creator with large following",
            'bot_like': "Account with repetitive behavior patterns",
            'lurker': "Mostly reads, rarely posts"
        }
        return backgrounds.get(archetype['type'], "Generic participant")

    def inject_scenario(self, scenario_event: dict):
        """Inject a scenario event into the simulation."""
        # Post the event as breaking news on both platforms
        self.platforms['microblog'].inject_content({
            'type': 'breaking_news',
            'content': scenario_event['headline'],
            'details': scenario_event['details'],
            'timestamp': self.current_time
        })
        self.platforms['forum'].inject_content({
            'type': 'megathread',
            'title': scenario_event['headline'],
            'body': scenario_event['full_text'],
            'timestamp': self.current_time
        })

    def run_prediction_cycle(self, question: str, n_rounds: int = 50) -> dict:
        """Run simulation and extract prediction from emergent behavior."""
        self.current_time = 0

        for round_num in range(n_rounds):
            # Each agent takes actions based on personality
            for agent in self.agents:
                if random.random() < agent['posting_frequency']:
                    self._agent_action(agent, question)
            self.current_time += 1

        # Analyze emergent consensus
        return self._extract_prediction(question)

    def _extract_prediction(self, question: str) -> dict:
        """Extract a prediction from the simulation state."""
        all_beliefs = [a['beliefs'].get(question, 0.5) for a in self.agents]
        expert_beliefs = [
            a['beliefs'].get(question, 0.5)
            for a in self.agents if a['archetype'] == 'expert'
        ]

        return {
            'population_mean': sum(all_beliefs) / len(all_beliefs),
            'expert_mean': sum(expert_beliefs) / len(expert_beliefs) if expert_beliefs else None,
            'std_dev': (sum((b - sum(all_beliefs)/len(all_beliefs))**2 for b in all_beliefs) / len(all_beliefs)) ** 0.5,
            'consensus_level': self._measure_consensus(all_beliefs),
            'dominant_narratives': self._extract_narratives(),
            'polarization': self._measure_polarization(all_beliefs)
        }

    def _measure_consensus(self, beliefs: list) -> float:
        mean = sum(beliefs) / len(beliefs)
        variance = sum((b - mean)**2 for b in beliefs) / len(beliefs)
        return max(0, 1 - variance * 4)  # Normalize to 0-1

    def _measure_polarization(self, beliefs: list) -> float:
        below = sum(1 for b in beliefs if b < 0.3)
        above = sum(1 for b in beliefs if b > 0.7)
        return (below + above) / len(beliefs)

    def _extract_narratives(self) -> list:
        """Extract dominant narratives from platform content."""
        # In production, would analyze posts with NLP
        return []

    def _agent_action(self, agent: dict, question: str):
        """Have an agent take an action on their preferred platform."""
        pass  # LLM-driven in production

Scaling to Millions of Agents

Performance Optimization Strategies

class ScalableSimulation:
    """Techniques for scaling agent simulations to millions."""

    def __init__(self, n_agents: int):
        self.n_agents = n_agents

    def hierarchical_update(self, agents: list, n_levels: int = 3):
        """
        Not every agent needs full LLM-driven updates every step.
        Use hierarchical update frequencies:
        - Level 1 (influencers, 1%): Full LLM update every step
        - Level 2 (active users, 10%): Full update every 5 steps
        - Level 3 (passive users, 89%): Rule-based update, LLM every 20 steps
        """
        update_schedule = {
            1: {'fraction': 0.01, 'llm_frequency': 1},
            2: {'fraction': 0.10, 'llm_frequency': 5},
            3: {'fraction': 0.89, 'llm_frequency': 20}
        }
        return update_schedule

    def spatial_partitioning(self, agents: list, grid_size: int = 100):
        """
        Divide agents into spatial cells.
        Only compute interactions within and between adjacent cells.
        Reduces O(n^2) interactions to O(n * k) where k is cell size.
        """
        cells = {}
        for agent in agents:
            cell_x = int(agent.state.position[0] * grid_size)
            cell_y = int(agent.state.position[1] * grid_size)
            cell_key = (cell_x, cell_y)
            if cell_key not in cells:
                cells[cell_key] = []
            cells[cell_key].append(agent)
        return cells

    def belief_compression(self, beliefs: dict) -> bytes:
        """
        Compress agent beliefs for memory efficiency.
        Instead of storing full belief dicts, use fixed-point arrays.
        """
        import struct
        topics = sorted(beliefs.keys())
        values = [int(beliefs[t] * 255) for t in topics]
        return struct.pack(f'{len(values)}B', *values)

    def batch_llm_calls(self, agents: list, prompt_template: str,
                        batch_size: int = 50) -> list:
        """
        Batch multiple agent decisions into single LLM calls.
        Group similar agents and use one call per group.
        """
        groups = {}
        for agent in agents:
            group_key = (
                agent['archetype'],
                round(agent['beliefs'].get('main_topic', 0.5), 1)
            )
            if group_key not in groups:
                groups[group_key] = []
            groups[group_key].append(agent)

        results = []
        for group_key, group_agents in groups.items():
            # One LLM call represents the whole group
            prompt = f"""You represent {len(group_agents)} {group_key[0]} agents
with current belief ~{group_key[1]}. {prompt_template}
Generate {min(len(group_agents), 5)} diverse responses."""
            # response = await call_llm(prompt)
            # Apply variations of response to all agents in group
            results.extend(group_agents)

        return results

Prediction Extraction from Simulations

Converting Agent States to Forecasts

class PredictionExtractor:
    """Extract calibrated predictions from simulation results."""

    def __init__(self, simulation_results: list):
        self.results = simulation_results  # Multiple simulation runs

    def ensemble_prediction(self, question: str) -> dict:
        """Combine predictions from multiple simulation runs."""
        predictions = []
        for result in self.results:
            pred = result.get(question, {})
            if 'population_mean' in pred:
                predictions.append(pred['population_mean'])

        if not predictions:
            return {'error': 'No predictions available'}

        mean = sum(predictions) / len(predictions)
        std = (sum((p - mean)**2 for p in predictions) / len(predictions)) ** 0.5

        return {
            'point_estimate': mean,
            'confidence_interval_90': (
                mean - 1.645 * std,
                mean + 1.645 * std
            ),
            'n_simulations': len(predictions),
            'inter_simulation_variance': std,
            'reliability': 'high' if std < 0.1 else 'medium' if std < 0.2 else 'low'
        }

    def scenario_probabilities(self, scenarios: list[str]) -> dict:
        """Estimate probability of each scenario based on simulation outcomes."""
        scenario_counts = {s: 0 for s in scenarios}
        total = len(self.results)

        for result in self.results:
            closest_scenario = self._match_scenario(result, scenarios)
            if closest_scenario:
                scenario_counts[closest_scenario] += 1

        return {
            scenario: count / total
            for scenario, count in scenario_counts.items()
        }

    def _match_scenario(self, result: dict, scenarios: list) -> str:
        """Match a simulation outcome to the closest predefined scenario."""
        # Simplified matching logic
        return scenarios[0] if scenarios else None

Key Takeaways

  1. Agent-based models capture nonlinear dynamics, tipping points, and emergent behaviors that equation-based models miss
  2. Agent personality diversity is critical: use realistic distributions from behavioral science, not uniform random
  3. Network topology dramatically affects outcomes: small-world networks produce different dynamics than scale-free ones
  4. Hierarchical update scheduling and spatial partitioning enable scaling to millions of agents
  5. Run multiple simulations with different random seeds and aggregate results for robust predictions
  6. The MiroFish dual-platform approach (microblog + forum) captures different types of social dynamics
  7. LLM-driven agents produce more realistic behaviors than rule-based agents, but require batching and caching for scale
  8. Emergent behavior detection (consensus, polarization, cascades) is where the predictive value lies

Install this skill directly: skilldb add prediction-skills

Get CLI access →