AI ENGINEERING
SELF-HOSTED
Memory layer across agents
A local memory layer Claude Code and Kiro both speak to over MCP. Three defects in it failed silently — tools reporting success while storing nothing.
- Python
- mem0
- Qdrant
- MCP
- Silent failures found
- 3
- Network egress
- 0
- MCP tools
- 11
Silent failures found
Network egress
MCP tools
The problem
Coding agents working the same codebases shared nothing. Claude Code kept its transcripts, Kiro kept its own, and neither could answer a question the other had already resolved. Re-explaining architecture at the start of every session was the tax, and it scaled with every agent added.
The approach
A local memory layer every agent speaks to over MCP: mem0 extracts facts, a local model embeds them, Qdrant stores the vectors. Everything runs on the machine — no API key, no cloud, no egress. It is strictly additive: transcripts and --resume are independent and keep working if the layer is down.
MEMORY PATH
Engineering work happening in Claude Code and Kiro.
What broke — and how it was caught
Every one of these failed silently. The tools returned success while storing nothing, which is the worst failure mode a memory layer can have: you only find out weeks later, when the thing you told it is gone.
DEFECT 01
Reasoning models returned empty content
- SYMPTOM
- Tools reported success while storing nothing. Zero facts extracted, no error anywhere.
- CAUSE
- mem0 sends num_predict=2000. Reasoning models spend the whole budget in their thinking channel, which Ollama strips from message.content — the response arrives done_reason="length" with an empty string. The upstream fix appends /no_think to the prompt text, which stopped working on Ollama 0.21+.
- FIX
- Set think=False at the API level, and prefer a non-thinking instruct model.
- EVIDENCE
- Against mem0's real fact-retrieval prompt, 3 inputs: qwen2.5:3b-instruct returned valid JSON 3/3, qwen3:4b 1/3.
DEFECT 02
The default prompt discarded every technical fact
- SYMPTOM
- A memory layer built for engineering context stored coffee preferences and dropped engineering statements.
- CAUSE
- mem0's FACT_RETRIEVAL_PROMPT opens "You are a Personal Information Organizer". Measured: "I prefer dark roast coffee, I work as a software engineer" extracted two facts; "The batch scheduler fix raised successfully placed jobs from 232 to 247" extracted an empty list.
- FIX
- A replacement extraction prompt written for engineering statements, wired in through custom_fact_extraction_prompt.
- EVIDENCE
- Silent total failure for the layer's actual purpose — success responses, empty store.
DEFECT 03
delete_all_memories ignored its own guard
- SYMPTOM
- A no-argument call wiped the entire default user scope, despite a docstring promising at least one filter is required.
- CAUSE
- The scope check ran against a value that had already been defaulted, so the guard could never be true — dead code. The sibling delete_entities checks the raw parameters and is correct; the two disagreeing is what makes it a bug rather than a design choice.
- FIX
- Wrap the registered tool and restore the documented behaviour, with an explicit environment-variable opt-out.
- EVIDENCE
- Test discriminates: guard on with no args refuses and 2 memories survive; guard off with no args deletes 2, reproducing the bug; guard on with explicit scope deletes 6, so the intended path still works.
Design notes
- WHY ONLY QDRANT IS CONTAINERISED
- Ollama stays on the host so it can reach the GPU. Docker Desktop on macOS has no Metal access, and on Linux this removes the nvidia-container-toolkit dependency entirely.
- WHY PATCHES LOAD AT RUNTIME
- Fixes are dropped into site-packages via a .pth file rather than editing the package, so the pinned commit stays byte-identical to what was reviewed and a reinstall cannot lose them.
- SECURITY POSTURE
- The HTTP transport has no inbound authentication, so the installer refuses to bind 0.0.0.0. Kiro is configured with the two destructive tools disabled.
- DEPENDENCY CEILINGS
- mcp and mem0ai both shipped breaking 2.x releases after the pinned commit; unpinned installs do not import. CI asserts the ceilings are still in place.