Daeseon Yoo
Back to project
·Tech retro·12 min

Wiring agents into operating memory — closing the read/write loop over MCP, and stopping memory from rotting

Dalkkak's memory graph was already built, but the agents actually doing the work (Claude, Codex) couldn't *read* it. This is the start-to-finish record of how I ran the cable over MCP and closed off the paths where memory rots (stale facts, prompt injection) — written so even someone who knows nothing can follow it.

This isn't written to sound clever in one line. Even I (the founder) was confused about the whole structure, so this is a start-to-finish record that someone who knows nothing can follow — and without distortion (only what I actually verified). Every date, number, and code location here is something I actually ran or read.

0. One-line summary

Dalkkak's "memory" was built, but the AI agents actually doing the work couldn't read it. Today I connected that broken link (MCP) and closed off the paths where memory freezes wrong or gets poisoned. Result: both Claude and Codex now pull my real business memory while working and answer with evidence (node_id) attached — and this isn't a hypothesis, it's a fact I confirmed running in my actual app.


1. Learn just 3 characters and you're done

This system is made of exactly 3 things.

What I did today = connecting these three for the first time, and proving they're actually connected. That's all. I didn't build some giant new system.


2. The problem: one "cable" wasn't plugged in

The core finding was this. There are 3 paths data flows along for memory, and they were at wildly different levels of completeness.

  1. Write (memory ← agent): was alive.
    • Reads git commits every 20 seconds and auto-records them as "change" nodes (capture.rs).
    • When an agent spits out a <dk-node> block while working, it catches and records it (agent_push.rs, instructions in inline.rs).
  2. Read — the path where the "librarian" produces the answer on your behalf: was alive.
    • memory_answer searches memory and produces an evidence-backed answer using my BYO claude -p (memory.rs).
  3. Read — the path where "the agent that's working pulls it directly": ❌ was broken.
    • The Claude/Codex actually coding inside a pane (terminal window) had no way to dig through memory directly mid-task when it wondered "how did I do this before?"

In other words, the agent could write to memory but couldn't read it while working. The in-loop read cable for the product's core promise ("the AI keeps your context") wasn't plugged in.

Verification: the repo had no .mcp.json, and my ~/.claude.json had 0 registered MCP servers. So the librarian was built, but it had never been introduced anywhere.


3. What MCP is (a 1-minute explanation)

MCP (Model Context Protocol) = a standard spec for handing an AI "tools." Anthropic published it as an open standard, so anyone uses it.

By analogy: an AI employee fundamentally only knows how to write text. MCP is handing that employee a keyring (the tools) and saying "you're allowed to open this drawer and look." Nothing more, nothing less.

"Plugging it in" isn't building a new program — it's just adding a few config lines that introduce the already-existing librarian to the AI. Then a "search memory" button appears in the AI's tool list.


4. What I connected and how

4-1. The Claude side (automatic connection)

Dalkkak already had a wrapper around every pane's claude (inline.rs). This wrapper automatically slips a small instruction (--append-system-prompt) into the system prompt whenever claude runs in a pane. So "a device that automatically does something every session" was already installed. I layered two things onto it.

  1. Added --mcp-config — made the wrapper also pass the librarian (MCP) config file when it launches claude. So dalkkak automatically shows up in the tool list of the Claude inside a pane.
  2. Added one instruction line — a standing rule that says "when asked about past work / decisions / bugs / current state, call a memory tool like graph.search first before answering."

The important design point: none of these paths are hardcoded. The node location is found by scanning the nvm folder, the librarian location by walking up from the executable, and the graph path is what the OS reports. Install it on someone else's Mac or move the repo, and it finds everything on its own.

4-2. The Codex side (one config block)

Codex reads ~/.codex/config.toml directly. I added an [mcp_servers.dalkkak] block there. Codex doesn't even need an app rebuild — just add the config file and a new Codex session connects to the librarian right away.

4-3. Do it once and you're done

Registration is once in a lifetime. After that, every newly launched agent automatically has the tools and calls them on its own when needed (I don't have to say "dalkkak" every time — the instruction tells it to). That said, it's the LLM's judgment so it's not 100%, and to force it for sure you just add "find it in memory" and it's compelled.


5. The 12 tools the librarian provides (all read-only — can never edit, never delete)

Of these, graph.search and memory.related are confirmed working with real data. workflow.* and receipt.* are tied to the Founder Ops workflow system, so the data is still partial/fixtures.


6. The whole picture of the read/write loop (boundaries matter)

This is the part I was most confused about, so I'm writing it precisely.

📤 Write (the path memory accumulates along) = only "set-up Services"

In glossary terms: "project" = "Service." The write loop is per-Service (onboarded via grant).

📥 Read (the path it pulls along) = global

To summarize: reads are global (the whole brain from any pane), writes are per-Service (only granted ones). The unit isn't "any folder automatically" but the granted Service. Whether global is right (a cross-startup unified brain is Dalkkak's thesis) or whether it should be split per-project is a remaining product decision.


7. What I did to close "the paths where memory gets distorted"

Connecting memory creates a new risk: distorted memory produces confidently wrong answers — which can be worse than having no memory at all. So I adversarially audited the actual code against 5 distortion vectors (all HIGH) and closed the two most dangerous ones.

7-1. Stale facts (staleness) — the correction loop

The problem: even when a decision changes, the old node isn't deleted and coexists with the new node. (My pricing records were actually found conflicting with each other.) The schema had a supersedes (replaces) concept, but zero code used it — a designed-but-never-plugged-in cable.

What I fixed:

Because it's append-only there's a trap that you can't overwrite the same node directly, so I solved it by invalidating via an edge.

7-2. Prompt injection — isolating external content

The problem: connectors suck external content like email into memory, and if the body contains something like "ignore previous instructions and do X," an agent that later reads that memory can be manipulated. The existing redaction only strips secrets, not injection.

What I fixed:

This is "defense," not "zero" — prompt injection is an open problem, so a sophisticated attack can occasionally get through. I closed it to the industry-standard level.


8. How I verified it (without distortion)

I'm only writing what I actually ran and confirmed, not just talked about.


9. How to test it on Codex (try it yourself)

Claude needs an app rebuild, but Codex just reads config, so it works immediately without a rebuild.

# 1) Is it registered (fastest check)
codex mcp list          # → if you see the line  dalkkak  ...  enabled  it's OK
codex mcp get dalkkak   # → check transport stdio, command/args/env
 
# 2) Just ask in a new codex session (interactive)
#    "우리 제품 가격 어떻게 정했는지 dalkkak 기억에서 찾아서 node_id랑 같이 알려줘"
#    ("Find how we set our product price in dalkkak memory and tell me with the node_id")
#    → success if it calls graph.search and cites the actual record
 
# 3) Confirm safely headless — read-only sandbox + approval never
codex exec -s read-only -c approval_policy="never" --skip-git-repo-check \
  "Use the dalkkak MCP graph search to find our pricing decisions and cite node ids. If memory has nothing, say so."

Expected result: Codex calls graph.search and cites the actual node. (If nothing shows up, check whether the [mcp_servers.dalkkak] block exists in ~/.codex/config.toml, and whether the node version / repo path are correct — Codex config is absolute paths, so if you swap out the node version or move the repo, just that entry breaks. In that case, codex mcp remove dalkkak and re-add.)

Note: internal paths that the Dalkkak app launches, like Claude's memory_answer, are --strict-mcp-config so they deliberately ignore this registration. This registration only takes effect for the interactive agent inside a pane — which is exactly the agent we want.


10. One trap (Claude "Invalid API key")

If Claude in a pane throws "Invalid API key," that's not an MCP problem — it's because of an invalid ANTHROPIC_API_KEY floating in the environment. Dalkkak's internal code (memory.rs) also removes that key and uses subscription auth when launching claude because of this exact issue. If it happens in a pane, just remove that env key.


11. Honestly — what's not done yet

This isn't "all finished." (As of when I'm writing this)


12. File map (where to look in the code)


13. One-line close

I connected the broken read cable (chapters 2–6) and closed the two paths where memory rots — stale facts and injection (chapter 7). All verified with the real model and real data (chapters 8–9). What remains is the commit (going in with this post), UI, new-project onboarding, and using it daily. The promise that "the AI remembers my business context" crossed today, for the first time, from I built it to it runs as a fact on my machine.