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

> Discover how the Mirofish Simulation Manager orchestrates the entire simulation lifecycle. Learn how it manages state transitions, preparation pipelines, and execution.

- Repository: [BaiFu/mirofish](https://github.com/666ghj/mirofish)
- Tags: internals
- Published: 2026-02-23

---

**The `SimulationManager` in [`backend/app/services/simulation_manager.py`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py). The **`SimulationStatus`** enum (lines [24‑33](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L24-L33)) defines the canonical state machine with values: `created`, `preparing`, `ready`, `running`, `paused`, `stopped`, `completed`, and `failed`.

The **`SimulationState`** dataclass (lines [42‑78](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L42-L78)) 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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L44-L55) and [56‑73](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L56-L73)), 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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L15-L23)) 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

```python
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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L29-L47)), 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`](https://github.com/666ghj/mirofish/blob/main/simulation_config.json) using LLM-driven parameters
- **Artifact persistence**: Writes [`reddit_profiles.json`](https://github.com/666ghj/mirofish/blob/main/reddit_profiles.json) or `twitter_profiles.csv` to the simulation directory

The method sets status to **`preparing`** at line [66](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L66) and updates to **`ready`** upon successful completion (line [41](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L41)).

```python
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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L506-L528)) assembles command strings for external runner scripts:

```python
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`](https://github.com/666ghj/mirofish/blob/main/backend/scripts/run_reddit_simulation.py) or [`run_twitter_simulation.py`](https://github.com/666ghj/mirofish/blob/main/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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L58-L60)) to retrieve the current `SimulationState`, enabling real-time status polling:

```python
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`](https://github.com/666ghj/mirofish/blob/main/backend/app/api/simulation.py))
- **`failed`**: Captured in the `prepare_simulation` exception handler (lines [49‑56](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L49-L56)), 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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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](https://github.com/666ghj/mirofish/blob/main/backend/app/services/simulation_manager.py#L49-L56) 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`](https://github.com/666ghj/mirofish/blob/main/run_reddit_simulation.py) and [`run_twitter_simulation.py`](https://github.com/666ghj/mirofish/blob/main/run_twitter_simulation.py)) typically manage the `running` status, the HTTP API defined in [`backend/app/api/simulation.py`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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.