Context Engineering Series · Part 4
Part 1 gave Claude memory, but 200+ files later that memory had started to drown it, because every session loaded everything whether it mattered or not. What I actually wanted was for it to load the right things, so a local knowledge graph took over the job manual curation used to do and started routing context by the entities a task actually touches.
The 5-layer architecture from Part 1 worked... Claude remembered across sessions, loaded wiki pages on demand, and applied rules consistently. But after six months of daily use it ran into a scaling problem the original design was never built to handle, and I didn't see it coming until the context started feeling noisy.
By then I had 217 memory files, and the flat MEMORY.md index had swollen to 222 lines against a 200-line budget, so every new memory quietly pushed an older one off the bottom. The wiki was at 86 pages, and every one I added meant a manual routing-table edit in CLAUDE.md. If you forgot the edit, Claude simply never found the page. On top of that, session startup surfaced the 3 most recently modified files, but recent and relevant aren't the same thing, and you could be deep in a slide deck and still get handed yesterday's deployment fix.
What was really going on is that every layer had its own idea of relevance. Memory ranked by modification time, the wiki leaned on a hand-maintained routing table, and the rules loaded everything regardless, so I had three mechanisms stacked on top of each other that were never actually connected. The question underneath all of it was always "what should Claude load for this task?", and three independent systems kept answering it without comparing notes, which is why the answer they assembled between them came out incomplete just about every time.
A knowledge graph ties those three systems into one structure you can actually traverse, so instead of asking "show me recent files," the session asks "which entities matter to this work, and which files discuss them?"
The files themselves are nodes, about 130 memory files, 65 wiki pages, and 11 rules files. So are the entities they talk about (12 accounts, 13 products, 10 systems), and so are the 25 concepts and 9 projects, which puts the whole graph at 379 nodes.
| Edge | Meaning and example |
|---|---|
REFERENCES | A file mentions an entity. pitch-craft.md → REFERENCES → "slide generation" |
APPLIES_TO | A wiki page serves a work context. security-governance.md → APPLIES_TO → "Apex development" |
RELATES_TO | Two files share 3+ entities (IDF-filtered). pitch-craft.md → RELATES_TO → slide-visual-design.md |
DISCUSSED_IN | An entity appears in a memory file. "Agentforce" → DISCUSSED_IN → agent-harness-project.md |
PART_OF | An entity belongs to a project. "AIP" → PART_OF → "Meridian Platform" |
I ran graph-based context against the old mtime-based approach across 5 real scenarios and set the bar at 3 wins out of 5 before I'd trust it, and the graph won all five.
Each one lines up what the old system would have surfaced against what the graph surfaces for the same session start.
With mtime, session start handed Claude whatever 3 files I'd edited last, which on a slide day was usually deployment notes, an unrelated session log, or NNAOV metrics, and none of that helps you build a deck. The graph matched the slide signals to entities and came back with these six instead.
The account analysis, the Apex debug, and the harness email all went the way the slide day did, with mtime handing back whatever I'd touched last and the graph pulling files tied to the task.
| Task | Mtime surfaced | Graph surfaced |
|---|---|---|
| Preparing a customer analysis for Acme | Nothing about Acme at all, just the 3 files touched last | sharktank-account-blitz.md (references Acme) corporate-grounding-system.md (applies to analysis) feedback-verify-sources-before-building.md |
| Debugging an Apex deployment issue | Random files, none of them Apex-related, and no development rules | salesforce-development-rules.md (APPLIES_TO Apex) agentforce-design-patterns.md feedback-apex-metadata-deploy-workaround.md |
| Writing a customer email about Agent Harness | No harness context unless a harness file happened to be the last thing edited, and no communication rules | agent-harness-architecture.md hybrid-reasoning-vs-harness.md headless-360-architecture.md communication-system.md, anti-slop.md |
Scenario 5 was deploying a Cloud Run update, and mtime surfaced no deployment or security context and none of the TVM compliance rules, while the graph pulled gcp-deployment-patterns.md, gcp-security-compliance.md, integration-topology.md, security-architecture.md, and my sa-key-rotation.md notes.
Across two sessions I built the whole thing... graph construction, session integration, auto-indexing, and live tool enrichment. It all runs on local SQLite without any cloud infrastructure or API calls behind it, just entity extraction and traversal, which is the part I like most about it.
First I entity-extract every memory, wiki, and rules file, then seed 68 entities across 6 types (accounts, products, projects, systems, concepts). From there the CLAUDE.md routing table gets parsed into APPLIES_TO edges, every file is scanned for entity mentions, and RELATES_TO relationships are computed between any two files that share 3+ non-stop-word entities.
With the graph built, the SessionStart hook could query it instead of the hardcoded routing table. It pulls signals out of git state (changed files, recent commits, branch name) and the working directory, matches them to entity nodes, and traverses APPLIES_TO (weighted 3x) and REFERENCES edges to rank the wiki and memory files that matter. If graph.sqlite is missing, it falls back to the old routing.
To keep the graph current, a PostToolUse hook fires on every Write or Edit to a memory, wiki, or rules file. It upserts that file's node, deletes its old REFERENCES edges, re-scans it for entity mentions, and recomputes RELATES_TO with the IDF filter, all in 61ms per file, which means there's no full rebuild and the graph keeps up as files change, get created, or get deleted.
The last phase took the graph out of session startup and into the middle of the work. A second PostToolUse hook fires on every MCP tool call to the Salesforce server, pulls entity signals out of the tool input (account names, search queries, SOQL strings), queries the graph for matches, and surfaces the related memory and wiki files right alongside the tool results.
Both bugs showed up the same way, as a graph that connected far more than it should have. The first was a false positive, where the entity "Exa" (a search API) was substring-matching "example," "exactly," and "examination," so it turned up in 91 out of 308 files, close to a third of the whole corpus. Switching to a word-boundary regex for terms under 5 characters took it from 91 references down to 19.
The second was an edge explosion. A handful of high-frequency entities (AIP in 44% of files, Agentforce in 42%, Slack in 35%) made nearly every file relate to every other one, and with the threshold at 2+ shared entities I ended up with 16,274 RELATES_TO edges, which made the graph fully connected and useless for ranking. I added an IDF stop-word filter (>25%) and raised the threshold to 3, and that brought it down to 2,877 edges, an 82% reduction.
The entire system is local... SQLite running on your laptop, driven by a TypeScript build script and a handful of Python hooks, with no cloud database, no API calls, and no network anywhere in the loop.
By the end of the build the graph ran both session startup and live tool enrichment, and I had two extensions left that were waiting on the platform.
When a CRM query returns data about an account, the graph injects the related context automatically. Ask about Acme and Claude sees the prior analysis notes, the relevant feedback, and the wiki pages sitting right alongside the raw data, because a PostToolUse hook extracts entity signals from the tool input, queries the graph, and surfaces the top 5 related files in 55ms.
The first extension I parked was a re-query after the first message. Session-init runs before the user types, so it can't know intent, and a second graph query once you've said what you're working on would load topic-specific context mid-conversation. When I built this, Claude Code didn't have a PostUserMessage hook type, so there was nothing for that second query to hang off of.
The other one is task-specific rule loading. All 11 rules files loaded every session, and the graph could have worked out which rules actually apply to the task at hand, but Claude Code loaded every rule before any hook ran, so I parked it until the platform could load rules conditionally.
The graph gave Claude a map of how things connect. Part 5 puts that map to work, packaging the whole approach into skills Claude can reuse on its own.
Each part builds on the one before it, and you can start anywhere, though Part 1 is where the foundation gets laid.
The 5-layer foundation: CLAUDE.md, wiki, memory, hooks, and rules.
What happens without context, told through an Agent Script case study that was confident and wrong.
Nine MCP servers, 120+ tools, and the internal CRM case study.
A knowledge graph of 379 nodes and 5,120 edges, doing entity-based context assembly.
Skills and Salesforce development automation.
The context layers stop being storage and start behaving like an operating system.
The system starts changing itself, and what it takes to keep that safe.
Making the harness safe and portable across frontier labs.
Closing the self-improvement loop behind a gate it can't grade for itself.
Where the whole series lands: the thesis, stated plainly.