Denkmaschinen · Teil 1 · Souveränität · 6. Juli 2026

Digitale Souveränität: Wem gehört der Agent eigentlich, wenn er läuft?

Teil 1 einer Artikelserie über zwei eigene Agenten-Projekte: wintermute (produktiver persönlicher KI-Agent) und agenticframework (produktiv eingesetztes Meta-Framework für autonome Entwicklungs-Pipelines).

Eine Abgrenzung, bevor es losgeht

Im letzten Teil ging es um die Grundsatzfrage, warum man überhaupt selbst baut. Diesmal geht es um etwas Konkreteres, das in dieser Antwort schon anklang, aber eine eigene Behandlung verdient: Souveränität.

Ich benutze das Wort hier bewusst eng, damit es nicht zum Buzzword verkommt, das für alles und nichts steht:

  • Souveränität heißt für mich: Wer entscheidet, wo Daten liegen, welches Modell antwortet, und was ein Agent überhaupt sehen darf?
  • Sicherheit (Thema des nächsten Teils) heißt: Wie wird verhindert, dass diese Entscheidungen im laufenden Betrieb unterlaufen werden?
  • Kosten (ein späterer Teil) heißt: Was kostet mich diese Entscheidung in Euro pro Monat?

Alle drei hängen zusammen, aber sie sind nicht dasselbe Argument in drei Verpackungen. Dieser Artikel bleibt bei der ersten Frage: der Entscheidungshoheit, nicht ihrer Durchsetzung oder ihrem Preis.

1. Wo lebt der Kontext, physisch?

Wenn ich einen gehosteten Chat-Assistenten nutze, liegt mein Gesprächsverlauf auf Servern, deren Standort, Aufbewahrungsfrist und Zugriffsrechte ich im Zweifel aus den AGB zusammensuchen muss. Bei wintermute ist die Antwort auf "wo liegt das Gedächtnis" ein Docker-Netzwerk-Hostname, den ich selbst kontrolliere:

- QDRANT_URL=${QDRANT_URL:-http://qdrant:6333}
- QDRANT_COLLECTION=${QDRANT_COLLECTION:-wintermute-memory}
- EMBEDDING_PROVIDER=${EMBEDDING_PROVIDER:-ollama}
- EMBEDDING_MODEL=${EMBEDDING_MODEL:-nomic-embed-text}
- EMBEDDING_URL=${EMBEDDING_URL:-http://ollama:11434}

Der Vektorspeicher (Qdrant) und das Embedding-Modell (Ollama, lokal, nomic-embed-text) laufen beide im selben Docker-Netzwerk wie der Agent selbst — nicht bei einem Drittanbieter. Kein API-Call verlässt die Maschine, nur um herauszufinden, ob zwei Sätze semantisch ähnlich sind. Das ist kein Implementierungsdetail, das ist die Antwort auf die Frage "wer hat Zugriff auf mein Gedächtnis": ich, und sonst niemand, weil es technisch nirgendwo anders hinkommt.

Genauso wichtig wie die Frage, wo Daten hinkommen, ist die Frage, wo sie nicht hinkommen. wintermute hat Zugriff auf mein E-Mail-Postfach (IMAP-Tools: lesen, suchen, verschieben). Aber die Ausgaben dieser Tools sind explizit vom semantischen Gedächtnis ausgeschlossen — E-Mail-Inhalte werden nie in den Vektorspeicher indexiert, auch wenn der Agent sie gerade gelesen hat (dokumentiert in Issue #24 §9). Der Agent darf über Mails reden, und diese Antworten landen im Gedächtnis — aber der Mail-Inhalt selbst nicht. Das ist Daten-Kompartimentierung als bewusste Architekturentscheidung: Nicht alles, was der Agent sehen darf, darf er sich auch merken.

2. Wer entscheidet, welches Modell antwortet?

Souveränität heißt nicht zwangsläufig "alles selbst hosten" — bei den eigentlichen Sprachmodellen tue ich das bewusst nicht, dafür sind die guten Modelle zu ressourcenhungrig. Aber die Entscheidung, welcher Anbieter antwortet, bleibt bei mir, nicht bei einer Codebasis, die einen Anbieter fest verdrahtet:

environment:
  - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
  - MOONSHOT_API_KEY=${MOONSHOT_API_KEY:-}
  - DEFAULT_MODEL=${DEFAULT_MODEL:-anthropic/claude-opus-4-7}
  - THINKING_MODEL=${THINKING_MODEL:-moonshot/kimi-k2.6}

Das ist der Unterschied zwischen "ich nutze Anthropic" und "ich nutze, wen ich gerade will, und Anthropic ist heute meine Wahl". Klingt nach Semantik, ist aber genau der Punkt: Wenn ein Anbieter morgen sein Preismodell ändert, seinen API-Zugang für bestimmte Regionen einschränkt oder ein Modell abkündigt, ist meine Reaktion eine geänderte Umgebungsvariable — kein Rewrite, kein Notfall-Meeting.

Dasselbe Prinzip gilt übrigens bis in die Websuche hinein: Der Default-Provider ist DuckDuckGo per HTML-Scrape — kein API-Key, kein Account, keine stille Datenweitergabe an eine Big-Tech-Such-API. Brave Search gibt es als expliziten Opt-in für mehr Qualität. Der Punkt ist nicht, dass DuckDuckGo heilig wäre — der Punkt ist, dass der datensparsame Weg der Default ist und jede Abweichung eine bewusste, konfigurierte Entscheidung.

Bei agenticframework geht dieselbe Logik noch einen Schritt weiter: Dort wird pro Agenten-Rolle festgelegt, ob ein Modell lokal oder in der Cloud läuft. Der Coding-Agent arbeitet mit lokalen Qwen2.5-Coder-Modellen:

c1_coding_agent:
  primary_model: "qwen2.5-coder-7b-instruct"
  fallback_model: "qwen2.5-coder-32b-instruct"
  hosting: "local"
  estimated_cost_per_run_usd: 0.0

hosting: "local" ist hier die eigentliche Souveränitäts-Aussage: Der Quellcode, an dem der Agent arbeitet, verlässt für die Routine-Implementierung nie die eigene Maschine. Reasoning-lastige Aufgaben (Architektur, Security-Review) gehen an Cloud-Modelle — aber das ist eine pro Aufgabentyp getroffene, im Routing-File nachlesbare Entscheidung, kein Pauschal-Abfluss. Dass daraus nebenbei ein erheblicher Kostenvorteil entsteht, ist ein eigenes Thema für einen späteren Teil dieser Serie.

3. Wer entscheidet, was ein Agent überhaupt sehen darf?

Bei agenticframework stellt sich dieselbe Souveränitätsfrage anders: Nicht "wo liegen meine Daten", sondern "wie groß ist der Ausschnitt der Welt, den ein Agent zu Gesicht bekommt". Ein Filesystem-Connector im MCP Gateway bekommt genau einen root_path zugewiesen — und die Durchsetzung, dass er diesen nicht verlässt, ist keine Konvention, sondern eine Funktion:

def _safe_resolve(root: str, relative: str) -> str | None:
    """Resolve a relative path within root, blocking traversal outside root."""
    root_p = Path(root).resolve()
    target = (root_p / relative).resolve()
    if not str(target).startswith(str(root_p)):
        return None
    return str(target)

Der naive Fehler wäre, Pfade einfach mit os.path.join(root, relative) zusammenzukleben und dem Ergebnis zu vertrauen — dann reicht ein relative="../../../../etc/passwd", um den gesamten Host lesbar zu machen. Hier wird stattdessen der Zielpfad vollständig aufgelöst (resolve(), folgt auch ..-Sprüngen) und geprüft, ob er wirklich noch unterhalb des erlaubten Wurzelverzeichnisses liegt. Erst dann wird der Zugriff gewährt.

Der Souveränitäts-Punkt dabei: Ich entscheide bei jedem Connector explizit, welcher Ausschnitt meiner Infrastruktur sichtbar ist — ein Projektordner, keine ganze Festplatte. Und diese Entscheidung ist etwas, das ich bei einem fertigen SaaS-Agenten schlicht nicht treffen kann, weil ich nicht weiß (und nicht beeinflussen kann), was der Agent im Hintergrund an Kontext heranzieht.

4. Das Vertrauensmodell steht im Repo, nicht in einer Werbebroschüre

Der vielleicht unterschätzteste Aspekt von Souveränität: Man kann sein eigenes Misstrauen dokumentieren. In wintermutes docs/SECURITY.md steht das Vertrauensmodell des Gesamtsystems explizit drin — als Hierarchie, wer wem vertraut:

You (operator)
  └── trusts: the host
       └── trusts: the wintermute-updater service (root, single-purpose)
       └── trusts: docker, but NOT what runs inside containers
  └── runs containers:
       ├── mcp-gateway   (unprivileged, uid 1000, no Docker, no SSH key)
       ├── agent         (unprivileged, uid 1000)
       ├── qdrant        (unprivileged image)
       └── ollama        (unprivileged image)

Die zentrale Zeile ist docker, but NOT what runs inside containers: Alles, was im Container läuft — inklusive des Agenten selbst — wird behandelt wie Input, der bösartig sein will. Das ist kein Misstrauen gegen die eigene Software aus Prinzip, sondern die nüchterne Konsequenz daraus, dass ein LLM-Agent fremde Eingaben verarbeitet und man nie ausschließen kann, dass eine davon ihn manipuliert. Welcher SaaS-Anbieter veröffentlicht ein Diagramm, das zeigt, wo genau er seinen eigenen Komponenten nicht traut? Eben. Dieses Dokument existiert nur, weil das System jemandem gehört, der es vor sich selbst rechtfertigen muss.

Der Gegenbeweis: Souveränität ist nicht kostenlos

Auch hier gilt das Prinzip aus Teil 0: ehrlich bleiben heißt, den Preis zu nennen. Selbst gehostete Infrastruktur bedeutet, dass ich für Dinge verantwortlich bin, die ein SaaS-Anbieter mir sonst abnimmt. Ein kleines, aber echtes Beispiel: wintermute überwacht den eigenen RAM-Verbrauch des Memory-Subsystems selbst, mit fest konfigurierten Schwellwerten:

- MEMORY_RAM_WARN_BYTES=${MEMORY_RAM_WARN_BYTES:-268435456}   # 256 MB
- MEMORY_RAM_CRIT_BYTES=${MEMORY_RAM_CRIT_BYTES:-536870912}   # 512 MB

Diese beiden Zeilen existieren, weil auf einem selbst betriebenen Server (bei mir: ein Hetzner CAX11, keine unbegrenzte Cloud-Skalierung im Hintergrund) Speicher ein echtes, endliches Gut ist. Bei einem gehosteten Produkt sieht man diese Zahlen nie — nicht weil das Problem dort nicht existiert, sondern weil jemand anderes es für einen löst. Souveränität bedeutet, dieses "jemand anderes" durch "ich, mit Monitoring" zu ersetzen.

Und manchmal ist der Preis noch banaler. Eine echte Geschichte aus dem Lessons-Log (Lesson #16): Beim Einrichten wurde der Name der Qdrant-Collection per Copy-Paste verstümmelt — aus wintermute-memory wurde wintermute-mem ory, mit Leerzeichen. Das Fiese daran: Nichts ist abgestürzt. Der Health-Check meldete "alles gesund", weil die falsch benannte Collection ja existierte. Nur die Erinnerungssuche lieferte konsequent null Treffer. Eine halbe Stunde Debugging für ein Leerzeichen. Die Konsequenz war eine neue Regel, die sich inzwischen durchs ganze Projekt zieht: identifier-förmige Konfigurationswerte (Collection-Namen, Modell-IDs, Tokens) werden an der Systemgrenze gegen ein striktes Muster validiert — Whitespace oder Steuerzeichen führen sofort zum harten Abbruch statt zu stillem Fehlverhalten. Bei einem SaaS-Produkt hätte mir diese Klasse von Problem der Anbieter abgenommen. Dafür wüsste ich auch nicht, was er sonst noch alles für mich entscheidet.

Das ist die Rechnung, die man aufmachen muss, bevor man sich für den eigenen Weg entscheidet.

Fazit und Ausblick

Souveränität ist in beiden Projekten keine Ideologie, sondern eine Reihe konkreter, im Code sichtbarer Entscheidungen: wo Gedächtnis liegt (und was bewusst nicht hineindarf), wer das Sprachmodell stellt (und welche Daten dafür die Maschine verlassen), wie groß der Ausschnitt ist, den ein Agent zu sehen bekommt — und wem das System an welcher Stelle ausdrücklich nicht vertraut. Jede dieser Entscheidungen hätte man auch anders treffen können — die Souveränität liegt darin, dass man es überhaupt entscheidet, statt es implizit hinzunehmen.

Im nächsten Teil geht es um die Kehrseite: Wenn man diese Kontrolle hat, wie verhindert man technisch, dass sie im laufenden Betrieb wieder verloren geht — durch einen Agenten, der mehr darf, als er sollte, oder durch eine Prompt-Injection, die die eigenen Regeln aushebelt?


Quellen: wintermute/docker-compose.yml (Qdrant-/Ollama-Konfiguration, Modell-Routing-Env-Vars, Memory-RAM-Schwellwerte); wintermute/docs/SECURITY.md (Trust Model); wintermute/docs/IMAP-TOOLS.md + Issue #24 §9 (IMAP-Ausschluss aus dem Vektorspeicher); wintermute/mcp-gateway/src/gateway/tools/web.py (DuckDuckGo-Default); wintermute/LESSONS.md (Lesson #16); agenticframework/model_routing.yml (lokales C1-Coding-Routing); agenticframework/mcp-gateway/src/gateway/tools/filesystem.py (_safe_resolve, Connector-Sandboxing); agenticframework/mcp-gateway/src/gateway/tools/connectors.py.