A Swarm Intelligence Engine That Predicts the Future
github.com/666ghj/MiroFish · Python 57.8% · Vue 41.1% · AGPL-3.0
Explain Like I'm Five
What if you could build a tiny world inside a computer, fill it with thousands of little people who think for themselves, and then watch what happens next?
You have a giant sandbox. But instead of sand, you fill it with little people — each one has their own personality, memories, and opinions.
You drop in a newspaper article or a new rule, and then you sit back and watch what happens.
The little people read the news, argue about it, share posts, change their minds, and form groups — all on their own.
Architecture, code patterns, and implementation details
Single-page app on :3000. Five wizard steps track the full pipeline. Real-time polling at 2-3s intervals updates a dual-platform timeline feed.
REST API on :5001. Orchestrates the full lifecycle: ontology generation, graph building, profile synthesis, simulation execution, and report writing.
Zep Cloud for GraphRAG memory. LLM API (OpenAI-compatible) for all generation. OASIS engine for agent simulation.
| Component | Details |
|---|---|
| Backend services | 12 Python modules |
| API endpoints | 30+ REST routes |
| Vue components | 8 views + 5 step components |
| Simulation platforms | Twitter + Reddit parallel |
| IPC mechanism | File-based JSON polling |
| Agent profiles | 16 MBTI types, 11 countries |
Each stage maps to a Vue step component and a set of backend services:
| # | Stage | Frontend | Backend Service | What Happens |
|---|---|---|---|---|
| 1 | Graph Build | Step1GraphBuild | graph_builder | Upload docs, generate ontology, build knowledge graph in Zep |
| 2 | Env Setup | Step2EnvSetup | simulation_manager | Read entities from graph, generate agent profiles & config |
| 3 | Simulation | Step3Simulation | simulation_runner | Launch OASIS, run rounds on Twitter & Reddit in parallel |
| 4 | Report | Step4Report | report_agent | ReACT-based agent writes multi-section prediction report |
| 5 | Interaction | Step5Interaction | simulation_ipc | Interview agents, explore the simulated world |
Given uploaded documents and a natural-language simulation requirement, the OntologyGenerator asks the LLM to produce a structured schema:
{
"entity_types": [
{
"name": "Politician",
"description": "A public official",
"attributes": [
{"name": "party",
"description": "Political party"}
]
}
],
"edge_types": [
{
"name": "opposes",
"source_targets": [
{"source": "Politician",
"target": "Policy"}
]
}
]
}
Entity types from the LLM become actual Python classes at runtime:
# Dynamic class creation entity_class = type( name, # "Politician" (EntityModel,), # Zep base attrs # Fields from ontology ) # Edge classes with source/target pairs edge_class = type( class_name, # "Opposes" (EdgeModel,), attrs ) # Bound to EntityEdgeSourceTarget
The ZepEntityReader reads all nodes from the knowledge graph, filtering out system labels (Entity, Node). Each entity becomes a candidate agent.
The OasisProfileGenerator then enriches each entity with:
{
"user_id": 1,
"name": "Wang Xiaoming",
"age": 34,
"gender": "male",
"mbti": "ENTJ",
"profession": "Policy Analyst",
"country": "China",
"interested_topics": [...],
"persona": "2000-char biography
with memories, opinions,
behavioral tendencies...",
"bio": "Short public bio"
}
Retry logic: 3 attempts with decreasing temperature (0.7 → 0.5 → 0.3). JSON repair for truncated LLM output.
Instead of manual tuning, the SimulationConfigGenerator asks the LLM to reason about optimal parameters given:
The LLM outputs a SimulationParameters object with reasoning for its choices.
| Parameter | Purpose |
|---|---|
| Num rounds | Simulation duration |
| Time per round | Simulated time granularity |
| Activity level | How often agents act |
| Post frequency | Content creation rate |
| Interaction rate | Likes, follows, comments |
| Platform split | Twitter vs Reddit balance |
The SimulationRunner launches OASIS as a subprocess using Python's subprocess module. It supports three modes:
class RunnerStatus(Enum): IDLE STARTING RUNNING PAUSED STOPPING STOPPED COMPLETED FAILED
Agent actions are logged to JSONL files. The runner parses these in real time:
# Each action is an AgentAction { "round": 3, "platform": "twitter", "agent_id": 42, "action": "CREATE_POST", "success": true, "timestamp": "..." }
Completion detected via "simulation_end" event. Cross-platform cleanup uses killpg (Unix) or taskkill (Windows). Signal handlers + atexit callbacks ensure orphan prevention.
OASIS (Open Agent Social Interaction Simulations) is an external framework by CAMEL-AI that MiroFish wraps.
It provides:
The ReportAgent uses a ReACT loop (Reason + Act) to write each section of the prediction report.
Min 3 tool calls, max 5 per section. Conflict resolution with 3-tier retry.
| Tool | Purpose |
|---|---|
| insight_forge | Deep multi-dimensional analysis with auto sub-question decomposition |
| panorama_search | Full-scope event timeline and evolution tracking |
| quick_search | Fast targeted fact verification |
| interview_agents | Real OASIS API calls — asks simulated agents questions |
reports/{report_id}/
meta.json
outline.json
progress.json
section_01.md ... section_N.md
full_report.md
agent_log.jsonl
console_log.txt
MiroFish uses a simple but effective file-based inter-process communication system to talk to running OASIS simulations:
simulation_dir/
ipc_commands/
{uuid}.json ← server writes
ipc_responses/
{uuid}.json ← OASIS writes back
env_status.json ← alive/stopped
The SimulationIPCClient writes a command JSON, then polls the responses directory at 0.5s intervals with a 60s timeout.
| Command | What it does |
|---|---|
| INTERVIEW | Ask one agent a question |
| BATCH_INTERVIEW | Ask multiple agents at once |
| CLOSE_ENV | Gracefully shut down simulation |
Zep Cloud provides the knowledge graph infrastructure that gives agents their "world knowledge".
| Module | Role |
|---|---|
| graph_builder | Creates graphs, sets ontology, ingests text |
| zep_entity_reader | Reads nodes/edges with retry + pagination |
| zep_graph_memory_updater | Updates graph during simulation |
# zep_paging.py def fetch_all_nodes(client, graph_id): # Handles Zep's paginated API # Returns complete node list def fetch_all_edges(client, graph_id): # Same for edges
| View | Purpose |
|---|---|
| Home.vue | Landing page |
| MainView.vue | 5-step wizard container |
| Process.vue | Pipeline progress tracker |
| SimulationView.vue | Simulation config & launch |
| SimulationRunView.vue | Live simulation monitor |
| ReportView.vue | Generated report display |
| InteractionView.vue | Agent interview UI |
The backend exposes 30+ endpoints across three Flask Blueprints:
| Endpoint | Purpose |
|---|---|
| POST /create | New simulation |
| POST /prepare | Async entity + profile + config |
| POST /start | Launch OASIS subprocess |
| POST /stop | Kill simulation |
| GET /run-status | Round progress, action counts |
| GET /actions | Paginated action history |
| GET /timeline | Per-round summaries |
| POST /interview | Talk to an agent |
| POST /interview/batch | Talk to many agents |
| Endpoint | Purpose |
|---|---|
| POST /ontology | Generate ontology from text |
| POST /build | Start graph construction |
| GET /data | Full graph nodes + edges |
| Endpoint | Purpose |
|---|---|
| POST /generate | Start report generation |
| GET /status | Section-level progress |
| GET /content | Full/partial report MD |
Long-running operations use a consistent pattern across the codebase:
def build_graph_async(self, text, ...): # 1. Create a task ID task_id = self.task_manager.create_task( task_type="graph_build", metadata={...} ) # 2. Spawn daemon thread thread = threading.Thread( target=self._build_graph_worker, args=(task_id, text, ...) ) thread.daemon = True thread.start() # 3. Return task ID immediately return task_id
Workers update task progress via the TaskManager:
self.task_manager.update_task( task_id, status=TaskStatus.PROCESSING, progress=60, message="Waiting for Zep..." )
The frontend polls /prepare/status to get updates. This avoids WebSockets while still providing real-time feedback.
MiroFish relies heavily on LLM-generated JSON. Here is how it handles the messiness of real-world LLM output:
# Layer 1: Fix truncated JSON def _fix_truncated_json(text): # Count open/close braces # Append missing closers # Layer 2: Regex extraction def _try_fix_json(text): # Strip markdown code fences # Remove control characters # Extract JSON via regex # Fallback to basic structure # Layer 3: Temperature descent # Retry: 0.7 → 0.5 → 0.3 # Lower temp = more deterministic
When the LLM simultaneously outputs a tool call AND a final answer (a common failure mode):
def _call_with_retry( self, func, operation_name, max_retries=3, initial_delay=1.0 ): for attempt in range(max_retries): try: return func() except Exception as e: delay = initial_delay * (2 ** attempt) time.sleep(delay) raise Exception( f"Failed after {max_retries}" )
| Component | Strategy |
|---|---|
| Zep API calls | Exponential backoff, 3 retries |
| LLM profile gen | Temperature descent, JSON repair |
| IPC commands | 60s timeout, 0.5s poll interval |
| Episode processing | 600s timeout, 3s poll, partial success |
| Report sections | Save partial on failure, section isolation |
| Process cleanup | Signal handlers + atexit + killpg |
# Prerequisites Node.js 18+ / Python 3.11-3.12 / uv # One-click setup $ npm run setup:all # Launch both services $ npm run dev # Frontend → :3000 # Backend → :5001
# .env (from .env.example) LLM_API_KEY=sk-... LLM_BASE_URL=https://... LLM_MODEL_NAME=qwen-plus ZEP_API_KEY=z_...
# Single command
$ docker compose up -d
The docker-compose.yml bundles both services. GitHub Actions CI builds the image on push.
| Service | Notes |
|---|---|
| LLM API | Any OpenAI-compatible endpoint. Recommended: Aliyun qwen-plus |
| Zep Cloud | Free tier available (monthly limits) |
An orchestration layer that chains together:
The key innovation is making this accessible: upload documents, describe what you want to predict, get a report.
agent37.club