Skip to content

[v3.8.50] feat(memory): MemoryBackend provider pattern with generic HTTP connector - #8752

Merged
diegosouzapw merged 13 commits into
diegosouzapw:release/v3.8.50from
oyi77:feat/memory-backend-provider
Aug 6, 2026
Merged

diegosouzapw merged 13 commits into
diegosouzapw:release/v3.8.50from
oyi77:feat/memory-backend-provider

Conversation

@oyi77

@oyi77 oyi77 commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

feat(memory): MemoryBackend Provider Pattern with Generic HTTP Connector

Summary

Introduces a pluggable MemoryBackend provider architecture with a generic HTTP connector that supports dynamic endpoint/query/path mapping for any REST-based memory backend (Obsidian, Notion, custom).

Changes

Core Architecture (Phases 1-3)

File Description
src/lib/memory/backend.ts MemoryBackend interface + CreateMemoryInput, MemoryFilter, SearchConfig, HealthCheckResult
src/lib/memory/manager.ts MemoryManager singleton — register, configure, fallback routing, health checks
src/lib/memory/sqliteBackend.ts Thin wrapper around existing store.ts/retrieval.ts — implements MemoryBackend
src/lib/memory/index.ts Exports + auto-registers SQLiteBackend + initMemoryBackends() for app bootstrap
src/app/api/memory/route.ts Delegates to memoryManager.list() / memoryManager.create()
src/app/api/memory/[id]/route.ts Delegates to memoryManager.get/delete/update()

Optional Backends (Phase 4)

File Description
src/lib/memory/genericBackend.ts Generic HTTP connector with dynamic endpoint/query/path mapping
src/lib/memory/obsidianBackend.ts Obsidian vault file-based backend (markdown + YAML frontmatter)
src/lib/memory/settings.ts Extended with primaryBackend, fallbackBackends, backendConfigs + normalization
src/shared/schemas/memory.ts Added backend fields to MemorySettingsExtendedSchema

Settings Schema (DB + API)

{
  "memoryEnabled": true,
  "primaryBackend": "sqlite",
  "fallbackBackends": ["obsidian"],
  "backendConfigs": {
    "obsidian": {
      "baseUrl": "http://localhost:27123",
      "apiKey": "...",
      "endpoints": {
        "search": "/api/v1/vault/memories/search",
        "create": "/api/v1/vault/memories",
        "get": "/api/v1/vault/memories/{memoryId}"
      },
      "queryParams": { "query": "q", "apiKeyId": "api_key" },
      "pathParams": { "id": "memoryId" }
    }
  }
}

GenericMemoryBackend — Dynamic Endpoint Mapping

// Any REST backend becomes pluggable via config
createGenericMemoryBackend("obsidian", "Obsidian Vault", {
  baseUrl: "http://localhost:27123",
  apiKey: process.env.OBSIDIAN_API_KEY,
  endpoints: {
    search: "/api/v1/vault/memories/search",
    create: "/api/v1/vault/memories",
    get: "/api/v1/vault/memories/{memoryId}",
  },
  queryParams: { query: "q", apiKeyId: "api_key" },
  pathParams: { id: "memoryId" },
});

Supported mappings:

  • endpoints — override any REST path (supports {id}, {memoryId} placeholders)
  • queryParams — rename any query parameter (query → q, apiKeyId → api_key, etc.)
  • pathParams — rename path placeholders (id → memoryId)

Connecting Global omniroute to Custom Memory Backend

# Via environment variables (auto-registers on startup)
export OBSIDIAN_API_URL=http://localhost:27123
export OBSIDIAN_API_KEY=your-key
omniroute start

# Or via Settings API after startup (persists to DB)
curl -X PUT /api/settings/memory -d '{ "primaryBackend": "obsidian", ... }'

Verification

  • ✅ Typecheck: tsc --noEmit — clean
  • ✅ Memory tests: 45 passed, 6 skipped (pre-existing FTS5 test infra issue)
  • ✅ Skills integration tests: 8 passed
  • ✅ Behavioral parity: store.ts/retrieval.ts identical to v3.8.49 (newline-only diff)

Migration Notes

  • Zero behavior change for existing SQLite memory — store.ts/retrieval.ts unchanged
  • New settings are additive with safe defaults (primaryBackend: "sqlite", fallbackBackends: [])
  • Existing callers (chatCore.ts, dashboard, CLI) work unchanged — they route through memoryManager

Future Work (Not in this PR)

  • Dashboard UI for backend selector + config forms (Phase 5)
  • Auto-discovery of npm plugins (omniroute-memory-backend-*)
  • Response transformers for non-standard backend shapes

@oyi77
oyi77 requested a review from diegosouzapw as a code owner July 27, 2026 08:27
@oyi77
oyi77 force-pushed the feat/memory-backend-provider branch from 84ca591 to 42f9c11 Compare July 27, 2026 11:01
@diegosouzapw diegosouzapw added hold-vps PR verde, merge aguardando validação live (release-drain) needs-rebase PR ejetada do merge-train — triagem separada merge-train-deferred PR ejetada do merge-train — triagem separada labels Jul 27, 2026
@diegosouzapw

Copy link
Copy Markdown
Owner

⏸️ Ejetada do merge-train v3.8.49 — triagem de cherrypick pendente

Esta PR foi ejetada do merge-train local (30→24 PRs) porque sua pegada no diff excede o orçamento seguro de validação em lote (>700 linhas ou nova fronteira arquitetural). Não é rejeição — é adiamento com motivo rastreável.

Motivo da ejeção

PR Título Files Linhas Risco em trem
#8760 feat: Vendo platform (novo módulo) 24 +3046 Novo módulo 3046+ linhas — precisa revisão arquitetural antes de entrar no release
#8713 fix(github): Copilot targetFormat override 581 +36652/-29407 Mass migration 36k+ linhas — provavelmente regen de lockfile + tree-wide diff, alto risco de regressão silenciosa
#8673 fix(security): tar ^7.5.22 (GHSA-w8wr-v893-vjvp) 578 +36542/-29411 Dependabot mass bump — GHSA real mas precisa validação isolada + tests de regressão de extract/archive
#8757 refactor(db): combo repository boundary (DRAFT) 9 +1114/-553 Draft + fronteira em DB core — refactor arquitetural, exige revisão de compat com localDb.ts (Hard Rule #2)
#8752 feat(memory): MemoryBackend provider pattern 17 +1347 Novo padrão de provider com HTTP connector genérico — abre nova superfície, exige revisão do contrato
#8718 fix(test): revive orphaned vitest tests 22 +733/-772 Suite-wide em vitest — risco de timing-flakes mascarando regressões (medido em #6218 follow-ups)

Como destravar (sugestão do operador)

O operador pediu que toda PR ejetada seja revisada para possível cherrypick. Providências sugeridas por categoria:

  1. PRs com subconjunto cherrypickável (feat: add Vendo platform - African commerce MVP foundation #8760, [v3.8.50] feat(memory): MemoryBackend provider pattern with generic HTTP connector #8752, [v3.8.50] fix(test): revive orphaned vitest tests and fix CI routing #8718): abrir gh pr diff <N> e identificar arquivos pequenos, isolados, sem dependência cross-module. Considerar:

    git remote add author-<login> <fork-url> 2>/dev/null
    git fetch author-<login> <branch>
    git checkout -b cherry/<N>-<slug> release/v3.8.49
    git cherry-pick <commit-sha-do-subconjunto>

    Abrir nova PR com o subconjunto (mantendo Co-authored-by do autor original).

  2. PRs com regen/lockfile ([v3.8.50] fix(github): honor per-model targetFormat override for Copilot custom models #8713, [v3.8.50] fix(security): override tar to ^7.5.22 (GHSA-w8wr-v893-vjvp) — unblocks the Lint job on every PR #8673): provavelmente só o package.json + package-lock.json + o patch de código mínimo. Rodar npm ls tar no tip local para confirmar a versão segura antes de cherrypick.

  3. PRs com refactor arquitetural ([v3.8.50] refactor(db): add combo repository boundary #8757): exige review 1-a-1 com o operador — a fronteira db/ precisa ser validada contra Hard Rule build(deps): bump actions/github-script from 7 to 8 #2 (nunca lógica em localDb.ts).

Próximos passos

  • Comentário fixado para rastreabilidade.
  • Labels aplicadas: hold-vps, needs-rebase, merge-train-deferred.
  • Estas 6 NÃO bloqueiam o trem das outras 24 (que vai entrar em modo --fast na .113).

@oyi77

oyi77 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Merge-train ejection — status update

Thanks for the detailed breakdown of why this PR was ejected. Here is what has been done:

Fixed:

  • Migration 118 (provider_param_filters): The file was docs-only with no executable SQL, causing "Migration FAILED: Query contained no valid SQL statement". Added SELECT 1; as a no-op so the migration registers properly. Pushed as commit 7446aab.

Pre-existing (not from our changes):

  • DuckDuckGo Web Executor timeout (Unit Tests fast-path 3/4): The DuckDuckGo tests time out hitting a live endpoint. This is a pre-existing CI flake unrelated to our memory backend changes.

Plan:

Per your suggestion, I can prepare a cherry-pick subset isolating the most impactful pieces:

  1. Core interfaces + schemas (backend.ts, genericBackend.ts, settings, schemas)
  2. Existing backend refactors (sqliteBackend.ts, obsidianBackend.ts)
  3. API routes + plumbing layer

Happy to arrange a 1-on-1 architecture review if you prefer to validate the full module boundary first. Which path works best?

@diegosouzapw diegosouzapw changed the title feat(memory): MemoryBackend provider pattern with generic HTTP connector [v3.8.50] feat(memory): MemoryBackend provider pattern with generic HTTP connector Jul 27, 2026
@diegosouzapw diegosouzapw added the deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) label Jul 27, 2026
@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.49 to release/v3.8.50 July 28, 2026 18:41
@diegosouzapw

Copy link
Copy Markdown
Owner

Re-homed to release/v3.8.50: v3.8.49 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

@oyi77
oyi77 force-pushed the feat/memory-backend-provider branch from f84efe4 to 6c92b89 Compare July 28, 2026 19:37
oyi77 added 10 commits July 31, 2026 02:36
- MemoryBackend interface + MemoryManager singleton
- SQLiteBackend (thin wrapper, zero behavior change)
- GenericMemoryBackend with dynamic endpoint/path/query mapping
- ObsidianBackend (markdown + frontmatter)
- Settings: primaryBackend, fallbackBackends, backendConfigs
- API routes delegated to MemoryManager
- KNOWN_BACKENDS presets: Vilona, Obsidian, Notion
- Remove Vilona from KNOWN_BACKENDS presets
- Remove Vilona references from PR body and code comments
- Keep Obsidian and Notion as generic examples
- Add MEMORY_VEC_TOP_K, MEMORY_RRF_K to Memory Engine section
- Add NOTION_API_KEY, NOTION_API_URL, OBSIDIAN_API_KEY, OBSIDIAN_API_URL
- Fix .env.example sync for new memory backend vars
…nit tests

- Fix get/update/delete to pass both pathParams.id and memoryId
- Fix buildListQuery to not filter offset=0 as falsy
- Add 26 comprehensive unit tests for GenericMemoryBackend
  (health, init, CRUD, search, auth headers, factory)
…est-discovery collector

Ensures generic-backend.test.ts is collected by CI runner (vitest)
instead of being detected as a new orphan by the test-discovery gate.
@oyi77
oyi77 force-pushed the feat/memory-backend-provider branch 2 times, most recently from 6c92b89 to 511614a Compare July 30, 2026 19:37
…p, add SSRF guard, remove .skip

- Remove PR_BODY.md artifact (committed by mistake)
- Wire initMemoryBackends() into instrumentation-node.ts startup
- Remove .skip from retrieval.test.ts FTS5 test describe block
- Add SSRF prevention guard in GenericMemoryBackend request() method
  (blocks loopback, private, cloud-metadata IPs and non-http schemes)
- Add 9 unit tests for SSRF validation

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw and others added 2 commits August 4, 2026 23:26
summarization.ts declared its own local MemoryRow interface with
type: string, unlike store.ts's MemoryRow (type: MemoryType). Both read
the same `memories.type` column, and rowToMemory() already casts the
value to MemoryType downstream, so the looser interface type was
untightened debt rather than an intentional difference. Narrows the
field to MemoryType, matching store.ts's convention.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw merged commit 2ddbbc6 into diegosouzapw:release/v3.8.50 Aug 6, 2026
4 of 5 checks passed
@oyi77
oyi77 deleted the feat/memory-backend-provider branch August 7, 2026 21:07
diegosouzapw added a commit that referenced this pull request Aug 8, 2026
…e — every handler 500'd (#9737) (#9785)

* fix(api): validate request bodies with Zod in 4 routes — restores the t06 gate

The release-green verdict (#9737) lists check:route-validation:t06 as a HARD
failure and it is STILL red on the current tip: four routes call
request.json() and hand-roll `typeof x === "string"` checks instead of using
Zod, which Hard Rule #7 requires and the gate enforces (it scans source and
has no allowlist).

- src/app/api/plugins/marketplace/install (#9445): InstallBodySchema; the
  400 'Missing or invalid name field' response is preserved verbatim.
- src/app/api/services/dario/admin/accounts (#8523): DeleteAccountBodySchema
  for the optional { alias } DELETE body; query-param path untouched.
- src/app/api/services/dario/admin/login-start (#8523): LoginStartBodySchema;
  trimming now happens in the schema, so the forward body is unchanged.
- src/app/api/services/dario/admin/import-from-omniroute (#8523):
  ImportBodySchema for connectionId/alias; invalid shapes fall back to the
  same 'connectionId is required' 400 as before.

All four keep their exact status codes and messages — this is a validation
mechanism swap, not a contract change (plugins route suite still 33/33).

Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own
rule inside the unit suite so the next such route fails on ITS OWN PR instead
of surfacing weeks later in a base-red sweep. Guard verified by mutation:
renaming .safeParse( in one route makes it fail (1 fail), restored from a
pre-probe copy.

Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage,
dead-code exit 0; typecheck:core clean; eslint clean.

Refs #9737

* fix(memory): register the sqlite backend on the /api/memory/[id] route — every handler 500'd

GET/PUT/DELETE /api/memory/[id] threw `Primary backend "sqlite" not
registered` and returned 500. #8752 (MemoryBackend provider pattern) wired the
route to `@/lib/memory/manager` directly, but the registry is populated by an
import-time side effect in the module INDEX (src/lib/memory/index.ts:23,
`memoryManager.register(sqliteBackend)`). Importing the bare manager gives an
empty registry.

In production the failure is order-dependent, which is why it went unnoticed:
if /api/memory (which imports the index) is hit first in the same process, the
singleton is already populated and [id] works. Reached first — the common case
for a client that edits a known memory id — every request 500s. The sibling
route is the only other consumer and already imports the index; this was the
lone direct-manager import in src/.

- Fix: import from `@/lib/memory` (index) with a comment stating WHY the
  indirection matters, so the next refactor does not simplify it back.
- Guard: tests/integration/memory-route-put.test.ts already covered this and
  was failing 2/5 on the base (it only surfaced now because the integration
  suite runs on the release-PR CI, not per-PR). Now 5/5.

Also fixes a test-isolation defect in the same run:
tests/integration/combo-matrix/context-relay-codex.test.ts reused one combo
name across both tests, and the control failed with `UNIQUE constraint failed:
combos.name` — resetStorage() unlinks the DB file but the previous
better-sqlite3 handle keeps writing to the same inode. Gave the control its own
combo name and parameterized the request builder; the assertion is unchanged
(it never depended on the name). 2/2.

Integration suite on this tip: 936 tests, 32m19s — under the 40min ceiling the
old verdict reported as exceeded (#9737 item 6), which the migration-135
collision was causing.

Refs #9737

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…TTP connector (diegosouzapw#8752)

Validated in local merge-train T7 (ungrouped batch 2)
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…e — every handler 500'd (diegosouzapw#9737) (diegosouzapw#9785)

* fix(api): validate request bodies with Zod in 4 routes — restores the t06 gate

The release-green verdict (diegosouzapw#9737) lists check:route-validation:t06 as a HARD
failure and it is STILL red on the current tip: four routes call
request.json() and hand-roll `typeof x === "string"` checks instead of using
Zod, which Hard Rule diegosouzapw#7 requires and the gate enforces (it scans source and
has no allowlist).

- src/app/api/plugins/marketplace/install (diegosouzapw#9445): InstallBodySchema; the
  400 'Missing or invalid name field' response is preserved verbatim.
- src/app/api/services/dario/admin/accounts (diegosouzapw#8523): DeleteAccountBodySchema
  for the optional { alias } DELETE body; query-param path untouched.
- src/app/api/services/dario/admin/login-start (diegosouzapw#8523): LoginStartBodySchema;
  trimming now happens in the schema, so the forward body is unchanged.
- src/app/api/services/dario/admin/import-from-omniroute (diegosouzapw#8523):
  ImportBodySchema for connectionId/alias; invalid shapes fall back to the
  same 'connectionId is required' 400 as before.

All four keep their exact status codes and messages — this is a validation
mechanism swap, not a contract change (plugins route suite still 33/33).

Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own
rule inside the unit suite so the next such route fails on ITS OWN PR instead
of surfacing weeks later in a base-red sweep. Guard verified by mutation:
renaming .safeParse( in one route makes it fail (1 fail), restored from a
pre-probe copy.

Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage,
dead-code exit 0; typecheck:core clean; eslint clean.

Refs diegosouzapw#9737

* fix(memory): register the sqlite backend on the /api/memory/[id] route — every handler 500'd

GET/PUT/DELETE /api/memory/[id] threw `Primary backend "sqlite" not
registered` and returned 500. diegosouzapw#8752 (MemoryBackend provider pattern) wired the
route to `@/lib/memory/manager` directly, but the registry is populated by an
import-time side effect in the module INDEX (src/lib/memory/index.ts:23,
`memoryManager.register(sqliteBackend)`). Importing the bare manager gives an
empty registry.

In production the failure is order-dependent, which is why it went unnoticed:
if /api/memory (which imports the index) is hit first in the same process, the
singleton is already populated and [id] works. Reached first — the common case
for a client that edits a known memory id — every request 500s. The sibling
route is the only other consumer and already imports the index; this was the
lone direct-manager import in src/.

- Fix: import from `@/lib/memory` (index) with a comment stating WHY the
  indirection matters, so the next refactor does not simplify it back.
- Guard: tests/integration/memory-route-put.test.ts already covered this and
  was failing 2/5 on the base (it only surfaced now because the integration
  suite runs on the release-PR CI, not per-PR). Now 5/5.

Also fixes a test-isolation defect in the same run:
tests/integration/combo-matrix/context-relay-codex.test.ts reused one combo
name across both tests, and the control failed with `UNIQUE constraint failed:
combos.name` — resetStorage() unlinks the DB file but the previous
better-sqlite3 handle keeps writing to the same inode. Gave the control its own
combo name and parameterized the request builder; the assertion is unchanged
(it never depended on the name). 2/2.

Integration suite on this tip: 936 tests, 32m19s — under the 40min ceiling the
old verdict reported as exceeded (diegosouzapw#9737 item 6), which the migration-135
collision was causing.

Refs diegosouzapw#9737

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) hold-vps PR verde, merge aguardando validação live (release-drain) merge-train-deferred PR ejetada do merge-train — triagem separada needs-rebase PR ejetada do merge-train — triagem separada

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants