#8497)Moving Orbiter's Outcomes chat memory off Zep and onto Mem0 as a recall layer for distilled memories — not a transcript store, not the canonical profile store. Built and verified on a sandbox clone first; the live demo endpoint stays pristine until a gated go.
Mapped 1:1 to Mark's callout titles. Green = decided & in this pass. Amber = deferred to that surface's own migration.
outcome_conversations #689 persistence stays exactly as-is. First-party transcript persistence would be a separate endpoint, out of scope here.meeting_prep_context_id ↔ conversation_id before wiring Mem0.meeting_prep_context_id onto copilot_conversations as the bridge before wiring its run-scope Mem0. Does not touch the Outcomes swap.source_id; skip/upsert/create on repeat; persist in ai_memory_event_log.ai_memory_event_log does not exist (verified — only copilot_memory #658 / copilot_memory_log #659 from the Zep/copilot era). We will not add a table to the demo-critical path in v1. Instead: (a) deterministic source_id in Mem0 add metadata = outcome:{suggestion_request_id}:{hash(query)}; (b) infer:false; (c) dedup approved memories vs the retrieved set. A re-dispatched identical turn becomes a no-op. A durable event-log table is an explicit fast-follow once Outcomes is proven.self_markdown.self_yaml_context arrives as a #8497 input (frontend-generated via get-all-user-context_v2 #13076) and fills the [YOU] context slot only — it is never written to Mem0. No self_markdown blob lives in Mem0, so the staleness-metadata concern doesn't arise on this path. The recommendation applies only if/when self-context snapshots are persisted in Xano — out of scope for the swap.master_person_relationship #729 as the active store.#8581 interview-only, #8582 load, #8583 save; Mem0 holds the durable individual memories behind the UI summary. Does not touch the Outcomes swap.auth.id before any Mem0 search or write.suggestion_request_id > 0, db.get suggestion_request and verify user_id == $userId (owner column confirmed = user_id (int); #8497 is auth = "user" so $userId = $auth.id is trustworthy). On mismatch or missing row → skip all run-scope Mem0 search + write, degrade to user-scope only. User-scope is always keyed by the authenticated user, so it is inherently owned.$input.thread_id != "" && $env.zep != "":
GET /threads/{id}/context → $mem_context, which then fills the [NETWORK MEMORY] slot inside the [YOU] / [HELPING TARGET] / [YOUR COMPANY] / [TARGET COMPANY] wrapper. Clean Mem0 drop-in target.POST /threads/{id}/messages (user query + routing summary) → $mem_ingested = true. It's the last block before $latency_ms.auth = "user"; $userId = $auth.id (first stack line) — trustworthy server-side owner.suggestion_request #529 owner column = user_id (int) → the column the ownership guard checks.ai_memory_event_log table exists — only copilot_memory #658 / copilot_memory_log #659. Idempotency must not assume that table (decision #3).Authorization: Token $env.MEM0_API_KEY (provisioned). search POST /v3/memories/search/ {query, filters:{user_id|run_id}, top_k:8, threshold:0.1}; add POST /v3/memories/add/ {user_id|run_id, infer:false, messages, metadata}. user_id is the bare id ("15"); run_id = suggestion_request:{id}.openai/gpt-5-nano via OpenRouter — header Authorization: Bearer $env.openRouter (camelCase, verified in find-investors #8491). Fallback: Anthropic direct $env.anthropic + claude-haiku-4-5 (matches live classify #8490).foreach + api.request inside is proven in #8491 → safe for writing N approved memories. api.request never throws on non-2xx → Mem0/LLM failures degrade gracefully; the chat never breaks.query/thread_id/suggestion_request_id and reads back mem_used/mem_ingested.| # | Decision | Rationale |
|---|---|---|
| D1 | Extractor = openai/gpt-5-nano via OpenRouter, Anthropic claude-haiku-4-5 fallback | Matches spec; OpenRouter key already wired; no OpenAI key. Verify slug on first run. |
| D2 | Build + verify on sandbox clone openui05/dispatch-mem0; leave live #8497 pristine | Demo-safety. Cutover is a separate, Robert/Mark-gated step. |
| D3 | Mem0 recall fills the existing [NETWORK MEMORY] slot | Drop-in; preserves the YOU/TARGET/COMPANY wrapping unchanged. |
| D4 | Save confidence:"high" only, dedup vs retrieved; medium/low skipped in v1 | Per spec save policy; conservative for the real Mem0 graph. |
| D5 | Cutover keeps Zep blocks, re-gated to $env.zep != "" && $env.MEM0_API_KEY == "" | Honors "Zep stays in dev"; fully reversible by unsetting the key. |
| D6 | Ownership guard — verify suggestion_request.user_id == $userId before any run-scope op; mismatch → user-scope only | Mark's required guardrail. Blocks reading/writing another user's run memory via a forged id. |
| D7 | Idempotency — deterministic source_id in metadata + infer:false + retrieved-dedup; no new table in v1 | Event-log table doesn't exist; keep the demo path minimal; durable log is a fast-follow. |
openai/gpt-5-nano → confirm 200 + content. The only remaining unknown.openui05/dispatch-mem0 in group 1276 = verbatim #8497. Known-good base.$userId): set $run_id and $run_owned = (suggestion_request.user_id == $userId). Gates run-scope read and write. (D6)$run_owned. Defensive lambda pull → build [NETWORK MEMORY] → assign $mem_context, wrapper untouched. (D3)source_id (D7), call gpt-5-nano (Anthropic fallback), strip fences + decode, dedup & keep high-confidence (D4), foreach → Mem0 add with infer:false + scope routed by $run_owned; set $mem_ingested.mem_recall, hit counts, run_owned, extractor raw, add ids); removed before cutover.mem_ingested:true + visible in Mem0. Turn 2 related query → mem_used:true with the preference in context. Negative test: a non-owned suggestion_request_id → run_owned:false, zero run-scope traffic._endpoints.ts → /openui05/dispatch-mem0, select a draft, run a turn, confirm cards render + mem_used/mem_ingested. Revert.user_id=15 / run_id=suggestion_request:{id}.suggestion_request_id → run_owned:false and zero run-scope Mem0 traffic.source_id, deduped).pnpm dev + agent-browser) — mem_used/mem_ingested true, result cards unchanged.MEM0_API_KEY unset, sandbox/#8497 behaves exactly as today (graceful no-op).ai_memory_event_log durable event-log table — explicit fast-follow after Outcomes is proven (decision #3 uses deterministic source_id + dedup instead).outcome_conversations #689 (decision #1 — unchanged by this migration).