0Pricing
AI Engineering Academy · Lezione

Memoria condivisa e comunicazione tra agenti

Implementi un livello di memoria condivisa usando un archivio chiave-valore da cui gli agenti leggono e in cui scrivono, consentendo la collaborazione asincrona senza uno stretto accoppiamento tra gli agenti.

Memoria condivisa e comunicazione tra agenti è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Engineering Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Engineering Academy include 4 lezioni in totale.

Il problema dell'isolamento degli agenti

In un sistema multi-agente, ogni agente opera nel proprio contesto e non sa che cosa gli altri agenti stanno facendo o hanno fatto. Se un agente ricercatore scopre un fatto importante, come può venirne a conoscenza l'agente scrittore? Come può un agente di monitoraggio sapere quando l'agente programmatore riscontra un errore? Senza un meccanismo di comunicazione condiviso, gli agenti sono silos isolati che non possono collaborare efficacemente.

Memoria condivisa: il modello della lavagna

La soluzione classica per la comunicazione tra agenti è il modello della lavagna: un archivio dati condiviso (la lavagna) da cui qualsiasi agente può leggere o in cui può scrivere. Gli agenti pubblicano i propri risultati, leggono i contributi altrui e si coordinano implicitamente attraverso lo stato condiviso. Questo modello disaccoppia gli agenti: non devono conoscere l'esistenza reciproca, ma solo la struttura della memoria condivisa.

# Simple in-memory blackboard using a dictionary
from threading import Lock

class Blackboard:
    def __init__(self):
        self._data = {}
        self._lock = Lock()  # thread-safe for parallel agents

    def write(self, key: str, value, agent_id: str):
        with self._lock:
            self._data[key] = {'value': value, 'written_by': agent_id}
            print(f'[{agent_id}] wrote: {key}')

    def read(self, key: str):
        with self._lock:
            return self._data.get(key, {}).get('value')

    def keys(self):
        with self._lock:
            return list(self._data.keys())

blackboard = Blackboard()

Memoria condivisa persistente con Redis

Per i sistemi multi-agente in produzione, in cui gli agenti vengono eseguiti come processi o servizi separati, i dizionari in memoria non sono sufficienti. Redis è la scelta più diffusa per la memoria condivisa degli agenti: è veloce, supporta tipi di dati articolati (stringhe, hash, liste, insiemi ordinati), offre un TTL integrato per la scadenza e gestisce in modo sicuro le letture e le scritture concorrenti tramite operazioni atomiche.

import redis
import json

class RedisSharedMemory:
    def __init__(self, prefix='agent:'):
        self.redis = redis.Redis(host='localhost', port=6379, decode_responses=True)
        self.prefix = prefix

    def set(self, key: str, value, ttl_seconds=3600):
        full_key = self.prefix + key
        self.redis.setex(full_key, ttl_seconds, json.dumps(value))

    def get(self, key: str):
        full_key = self.prefix + key
        raw = self.redis.get(full_key)
        return json.loads(raw) if raw else None

    def append_to_list(self, key: str, item):
        full_key = self.prefix + key
        self.redis.rpush(full_key, json.dumps(item))

    def get_list(self, key: str):
        full_key = self.prefix + key
        return [json.loads(x) for x in self.redis.lrange(full_key, 0, -1)]

memory = RedisSharedMemory(prefix='research_project:')

Spazi dei nomi gerarchici per la memoria condivisa

Nei sistemi multi-agente complessi, gli agenti scrivono molti tipi diversi di dati e uno spazio dei nomi piatto per le chiavi diventa rapidamente caotico. Utilizzi gli spazi dei nomi gerarchici per organizzare con chiarezza la memoria condivisa. Un pattern comune è project_id:agent_role:data_type, ad esempio proj_123:researcher:findings o proj_123:coder:error_log. In questo modo è facile interrogare tutti i dati di un progetto oppure tutto l'output di uno specifico agente.

class NamespacedMemory:
    def __init__(self, project_id: str, agent_id: str, redis_client):
        self.base = f'{project_id}:{agent_id}'
        self.redis = redis_client

    def write_finding(self, topic: str, content: str):
        key = f'{self.base}:findings:{topic}'
        self.redis.set(key, content)

    def read_all_findings(self, project_id: str):
        # Read findings from ALL agents in this project
        pattern = f'{project_id}:*:findings:*'
        keys = self.redis.keys(pattern)
        return {k: self.redis.get(k) for k in keys}

# Usage
researcher_memory = NamespacedMemory('proj_123', 'researcher', redis_client)
researcher_memory.write_finding('competitors', 'OpenAI, Anthropic, Google...')

writer_memory = NamespacedMemory('proj_123', 'writer', redis_client)
all_findings = writer_memory.read_all_findings('proj_123')

Memoria strutturata e non strutturata

Il contenuto della memoria condivisa può essere non strutturato (blocchi di testo grezzo che l'LLM leggerà) oppure strutturato (oggetti JSON/Python con campi tipizzati). La memoria strutturata è preferibile perché consente query programmatiche, convalida e fusione. Definisca sempre uno schema per ciò che ogni agente scrive nella memoria condivisa, lo documenti e convalidi le scritture rispetto a tale schema, così da evitare che un singolo agente difettoso danneggi l'archivio di memoria.

from pydantic import BaseModel
from typing import Optional, list
from datetime import datetime

class ResearchFinding(BaseModel):
    topic: str
    summary: str
    sources: list[str]
    confidence: float  # 0.0 to 1.0
    written_by: str
    timestamp: datetime

# Validated write - bad data is caught before it enters shared memory
def write_finding(memory, finding_dict: dict):
    finding = ResearchFinding(**finding_dict)  # validates on creation
    memory.set(f'findings:{finding.topic}', finding.model_dump())
    print(f'Validated finding written for topic: {finding.topic}')

Comunicazione basata su eventi con Pub/Sub

Anziché interrogare periodicamente la memoria condivisa per verificare la presenza di aggiornamenti, gli agenti possono utilizzare la comunicazione publish/subscribe (pub/sub) per notificare agli altri agenti il completamento di un'attività. L'agente A pubblica un evento ('research_complete') e l'agente B, che è iscritto a tale evento, si attiva e inizia l'elaborazione. Redis pub/sub e code di messaggi come RabbitMQ o Kafka supportano questo pattern.

import redis

# Publisher (researcher agent)
def researcher_agent(topic, redis_client):
    findings = do_research(topic)
    redis_client.set(f'findings:{topic}', findings)
    
    # Notify all subscribers that research is done
    redis_client.publish('agent_events', f'research_complete:{topic}')
    print(f'Research complete, published event for topic: {topic}')

# Subscriber (writer agent) - runs in separate process
def writer_agent_listener(redis_client):
    pubsub = redis_client.pubsub()
    pubsub.subscribe('agent_events')
    
    for message in pubsub.listen():
        if message['type'] == 'message':
            event = message['data']
            if event.startswith('research_complete:'):
                topic = event.split(':')[1]
                findings = redis_client.get(f'findings:{topic}')
                write_draft(findings)  # start writing immediately

Memoria condivisa in LangGraph

In LangGraph, la memoria condivisa tra gli agenti è costituita dall'oggetto di stato del grafo. Ogni nodo legge e scrive nello stesso dizionario di stato tipizzato. LangGraph gestisce automaticamente il coordinamento delle operazioni di lettura e scrittura. Per scenari più complessi, è anche possibile iniettare un client di memoria esterno (Redis, un database) nella funzione di ogni nodo tramite l'iniezione delle dipendenze.

from langgraph.graph import StateGraph
from typing import TypedDict

class SharedState(TypedDict):
    # All shared data lives here - every node can read any field
    query: str
    research_findings: str   # written by researcher, read by writer
    written_draft: str       # written by writer, read by reviewer
    review_notes: str        # written by reviewer, read by writer (loop)
    final_output: str        # written by synthesizer

# Researcher writes to 'research_findings'
def researcher(state: SharedState) -> dict:
    findings = search_and_summarize(state['query'])
    return {'research_findings': findings}  # partial state update

# Writer reads 'research_findings', writes 'written_draft'
def writer(state: SharedState) -> dict:
    draft = write_from_findings(state['research_findings'])  # reads researcher output
    return {'written_draft': draft}

Conflitti e coerenza della memoria

Quando più agenti scrivono contemporaneamente nella memoria condivisa, possono verificarsi conflitti di scrittura. Due agenti potrebbero sovrascrivere il lavoro dell'altro oppure leggere dati obsoleti tra un'operazione di lettura e una di scrittura. Per gestire il problema, utilizzi il locking ottimistico (verifica della versione prima della scrittura), operazioni atomiche di compare-and-swap in Redis oppure serializzi le scritture tramite un agente orchestratore che sia l'unico a scrivere nei campi critici della memoria.

# Optimistic locking with Redis
def safe_write(redis_client, key, new_value, expected_version):
    with redis_client.pipeline() as pipe:
        try:
            pipe.watch(key + ':version')  # watch for concurrent modification
            current_version = int(pipe.get(key + ':version') or 0)
            
            if current_version != expected_version:
                raise ValueError(f'Version conflict: expected {expected_version}, got {current_version}')
            
            pipe.multi()  # start transaction
            pipe.set(key, new_value)
            pipe.set(key + ':version', current_version + 1)
            pipe.execute()  # atomic commit
            print('Write successful')
        except redis.WatchError:
            print('Conflict detected, retry write')

TTL e pulizia della memoria

La memoria condivisa degli agenti si accumula nel tempo e può crescere senza limiti se non viene gestita. Imposti sempre il time-to-live (TTL) per le voci di memoria, in modo che scadano automaticamente. Per la memoria associata a un progetto, elimini tutte le voci al completamento del progetto. Utilizzi valori di TTL coerenti con la durata prevista del workflow: TTL brevi (minuti) per i dati temporanei, TTL più lunghi (da ore a giorni) per i risultati che potrebbero essere riutilizzati.

def cleanup_project_memory(redis_client, project_id: str):
    pattern = f'{project_id}:*'
    keys = redis_client.keys(pattern)
    if keys:
        redis_client.delete(*keys)
        print(f'Cleaned up {len(keys)} memory entries for project {project_id}')

# Set TTL when writing
def write_with_ttl(redis_client, key, value, ttl_hours=2):
    redis_client.setex(
        key,
        ttl_hours * 3600,  # convert to seconds
        json.dumps(value)
    )

# Register cleanup callback when workflow completes
def on_workflow_complete(project_id):
    cleanup_project_memory(redis_client, project_id)
    print(f'Workflow {project_id} complete, memory cleaned up')

La memoria come cronologia dell'agente

La memoria condivisa può contenere anche la cronologia delle azioni degli agenti, non solo i relativi output. Registrare quale agente ha fatto cosa, quando e perché crea una traccia di audit preziosa per il debug degli errori, per capire come è stato prodotto l'output finale e per riprendere workflow interrotti. Questo registro delle azioni è l'equivalente basato sulla memoria del tracing di LangSmith.

import time
from dataclasses import dataclass

@dataclass
class AgentAction:
    agent_id: str
    action_type: str     # 'research', 'write', 'review', 'tool_call'
    input_summary: str
    output_summary: str
    timestamp: float
    success: bool

def log_action(memory, action: AgentAction):
    key = f'action_log:{action.agent_id}:{action.timestamp}'
    memory.set(key, vars(action))

# Usage in an agent
def researcher_with_logging(state, memory):
    start = time.time()
    findings = do_research(state['query'])
    log_action(memory, AgentAction(
        agent_id='researcher',
        action_type='research',
        input_summary=state['query'][:100],
        output_summary=findings[:100],
        timestamp=start,
        success=True
    ))
    return findings

Scelta dell'architettura della memoria

L'architettura corretta per la memoria condivisa dipende dal modello di deployment. Per i workflow LangGraph a processo singolo, è sufficiente lo stato del grafo. Per gli agenti multiprocesso o distribuiti, utilizzi Redis. Per i progetti di lunga durata che richiedono persistenza tra i riavvii, utilizzi un database relazionale con un'indicizzazione appropriata. Per il coordinamento basato su eventi tra i servizi, aggiunga il pub/sub al sistema di archiviazione scelto.

Verifica rapida

Verifichi la propria comprensione della memoria condivisa e della comunicazione tra agenti trattate in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: il modello blackboard utilizza un archivio dati condiviso in cui ogni agente può leggere e scrivere, consentendo un coordinamento implicito senza accoppiare direttamente gli agenti; Redis è la scelta principale per la memoria condivisa persistente nei sistemi multi-agente distribuiti; e il pub/sub abilita la comunicazione basata su eventi, così gli agenti reagiscono immediatamente al completamento delle dipendenze. Nella prossima lezione analizzeremo il ciclo di esecuzione del codice per le attività degli agenti.

Domande Frequenti

La lezione «Memoria condivisa e comunicazione tra agenti» è gratuita?

Sì — il testo completo di «Memoria condivisa e comunicazione tra agenti» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Engineering Academy, passa a CoddyKit PRO. Il corso AI Engineering Academy include 4 lezioni in totale.

Cosa imparerò in «Memoria condivisa e comunicazione tra agenti»?

Implementi un livello di memoria condivisa usando un archivio chiave-valore da cui gli agenti leggono e in cui scrivono, consentendo la collaborazione asincrona senza uno stretto accoppiamento tra gl… Eserciti AI Engineering Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Engineering Academy?

Non è richiesta alcuna esperienza precedente. AI Engineering Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Memoria condivisa e comunicazione tra agenti»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Engineering Academy?

Sì. Ogni lezione AI Engineering Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Perché gli agenti singoli arrivano a un limite
  2. Pattern orchestratore-sottoagente
  3. Creare pipeline multi-agente con LangGraph
  4. Memoria condivisa e comunicazione tra agenti
← Torna a AI Engineering Academy