767 lines
32 KiB
Plaintext
767 lines
32 KiB
Plaintext
---
|
|
title: MiroFish Swarm Memory
|
|
description: "Build a multi-agent swarm simulation with graph-powered memory using Mem0 and MiroFish patterns."
|
|
---
|
|
|
|
<Snippet file="blank-notif.mdx" />
|
|
|
|
Build a multi-agent swarm simulation with graph-powered memory using Mem0 OSS and [MiroFish](https://github.com/666ghj/MiroFish) patterns. MiroFish is a graph-centric system — it extracts entities and relationships from documents, builds a knowledge graph, and queries it throughout its pipeline. Mem0's Graph Memory is a natural replacement for its Zep Cloud integration.
|
|
|
|
<Note>
|
|
This cookbook demonstrates the **core memory patterns** using a simplified simulation. MiroFish's actual architecture uses a factory pattern (`memory_factory.py`) with abstract providers, batch buffering with retries in `ZepGraphMemoryUpdater`, and IPC-based agent interviews. This cookbook focuses on the Mem0 API integration points — wrap these calls in your own retry/batch logic for production use.
|
|
</Note>
|
|
|
|
## Overview
|
|
|
|
This cookbook implements a **Housing Policy Prediction Simulation** following MiroFish's five-stage workflow:
|
|
|
|
1. **Graph Building** — Ingest seed documents, extract entities and relationships
|
|
2. **Environment Setup** — Query the knowledge graph to enrich agent profiles
|
|
3. **Simulation** — Track agent interactions with per-agent memory isolation
|
|
4. **Report Generation** — Semantic search + graph traversal for analysis
|
|
5. **Deep Interaction** — Query post-simulation memory and relationships (MiroFish also supports live agent interviews via IPC — not covered here)
|
|
|
|
Three agents debate a housing policy reform:
|
|
- **Mayor Chen** — Policy advocate pushing for zoning reform
|
|
- **Wang (Homeowner)** — Opposition leader organizing resistance
|
|
- **Professor Li** — Academic providing data-driven analysis
|
|
|
|
## Prerequisites
|
|
|
|
```bash
|
|
pip install "mem0ai[graph]"
|
|
```
|
|
|
|
You need a graph backend. Choose one:
|
|
|
|
| Backend | Setup | Best for |
|
|
|---|---|---|
|
|
| **Neo4j Aura** (free tier) | [Sign up](https://neo4j.com/product/auradb/), get Bolt URI | Production, closest to Zep |
|
|
| **Neo4j Docker** | `docker run -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:5` | Local development |
|
|
| **Kuzu** (embedded) | No setup needed — runs in-process | Quick testing, zero dependencies |
|
|
|
|
```bash
|
|
export OPENAI_API_KEY="sk-..."
|
|
|
|
# Option A: Neo4j Docker (local development)
|
|
docker run -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:5
|
|
export NEO4J_URL="neo4j://localhost:7687"
|
|
export NEO4J_USERNAME="neo4j"
|
|
export NEO4J_PASSWORD="password"
|
|
|
|
# Option B: Neo4j Aura (production — free tier available)
|
|
export NEO4J_URL="neo4j+s://<your-instance>.databases.neo4j.io"
|
|
export NEO4J_USERNAME="neo4j"
|
|
export NEO4J_PASSWORD="your-aura-password"
|
|
|
|
# Option C: Kuzu (zero setup — auto-detected when NEO4J_URL is not set)
|
|
# No exports needed
|
|
```
|
|
|
|
## Complete Implementation
|
|
|
|
```python
|
|
"""
|
|
MiroFish Swarm Prediction Simulation with Mem0 Graph Memory
|
|
|
|
MiroFish uses Zep Cloud as its knowledge graph backend. This implementation
|
|
replaces Zep with Mem0 OSS Graph Memory, which provides:
|
|
- Automatic entity extraction from text
|
|
- Relationship mining (source → relationship → destination triples)
|
|
- Combined vector + graph search returning memories AND relations
|
|
- Per-agent isolation via run_id
|
|
- Self-hosted with no node caps
|
|
|
|
Follows MiroFish's 5-stage pipeline:
|
|
1. Graph Building - Ingest seed documents, extract entities
|
|
2. Environment Setup - Query graph to enrich agent profiles
|
|
3. Simulation - Track agent actions with per-agent isolation
|
|
4. Report Generation - Semantic + graph search for analysis
|
|
5. Deep Interaction - Query post-simulation knowledge graph
|
|
|
|
Run:
|
|
export OPENAI_API_KEY="sk-..."
|
|
export NEO4J_URL="neo4j://localhost:7687"
|
|
export NEO4J_USERNAME="neo4j"
|
|
export NEO4J_PASSWORD="password"
|
|
python mirofish_swarm_memory.py
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
from mem0 import Memory
|
|
|
|
|
|
# ======================================================================
|
|
# MiroFish Agent Action Types (matches OASIS simulation output)
|
|
# ======================================================================
|
|
|
|
# Twitter actions
|
|
TWITTER_ACTIONS = [
|
|
"CREATE_POST", "LIKE_POST", "REPOST", "FOLLOW",
|
|
"DO_NOTHING", "QUOTE_POST",
|
|
]
|
|
|
|
# Reddit actions (superset — includes moderation + discovery)
|
|
REDDIT_ACTIONS = [
|
|
"LIKE_POST", "DISLIKE_POST", "CREATE_POST", "CREATE_COMMENT",
|
|
"LIKE_COMMENT", "DISLIKE_COMMENT", "SEARCH_POSTS", "SEARCH_USER",
|
|
"TREND", "REFRESH", "DO_NOTHING", "FOLLOW", "MUTE",
|
|
]
|
|
|
|
# Combined (DO_NOTHING is skipped during memory storage)
|
|
MIROFISH_ACTIONS = list(set(TWITTER_ACTIONS + REDDIT_ACTIONS) - {"DO_NOTHING"})
|
|
|
|
|
|
# ======================================================================
|
|
# Graph Memory Configuration
|
|
# ======================================================================
|
|
|
|
def build_config():
|
|
"""Build Mem0 config with Graph Memory.
|
|
|
|
Uses Neo4j if credentials are set, otherwise falls back to Kuzu (embedded).
|
|
"""
|
|
neo4j_url = os.environ.get("NEO4J_URL")
|
|
|
|
# Shared config for LLM, embedder, and vector store
|
|
base = {
|
|
"llm": {
|
|
"provider": "openai",
|
|
"config": {"model": "gpt-4o-mini", "temperature": 0.1}
|
|
},
|
|
"embedder": {
|
|
"provider": "openai",
|
|
"config": {"model": "text-embedding-3-small", "embedding_dims": 1536}
|
|
},
|
|
"vector_store": {
|
|
"provider": "qdrant",
|
|
"config": {
|
|
"collection_name": "mirofish",
|
|
"embedding_model_dims": 1536,
|
|
}
|
|
},
|
|
}
|
|
|
|
custom_prompt = (
|
|
"Extract all people, organizations, policies, locations, "
|
|
"and their relationships. Capture support/opposition stances, "
|
|
"affiliations, and quantitative claims."
|
|
)
|
|
|
|
if neo4j_url:
|
|
base["graph_store"] = {
|
|
"provider": "neo4j",
|
|
"config": {
|
|
"url": neo4j_url,
|
|
"username": os.environ.get("NEO4J_USERNAME", "neo4j"),
|
|
"password": os.environ.get("NEO4J_PASSWORD", "password"),
|
|
},
|
|
"custom_prompt": custom_prompt,
|
|
}
|
|
else:
|
|
# Fallback: Kuzu embedded (no external services needed)
|
|
print(" NEO4J_URL not set — using Kuzu (embedded) graph store")
|
|
base["graph_store"] = {
|
|
"provider": "kuzu",
|
|
"config": {"db": "/tmp/mirofish_graph.kuzu"},
|
|
"custom_prompt": custom_prompt,
|
|
}
|
|
|
|
return base
|
|
|
|
|
|
# ======================================================================
|
|
# Simulation Engine
|
|
# ======================================================================
|
|
|
|
class MiroFishSimulation:
|
|
"""
|
|
Multi-agent simulation with graph-powered memory.
|
|
|
|
Uses Mem0 Graph Memory to replace MiroFish's Zep Cloud integration:
|
|
- Entities and relationships are extracted automatically from text
|
|
- search() returns both semantic memories AND graph relations
|
|
- Per-agent isolation via run_id
|
|
- Project isolation via user_id
|
|
"""
|
|
|
|
def __init__(self, project_id: str, config: dict):
|
|
self.project_id = project_id
|
|
self.memory = Memory.from_config(config)
|
|
self.stats = {
|
|
"documents_ingested": 0,
|
|
"activities_recorded": 0,
|
|
"rounds_completed": 0,
|
|
}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Stage 1: Graph Building — Seed Document Ingestion
|
|
# ------------------------------------------------------------------
|
|
|
|
def ingest_documents(self, documents: list[str]):
|
|
"""Ingest seed documents and extract entities + relationships.
|
|
|
|
MiroFish equivalent: GraphBuilderService.build_graph()
|
|
Zep equivalent: graph.add_batch() with episode polling
|
|
|
|
With Mem0 Graph Memory, each document is processed by the LLM
|
|
to extract entities (people, orgs, policies) and relationships
|
|
(supports, opposes, filed). These become nodes and edges in the
|
|
graph store, alongside vector embeddings for semantic search.
|
|
"""
|
|
print(" Ingesting documents and building knowledge graph...")
|
|
for i, doc in enumerate(documents):
|
|
result = self.memory.add(
|
|
[{"role": "user", "content": doc}],
|
|
user_id=self.project_id,
|
|
metadata={"stage": "graph_building", "source": "seed_document", "chunk_index": i}
|
|
)
|
|
# Graph Memory returns extracted relations
|
|
relations = result.get("relations", {})
|
|
added = relations.get("added_entities", [])
|
|
if added:
|
|
print(f" Doc {i}: extracted {len(added)} entities/relations")
|
|
|
|
self.stats["documents_ingested"] = len(documents)
|
|
print(f" Ingested {len(documents)} documents")
|
|
|
|
# ------------------------------------------------------------------
|
|
# Stage 2: Environment Setup — Agent Profile Enrichment
|
|
# ------------------------------------------------------------------
|
|
|
|
def enrich_agent_profile(self, agent_name: str, persona_query: str) -> dict:
|
|
"""Search memory + graph for context relevant to an agent's persona.
|
|
|
|
MiroFish equivalent: OasisProfileGenerator using graph.search()
|
|
|
|
Returns both semantic memories and graph relations that can be
|
|
injected into the agent's system prompt.
|
|
"""
|
|
results = self.memory.search(
|
|
persona_query,
|
|
user_id=self.project_id,
|
|
limit=10
|
|
)
|
|
facts = [r["memory"] for r in results.get("results", [])]
|
|
relations = results.get("relations", [])
|
|
|
|
print(f" {agent_name}: {len(facts)} facts, {len(relations)} relations")
|
|
return {"facts": facts, "relations": relations}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Stage 3: Simulation — Agent Activity Tracking
|
|
# ------------------------------------------------------------------
|
|
|
|
def record_action(self, agent_id: str, agent_name: str,
|
|
action_type: str, content: str,
|
|
platform: str, round_num: int):
|
|
"""Record a single agent action as a memory with graph extraction.
|
|
|
|
MiroFish equivalent: ZepGraphMemoryUpdater.add_activity()
|
|
Zep equivalent: graph.add(type="text", data=episode_text)
|
|
|
|
Agent memories use run_id to group by agent (no assistant
|
|
memories involved). Graph Memory extracts entities/relationships
|
|
from the action content automatically.
|
|
"""
|
|
formatted = f"{agent_name} [{action_type}]: {content}"
|
|
|
|
self.memory.add(
|
|
[{"role": "user", "content": formatted}],
|
|
run_id=agent_id,
|
|
metadata={
|
|
"action_type": action_type,
|
|
"platform": platform,
|
|
"round": round_num,
|
|
"agent_name": agent_name,
|
|
}
|
|
)
|
|
self.stats["activities_recorded"] += 1
|
|
|
|
def run_round(self, round_num: int, activities: list[tuple]):
|
|
"""Execute one simulation round."""
|
|
print(f" Round {round_num}: {len(activities)} actions")
|
|
for agent_id, agent_name, action_type, content, platform in activities:
|
|
self.record_action(agent_id, agent_name, action_type, content, platform, round_num)
|
|
self.stats["rounds_completed"] = max(self.stats["rounds_completed"], round_num)
|
|
|
|
def recall_agent_memory(self, agent_id: str, query: str) -> dict:
|
|
"""Agent recalls its own memories mid-simulation.
|
|
|
|
Searches by run_id to match the scope used during add().
|
|
"""
|
|
results = self.memory.search(
|
|
query,
|
|
run_id=agent_id,
|
|
limit=5
|
|
)
|
|
return {
|
|
"memories": [r["memory"] for r in results.get("results", [])],
|
|
"relations": results.get("relations", []),
|
|
}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Stage 4: Report Generation — Semantic + Graph Retrieval
|
|
# ------------------------------------------------------------------
|
|
|
|
def quick_search(self, query: str, limit: int = 10) -> dict:
|
|
"""Semantic search + graph relations across all agents.
|
|
|
|
MiroFish equivalent: ZepToolsService.quick_search()
|
|
Returns both vector-matched memories and related graph triples.
|
|
"""
|
|
results = self.memory.search(
|
|
query,
|
|
user_id=self.project_id,
|
|
limit=limit
|
|
)
|
|
return {
|
|
"memories": [r["memory"] for r in results.get("results", [])],
|
|
"relations": results.get("relations", []),
|
|
}
|
|
|
|
def panorama_search(self) -> dict:
|
|
"""Retrieve all memories + all graph relations.
|
|
|
|
MiroFish equivalent: ZepToolsService.panorama_search()
|
|
Returns the complete knowledge state for report generation.
|
|
"""
|
|
results = self.memory.get_all(user_id=self.project_id)
|
|
return {
|
|
"memories": [r["memory"] for r in results.get("results", [])],
|
|
"relations": results.get("relations", []),
|
|
}
|
|
|
|
def agent_search(self, agent_id: str, query: str, limit: int = 10) -> dict:
|
|
"""Search within a single agent's memory space."""
|
|
results = self.memory.search(
|
|
query,
|
|
run_id=agent_id,
|
|
limit=limit
|
|
)
|
|
return {
|
|
"memories": [r["memory"] for r in results.get("results", [])],
|
|
"relations": results.get("relations", []),
|
|
}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Cleanup
|
|
# ------------------------------------------------------------------
|
|
|
|
def cleanup(self):
|
|
"""Delete all memories and graph data for this simulation."""
|
|
self.memory.delete_all(user_id=self.project_id)
|
|
print(f" Cleaned up all memories for {self.project_id}")
|
|
|
|
|
|
# ======================================================================
|
|
# Run the full 5-stage pipeline
|
|
# ======================================================================
|
|
|
|
def main():
|
|
project_id = f"mirofish_housing_{int(time.time())}"
|
|
config = build_config()
|
|
sim = MiroFishSimulation(project_id=project_id, config=config)
|
|
|
|
# ==================================================================
|
|
# STAGE 1: Graph Building — Ingest seed documents
|
|
# ==================================================================
|
|
print("=" * 60)
|
|
print("STAGE 1: Graph Building")
|
|
print("=" * 60)
|
|
|
|
sim.ingest_documents([
|
|
"The city council proposed a new zoning reform allowing higher "
|
|
"density housing in suburban areas. Mayor Chen expressed strong "
|
|
"support, citing a 40% housing shortage affecting young professionals. "
|
|
"The reform would allow buildings up to 8 stories in previously "
|
|
"restricted 3-story zones.",
|
|
|
|
"Local homeowners association president Wang opposes the reform, "
|
|
"arguing it will decrease property values by 15-20%. The association "
|
|
"represents 5,000 homeowners in the affected districts. Wang has "
|
|
"organized three community meetings and collected 2,000 signatures.",
|
|
|
|
"Professor Li from Beijing University published research showing "
|
|
"similar reforms in Shenzhen led to 15% price drops in existing "
|
|
"homes but created 30% more affordable housing units within 3 years. "
|
|
"The study covered 12 districts and 50,000 housing units.",
|
|
])
|
|
|
|
# ==================================================================
|
|
# STAGE 2: Environment Setup — Enrich agent profiles
|
|
# ==================================================================
|
|
print("\n" + "=" * 60)
|
|
print("STAGE 2: Environment Setup")
|
|
print("=" * 60)
|
|
|
|
mayor_context = sim.enrich_agent_profile(
|
|
"Mayor Chen",
|
|
"Mayor Chen housing reform zoning policy"
|
|
)
|
|
wang_context = sim.enrich_agent_profile(
|
|
"Wang",
|
|
"Wang homeowner opposition property values petition"
|
|
)
|
|
li_context = sim.enrich_agent_profile(
|
|
"Professor Li",
|
|
"Professor Li research housing data Shenzhen"
|
|
)
|
|
|
|
print("\n Example profile context for Mayor Chen:")
|
|
for fact in mayor_context["facts"][:3]:
|
|
print(f" Fact: {fact}")
|
|
for rel in mayor_context["relations"][:3]:
|
|
src = rel.get("source", "?")
|
|
edge = rel.get("relationship", "?")
|
|
dst = rel.get("destination", rel.get("target", "?"))
|
|
print(f" Relation: {src} --[{edge}]--> {dst}")
|
|
|
|
# ==================================================================
|
|
# STAGE 3: Simulation — Run agent interactions
|
|
# ==================================================================
|
|
print("\n" + "=" * 60)
|
|
print("STAGE 3: Simulation")
|
|
print("=" * 60)
|
|
|
|
# Round 1: Opening statements
|
|
sim.run_round(1, [
|
|
("mayor_chen", "Mayor Chen", "CREATE_POST",
|
|
"This reform will create 10,000 new housing units by 2028. "
|
|
"Young families deserve affordable homes. #HousingForAll",
|
|
"twitter"),
|
|
|
|
("wang_homeowner", "Wang", "CREATE_POST",
|
|
"Our property values will plummet! The council ignores the "
|
|
"voices of 5,000 homeowners. #StopTheReform",
|
|
"twitter"),
|
|
|
|
("prof_li", "Professor Li", "CREATE_POST",
|
|
"New analysis: Shenzhen zoning data shows net positive outcomes "
|
|
"after 3 years. Short-term pain, long-term gain for housing equity.",
|
|
"twitter"),
|
|
])
|
|
|
|
# Round 2: Debate and interaction
|
|
sim.run_round(2, [
|
|
("wang_homeowner", "Wang", "CREATE_COMMENT",
|
|
"Replied to Professor Li: 'Shenzhen is a tier-1 city with "
|
|
"completely different dynamics. Your comparison is misleading.'",
|
|
"twitter"),
|
|
|
|
("mayor_chen", "Mayor Chen", "LIKE_POST",
|
|
"Liked Professor Li's post about Shenzhen housing data.",
|
|
"twitter"),
|
|
|
|
("prof_li", "Professor Li", "CREATE_COMMENT",
|
|
"Replied to Wang: 'The methodology controls for city tier "
|
|
"and population density. I invite you to review the full dataset.'",
|
|
"twitter"),
|
|
|
|
("mayor_chen", "Mayor Chen", "CREATE_POST",
|
|
"Data from @ProfLi confirms what we've been saying: zoning "
|
|
"reform works. Let's move forward with evidence, not fear.",
|
|
"twitter"),
|
|
])
|
|
|
|
# Round 3: Escalation and platform expansion
|
|
sim.run_round(3, [
|
|
("wang_homeowner", "Wang", "CREATE_POST",
|
|
"Filing formal petition with 3,000 signatures against the "
|
|
"zoning reform. Council meeting next Tuesday. All homeowners "
|
|
"must attend!",
|
|
"reddit"),
|
|
|
|
("mayor_chen", "Mayor Chen", "CREATE_POST",
|
|
"Announcing public town hall on zoning reform this Saturday. "
|
|
"All voices welcome. Data-driven decisions benefit everyone.",
|
|
"twitter"),
|
|
|
|
("prof_li", "Professor Li", "CREATE_POST",
|
|
"Published full dataset and methodology on my university page. "
|
|
"Transparency is essential for informed public debate.",
|
|
"twitter"),
|
|
|
|
("wang_homeowner", "Wang", "FOLLOW",
|
|
"Followed @MayorChen to monitor policy updates.",
|
|
"twitter"),
|
|
])
|
|
|
|
# Mid-simulation: agent recalls own memory + graph
|
|
print("\n Mid-simulation recall for Mayor Chen:")
|
|
mayor_recall = sim.recall_agent_memory(
|
|
"mayor_chen",
|
|
"What positions have I taken on housing reform?"
|
|
)
|
|
for mem in mayor_recall["memories"]:
|
|
print(f" Memory: {mem}")
|
|
for rel in mayor_recall["relations"][:3]:
|
|
src = rel.get("source", "?")
|
|
edge = rel.get("relationship", "?")
|
|
dst = rel.get("destination", rel.get("target", "?"))
|
|
print(f" Relation: {src} --[{edge}]--> {dst}")
|
|
|
|
# ==================================================================
|
|
# STAGE 4: Report Generation — Retrieve memories + graph for analysis
|
|
# ==================================================================
|
|
print("\n" + "=" * 60)
|
|
print("STAGE 4: Report Generation")
|
|
print("=" * 60)
|
|
|
|
# Quick search: targeted query
|
|
print("\n Quick Search: 'opposition to housing reform'")
|
|
opposition = sim.quick_search("opposition to housing reform", limit=5)
|
|
for mem in opposition["memories"]:
|
|
print(f" Memory: {mem}")
|
|
for rel in opposition["relations"][:3]:
|
|
src = rel.get("source", "?")
|
|
edge = rel.get("relationship", "?")
|
|
dst = rel.get("destination", rel.get("target", "?"))
|
|
print(f" Relation: {src} --[{edge}]--> {dst}")
|
|
|
|
# Agent-specific search
|
|
print("\n Agent Search: Wang's activities")
|
|
wang_activities = sim.agent_search("wang_homeowner", "all actions and statements")
|
|
for mem in wang_activities["memories"]:
|
|
print(f" Memory: {mem}")
|
|
|
|
# Panorama: full overview
|
|
print("\n Panorama Search: all memories + relations")
|
|
panorama = sim.panorama_search()
|
|
print(f" Total memories: {len(panorama['memories'])}")
|
|
print(f" Total relations: {len(panorama['relations'])}")
|
|
for mem in panorama["memories"][:5]:
|
|
print(f" Memory: {mem}")
|
|
if len(panorama["memories"]) > 5:
|
|
print(f" ... and {len(panorama['memories']) - 5} more")
|
|
for rel in panorama["relations"][:5]:
|
|
src = rel.get("source", "?")
|
|
edge = rel.get("relationship", "?")
|
|
dst = rel.get("destination", rel.get("target", "?"))
|
|
print(f" Relation: {src} --[{edge}]--> {dst}")
|
|
|
|
# ==================================================================
|
|
# STAGE 5: Deep Interaction — Post-simulation queries
|
|
# ==================================================================
|
|
print("\n" + "=" * 60)
|
|
print("STAGE 5: Deep Interaction")
|
|
print("=" * 60)
|
|
|
|
queries = [
|
|
"How did the debate evolve across the three rounds?",
|
|
"What evidence was cited by each side?",
|
|
"Who supports and who opposes the reform?",
|
|
]
|
|
|
|
for query in queries:
|
|
print(f"\n Query: '{query}'")
|
|
results = sim.quick_search(query, limit=3)
|
|
for mem in results["memories"][:2]:
|
|
print(f" Memory: {mem}")
|
|
for rel in results["relations"][:2]:
|
|
src = rel.get("source", rel.get("source_node", "?"))
|
|
edge = rel.get("relationship", rel.get("relation", "?"))
|
|
dst = rel.get("destination", rel.get("destination_node", "?"))
|
|
print(f" Relation: {src} --[{edge}]--> {dst}")
|
|
|
|
# ==================================================================
|
|
# Summary
|
|
# ==================================================================
|
|
print("\n" + "=" * 60)
|
|
print("SIMULATION COMPLETE")
|
|
print("=" * 60)
|
|
print(f" Project ID: {project_id}")
|
|
print(f" Documents ingested: {sim.stats['documents_ingested']}")
|
|
print(f" Activities tracked: {sim.stats['activities_recorded']}")
|
|
print(f" Rounds completed: {sim.stats['rounds_completed']}")
|
|
print(f" Total memories: {len(panorama['memories'])}")
|
|
print(f" Total relations: {len(panorama['relations'])}")
|
|
|
|
# Cleanup (uncomment to delete all memories + graph data)
|
|
# sim.cleanup()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("MiroFish Swarm Prediction Simulation powered by Mem0 Graph Memory\n")
|
|
main()
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### Graph Memory: The Right Fit for MiroFish
|
|
|
|
MiroFish's entire pipeline revolves around a **knowledge graph** — it extracts entities from documents, builds relationships, and queries the graph throughout simulation and reporting. Mem0's Graph Memory provides the same capabilities:
|
|
|
|
| MiroFish needs | Zep Cloud | Mem0 Graph Memory |
|
|
|---|---|---|
|
|
| **Entity extraction** | Built-in via Zep API | Automatic via LLM extraction |
|
|
| **Relationship mining** | Graph edges | `(source) --[relationship]--> (destination)` triples |
|
|
| **Semantic + keyword search** | Semantic + BM25 | Vector similarity + graph relation retrieval |
|
|
| **Graph traversal** | Node/edge queries | `relations` array in search results |
|
|
| **Per-agent isolation** | Single shared graph in MiroFish | Native `run_id` scoping |
|
|
| **Self-hosting** | No (cloud only) | Yes — Neo4j, Memgraph, Kuzu, Neptune |
|
|
| **Node/memory limits** | Capped on free tier | Unlimited (self-hosted) |
|
|
|
|
### How search() Returns Both Memories and Relations
|
|
|
|
When Graph Memory is enabled, every `search()` call returns two arrays:
|
|
|
|
```python
|
|
results = memory.search("housing reform", user_id="my_sim")
|
|
|
|
# Vector-matched memories (ordered by similarity)
|
|
results["results"] # [{"memory": "...", "score": 0.85, ...}, ...]
|
|
|
|
# Graph relations connected to query entities
|
|
results["relations"] # [{"source": "mayor_chen", "relationship": "supports", "destination": "zoning_reform"}, ...]
|
|
```
|
|
|
|
This is what makes Mem0 Graph Memory a natural replacement for Zep — you get semantic search AND structured graph data in a single call.
|
|
|
|
### Per-Agent Memory Isolation
|
|
|
|
`user_id` scopes the simulation project. `run_id` tags individual agent actions at storage time (we use `run_id` instead of `agent_id` since no assistant memories are involved). Searches use `user_id` for project-wide retrieval:
|
|
|
|
```python
|
|
# Store project-level memories (seed documents)
|
|
memory.add(
|
|
[{"role": "user", "content": "Mayor Chen supports the zoning reform."}],
|
|
user_id="my_sim"
|
|
)
|
|
|
|
# Store agent-specific memories (simulation actions)
|
|
memory.add(
|
|
[{"role": "user", "content": "Mayor Chen [CREATE_POST]: Reform works!"}],
|
|
run_id="mayor_chen"
|
|
)
|
|
|
|
# Search project-level memories (seed docs)
|
|
memory.search("housing reform", user_id="my_sim")
|
|
|
|
# Search agent-specific memories (actions stored with run_id)
|
|
memory.search("housing reform", run_id="mayor_chen")
|
|
|
|
# Get all project-level memories + graph relations
|
|
memory.get_all(user_id="my_sim")
|
|
```
|
|
|
|
<Note>
|
|
Use `user_id` for project-level data (seed documents) and `run_id` for agent actions — both for `add()` and `search()`. Always match the scope: if you `add()` with `run_id`, `search()` with `run_id`. Use the message list format `[{"role": "user", "content": "..."}]` for all `add()` calls — it works on both OSS and Cloud.
|
|
</Note>
|
|
|
|
### Stage Mapping
|
|
|
|
| MiroFish Stage | What Happens | Mem0 Graph Memory Call |
|
|
|---|---|---|
|
|
| **1. Graph Building** | Ingest docs, extract entities | `memory.add(doc, user_id=project)` — entities/relations extracted automatically |
|
|
| **2. Environment Setup** | Enrich agent personas from graph | `memory.search(query, user_id=project)` — returns facts + relations |
|
|
| **3. Simulation** | Track per-agent actions | `memory.add(messages, run_id=agent)` |
|
|
| **3. Simulation** | Mid-round recall | `memory.search(query, run_id=agent)` |
|
|
| **4. Report Generation** | Targeted analysis | `memory.search(query, user_id=project)` — memories + graph |
|
|
| **4. Report Generation** | Full overview | `memory.get_all(user_id=project)` — all memories + all relations |
|
|
| **5. Deep Interaction** | Follow-up queries | `memory.search(query, user_id=project)` |
|
|
|
|
### Zep-to-Mem0 Migration Reference
|
|
|
|
For developers replacing MiroFish's Zep integration. Note that Mem0 Graph Memory covers the core graph operations but some Zep features have no direct equivalent — see caveats below.
|
|
|
|
| MiroFish Service | Zep Call | Mem0 Graph Memory Equivalent | Caveat |
|
|
|---|---|---|---|
|
|
| GraphBuilderService | `client.graph.create()` | Implicit on first `memory.add()` | |
|
|
| GraphBuilderService | `client.graph.set_ontology()` | `custom_prompt` in graph_store config | Freeform text, not a typed schema like Zep's `EntityModel`/`EdgeModel` |
|
|
| GraphBuilderService | `client.graph.add_batch(episodes)` | `memory.add()` per chunk | No batch API — call per chunk |
|
|
| GraphBuilderService | `client.graph.episode.get(uuid)` | Not needed (add is synchronous in OSS) | |
|
|
| GraphBuilderService | `client.graph.delete(id)` | `memory.delete_all(user_id=...)` | |
|
|
| ZepEntityReader | `client.graph.node.get_by_graph_id()` | `memory.get_all(user_id=...)` → `relations` | |
|
|
| ZepEntityReader | `client.graph.node.get(uuid)` | `memory.search(entity_name, user_id=...)` | Semantic search, not exact ID lookup |
|
|
| ZepEntityReader | `client.graph.node.get_entity_edges()` | `memory.search(entity_name, user_id=...)` → `relations` | Returns all matching relations, not edges for a specific node |
|
|
| ZepGraphMemoryUpdater | `client.graph.add(type="text")` | `memory.add(messages, run_id=...)` | No batch buffering or retry — implement in your wrapper |
|
|
| ZepToolsService | `search_graph(query, scope)` | `memory.search(query, user_id=...)` → memories + relations | |
|
|
| ZepToolsService | `get_entities()` | `memory.get_all(user_id=...)` → `relations` | |
|
|
| ZepToolsService | Panorama (all nodes + edges) | `memory.get_all(user_id=...)` | No temporal fact separation (active vs historical) |
|
|
| ZepToolsService | InsightForge (multi-query decomposition) | Not available | Implement LLM-driven sub-query decomposition in your own ReportAgent |
|
|
| OasisProfileGenerator | `client.graph.search()` | `memory.search(query, user_id=...)` | |
|
|
|
|
<Note>
|
|
**What Mem0 Graph Memory does not cover**: Zep's typed ontology schemas (`EntityModel`, `EdgeModel`), temporal fact lifecycle (`valid_at`/`invalid_at`/`expired_at`), single-node-by-ID lookup, and InsightForge's multi-query decomposition. For InsightForge-like functionality, implement sub-query logic in your own ReportAgent using `memory.search()` as the retrieval primitive.
|
|
</Note>
|
|
|
|
### Custom Extraction Prompts
|
|
|
|
Guide what entities and relationships Mem0 extracts — analogous to (but less structured than) Zep's `set_ontology()`:
|
|
|
|
```python
|
|
config = {
|
|
"graph_store": {
|
|
"provider": "neo4j",
|
|
"config": {"url": "...", "username": "...", "password": "..."},
|
|
"custom_prompt": (
|
|
"Extract all people, organizations, policies, locations, "
|
|
"and their relationships. Capture support/opposition stances, "
|
|
"affiliations, and quantitative claims."
|
|
),
|
|
}
|
|
}
|
|
```
|
|
|
|
### Action Types
|
|
|
|
MiroFish's OASIS engine produces these agent action types. Format them as natural language when storing. Skip `DO_NOTHING` actions (no memory value). `TREND` and `REFRESH` are Reddit-only discovery actions — store if you want to track browsing behavior.
|
|
|
|
| Action Type | Platform | Example Memory Content |
|
|
|---|---|---|
|
|
| `CREATE_POST` | Both | `"Mayor Chen [CREATE_POST]: This reform will create 10,000 units"` |
|
|
| `CREATE_COMMENT` | Reddit | `"Wang [CREATE_COMMENT]: Replied to Prof Li: 'Your data is misleading'"` |
|
|
| `LIKE_POST` | Both | `"Mayor Chen [LIKE_POST]: Liked Prof Li's post about Shenzhen data"` |
|
|
| `REPOST` | Twitter | `"Prof Li [REPOST]: Reposted Mayor Chen's town hall announcement"` |
|
|
| `FOLLOW` | Both | `"Wang [FOLLOW]: Followed @MayorChen"` |
|
|
| `QUOTE_POST` | Twitter | `"Mayor Chen [QUOTE_POST]: 'Data confirms reform works' quoting Prof Li"` |
|
|
| `DISLIKE_POST` | Reddit | `"Wang [DISLIKE_POST]: Downvoted Mayor Chen's reform post"` |
|
|
| `TREND` | Reddit | `"Prof Li [TREND]: Browsed trending topics"` |
|
|
| `DO_NOTHING` | Both | Skip — no memory value |
|
|
|
|
## Running the Example
|
|
|
|
```bash
|
|
# Option A: Neo4j (production)
|
|
export OPENAI_API_KEY="sk-..."
|
|
export NEO4J_URL="neo4j://localhost:7687"
|
|
export NEO4J_USERNAME="neo4j"
|
|
export NEO4J_PASSWORD="password"
|
|
python mirofish_swarm_memory.py
|
|
|
|
# Option B: Kuzu (zero dependencies, just need OpenAI key)
|
|
export OPENAI_API_KEY="sk-..."
|
|
python mirofish_swarm_memory.py # auto-detects missing NEO4J_URL, uses Kuzu
|
|
```
|
|
|
|
<Note>
|
|
Exact output varies as Mem0 automatically extracts and deduplicates entities. The specific relations and memory counts depend on LLM extraction quality.
|
|
</Note>
|
|
|
|
## Best Practices
|
|
|
|
1. **Unique `user_id` per simulation** — Use timestamps or UUIDs (e.g., `mirofish_housing_1742198400`) to prevent memory collisions between runs
|
|
2. **Always set `run_id` for agent actions** — Per-agent isolation prevents memory cross-contamination between agents
|
|
3. **Use `custom_prompt`** — Guide entity extraction to capture domain-specific relationships (people, policies, stances)
|
|
4. **Format actions as natural language** — `"Mayor Chen [CREATE_POST]: content"` extracts better entities than raw JSON
|
|
5. **Query relations for reports** — The `relations` array in search results gives structured `(source, relationship, destination)` triples for building analytical reports
|
|
6. **Cleanup old simulations** — Call `delete_all(user_id=...)` when a simulation run is no longer needed
|
|
|
|
## Resources
|
|
|
|
- [MiroFish GitHub](https://github.com/666ghj/MiroFish) — Source code and setup guide
|
|
- [MiroFish Documentation](https://deepwiki.com/666ghj/MiroFish) — Full framework docs
|
|
- [Mem0 Graph Memory](/open-source/features/graph-memory) — Graph Memory documentation
|
|
- [Mem0 Documentation](https://docs.mem0.ai/) — Full API reference
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Graph Memory" icon="network-wired" href="/open-source/features/graph-memory">
|
|
Full Graph Memory documentation with provider setup.
|
|
</Card>
|
|
<Card title="MiroFish GitHub" icon="fish" href="https://github.com/666ghj/MiroFish">
|
|
MiroFish source code and setup guide.
|
|
</Card>
|
|
</CardGroup>
|