
mirofish-offline-simulation
Fully local multi-agent swarm intelligence simulation engine using Neo4j + Ollama for public opinion, market sentiment, and social dynamics prediction.
- Installs
- 1.4K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/reason-machines/trending-skills --skill mirofish-offline-simulation- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads mirofish-offline-simulation automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
MiroFish-Offline Skill
Skill by ara.so — Daily 2026 Skills collection.
MiroFish-Offline is a fully local multi-agent swarm intelligence engine. Feed it any document (press release, policy draft, financial report) and it generates hundreds of AI agents with unique personalities that simulate public reaction on social media — posts, arguments, opinion shifts — hour by hour. No cloud APIs required: Neo4j CE 5.15 handles graph memory, Ollama serves the LLMs.
Architecture Overview
Document Input
│
▼
Graph Build (NER + relationship extraction via Ollama LLM)
│
▼
Neo4j Knowledge Graph (entities, relations, embeddings via nomic-embed-text)
│
▼
Env Setup (generate hundreds of agent personas with personalities + memory)
│
▼
Simulation (agents post, reply, argue, shift opinions on simulated platforms)
│
▼
Report (ReportAgent interviews focus group, queries graph, generates analysis)
│
▼
Interaction (chat with any individual agent, full memory persists)
Backend: Flask + Python 3.11
Frontend: Vue 3 + Node 18
Graph DB: Neo4j CE 5.15 (bolt protocol)
LLM: Ollama (OpenAI-compatible /v1 endpoint)
Embeddings: nomic-embed-text (768-dimensional, via Ollama)
Search: Hybrid — 0.7 × vector similarity + 0.3 × BM25
Installation
Option A: Docker (Recommended)
git clone https://github.com/nikmcfly/MiroFish-Offline.git
cd MiroFish-Offline
cp .env.example .env
# Start Neo4j + Ollama + MiroFish backend + frontend
docker compose up -d
# Pull required models into the Ollama container
docker exec mirofish-ollama ollama pull qwen2.5:32b
docker exec mirofish-ollama ollama pull nomic-embed-text
# Check all services are healthy
docker compose ps
Open http://localhost:3000.
Option B: Manual Setup
1. Neo4j
docker run -d --name neo4j \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/mirofish \
neo4j:5.15-community
2. Ollama
ollama serve &
ollama pull qwen2.5:32b # Main LLM (~20GB, requires 24GB VRAM)
ollama pull qwen2.5:14b # Lighter option (~10GB VRAM)
ollama pull nomic-embed-text # Embeddings (small, fast)
3. Backend
cp .env.example .env
# Edit .env (see Configuration section)
cd backend
pip install -r requirements.txt
python run.py
# Backend starts on http://localhost:5000
4. Frontend
cd frontend
npm install
npm run dev
# Frontend starts on http://localhost:3000
Configuration (.env)
# ── LLM (Ollama OpenAI-compatible endpoint) ──────────────────────────
LLM_API_KEY=ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL_NAME=qwen2.5:32b
# ── Neo4j ─────────────────────────────────────────────────────────────
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=mirofish
# ── Embeddings (Ollama) ───────────────────────────────────────────────
EMBEDDING_MODEL=nomic-embed-text
EMBEDDING_BASE_URL=http://localhost:11434
# ── Optional: swap Ollama for any OpenAI-compatible provider ─────────
# LLM_API_KEY=$OPENAI_API_KEY
# LLM_BASE_URL=https://api.openai.com/v1
# LLM_MODEL_NAME=gpt-4o
Core Python API
GraphStorage Interface
The abstraction layer between MiroFish and the graph database:
from backend.storage.base import GraphStorage
from backend.storage.neo4j_storage import Neo4jStorage
# Initialize storage (typically done via Flask app.extensions)
storage = Neo4jStorage(
uri=os.environ["NEO4J_URI"],
user=os.environ["NEO4J_USER"],
password=os.environ["NEO4J_PASSWORD"],
embedding_model=os.environ["EMBEDDING_MODEL"],
embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
llm_base_url=os.environ["LLM_BASE_URL"],
llm_api_key=os.environ["LLM_API_KEY"],
llm_model=os.environ["LLM_MODEL_NAME"],
)
Building a Knowledge Graph from a Document
from backend.services.graph_builder import GraphBuilder
builder = GraphBuilder(storage=storage)
# Feed a document string
with open("press_release.txt", "r") as f:
document_text = f.read()
# Extract entities + relationships, store in Neo4j
graph_id = builder.build(
content=document_text,
title="Q4 Earnings Report",
source_type="financial_report",
)
print(f"Graph built: {graph_id}")
# Returns a graph_id used for subsequent simulation runs
Creating and Running a Simulation
from backend.services.simulation import SimulationService
sim = SimulationService(storage=storage)
# Create a simulation environment from an existing graph
sim_id = sim.create_environment(
graph_id=graph_id,
agent_count=200, # Number of agents to generate
simulation_hours=24, # Simulated time span
platform="twitter", # "twitter" | "reddit" | "weibo"
)
# Run the simulation (blocking — use async wrapper for production)
result = sim.run(sim_id=sim_id)
print(f"Simulation complete. Posts generated: {result['post_count']}")
print(f"Sentiment trajectory: {result['sentiment_over_time']}")
Querying Simulation Results
from backend.services.report import ReportAgent
report_agent = ReportAgent(storage=storage)
# Generate a structured analysis report
report = report_agent.generate(
sim_id=sim_id,
focus_group_size=10, # Number of agents to interview
include_graph_search=True,
)
print(report["summary"])
print(report["key_narratives"])
print(report["sentiment_shift"])
print(report["influential_agents"])
Chatting with a Simulated Agent
from backend.services.agent_chat import AgentChatService
chat = AgentChatService(storage=storage)
# List agents from a completed simulation
agents = chat.list_agents(sim_id=sim_id, limit=10)
agent_id = agents[0]["id"]
print(f"Chatting with: {agents[0]['persona']['name']}")
print(f"Personality: {agents[0]['persona']['traits']}")
# Send a message — agent responds in-character with full memory
response = chat.send(
agent_id=agent_id,
message="Why did you post that criticism about the earnings report?",
)
print(response["reply"])
# → Agent responds using its personality, opinion bias, and post history
Hybrid Search on the Knowledge Graph
from backend.services.search import SearchService
search = SearchService(storage=storage)
# Hybrid search: 0.7 * vector similarity + 0.3 * BM25
results = search.query(
text="executive compensation controversy",
graph_id=graph_id,
top_k=5,
vector_weight=0.7,
bm25_weight=0.3,
)
for r in results:
print(r["entity"], r["relationship"], r["score"])
Implementing a Custom GraphStorage Backend
from backend.storage.base import GraphStorage
from typing import List, Dict, Any
class MyCustomStorage(GraphStorage):
"""
Swap Neo4j for any graph DB by implementing this interface.
Register via Flask app.extensions['neo4j_storage'] = MyCustomStorage(...)
"""
def store_entity(self, entity: Dict[str, Any]) -> str:
# Store entity, return entity_id
raise NotImplementedError
def store_relationship(
self,
source_id: str,
target_id: str,
relation_type: str,
properties: Dict[str, Any],
) -> str:
raise NotImplementedError
def vector_search(
self, embedding: List[float], top_k: int = 5
) -> List[Dict[str, Any]]:
raise NotImplementedError
def keyword_search(
self, query: str, top_k: int = 5
) -> List[Dict[str, Any]]:
raise NotImplementedError
def get_agent_memory(self, agent_id: str) -> Dict[str, Any]:
raise NotImplementedError
def update_agent_memory(
self, agent_id: str, memory_update: Dict[str, Any]
) -> None:
raise NotImplementedError
Flask App Integration Pattern
# backend/app.py — how storage is wired via dependency injection
from flask import Flask
from backend.storage.neo4j_storage import Neo4jStorage
import os
def create_app():
app = Flask(__name__)
# Single storage instance, injected everywhere via app.extensions
storage = Neo4jStorage(
uri=os.environ["NEO4J_URI"],
user=os.environ["NEO4J_USER"],
password=os.environ["NEO4J_PASSWORD"],
embedding_model=os.environ["EMBEDDING_MODEL"],
embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
llm_base_url=os.environ["LLM_BASE_URL"],
llm_api_key=os.environ["LLM_API_KEY"],
llm_model=os.environ["LLM_MODEL_NAME"],
)
app.extensions["neo4j_storage"] = storage
from backend.routes import graph_bp, simulation_bp, report_bp
app.register_blueprint(graph_bp)
app.register_blueprint(simulation_bp)
app.register_blueprint(report_bp)
return app
Accessing Storage in a Flask Route
from flask import Blueprint, current_app, request, jsonify
simulation_bp = Blueprint("simulation", __name__)
@simulation_bp.route("/api/simulation/run", methods=["POST"])
def run_simulation():
storage = current_app.extensions["neo4j_storage"]
data = request.json
sim = SimulationService(storage=storage)
sim_id = sim.create_environment(
graph_id=data["graph_id"],
agent_count=data.get("agent_count", 200),
simulation_hours=data.get("simulation_hours", 24),
)
result = sim.run(sim_id=sim_id)
return jsonify(result)
REST API Reference
| Method | Endpoint | Description |
|---|---|---|
POST | /api/graph/build | Upload document, build knowledge graph |
GET | /api/graph/:id | Get graph entities and relationships |
POST | /api/simulation/create | Create simulation environment |
POST | /api/simulation/run | Execute simulation |
GET | /api/simulation/:id/results | Get posts, sentiment, metrics |
GET | /api/simulation/:id/agents | List generated agents |
POST | /api/report/generate | Generate ReportAgent analysis |
POST | /api/agent/:id/chat | Chat with a specific agent |
GET | /api/search | Hybrid search the knowledge graph |
Example: Build graph from document
curl -X POST http://localhost:5000/api/graph/build \
-H "Content-Type: application/json" \
-d '{
"content": "Acme Corp announces record Q4 earnings, CFO resigns...",
"title": "Q4 Press Release",
"source_type": "press_release"
}'
# → {"graph_id": "g_abc123", "entities": 47, "relationships": 89}
Example: Run a simulation
curl -X POST http://localhost:5000/api/simulation/run \
-H "Content-Type: application/json" \
-d '{
"graph_id": "g_abc123",
"agent_count": 150,
"simulation_hours": 12,
"platform": "twitter"
}'
# → {"sim_id": "s_xyz789", "status": "running"}
Hardware Selection Guide
| Use Case | Model | VRAM | RAM |
|---|---|---|---|
| Quick test / dev | qwen2.5:7b | 6 GB | 16 GB |
| Balanced quality | qwen2.5:14b | 10 GB | 16 GB |
| Production quality | qwen2.5:32b | 24 GB | 32 GB |
| CPU-only (slow) | qwen2.5:7b | None | 16 GB |
Switch model by editing .env:
LLM_MODEL_NAME=qwen2.5:14b
Then restart the backend — no other changes needed.
Common Patterns
PR Crisis Test Pipeline
import os
from backend.storage.neo4j_storage import Neo4jStorage
from backend.services.graph_builder import GraphBuilder
from backend.services.simulation import SimulationService
from backend.services.report import ReportAgent
storage = Neo4jStorage(
uri=os.environ["NEO4J_URI"],
user=os.environ["NEO4J_USER"],
password=os.environ["NEO4J_PASSWORD"],
embedding_model=os.environ["EMBEDDING_MODEL"],
embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
llm_base_url=os.environ["LLM_BASE_URL"],
llm_api_key=os.environ["LLM_API_KEY"],
llm_model=os.environ["LLM_MODEL_NAME"],
)
def test_press_release(text: str) -> dict:
# 1. Build knowledge graph
builder = GraphBuilder(storage=storage)
graph_id = builder.build(content=text, title="Draft PR", source_type="press_release")
# 2. Simulate public reaction
sim = SimulationService(storage=storage)
sim_id = sim.create_environment(graph_id=graph_id, agent_count=300, simulation_hours=48)
sim.run(sim_id=sim_id)
# 3. Generate report
report = ReportAgent(storage=storage).generate(sim_id=sim_id, focus_group_size=15)
return {
"sentiment_peak": report["sentiment_over_time"][0],
"key_narratives": report["key_narratives"],
"risk_score": report["risk_score"],
"recommended_edits": report["recommendations"],
}
# Usage
with open("draft_announcement.txt") as f:
result = test_press_release(f.read())
print(f"Risk score: {result['risk_score']}/10")
print(f"Top narrative: {result['key_narratives'][0]}")
Use Any OpenAI-Compatible Provider
# Claude via Anthropic (or any proxy)
LLM_API_KEY=$ANTHROPIC_API_KEY
LLM_BASE_URL=https://api.anthropic.com/v1
LLM_MODEL_NAME=claude-3-5-sonnet-20241022
# OpenAI
LLM_API_KEY=$OPENAI_API_KEY
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL_NAME=gpt-4o
# Local LM Studio
LLM_API_KEY=lm-studio
LLM_BASE_URL=http://localhost:1234/v1
LLM_MODEL_NAME=your-loaded-model
Troubleshooting
Neo4j connection refused
# Check Neo4j is running
docker ps | grep neo4j
# Check bolt port
nc -zv localhost 7687
# View Neo4j logs
docker logs neo4j --tail 50
Ollama model not found
# List available models
ollama list
# Pull missing models
ollama pull qwen2.5:32b
ollama pull nomic-embed-text
# Check Ollama is serving
curl http://localhost:11434/api/tags
Out of VRAM
# Switch to smaller model in .env
LLM_MODEL_NAME=qwen2.5:14b # or qwen2.5:7b
# Restart backend
cd backend && python run.py
Embeddings dimension mismatch
# nomic-embed-text produces 768-dim vectors
# If you switch embedding models, drop and recreate the Neo4j vector index:
# In Neo4j browser (http://localhost:7474):
# DROP INDEX entity_embedding IF EXISTS;
# Then restart MiroFish — it recreates the index with correct dimensions.
Docker Compose: Ollama container can't access GPU
# docker-compose.yml — add GPU reservation:
services:
ollama:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
Slow simulation on CPU
- Use
qwen2.5:7bfor faster (lower quality) inference - Reduce
agent_countto 50–100 for testing - Reduce
simulation_hoursto 6–12 - CPU inference with 7b model: expect ~5–10 tokens/sec
Frontend can't reach backend
# Check VITE_API_BASE_URL in frontend/.env
VITE_API_BASE_URL=http://localhost:5000
# Verify backend is up
curl http://localhost:5000/api/health
Project Structure
MiroFish-Offline/
├── backend/
│ ├── run.py # Entry point
│ ├── app.py # Flask factory, DI wiring
│ ├── storage/
│ │ ├── base.py # GraphStorage abstract interface
│ │ └── neo4j_storage.py # Neo4j implementation
│ ├── services/
│ │ ├── graph_builder.py # NER + relationship extraction
│ │ ├── simulation.py # Agent simulation engine
│ │ ├── report.py # ReportAgent + focus group
│ │ ├── agent_chat.py # Per-agent chat interface
│ │ └── search.py # Hybrid vector + BM25 search
│ └── routes/
│ ├── graph.py
│ ├── simulation.py
│ └── report.py
├── frontend/ # Vue 3 (fully English UI)
├── docker-compose.yml
├── .env.example
└── README.md
FAQ
What does the mirofish-offline-simulation skill do?
Fully local multi-agent swarm intelligence simulation engine using Neo4j + Ollama for public opinion, market sentiment, and social dynamics prediction.
How do I install mirofish-offline-simulation?
Run npx skills add https://github.com/reason-machines/trending-skills --skill mirofish-offline-simulation in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/mirofish-offline-simulation/ (personal) or .claude/skills/mirofish-offline-simulation/ (project).
Which AI agents can use mirofish-offline-simulation?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is mirofish-offline-simulation safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains mirofish-offline-simulation?
It is published by reason-machines in reason-machines/trending-skills. It has 1,362 recorded installs.
More from reason-machines/trending-skills
openclaw-control-center
reason-machines/trending-skills
Local-first, security-first control center for OpenClaw agents — visibility dashboard with readonly defaults, token attribution, collaboration tracing, and safe write operations.
inkos-multi-agent-novel-writing
reason-machines/trending-skills
Multi-agent CLI system for autonomous novel writing, auditing, and revision with human review gates
claude-hud-statusline
reason-machines/trending-skills
A Claude Code plugin that displays a real-time HUD showing context usage, active tools, running agents, and todo progress in your terminal statusline.
understand-anything-knowledge-graph
reason-machines/trending-skills
Turn any codebase into an interactive knowledge graph using Claude Code skills — explore, search, and ask questions about any project visually.
agency-agents-ai-specialists
reason-machines/trending-skills
A collection of specialized AI agent personalities for Claude Code, Cursor, Aider, Windsurf, and other AI coding tools — covering engineering, design, marketing, sales, and more.
agent-browser-automation
reason-machines/trending-skills
Headless browser automation CLI for AI agents using native Rust binary with Chrome DevTools Protocol
Popular ai & agents skills
google-agents-cli-publish
google/agents-cli
This skill should be used when the user wants to "publish an agent", "publish my ADK agent", "register an agent with Gemini", "publish to Gemini" (formerly Gemini Enterprise), or needs guidance on the agents-cli publish gemini command. Also use when the user wants to "manage agents in Agent Registry", "list/update/delete registered agents", or "register an MCP server". Covers ADK vs A2A registration modes, programmatic and interactive usage, flag reference, auto-detection from deployment metadata, Agent Registry fleet management, and troubleshooting. Part of the agents-cli skills suite. Do NOT use for deployment (use google-agents-cli-deploy).
google-agents-cli-eval
google/agents-cli
This skill should be used when the user wants to "run an evaluation", "evaluate my agent", "evaluate my ADK agent", "write an eval dataset", "analyze eval failures", "compare eval results", "optimize agent", or needs guidance on the Gemini platform eval methodology and the Quality Flywheel. Covers eval metrics, dataset schema, LLM-as-judge scoring, and common failure causes. Applies to any agents-cli project, whatever framework the agent is written in. Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), deployment (use google-agents-cli-deploy), or project scaffolding (use google-agents-cli-scaffold).
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
codex-pet
prime-skills/runcomfy-agent-skills
Codex Pet generator on RunComfy. Build a Codex-compatible Codex Pet spritesheet.webp + pet.json from a single reference image, drop it into ${CODEXHOME:-$HOME/.codex}/pets/ / and Codex picks it up as a custom Codex Pet next to the 8 built-ins. This skill produces the exact Codex Pet atlas Codex expects (1536x1872 PNG/WebP, 8 cols x 9 rows, 192x208 cells, 9 animation states — idle, running-right, running-left, waving, jumping, failed, waiting, running, review). Calls OpenAI GPT Image 2 edit ONCE via the local RunComfy CLI as runcomfy run openai/gpt-image-2/edit to produce a canonical Codex Pet pose, then assembles all 9 animation rows programmatically with ImageMagick micro-transforms — no Codex Pro, no $imagegen, no OPENAIAPIKEY required, only RUNCOMFYTOKEN. Triggers on "codex pet", "create codex pet", "make codex pet", "hatch codex pet", "/hatch image", "desktop pet codex", "codex pets", "spritesheet.webp", or any explicit ask to build a custom pet for OpenAI Codex.
full-output-enforcement
leonxlnx/taste-skill
Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.
executing-plans
obra/superpowers
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available