Memoria per agenti IA con Convex: Architettura completa

Come abbiamo costruito Cortex, un sistema di memoria cognitiva per agenti IA su Convex — con archivi sensoriali, episodici e semantici, decadimento della memoria, attivazione diffusa e zero costi di infrastruttura.

Memoria per agenti IA con Convex: Architettura completa

Memoria per agenti IA con Convex: Architettura completa

OpenClaw fornisce agli agenti IA una memoria basata su file fin da subito — file Markdown, log giornalieri, persino un tool memory_search per la ricerca semantica. Funziona. Il nostro agente Timmy poteva ricordare le decisioni di ieri leggendo MEMORY.md, verificare cosa è successo la settimana scorsa e cercare contesto rilevante tra i file.

Ma quando Timmy ha assunto più responsabilità — scrivere articoli per il blog, gestire i social media, automatizzare attività del browser, eseguire cron job — abbiamo iniziato a scontrarci con i limiti della memoria basata su file. La ricerca era lineare, senza classificazione per rilevanza. I vecchi ricordi non svanivano mai, e i file si gonfiavano di decisioni obsolete. Non c'era modo di far emergere automaticamente contesto correlato o distinguere una decisione architetturale critica da un'osservazione casuale.

Volevamo qualcosa di più cognitivo. Non solo archiviazione — un sistema che modella il funzionamento reale della memoria.

Questa è la storia di come abbiamo costruito Cortex, un sistema di memoria cognitiva per agenti IA, alimentato da Convex.

Corso accelerato di 60 secondi sulla memoria cognitiva

Prima di immergerci nell'architettura, parliamo di come funziona realmente la memoria umana. Le scienze cognitive hanno identificato diversi sistemi mnestici distinti:

🧠 Memoria sensoriale — Il tuo buffer a breve termine. Come ricordare cosa qualcuno ha detto 30 secondi fa. Alto volume, molto temporanea. La maggior parte evapora in minuti.

📔 Memoria episodica — Il tuo diario. Eventi specifici legati a tempi e luoghi: "Martedì abbiamo deciso di usare Typefully per i social media." Autobiografica, contestuale, con timestamp.

📚 Memoria semantica — La tua base di conoscenza. Fatti che semplicemente sai: "Convex supporta la ricerca vettoriale" o "il comando di deploy è npx convex deploy." Distillata dall'esperienza nel tempo.

⚙️ Memoria procedurale — Memoria muscolare. Come fare le cose: "Per pubblicare un articolo: creare bozza → aggiungere immagine → pubblicare → verificare URL → social media."

📋 Memoria prospettica — La tua lista di cose da fare. Cose che devi fare in futuro: "Controllare le performance SEO lunedì prossimo" o "Seguire quel lead dopo la demo."

L'intuizione: non sono solo categorie. Ogni tipo ha persistenza, tassi di decadimento e schemi di recupero diversi. I ricordi sensoriali dovrebbero svanire in ore. La conoscenza semantica dovrebbe persistere indefinitamente. I ricordi emotivi dovrebbero resistere all'oblio. E i ricordi correlati dovrebbero attivarsi a vicenda.

Questo è ciò che fa Cortex. Costruiamolo.

Perché Convex? (Non era la nostra prima scelta)

Usavamo già Convex per il nostro CMS blog su contextstudios.ai. La scelta per la memoria cognitiva è venuta da tre constatazioni:

1. Il modello documento si adatta naturalmente agli oggetti memoria

Un ricordo non è una riga in una tabella. È un oggetto ricco con titolo, contenuto, valenza emotiva, forza, tag, provenienza e relazioni. Il modello documento di Convex si adatta perfettamente:

// convex/schema.ts — Lo schema memoria Cortex
cortexMemories: defineTable({
  store: v.union(
    v.literal("sensory"),    // buffer 24h
    v.literal("episodic"),   // eventi specifici
    v.literal("semantic"),   // conoscenza fattuale
    v.literal("procedural"), // workflow pratici
    v.literal("prospective") // intenzioni future
  ),
  category: v.union(
    v.literal("decision"), v.literal("lesson"),
    v.literal("person"),   v.literal("rule"),
    v.literal("event"),    v.literal("fact"),
    v.literal("goal"),     v.literal("workflow")
  ),
  title: v.string(),
  content: v.string(),
  embedding: v.array(v.float64()), // vettore 1536-dim
  strength: v.float64(),      // 0–1, decade nel tempo
  confidence: v.float64(),    // 0–1, quanto sicuro
  valence: v.float64(),       // -1 a 1 (carica emotiva)
  arousal: v.float64(),       // 0–1 (intensità emotiva)
  accessCount: v.number(),    // frequenza di recupero
  lastAccessedAt: v.number(), // per punteggio di recenza
  source: v.union(
    v.literal("conversation"),
    v.literal("cron"),
    v.literal("observation"),
    v.literal("inference"),
    v.literal("external")
  ),
  tags: v.array(v.string()),
})

2. Ricerca vettoriale integrata (nessuna infrastruttura aggiuntiva)

Questa era la funzionalità decisiva. L'indicizzazione vettoriale nativa significa che archiviamo gli embedding OpenAI accanto ai documenti memoria e li cerchiamo senza installare Pinecone o Qdrant:

.vectorIndex("by_embedding", {
  vectorField: "embedding",
  dimensions: 1536,
  filterFields: ["store", "category", "tags"],
})

3. Funzioni serverless per processi cognitivi

La memoria non è solo archiviazione — è un processo. Convex ci dà mutations, queries, actions e cron job — esattamente le primitive giuste per decadimento, consolidamento e associazione:

// convex/crons.ts — Processi cognitivi in background
crons.interval("cortex consolidation", { hours: 12 }, internal.cortex.consolidate);
crons.interval("cortex decay", { hours: 24 }, internal.cortex.decay);
crons.interval("cortex cleanup", { hours: 24 }, internal.cortex.cleanupExpired);

Niente funzioni Lambda. Niente code di worker. Solo funzioni TypeScript che girano secondo un programma.

L'architettura Cortex

Cinque archivi di memoria

┌───────────────────────────────────────────────────────┐
│                    MEMORIA CORTEX                      │
├────────────┬──────────────┬──────────────┬────────────┤
│ SENSORIALE │  EPISODICA   │  SEMANTICA   │ PROCEDURALE│
│ (buffer    │  (eventi)    │  (sapere)    │ (pratica)  │
│  24h)      │              │              │            │
├────────────┴──────────────┴──────────────┴────────────┤
│                    PROSPETTICA                         │
│          Obiettivi, piani, intenzioni future           │
└───────────────────────────────────────────────────────┘

Auto-promozione: Sensoriale → Episodica → Semantica

Ogni 12 ore, un cron di consolidamento raggruppa i ricordi sensoriali correlati (similarità vettoriale > 0,75), li sintetizza in ricordi episodici e segna gli originali come consolidati. Nel tempo, i ricordi episodici frequentemente acceduti con alta confidenza vengono promossi a memoria semantica. Il sistema impara letteralmente cosa è importante.

Decadimento della memoria: dimenticare è una funzionalità

Ogni ricordo ha un campo strength che inizia a 1.0 e decade giornalmente:

export const decay = internalMutation({
  handler: async (ctx) => {
    const now = Date.now();
    const memories = await ctx.db
      .query("cortexMemories")
      .filter(q => q.eq(q.field("archivedAt"), undefined))
      .collect();

    for (const mem of memories) {
      if (mem.store === "prospective") continue;
      const daysSinceAccess = (now - mem.lastAccessedAt) / (1000 * 60 * 60 * 24);
      const isHighEmotion = Math.abs(mem.valence) > 0.7 && mem.arousal > 0.7;
      const decayRate = isHighEmotion ? 0.01 : 0.02;
      const newStrength = Math.max(0, mem.strength - decayRate * daysSinceAccess);

      if (newStrength < 0.1) {
        await ctx.db.patch(mem._id, { strength: newStrength, archivedAt: now });
      } else if (newStrength !== mem.strength) {
        await ctx.db.patch(mem._id, { strength: newStrength });
      }
    }
  },
});

Due principi: i ricordi emotivi persistono più a lungo, e il recupero rinforza la memoria — ripetizione spaziata per agenti IA.

Attivazione diffusa: ricordi che si connettono

I nuovi ricordi trovano automaticamente quelli correlati tramite ricerca vettoriale e creano legami associativi:

export const createAutoAssociations = internalAction({
  handler: async (ctx, args) => {
    const similar = await ctx.vectorSearch("cortexMemories", "by_embedding", {
      vector: args.embedding,
      limit: 6,
    });
    for (const result of similar) {
      if (result._id === args.memoryId) continue;
      await ctx.runMutation(internal.cortex.insertAssociations, {
        associations: [{
          from: args.memoryId,
          to: result._id,
          type: "related",
          weight: result._score,
          createdAt: Date.now(),
        }],
      });
    }
  },
});

Il recupero usa una funzione di punteggio composito che combina quattro segnali:

const compositeScore =
  mem.strength * 0.3 +
  recencyScore * 0.2 +
  accessScore * 0.1 +
  mem.vectorScore * 0.4;

Integrazione con OpenClaw

Cortex è esposto tramite 8 strumenti MCP (Model Context Protocol):

StrumentoScopo
cortex_rememberArchiviare un nuovo ricordo
cortex_recallCercare e recuperare ricordi
cortex_what_do_i_knowVerifica di consapevolezza tematica
cortex_why_did_weArcheologia delle decisioni
cortex_forgetRimozione esplicita di ricordi
cortex_statsStatistiche del sistema di memoria
cortex_checkpointSalvare il contesto di lavoro
cortex_wakeBriefing mattutino

Il pattern doppia-scrittura

Ogni ricordo va in Cortex (Convex) e in file Markdown locali. Cortex offre recupero strutturato, ricerca vettoriale e decadimento. I file Markdown forniscono una traccia di audit leggibile.

Costruisci il tuo: Cortex minimo funzionante

Vuoi realizzare qualcosa di simile in un weekend? Ecco la versione minima — memoria sensoriale + semantica con ricerca vettoriale.

Passo 1: Configurare lo schema Convex

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  memories: defineTable({
    store: v.union(v.literal("sensory"), v.literal("semantic")),
    title: v.string(),
    content: v.string(),
    embedding: v.array(v.float64()),
    strength: v.float64(),
    createdAt: v.number(),
    lastAccessedAt: v.number(),
    tags: v.array(v.string()),
  })
    .vectorIndex("by_embedding", {
      vectorField: "embedding",
      dimensions: 1536,
      filterFields: ["store", "tags"],
    })
    .index("by_store", ["store"]),
});

Passo 2: Scrivere la funzione Remember

export const remember = action({
  args: {
    store: v.union(v.literal("sensory"), v.literal("semantic")),
    title: v.string(),
    content: v.string(),
    tags: v.array(v.string()),
  },
  handler: async (ctx, args) => {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "text-embedding-3-small",
        input: `${args.title}: ${args.content}`,
      }),
    });
    const data = await response.json();
    const now = Date.now();
    await ctx.runMutation(internal.memory.insert, {
      ...args, embedding: data.data[0].embedding, strength: 1.0, createdAt: now, lastAccessedAt: now,
    });
  },
});

Passo 3: Scrivere la funzione Recall

export const recall = action({
  args: {
    query: v.string(),
    store: v.optional(v.union(v.literal("sensory"), v.literal("semantic"))),
    limit: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model: "text-embedding-3-small", input: args.query }),
    });
    const data = await response.json();
    const results = await ctx.vectorSearch("memories", "by_embedding", {
      vector: data.data[0].embedding,
      limit: args.limit ?? 5,
      filter: args.store ? { store: args.store } : undefined,
    });
    for (const r of results) {
      await ctx.runMutation(internal.memory.touch, { id: r._id });
    }
    return results;
  },
});

Passo 4: Aggiungere il decadimento giornaliero

import { cronJobs } from "convex/server";
const crons = cronJobs();
crons.daily("memory decay", { hourUTC: 4, minuteUTC: 0 }, internal.memory.decay);
export default crons;

Passo 5: Collegare al tuo agente

Se usi MCP, esponi remember e recall come strumenti. Fatto — hai un sistema di memoria funzionante con ricerca semantica, decadimento automatico e due livelli di memoria.

Lezioni pratiche

  1. Inizia con il sensoriale, non il semantico. Lascia che la pipeline di consolidamento decida cosa è importante.
  2. Il decadimento è essenziale. Senza di esso, il tuo archivio memoria diventa un ripostiglio di decisioni obsolete.
  3. I metadati emotivi contano. Taggare i ricordi con valenza e attivazione migliora effettivamente la qualità del recupero.
  4. Ricerca vettoriale + filtraggio metadati > ricerca vettoriale pura. Convex lo rende banale.
  5. Le associazioni creano serendipità. Il recupero più prezioso è a volte un ricordo correlato.

I risultati

Dopo il lancio di Cortex, le sessioni iniziano con l'agente che conosce già il contesto. Le decisioni sono istantaneamente recuperabili. Le lezioni persistono davvero. Sul tier gratuito di Convex, archiviamo centinaia di ricordi senza costi di infrastruttura. La ricerca vettoriale restituisce risultati in meno di 100ms.

L'impatto reale è qualitativo: lavorare con un'IA che ricorda è come collaborare con un collega nel team da mesi, invece di spiegare tutto a un nuovo consulente ogni mattina.

Cosa viene dopo

Cortex è in produzione. Se stai costruendo agenti IA e lotti con la persistenza del contesto, considera questo approccio. Consulta la documentazione agenti IA di Convex e il protocollo MCP. La tua IA non deve ricominciare da zero ad ogni sessione.


FAQ

Qual è la differenza tra Cortex e un database vettoriale come Pinecone? Cortex aggiunge decadimento della memoria, consolidamento automatico, metadati emotivi e attivazione diffusa sopra la ricerca vettoriale. Su Convex, tutto è su un'unica piattaforma.

Quanto costa? Attualmente 0€ sul tier gratuito di Convex. Gli unici costi esterni sono gli embedding OpenAI (frazioni di centesimo per ricordo).

Funziona con modelli diversi da Claude? Sì. Cortex è agnostico al modello, esposto tramite strumenti MCP. Gli embedding usano text-embedding-3-small di OpenAI, ma potresti usare qualsiasi modello a 1536 dimensioni.

Come prevenite l'archiviazione di informazioni errate? Ogni ricordo ha un punteggio di confidence, cortex_forget permette la rimozione esplicita, e il decadimento naturale fa sì che anche i ricordi errati svaniscano.

E se Convex va in down? Il pattern doppia-scrittura significa che ogni ricordo esiste sia in Convex che in file Markdown locali. L'agente ricade sulla ricerca basata su file.

Condividi articolo

Share: