Startseite / Artikel

Hören Sie auf, Ihren Programmier-Assistenten dazu zu zwingen, den Codebase erneut durchzulesen: Das LLM-Wiki-Muster

Karpathys Idee einer LLM-Wiki sowie LangChains openwiki: Rohdaten, eine agentenbasierte Wiki-Struktur und ein AGENTS.md-Verweis, zusätzlich Update-Aufgaben, Bewertung und Nachverfolgung.

Verlag: REACTAPP.TOP12 Min. Lesezeit2695 Wörter

Quellen: Stop Making Your AI Agent Re-Read Your Entire Codebase Every Session

Die meisten Programmier-Agenten haben eine nervige Angewohnheit: Jede Sitzung beginnt von vorne – sie öffnen die Dateien erneut, entdecken erneut, welches Modul von welchem abhängt, und lernen erneut, wofür jeder Teil der Codebasis dient. Man zahlt mit Tokens – also mit Geld – für Wissen, das der Agent noch vor fünf Minuten besaß, aber verlor, sobald das Kontextfenster geschlossen wurde.

Andrej Karpathy beschrieb dieses Problem und skizzierte eine Lösung, die er das LLM-Wiki-Muster nannte. LangChain hat eine Open-Source-Implementierung unter dem Namen openwiki veröffentlicht. Im Folgenden werden das Muster sowie die Implementierung nebeneinander betrachtet: Was die Idee ist, warum sie wichtig ist und wie der Code hinter einer Demo-App funktioniert. Das Ergebnis ist ein mentales Modell, das man auf jede Codebasis anwenden kann, die ein Agent verstehen muss.

Agenten mit Amnesie

Zunächst der Ausfallmodus in präzisen Begriffen. Die übliche Antwort auf die Frage „Wie versteht ein Agent eine Codebasis?“ ist RAG. Die Quelldateien werden eingebettet, in eine Vektordatenbank gespeichert und zum Zeitpunkt der Abfrage relevante Teile abgerufen.

RAG eignet sich gut zum Beantworten von Fragen. Für Programmieragenten reicht es jedoch in drei Aspekten nicht aus.

Das Verständnis wird jedes Mal neu berechnet. Angenommen, der Agent benötigt den Zusammenhang zwischen agent.py und memory.py. Er zieht die entsprechenden Teile heran, das Modell kommt erneut zu einer Antwort, und das Ergebnis wird wieder verworfen.

Die Abrufung hängt von den Embeddings ab. Wenn der Codeverfasser ein anderes Vokabular verwendet hat als die Abfrage des Agents, werden die richtigen Teile nie gefunden. Embeddings sind annähernd, während die Architektur exakt ist.

Es wird geruchlos unbrauchbar. Der Vektor-Speicher hat keine Ahnung, dass memory.py letzten Dienstag überarbeitet wurde, und liefert weiterhin alte Datenabschnitte mit voller Zuversicht aus.

Karpathys Idee war einfach: Das Modell soll selbst die Dokumentation in einem für Modelle lesbaren Format erstellen und diese automatisch auf dem neuesten Stand halten. Das bedeutet Dokumentation für Agenten, nicht für Menschen.

Drei Ebenen

Jeder Bestandteil von openwiki gehört zu einer der drei Ebenen in Karpathys Konzept.

┌─────────────────────────────────────────────────┐
│  Layer 3: The Schema                            │
│  AGENTS.md / CLAUDE.md — "the wiki exists here" │
├─────────────────────────────────────────────────┤
│  Layer 2: The Wiki                              │
│  openwiki/*.md — LLM-generated structured docs  │
├─────────────────────────────────────────────────┤
│  Layer 1: Raw Sources                           │
│  Your actual codebase — immutable to the wiki   │
└─────────────────────────────────────────────────┘

Jede Schicht liegt auf der darunterliegenden Schicht.

Schicht 1: die Rohquellen

Die Rohquellen sind einfach Ihre Codebasis. Aus Sicht der Wiki sind sie unveränderlich: OpenWiki liest sie und ändert sie niemals.

Das Demo-Projekt speichert seine Quellen in einem Verzeichnis namens sample_agent/ – ein realistischer LangChain ReAct-Agent, den die meisten KI-Entwickler erkennen werden.

# sample_agent/agent.py
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from .memory import build_memory
from .prompts import build_prompt
from .tools import TOOLS
def build_agent(
    model: str = "openai/gpt-4o-mini",
    base_url: str = "https://openrouter.ai/api/v1",
    api_key: str | None = None,
    temperature: float = 0.0,
) -> AgentExecutor:
    llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=temperature)
    prompt = build_prompt()
    memory = build_memory()
    agent = create_react_agent(llm=llm, tools=TOOLS, prompt=prompt)
    return AgentExecutor(
        agent=agent,
        tools=TOOLS,
        memory=memory,
        verbose=True,
        handle_parsing_errors=True,
        max_iterations=10,
    )

Vier Komponenten sind miteinander verbunden: ein Modellklient (ChatOpenAI), ein Prompt-Muster, ein Gesprächsmerkmal sowie die Tools. Es handelt sich um klassisches ReAct – genau die Art von Code, für die OpenWiki konzipiert ist. Die Tools werden absichtlich klein gehalten: eine simulierte Suchfunktion und ein sichere Taschenrechner:

# sample_agent/tools.py
@tool
def calculator(expression: str) -> str:
    """Evaluate a safe mathematical expression and return the result."""
    allowed_names: dict = {}
    try:
        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return str(result)
    except Exception as exc:
        return f"Error evaluating '{expression}': {exc}"

Betrachten Sie {"__builtins__": {}}. Es entfernt alle Python-Builtin-Funktionen aus dem eval-Scope, sodass ein Benutzer nicht etwas wie __import__('os').system('rm -rf /') übergeben und ausführen kann. Ein kleiner Detail mit ernsthaften Sicherheitsfolgen.

Was für das Muster wichtig ist, ist, dass OpenWiki jede dieser Dateien liest, herausfindet, wie sie miteinander verbunden sind, und einmal eine Wiki-Seite erstellt, die alles erklärt – und nicht jedes Mal, wenn ein Agent gestartet wird.

Layer 2: die Wiki-Seite selbst

Hier liegt die zentrale Idee. Handgeschriebene Dokumente veralten, und RAG an Rohcode berechnet bei jeder Abfrage alles neu, weshalb das Muster das Modell dazu bringt, strukturierten Markdown im Format zu erzeugen, das für die Art und Weise geeignet ist, wie Modelle Kontext aufnehmen.

Wie OpenWiki dies erzeugt

In der Streamlit-App der Demo führt ein „Wiki erstellen“-Button Folgendes aus:

# app.py
def run_openwiki(
    args: list[str],
    provider: str,
    api_key: str,
    model: str,
    langsmith: str,
    cf_account_id: str = "",
) -> tuple[str, int]:
    import sys
    env = _build_openwiki_env(provider, api_key, model, langsmith, cf_account_id)
    result = subprocess.run(
        ["openwiki", *args],
        cwd=str(SAMPLE_AGENT_DIR),
        env=env,
        capture_output=True,
        text=True,
        timeout=300,
        shell=sys.platform == "win32",  # npm .cmd files need shell=True on Windows
    )
    return result.stdout + result.stderr, result.returncode

Zwei Details sind wichtig.

Zunächst ist cwd=SAMPLE_AGENT_DIR absichtlich gewählt. Die openwiki CLI durchsucht den Ordner, aus dem sie ausgeführt wird. Durch Festlegung des Arbeitsverzeichnisses auf sample_agent/ wird das Wiki auf diese Codebasis beschränkt und nicht auf das gesamte Projekt. Wenn man diesen Parameter weglässt, durchsucht die CLI den Projektroot, wodurch ein Wiki entsteht, das Tests, CI-Konfigurationen sowie node_modules umfasst.

Zweitens shell=True unter Windows. Globale npm-Pakete werden dort als .cmd-Wrapper-Skripte installiert. Wenn man subprocess.run(["openwiki", ...]) aufruft, wird nach einem Binärdatei namens openwiki gesucht, diese wird nicht gefunden, wodurch FileNotFoundError: [WinError 2] ausgelöst wird. Durch das Übergeben von shell=sys.platform == "win32" wird der Aufruf über cmd.exe geleitet, welches .cmd-Dateien ordnungsgemäß verarbeitet. Das ist beim ersten Mal verwirrend, wird aber danach klar.

Was „agent-native“-Formatierung bedeutet

Menschliche Dokumentationen sind so verfasst, dass man sie von oben nach unten liest. Man öffnet README.md, beginnt oben und erarbeitet sich im Laufe des Lesens den Kontext. Agenten lesen jedoch nicht auf diese Weise. Sie scannen Überschriften nach relevanten Abschnitten, ziehen Fakten heraus und gehen weiter.

Agent-native Dokumentationen sind entsprechend diesem Verhalten gestaltet:

  • Dichte Überschriften auf jeder Ebene, sodass die Struktur ohne Lesen von Prosa überflogen werden kann.
  • Explizite Querverweise wie See: patterns.md#memory-strategy, denen ein Agent mechanisch folgt und nicht aus Intuition.
  • Ein-Paragrafen-Zusammenfassungen pro Komponente: ausreichend, um zu entscheiden, ob weitergelesen werden soll, nicht als vollständige Darstellung gedacht.
  • Keine Einräumungen. Überspringen Sie den Einstieg „In diesem Dokument werden wir untersuchen“ sowie jegliche abschließende Zusammenfassung.

Ein generiertes openwiki/architecture.md für den Beispiel-Agenten könnte so aussehen:

# Architecture — sample_agent
## Overview
ReAct agent built on LangChain AgentExecutor. Entry point: agent.py.
Four components: LLM (ChatOpenAI via OpenRouter), tools, memory, prompt.
## Components
- agent.py - AgentExecutor wiring. See: components.md#agent
- tools.py - search (simulated) + calculator (sandboxed eval). See: components.md#tools
- memory.py - ConversationBufferWindowMemory, k=5 turns
- prompts.py - ReAct ChatPromptTemplate with agent_scratchpad placeholder
- evaluator.py - LLM-as-judge chain. See: patterns.md#evaluation
## Data Flow
question → AgentExecutor.invoke() → ReAct loop (max 10 iter)
→ tool calls → final answer → evaluator.evaluate() (optional)

Stellen Sie das neben ein typisches menschliches README mit drei Abschnitten zur Einleitung, bevor konkrete Inhalte erscheinen. Agenten benötigen diese Einleitung nicht – sie brauchen die Fakten.

Ebene 3: das Schema, das den Kreis schließt

Das Erstellen einer Wiki ist nur die halbe Arbeit. Die andere Hälfte besteht darin sicherzustellen, dass der Programmieragent weiß, dass die Wiki existiert.

Dafür sind AGENTS.md und CLAUDE.md zuständig – die Anweisungsdateien, die Claude Code, Cursor, GitHub Copilot sowie die meisten modernen Programmieragenten zu Beginn einer Sitzung lesen. OpenWiki fügt eine kurze Referenz in die jeweilige Datei ein, die Ihr Projekt verwendet. Zuvor:

# sample_agent/AGENTS.md (before OpenWiki)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
- tools.py - available tools (search, calculator)
- memory.py - conversation memory strategy
- prompts.py - system prompt and ReAct template
- evaluator.py - LLM-as-judge evaluation chain

Und nach dem Ausführen von openwiki --init:

# sample_agent/AGENTS.md (after OpenWiki injection)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
...
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
Key files:
- `openwiki/architecture.md` - overall structure and file responsibilities
- `openwiki/patterns.md` - coding patterns and conventions
- `openwiki/components.md` - detailed component documentation
The wiki is auto-updated daily. Always prefer wiki context over re-reading source files.

Die Einbettung ist absichtlich kurz. Wenn die gesamte Wiki-Struktur in die Anweisungsdatei aufgenommen würde, würde dies bei großen Codebasen das Kontextfenster zu Beginn jeder Sitzung überlasten. Ein Pointer reicht aus. Der Agent liest ihn, öffnet openwiki/architecture.md nur dann, wenn der Kontext der Codebasis benötigt wird, und lernt so, was erforderlich ist, ohne auch nur eine Quelldatei anzufassen.

Die vierte Registerkarte der Demo zeigt den Zustand vor und nach der Einbettung und simuliert diese, damit der Mechanismus sichtbar wird:

# app.py — Tab 4
agents_md_content = AGENTS_MD.read_text(encoding="utf-8")
openwiki_injection = """
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
...
"""
col_before, col_after = st.columns(2)
with col_before:
    st.code(agents_md_content, language="markdown")
with col_after:
    st.code(agents_md_content + openwiki_injection, language="markdown")

Wahl des Modellanbieters

OpenWiki kümmert sich nicht darum, welches LLM dahintersteht. Es verwendet LangChains ChatOpenAI, der OPENAI_BASE_URL für einen benutzerdefinierten Endpunkt abruft, sodass jeder OpenAI-kompatible Anbieter funktioniert. Die Demo unterstützt zwei davon:

# app.py
def _build_openwiki_env(
    provider: str,
    api_key: str,
    model: str,
    langsmith: str,
    cf_account_id: str = "",
) -> dict:
    env = {**os.environ, "OPENWIKI_MODEL_ID": model}
if provider == "Cloudflare Workers AI" and cf_account_id:
        cf_base = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/ai/v1"
        env["OPENAI_API_KEY"] = api_key
        env["OPENAI_BASE_URL"] = cf_base      # LangChain reads this automatically
    else:
        env["OPENROUTER_API_KEY"] = api_key   # openwiki's native key name
    return env

OpenRouter ist die einfache Option. Ein einziger Schlüssel öffnet die meisten gängigen Modelle über einen einzigen Endpunkt, und die Einstellung OPENWIKI_MODEL_ID=openai/gpt-4o-mini vervollständigt die Einrichtung.

Cloudflare Workers AI stellt Modelle direkt vom Edge bereit. Das GLM-Modell von ZhipuAI (@cf/zai-org/glm-5.2) läuft auf Cloudflare’s eigener Infrastruktur, wodurch die Latenz sinkt, wenn man in die Produktion geht und nur einen Cloud-Anbieter verwenden möchte. Da Cloudflare einen mit OpenAI kompatiblen Endpunkt unter /ai/v1 anbietet, ist kein eigener Client erforderlich. Weisen Sie OPENAI_BASE_URL darauf hin – die bereits vorhandene LangChain-Integration erledigt den Rest.

Die Wiki aktuell halten

Jedes Dokumentationssystem versagt letztendlich auf dieselbe Weise: Die Nutzer vergessen, es bei Codeänderungen zu aktualisieren.

Karpathys Argument war, dass es sich hier nicht um ein Disziplinproblem, sondern um ein Wirtschaftsproblem handelt. Die Wartungskosten steigen mit der Größe des Codebases, während die Person, die diese Arbeit erledigt, keine unmittelbare Belohnung erhält. Irgendwann weichen die Dokumente so stark von der Realität ab, dass niemand ihnen mehr vertraut, und der Kreislauf beginnt von vorn.

Die Lösung in OpenWiki ist openwiki --update. Anstatt alles erneut zu scannen, fragt es git log nach den Commits, die nach dem letzten Ausführungsvorgang hinzugefügt wurden, verwendet git diff, um nur die geänderten Dateien zu lesen, und schreibt nur die Seiten um, die von diesen Änderungen betroffen sind. Eine mit dem Projekt verbundene GitHub Action führt dies zu einem festgelegten Zeitplan aus:

# .github/workflows/openwiki-update.yml
name: OpenWiki Update
on:
  schedule:
    - cron: "0 8 * * *"   # 08:00 UTC daily
  workflow_dispatch:
jobs:
  update-wiki:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # full git history so openwiki can read diffs
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g openwiki
      - name: Update wiki
        working-directory: sample_agent     # scope to our codebase
        run: openwiki --update --print
        env:
          OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
          OPENWIKI_MODEL_ID: openai/gpt-4o-mini
      - uses: peter-evans/create-pull-request@v7
        with:
          commit-message: "docs: update openwiki pages"
          title: "OpenWiki: daily documentation update"
          branch: openwiki/auto-update

Achten Sie auf working-directory: sample_agent – dies ist dieselbe Einstellung wie bei der Aufrufung des Python subprocess. Wo auch immer Sie dies einsetzen, führen Sie --update vom Wurzelverzeichnis der Codebasis aus durch und nicht vom Projektverzeichnis.

Das Eröffnen eines Pull Requests ist die richtige Vorgehensweise. Ein automatisches Commit in main würde es ermöglichen, dass Dokumente ohne Überprüfung geändert werden, während ein PR Ihnen einen Diff zur Prüfung sowie die Möglichkeit gibt, fehlerhafte Modellausgaben abzulehnen. Die fünfte Registerkarte der Demo simuliert den lokalen Update-Vorgang:

# app.py — Tab 5
if st.button("🔄 Run openwiki --update", type="primary"):
    with st.spinner("Checking for changes and updating wiki…"):
        output, returncode = run_openwiki(
            ["--update", "--print"],
            provider,
            api_key if provider == "Cloudflare Workers AI" else openrouter_key,
            model_id,
            langsmith_key,
            cf_account_id,
        )
    st.code(output, language="bash")

Ein LLM-as-Judge-Bewerter

Neben dem Agenten gibt es einen LLM-as-Judge-Bewerter, eine Technik, die eine eigene Betrachtung verdient.

# sample_agent/evaluator.py
def evaluate(
    question: str,
    response: str,
    model: str = "openai/gpt-4o-mini",
    base_url: str = "https://openrouter.ai/api/v1",
    api_key: str | None = None,
) -> dict[str, int | str]:
    llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=0)
    chain = _JUDGE_PROMPT | llm
    result = chain.invoke({"question": question, "response": response})
    try:
        return json.loads(result.content)
    except json.JSONDecodeError as exc:
        raise ValueError(
            f"LLM returned non-JSON response: {result.content!r}"
        ) from exc

temperature=0 ist hier nicht optional. Der Bewertungsalgorithmus muss deterministisch sein, damit dieselbe Frage und Antwort stets die gleiche Punktzahl erhalten. Eine zufällige Bewertung untergräbt den Sinn von Punktzahlen.

json.loads wird in try/except JSONDecodeError eingebettet, weil Modelle manchmal ihren JSON-Text in Markdown-Code-Frames platzieren, einen einleitenden Satz hinzufügen oder bei langen Antworten abbrechen. Die explizite Ausnahme mit result.content!r im Ergebnis zeigt genau, was das Modell zurückgegeben hat, was das Debuggen erleichtert.

Der Prompt für den Bewertungsalgorithmus bewertet Genauigkeit, Nützlichkeit und Klarheit getrennt voneinander, da diese Eigenschaften unabhängig voneinander versagen können. Eine Antwort kann zwar richtig sein, aber die eigentliche Frage nicht beantworten, oder den richtigen Weg weisen, aber den Leser verwirren. Durch die getrennte Bewertung erhält man konkrete Ergebnisse, auf die man reagieren kann.

Ins Innere blicken mit LangSmith

Durch die Einstellung von LANGSMITH_API_KEY wird jeder Schritt des openwiki-Agenten nachverfolgt: welche Dateien er liest, die Anfrage, die er sendet, was das Modell zurückgibt und wie lange jede Aufruf dauert.

# app.py
def _build_openwiki_env(...) -> dict:
    ...
    if langsmith:
        env["LANGSMITH_API_KEY"] = langsmith
        env["LANGCHAIN_TRACING_V2"] = "true"
        env["LANGCHAIN_PROJECT"] = "openwiki-demo"
    return env

Drei Umgebungsvariablen reichen aus. LangChain erkennt sie automatisch, ohne dass am Code von openwiki oder dem Agenten etwas geändert werden muss. Das ist das richtige Muster für die Überwachbarkeit in LangChain-Projekten: Überwachung über die Umgebung und nicht über den Code, sodass die Überwachung in der Produktion ohne Berührung der Anwendung aktiviert werden kann.

Die Überwachungsdaten zeigen außerdem etwas Nützliches: Der Aufruf --init ist keine einzige Anfrage an das LLM. Es handelt sich um einen Agentenzyklus aus Planungsaufgaben, Datei-Lesevorgängen und Wiki-Schreibvorgängen, und die Überwachungsdaten zeigen die Tokenanzahl sowie die Latenz für jeden Vorgang an. So lässt sich die Frage „Warum dauert --init 90 Sekunden?“ beantworten: Man öffnet die Überwachungsdaten und sieht drei aufeinanderfolgende Modellaufrufe à 30 Sekunden.

Der gesamte Ablauf

Zusammenfassend:

Developer commits code change
    ↓
GitHub Action runs openwiki --update at 08:00 UTC
    ↓
CLI reads git diff → only changed files
    ↓
LLM regenerates affected wiki pages
    ↓
PR opened with updated openwiki/*.md
    ↓
Developer merges PR
    ↓
Agent starts next session → reads AGENTS.md → sees wiki reference
    ↓
Agent fetches openwiki/architecture.md when it needs codebase context
    ↓
Agent makes informed change without reading source files

Die Wiki-Struktur entwickelt sich im Laufe der Zeit weiter. Sechs Monate täglicher Aktualisierungen sorgen dafür, dass openwiki/patterns.md mehr enthält als nur die aktuelle Struktur: Es dokumentiert außerdem, wie sich die Codebasis entwickelt hat, einschließlich der Muster, die bei Refaktorisierungen auftauchten, sowie die Gründe hinter den Konventionen. Ein neuer Agent oder ein frisch eingestellter Mitarbeiter erhält all das sofort.

Die eigentliche Erkenntnis

Im Grunde geht es bei den Mustern nicht um Dokumentation. Es geht darum, wer die Kosten für das Verständnis einer Codebasis trägt.

Im RAG-Modell zahlt jede Agentensitzung: Es müssen Beziehungen neu abgeleitet, Konventionen neu gelernt und bereits gelesene Dateien erneut gelesen werden. Die Kosten steigen mit der Häufigkeit der Sitzungen.

Im Wiki-Modell werden die Kosten einmal pro Codeänderung durch einen planmäßig ausgeführten Job bezahlt, während niemand wartet – und zwar für einen Bruchteil eines Cent pro Seite. Das Ergebnis gewinnt dabei ständig an Wert.

Modelle ermüden nie dabei, Wikis auf dem neuesten Stand zu halten. Ein Fristtermin rückt die Dokumentation nicht nach hinten, und sie hinterlassen niemals einen Eintrag „TODO: später dokumentieren“ und vergessen ihn. Genau dieses Ungleichgewicht ist der Kernpunkt.

Um es auszuprobieren, installieren Sie Node.js 18 oder neuer für die openwiki CLI und holen Sie sich eine API-Schlüssel von OpenRouter oder Cloudflare Workers AI.