[Bug]: MCP `remember` always runs `improve()` — full-graph projection per call, whole-graph re-embed with triplet_embedding
Current remaining scope — September 2026 triage
Reopened for the missing MCP self_improvement control only. The prior full-graph projection/re-embedding fix remains historical completed work; this ticket does not assert that those costs recur today.
Expose an optional boolean on MCP remember and forward explicit False through CogneeClient in direct and API modes. Extend the REST path if needed so the flag cannot be silently ignored. Preserve the existing default when omitted and document permanent/session-mode behavior.
Acceptance criteria:
- MCP
remember(..., self_improvement=False)reaches the underlying operation unchanged in direct and API modes. - Explicit
Falsesuppresses the automatic improvement stage without skipping ingestion. - Omitted flag preserves current defaults; session-mode behavior is documented and tested.
- Regression tests cover true, false, omission, and background permanent writes.
The original community report and resolved performance history are retained below.
Environment
cognee1.4.0,cognee-mcp0.5.5 (stdio transport, direct mode — not API mode)- Postgres 17 + pgvector, Neo4j 5 Community
- Graph: ~27,100 nodes / ~86,400 edges
- macOS 15, Python 3.12
Summary
remember() defaults to self_improvement=True, so every call runs improve() → memify(). Because memify builds its memory fragment by projecting the entire graph out of the graph DB, every remember pays a full-graph projection — and cognee-mcp gives no way to turn it off.
On our graph that is ~1.1 GB RSS and several seconds per remember in the best case. In the worst case — triplet_embedding enabled — every remember re-embeds the whole graph: ~86k triplets, ~30 minutes, ~US$8 of embeddings per remember.
There are really three separable problems here; (2) is the one I'd most like to see fixed.
1. self_improvement is not reachable through the MCP server
remember() accepts it (remember.py#L643) and calls improve() when true (#L1126), but the MCP client builds its kwargs without it, so the default always wins:
# cognee-mcp/src/cognee_client.py, remember()
kwargs = {"data": data, "dataset_name": dataset_name}
if session_id:
kwargs["session_id"] = session_id
if custom_prompt:
kwargs["custom_prompt"] = custom_prompt
result = await self.cognee.remember(**kwargs)The MCP remember tool exposes data, dataset_name, session_id, custom_prompt — no self_improvement, and no env var overrides it. So an MCP-based deployment cannot opt out of per-call self-improvement at all.
2. memify projects the whole graph even when it has no work to do
In memify.py#L104 the fragment is built whenever data is None, before anything considers whether the task list is empty:
if not data:
memory_fragment = await get_memory_fragment(node_type=node_type, node_name=node_name)
data = [memory_fragment]Meanwhile the default extraction tasks are gated on a config flag (memify_default_tasks.py#L11):
if not get_cognify_config().triplet_embedding:
return []triplet_embedding defaults to False, so in a default install the extraction list is empty and the only enrichment task runs over a fragment nothing will consume. The projection is pure waste — but it still costs a full graph read plus ~1 GB of Python objects, on every remember.
Compounding it, improve() coerces node_type back to NodeSet even when a caller explicitly passes None (improve.py#L227-229). Any graph built through the ordinary add() + cognify() path has zero NodeSet nodes, so the fragment is empty anyway — the whole graph is projected to produce nothing.
Suggested fix: skip get_memory_fragment when the resolved task list can't consume it, and/or let an explicit node_type=None mean "no filter" instead of being overridden.
3. With triplet_embedding=true, every remember re-embeds the entire graph
Enabling the flag (reasonable-looking: it makes cognify maintain triplet coverage incrementally at ingest) turns the empty extraction list into a real one. Combined with (1) and (2), each remember becomes a full re-embed of every triplet in the graph.
Measured on our deployment: OpenAI spend went from US$8.03 to US$36.13 in a single day purely from ordinary remember calls, each running ~30 minutes and pinning ~1.1 GB. Reverted the flag within the hour.
I don't think the flag is wrong to exist — it's that (1) and (2) make its blast radius far larger than "embed triplets at ingest" suggests.
Reproduction
import cognee
await cognee.add("some text"); await cognee.cognify() # build a non-trivial graph
# then, with TRIPLET_EMBEDDING=true in the environment:
await cognee.remember("a short fact")Watch ~/.cognee/logs — a memify_pipeline run starts and get_triplet_datapoints walks the whole graph, logging Batch N complete: processed 100 triplets (total processed: …) up to the full edge count. Same via the MCP remember tool, with no way to prevent it.
What I'd suggest
- Expose
self_improvementon the MCPremembertool (or honour an env var such asREMEMBER_SELF_IMPROVEMENT=false). - Don't build the memory fragment when no task will consume it — this alone removes a full-graph read from every
rememberin a default install. - Consider whether
self_improvement=Trueis the right default forremember()at all, given it grows with total graph size rather than with what was just remembered.
Side note — a diagnostic trap worth documenting
While debugging this I twice concluded no re-embedding was happening because the Triplet_text row count stayed flat. It was wrong: index_data_points upserts by id, so rewriting every row leaves the count unchanged. The reliable signals are the total processed: N counter in the logs, or the provider's cost report. Might be worth a line in the docs — the flat row count is very convincing and very wrong.
Happy to test a patch or open a PR for (2) if that's useful.
Source: topoteretes/cognee