Files
ForgeLM/docs/plans/2026-04-03-memory-architecture-chat-user-consolidation.md
T

549 lines
17 KiB
Markdown

# Memory Architecture: Chat + User Consolidation Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Introduzir uma arquitetura de memória segura e incremental que extraia memórias estruturadas com o modelo local `models/embedding/gemma-3-270m-it-Q8_0.gguf`, separe memória de chat e memória de usuário, consolide memórias entre chats sem duplicação e prepare o terreno para uso dinâmico em prompts futuros.
**Architecture:** A implementação será dividida em duas pipelines desacopladas: (1) extração de memória de chat a partir de mensagens concluídas em um chat específico; (2) consolidação de memória de usuário a partir das memórias de chat já processadas. Para evitar quebrar o fluxo atual, a extração será feita em background, com schema novo e compatibilidade retroativa com `user_memories`. A primeira fase não altera o prompt principal do chat; apenas cria, valida, deduplica e persiste a memória corretamente.
**Tech Stack:** React/TypeScript, Tauri frontend, SQLite local (`db.ts`), API compatível OpenAI do runtime local, modelo GGUF local `gemma-3-270m-it-Q8_0.gguf`, Vitest.
---
## Princípios de segurança desta implementação
1. **Não quebrar o chat atual**: memória nova roda em background e falha silenciosamente.
2. **Não misturar memória bruta com prompt principal ainda**: primeiro persistimos corretamente, depois conectamos ao prompt.
3. **Não usar mensagens inteiras como memória final**: só fatos estruturados, classificados e deduplicados.
4. **Separação explícita**:
- `chat memories` = fatos extraídos de um chat específico
- `user memories` = fatos consolidados a partir de vários chats
5. **Idempotência**: nunca reprocessar o mesmo insumo sem necessidade.
6. **Deduplicação conservadora**: primeiro por chave/categoria/valor normalizado; sem embeddings sofisticados nesta fase.
7. **Modelo único para extração/classificação**: usar `gemma-3-270m-it-Q8_0.gguf` como extractor/classifier local.
---
## Resultado esperado desta fase
Ao final desta implementação:
- cada chat poderá gerar memórias estruturadas próprias;
- memórias de usuário serão derivadas das memórias de chat já consolidadas;
- duplicatas não serão reinseridas;
- memórias terão tipo/classificação/confiança/origem;
- o sistema estará pronto para usar essas memórias em prompts dinâmicos futuros, sem ainda acoplar tudo ao chat principal.
---
## Modelo de dados recomendado
### Tabela 1: `chat_memories`
Representa fatos persistidos derivados de um chat.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `chat_id INTEGER NOT NULL`
- `source_message_id INTEGER NULL`
- `memory_key TEXT NOT NULL`
- `memory_value TEXT NOT NULL`
- `memory_category TEXT NOT NULL`
- `memory_scope TEXT NOT NULL DEFAULT 'chat'`
- `confidence REAL NOT NULL`
- `evidence TEXT NULL`
- `first_seen_at INTEGER NOT NULL`
- `last_seen_at INTEGER NOT NULL`
- `normalized_value TEXT NOT NULL`
- `superseded_by INTEGER NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
Categorias permitidas inicialmente:
- `identity`
- `profile`
- `preference`
- `interest`
- `habit`
- `project`
- `constraint`
- `goal`
- `topic_pattern`
- `skill`
### Tabela 2: `user_memories_v2`
Representa fatos consolidados a partir de `chat_memories`.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `memory_key TEXT NOT NULL`
- `memory_value TEXT NOT NULL`
- `memory_category TEXT NOT NULL`
- `confidence REAL NOT NULL`
- `normalized_value TEXT NOT NULL`
- `supporting_chat_memory_ids TEXT NOT NULL` (JSON array)
- `evidence_summary TEXT NULL`
- `first_seen_at INTEGER NOT NULL`
- `last_seen_at INTEGER NOT NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
### Tabela 3: `memory_jobs`
Fila/log de processamento para idempotência.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `job_type TEXT NOT NULL` (`chat_extract` | `user_consolidate`)
- `target_id INTEGER NOT NULL`
- `status TEXT NOT NULL` (`pending` | `running` | `done` | `failed`)
- `attempts INTEGER NOT NULL DEFAULT 0`
- `last_error TEXT NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
---
## Prompt recomendado para extração de memória de chat
Arquivo novo sugerido: `tauri-app/src/prompts/memory-extraction.ts`
### Prompt base
```text
You are a local memory extraction and classification engine.
Your task is to read a conversation fragment and extract ONLY durable, reusable memory items.
Return ONLY valid JSON as an array.
Each item must follow this schema:
{
"memory_key": "snake_case_key",
"memory_value": "concise factual value",
"memory_category": "identity|profile|preference|interest|habit|project|constraint|goal|topic_pattern|skill",
"confidence": 0.0,
"evidence": "short quote or short rationale",
"is_user_memory_candidate": true
}
Extraction policy:
- Extract durable facts, not temporary requests.
- Extract user preferences, likes/dislikes, recurring interests, profession, age, location, constraints, active projects, goals, skills, and patterns in what the user usually searches or asks about.
- Prefer atomic facts.
- Do not repeat equivalent items.
- If a fact is uncertain, lower confidence.
- If nothing durable exists, return [].
- Never output markdown.
```
### Prompt adicional para consolidação de memória de usuário
```text
You are a user memory consolidator.
Input: existing user memories + candidate chat memories.
Goal: produce ONLY new or improved user-level memories.
Rules:
- Do not repeat existing memories.
- Merge semantically equivalent facts.
- Prefer the most stable formulation.
- Keep only durable cross-chat knowledge.
- If a fact appears in multiple chats, increase confidence.
- Return ONLY valid JSON array with the same schema.
```
---
## Fases de implementação
### Task 1: Congelar o comportamento atual com testes de regressão
**Files:**
- Create: `tauri-app/src/hooks/useMemoryExtractor.test.ts`
- Test: `tauri-app/src/components/ChatView.test.tsx`
**Step 1: Escrever teste de regressão do fluxo atual**
Validar que, se o extractor falhar, o chat principal continua funcionando.
**Step 2: Rodar teste para confirmar baseline**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts`
Expected: FAIL inicialmente (arquivo novo).
**Step 3: Criar teste mínimo de não-regressão**
Cobrir:
- extractor não bloqueia envio de mensagem
- erro de memória não quebra render/chat
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/hooks/useMemoryExtractor.test.ts tauri-app/src/components/ChatView.test.tsx
git commit -m "test: add memory extraction regression coverage"
```
---
### Task 2: Introduzir schema novo sem remover o antigo
**Files:**
- Modify: `tauri-app/src/lib/db.ts`
- Modify: `tauri-app/src/lib/types.ts`
- Create: `tauri-app/src/lib/memory-types.ts`
- Test: `tauri-app/src/lib/db.test.ts`
**Step 1: Escrever testes falhos para novas tabelas**
Cobrir criação idempotente de:
- `chat_memories`
- `user_memories_v2`
- `memory_jobs`
**Step 2: Adicionar migração aditiva**
Em `db.ts`, usar `ensureTable`/`ensureColumn` (ou equivalente local) sem remover `user_memories` atual.
**Step 3: Declarar tipos explícitos**
Criar interfaces para:
- `ChatMemory`
- `UserMemoryV2`
- `MemoryJob`
- `MemoryCandidate`
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/db.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/lib/db.ts tauri-app/src/lib/types.ts tauri-app/src/lib/memory-types.ts tauri-app/src/lib/db.test.ts
git commit -m "feat: add chat and user memory schema"
```
---
### Task 3: Configurar o modelo local de memória sem tocar no modelo principal
**Files:**
- Create: `tauri-app/src/config/memory.ts`
- Modify: `tauri-app/src/config/ai.ts` or `tauri-app/src/config/ai/index.ts`
- Test: `tauri-app/src/features/runtime/runtime-utils.test.ts`
**Step 1: Escrever teste falho de resolução da configuração**
Configurar constante para:
- `MEMORY_MODEL_PATH = "models/embedding/gemma-3-270m-it-Q8_0.gguf"`
- `MEMORY_MODEL_ID = "gemma-3-270m-it-Q8_0"`
**Step 2: Implementar configuração dedicada**
A memória deve usar modelo separado do chat principal.
**Step 3: Adicionar fallback seguro**
Se o arquivo não existir, a memória desabilita-se sem derrubar o app.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/features/runtime/runtime-utils.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/config/memory.ts tauri-app/src/config/ai.ts tauri-app/src/config/ai/index.ts tauri-app/src/features/runtime/runtime-utils.test.ts
git commit -m "feat: configure dedicated local memory model"
```
---
### Task 4: Implementar prompt e parser de extração estruturada
**Files:**
- Create: `tauri-app/src/prompts/memory-extraction.ts`
- Create: `tauri-app/src/lib/memory-parser.ts`
- Create: `tauri-app/src/lib/memory-parser.test.ts`
**Step 1: Escrever testes falhos para parsing de JSON robusto**
Cobrir:
- array JSON puro
- JSON com ruído ao redor
- array vazio
- item inválido descartado
- normalização de `memory_key`
- clamp de confiança
**Step 2: Implementar parser minimalista**
Funções sugeridas:
- `parseMemoryCandidates(raw: string): MemoryCandidate[]`
- `normalizeMemoryCandidate(candidate): MemoryCandidate | null`
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/memory-parser.test.ts`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/prompts/memory-extraction.ts tauri-app/src/lib/memory-parser.ts tauri-app/src/lib/memory-parser.test.ts
git commit -m "feat: add structured memory extraction prompt and parser"
```
---
### Task 5: Extrair memórias de chat em background
**Files:**
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
- Create: `tauri-app/src/lib/chat-memory-repository.ts`
- Test: `tauri-app/src/hooks/useMemoryExtractor.test.ts`
**Step 1: Escrever testes falhos para pipeline de chat memory**
Cobrir:
- usa modelo de memória dedicado
- envia prompt de extração correto
- salva em `chat_memories`
- marca job/mensagem como processado
- não salva duplicata exata
**Step 2: Refatorar o hook atual**
O hook atual não deve mais gravar direto em `user_memories`.
Agora deve:
- ler mensagens recentes
- chamar o modelo de memória
- parsear candidatos
- persistir em `chat_memories`
- registrar job concluído
**Step 3: Implementar deduplicação conservadora**
Deduplicar por:
- `chat_id`
- `memory_key`
- `normalized_value`
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/hooks/useMemoryExtractor.ts tauri-app/src/lib/chat-memory-repository.ts tauri-app/src/hooks/useMemoryExtractor.test.ts
git commit -m "feat: extract chat-scoped memories with local model"
```
---
### Task 6: Consolidar memórias de usuário a partir das memórias de chat
**Files:**
- Create: `tauri-app/src/lib/user-memory-consolidator.ts`
- Create: `tauri-app/src/lib/user-memory-repository.ts`
- Create: `tauri-app/src/lib/user-memory-consolidator.test.ts`
**Step 1: Escrever testes falhos da consolidação**
Cobrir:
- lê `chat_memories` já processadas
- compara com `user_memories_v2`
- cria apenas memórias novas
- melhora memória existente quando confiança aumenta
- não duplica fatos equivalentes
**Step 2: Implementar consolidador em duas etapas**
Etapa A:
- selecionar candidatas recentes de `chat_memories`
Etapa B:
- chamar o modelo com:
- memórias de usuário já existentes
- novas memórias de chat
- receber apenas novos fatos consolidados
**Step 3: Persistir com rastreabilidade**
Salvar também `supporting_chat_memory_ids`.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/user-memory-consolidator.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/lib/user-memory-consolidator.ts tauri-app/src/lib/user-memory-repository.ts tauri-app/src/lib/user-memory-consolidator.test.ts
git commit -m "feat: consolidate user memories from chat memories"
```
---
### Task 7: Definir política de classificação e uso futuro em prompts
**Files:**
- Create: `tauri-app/src/lib/memory-prompt-context.ts`
- Create: `tauri-app/src/lib/memory-prompt-context.test.ts`
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
**Step 1: Escrever teste falho para construção de contexto dinâmico**
Sem injetar ainda no chat principal, criar builder que monte:
- `chat memory context`
- `user memory context`
separados e filtrados por confiança/categoria.
**Step 2: Implementar builder de contexto**
Regras iniciais:
- `identity/profile/preferences/constraints/goals` com prioridade alta
- `topic_pattern/interests` limitados por recência e confiança
- sem repetir fatos equivalentes
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/memory-prompt-context.test.ts`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/lib/memory-prompt-context.ts tauri-app/src/lib/memory-prompt-context.test.ts tauri-app/src/hooks/useMemoryExtractor.ts
git commit -m "feat: prepare structured memory prompt context builder"
```
---
### Task 8: Adicionar comando de consolidação segura e agendada
**Files:**
- Create: `tauri-app/src/lib/memory-orchestrator.ts`
- Modify: `tauri-app/src/components/ChatView.tsx`
- Test: `tauri-app/src/components/ChatView.test.tsx`
**Step 1: Escrever teste falho do orquestrador**
Cobrir:
- após resposta concluída, roda extração de chat
- consolidação de usuário roda desacoplada/debounced
- falha em qualquer etapa não afeta o chat
**Step 2: Implementar orquestrador leve**
Fluxo:
- `assistant turn complete` → enqueue `chat_extract`
- depois → enqueue `user_consolidate` com debounce maior
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/ChatView.test.tsx`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/lib/memory-orchestrator.ts tauri-app/src/components/ChatView.tsx tauri-app/src/components/ChatView.test.tsx
git commit -m "feat: orchestrate chat and user memory processing safely"
```
---
### Task 9: Estratégia de migração sem quebra
**Files:**
- Create: `docs/memory-migration-notes.md`
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
- Test: `tauri-app/src/lib/db.test.ts`
**Step 1: Definir política de coexistência**
- `user_memories` antiga fica preservada
- nova pipeline escreve em `chat_memories` e `user_memories_v2`
- nenhuma leitura crítica do app depende imediatamente da nova tabela
**Step 2: Opcional: migração parcial de legado**
Se necessário, criar utilitário futuro de backfill de `user_memories` → `user_memories_v2`, mas **não nesta fase**.
**Step 3: Documentar explicitamente**
Descrever:
- rollback fácil
- como desligar a feature
- como validar DB localmente
**Step 4: Commit**
```bash
git add docs/memory-migration-notes.md tauri-app/src/hooks/useMemoryExtractor.ts tauri-app/src/lib/db.test.ts
git commit -m "docs: add safe migration notes for new memory architecture"
```
---
### Task 10: Validação final e critérios de aceite
**Files:**
- No new files required
**Step 1: Rodar suíte de memória**
Run:
```bash
cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts src/lib/memory-parser.test.ts src/lib/user-memory-consolidator.test.ts src/lib/memory-prompt-context.test.ts
```
Expected: PASS
**Step 2: Rodar suíte principal**
Run:
```bash
cd tauri-app && npm test
cd tauri-app && npm run build
```
Expected: PASS
**Step 3: Validação manual**
Verificar:
- mensagem normal continua funcionando
- memórias de chat são criadas
- memórias de usuário aparecem só quando novas/mais fortes
- duplicatas não são inseridas
- se modelo de memória falhar, chat continua estável
**Step 4: Commit final**
```bash
git add .
git commit -m "feat: add safe chat and user memory architecture"
```
---
## Riscos e mitigação
### Risco 1: O modelo `gemma-3-270m-it-Q8_0.gguf` não ser bom para extração factual
**Mitigação:**
- manter parser robusto
- limitar escopo a fatos atômicos
- usar confiança + deduplicação conservadora
- tratar esse modelo como extractor heurístico, não como verdade absoluta
### Risco 2: Duplicação excessiva
**Mitigação:**
- chave normalizada
- valor normalizado
- consolidação só com fatos novos ou confiança melhor
### Risco 3: Impacto de performance
**Mitigação:**
- debounce
- background only
- janelas pequenas de contexto
- consolidação de usuário menos frequente que extração de chat
### Risco 4: Quebra do fluxo atual
**Mitigação:**
- schema aditivo
- falhas silenciosas
- nenhuma injeção automática no prompt principal nesta fase
---
## Critérios de aceitação
1. O chat principal continua funcionando sem regressão.
2. O sistema usa o modelo `models/embedding/gemma-3-270m-it-Q8_0.gguf` exclusivamente para a pipeline de memória.
3. Memórias de chat são estruturadas, classificadas e persistidas separadamente.
4. Memórias de usuário são geradas a partir das memórias de chat já processadas.
5. O sistema não duplica memórias equivalentes.
6. Existe builder separado para uso futuro dessas memórias em prompts.
7. Falhas na pipeline de memória não quebram chat, build ou testes.
---
## Recomendação de rollout
### Release 1
- schema novo
- extractor de chat
- consolidator de usuário
- sem injeção no prompt principal
### Release 2
- leitura controlada de `user_memories_v2` + `chat_memories` em prompt dinâmico
- observabilidade de qualidade
### Release 3
- integração com pesquisa web + contexto híbrido
- memória + pesquisa + multiagente no orquestrador final