Skip to content

Releases: fabiolenine/mcp_deepmen0

deepmem0-mcp-v0.3.0 — a UI do cofre navega o corpus

Choose a tag to compare

@fabiolenine fabiolenine released this 02 Aug 19:15

O cofre deixa de ser apenas o gestor de credenciais e passa a ser a interface gráfica da memória: as telas de corpus entram no mesmo app Starlette, com a mesma sessão, a mesma CSP fechada, o mesmo i18n PT/EN e a mesma auditoria. Nenhuma porta nova, nenhuma dependência nova.

Telas

Rota O que mostra
/memories Lista com facetas e cursor real
/memories/{id} Payload completo, ativação ACT-R, cadeia de versões, histórico, entidades vinculadas, proveniência de documento
/search Busca semântica com as_of, event_date e recordação histórica
/queue Fila de ingestão: profundidade, progresso por trecho, dead letters
/entities Entity store com identidade normalizada
/users Usuários e tokens (extraídos do painel)

Por que cada superfície lê de onde lê

Não é preferência — é restrição medida do servidor:

  • a navegação vai ao Qdrant direto porque get_memories é primeira-página-com-teto e não expõe cursor: num corpus maior que o teto, o excedente é inalcançável pela tool;
  • os campos de ACT-R não passam pela whitelist de metadata do search_memories;
  • o entity store não tem caminho MCP nenhum (list_entities faceta a collection principal, e as tools de grafo falam com um Neo4j que não está de pé);
  • fila e histórico saem do SQLite em mode=ro + query_only;
  • a busca é a exceção deliberada e vai pelo MCP, porque o valor está no pipeline (denso + BM25 + reranker + ativação + as_of), não no índice. Sempre com reinforce=false: navegar num console de operador não é um re-encontro da memória, e contá-lo enviesaria o ranking que a própria tela exibe.

Três achados do servidor real que moldaram o código

  1. facet() exige índice de payload. Campos como domain não têm e devolvem 400. As facetas são contadas em Python sobre um scroll projetado. Filtrar por campo sem índice funciona; só facetar é que não.
  2. A paginação é por valor, e o valor não é único. Com order_by o scroll não devolve next_page_offset, start_from é inclusivo, e os trechos de um documento nascem no mesmo created_at — um instante pode ter mais pontos que a página inteira. O cursor carrega os ids já entregues naquele instante: guardar só o timestamp repetiria o grupo para sempre, e um contador dependeria de uma ordem que a API não promete.
  3. O SDK esconde a causa. O transporte roda num task group, então um token recusado chegava como ExceptionGroup: unhandled errors in a TaskGroup. A causa é desembrulhada antes de virar mensagem.

Segurança e robustez

  • Telas de corpus estritamente de leitura — um teste lê o fonte do leitor e reprova qualquer verbo de escrita
  • Fonte indisponível não derruba a UI: vira card na tela, e as telas de credencial seguem servindo
  • O pacote sai com defaults neutros; o escopo e a collection de cada instalação vivem no env file, e o .env.example avisa que deixá-los em branco rende uma tela vazia sem erro nenhum

Gate

1278 testes verdes, paridade i18n PT/EN travada por teste, e smoke de 16 verificações contra corpus real.

deepmem0-mcp-v0.2.1 — tie-band defaults aligned with the fork

Choose a tag to compare

@fabiolenine fabiolenine released this 01 Aug 00:21

Documentation-only patch, released to keep the MCP's lineage legible next to DeepMem0 v0.13.0, which fixed the rerank_score score space.

What changed

Two env-var fallbacks in config.py were describing a score space that no longer exists. Both live inside if opt_env(...), so the second argument to env() is unreachable — nothing about behaviour changes. They were misleading to read:

  • MEM0_DYNAMICS_TIE_BAND fell back to "0.002", the tie band measured on the doubly-sigmoided axis the fork removed in v0.13.0;
  • MEM0_EVENT_TIE_BAND fell back to "0.05", the wide event band a prior review rejected for overfitting a single pair. Written there, it read like a reference value.

Both now read "0.008", matching RERANK_TIE_BAND in the fork.

Context

DeepMem0 v0.13.0 found that rerank_score is already an absolute relevance in [0, 1] — sentence-transformers applies nn.Sigmoid for a num_labels=1 cross-encoder — and the core was applying a second sigmoid on top, compressing the axis to [0.5, 0.731]. Ordering never moved (a sigmoid is monotonic), but the superseded penalty and the tie bands were measured against a compressed axis. The band constant was converted, not re-fitted.

Deployments that set either variable explicitly are unaffected; the value they set is the value used, exactly as before.

Verification

1063 tests pass (-m "not integration"). The production golden retrieval eval, run against this MCP with the v0.13.0 core, is unchanged at +0.0000 across all 16 metrics.

deepmem0-mcp-v0.2.0 — escopo de delete e readiness

Choose a tag to compare

@fabiolenine fabiolenine released this 31 Jul 19:23

Escopo de delete e readiness. Um tema só: valor errado que não produz erro.

O escopo do delete não era normalizado

O filtro do vector store é casamento EXATO. " alice" e "alice" são escopos
diferentes, e a diferença não aparece como erro — aparece como resultado vazio.
Num delete isso é pior que um crash: nada casa, nada é removido, e a chamada
responde sucesso. Medido no corpus de produção: um user_id real casa 1159
pontos; o MESMO valor com um espaço à esquerda casa 0.

delete_all_memories e delete_entities normalizam antes de montar o filtro, e
safe_bulk_delete normaliza como defesa em profundidade — só as quatro chaves de
ESCOPO, porque normalizar chave livre quebraria filtro de metadata legítimo. A
regra é importada do core (normalize_scope_id), nunca reimplementada: duas
definições de "escopo válido" foi o que deixou importance='high' entrar no
corpus.

O escopo resolvido escapava por dois caminhos

Só o argumento do cliente passava pela regra. get_default_user_id() lê por
env(), que apara apenas as BORDAS, então MEM0_USER_ID="a b" chegava como
'a b' e " " como '' — os dois sem erro, em toda tool escopada. E o
user_id amarrado ao token vinha do cofre, que nunca aplicou a regra. O valor
RESOLVIDO agora normaliza, e o erro nomeia a ORIGEM: sem isso o operador procura
o defeito no cliente enquanto ele está no drop-in do systemd.

Isso também corrige a comparação de autorização, que rodava != sobre a string
crua — um token amarrado a "alice" recusava " alice" como "token não pode
acessar esse escopo", mensagem errada para um erro de digitação.

A sonda de readiness não podia reprovar

O /health passa a expor entity_pipeline e a responder 503 quando o
pipeline de entidade está degradado. O campo era documentado e não existia.

A versão anterior chamava get_nlp_full() sem argumento, o que inspeciona o
modelo do idioma DEFAULT: dizia en_core_web_sm num deployment português.
Carregar também dispara spacy.cli.download, e uma sonda de readiness que toca a
rede pendura. O idioma vem de configured_language(), a mesma função que o
build_config() usa — uma sonda que discorda do runtime é pior que sonda
nenhuma, porque ela afirma.

Proveniência do próprio MCP no boot

O carimbo identificava só o FORK. É o servidor que decide escopo, autorização e
contrato de resposta, então um deploy dele era verificável apenas pelo
comportamento — bom para o smoke do dia, inútil no dia seguinte.
boot_mcp_sha, boot_mcp_tree_dirty e os hashes de server.py/helpers.py
entram no /health.

Testes

A suíte de integração fixava anthropic na mão enquanto o deployment roda
MEM0_PROVIDER=ollama sem token Anthropic nenhum: exercitava um provedor que
produção não usa e morria em 429 no setup. Passou a resolver com a mesma
precedência do build_config(). Medido: 8 falhas antes, 15 passed / 4
skipped
depois.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 24 Jul 12:31

v0.3.0 (2026-07-24)

Features

  • Event-date-aware ranking (v0.6) + sync to current (c0a9767)

  • v0.6 event-date-aware ranking: search_memories gains event_from/event_to
    event-time window filter + automatic query-anchor ranking (fusion boost +
    bounded post-rerank tie-break, decoupled from the ACT-R tie-break)

  • DeepMem0 Vault: bearer-token auth gate + admin UI (deepmem0-vault)

  • async ingest queue, document/vision extraction, and related server updates


Detailed Changes: v0.2.0...v0.3.0

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 19 Jul 21:34

v0.2.0 (2026-07-19)

Documentation

  • Restore fork note in README (lost in snapshot sync) (4143a4d)

Features

  • scope: Passive memory_scope passthrough (ontology v1, step 2) (0bba795)

Validates the 4-value scope enum (or null = absence) and leveled evidence on add_memory/add_document, stamps default provenance (version=1, source=manual), and exposes memory_scope + provenance fields through the metadata whitelist. No routing, no search behavior change — scope-aware retrieval is a later step gated on its own eval. Mirrors deepmen0 0.6.0 (promoted key + keyword index).


Detailed Changes: v0.1.0...v0.2.0

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 01:42

v0.1.0 (2026-07-14)

Features

  • update: Make update_memory asynchronous via the durable queue (cd51fb6)

Mirrors add_memory's async contract with a new kind="update": the tool validates the memory exists + resolves owner scope at submit, enqueues, and returns {status:"queued", task_id} immediately; the worker re-embeds + re-classifies the metadata in the background. An identical re-submit while the job is active returns the same task_id (sentinel idempotency key) — no double-apply. Fixes the ambiguous client-timeout on the previously synchronous update path (the update succeeded server-side while the client saw a timeout).


Detailed Changes: v0.0.0...v0.1.0

v0.0.0

Choose a tag to compare

@github-actions github-actions released this 09 Jul 18:13

v0.0.0 (2026-07-09)