# Plan — Activate XMem backend & prove the no-vendor-lock-in seam

**Status:** Phases A, C, D done. Phase B blocked externally — see "Phase B status" below.
**Owner:** Nicholas
**Started:** 2026-05-17
**Last updated:** 2026-05-17

## Phase B status — externally blocked

The published `@xtraceai/memory` TS SDK (v0.0.1) hardcodes `baseUrl = "https://api.xtrace.ai"` and constructs paths like `/v1/memories`. As of probe at 2026-05-17 (with valid credentials provisioned at `app.xtrace.ai` — orgID `uReLheOXeIdt`, API key created and configured), all routes return `{"message":"Cannot GET /v1/memories","error":"Not Found","statusCode":404}` — generic Express 404 from the host. The host responds; the routes are simply not deployed yet. The `environment: 'staging' | 'production'` SDK parameter does NOT switch hosts (verified by reading the SDK source).

This is an external block on the xtrace.ai team's deploy. Nothing further the PF side can do until they ship the backend. To unblock when they do: just confirm health probe flips green by hitting `curl http://127.0.0.1:3456/xmem/status` — no code change needed.

### What's verified as ready

- xmem adapter sends the correct auth surface: `Authorization: Bearer ${apiKey}` + `X-Org-Id: ${orgID}` (SDK convention, verified from SDK source).
- Config schema carries both `apiKey` and `orgID` with `xmemCredsComplete` flag in status.
- Wire format (POST body shape, GET query params) matches the SDK source exactly.
- Health probe correctly identifies 404-on-route as "route not deployed."

### Phase B substitute proof (pgvector E2E)

Since x-mem is unavailable, the same end-to-end pipeline was exercised against the pgvector adapter to prove the abstraction works. Live PF backend, real REST task creation:

1. `POST /tasks` → real PF task created.
2. `TeamPulseSyncEmitter` fires → memory-hub subscriber → `PgvectorMemoryHub.write()`.
3. `GET /xmem/team-activity?teamId=...` returns the `task_created` event with full payload.
4. `POST /xmem/search` with a relevant query returns the event with a cosine-similarity score.
5. Task lifecycle (`in_progress`, `complete`) emits the expected `task_selected`, `task_updated`, `task_completed` events — all 4 visible in pgvector within seconds.

This validates the entire memory-hub plumbing end-to-end. When xmem ships, swapping `backend: "pgvector"` → `backend: "xmem"` in `~/.priority-forge/xmem.json` is the only change required.

## What this is

PF already ships a fully-realized memory-hub abstraction (`packages/priority-forge-hub/src/`) with three adapters: `noop`, `dryrun`, `xmem`. The interface is intentionally small (five methods) and the recipe for adding new adapters is documented in `ADAPTERS.md`. This plan does NOT rebuild that abstraction — it does the four things that close the loop:

- **A.** Reconcile the existing `xmem` adapter against the now-public XMem API (`docs.mem.xtrace.ai/api/typescript`, package `@xtraceai/memory`).
- **B.** Run an end-to-end smoke against real credentials.
- **C.** Add a second adapter (Postgres + pgvector) to demonstrate the abstraction holds against a non-xtrace backend.
- **D.** Ship the missing adapter contract test harness flagged in `ADAPTERS.md` ("open work").

## Drift to fix (Phase A)

The xmem adapter was written against an assumed API surface. Public docs reveal small but real drift:

| Concern | Adapter today | Real API |
|---|---|---|
| Auth | `Authorization: Bearer ${apiKey}` only | requires `apiKey` AND `orgID` |
| Config | `xmem: { baseUrl, apiKey }` | needs `xmem: { baseUrl, apiKey, orgID, environment? }` |
| Docstring URL | `docs.x-mem.xtrace.ai` | `docs.mem.xtrace.ai` |
| Environment toggle | none | SDK exposes `staging` / `production` |
| Wire format | raw HTTP to `/memories` and `/memories/search` | likely matches the SDK; **must verify** with a curl-equivalent probe before declaring done |

**Decision:** keep raw HTTP. Don't pivot to the `@xtraceai/memory` npm package yet. The surgical fix is ~30 lines across `config.ts`, `types.ts`, and `xmem-adapter.ts`. Revisit the SDK pivot if XMem adds features (batching, retry policies, transparent encryption pre-processing) we'd otherwise duplicate.

## Phase B — credentials + smoke

Provision at `app.xtrace.ai`, capture `apiKey` + `orgID`, run `xmem_enable_sync { backend: 'xmem', teamId, agentId, apiKey, orgID }`. Then: create a task, wait ≤60s, verify `xmem_get_team_activity` returns it, then `xmem_search_team_memory` returns it ranked.

## Phase C — Postgres+pgvector adapter

**Decision:** pgvector over mem0 or in-process embedding extension. Reasoning: zero external API dep, full schema control, no rate limits, deterministic fallback when xmem is degraded.

Plan:
- `packages/priority-forge-hub/src/adapters/pgvector-adapter.ts` (~200 lines, follow `_template.ts`).
- `packages/priority-forge-hub/src/adapters/setup-pgvector.sql` — one-shot schema bootstrap.
- Table `team_memory_events` mirrors `TeamMemoryEvent` 1:1 plus an `embedding VECTOR(384)` column. Index on `(team_id, timestamp DESC)` + ivfflat on `embedding`.
- `write()` upserts on `event_id` (idempotent). `search()` cosine-similarity on embedding. `read()` filter pushdown.
- Embedding model: local `Xenova/all-MiniLM-L6-v2` via `@xenova/transformers`. No network dependency. Configurable.
- Wire: factory + `MemoryHubBackend` union + MCP `xmem_enable_sync` enum + ADAPTERS.md table entry.

## Phase D — contract test harness

`packages/priority-forge-hub/src/__tests__/contract.test.ts`. Parameterized by adapter fixture. Asserts the five-method contract:

- `write` returns `{accepted: true, remoteId?}` on success / `{accepted: false}` on failure (never throws).
- `read` honors `sinceTs`, `agentIds`, `workspaceIds`, `eventTypes`, `limit`.
- `search` returns ranked events.
- `blockersFor` returns active edges scoped to the given agent.
- `health` returns `{ok, backend, detail?}` and never throws.

Runs against `dryrun` and `pgvector` unconditionally. Runs against `xmem` when `XTRACE_TEST_API_KEY` + `XTRACE_TEST_ORG_ID` are set. `noop` is exempt — separately asserted to return empty by design.

## What's NOT in this plan

- **V5+ schema expansion** (`longDescription`, `conversationContext`, etc. per `docs/DEVELOPMENT_ROADMAP.md`).
- **Workspace-scoped subscription rules.**
- **Replacement of legacy Team Pulse sync.** Coexists fine.
- **First-class encryption-knob exposure on the hub interface.** Adapters handle their own auth. Revisit only if multiple adapters need user-controlled keys.

## Verification (end-to-end, in order)

1. `npm run typecheck && npm test` pass after Phase A.
2. `xmem_get_status` after Phase A on `noop` default: `ok: true, backend: 'noop'`.
3. Phase B: `xmem_get_status` against real API returns `ok: true, backend: 'xmem'`. Task-create → `xmem_get_team_activity` within 60s. `xmem_search_team_memory` returns it.
4. Phase C: `xmem_enable_sync { backend: 'pgvector', ... }` succeeds; same smoke as step 3 against pgvector.
5. Phase D: `npm test -- contract.test.ts` runs the parameterized suite against `dryrun` + `pgvector` and passes. With env credentials, also against `xmem`.
6. `XMEM_FORCE_NOOP=1 npm run dev` still serves PF cleanly (kill switch verified).

## Files

**Modify (Phase A):** `packages/priority-forge-hub/src/types.ts`, `config.ts`, `adapters/xmem-adapter.ts`
**Modify (Phase C):** `types.ts` (union), `factory.ts`, `mcp-tools.ts` (backend enum), `ADAPTERS.md`
**Modify (Phase D):** `ADAPTERS.md` (remove the "no test harness" open-work note)
**Create (Phase C):** `adapters/pgvector-adapter.ts`, `adapters/setup-pgvector.sql`
**Create (Phase D):** `__tests__/contract.test.ts`
**Reuse unchanged:** `subscriber.ts`, `blocker-emitter.ts`, `sync-reader.ts`, `adapters/_template.ts`, `adapters/noop-adapter.ts`, `adapters/dryrun-adapter.ts`
