# Persistent Agent Memory with an Obsidian Wiki and Claude Code

> An Obsidian wiki that agents write and read gives Claude Code persistent memory, and its schema file tells each agent where a fact belongs before it writes.

- Canonical URL: https://webofmike.com/agent-memory-obsidian-wiki/
- Author: Mike Moore (https://webofmike.com/about/)
- Published: 2026-11-09
- Last modified: 2026-11-09
- Tags: AI Agents, Generative AI, AI Tools, Platform Engineering
- Cite as: Mike Moore, "Persistent Agent Memory with an Obsidian Wiki and Claude Code", Web of Mike (webofmike.com), 2026-11-09. https://webofmike.com/agent-memory-obsidian-wiki/


My agents start every session already knowing a week of meetings, every demo I've built, and every trap I've hit. That isn't because the model remembers. It's because they read and write a plain markdown wiki that I open in Obsidian, and a schema file at its root tells each agent where a fact goes before it writes it. The starter kit, with a fictional sample vault, is at [github.com/themsquared/agent-wiki-graph](https://github.com/themsquared/agent-wiki-graph).

This post covers both halves: the graph as **persistent memory**, and the graph as a way to **direct agents**. Both come out of one real run, where I had Claude Code ingest a week of meeting recordings, my recent demo repos, and this blog into the wiki in one sitting.

## What problem does a wiki solve that agent memory doesn't?

Built-in agent memory is a short index of notes that loads into every session. That's useful, and I use it. But an index can't hold a three-hour technical session, a stakeholder's position over two months, or the reason a version number in a summary is wrong. It needs somewhere to point.

The wiki is that somewhere. My Claude Code memory index stays at one line per item, and each line points at a page in the wiki. The page holds the substance, the sources, and the links to everything related.

The starting point was Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) idea: raw sources, a wiki the model maintains, and a schema file that tells it how. What I added on top is the two-layer split, the provenance rule, and running it with parallel agents.

Three properties make a wiki work as memory where a pile of notes doesn't:

- **A person can read and fix it.** When an agent writes something wrong, I see it in Obsidian and correct it. A vector store doesn't give me that.
- **Sources are immutable.** Every transcript lands in a `_raw/` folder and is never edited. Every synthesized page cites it. When a page and a raw source disagree, the source wins.
- **Links carry meaning.** A page about one account links to the cross-account trap that bites it. An agent answering a question follows those links one hop out, and the answer is often on the linked page, not the obvious one.

## How the wiki is laid out

Two layers sit side by side. Per-account vaults hold relationship state: who, what stage, what's blocked, what was said. A root technical layer holds what outlives any one account: traps, version behavior, demo environments.

```
accounts/<slug>/       one vault per account
  CLAUDE.md              account-specific deltas only
  index.md               durable identifiers + overview
  status.md              REGENERATED operational dashboard
  timeline.md            reverse-chronological material events
  people/                one page per stakeholder
  topics/                one page per workstream
  meetings/              YYYY-MM-DD-slug.md, one per meeting
  _raw/                  IMMUTABLE sources, never modified after saving

concepts/              cross-account traps and technique
products/              per-product version behavior
demos/                 demo repos and environments
sources/               synthesized sources feeding the technical layer
```

The second layer is the one that compounds. A trap found at one account is worth more as a `concepts/` page than as a line buried in that account's timeline.

## How a schema file directs agents

Every agent reads the root `CLAUDE.md` before writing anything. It defines page types, frontmatter, and linking rules. The parts that actually steer agent behavior are three rules.

**A routing question.** Every fact gets one question before it's written:

> When ingesting, ask: *is this fact about this account, or about the product?* Account-specific goes in the vault. Generalizable goes in `concepts/`, linked from both.

That one sentence is why the technical layer grows on its own. An agent ingesting a call about a deployment problem writes the meeting into the account vault and the product behavior into a concept page, then links them.

**A provenance rule.** Agents may not write a fact they didn't read:

```markdown
- **Never write a fact you did not read from a source.** No inferring a config key from a related
  field, no guessing image tags.
- Mark unverified claims `[UNVERIFIED]` with what would confirm them.
- Prefer "reproduced on a live cluster 2026-10-14" over "supports X".
- Version-pin everything.
```

I ship commands and version numbers to people who act on them. The `[UNVERIFIED]` marker turns an agent's guess into a visible to-do instead of a quiet error.

**A fan-out target.** The ingest skill says a real source touches 5 to 15 pages, and that touching one means you under-ingested. A meeting creates the meeting page, then updates each attendee, each workstream, the timeline if something material happened, the status page if risk moved, and the technical layer if it revealed something general.

This isn't a security boundary. I've written before about why [an AGENTS.md is not a security control](/agents-md-not-a-security-control/), and that still holds. A schema file is a routing control. It tells a cooperative agent where things go, and it's very good at that.

## The real run: a week of recordings into the graph

I record meetings with a [Pocket AI recorder](/pocket-ai-recorder/) and read them from Claude through [Pocket's MCP server](/connect-pocket-ai-to-claude/). The week had 28 recordings. The run went like this.

**1. Pull and triage.** The recency search returned full transcripts, so each page of five came back larger than the tool result limit and was saved to a file. I listed title, length, and speakers per recording, then sorted them: account calls, internal sessions, and noise. Four were empty clips or small talk. One was a personal call that had no business in a work wiki, so it was excluded before anything was written.

**2. Save raw first.** Every kept recording became an immutable raw file before any synthesis. The script takes per-recording notes, including warnings that get stamped into the file header:

```json
{
  "example-0001": {
    "slug": "2026-10-14-pocket-acme-robotics-kickoff",
    "start": "2026-10-14T16:00:00Z",
    "event": "Acme Robotics POC kickoff (fictional example)",
    "warnings": [
      "The second 'Jane Doe' segment refers to Jane in the third person ('Jane asked me to raise budgets'). That label is on another speaker. Do not attribute the budget request to Jane.",
      "The Pocket summary says 'Kubernetes 1.3'. The transcript says 1.33 on POC clusters and 1.31 in production. The summary dropped a digit."
    ]
  }
}
```

```bash
python3 scripts/pocket_to_raw.py examples/pocket-sample.json accounts/acme-robotics/_raw examples/notes.json
```

```
wrote accounts/acme-robotics/_raw/2026-10-14-pocket-acme-robotics-kickoff.md
```

Run it again and it refuses, because raw is immutable:

```
exists, skipped: accounts/acme-robotics/_raw/2026-10-14-pocket-acme-robotics-kickoff.md
```

The real run saved 23 raw transcripts. Seven of them had already been summarized into the wiki straight from Pocket in an earlier session, without the raw ever being saved. Saving them now gave those meeting pages a source to cite and let an agent check the summary against the transcript.

**3. Fan out with parallel subagents.** Six subagents ran at once, split by account: one per large account, one for a group of smaller ones, one for internal sessions, and one to create a vault for a brand-new account. Then three more ran to map my demo repos, this blog, and my Claude Code memory notes into the technical layer.

## How parallel agents share one vault without overwriting each other

Nine agents writing one folder of markdown will lose a paragraph eventually. The rule that prevents it is ownership:

```markdown
For a week of recordings, split by account and give each subagent exactly one vault to write.
Shared root files (`index.md`, `log.md`, `products/*`, existing `concepts/*`) belong to the
orchestrator only. Subagents may create new `concepts/` pages and must return proposed edits to
shared files in their report instead of writing them.
```

Each agent's report comes back with three sections the orchestrator acts on: proposed edits to shared pages, facts that belong in other accounts' vaults, and open questions. The internal-sessions agent, for example, found facts for seven different accounts in two internal syncs. It wrote none of them into those vaults. It handed them back, and they were routed after the account agents had finished.

This has a business side too. The routing step is where cross-account knowledge shows up: the same product gap raised in an internal sync and on two customer calls lands on one concept page with three backlinks, and that page is what I read before the next call where it might come up.

## What the graph looks like, and what that tells agents

I wrote a small checker that walks every wikilink, resolves it the way Obsidian does, and counts pages, edges, broken links, orphans, and the most-linked pages:

```bash
python3 scripts/graph_check.py .
```

```
pages 17  edges 44  broken targets 0  orphans 0
```

That's the sample repo. My real vault came back at 1,658 pages and 12,870 edges mid-run, with 108 orphans: pages nothing linked to, mostly older meetings and people. An orphan is memory an agent can't reach by following links, so it only turns up in a grep.

The fix was a regenerated page map at the bottom of every account's `index.md`, listing its topics, people, meetings and resources. The last 13 orphans were people pages with the same filename in two accounts, where a bare link like `[[sam-sample]]` only reaches one of them. Full-path links in the map (`[[accounts/<slug>/people/<name>|<name>]]`) fixed those. After the run: 1,718 pages, 14,458 edges, zero orphans.

The most useful output is the top hubs. In my wiki, the most-linked pages aren't products or accounts. They're traps in the tools agents use to read data: a CRM assistant whose narrative summary contradicts its own raw fields, a chat search that silently drops messages with a date filter, a dashboard metric whose daily changes are portfolio-wide rather than per account. Hundreds of pages link to them, because every sync that hit the trap cited the page.

That's the directing half. When an agent reads an account vault's `CLAUDE.md`, the "Known traps that bite this account" section links to those concept pages, and the agent reads the warning before it calls the tool. The most-linked part of the graph is a list of ways the agent's own inputs lie, which is what an agent needs to know first.

## Gotchas from the run

**Named speaker labels are wrong more often than they look.** Pocket labels some speakers with a real attendee's name and others as `SPEAKER_01`. A named label is a diarization guess that only looks more reliable than a numbered one. When content can't confirm who spoke, attribute the quote to the side ("customer side") rather than the person. The tell is a speaker using their own label in the third person. In this run, an internal session put my name on a product presenter, and the recorder's summary then turned it into something I supposedly said. The fix is in the raw header warnings and in a concept page that every ingest agent reads.

**Recorder summaries restate transcription errors as facts.** A summary is written from the transcript, including its mistakes. Dropped digits in version numbers are the common case. Extract facts from transcript segments only, and keep the summary under a heading that marks it as machine output.

**Escaped pipes break naive link parsing.** Obsidian tables need `[[path|Display]]` written as `[[path\|Display]]`. My first version of the checker kept the backslash in the target and reported every table link as broken. The fix is one character class in the regex.

**A path bug in a shell loop is silent.** My first raw-save loop derived each vault folder with `sed` and left a trailing underscore, so every file landed in `<vault>_/_raw/`. The script reported success for each one. A `git status` caught it, and a move with a collision check fixed it. Check where files actually landed, not what the script printed.

**Some recordings don't belong.** A work wiki is a git repo full of account data. A personal call recorded on the same device must be excluded at triage, before the raw save, because `_raw/` is immutable by design.

**Recordings run past the end of the call.** Two transcripts in this run kept going after the meeting ended and picked up household audio, and the recorder's summary turned that into a section of the meeting notes. The tell is a duration longer than the calendar slot, or speaker labels that first appear after the goodbye. Ingest agents found both, flagged them, and stopped, because redacting an immutable source is a person's call. The rule now in the wiki: find the end of the call before saving, and note it in the raw file's warning block.

## Quickstart

Prerequisites: Python 3, Obsidian, and Claude Code if you want the `/wiki` skill.

```bash
git clone https://github.com/themsquared/agent-wiki-graph
cd agent-wiki-graph
python3 scripts/graph_check.py .
```

Open the folder as an Obsidian vault and look at the graph view. Then open it in Claude Code and ask `/wiki query what are Acme's POC success criteria?`. The answer should cite the meeting page, and it should not attribute the budget request to Jane.

## What I'd build next

The wiki answers "what is true now" well. It answers "what changed since last week" less well, because that lives in git history rather than in the graph. A weekly diff page generated from commits is the next piece. The starter kit is at [github.com/themsquared/agent-wiki-graph](https://github.com/themsquared/agent-wiki-graph).

