@ :~/lab/mirofish-swarm-intelligence-simulazione-agenti$
← cd ~/lab

./run mirofish-swarm-intelligence-simulazione-agenti

MiroFish: ho dato 80 capitoli a mille agenti per vedere come finivano il romanzo

Esplorazione di MiroFish, motore open-source di swarm intelligence: mille agenti che twittano il futuro. Come funziona il loop OASIS + GraphRAG + Zep, e cosa serve per farlo girare in casa.

status
draft
project
mirofish
updated
2026-07-07
author
terzi · esplorazione
source
github.com/666ghj/MiroFish
tags
#swarm intelligence#oasis camel-ai#graphrag#zep memory#multi-agent simulation#vllm on-prem
generate cover # alt: Grafo di conoscenza al centro circondato da centinaia di agenti-persona che pubblicano post; frecce che riportano ogni azione dentro il grafo, formando un ciclo chiuso di memoria e comportamento

Il romanzo Il sogno della camera rossa si interrompe: gli ultimi capitoli sono andati perduti, e per due secoli si è discusso come dovesse finire. La demo di punta di MiroFish — un progetto open-source di Shanda Group (666ghj/MiroFish), non mio — prende i primi 80 capitoli, centinaia di migliaia di parole, e chiede a uno sciame di agenti di scrivere il finale. Non generando testo alla maniera di un LLM che completa una frase: trasformando i personaggi in utenti di un social network e lasciandoli agire finché una storia emerge. Sono andato nel codice per capire come una manciata di post e like diventi un epilogo letterario. Questo è il diario di quell’esplorazione — con il codice altrui alla mano.

L’idea: la previsione che non predice

MiroFish si vende come “motore di swarm intelligence che predice qualsiasi cosa”: gli dai un seme (una notizia, una bozza di policy, un romanzo) e ti costruisce un mondo digitale parallelo dove migliaia di agenti con personalità e memoria interagiscono, mentre tu inietti variabili “dall’alto” come un dio. La scoperta controintuitiva, leggendo il codice, è che non c’è nessun forecaster. Non c’è un modello che stima P(finale). C’è una simulazione sociale: i personaggi diventano account, postano, si seguono, si mettono like, e il “finale” è l’artefatto emergente di quelle interazioni. La previsione è la storia che il mondo si racconta da solo.

Il cuore tecnico è OASIS (Open Agent Social Interaction Simulations) del team CAMEL-AI. Le versioni pinnate nel pyproject.toml sono concrete: camel-oasis==0.2.5, camel-ai==0.2.78, backend Flask 3, frontend Vue 3, Python 3.11–3.12. La memoria e il GraphRAG girano su Zep Cloud (zep-cloud==3.13.0), e l’inferenza di default su Qwen-plus di Alibaba via API OpenAI-compatibile.

La pipeline: da testo a sciame in cinque passi

  1. Ontologia — un LLM legge il testo-seme e progetta lo schema: quali tipi di entità (Personaggio, Fazione, Luogo…) e quali tipi di relazione esistono in questo mondo. Niente ontologia hard-coded: la disegna il modello per ogni corpus.
  2. Costruzione del grafo — il testo viene spezzato in chunk e dato in pasto a Zep, che estrae entità e relazioni in un knowledge graph temporale (gli edge hanno valid_at / invalid_at / expired_at: i fatti nascono e muoiono nel tempo).
  3. Lettura entità — si filtrano i nodi per i tipi definiti nell’ontologia.
  4. Generazione delle persona — ogni entità-personaggio diventa un profilo agente: bio, persona, age, gender, mbti, profession, interested_topics. Zep viene interrogato una seconda volta per arricchire il nodo prima di scrivere il carattere.
  5. Simulazione — OASIS costruisce l’agent_graph e gira.

Il loop principale, ripulito ma fedele, è sorprendentemente scarno:

self.env = oasis.make(
    agent_graph=self.agent_graph,
    platform=oasis.DefaultPlatformType.TWITTER,
    database_path=db_path,
    semaphore=30,  # max richieste LLM concorrenti, per non affogare l'API
)
await self.env.reset()

for round_num in range(total_rounds):
    active_agents = self._get_active_agents_for_round(env, simulated_hour, round_num)
    actions = {agent: LLMAction() for _, agent in active_agents}
    await self.env.step(actions)

LLMAction() è la delega totale: per ogni agente attivo, il modello decide da solo cosa fare tra CREATE_POST, LIKE_POST, REPOST, QUOTE_POST, FOLLOW, DO_NOTHING. Non tutti agiscono ad ogni round: chi è attivo dipende da un orologio simulato. La config di default copre 72 ore in round da 30 minuti = 144 round, con moltiplicatori di attività per fascia oraria (×1.5 nelle ore di punta, ×0.3 di notte) e una probabilità per-agente (activity_level). Il seme è un initial_post: l’evento che accende la miccia. Su Reddit c’è anche il commento; è previsto pure il parallelo dual-platform Twitter+Reddit via subprocess.

La spina: il loop che chiude su sé stesso

Qui sta il pezzo che rende MiroFish più di un generatore di chiacchiere. Ogni azione che un agente compie viene ritradotta in linguaggio naturale e reiniettata nel grafo di memoria mentre la simulazione gira. Il metodo che fa la traduzione è banale e per questo geniale:

def to_episode_text(self) -> str:
    describe_func = action_descriptions.get(self.action_type, self._describe_generic)
    description = describe_func()
    # "nome_agente: descrizione dell'azione", senza prefissi da simulazione
    return f"{self.agent_name}: {description}"

Un like non è un contatore che sale: diventa la frase “Tizio: ha messo like al post di Caio «…»”. Queste frasi vengono bufferizzate per piattaforma (BATCH_SIZE = 5) e spinte in un grafo via graph.add() da un thread di background con una coda — così la simulazione non si blocca aspettando la rete. Il risultato è un anello chiuso: gli agenti agiscono → le azioni diventano fatti nel grafo → il grafo è la memoria che gli agenti leggono al round dopo → nuove azioni. Il mondo scrive la propria storia e la rilegge in tempo reale. Il “finale” del Sogno della camera rossa non è predetto: sedimenta in questo grafo, round dopo round, e alla fine un ReportAgent in stile ReACT lo interroga con degli strumenti di ricerca semantica e ne cava un report leggibile. Puoi anche fermarti e intervistare un singolo agente (azione INTERVIEW, iniettata via IPC): il tuo “punto di vista da dio”.

Il gotcha onesto: non è privato, e farlo diventare tale fa male

La mia seconda scoperta è meno poetica. MiroFish, così com’è, telefona a casa: la memoria vive su Zep Cloud (SaaS), l’inferenza su Alibaba Bailian. Per un corpus riservato — l’uso “serio” che il progetto stesso rivendica, testare policy e PR a rischio zero — è un no. Quindi ho mappato cosa servirebbe a portarlo interamente sul proprio ferro, e i bordi taglienti sono istruttivi:

  • Zep → Graphiti + FalkorDB. Zep è il SaaS costruito sopra Graphiti (OSS, Apache-2.0); FalkorDB è il grafo con protocollo Redis. Ho contato 13 metodi SDK Zep usati in 9 file — graph.search(), node.get_by_graph_id(), graph.add()… — da replicare dietro un adapter con interfaccia identica, così il codice chiamante non cambia.
  • L’inferno async/sync. Graphiti è interamente async; Flask è sincrono e l’updater della memoria usa già thread+code. Un ingenuo asyncio.run() per chiamata distrugge le connessioni persistenti a FalkorDB. Serve un unico event-loop di sfondo e run_coroutine_threadsafe(). nest_asyncio maschera il problema e basta.
  • Il killer silenzioso. Graphiti estrae entità chiamando un LLM che deve restituire JSON valido. Se vLLM parte senza guided decoding (--guided-decoding-backend), l’estrazione “riesce” senza errori ma il grafo resta vuoto: zero nodi, simulazioni spazzatura, e te ne accorgi solo alla fine. Smoke test obbligatorio: dopo l’ingest, conta i nodi.
  • La matematica della VRAM. Qwen2.5-72B in FP16 vuole ~144 GB; una H100 ne ha 80. Non ci sta. O AWQ (~40 GB) o tensor-parallel su due schede.

Come va (stato reale)

Sono onesto: non ho fatto girare i 144 round completi. Il README lo dice esplicitamente — “alto consumo, prova prima con meno di 40 round” — e senza una chiave LLM e un account Zep la simulazione non parte. Ho letto il codice a fondo, tracciato il loop, e progettato la migrazione on-prem; l’adapter Graphiti+FalkorDB è disegnato, non costruito. Il progetto a monte è vivo e reale (è su Trendshift, ha una demo pubblica); la mia esplorazione è a livello di architettura e di “cosa succederebbe se”. Il salto tra “documentato” e “girato in casa” resta tutto lì, in quei quattro bordi taglienti sopra.

Cosa ho imparato

Che la parola “previsione” qui è un cavallo di Troia. MiroFish non stima un futuro: ne coltiva uno, e la qualità del risultato dipende meno dal modello e più dal loop — dalla fedeltà con cui ogni azione micro torna a essere memoria condivisa. Il pezzo di codice più importante non è l’LLM: sono quelle cinque righe che trasformano un like in una frase e la rimettono nel grafo. E ho imparato, di nuovo, che “open-source” e “self-hosted” non sono sinonimi: un progetto può essere tutto su GitHub e restare, di fatto, un client di due cloud. La distanza tra i due si misura in adapter, event-loop e VRAM — non in licenze.

Progetto altrui, pubblico: github.com/666ghj/MiroFish · motore di simulazione: OASIS / CAMEL-AI. Io ci ho solo guardato dentro.