| name | mirofish-offline-simulation |
| description | Fully local multi-agent swarm intelligence simulation engine using Neo4j + Ollama for public opinion, market sentiment, and social dynamics prediction. |
| triggers | ["set up mirofish offline simulation","run multi-agent social simulation locally","simulate public reaction to a document","configure neo4j ollama agent simulation","mirofish offline install and configure","generate swarm intelligence prediction","simulate market sentiment with local llm","add mirofish agent simulation to my project"] |
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
docker compose up -d
docker exec mirofish-ollama ollama pull qwen2.5:32b
docker exec mirofish-ollama ollama pull nomic-embed-text
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
ollama pull qwen2.5:14b
ollama pull nomic-embed-text
3. Backend
cp .env.example .env
cd backend
pip install -r requirements.txt
python run.py
4. Frontend
cd frontend
npm install
npm run dev
Configuration (.env)
LLM_API_KEY=ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL_NAME=qwen2.5:32b
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=mirofish
EMBEDDING_MODEL=nomic-embed-text
EMBEDDING_BASE_URL=http://localhost:11434
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
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)
with open("press_release.txt", "r") as f:
document_text = f.read()
graph_id = builder.build(
content=document_text,
title="Q4 Earnings Report",
source_type="financial_report",
)
print(f"Graph built: {graph_id}")
Creating and Running a Simulation
from backend.services.simulation import SimulationService
sim = SimulationService(storage=storage)
sim_id = sim.create_environment(
graph_id=graph_id,
agent_count=200,
simulation_hours=24,
platform="twitter",
)
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)
report = report_agent.generate(
sim_id=sim_id,
focus_group_size=10,
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)
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']}")
response = chat.send(
agent_id=agent_id,
message="Why did you post that criticism about the earnings report?",
)
print(response["reply"])
Hybrid Search on the Knowledge Graph
from backend.services.search import SearchService
search = SearchService(storage=storage)
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:
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
) -> [[, ]]:
NotImplementedError
() -> [, ]:
NotImplementedError
() -> :
NotImplementedError
Flask App Integration Pattern
from flask import Flask
from backend.storage.neo4j_storage import Neo4jStorage
import os
def create_app():
app = Flask(__name__)
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"
}'
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"
}'
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:
builder = GraphBuilder(storage=storage)
graph_id = builder.build(content=text, title="Draft PR", source_type="press_release")
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)
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[],
}
() f:
result = test_press_release(f.read())
()
()
Use Any OpenAI-Compatible Provider
LLM_API_KEY=$ANTHROPIC_API_KEY
LLM_BASE_URL=https://api.anthropic.com/v1
LLM_MODEL_NAME=claude-3-5-sonnet-20241022
LLM_API_KEY=$OPENAI_API_KEY
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL_NAME=gpt-4o
LLM_API_KEY=lm-studio
LLM_BASE_URL=http://localhost:1234/v1
LLM_MODEL_NAME=your-loaded-model
Troubleshooting
Neo4j connection refused
docker ps | grep neo4j
nc -zv localhost 7687
docker logs neo4j --tail 50
Ollama model not found
ollama list
ollama pull qwen2.5:32b
ollama pull nomic-embed-text
curl http://localhost:11434/api/tags
Out of VRAM
LLM_MODEL_NAME=qwen2.5:14b
cd backend && python run.py
Embeddings dimension mismatch
Docker Compose: Ollama container can't access GPU
services:
ollama:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
Slow simulation on CPU
- Use
qwen2.5:7b for faster (lower quality) inference
- Reduce
agent_count to 50–100 for testing
- Reduce
simulation_hours to 6–12
- CPU inference with 7b model: expect ~5–10 tokens/sec
Frontend can't reach backend
VITE_API_BASE_URL=http://localhost:5000
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