Skip to content

fix(mcp): consolidate and recall memory on every request - #112

Merged
alpibrupa merged 1 commit into
mainfrom
fix-mcp-memory-consolidation
Sep 4, 2026
Merged

fix(mcp): consolidate and recall memory on every request#112
alpibrupa merged 1 commit into
mainfrom
fix-mcp-memory-consolidation

Conversation

@alpibrupa

Copy link
Copy Markdown
Contributor

Summary

  • `make_handler` called `ag.run_loop` directly on a raw `AgentLoop`, never constructing a `session.lex` `Session` — so `consolidate.run()` never ran for MCP requests. Any `remember` tool call made over MCP wrote a candidate to `.lex/memory-candidates.jsonl` that stayed an unconsolidated candidate forever, regardless of provider.
  • Since brains are built once in `main()`, before any candidate exists, a per-request refresh is the only place MCP can pick this up. Each `code` tool call now:
    1. Consolidates whatever candidates are pending under a fresh synthetic id (`crypto.random_str_hex(16)` — MCP has no client-tracked session to attribute them to).
    2. Re-recalls current memory and prepends it to the selected brain's system prompt via `sess.with_memory` (same mechanism `session.lex` uses for ACP/TUI/graph.lex), so a fact remembered on one call is visible — attested in the trail — by the next.
  • Recall goes through `pmem.open()` + `recall_for_prompt()` rather than the higher-level `recall_context()`: the latter's `fs.exists` guard needs `fs_walk`, which `Skill.handle`'s fixed effect row (`lex-agent/src/server.lex`) does not carry. `open()` already creates the db lazily on a missing file, so the guard isn't needed here.

Fixes #109

Test plan

  • `lex check src/server/mcp_main.lex` — ok
  • `lex fmt --check` — formatted
  • Full repo sweep (`lex check` over every tracked `.lex` file) — all pass
  • `lex test` — 4/4 passed
  • `lex doc-sync --check` — current
  • Live pipeline test with a standalone driver replicating this exact code path (`candidates.propose()` → `consolidate.run()` → `pmem.open()`/`recall_for_prompt()`): candidate correctly promoted into `project_memory.db` with a full attestation trail in `memory_trail.db`, and recall rendered `"\n\nMemory:\n- [convention/commit-style] ..."` — which `prefix_goal`'s `str.trim` correctly treats as non-blank and prepends to the agent's goal

make_handler ran ag.run_loop directly on a raw AgentLoop, never
constructing a session.lex Session the way ACP/TUI/graph.lex do — so
consolidate.run() never ran for MCP, and a remember tool call made
over MCP wrote a candidate that stayed an unconsolidated candidate
forever, regardless of provider.

Brains are built once in main(), before any candidate exists, so a
per-request refresh is the only place MCP can pick up memory at all:
each call now consolidates pending candidates under a fresh synthetic
id (MCP has no client-tracked session to attribute them to) and
re-recalls before running the loop, via sess.with_memory — same
mechanism session.lex uses. Recall goes through pmem.open() +
recall_for_prompt() rather than the higher-level recall_context(),
since the latter's fs.exists guard needs fs_walk, an effect
Skill.handle's fixed row (lex-agent/src/server.lex) does not carry;
open() already creates the db lazily so the guard isn't needed here.

Fixes #109
@alpibrupa
alpibrupa merged commit accd83f into main Sep 4, 2026
1 check passed
@alpibrupa
alpibrupa deleted the fix-mcp-memory-consolidation branch September 4, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP mode never consolidates proposed memory candidates

1 participant