Technik der Schichten
Das Schichtenmodell erklärt, warum Apparat in Stufen aufgebaut ist. Diese Seite nennt zu jeder Schicht die konkreten Bausteine und sagt in einem Satz, was durch sie entsteht. Wer Apparat nur benutzt, braucht das nicht zu wissen — wer verstehen will, was unter der Haube läuft, findet hier den Querschnitt von unten nach oben.
Schichten 0 bis 3 sind heute fertig. Schicht 4 ist in Teilen umgesetzt — die dort genannten Technologien sind die aktuell präferierten Festlegungen.
Schicht 0 — Rohdaten und Kommandos
Schicht 0 ist selbst gestaffelt: ein universeller Konventionskern, der nur Editor und Festplatte verlangt, eine PHP-Anwendung obendrauf für die ergonomische Bedienung, und optional ein Container für den portablen Betrieb. Die ersten Bausteine sind kein Apparat, sondern Vereinbarungen, die jedes Werkzeug einhalten kann.
Konventionen — nichts außer Editor und Festplatte nötig
- Markdown (CommonMark) — Notiz-Inhalte als reine Textdatei.
- YAML-Frontmatter — Strukturierte Metadaten am Dateianfang (Typ, Datum, Sprache, freie Felder).
- Dateinamen-Schema
YYYY-MM-DD--slug--nanoid.md— sortierbar, kollisionsfrei, Datum und Titel auf einen Blick lesbar. - NanoID — Acht Zeichen, einmal vergeben, nie geändert. Gibt jeder Notiz eine stabile Identität unabhängig vom Dateinamen.
- Verzeichniskonventionen —
content/für Notizen,assets/für Binärdateien,.apparat/für Index- und Konfigurationsdateien des Vaults. - Standard-Markdown-Links mit relativen Pfaden —
[Label](./datei.md), in jedem Markdown-Editor öffnenbar. - Vault-Manifest
.apparat/manifest.json— JSON-Datei mit der kanonischen URL des Vaults. - Mount-Registry
~/.config/apparat/mounts.yaml— YAML-Datei, die Vault-URLs auf lokale Pfade abbildet.
Bis hierher gibt es kein Programm. Ein Vault, der diese Konventionen einhält, ist mit vim, cat, git oder dem Dateimanager vollständig bedienbar — auch ohne dass Apparat je installiert war.
Apparat als Programm — PHP-Anwendung über den Konventionen
- PHP 8.5 — Sprache, in der Apparat geschrieben ist.
- Laravel 13 — Anwendungs-Framework. Liefert Service-Container, Konfiguration, Test-Pipeline.
- Artisan — Laravels Kommando-Runner. Stellt
vault:*,entity:*undasset:*als ausführbare CLI-Kommandos bereit. apparat-Fassade — Shell-Wrapper, derapparat vault initinphp artisan vault:initübersetzt.
Ab hier braucht es eine PHP-Laufzeit. Die Anwendung pflegt die Konventionen oben — sie definiert sie nicht.
Container — optionaler Betrieb
- Docker-Compose — Containerisierter Betrieb mit Bind-Mount der Vault-Verzeichnisse. Im Standard liegt jeder Mandant (jede Domain) unter
tenants/<name>/, und pro Mandant läuft ein eigener Web-Container (web-<tenant>, lokalweb-local); ein geteilter Hintergrund-Container erledigt Dateibeobachtung und Embedding-Arbeit für alle. Kommandos, die Container-Dienste brauchen (Ollama für Schicht 2,inotifyfür den File-Watcher in Schicht 1), werden vomapparat-Kommando automatisch — alsapparat-Nutzer — in den Web-Container des jeweiligen Mandanten delegiert; den Mandanten wählt--tenant=<name>bzw.APPARAT_TENANT, bei genau einem Mandanten automatisch. Der Nutzer tippt einheitlichapparat …, egal ob das Kommando host-local oder container-seitig läuft. Details und Opt-out (--no-docker) im Kapitel Das apparat-Kommando.
Was dadurch entsteht: Ein vollständig benutzbares Notizsystem. Anlegen, Lesen, Verknüpfen, Umbenennen, Löschen, Suchen — alles funktioniert allein mit den Dateien und der CLI.
Schicht 1 — Parser und Graph
Liest die Markdown-Dateien systematisch aus und legt einen schnellen Lesezugriff darüber. Der Index ist jederzeit verlustfrei aus den Dateien rekonstruierbar.
- CommonMark-Parser (
league/commonmark) — Zerlegt den Notiz-Inhalt in einen syntaktischen Baum; die mitgelieferte FrontMatter-Extension liest den YAML-Kopf im selben Durchlauf. Beziehungen aus dem Frontmatter (works_at: …,references: …) werden zu Kanten. - Symfony YAML (
symfony/yaml) — YAML-Parser für alles außerhalb des Markdown-Wegs: Mount-Registry, Frontmatter-Editor, Validierung. - Aktive CommonMark-Erweiterungen — Tables, TaskLists, Strikethrough, Autolinks, Attributes, Footnotes. Festgeschrieben, weil eine spätere Änderung die Graph-Ableitung rückwirkend verschöbe.
- SQLite als Graph-Datenbank (
<vault>/.apparat/graph.sqlite) — Ein Property-Graph: Knoten für Entitäten und Assets, gerichtete Kanten für Beziehungen, freie Frontmatter-Felder als JSON-Spalte. Eine Datei pro Vault. - Volltext-Index (SQLite FTS5) — Eingebauter Volltextsuchindex für Titel und Felder.
- WAL + Performance-Pragmas — Write-Ahead-Logging, 256 MB Page-Cache, 512 MB Memory-Mapped I/O. Lesende und schreibende Operationen blockieren sich nicht.
- Repository-Schicht — Alle Graph-Zugriffe laufen über eine treiberagnostische Schnittstelle.
- Write-Ahead-Journal (
.apparat/journal/) — Protokolliert Multi-Datei-Operationen (Rename, Soft-Delete) atomar. Bricht ein Lauf ab, holt der nächste CLI-Start die Operation idempotent nach. - Soft-Delete (
.apparat/trash/) — Gelöschte Entitäten landen vault-intern im Papierkorb und sind übervault trash restorezurückholbar. - inotify-File-Watcher (Container-only) —
graph watchlauscht auf Dateisystem-Ereignisse und aktualisiert den Index inkrementell. Außerhalb des Containers läuft Apparat ohne Watcher; der nächste CLI-Aufruf zieht den Stand nach.
Was dadurch entsteht: Schnelle Listen, Filter, Nachbarschaftsabfragen und Konsistenzprüfungen — entity list, entity show, vault validate, Erkennung kaputter Links und verwaister Assets. Der Graph ist gleichzeitig die Datenquelle, aus der die höheren Schichten ihren Kontext ziehen.
Schicht 2 — Semantische Suche
Macht Notizen über ihren Inhalt auffindbar, nicht nur über Titel und Tags.
- Embedding-Modell
jina/jina-embeddings-v2-base-de(768 Dimensionen, deutsch- und englischfähig) — übersetzt Text in einen hochdimensionalen Vektor; semantisch ähnliche Texte bekommen ähnliche Vektoren. Modell und Endpunkt sind überAPPARAT_EMBEDDING_MODEL/APPARAT_EMBEDDING_ENDPOINTkonfigurierbar. - Embedding-Service via Ollama oder OpenAI-kompatibel — eigener Dienst, der die Embedding-Berechnung über HTTP übernimmt. Default ist der Compose-managed Ollama-Container im selben Netz (Profil
llm-localin.env.defaults); alternativ spricht Apparat über den TreiberAPPARAT_EMBEDDING_DRIVER=openaijeden OpenAI-kompatiblen Endpunkt im eigenen LAN/VPN an (mit optionalem Bearer-Token, ohne Code-Change). Jedes Embedding-Modell hält dabei seinen eigenen Vektor-Index, sodass das Hin- und Herschalten keinen Neuaufbau erzwingt. Das Kommandoapparat llm:modestellt Embedding und Chat in einem Schritt zwischen lokal und einem Remote-Profil um. Kein Fremd-Cloud-Aufruf, keine Telemetrie. - Vektor-Datenbank: SQLite mit
sqlite-vec-Extension (<vault>/.apparat/vectors.sqlite) — eine Datei pro Vault, im Container mit der vorkompiliertenvec.so-Erweiterung geladen. Ähnlichkeitssuche pervec_distance_cosinein Millisekunden. - Chunking-Strategie für Markdown — kurze Bodies (≤ 512 Tokens) ergeben einen Title-Chunk; längere Bodies werden zusätzlich entlang der H2-Grenzen gespalten und als
kind='section'-Chunks indiziert. Frontmatter-Beziehungen einer Entity bekommen einen eigenenkind='relations'-Chunk als englische Rahmen-Phrase mit lokalisierten Werten (Relations of "…":, sortiert nach Beziehungstyp und Target-Titel), freie Frontmatter-Werte (residence,email,birthdate, …) einenkind='frontmatter'-Chunk im selben Format (Properties of "…":). Damit werden sowohl Beziehungsfragen als auch strukturelle Fragen direkt im Vektor-Raum auffindbar, ohne dass der Body sie wörtlich enthalten muss. Jeder der fünf Chunk-Typen deckt genau eine Datenquelle ab; Idempotenz greift pro Chunk-Typ — eine Edit am Body löst kein Re-Embedding der Frontmatter-Chunks aus, und umgekehrt. - Inkrementelles Re-Embedding — bei Änderungen wird nur das neu berechnet, was sich geändert hat.
BodyHashals Idempotenz-Schlüssel; der Watcher und der Embedding-Worker arbeiten Backlog-getrieben über die Laravel-Queue. - Embedding-Worker (
queue:work --queue=embeddings) — eigener Supervisor-Prozess im Container, persistente SQLite-Job-Queue, sodass Embeddings auch über Container-Neustarts hinweg robust verarbeitet werden. - FTS5-Volltextsuche (
nodes_ftsaus dem Schicht-1-Graph) — separat von der Vektor-Suche, ergänzt diese um exakte Token- und Diakritika-tolerante Suche;entity searchohne Flags nutzt FTS5, mit--semanticdie Vektoren, mit--hybriddie RRF-Fusion beider Pfade. - Hybrid-Fusion (RRF — Reciprocal Rank Fusion) — kombiniert FTS5- und Vektor-Treffer zu einer einheitlichen Rangliste; default-Modus für
entity search --hybridundentity similar.
Was dadurch entsteht: Drei Suchmodi (entity search default-FTS, --semantic, --hybrid) plus k-NN-Ähnlichkeit (entity similar) plus Wartungskommandos (vector ping/stats/drift/rebuild/cleanup). Die Datengrundlage, aus der Schicht 3 für gezielte Antworten schöpft, ist damit gelegt.
Schicht 3 — KI-Dialog
Drei eigenständige KI-Säulen, die unabhängig voneinander entwickelt und betrieben werden. Jede darf einzeln ausfallen, ohne die anderen oder die unteren Schichten zu beeinträchtigen.
Säule 3a — NER-Schreibassistenz
- spaCy als separater Python-Service im Compose-Stack —
de_core_news_md(~46 MB), CPU, einstellige bis niedrige zweistellige Millisekunden pro Entity. Englisch-Modell on-demand. - HTTP-Kontrakt:
POST /nernimmt einen Text, gibt erkannte Personen/Orte/Organisationen mit Spans zurück. Der Service kennt den Vault nicht. - Mapping-Logik im PHP-Backend: erkannte Spans werden gegen Schicht-1-Graph (FTS5-Title-Match) und Schicht-2-Vektor (Embedding-Nähe) gematcht und als bestätigungsbedürftige Vorschläge ausgegeben.
- Vorschläge sind ephemere Hinweise — nicht im Markdown persistiert. Annahme triggert Schicht-0-Kommandos (
entity link,entity new); die KI selbst schreibt nichts.
Säule 3b — Konsolidierung
- Hybride Distanz aus Damerau-Levenshtein (lexikalisch) und Embedding-Distanz (semantisch) — schlägt ähnliche bestehende Beziehungsnamen, Entitätstypen und Tags vor, wenn neue eingeführt werden.
- Schwellwert-konfiguriert, kein Auto-Merge. Der Nutzer entscheidet, ob
arbeitet_beiinworks_ataufgehen soll. - Verstärkt das in Schicht 0 bereits angelegte Type-Picker-Pattern um eine semantische Sortierung.
Säule 3c — RAG-Chat
- Sprachmodell via Ollama oder OpenAI-kompatibel — das LLM läuft im lokalen Container oder auf einem dedizierten Inferenz-Host im LAN/VPN. Der Treiber
APPARAT_LLM_DRIVERwählt zwischenollama(lokal) undopenai(jeder OpenAI-kompatible Endpunkt, optionaler Bearer-Token); konfiguriert überAPPARAT_LLM_ENDPOINT(Default: gleicher Wert wieAPPARAT_EMBEDDING_ENDPOINT), umschalten ohne Code-Change bzw. in einem Schritt überapparat llm:mode. - Prism PHP als Provider-Abstraktion — Octane-sicher, schmal, Provider-Vielfalt als Migrationsreserve.
- Multi-Turn-Konversationen mit Tool-Calling von Anfang an. Sieben read-only Tools:
find_entities(Hybrid-Suche),similar_entities(k-NN),entity_relations(Graph-Nachbarn mitrelation_filter),read_entity(mit Section-Selektion und sowohl ausgehenden als auch eingehenden Beziehungen im Output),graph_path(kürzester Pfad zwischen zwei Entitäten — beantwortet „wie hängen X und Y zusammen"),find_incomplete(strukturelle Lücken — „welche Personen haben keinworks_at") undlist_entities_by_type(struktureller Bestandsüberblick — „welche Personen kennt der Vault?"). Bewusste Reduktion gegenüber generischen List-/Fetch-Tools, nach Anthropic-Empfehlung. - Triple-Output-Format für Beziehungen — das LLM sieht
Maria Beispiel --[works_at]--> Tollwerk GmbHstatt einer flachen Knotenliste. Dasproperties-Feld pro Triple trägt die nutzerdefinierten Zusatzfelder der Beziehung (since: 2019,role: lead); Apparat-gepflegte Komfort-Felder (label,path) bleiben außen vor. Eine Frage wie „wer arbeitet seit 2019 bei Tollwerk" bekommt damit die strukturelle Information bis ins Modell durchgereicht. - Quellenangaben — Jede Antwort verweist auf die NanoIDs der Entitäten, die in sie eingeflossen sind.
- Streaming-Antworten — Wort-für-Wort, statt erst am Ende komplett.
- Token-Budget-Management — Hard-Cap pro Konversation, Truncation der ältesten Turns wenn überschritten.
- Konversations-Persistenz vault-lokal in
<vault>/.apparat/conversations.sqlite— abgeleitet, ephemer per Default. Wer einen ganzen Dialog behalten will, exportiert ihn alstype: conversation-Entity (Kommandoapparat chat export); wer nur die fachliche Substanz einer einzelnen Antwort als eigenständige Wissens-Notiz festhalten will, nutztapparat chat synthesize— das Modell formuliert die Antwort kontextfrei um und schreibt eine Notiz mitreferences:zu den zitierten Quellen. Beide Wege münden in normale Vault-Entitäten, die danach wie alle anderen Teil des Graphen und Embedding-Zyklus werden. - Read-Only-Verbot — kein Tool darf den Vault verändern. Schreibvorschläge des LLM landen als Antwort-Text, den der Nutzer in der UI bestätigt.
Was dadurch entsteht: Drei abgrenzbare KI-Hilfen — eine schnell und nicht-störend beim Schreiben, eine die das Vokabular sauber hält, eine die echte Recherche im eigenen Wissen erlaubt. Alle drei abschaltbar ohne Verlust der darunter liegenden Schichten.
Schicht 4 — Web-Oberfläche
Macht Apparat für Nutzer zugänglich, die die CLI nicht bedienen wollen. Bleibt optional.
- Laravel Octane + FrankenPHP — Langlaufender PHP-Worker statt Request-pro-Prozess.
- Server-gerendertes HTML (Blade) — Seiten werden vollständig auf dem Server gebaut.
- Vanilla JavaScript — Interaktivität als progressive Anreicherung.
- Markdown-Editor — Textarea mit Live-Vorschau und Frontmatter-Editor; Autocomplete für Typen, Tags und Beziehungsziele aus dem Graphen.
- Graph-Visualisierung — Interaktive Darstellung der Beziehungen rund um eine Entität (ein, zwei, drei Sprünge).
- Chat-Integration — Schicht 3 direkt aus dem Editor erreichbar; die Antwort kennt den Kontext der gerade offenen Notiz.
- WCAG 2.2 AA — Tastaturbedienbarkeit, Screenreader-Tauglichkeit, semantisches HTML als Querschnittsanforderung.
- PWA, responsive, offline-fähig — Installierbar als App, läuft auf Desktop, Tablet und Smartphone.
Was dadurch entsteht: Apparat in der Form, in der es sich für die meisten Nutzer „anfühlt".
Wie die Schichten zusammenwirken
Jede höhere Schicht liest aus den darunter liegenden, nie umgekehrt:
- Schicht 1 verändert die Markdown-Dateien nicht; sie liest sie und schreibt nur ihren eigenen Index. Fällt der Index weg, baut
graph rebuildihn aus den Dateien neu auf. - Schicht 2 entnimmt Schicht 1, was eingebettet werden soll.
- Schicht 3 fragt Schicht 2 und Schicht 1 nach Kontext, bevor sie das Sprachmodell befragt.
- Schicht 4 zeigt nur an, was die Schichten unter ihr bereitstellen.
Diese Einbahn-Richtung — unten trägt oben, oben kann jederzeit ausfallen — ist das, was Progressive Enhancement im Apparat-Sinne konkret bedeutet.