How the Simulation Manager Orchestrates Simulation Lifecycle and State Transitions in Mirofish

The SimulationManager in backend/app/services/simulation_manager.py drives simulations through seven distinct states—from creation to completion—by persisting state to disk, coordinating multi-stage preparation pipelines, and providing run-time instructions to external execution scripts.

The mirofish repository implements a robust simulation orchestration system where the SimulationManager serves as the central authority for managing distributed social media simulations. Understanding how this component handles simulation lifecycle and state transitions is essential for extending the platform or debugging complex multi-agent scenarios. The implementation uses a hybrid persistence model combining in-memory caching with JSON file storage to ensure durability across process restarts.

Core Architecture and State Model

The orchestration system centers on two critical data structures defined in backend/app/services/simulation_manager.py. The SimulationStatus enum (lines 24‑33) defines the canonical state machine with values: created, preparing, ready, running, paused, stopped, completed, and failed.

The SimulationState dataclass (lines 42‑78) encapsulates all metadata required for lifecycle management:

  • Identifiers: simulation_id, project_id, graph_id
  • Platform configuration: enable_twitter, enable_reddit booleans
  • Progress tracking: Entity counts, profile generation metrics, current round
  • Temporal data: created_at, updated_at timestamps
  • Error handling: Optional error field for failure diagnostics

State durability is guaranteed by the _save_simulation_state and _load_simulation_state helper methods (lines 44‑55 and 56‑73), which serialize to uploads/simulations/<simulation_id>/state.json and maintain synchronization with an in-memory cache.

Phase-by-Phase Lifecycle Orchestration

The manager progresses simulations through discrete phases, each implementing specific transition logic and persistence guarantees.

1. Creation and Initialization

The create_simulation method (lines 15‑23) initiates the lifecycle by:

  1. Generating a unique simulation_id
  2. Instantiating a SimulationState object with status created
  3. Persisting the initial state to disk via _save_simulation_state
  4. Recording the mapping in the in-memory cache
from backend.app.services.simulation_manager import SimulationManager

mgr = SimulationManager()
state = mgr.create_simulation(
    project_id="proj_123",
    graph_id="graph_456",
    enable_twitter=True,
    enable_reddit=False,
)
print(state.simulation_id, state.status)   # Output: sim_…  created

2. Preparation Pipeline

The transition from created to ready occurs through the prepare_simulation method (lines 29‑47), which executes a multi-stage pipeline:

  • Entity ingestion: Reads and filters nodes from a Zep knowledge graph using ZepEntityReader
  • Profile generation: Creates OASIS Agent Profiles via OasisProfileGenerator with optional LLM enrichment
  • Configuration synthesis: Generates simulation_config.json using LLM-driven parameters
  • Artifact persistence: Writes reddit_profiles.json or twitter_profiles.csv to the simulation directory

The method sets status to preparing at line 66 and updates to ready upon successful completion (line 41).

def progress(stage, pct, msg, **kw):
    print(f"[{stage}] {pct}% – {msg}")

state = mgr.prepare_simulation(
    simulation_id=state.simulation_id,
    simulation_requirement="Explore community sentiment about AI.",
    document_text="Full text of the policy document …",
    defined_entity_types=["person", "organization"],
    use_llm_for_profiles=True,
    progress_callback=progress,
    parallel_profile_count=5,
)
print(state.status)   # Output: ready

3. Execution Handoff

While the manager does not directly execute platform bots, it facilitates the transition to running by supplying execution parameters. The get_run_instructions method (lines 506‑528) assembles command strings for external runner scripts:

instr = mgr.get_run_instructions(state.simulation_id)
print(instr["commands"]["reddit"])

# Output: python /path/to/scripts/run_reddit_simulation.py --config /…/simulation_config.json

The actual status transition to running is performed by the external runner scripts (backend/scripts/run_reddit_simulation.py or run_twitter_simulation.py), which report their state back to the manager through the persistence layer.

4. Monitoring and Finalization

During execution, the manager exposes the get_simulation method (lines 58‑60) to retrieve the current SimulationState, enabling real-time status polling:

current = mgr.get_simulation(state.simulation_id)
print(current.to_simple_dict())

Terminal states are reached through distinct pathways:

  • completed: Set when runner scripts report successful termination
  • stopped: Triggered by manual intervention via API endpoints (defined in backend/app/api/simulation.py)
  • failed: Captured in the prepare_simulation exception handler (lines 49‑56), which updates SimulationState.error with diagnostic information

State Transition Diagram

The complete lifecycle follows this deterministic sequence:

  1. created → Initial state after create_simulation
  2. preparing → Entered at pipeline start
  3. ready → Preparation artifacts finalized
  4. running → External runners activated
  5. paused / stopped → Manual control interventions
  6. completed → Successful termination
  7. failed → Exception or runtime error encountered

Each transition triggers an atomic write to state.json, ensuring that the system can recover to the last known valid state following unexpected crashes or restarts.

Summary

  • The SimulationManager in backend/app/services/simulation_manager.py implements a finite state machine with eight distinct statuses defined in the SimulationStatus enum.
  • State persistence uses JSON serialization to uploads/simulations/<id>/state.json with helper methods _save_simulation_state and _load_simulation_state ensuring cache consistency.
  • The preparation pipeline (lines 29‑47) transitions simulations from created through preparing to ready by orchestrating entity reading, profile generation, and configuration synthesis.
  • Execution decoupling allows the manager to remain platform-agnostic; external runner scripts consume prepared artifacts via get_run_instructions (lines 506‑528) and manage the running state independently.
  • Error handling at lines 49‑56 ensures that pipeline failures transition the state to failed and populate the error field with exception details.

Frequently Asked Questions

How does the SimulationManager handle crashes during the preparation phase?

If an exception occurs within prepare_simulation, the catch block at lines 49‑56 immediately updates the simulation status to failed and persists the error message to SimulationState.error. Because the manager writes state atomically to disk at each phase transition, the system maintains a consistent record of the failure point without corrupting previous successful stages.

Can the simulation status be modified manually while external runners are executing?

Yes. While the external runner scripts (run_reddit_simulation.py and run_twitter_simulation.py) typically manage the running status, the HTTP API defined in backend/app/api/simulation.py exposes endpoints that allow authorized clients to transition states to paused or stopped. The manager treats these as terminal or interrupt states, though the external scripts must implement their own polling logic to respect these transitions.

What distinguishes the ready state from the created state in the simulation lifecycle?

The created state represents a minimal simulation record containing only identifiers and platform flags, whereas the ready state indicates that prepare_simulation has successfully executed the full pipeline: Zep graph entities have been ingested, OASIS profiles have been generated (potentially via LLM), and simulation_config.json has been written to disk. Only ready simulations possess the necessary artifacts for get_run_instructions to construct valid execution commands.

How does the manager support multiple simulations within the same project?

The list_simulations method filters the in-memory cache and persistent storage by project_id, returning all SimulationState objects associated with a given project. Each simulation maintains its own isolated directory under uploads/simulations/<simulation_id>/, preventing artifact collisions and allowing independent lifecycle management across concurrent simulations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →