feat(memory): memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (plan 21) - #2873
Conversation
… tests (plan 21 F1) - src/lib/memory/embedding/types.ts — EmbeddingSource, EmbeddingProviderListing, EmbeddingResolution, EmbeddingResult, EmbeddingError (verbatim §3.1) - src/shared/schemas/memory.ts — 7 Zod schemas (MemorySettingsExtended, MemoryUpdatePut, RetrievePreview, MemoryReindex, MemorySummarize, EmbeddingProviderListing, MemoryEngineStatus, RetrievePreviewResult) + z.infer types (verbatim §3.2) - src/shared/schemas/qdrant.ts — 4 Zod schemas (QdrantSettings, QdrantSettingsUpdate, QdrantSearch, QdrantHealthResult) + z.infer types (verbatim §3.3) - tests/unit/memory-schemas-roundtrip.test.ts — 34 assertions (≥22 required); all pass
…e-export (plan 21 F2) - 073_memory_vec.sql: creates memory_vec_meta singleton table (active_dim, embedding_signature, last_reset_at, vec_loaded) and adds needs_reindex column to memories table with a partial index; idempotent via CREATE IF NOT EXISTS + INSERT OR IGNORE + migration runner's duplicate-column-name guard - src/lib/db/memoryVec.ts: implements 6 CRUD functions per §3.8 contract (getMemoryVecMeta, setMemoryVecMeta, markMemoryNeedsReindex, markAllMemoriesNeedReindex, getMemoryReindexQueue, countMemoryReindexPending) - src/lib/localDb.ts: adds re-export block for the 6 functions (Hard Rule #2) - .env.example: documents 7 new MEMORY_* env vars per §3.9 - tests/unit/memory-vec-meta.test.ts: 7 tests (meta get/set, migration idempotency) - tests/unit/memory-needs-reindex.test.ts: 12 tests (mark/unmark, markAll, queue)
…(plan 21 F3)
Implements the multi-source embedding layer for the Memory Engine Redesign (plan 21).
Adds 5 production modules under src/lib/memory/embedding/:
- cache.ts: LRU+TTL in-memory cache (max=1000, TTL=5min, sha256 keyed)
- remote.ts: delegates to createEmbeddingResponse(), maps HTTP 401/403→no_key, 429→rate_limited, AbortError→timeout; all errors via sanitizeErrorMessage()
- staticPotion.ts: download-once potion-base-8M (JS-only WordPiece tokenizer + mean pooling, no WASM)
- transformersLocal.ts: lazy await import('@huggingface/transformers') singleton pipeline (Xenova/all-MiniLM-L6-v2, q8)
- index.ts: resolveEmbeddingSource (pure, sync), embed (cached dispatch), listEmbeddingProviders, invalidateEmbeddingCache
Also adds @huggingface/transformers and sqlite-vec to dependencies, and registers
@huggingface/transformers in next.config.mjs serverExternalPackages (D8/D25).
6 unit test files: cache (9), resolve (14), remote (10), static-potion (13), transformers (6), list-providers (8) — all 60 tests green.
…n 21 F4) Implements VectorStore interface contract from master plan 21 §3.4: - sqlite-vec v0.1.9 extension loaded via createRequire (ESM compat) - vec0 virtual table with FLOAT[N] dimensions driven by EmbeddingResolution - Upsert via DELETE+INSERT (vec0 does not support INSERT OR REPLACE) - BigInt rowids required by vec0 v0.1.9 for primary key insertion - Hybrid RRF (k=60) fusing FTS5 + vector KNN via UNION ALL + GROUP BY - FTS join on m.memory_id = fts.rowid (migration 023 bridge column) - VECTOR_STORE_DISABLE_VEC=true test seam for null-extension path - sanitizeErrorMessage in 3 error paths (Hard Rule #12) - Raw SQL exception documented in header comment (Hard Rule #5 §D5) - 27 unit tests across 5 files; all lint/typecheck/cycles checks pass
… (plan 21 F5) - retrieval.ts — semantic/hybrid usa vectorStore quando disponível; degrada para FTS5 transparente - retrieval.ts — adiciona retrievePreview() (dry-run para Playground) e engineStatus() - retrieval.ts — rerank opcional via provider configurado (D13) - store.ts — createMemory/updateMemory geram vetor best-effort; deleteMemory sincroniza vec + Qdrant (D15) - settings.ts — 7 campos novos (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore) com defaults - summarization.ts — summarizeMemoriesOlderThan exposta para uso manual (D19) - reindex.ts (novo) — runReindexBatch processa fila lazy de backfill (D21) - 9 testes unitários adicionados; testes F1-F4 sem regressão
…ry types (plan 21 F8) - memoryTools.ts: replace hardcoded retrievalStrategy:"exact" with getMemorySettings()+toMemoryRetrievalConfig(); fallback to "exact" on catch - memory.mjs: VALID_TYPES updated to ["factual","episodic","procedural","semantic"]; default changed from "user" to "factual"; legacy types (user/feedback/project/reference) emit deprecation warning and map to "factual" - tests: mcp-memory-tools-strategy.test.ts (7 cases) + cli-memory-types.test.mjs (12 cases)
…sKey check (TS18047) The previous expression was parsed as (A && B && C) || D, allowing D to evaluate with creds possibly null. Wrap (apiKey || accessToken) in parens so creds-narrowing covers the whole disjunction.
…plan 21) New routes: - POST /api/memory/retrieve-preview (dry-run playground) - GET /api/memory/embedding-providers - GET /api/memory/engine-status - POST /api/memory/summarize - POST /api/memory/reindex - GET/PUT /api/settings/qdrant - GET /api/settings/qdrant/health - POST /api/settings/qdrant/search - POST /api/settings/qdrant/cleanup Modified: - PUT /api/memory/[id] added (Hard Rule #12 sanitize) - /api/memory/route.ts: Hard Rule #12 fix (sanitizeErrorMessage) - /api/settings/memory/route.ts: MemorySettingsExtendedSchema (D9 7 new fields) Tests: 7 integration test files (33 tests total) all passing. Hard Rules #5, #7, #8, #12 verified.
…y tests - store.ts: wrap markMemoryNeedsReindex in safeMarkNeedsReindex helper that swallows errors when the DB is no longer available (e.g. test teardown after the parent promise resolved). Prevents fire-and-forget vector upserts from triggering unhandledRejection in tests. - memory-store.test.ts: drain setImmediate in afterEach/after hooks so pending vector upsert tasks settle before DATA_DIR is removed. - memory-settings.test.ts: extend deepEqual expected shape with the 7 new fields introduced by plan 21 F5 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore).
Converts the monolithic memory page into a 3-tab Studio layout (Memories | Playground | Engine) with URL-driven tab state, 8 new React components, 2 SWR hooks, 50+ i18n keys, and 8 Vitest unit tests covering all new components (45/45 passing).
F7 originally implemented useEngineStatus/useMemorySettings via swr, but the package is not in package.json — would crash at runtime. Replaced with native useState + useEffect + setInterval polling. Same public API (status/settings/isLoading/isError/mutate/save) so the existing components and the 8 UI tests (which mock the hooks directly) keep working unchanged.
…i18n sort
- Add tests/e2e/memory-engine.spec.ts (7 scenarios: 3-tab render, memories table,
add/edit modal, playground simulate, engine status chips, reindex button)
- Add tests/e2e/memory-qdrant-routes.spec.ts (3 scenarios: Qdrant config card,
Test Connection → sanitized error without stack, Cleanup → sanitized error)
- Update docs/frameworks/MEMORY.md: add Engine architecture (3-tier ASCII diagram),
Embedding sources table, Hybrid RRF (k=60) section, Backfill lazy+reindex section,
Settings extension (7 new fields D9), updated REST API table (10 new routes),
updated Dashboard section (Studio 3 tabs), MCP D16 strategy from settings, See Also
- Update docs/reference/openapi.yaml: add Memory tag + 13 new paths
(/api/memory, /api/memory/{id} PUT, retrieve-preview, embedding-providers,
engine-status, summarize, reindex, /api/settings/memory GET+PUT,
/api/settings/qdrant GET+PUT+health+search+cleanup+embedding-models)
+ MemoryEntry, MemorySettingsExtended, QdrantSettings, QdrantHealthResult schemas
- Update docs/architecture/REPOSITORY_MAP.md: add embedding/, vectorStore.ts,
reindex.ts, memoryVec.ts, memory Studio UI entries
- Sort memory.* namespace keys alphabetically in pt-BR.json and en.json
(no duplicate found — pure reorder, no content change)
- .env.example already has all 7 vars from §3.9 (confirmed, no change needed)
…ing + env doc tweaks Plan 21 / D17 — CLI now remaps legacy types (user/feedback/project/reference) to canonical (factual/episodic/procedural/semantic). The legacy assertion in cli-memory-commands.test.ts still expected 'user'; update to expect 'factual'. Also includes incidental .env.example + docs/ENVIRONMENT.md + qdrant embedding-models route tweaks captured by F10 audit pass.
…vitest:ui script Gap 1 (auditor deep review): `GET /api/memory` was computing hitRate from memoryCache.stats() but never exposing cacheStats in the response. MemoriesTab reads `stats.cacheStats` to decide whether to render the Hit Rate card (only when hits + misses > 0). Without this field the card never appeared even when hitRate > 0, contradicting plan 21 §7 #6 and bug-fix #5. Gap 2 (auditor deep review): 8 `tests/unit/ui/*.test.tsx` files created by F7 were orphaned — `test:unit` filters `*.test.ts` only, and `vitest.mcp.config.ts` does not include `tests/unit/ui/`. Added `test:vitest:ui` script using the default vitest.config.ts (which already includes `tests/unit/**/*.test.tsx`). Coverage gate aligned with the effective 40/40/40/40 per user decision.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Kilo Code Review could not run — your account is out of credits. Add credits or switch to a free model to enable reviews on this change. |
There was a problem hiding this comment.
Code Review
This pull request implements the Memory Engine Redesign (Plan 21), introducing a multi-source embedding layer, sqlite-vec hybrid RRF, Qdrant tier 2 integration, and a new 3-tab Memory Studio dashboard. Feedback highlights a critical multi-tenant data leakage vulnerability in the summarization logic when apiKeyId is undefined. Additionally, the reduction of test coverage thresholds in package.json violates repository style guidelines. Other recommendations include optimizing the safetensors parser to avoid blocking the event loop, correcting the LRU cache eviction logic, handling empty metadata strings gracefully in the edit modal, and parallelizing sequential memory imports.
| export async function summarizeMemoriesOlderThan( | ||
| apiKeyId: string | undefined, | ||
| days: number, | ||
| dryRun: boolean | ||
| ): Promise<SummarizeOlderThanResult> { | ||
| const db = getDbInstance(); | ||
|
|
||
| const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); | ||
|
|
||
| const rows: MemoryRow[] = apiKeyId | ||
| ? (db | ||
| .prepare( | ||
| "SELECT * FROM memories WHERE api_key_id = ? AND created_at < ? ORDER BY created_at ASC" | ||
| ) | ||
| .all(apiKeyId, cutoff) as MemoryRow[]) | ||
| : (db | ||
| .prepare("SELECT * FROM memories WHERE created_at < ? ORDER BY created_at ASC") | ||
| .all(cutoff) as MemoryRow[]); |
There was a problem hiding this comment.
Se apiKeyId for undefined, a função buscará memórias antigas de todas as chaves de API (diferentes usuários/clientes), mesclará os conteúdos de todos eles em um único resumo com apiKeyId = "" e excluirá as memórias originais de cada usuário. Isso causará perda de dados e vazamento de informações privadas entre diferentes chaves de API. A compactação deve ser agrupada e executada estritamente por api_key_id.
| "test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs", | ||
| "test:system": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=1 tests/e2e/system-failover.test.ts", | ||
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", | ||
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 40 --lines 40 --functions 40 --branches 40 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", |
There was a problem hiding this comment.
A alteração reduz o limite de cobertura de testes para 40%, o que viola diretamente a Regra Rígida #10 do guia de estilo do repositório (que exige cobertura ≥ 75% para statements/lines/functions e ≥ 70% para branches). Por favor, reverta essa alteração e garanta que a cobertura atenda aos requisitos mínimos estabelecidos.
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 40 --lines 40 --functions 40 --branches 40 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", | |
| "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 75 --lines 75 --functions 75 --branches 70 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", |
References
- Coverage must stay ≥ 75 % statements / 75 % lines / 75 % functions / 70 % branches (link)
|
|
||
| const floatCount = (dataEnd - dataStart) / 4; | ||
| const arr = new Float32Array(floatCount); | ||
| for (let i = 0; i < floatCount; i++) { | ||
| arr[i] = dataSlice.readFloatLE(i * 4); | ||
| } |
There was a problem hiding this comment.
O loop manual usando readFloatLE para ler cada float do modelo safetensors é extremamente ineficiente e pode bloquear o event loop por vários segundos ao carregar um modelo de 8M parâmetros. Como os dados no safetensors são alinhados, você pode criar o Float32Array instantaneamente compartilhando a memória do buffer subjacente, evitando qualquer cópia ou loop.
| const floatCount = (dataEnd - dataStart) / 4; | |
| const arr = new Float32Array(floatCount); | |
| for (let i = 0; i < floatCount; i++) { | |
| arr[i] = dataSlice.readFloatLE(i * 4); | |
| } | |
| const floatCount = (dataEnd - dataStart) / 4; | |
| const arr = new Float32Array( | |
| dataSlice.buffer, | |
| dataSlice.byteOffset, | |
| floatCount | |
| ); | |
| return { matrix: arr, shape: meta.shape }; |
|
|
||
| export function get(key: string): Float32Array | undefined { | ||
| const entry = store.get(key); | ||
| if (!entry) { | ||
| missCount++; | ||
| return undefined; | ||
| } | ||
| if (Date.now() - entry.ts > getTtl()) { | ||
| store.delete(key); | ||
| missCount++; | ||
| return undefined; | ||
| } | ||
| hitCount++; | ||
| return entry.vector; | ||
| } |
There was a problem hiding this comment.
O cache está se comportando como FIFO (First-In, First-Out) em vez de LRU (Least Recently Used). No JavaScript, a ordem de inserção em um Map é preservada, mas para implementar um comportamento LRU real, os itens acessados com sucesso no método get devem ser reordenados (removidos e reinseridos) para evitar que itens frequentemente usados sejam despejados precocemente.
| export function get(key: string): Float32Array | undefined { | |
| const entry = store.get(key); | |
| if (!entry) { | |
| missCount++; | |
| return undefined; | |
| } | |
| if (Date.now() - entry.ts > getTtl()) { | |
| store.delete(key); | |
| missCount++; | |
| return undefined; | |
| } | |
| hitCount++; | |
| return entry.vector; | |
| } | |
| export function get(key: string): Float32Array | undefined { | |
| const entry = store.get(key); | |
| if (!entry) { | |
| missCount++; | |
| return undefined; | |
| } | |
| if (Date.now() - entry.ts > getTtl()) { | |
| store.delete(key); | |
| missCount++; | |
| return undefined; | |
| } | |
| store.delete(key); | |
| store.set(key, entry); | |
| hitCount++; | |
| return entry.vector; | |
| } |
| const handleMetadataChange = (value: string) => { | ||
| setMetadataStr(value); | ||
| try { | ||
| JSON.parse(value); | ||
| setMetadataError(""); | ||
| } catch { | ||
| setMetadataError(t("editModal.metadataInvalid")); | ||
| } | ||
| }; | ||
|
|
||
| const handleSave = async () => { | ||
| if (!memory) return; | ||
| if (metadataError) return; | ||
| setIsSaving(true); | ||
| setError(""); | ||
| try { | ||
| let metadata: Record<string, unknown> = {}; | ||
| try { | ||
| metadata = JSON.parse(metadataStr); | ||
| } catch { | ||
| setError(t("editModal.metadataInvalid")); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Se o usuário limpar o campo de metadados (deixando-o vazio), JSON.parse(value) lançará um erro, impedindo o salvamento mesmo que um objeto vazio seja aceitável. É recomendável tratar strings vazias como um objeto vazio {} válido.
const handleMetadataChange = (value: string) => {
setMetadataStr(value);
if (!value.trim()) {
setMetadataError("");
return;
}
try {
JSON.parse(value);
setMetadataError("");
} catch {
setMetadataError(t("editModal.metadataInvalid"));
}
};
const handleSave = async () => {
if (!memory) return;
if (metadataError) return;
setIsSaving(true);
setError("");
try {
let metadata: Record<string, unknown> = {};
try {
metadata = metadataStr.trim() ? JSON.parse(metadataStr) : {};
} catch {
setError(t("editModal.metadataInvalid"));
return;
}
| for (const m of memoriesToImport) { | ||
| if (!m.key || !m.content) { | ||
| skipped++; | ||
| continue; | ||
| } | ||
| const res = await fetch("/api/memory", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| type: m.type || "factual", | ||
| key: m.key, | ||
| content: m.content, | ||
| metadata: m.metadata || {}, | ||
| }), | ||
| }); | ||
| if (res.ok) imported++; | ||
| else skipped++; | ||
| } |
There was a problem hiding this comment.
Importar memórias sequencialmente usando await dentro de um loop for pode causar sérios problemas de desempenho se o arquivo contiver muitas entradas. Considere processar as requisições em paralelo usando Promise.all (com um limite de concorrência se necessário) ou criar um endpoint de importação em lote (bulk import) no backend.
Code review of plan 21 found two functional gaps: FAIL #1 — toMemoryRetrievalConfig never forwarded the user query, so the gate `if (config.query && useModernTable)` in retrieval.ts was always false in the chat hot path. semantic/hybrid silently fell back to "ORDER BY created_at DESC LIMIT 100", the pre-plan-21 behaviour. sqlite-vec + RRF only ran in the Playground (retrievePreview, which takes `query` positionally). FAIL #2 — Bug #1 was not closed: searchSemanticMemory (Qdrant) was only imported by /api/settings/qdrant/search, never by retrieval. With vectorStore="qdrant", engineStatus reported backend="qdrant" but retrieveMemories/retrievePreview kept using sqlite-vec — the status diverged from the actual search path. - settings.ts: toMemoryRetrievalConfig(settings, { query? }) accepts and forwards the query. - chatCore.ts: extracts the last user message from body.messages or body.input (Chat + Responses APIs). - retrieval.ts: adds a Qdrant branch in case "semantic", case "hybrid" and retrievePreview; falls through to sqlite-vec on failure or empty results so the §7 "degrades to sqlite-vec / FTS5" contract holds. - engineStatus only reports backend="qdrant" when the user opted in (settings.vectorStore === "qdrant") and Qdrant is healthy. - memories[] tier union includes "qdrant". 331 memory unit tests pass; typecheck:core / typecheck:noimplicit:core / check:cycles clean.
D12 of master-plan-21 assumed next-intl had a built-in fallback to EN
already configured. It did not — request.ts loaded only
messages/${locale}.json and no getMessageFallback was defined, so any
key absent in the user's locale rendered the key path literally
(for example "memory.concept.title").
Plan 21 added 156 memory.* keys to en and pt-BR but the other 39
locales kept only the pre-existing 36 memory.* keys, so users on those
locales saw raw key paths across the new Memory studio.
Fix: load en.json as the base and deep-merge the locale-specific
messages on top. Existing translations are untouched; only missing
keys fall back to English. Satisfies §7 "i18n 41 locales".
Smaller fixes from the 2nd code-review pass on plan 21. Backend / CLI / DB: - memoryTools.ts: error-path fallback no longer hardcodes retrievalStrategy:"exact"; uses DEFAULT_MEMORY_SETTINGS via toMemoryRetrievalConfig (D16 / Bug #7). - memory.mjs: applyLegacyTypeMap also runs on search / list / clear (was only on add); legacy user/feedback/project/reference remap to canonical types with a stderr warning (D17 / Bug #4). - migrationRunner.ts: case "073" guards via hasColumn(memories, needs_reindex) so an unmarked re-run of 073_memory_vec.sql is skipped cleanly (D27). UI: - MemoryEngineStatus: optional onConfigure callback; "Configurar →" CTAs on the Embedding / Qdrant / Rerank rows when those components are off or missing (matches §4.3 wireframe). - EngineTab: scroll IDs on config cards + handleConfigure wired to the status panel. Providers fetch moved from render body (setState-during-render anti-pattern) into useEffect. - RerankConfigCard: toggle is disabled when no provider has a key — blocks turning rerank ON without a provider, still allows turning it OFF (D13). - MemoriesTab: Import validates each entry against the canonical type enum before POST so invalid types are caught locally with a clear skipped count. Tooling: - package.json: test:all includes test:vitest:ui so the UI suite is no longer orphaned in CI. Tests: - cli-memory-commands: asserts updated for the new legacy->canonical remap on search/clear. - memory-embedding-resolve: drop always-true `|| reason.length > 0` clauses that neutralized two assertions. - memory-embedding-static-potion: model_load_failed test forces a real load failure via MEMORY_STATIC_CACHE_DIR=/dev/null/<subdir> and asserts EmbeddingError shape + reason + sanitized message (replaces the previous `assert.ok(true)`). - rerank-config-card.test.tsx: happy-path now uses a provider with hasKey; new test covers the disabled-toggle guard. Full memory suite green: 331/331 unit tests, 46/46 UI tests. typecheck:core, typecheck:noimplicit:core, check:cycles clean.
…8n key
Third code-review pass on plan 21 found two follow-up issues from the
previous round.
1. extractLastUserText accepted Responses API items with role===undefined
regardless of their type. function_call_output / tool_call_output /
reasoning items would slip through and be treated as user query input,
leaking the tool's reply or the model's chain of thought into the
memory retrieval query.
Fix: when role is undefined, skip items whose type is in a denylist of
non-user item types (function_call, function_call_output, tool_call,
tool_call_output, reasoning, computer_call, computer_call_output,
web_search_call, file_search_call). Also reject non-text content
parts inside multi-modal arrays (image_url, tool_use, ...) so that
image-only or tool-only user messages do not produce a query made of
irrelevant fragments.
2. MemoryEngineStatus introduced t("engine.configureCta") in round 2 but
the key was never added to en.json / pt-BR.json — even with the new
EN fallback merger, the CTA would render the literal key path.
Added "Configure" / "Configurar" to both locales.
Verified: typecheck:core clean; vitest UI 46/46; cli-memory-commands,
memory-settings, and mcp-memory-tools-strategy isolated sanity all
green; grep audit of memory.* i18n keys used by the UI confirms zero
missing keys in en.json.
…n — sqlite-vec + RRF + Studio) Conflicts: migration 073_memory_vec->083; localDb/.env/REPOSITORY_MAP union; request.ts->base; i18n auto-merged; .source regenerated (+3 docs). openapi: --theirs base + surgically inserted 14 memory paths + 4 schemas + Memory tag via js-yaml extract (union-blind broke YAML structure). +938 lines, base formatting preserved, gen-openapi validates. deps: @huggingface/transformers + sqlite-vec added (package.json); npm install ran, lock regenerated. chatCore auto-merged (memory + quota hooks coexist, transform OK). typecheck:core 0 errors.
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (#2859/#3022/#3032), page redesigns (#2827/#2839/#2847/#2849/#2869/#2873), and fixes #2960/#2973/#2984/ #3021/#3029/#3031/#3035/#3036/#3037/#3039/#3043/#3028; folded #2978/#2988/#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-21-memory-engine-redesign feat(memory): memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (plan 21)
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-21-memory-engine-redesign feat(memory): memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (plan 21)
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…-v3-21-memory-engine-redesign feat(memory): memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (plan 21)
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (diegosouzapw#2859/diegosouzapw#3022/diegosouzapw#3032), page redesigns (diegosouzapw#2827/diegosouzapw#2839/diegosouzapw#2847/diegosouzapw#2849/diegosouzapw#2869/diegosouzapw#2873), and fixes diegosouzapw#2960/diegosouzapw#2973/diegosouzapw#2984/ diegosouzapw#3021/diegosouzapw#3029/diegosouzapw#3031/diegosouzapw#3035/diegosouzapw#3036/diegosouzapw#3037/diegosouzapw#3039/diegosouzapw#3043/diegosouzapw#3028; folded diegosouzapw#2978/diegosouzapw#2988/diegosouzapw#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
Summary
Implementa o plano 21 (Memory Engine Redesign) — refaz a tela
/dashboard/memorycomo Studio com 3 abas, conserta toda a dívida técnica do subsistema, e entrega busca semântica REAL embarcada via sqlite-vec + hybrid RRF (k=60) com camada de embedding multi-source (provider remoto · static potion local · transformers.js MiniLM opt-in), rerank opcional OFF por default, e Qdrant como tier 2 opt-in.Entregas principais
/dashboard/memory) — 3 abas (Memórias / Playground / Engine) + Memory Concept Card explicativo.keyword → embedding → vector store → Qdrant → rerankcom chips 🟢/⚪/🔴 + CTA.retrieveMemoriesmostrando score · tokens · tier (FTS5/vector/hybrid-RRF/Qdrant) · embedding model usado.auto(default) → remote → static potion → transformers.js MiniLM (opt-in com aviso de RAM).vec_memoriescom dim dinâmico; degradação transparente para FTS5 se sqlite-vec não carregar.PUT /api/memory/[id]+EditMemoryModal.memory_searchlê strategy das settings (fim do hardcoded"exact").factual/episodic/procedural/semantic(legacy types remapeados com warning).POST /api/memory/summarize+ botão "Compactar antigas" com dryRun + confirm.POST /api/memory/reindex+ botão "Reindex Now" com polling do engine-status.Bugs corrigidos (todos do §2.4 do plano)
retrieval.ts/api/settings/qdrant{,/health,/search,/cleanup}404 → 5 rotas criadasdeleteMemory()não sincronizava vetor → agora chamavec.deleteVector()EdeleteSemanticMemoryPoint()antes do DELETE SQLiteVALID_TYPES+LEGACY_TYPE_MAPcom warninggetMemoryTokensUsed()(SQL);cacheStatsexposto na response → HitRate card renderiza condicional"exact"→ lê degetMemorySettings()Decisões arquiteturais (master-plan-21 §0/§11)
sqlite-vec@^0.1.9pinado; brute-force KNN apenas; fallback para FTS5 se não carregar.memory_vec_meta(active_dim, embedding_signature)gerencia troca de modelo com reindex lazy.Map<sha256, Float32Array>LRU, TTL 5min, max 1000.@huggingface/tokenizers).embeddingSource,embeddingProviderModel,transformersEnabled,staticEnabled,rerankEnabled,rerankProviderModel,vectorStore.deleteMemory()sincroniza AMBOS sqlite-vec + Qdrant.@huggingface/transformersemserverExternalPackagesdo Next 16.Hard Rules compliance
localDb.tsapenas re-export (auditado via grep)eval()/new Function()src/lib/db/+ exceção documentada emvectorStore.ts(header cita §D5)--no-verifysanitizeErrorMessageem todas as 13 rotas novasCo-Authored-ByTest plan
npm install(instalasqlite-vec@^0.1.9+@huggingface/transformers@^4.2.0)npm run lint— 0 errorsnpm run typecheck:core+npm run typecheck:noimplicit:core— cleannpm run check:cycles— sem ciclos novosnpm run test:unit— todos os testes memory novos passandonpm run test:vitest:ui(novo script) — 45 UI tests passandonpm run test:vitest— MCP suite sem regressãonode --import tsx/esm --test tests/integration/memory-*.test.ts tests/integration/qdrant-routes.test.ts— 33 integration tests verdesnpm run test:coverage— gate 40/40/40/40 atingidonpm run build— production build verde com as 14 novas rotas registradas/dashboard/memoryno browser — 3 tabs + concept cardomniroute memory add --content "test"→ cria comtype:factual;--type user→ warning + remapeiaomniroute_memory_searchrespeita a strategy de settings (não hardcoded)Validação local (executada na worktree)
npm run lintcleannpm run typecheck:core+noimplicit:corecleannpm run check:cyclessem ciclossrc/lib/memory/**: stmts 70.68% / lines 70.68% / funcs 82.67% / branches 71.31%npm run buildsucesso — 14 rotas memory +/dashboard/memoryStudio@huggingface/transformersemserverExternalPackages(lazy/server-only)Documentação atualizada
docs/frameworks/MEMORY.md(+306 linhas) — arquitetura 3-tier, embedding sources, RRF, backfill, settings extensiondocs/reference/openapi.yaml(+1304 linhas) — 13 paths + 4 schemas novosdocs/architecture/REPOSITORY_MAP.md— novos pathssrc/i18n/messages/{pt-BR,en}.json— 54 chaves novas no namespacememory.*(D12)Notas de migração
073_memory_vec.sql— idempotente (CREATE TABLE IF NOT EXISTS+INSERT OR IGNORE+ALTER TABLE ADD COLUMNcom guard viaduplicate column nameno migration runner).POST /api/memory/reindex.retrieveMemories(apiKeyId, config)assinatura idêntica →chatCore.tsnão muda.