004 — Memory architecture (agente conversacional)
Status: Aceita (2026-06-03)
Contexto
Section titled “Contexto”Cenário 008 estabeleceu AgentChat como Domain Service stateless: recebe { messages, budget, goal, household, model } e devolve a próxima mensagem. Histórico fica fora do domínio — decisão consciente, mirror de FeasibilityCheck.evaluate({ today }) (sem clock implícito, sem estado escondido).
A consequência prática: hoje conversa some no restart. Não há retrieval do que o casal disse semana passada, não há persistência de preferências (“a gente conta em BRL”), não há compressão de turns antigos. Cenários 012+ (chat persistido com retrieval) e 013 (write tools que persistem state) batem nessa parede.
Constraint operacional do Gabriel: o agente continua stateless. Ele recebe messaging context + dados relevantes injetados por turno. O que precisa entrar é UMA memória persistente — uma camada externa que sobrevive restart, retorna o slice relevante por turno, e é injetada via DI no AgentChat. Não é estado escondido do service; é input enriquecido pelo caller.
Otimização-chave: context window. Gemini 2.5 Flash via OpenRouter (ADR 001) tem janela larga mas custo proporcional. Despejar histórico inteiro a cada turno é caro e ruim — precisa de tiers (conversa recente verbatim, working memory denormalizada, observações comprimidas, semantic recall opcional).
Research externo já foi feito (relatório separado, conhecido pelo time) avaliando Letta, mem0, Zep/Graphiti, LangGraph memory, Cognee, Mastra, @ai-sdk-tools/memory (Midday), e DIY com better-sqlite3 + sqlite-vec. Esta ADR consolida a escolha — sem reabrir benchmark.
Decisão
Section titled “Decisão”Adotar @mastra/memory + @mastra/libsql como memory layer, ponte com Vercel AI SDK via withMastra() de @mastra/ai-sdk (ou injeção direta da classe Memory no AgentChat.ask, caso o wrapper se mostre flaky no spike).
Contrato stateless preservado
Section titled “Contrato stateless preservado”AgentChat.ask recebe identificadores opacos pra memória — não a memória em si:
agentChat.ask({ messages: CoreMessage[], resourceId: string, // "casa-gabriel" (mapeia ao Household) threadId: string, // por conversa}): Promise<{ reply, toolCalls }>Memória é injetada via construtor (DI) — mirror do pattern FeasibilityCheck.evaluate({ today }): dependências externas chegam como input, não como singleton.
Quatro tiers, ligados on/off conforme cenário
Section titled “Quatro tiers, ligados on/off conforme cenário”- Conversation history — durável, libSQL tables
chat_threads+chat_messages. Substitui o “histórico some no restart”. Padrão ligado a partir do cenário 012. - Working memory — JSON estruturado por
resourceId(couple/Household). Denormalized read model:LLM atualiza via tool-call específico (Mastra default — sem fact-extraction silencioso rodando em background). Trade-off consciente: menos determinístico que rules-based, mas evita lógica de extração custom.{members: [{ name, role? }],currencyPreferred: "BRL",monthlyIncomeBRL: 12000,activeGoals: [{ name, target, deadline, savedInTarget }],// ...} - Observational memory — compressão de turns antigos em observações densas quando thread cresce. Não exige vector DB; Mastra faz com summarization in-place.
- Semantic recall — OPT-IN, OFF por default. Liga só quando miss-rate de retrieval doer. Quando ligar: Gemini Embedding 001 via OpenRouter (mesmo provider que LLM, ADR 001). Custo de embeddings deferido até ter sinal.
Storage
Section titled “Storage”- libSQL single-file
./mel.db(SQLite-compatible) gerenciado pelo@mastra/libsqladapter. - Schemas + tables criadas pelo Mastra. Não tem schema-as-TS por drizzle aqui (Mastra gerencia internamente).
- Dois SQLite drivers no início:
better-sqlite3(sync) pros domain repos via drizzle (ADR 001), libSQL pro Mastra. Coexistem em arquivos.dbseparados ou no mesmo arquivo (libSQL é SQLite-compat). Reconciliação futura: drizzle suporta libSQL — convergir pra um driver só quando schema estabilizar e custo de migração for baixo.
Skip explícito
Section titled “Skip explícito”- Letta/MemGPT — Python runtime, conflita com Vercel AI SDK (Node/TS).
- mem0 — best path é managed cloud (conflita com local-first do projeto); Node SDK tem bug P1 #3291 conhecido.
- Zep/Graphiti — exige graph DB (Neo4j/Postgres/Kuzu). Overkill pra app local-first de casal.
- LangGraph memory — graph runtime + checkpointer, não memory layer; substituiria Vercel AI SDK (conflita com ADR 001).
- Cognee — Python-first, graph DB.
- DIY com
sqlite-vec— viável e alinhado com ethos local-first/minimal-deps. Fallback se Mastra surface incomodar. Perde fact extraction + observational prontos. @ai-sdk-tools/memory(Midday) — API mais Vercel-shaped, mas falta semantic recall na doc e é coupled com@ai-sdk-tools/agents. Strong reference se Mastra falhar — re-avaliar.
Consequências
Section titled “Consequências”Positivas:
AgentChatcontinua stateless — contract do cenário 008 só ganha dois IDs (resourceId,threadId), não vaza memória pro domínio.- Local-first preservado — libSQL é SQLite-compat, single-file. Nenhum serviço externo obrigatório.
- 4 tiers de memória disponíveis sem inventar lógica de compressão/extração.
- Embeddings + custo associado deferidos até precisar (semantic recall OFF default).
- Apache-2.0, 99% TS, 24.7k stars, último release maio/2026 — bus factor razoável.
- Mock LLM (ADR 002) continua viável: memory layer é injetada via DI, spec passa stub determinístico.
Negativas / Trade-offs:
- 4 packages novos (
@mastra/core,@mastra/memory,@mastra/libsql,@mastra/ai-sdk) sobre a stack do ADR 001 — surface de dep cresce. - Dois SQLite drivers coexistindo (
better-sqlite3+ libSQL) até reconciliar. Risco de divergência de schema/conexão; mitigado por arquivos.dbseparados no início. withMastra()wrapper é novo — spike necessário pra validar ergonomia com Vercel AI SDK. Plano B: injeção direta da classeMemorynoAgentChat.- Mastra traz
Agent, workflows e outros conceitos que não vamos usar — surface conceitual maior que um lib focada em memory only. Aceito: usamos sóMemory+libsql, ignoramos o resto. - Working memory é LLM-driven (tool-call edita o JSON) — menos determinístico que rules-based. Trade-off aceito pra não construir fact extraction custom.
- ADR 001 não menciona Mastra. Amendment formal só quando virar dep instalada — esta ADR já documenta a escolha; sobreposição não justifica revisar 001 ainda.
Alternativas consideradas
Section titled “Alternativas consideradas”- DIY com
better-sqlite3 + sqlite-vec + drizzle— alinha 100% com ethos minimal-deps e single-driver. Perde fact extraction + observational compression prontos; vira projeto-dentro-do-projeto. Mantido como fallback se Mastra surface incomodar (semestre). @ai-sdk-tools/memory(Midday) — API Vercel-shaped mais natural pro AgentChat. Falta doc de semantic recall e tem coupling com@ai-sdk-tools/agents. Strong reference se Mastra falhar no spike.- Letta/mem0/Zep/Cognee/LangGraph — descartados por runtime (Python), modelo (cloud-only), ou infra (graph DB). Todos conflitam com local-first + Node/TS do projeto.
- Manter
AgentChatstateless sem memória persistente — adiar mais um ciclo. Funciona pra 008–011 mas trava 012+ (chat persistido) e 013 (write tools com state) ao mesmo tempo. Decidir agora evita escolha em pânico depois. - Postgres +
pgvector— overkill pra projeto local-first; sem operação de DB externo justificada por um app de casal.
Referências
Section titled “Referências”- ADR 001 — stack de infraestrutura (Vercel AI SDK + OpenRouter + Gemini 2.5 Flash; drizzle + better-sqlite3).
- ADR 002 — test tiers (mock LLM no domain, MockLanguageModelV1 segue válido com memory stub).
- ADR 003 — docs strategy (esta ADR é decisão técnica atemporal, não cenário).
- Scenario 008 —
AgentChatstateless contract que esta ADR enriquece comresourceId/threadId. - AGENTS.md — gotcha “domain não converte moeda; UX converte” (working memory é read model denormalizado, segue a regra).
- Research report (2026-06-03, knowledge interno do time) — comparativo Letta/mem0/Zep/LangGraph/Cognee/Mastra/Midday/DIY.
- Mastra — https://github.com/mastra-ai/mastra
- Mastra memory overview — https://mastra.ai/docs/memory/overview
- Mastra ai-sdk integration — https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk
- libSQL como vector DB — https://turso.tech/blog/using-sqlite-as-your-llm-vector-database