Perspective: 👤 User 🔧 Developer
Skill-Architektur · 14 Scripts · Python

Under the Hood of Context Memory

Wie der Skill intern funktioniert: Dateisystem-basierter Knowledge Graph, mehrstufige Suche, Dokument-Extraktion und 6 Export-Formate. Alles in reinem Python ohne Datenbank.

14
Python Scripts
0
Dependencies *
JSON
Storage Format
~2K
Lines of Code

* Nutzt Python-Stdlib + bereits installierte Libs (pypdf, pandas, python-docx, python-pptx, pandoc)

01
Architecture — Files Instead of a Database

No SQLite, no Redis, no vector DB. Context Memory stores everything as JSON and Markdown in the file system. This makes it portable, inspectable, and trivially debuggable.

~/.context-memory/ ├── mein-projekt/ │ ├── tree.json ← Baumstruktur mit Parent/Child-Refs │ ├── index.json ← Flacher Such-Index: ID→{title,type,tags,relevance,confidence,temporal} │ ├── relations.json ← Kanten-Liste: [{from,to,relation,note,created}] │ ├── history.json ← Audit-Log, max 500 Einträge, FIFO │ └── nodes/ │ ├── arch-001.md ← Markdown mit YAML-Header (Typ, Relevanz, Confidence, Temporal) │ ├── dec-001.md │ └── les-001.md └── anderes-projekt/ └── ... ← Komplett isolierter Workspace
Warum JSON statt SQLite?
Weil JSON schneller ist
Weil Claude keinen Zugriff auf Datenbanken hat
Weil es portabel, inspizierbar und ohne externe Dependencies funktioniert
02
The Cast — 14 Scripts, One Core Library

Each script has exactly one job. cm_core.pyShared Library: Datenstrukturen, Storage-I/O, ID-Generierung, Duplikaterkennung, Tag-Normalisierung, temporale Metadaten. ist die gemeinsame Basis — alle anderen importieren daraus.

⚙️
cm_core.py
load/save_index, generate_id, find_similar_nodes, make_temporal
🚀
cm_init.py
mkdir + leere JSON-Dateien anlegen
➕
cm_add.py
Node erstellen mit Duplikat-Check + temporale Felder + Confidence
📥
cm_ingest.py
15+ Formate → Extractor-Dispatch → Splitting → Batch-ID-Gen
🔍
cm_curate.py
os.walk + package.json/pyproject Parsing → Auto-Nodes
🔎
cm_search.py
Quick(Tags) → Deep(Fulltext) → Relations-Enrichment
🌳
cm_tree.py
Rekursive Baumdarstellung mit Box-Drawing-Chars
🕸️
cm_relate.py
22 Relationstypen, BFS-Pfadfindung, Graph-Viz
✏️
cm_update.py
Partial Update: Titel, Tags, Content, Status, Confidence
🗑️
cm_delete.py
Löschen + Kinder-Reparenting auf Root
🧹
cm_cleanup.py
Stale-Detection via datetime-Diff + Auto-Mark
📊
cm_stats.py
Counter-Aggregation über Index
📤
cm_export.py
6 Formate: MD, JSON, JSON-LD, Mermaid, Chunks, Validation
📥
cm_import.py
Full Import oder Merge mit Duplikat-Skip
Dive deeper Every script in detail — data flows and decisions → ID generation, duplicate detection, in-memory index for batch ops
03
Data Flow — From Document to Knowledge Graph

Der Ingest-Pfad ist der komplexeste Teil. Ein PDF durchläuft: Typ-Erkennung → Extractor-Dispatch → Content-Splitting → Duplikat-Check → Batch-ID-Generierung → Index+Tree-Update.

Datei
→
detect_file_type()
→
EXTRACTORS[type]
→
split()
→
find_similar()
→
Nodes + Index
# Extractor-Dispatch in cm_ingest.py EXTRACTORS = { "pdf": extract_pdf, # pypdf → pdftotext fallback "docx": extract_docx, # pandoc → python-docx fallback "xlsx": extract_xlsx, # pandas mit engine-auto-detect "pptx": extract_pptx, # python-pptx, slides + notes "csv": extract_csv, # pandas + describe() "json": extract_json, # Struktur-Analyse bis depth 3 "text": extract_text, # raw + Markdown-Section-Split "image": extract_image, # PIL metadata ... } # Kritischer Bug-Fix: Batch-ID-Generierung # generate_id() liest normalerweise von Disk. # Bei Splits (10 Nodes in einer Schleife) wäre # die ID immer identisch → darum: in-memory Index node_id = generate_id(node_type, ws, index)
Warum braucht generate_id() einen optionalen in-memory Index?
Weil bei Batch-Inserts der Disk-Index noch nicht aktualisiert ist und sonst alle IDs gleich wären
Weil der Index zu groß für den Arbeitsspeicher sein könnte
Weil es threadsicher sein muss
04
Agentic Search — 3 Stages Without a Vector DB

Keine Embeddings, kein FAISS, kein Cosine Similarity. Stattdessen: gewichtete Tag/Title-Matches, dann Fulltext mit Phrase-Bonus, dann Relations-Anreicherung.

# Stufe 1: Quick Search (cm_search.py) score = 0.0 tag_matches = query_words & tags # × 3.0 Gewicht title_matches = query_words & title_words # × 2.0 Gewicht substring_matches ... # × 1.0 / 0.5 score *= (1 + relevance_boost * 0.2) # critical → +30% # Stufe 2: Deep Search — nur wenn Quick < limit for qw in query_words: count = content_lower.count(qw) score += min(count, 5) * 0.5 # Diminishing returns # Stufe 3: Relations (mit --with-relations) rels = get_node_relations(ws, result_id) # Zeigt: wovon hängt der Treffer ab, was behebt er, etc.
Dive deeper Search algorithm line by line → Weighting logic, stopword filter, snippet extraction, BFS path finding
05
Knowledge Graph — Adjacency List in JSON

Der Graph ist eine einfache Kanten-Liste in relations.json. 22 typisierte Relationstypen, jeder mit automatischer Umkehrung. BFS für Pfadfindung, N-Hop-Traversal für Kontextabruf.

# relations.json — einfache Edge-Liste { "edges": [ {"from": "arch-001", "to": "dec-001", "relation": "depends_on", "note": "API braucht DB"}, ... ] } # BFS-Pfadfindung in cm_relate.py def find_path(ws, from_id, to_id, max_depth=6): adj = {} # Adjazenzliste aus edges bauen for edge in edges: adj[edge["from"]].append((edge["to"], edge["relation"])) # Auch reverse traversieren! adj[edge["to"]].append((edge["from"], f"~{rel}")) # Standard-BFS mit visited-Set queue = deque([(from_id, [(from_id, None)])])
06
Export Pipeline — 6 Formats, 1 Validator

cm_export.py ist das größte Script. Es validiert automatisch und erzeugt einen Quality Score (0–100) basierend auf 8 Checks. Die Embedding-Chunks sind Markdown-bereinigt und starten mit natürlichsprachlichem Zeitbezug.

validate()
→
MD
+
JSON
+
JSON-LD
+
Mermaid
+
Chunks (JSONL)
# Chunk-Bereinigung — kein Markdown, keine Emojis clean = re.sub(r'#{1,6}\s+', '', body) # Headers clean = re.sub(r'\*\*(.+?)\*\*', r'\1', clean) # Bold clean = re.sub(r'\[\[(.+?)\]\]', r'\1', clean) # Wikilinks clean = re.sub(r'[🏗️🧩⚖️💡⚙️...]', '', clean) # Emojis # Zeitkontext als natürliche Sprache voranstellen if temporal.get("source_date"): time_prefix = f"As of {temporal['source_date']}: " chunk_text = f"{time_prefix}{title}. {clean}"
Warum werden Emojis aus den Embedding-Chunks entfernt?
Weil Emojis zu viele Tokens verbrauchen
Weil Embedding-Modelle sauberen Text ohne Formatierungs-Artefakte brauchen
Weil JSON keine Emojis unterstützt
07
The Big Picture — and What is Missing

Context Memory ist ein SkillEine Markdown-Datei (SKILL.md) die Claude Anweisungen gibt, plus unterstützende Scripts. Claude liest die SKILL.md und führt die Scripts im Hintergrund aus., kein eigenständiges Tool. Claude liest die SKILL.md, versteht den Workflow und ruft die Scripts über bash auf. Der Nutzer sieht nur natürliche Sprache.

Was gut gelöst ist: Dateisystem-Portabilität, Duplikat-Erkennung, in-memory Batch-IDs, saubere Chunk-Bereinigung, 22 Relationstypen mit BFS.

Was man erweitern könnte: Semantic Search via Embeddings (braucht API-Key), Real-time Collaboration (braucht Server), Automatisches Kuratieren bei jedem Gespräch (braucht Hook-Integration), Graph-Visualisierung als interaktives HTML.

# Gesamtarchitektur User ──„Context Memory: ..."──→ Claude │ liest SKILL.md │ ruft scripts/*.py via bash │ ┌──────────┼──────────┐ ↓ ↓ ↓ cm_add cm_search cm_export │ │ │ └──────────┼──────────┘ ↓ ~/.context-memory/projekt/ tree.json + index.json + nodes/ + relations.json