Knowledge Base (KB)
Overview
The Knowledge Base (KB) is a PARA-structured persistent memory system for AI agents. It survives agent compactions, session boundaries, and context window limits by storing facts, decisions, architecture records, and project state in files on disk.
PARA stands for:
- Projects — active deliverables with deadlines
- Areas — ongoing responsibilities without end dates
- Resources — reference material, templates, cross-references
- Archives — stale content, git-preserved, never deleted
The KB works alongside the memory MCP tools (search_nodes, open_nodes,
read_graph, create_entities, add_observations, create_relations) and the
file-based scratchpad pattern. Use all three layers together:
| Layer | Scope | Persistence | Example |
|---|---|---|---|
| KB files | Long-term org memory | Indefinite (git) | ~/kb/projects/foo/facts.yaml |
| MCP knowledge graph | Relationship graph | Per-session + recall | Entities, relations, observations |
| Scratchpad file | Active session bridge | < 30 min | ~/.kb-session-context.md |
Workflow
The KB lifecycle follows a predictable loop across every agent session:
Session Start → Load Context → Check Decisions → Work & Capture → Log → Session End
Per-Session Loop
- Cold start — MCP server connects, scratchpad is checked for < 30 min state
- Load yesterday — last session's End-of-Session Summary is retrieved
- Open decisions — all facts with
status: openacross every category - Work — agent performs tasks, captures facts as they arise
- Log decisions — every resolved decision gets a fact with
status: confirmed - End-of-Session — summary written, scratchpad updated, git commit + brain save
Weekly Loop
- Archive projects untouched > 90 days
- Prune stale observations
- Rebuild entity cross-reference index
- Review open decisions for closure
- Verify all code blocks in KB skills parse correctly
Querying the Knowledge Base
Using MCP Memory Tools
The memory MCP server exposes a knowledge graph. Query it with structured tools:
search_nodes — find entities by name or pattern:
# Python — via MCP
result = tool.memory_search_nodes(query="client-alpha")
for node in result.nodes:
print(f"{node.name}: {node.entity_type}")
for obs in node.observations:
print(f" - {obs}")
open_nodes — load full entity details including all observations and relations:
nodes = tool.memory_open_nodes(names=["client-alpha", "website-redesign"])
for node in nodes:
print(f"=== {node.name} ({node.entity_type}) ===")
for obs in node.observations:
print(f" {obs}")
read_graph — dump the entire knowledge graph for cold-start initialization:
graph = tool.memory_read_graph()
print(f"Entities: {len(graph.entities)}")
print(f"Relations: {len(graph.relations)}")
Using Filesystem grep
When the MCP server is unavailable, fall back to filesystem tools:
# Find all facts about a specific entity
grep -r "entity: client-alpha" ~/kb/
# List all open decisions across every project
grep -r "status: open" ~/kb/projects/ --include="facts.yaml"
# Quick count of entities per category
for d in projects areas resources; do
count=$(find ~/kb/$d -maxdepth 2 -name "facts.yaml" | wc -l)
echo "$d: $count entities"
done
# Search daily notes for a keyword
rg "blocker" ~/kb/areas/daily/
Writing to the Knowledge Base
Creating Entities
Register a new entity in the knowledge graph:
# Start tracking a project entity
tool.memory_create_entities([
{
"name": "client-alpha-website",
"entityType": "project",
"observations": [
"Client: Alpha Corp",
"Deadline: 2026-09-30",
"Stack: Next.js + Tailwind + Supabase",
"Status: in_progress"
]
}
])
Adding Observations
Add facts to an existing entity as you discover them:
# Attach new facts mid-session
tool.memory_add_observations([
{
"entityName": "client-alpha-website",
"observations": [
"Decided: use Stripe for payments, not Paddle",
"API contract: GET /api/projects returns paginated list",
"Blocker: waiting for client to send brand assets"
]
}
])
Creating Relations
Link entities to model dependencies, influences, and ownership:
# Wire up relationships between entities
tool.memory_create_relations([
{
"source": "client-alpha-website",
"target": "payment-integration",
"relationType": "depends_on"
},
{
"source": "client-alpha-website",
"target": "auth-patterns",
"relationType": "references"
},
{
"source": "alice",
"target": "client-alpha-website",
"relationType": "owns"
}
])
File-Based Storage (YAML)
Canonical writer for per-entity YAML fact files alongside the knowledge graph:
import yaml
from datetime import datetime
from pathlib import Path
KB = Path.home() / "kb"
def save_fact(entity: str, category: str, value: str, **kw):
"""Append one fact to a per-entity YAML file.
Args:
entity: the thing being described (project, tool, client)
category: 'projects', 'areas', or 'resources'
value: the fact text
**kw: optional metadata (status, priority, related, source)
"""
dir_ = KB / category / entity
dir_.mkdir(parents=True, exist_ok=True)
facts_file = dir_ / "facts.yaml"
facts = []
if facts_file.exists():
with open(facts_file) as f:
facts = yaml.safe_load(f) or []
facts.append({
"value": value,
"recorded_at": datetime.now().isoformat(),
**kw
})
with open(facts_file, "w") as f:
yaml.dump(facts, f, default_flow_style=False, sort_keys=False)
return facts_file
Maintenance
Pruning Stale Observations
Knowledge graphs accumulate noise. Run periodic pruning:
def prune_stale_observations(days_threshold=90):
"""Flag observations older than threshold for review."""
from datetime import timedelta
cutoff = datetime.now() - timedelta(days=days_threshold)
stale = []
for category in ["projects", "areas", "resources"]:
for facts_file in (KB / category).rglob("facts.yaml"):
with open(facts_file) as f:
records = yaml.safe_load(f) or []
fresh = []
for rec in records:
recorded = datetime.fromisoformat(rec["recorded_at"])
if recorded < cutoff and rec.get("status") != "archived":
stale.append({
"file": str(facts_file),
"value": rec["value"],
"recorded_at": rec["recorded_at"]
})
else:
fresh.append(rec)
if len(fresh) != len(records):
with open(facts_file, "w") as f:
yaml.dump(fresh, f, default_flow_style=False, sort_keys=False)
return stale
Weekly Review Checklist
- Move completed projects from
projects/toarchives/ - Delete observations marked "obsolete" or "superseded"
- Verify all open decisions still need resolution
- Run
entity-index-gen.pyto rebuild the cross-reference - Check that today's daily note has an End-of-Session Summary
Archiving Stale Content
#!/usr/bin/env bash
# archive-stale.sh — move projects untouched for 90+ days to archives
KB=~/kb
CUTOFF=$(date -d "90 days ago" +%s)
for dir in "$KB"/projects/*/; do
last=$(git -C "$KB" log -1 --format="%at" -- "$dir" 2>/dev/null || echo 0)
if [ "$last" -gt 0 ] && [ "$last" -lt "$CUTOFF" ]; then
name=$(basename "$dir")
mv "$dir" "$KB/archives/projects/$name"
echo "Archived: $name"
fi
done
Session Start Routine
This is the critical cold-start flow every agent session should execute.
Step 1 — Check the Scratchpad Bridge
from pathlib import Path
from datetime import datetime, timedelta
SCRATCHPAD = Path.home() / ".kb-session-context.md"
def load_session_context():
"""Read the bridge file if it exists and is fresh (< 30 min)."""
if not SCRATCHPAD.exists():
return {"state": "cold_start", "context": {}}
mtime = datetime.fromtimestamp(SCRATCHPAD.stat().st_mtime)
if datetime.now() - mtime > timedelta(minutes=30):
return {"state": "stale", "context": {}}
text = SCRATCHPAD.read_text()
context = {}
for line in text.split("\n"):
if line.startswith("- "):
key, _, val = line[2:].partition(": ")
context[key.lower()] = val
return {"state": "warm", "context": context}
Step 2 — Load the Knowledge Graph
# Re-establish entity context
graph = tool.memory_read_graph()
stale_entities = [e.name for e in graph.entities
if "stale" in (e.observations or [])]
# Open entities related to today's focus
focus = "client-alpha"
related = tool.memory_open_nodes(names=[focus])
for node in related:
print(f"Loaded: {node.name} — {len(node.observations)} observations")
Step 3 — Create Today's Daily Note
def ensure_daily_note():
"""Create today's note if it doesn't exist."""
KB = Path.home() / "kb"
today = datetime.now().strftime("%Y-%m-%d")
note_file = KB / "areas" / "daily" / f"{today}.md"
if note_file.exists():
return note_file
# Load yesterday's summary for continuity
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
yesterday_file = KB / "areas" / "daily" / f"{yesterday}.md"
yesterday_summary = ""
if yesterday_file.exists():
text = yesterday_file.read_text()
if "## End-of-Session Summary" in text:
yesterday_summary = text.split("## End-of-Session Summary", 1)[1]
content = f"""# Daily Note: {today}
## Session Focus
_(What is the single most important outcome this session?)_
## Continuity Context
{yesterday_summary}
## Open Decisions Needing Input
## Plan
- [ ]
## Decisions Made Today
## End-of-Session Summary
_Write before closing — this bridges the next session._
"""
note_file.parent.mkdir(parents=True, exist_ok=True)
note_file.write_text(content)
return note_file
Step 4 — Load Open Decisions
def load_open_decisions():
"""Return every entity fact with status=open."""
KB = Path.home() / "kb"
decisions = []
for category in ["projects", "areas", "resources"]:
for facts_file in (KB / category).rglob("facts.yaml"):
with open(facts_file) as f:
records = yaml.safe_load(f) or []
for rec in records:
if rec.get("status") in ("open", "pending"):
decisions.append({
"entity": facts_file.parent.name,
"category": category,
"value": rec["value"]
})
return decisions
Full Bash Session Starter
#!/usr/bin/env bash
# kb-session.sh — run at every agent session start
KB=~/kb
TODAY=$(date +%Y-%m-%d)
YESTERDAY=$(date -d yesterday +%Y-%m-%d)
NOTE=$KB/areas/daily/$TODAY.md
# Step 1: Load yesterday's summary
if [ -f "$KB/areas/daily/$YESTERDAY.md" ]; then
echo "=== Yesterday's Summary ==="
sed -n '/## End-of-Session Summary/,$ p' \
"$KB/areas/daily/$YESTERDAY.md" | head -20
fi
# Step 2: Check open decisions
echo ""
echo "=== Open Decisions ==="
grep -rl "status: open" "$KB"/{projects,areas,resources}/*/facts.yaml 2>/dev/null \
| while read -r f; do
entity=$(basename "$(dirname "$f")")
grep -A1 "value:" "$f" | head -2
echo " (entity: $entity)"
done || echo " None"
# Step 3: Create today's note
if [ ! -f "$NOTE" ]; then
cat > "$NOTE" <<EOF
# Daily Note: $TODAY
## Session Focus
## Decisions Made Today
## End-of-Session Summary
---
EOF
echo ""
echo "Created $NOTE — edit the focus line and begin."
fi
Anti-Rationalization Table
| Rationalization | Reality |
|---|---|
| "I remember what happened last session" | Agents lose everything on compaction. Without KB files, you start blank. |
| "The code comments are enough documentation" | Comments describe what; KB describes why you chose that approach. |
| "YAML facts are too structured for quick notes" | Unstructured daily notes cost nothing. Structure grows as the entity matures. |
| "I'll backfill facts when the project settles" | The gap between "I know this" and "I wrote this" is where information rot begins. |
| "grep is fast enough for recall" | grep finds text. Semantic relationships need the knowledge graph. |
| "My projects are too small for PARA" | PARA scales down to one file per entity. Starting flat means refactoring 200 files later. |
| "The scratchpad file is enough" | The scratchpad handles active bridging; the KB handles permanent organizational memory. |
| "Daily notes are for humans, not agents" | Agents read more daily notes than humans do — they have zero tacit context. |
| "I don't need relations — tags are fine" | Relations model causality and dependency. Tags model membership. Both are needed. |
| "Archiving is busywork" | Without archiving, every grep finds 3-year-old context about dead projects. |
Verification Checklist
-
~/kb/{projects,areas,resources,archives}directory tree exists - Today's daily note is present at
~/kb/areas/daily/YYYY-MM-DD.md - Yesterday's daily note contains an End-of-Session Summary section
-
save_fact()writes valid YAML re-readable withyaml.safe_load() - Entity facts contain
recorded_at,value, andstatusfields -
load_open_decisions()returns at least one result before cleanup - Knowledge graph has at least 3 entity types registered
- Scratchpad bridge file is written on session end, checked on session start
- Scratchpad older than 30 minutes is treated as stale (not loaded)
-
prune_stale_observations(90)returns no false positives - All Python code blocks parse without syntax errors
- All bash code blocks pass
bash -nsyntax check - Archive script has been run at least once
- YAML fact files are under git —
git log --onelineshows fact history - Weekly review checklist completed within the last 7 days
When to Use
Use this skill when working with kb. Specifically:
- Every session start — load context, check open decisions, create daily note
- After every decision — capture as a YAML fact with
status: openorstatus: confirmed - At session end — write End-of-Session Summary, update scratchpad, commit
- During investigations — store entity facts as you discover them
- Weekly — review stale content, archive untouched projects
- When spawning subagents — share relevant KB entities via
local://URIs so subagents start with context instead of cold