Skip to main content
Glama
README.md
# Recall

**Memória persistente para agentes de IA, via MCP.** O agente guarda o que aprende sobre você e o projeto, busca isso nas próximas conversas e deixa esquecer o que ninguém usa. Um painel local mostra tudo: o grafo de memórias, a linha do tempo e o que ele lembrou em cada sessão.

![O grafo de memórias se formando ao longo de nove sessões](docs/recall-replay.gif)

**[Ver ao vivo](https://leandromlmoreira.github.io/recall-mcp/)**: o painel rodando no navegador com um banco de exemplo (memórias fictícias de um agente ajudando uma dev a construir um app de feira livre). Aperte o play na linha do tempo.

| Uma memória aberta | Tema claro | No celular |
| --- | --- | --- |
| ![Memória aberta com retenção, ligações e histórico](docs/painel-memoria.png) | ![Painel no tema claro](docs/painel-claro.png) | ![Painel em 375 px](docs/painel-celular.png) |

---

## Por que

Cada conversa com um agente começa do zero. Você explica de novo a porta da API, a decisão sobre autenticação, que não quer Tailwind. O Recall dá ao agente uma memória de longo prazo que fica na sua máquina, num arquivo SQLite, e que se comporta um pouco como a nossa:

- **Lembra pelo sentido, não só pela palavra exata.** A busca combina BM25 com embeddings locais.
- **Esquece o que não usa.** Cada memória tem uma meia-vida. Ser lembrada a reforça; ficar esquecida a apaga devagar do ranking (sem apagar o dado).
- **Sabe o que foi substituído.** Quando um fato novo substitui um antigo, o antigo perde prioridade e aparece marcado.
- **Não duplica.** Guardar de novo algo que ela já sabe só reforça a memória existente.

## Ferramentas MCP

| Ferramenta | O que faz |
| --- | --- |
| `remember` | Guarda um fato com tags, fonte e importância. Pode já ligar a outras memórias. Quase duplicatas são mescladas. |
| `recall` | Busca híbrida (BM25 + embeddings) ponderada pela retenção. As memórias devolvidas são reforçadas. |
| `forget` | Apaga uma memória de vez (e as ligações dela). |
| `link` | Liga duas memórias: `supersedes`, `caused-by`, `depends-on`, `fixes`, `related`. |
| `timeline` | O que foi guardado, lembrado, ligado ou esquecido, por período, tag ou só nesta sessão. |
| `reinforce` | Marca uma memória como confirmada para ela decair mais devagar. |

O servidor também manda `instructions` para o cliente: buscar no começo de uma tarefa, guardar fatos duráveis em uma frase, nunca guardar segredos.

## Como usar

> O pacote está pronto para o npm (`@leandromlmoreira/recall-mcp`), mas ainda não foi publicado. Enquanto isso, rode a partir do código:
>
> ```bash
> git clone https://github.com/leandromlmoreira/recall-mcp && cd recall-mcp
> npm install && npm run build
> ```
>
> Nos exemplos abaixo, troque `npx -y @leandromlmoreira/recall-mcp` por `node /caminho/para/recall-mcp/dist/cli.js`.

Precisa de Node 22.13 ou mais novo (usa o `node:sqlite` nativo, sem compilar nada).

### Claude Code

```bash
claude mcp add recall -- npx -y @leandromlmoreira/recall-mcp
```

### Claude Desktop

Em `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "recall": {
      "command": "npx",
      "args": ["-y", "@leandromlmoreira/recall-mcp"],
      "env": { "RECALL_DB": "/Users/voce/.recall/memory.db" }
    }
  }
}
```

### O painel

```bash
npx @leandromlmoreira/recall-mcp ui --open     # painel do seu banco, em http://localhost:4417
npx @leandromlmoreira/recall-mcp demo --open   # painel com o banco de exemplo
```

O painel lê o mesmo arquivo que o servidor MCP escreve. Enquanto o agente trabalha numa conversa, o grafo cresce ao vivo (Server-Sent Events). O painel só lê: nada nele altera a memória, nem a busca.

| Opção | Padrão | Para quê |
| --- | --- | --- |
| `RECALL_DB` ou `--db` | `~/.recall/memory.db` | Onde fica o banco. Um por projeto funciona bem. |
| `RECALL_EMBEDDINGS` | `hash` | `hash` (local e instantâneo), `minilm` (semântico, precisa do pacote opcional `@huggingface/transformers` e baixa o modelo uma vez) ou `none` (só BM25). |
| `--port` | `4417` | Porta do painel (só escuta em `127.0.0.1`). |

Outros comandos: `recall stats` e `recall export` (o banco inteiro em JSON, para backup).

## Uma conversa de exemplo

Numa sessão, a dev comenta que trocou a porta da API. O agente guarda e liga ao fato antigo:

```text
> Mudei a API pra 8787, a 3000 batia com meu Grafana.

● recall · remember("A API agora sobe na porta 8787: a 3000 conflitava com o Grafana local.",
                    tags: [backend], link_to: [mem_xytybd], relation: supersedes)
  ⎿ Remembered mem_kzwc7b.
```

Dias depois, em outra conversa:

```text
> Em que porta a API roda? O cliente precisa de conta?

● recall · recall("porta da api")
  ⎿ 2 of 36 memories for "porta da api":

    [mem_kzwc7b] · score 0.78 · retention 0.94 · 18d ago · tags: backend, deploy
    A API agora sobe na porta 8787: a 3000 conflitava com o Grafana local da Marina.
    source: apps/api/.env.example
    links: supersedes → mem_xytybd

    [mem_xytybd] · score 0.25 · retention 0.41 · outdated, superseded by mem_kzwc7b · 25d ago · tags: backend
    A API sobe na porta 3000 em desenvolvimento.
    links: supersedes ← mem_kzwc7b

● A API roda na 8787. Vocês trocaram da 3000 no dia 8 porque conflitava com o Grafana.
```

## Como funciona

### Busca híbrida

Para cada memória candidata:

```
texto     = BM25(consulta, conteúdo + tags) / maior BM25 da consulta
sentido   = cosseno(embedding(consulta), embedding(memória)), calibrado para 0..1
combinado = 0,6 · texto + 0,4 · sentido
pontuação = combinado · (0,35 + 0,65 · retenção) · (0,4 se foi substituída)
```

Resultados abaixo de 30% da melhor pontuação são descartados, para a resposta não vir cheia de ruído. O tokenizador entende português e inglês (acentos, stopwords, plurais como `notificações → notificação`).

O embedding padrão é um **hashing de n-gramas** (palavras, pares de palavras e trigramas de caracteres em 1024 dimensões): roda em microssegundos, sem rede, e pega variações de forma como `autenticar` e `autenticação`. Ele não entende sinônimos. Para isso existe o modo `minilm`, opcional, com o `all-MiniLM-L6-v2` rodando localmente via transformers.js.

### Decaimento

```
meia-vida = 7 dias · (1 + log2(1 + usos)) · (0,5 + importância)
retenção  = 0,5 ^ (dias desde o último uso / meia-vida)
```

Uma memória nova e nunca usada cai para 50% em uma semana. Usada 3 vezes, a meia-vida vai para 21 dias. Reforços muito próximos (menos de 10 minutos) não contam duas vezes, como na repetição espaçada. Nada é apagado por decaimento: a memória só desce no ranking e aparece como "esquecendo" no painel.

### Armazenamento

SQLite em modo WAL com `node:sqlite`. O esquema é versionado por `PRAGMA user_version` e evolui por migrações em transação (a v3, por exemplo, move as tags de uma coluna JSON para uma tabela normalizada, sem perder dados). Os vetores ficam salvos no banco; se você trocar de embedder, eles são recalculados na abertura.

## Arquitetura

```mermaid
flowchart LR
  A[Claude Code / Claude Desktop] -- stdio, MCP --> S[recall serve]
  S --> ST[RecallStore]
  ST --> DB[(SQLite WAL)]
  UI[recall ui] --> ST2[RecallStore somente leitura] --> DB
  UI -- JSON + SSE --> P[Painel Vite]
  P -. mesmo motor .-> CORE[src/core]
  ST --> CORE
  DEMO[GitHub Pages] -- banco de exemplo + motor no navegador --> P
```

```
src/core/      motor isomórfico: tokenizador, BM25, embeddings, decaimento, ranking
src/store/     SQLite, migrações, embedders
src/server/    servidor MCP (ferramentas) e servidor HTTP do painel
src/demo/      o roteiro fictício que gera o banco de exemplo
web/           painel (Vite + TypeScript, canvas com d3-force)
test/          unidade e integração (Vitest)
e2e/           Playwright no painel de demonstração
```

O motor de busca é o mesmo no servidor e no navegador: a demonstração do GitHub Pages carrega o banco de exemplo e faz a busca híbrida localmente, sem backend.

## Stack

TypeScript, SDK oficial do MCP (`@modelcontextprotocol/sdk`) com Zod, `node:sqlite`, Vite, d3-force e d3-zoom, fontes Geist, ícones Phosphor. Testes com Vitest e Playwright. CI no GitHub Actions (Node 22 e 24) e deploy da demonstração no GitHub Pages.

## Desenvolvimento

```bash
npm install
npm run dev            # painel com o banco de exemplo em http://localhost:4416
npm run build          # servidor em dist/ e painel em dist/web
npm run build:demo     # versão para o GitHub Pages em dist-demo/
npm run seed:demo      # regenera o banco de exemplo a partir de src/demo/dataset.ts
```

## Testes

```bash
npm run lint
npm run typecheck
npm test               # unidade + integração
npm run test:e2e       # Playwright no painel de demonstração (desktop e 375 px)
```

- **Unidade:** tokenizador, BM25, embeddings, decaimento e reforço espaçado, ranking híbrido, substituições, migrações (incluindo atualizar um banco da v1 com dados e reverter uma migração que falha).
- **Integração:** um cliente MCP real (`Client` + `StdioClientTransport` do SDK) sobe o servidor por stdio, lista as ferramentas e faz `remember → recall → link → forget → timeline`. O servidor HTTP do painel é testado com banco real, incluindo SSE, bloqueio de host externo e path traversal.
- **E2E:** busca, abrir memória, reproduzir a linha do tempo, teclado no grafo e no controle deslizante, troca de tema e de sessão, sem erro no console e sem rolagem horizontal.

## Limites conhecidos

- Pensado para uma pessoa, na própria máquina. Não há autenticação no painel (ele só escuta em localhost).
- O embedding padrão é lexical. Sinônimos de verdade precisam do modo `minilm`.
- A busca carrega o índice em memória: ótimo até dezenas de milhares de memórias, não é um banco vetorial.

## Licença

MIT

TDQS

A4/5.0

Scored across 6 tools

Disambiguation5/5

Each tool maps to a distinct memory operation: store (remember), retrieve (recall), delete (forget), connect (link), log (timeline), and reinforce. No two tools overlap in purpose, and descriptions clearly delineate boundaries.

Naming Consistency4/5

Five of six tools are single-word imperative verbs (remember, recall, forget, link, reinforce), but timeline is a noun, breaking the verb pattern. Still readable and mostly consistent, with only this minor deviation.

Tool Count5/5

Six tools are well-scoped for a long-term memory server, covering core actions without bloat. Each tool earns its place and the set feels neither thin nor heavy.

Completeness4/5

Core lifecycle is covered: create, read, delete, link, reinforce, and event log. No direct update operation, but replacing a fact via remember + supersedes link handles it; minor gap but workable.

Maintenance

ActivityMaintained
ResponsivenessNo issues