Perspektive: 👤 Anwender 🔧 Entwickler
Skill-Architektur · 14 Scripts · Python

Unter der Haube von 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
Architektur — Dateien statt Datenbank

Kein SQLite, kein Redis, keine Vektor-DB. Context Memory speichert alles als JSON und Markdown im Dateisystem. Das macht es portabel, inspizierbar und trivial zu debuggen.

~/.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
Tiefer eintauchenDateisystem-Architektur im Detail →Workspace-Isolation, Index vs. Nodes, History
02
Die Akteure — 14 Scripts, eine Kernbibliothek

Jedes Script hat genau eine Aufgabe. 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
Tiefer eintauchen Jedes Script im Detail — Datenflüsse und Entscheidungen → ID-Generierung, Duplikaterkennung, In-Memory-Index für Batch-Ops
03
Datenfluss — Vom Dokument zum 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
Tiefer eintauchenIngest-Pipeline Schritt für Schritt →Extractor-Dispatch, Splitting-Logik, Batch-ID-Fix
04
Agentic Search — 3 Stufen ohne Vektor-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.
Tiefer eintauchen Such-Algorithmus Zeile für Zeile → Gewichtungslogik, Stoppwort-Filter, Snippet-Extraktion, BFS-Pfadfindung
05
Knowledge Graph — Adjazenzliste 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)])])
Tiefer eintauchenGraph-Implementierung im Code →Adjazenzliste, BFS-Pfadfindung, N-Hop-Traversal
06
Export-Pipeline — 6 Formate, 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
Tiefer eintauchenExport-Pipeline und Validation Engine →8 Quality-Checks, Chunk-Bereinigung, Mermaid-Generierung
07
Das große Ganze — und was fehlt

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