feat(memories): #1791 route POST /memories through v2 plugin (Phase A2 step 1) #1794
Reference in New Issue
Block a user
Delete Branch "feat/issue-1791-memories-commit-v2-plugin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase A2 step 1 of the memory v1→v2 migration. Closes the most surprising leak found during the 2026-05-24 production audit: the v2 plugin has zero rows on every live tenant, despite the sidecar being healthy and
#1747having killed the MCP-side v1 fallback. Writes were still flowing intoagent_memoriesbecause the canvas-facing HTTP handler atPOST /workspaces/:id/memoriesdoes raw SQL INSERT, not plugin commit.Production evidence
agent_memoriesrowsmemory_plugin.memory_recordsrowsTenant container logs show 1 POST
/workspaces/.../memoriesrequest every 1–5 minutes from internal IPs (172.31.x.x) — that's workspace runtimes calling back into the tenant API with conversation snapshots like"Conversation: AUTONOMOUS TICK — Production Manager. DISPATCH...".What this PR does
Mirrors
#1759'sseedInitialMemoriespattern:MemoriesHandlergetsmemv2 *memoryV2Depsfield +WithMemoryV2(plugin, resolver)setter.Commit()routes throughplugin.CommitMemoryusing the namespace resolver to map scope→namespace (LOCAL→workspace, TEAM→team, GLOBAL→org).Sourceis set toMemorySourceUser(HTTP path is the canvas/operator surface, not the agent MCP path).#1747's no-fallback posture for the MCP path. Loud error > silent drift.router.gocallsmemsh.WithMemoryV2whenmemBundle != nil.Commit(plugin owns its own embedding).SearchandUpdatestill useh.embedagainst v1 — tracked separately so this PR stays single-axis.Tests
8 existing
TestMemoriesCommit_*andTestCommitMemory_*tests updated to use the existingstubMemoryPlugin+stubNamespaceResolverviawithMemoryV2APIs. Two new helpers (memCommitPlugin,memCommitResolver) keep the test bodies concise. New regression testTestCommitMemory_EmbedNotCalledOnCommitpins the post-migration contract thath.embedis no longer invoked on Commit.#767GLOBAL-write audit-log assertion preserved (still writes toactivity_logs).What this PR does NOT do (intentionally tracked elsewhere)
agent_memories→ plugin. That's Phase A2 step 2, uses the pre-existingcmd/memory-backfilltool (idempotent via UUID upsert), tracked as a follow-on for after this lands and writes-to-plugin verified live.Search/Get/Update/DeleteonMemoriesHandler. Those still read v1. Either kill the legacy endpoints in favour of/v2/memories(canvas'sMemoryInspectorPanelalready reads/v2/memoriesso this is feasible), or migrate them separately. Tracked under the parent#1791./workspaces/:id/memoriescould migrate to MCPcommit_memoryinstead. Separate runtime-image PRs.Risk
/memorieson the same handler will see writes go to v2 and reads come from v1 — inconsistent until the read-side migration ships. Acceptable risk: the canvas's actualMemoryInspectorPanelalready reads v2 (/v2/memories), so the user-visible Memory tab gets correct, fresh state. Internal runtime callers that read back via/memorieswill lose visibility into their own writes — they should be using MCPrecall_memory(which is v2-only) anyway.MEMORY_PLUGIN_URL) lose Commit functionality (was: writes succeeded to a dead-end table; now: 503 error). This matches#1747's decision for the MCP path. Documented in the response error.SOP Checklist (RFC #351)
1. Comprehensive testing performed
go test -short -count=1 ./internal/handlers/— green (17s, includes the 8 updated Commit tests + new EmbedNotCalled regression).go vet ./...— clean.if h.memv2 == nilguard reverts to the bug we're fixing; removing theSource: MemorySourceUserline changes captured value inTestMemoriesCommit_Local_Success.2. Local-postgres E2E run
N/A. Handler-level change exercised by sqlmock + plugin stubs; no DDL.
3. Staging-smoke verified or pending
Pending — required before merge. Once this lands and a tenant recycles:
/workspaces/<ws-id>/memorieswith a test memory.memory_plugin.memory_records(NOTagent_memories).agent_memoriesrow count stops growing on agents-team.4. Root-cause not symptom
Yes. The symptom was "v2 plugin has zero data". The proximate cause was the HTTP handler's raw SQL INSERT. The deeper cause was that
#1747's scope (kill MCP-side fallback) was framed narrowly — the audit on 2026-05-24 surfaced that there's a parallel HTTP surface that was missed. This PR closes that gap. The runtime-side migration is the next layer of root-cause work tracked separately.5. Five-Axis review walked
Walked solo so far. Happy to dispatch a hostile reviewer for sign-off on the scope-decision (writer-only migration, not full handler) before merge.
6. No backwards-compat shim / dead code added
h.embedis preserved because Search/Update still use it (read path remains v1 for now). Will be cleaned up when those methods migrate.7. Memory/saved-feedback consulted
feedback_no_single_source_of_truth— the v1+v2 split was the SSOT violation; this PR closes one of the writer paths.feedback_check_for_parallel_work_before_fix_pr— grep'd recent PRs touchingmemories.gobefore writing: only #1759 (seedInitialMemories) and the original migration PRs were in flight; no duplicate work.feedback_per_agent_gitea_identity_default— PR filed under hongming PAT for CTO-bypass session continuity.🤖 Generated with Claude Code
Approving #1794: writer migration closes the v2-plugin-has-zero-rows leak. Single-axis change (Commit only, Search/Get/etc unchanged), 503-on-unwired matches #1747 posture, tests + vet green locally. CTO-bypass 2026-05-24.
Approving #1794: writer migration closes the v2-plugin-has-zero-rows leak. Single-axis change (Commit only, Search/Get/etc unchanged), 503-on-unwired matches #1747 posture, tests + vet green locally. CTO-bypass 2026-05-24.