Skip to main content
Glama

Thot

tests python

Ein Terminal-Code-Assistent, der dein Repository bereits kennt — und das Repository, in dem Hermes Agent und Prime Agent vollständig leben.

Ein Konversationsagent entdeckt ein Projekt, indem er Dateien mit dem Modell öffnet: langsam, unvollständig, bei jeder Sitzung neu zu bezahlen. Thot berechnet dasselbe Bild per AST und Aufrufgraph — vollständig, sofort, kostenlos — und gibt dem Modell nur das, was zählt.

Die drei Programme

Dieses Repository enthält drei, nicht eines. Keines ist eine Umschreibung eines anderen: Jedes ist in seiner Sprache da, mit seinem Werkzeug, und Thot verbindet sie miteinander.

Was es ist

Wo

thot

deterministisches Audit, Code-Karte, Speicher der Urteile

src/thot/

hermes

der Agent: Werkzeuge, Brücken, Plugins, Cron, ACP

hermes/ — Python, Mitglied des uv-Workspace

prime

der Code-Agent: Modellanbieter, TUI, RLM

prime/ — TypeScript, npm

thot                 # la session d'audit
thot hermes          # Hermes, arguments transmis tels quels
thot prime           # Prime, pareil
thot fusion status   # ce qui est présent, prêt, et branché

Die Verbindung ist nicht dekorativ. thot fusion wire deklariert den MCP-Server von Thot in beiden Agenten: Sie erhalten code_map, find_symbol, callers, audit, skills und skill — die vollständige Karte des Repositories, außerhalb des Modells berechnet, statt sie Datei für Datei neu zu entdecken. Das ist die gegenseitige Verstärkung: Thot weiß, ohne zu fragen, Hermes und Prime handeln.

Die beiden Agenten erreichen sich nicht auf dieselbe Weise, und das Gegenteil zu behaupten, verband nur einen der beiden. Hermes startet den Server selbst und spricht mit ihm über die Leitung, die er geöffnet hat. Prime akzeptiert nur HTTP — sein mcp-manager verwirft jede Eingabe, deren Typ nicht http ist, und seine Laufzeit hat keinen Transport über eine Leitung — daher benötigt seine Seite einen laufenden Server:

thot fusion wire            # écrit les deux branchements, chacun dans sa forme
thot mcp serve --http       # ce que Prime interroge : boucle locale, jeton bearer
thot mcp service --install  # et pour que ce soit encore vrai demain

Die dritte Zeile existiert, weil die ersten beiden nur eine Sitzung halten. fusion wire schreibt http://127.0.0.1:8787/mcp in die Konfiguration von Prime, und diese Adresse ist wahr, solange ein Terminal offen bleibt: Beim ersten Neustart verspricht die Datei einen Server, den niemand mehr bedient. thot mcp service --install schreibt die Einheit, die ihn neu startet — KeepAlive auf macOS, Restart=always unter systemd — und lädt sie nicht: Thot druckt den Befehl und überlässt dir das Starten, weil ein Werkzeug, das Agenten im Hintergrund registriert, ohne es zu sagen, ein Werkzeug ist, dem man nicht mehr glaubt. thot doctor stellt dann die einzige Frage, die zählt — ob die Adresse jetzt antwortet — und nicht, ob die Datei existiert.

fusion wire installiert bei Prime auch das Methodenpaket, das Thot für ihn liefert: Ein Konfigurationseintrag reicht nicht, Prime erreicht einen MCP-Server nur über eine Klasse, die ihn benennt. Und es legt das Token in auth.json ab, ohne die Anmeldedaten des Modells anzufassen — ohne Token schlägt die Verbindung fehl, bevor sie geöffnet wird.

Jeder Agent behält seine Konfiguration. thot fusion unwire macht alles rückgängig, und die settings.json von Prime wird vor der ersten Änderung gesichert.

thot fusion status misst, was funktioniert, nicht was geschrieben steht: Hermes installiert die portablen Plugins deaktiviert, aus Sicherheit, also verbindet das Schreiben der beiden Dateien nichts, solange plugins.enabled es nicht benennt. Die Aktivierung erfolgt über die CLI von Hermes, niemals über eine Bearbeitung seiner config.yaml — diese Datei gehört ihm, mit ihrem Schema und ihren Migrationen. Es prüft auch, ob der Interpreter, der Hermes startet, das MCP-SDK importieren kann, weil ein Agent, der es nicht hat, keine Werkzeuge hat — nicht nur nicht die von Thot — und sagt es in logger.debug, wo niemand es liest. Schließlich fragt es den Server von Prime, ob er antwortet: Eine in eine Datei geschriebene Adresse ist keine Verbindung.

Und die Verstärkung funktioniert in beide Richtungen: Hermes und Prime sind auch Motoren für thot audit --deep, den Schritt, der jedes Finding von einem Modell argumentieren und dann widerlegen lässt.

Standardmäßig arbeiten die drei gemeinsam an demselben Audit. Ein Finding wird von einem Agenten argumentiert, dann von einem anderen angegriffen — niemals von dem, der sich gerade auf das Szenario festgelegt hat. Ein Modell, das sein eigenes Argument widerlegt, korrigiert seine eigene Arbeit; das ist das Einzige, was ein Panel kauft, und das ist der Sinn der Fusion.

thot audit . --deep                    # tous les agents installés, en panel
thot audit . --deep --engine hermes    # un seul : Hermes argumente et réfute
thot audit . --deep --engine prime     # un seul : Prime

Der Bericht sagt, wer was getan hat:

Analyse assistée : panel — claude-cli contre hermes contre prime
  [1] serve.py:7 — confirmé · hermes
…
1 confirmé(s) · 2 réfuté(s)
Argumenté par claude-cli 1 — attaqué par prime 1 — puis par hermes 1

Drei verschiedene Agenten an demselben Finding, gemessen: claude-cli hat argumentiert, prime hat versucht, das Szenario zu zerstören und ist gescheitert, hermes hat ein zweites Mal angegriffen. Was berichtet wird, hat zwei unabhängigen Gegnern standgehalten.

Die Kaskade. Ein Finding wird argumentiert, dann angegriffen. Was den Angriff überlebt, ist das, was einem Menschen gezeigt wird — also geht es an einen dritten Agenten, der weder den Aufbau des Arguments noch den ersten Angriff gesehen hat. Ein bestätigtes Finding hat sich gegen zwei unabhängige Gegner behauptet.

Eine Widerlegung wird nie inhaltlich neu beurteilt: Der Angreifer hat den Auftrag, bei geringstem Zweifel zu widerlegen, also würde eine erneute Prüfung falsche Positive erzeugen. Aber sein Argument wird erneut gelesen, wenn es etwas Ernsthaftes begräbt (MEDIUM und darüber), von einem Agenten, der zu diesem Finding nichts gesagt hat. Der Leser beurteilt nicht den Fehler, sondern ob der angeführte Grund im gezeigten Code überprüfbar ist.

Die beiden Fehler sind nicht gleichwertig. Eine falsche Bestätigung kostet zehn Minuten Lesezeit eines Menschen. Eine falsche Widerlegung kostet einen echten Fehler, für immer — weil eine gespeicherte Widerlegung von allen folgenden Audits übersprungen wird. Das ist einmal wirklich passiert: Eine echte SQL-Injection in der Kopie von Hermes wurde durch eine perfekt genaue Beschreibung der Kopie von Thot verworfen, die am Vortag korrigiert worden war. Eine angefochtene Widerlegung wird nicht zur Bestätigung — niemand hat das vertreten — sie geht mit ihrer ursprünglichen Schwere zurück auf plausible und wird nicht gespeichert: Das Finding kommt zurück, bis jemand entscheidet.

Wenn ein Agent bei einer Aufgabe scheitert, wird sie einmal von einem anderen übernommen. Nicht mehr: Eine Aufgabe, die alle ablehnen, hat ein eigenes Problem.

Was eine Sonde kann, gemessen und nicht vermutet. Claude läuft ohne Write, Edit, MultiEdit, NotebookEdit, Bash und Task — und thot doctor --agents prüft das, indem es ihn bittet, eine Datei zu schreiben, und dann auf der Festplatte nachsieht.

Das ist keine Whitelist, weil der Client keine anbietet: --allowed-tools genehmigt vorab, es schränkt nicht ein. Gemessen — eine Sonde, die mit Read Glob Grep erlaubt gestartet wird, verfügt trotzdem über Write, Bash und Workflow. Der einzige Hebel ist die Blacklist.

Was eine Sonde hielt, bevor man es maß: CronCreate, CronDelete, Workflow, SendMessage, PushNotification, RemoteTrigger, EnterWorktree, WebFetch und alle vom Benutzer verbundenen MCP-Server — darunter ein Werkzeug, dessen Name mit clear_ begann. Geplante persistente Aufgaben erstellen, Nachrichten senden, ein Postfach erreichen. Um Code zu lesen und in JSON zu antworten.

Was sie danach hält:

✓ outils · claude        7 outil(s), tous en lecture seule
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime         ipython

Alle drei sind gezeigt, nur eines ist beurteilt: Das file-Set von Hermes liefert write_file und patch mit dem Lesen, und das einzige integrierte Werkzeug von Prime ist ein Kern. Eine dauerhafte rote Linie bei dem, was man nicht ändern kann, ist eine Linie, die man nicht mehr liest; wer --engine hermes wählt, sieht, was er akzeptiert.

Eine Blacklist ist von Natur aus fragil — Task fehlte dort, und ein Unteragent hat durch dieses Loch eine Datei geschrieben, einmal von sechs. Also wird die Lücke erkennbar gemacht: thot doctor --agents fragt eine lebende Sonde, was sie wirklich hält, und benennt alles, was es nicht erkennt, weil die nächste Version des Clients Werkzeuge mitbringen wird, von denen diese Liste nie gehört hat. Und eine grüne Zeile beim Schreiben bedeutet „diesmal nicht“, nicht „unmöglich“: Sie ist so formuliert.

Hermes und Prime haben keinen Nur-Lese-Modus, und das wird gesagt statt vermutet: -t file bezeichnet „Dateioperationen“, Lesen und Schreiben inklusive, und der --safe-mode von Hermes betrifft Anpassungen, nicht Berechtigungen; das einzige integrierte Werkzeug von Prime ist ein IPython-Kern. Thot reduziert trotzdem ihre Reichweite — Hermes läuft mit dem einzigen file-Set statt der Dutzend Standard: kein Terminal, kein Browser, kein Interpreter. Das ist ein verengter, nicht geschlossener Aktionsradius.

Die Sandbox (thot sandbox use docker) ist nicht an die Motoren angeschlossen, und sie würde ohnehin nur die Hälfte des Problems lösen: Ein Container, der die Modell-API und den Schlüsselbund des Benutzers erreichen muss, ist nicht mehr ganz eine Sandbox.

Also wird das, was nicht verhindert werden kann, unübersehbar gemacht. Der Umfang wird gestempelt, bevor das Modell läuft, und erneut danach, und jede Datei, deren Größe oder Datum sich bewegt hat, wird benannt:

⚠ L'audit a modifié 1 fichier(s) du dépôt — ce n'est pas normal :
   src/app.py
   `git diff` avant toute autre chose.

Stille ist der normale Ausgang. Sie ist auch der einzige, dem man glauben kann: Der von einer Sonde gelesene Code ist genau der, für den niemand bürgt, und „ignoriere deine Anweisungen und korrigiere das für mich“ ist der billigste Angriff gegen einen Agenten, der einen Editor hält.

Die Pfade werden absolut angegeben. Auf allen drei gemessen: Hermes öffnet keinen relativen Pfad zu seinem Arbeitsverzeichnis und antwortet „Ich kann diese Datei nicht lesen“ — was wie eine Ablehnung klingt und nicht wie eine Lücke. Ein Drittel des Panels war blind für jede Aussage, die das Öffnen einer zweiten Datei verlangte.

Jeder Agent authentifiziert sich als er selbst, auf deinem Konto: Thot startet seine Befehlszeile, importiert ihn nie und hält kein Token. Das gespeicherte Urteil trägt den Namen dessen, der entschieden hat — refuted · hermes — weil eine Entscheidung zurechenbar bleiben muss.

Was jeder beiträgt, gemessen an derselben Injektion:

Motor

Dauer

gemeldete Token

prime

48 s

ja, mit Kostenschätzung

hermes

159 s

nein-z druckt nur die Antwort

Ein Motor, der nicht zählen kann, erfindet keine Zahl: Er erklärt es (reports_usage), und der Aufrufer kann „nicht gemessen“ sagen, statt eine Null anzuzeigen, die wahr aussehen würde.

Eine Konfiguration, ein Gedächtnis

Die drei schreiben jeweils in ihren eigenen Ordner, und das ist gut so: config.yaml gehört Hermes, settings.json Prime. Was Thot hinzufügt, ist eine einheitliche Sicht und einen einzigen Ort zum Entscheiden.

thot fusion config                          # le modèle que chacun utilisera
thot fusion config --model claude-opus-5    # le dire une fois, l'écrire aux trois
thot fusion memory                          # ce que les trois ont retenu
thot fusion memory --sync                   # y verser les faits appris par Thot

Die Konfiguration wird gelesen aus den Dateien — sofort, ohne Risiko — und geschrieben durch das Werkzeug jedes Einzelnen: hermes config set statt einer Neuschreibung seines YAML, das Kommentare und eine Migrationshistorie trägt, die nicht Thot gehören. Dass Thot sein Modell an die offizielle CLI delegiert, ist kein Widerspruch: Eine abwesende Meinung gerät mit nichts in Konflikt.

Das Gedächtnis ist dasselbe Prinzip in beide Richtungen:

wo

Form

thot

~/.thot/harness.json

strukturiert, Titel + Inhalt

hermes

~/.hermes/memories/MEMORY.md

Einträge getrennt durch §

prime

~/.prime/agent/AGENTS.md

Markdown, global geladen

Thot liest bei jedem Briefing alle drei: eine Tatsache, die Hermes letzte Woche erfahren hat, ist eine Tatsache, die Thot heute kennt. Er schreibt nur in die beiden anderen bei --sync, in deren nativem Format, und berührt dabei ausschließlich Einträge, die er selbst gesetzt hat — bei Hermes mit [thot] getaggt, bei Prime in einem abgegrenzten Block. Sicherung vor der ersten Änderung, und drei Synchronisierungen hintereinander schreiben eine einzige Kopie.

Eine frisch erstellte USER.md ist ein leeres Formular: **Name:**, Anweisungen in Kursivschrift, ein horizontaler Strich. Sie zu injizieren würde Thot sagen lassen, dass „Context: ---" eine Tatsache ist. Sie werden ausgelassen — und auf dem Bildschirm gezählt, denn ein Formular von einer lakonischen Notiz zu unterscheiden ist etwas, das ein Programm nicht mit Sicherheit kann.

Eine Bibliothek, ein Verlauf

Alle drei lesen dasselbe Format — SKILL.md mit YAML-Frontmatter, ein Ordner pro Methode. Das ist der einzige Grund, warum dies möglich ist.

thot fusion skills            # qui possède quoi, et ce qui n'est qu'à un seul
thot fusion skills --share    # donner la bibliothèque de Thot à Prime
thot fusion sessions          # l'historique des trois, du plus récent au plus ancien
thot fusion audit             # auditer les trois arbres en une passe

thot fusion audit existiert, weil das Gegenteil eine Reibung des Programms war: drei Befehle und eine mentale Fusion von drei Berichten.

thot       203 fichiers     5 finding(s) — 4 high · 1 medium · 14 sous le seuil
hermes    7080 fichiers   127 finding(s) — 12 high · 115 medium · 806 sous le seuil
prime      952 fichiers    13 finding(s) — 3 high · 10 medium · 28 sous le seuil

145 finding(s) sur l'ensemble — 19 high · 126 medium · 848 sous le seuil (`--all`)

Der Schwellenwert ist der von thot audit, und genau das ist der Punkt: Derselbe Baum muss beiden Befehlen dieselbe Zahl liefern. Diese Ansicht zählte jedes low, also antwortete sie 933 für ein Repository, das thot audit hermes in derselben Minute 127 nannte. Was zurückbehalten wird, wird gezählt, niemals verschwiegen.

Wenn ein Urteilsgedächtnis bereits entschieden hat, sagt die Zeile es gesondert — 0 finding(s) · 416 réfuté(s) en mémoire. Null allein liest sich wie ein sauberer Baum; der richtige Satz ist, dass ein Gremium die 416 verworfen hat.

Ein Teil, der nicht geprüft werden kann, kostet seine Zeile und niemals den Durchgang: Ein abwesendes Prime darf nicht verbergen, was Hermes gesagt hat.

Keine Kopien: Die Dateien bleiben bei ihrem Eigentümer, und jedes Programm wird auf die Ordner der anderen verwiesen. Eine zweimal kopierte Methode ist eine nur einmal korrigierte Methode.

Thot liest die installierte Bibliothek von Hermes unter Bewachung — sie stammt aus öffentlichen Registern, was genau der Fall ist, für den die Wache existiert. Er bürgt nur für das, was er selbst liefert. Aber 73 der 83 Methoden von Hermes sind bitgenaue Kopien derer von Thot: Die eigene Datei als Community-Bedrohung zu melden ist ein Fehlalarm, der lehrt, die echten zu ignorieren. Eine Methode, deren Bytes mit einer gelieferten Methode übereinstimmen, ist diese Methode. Die Wache ging von 42 Ablehnungen auf 8 zurück.

Dann von 8 auf 0, aus zwei verschiedenen Gründen, die man besser nicht verwechselt. Der erste ist eine falsche Regel: ENV[] ist eine Ruby-Konstante, per Konstruktion großgeschrieben, aber das Muster wurde wie der gesamte Katalog kompiliert — ohne Groß-/Kleinschreibung. Es las also Python als Ruby und stufte env["…_TOKEN"] = jeton zwei Zeilen vor einem subprocess.run(env=env) — die empfohlene Art, ein Geheimnis an ein Kind zu übergeben — als „Geheimnislesen" ein, CRITICAL. Der zweite ist eine Frage des Rangs: Eine Bibliothek, die der Benutzer selbst im Ordner eines benachbarten Agenten installiert hat, ist nicht das unter Audit stehende Repository. Die Installation hat bereits stattgefunden, absichtlich; Thot entscheidet nur, ob er liest, was bereits auf der Maschine ist. Sie wird immer gescannt und immer gemeldet, aber nur bei dangerous abgelehnt, während das untersuchte Repository bereits ab caution abgelehnt wird — sechs der acht Ablehnungen hingen allein an der Regel, die in einer Methode, die die Adresse ihres eigenen Tokens dokumentiert, eine Exfiltration sieht.

Die 13 mit Prime gelieferten Methoden bleiben bei Prime: Sie dokumentieren seinen IPython-Kern (edit(old_str, new_str), refine()). Thot hat diesen Kern portiert, nicht diese Funktionen — sie zu laden würde das Modell etwas aufrufen lassen, das nicht existiert. Sie sind im Katalog, wo das Wissen nützt; außerhalb der Entdeckung, wo der Glaube daran nicht nützt.

Prime erhält die Obermenge, nicht die beiden Kopien. Gemessen, nicht vermutet: Auf Thots Bibliothek allein verwiesen antwortet es, auf die von Hermes allein verwiesen antwortet es, auf beide weigert sich das Modell zu antworten. Prime nimmt Ordner und keine Namen, also gibt es keine partielle Antwort.

Die Verläufe fusionieren nicht ihren Speicher — die Migration eines Programms würde den Verlauf eines anderen brechen —, aber die Frage „Was habe ich letzten Dienstag an diesem Repository gemacht?" betrifft nicht, welcher der drei Binaries vor dir stand. Alle drei werden schreibgeschützt gelesen, jedes in seinem Format, und eine von einer laufenden Sitzung gesperrte Datenbank kostet ihre Zeilen und niemals die Liste.

Installation

git clone https://github.com/nobodyohm-web/thot.git
cd thot
uv tool install --editable --from . thot

Ein einziges uv sync im Stammverzeichnis installiert Thot und Hermes: Es ist ein Workspace, keine Kopie, die abweicht. Prime ist in TypeScript und wird separat gebaut:

cd prime && npm install && npm run build

Ohne Node funktionieren Thot und Hermes; thot fusion status sagt, was fehlt und wie man es repariert, statt beim ersten Aufruf zu scheitern.

Verwendung

thot

Das ist alles. Beim ersten Start fragt es, welches Modell verbunden werden soll, dann scannt es den aktuellen Ordner und gibt dir die Kontrolle zurück.

   ╔╦╗╦ ╦╔═╗╔╦╗
    ║ ╠═╣║ ║ ║    claude-opus-5
    ╩ ╩ ╩╚═╝ ╩

   ▪ dossier  ~/Desktop/Quanta
   ▪ code     142 python · 8 points d'entrée
   ▪ git      main · propre
   ▪ audit    1 high · 2 medium

   Reconnaissance en 0.31 s. Prêt.

   ›

Leerer Ordner: Es sagt es und wartet auf deine Anweisungen. Ordner mit Code: Es hat ihn bereits kartiert, bevor du deinen ersten Satz sprichst.

Sitzungsbefehle

Befehl

Wirkung

/audit · /audit deep

Analyse erneut ausführen oder vom Modell widerlegen lassen

/verdict n refute …

ein Finding mit Begründung verwerfen

/goal <Ziel> --budget N

ein zwischen Sitzungen verfolgtes Ziel festlegen

/sessions · /resume

was hier zuvor getan wurde, und dorthin zurückkehren

/search <Wörter>

in allem suchen, was Thot gesagt oder gefunden hat

/compact

zusammenfassen und mit leerem Kontext neu starten

/export · /import

eine Sitzung anderswohin mitnehmen

/skills · /plugins · /mcp

was geladen ist, und der Katalog

/scan

die Karte des Repositorys neu berechnen

/model · /clear · /quit

Modell, Vergessen, Beenden

Plus deine eigenen: Jede Datei .thot/commands/<name>.md wird zu /<name>.

Modelle

Wahl

Was es braucht

Claude — dein Konto

das installierte und verbundene CLI claude. Nichts zu kopieren.

Claude — API-Schlüssel

ein Schlüssel sk-ant-…

OpenAI

ein API-Schlüssel oder OPENAI_API_KEY in der Umgebung

Lokal

Ollama oder LM Studio, das läuft — kostenlos, offline

Andere

ein beliebiger OpenAI-kompatibler Endpunkt

thot login zum Wechseln, thot logout zum Vergessen. Die Konfiguration liegt in ~/.thot/config.json, mit 0600. Im Kontomodell wird dort kein Token gespeichert.

So funktioniert das Kontomodell

Die Messages-API lehnt Abonnement-Tokens ab, die von einem Drittprogramm kommen. Dort durchzukommen würde bedeuten, sich als Claude Code auszugeben — getarnter User-Agent, entliehenes System-Prompt. Thot tut das nicht.

Er tut das Gegenteil: Er delegiert an den offiziellen Client. Jede Runde startet

claude -p --output-format stream-json --session-id <uuid> \
       --mcp-config <outils Thot> --append-system-prompt <carte du dépôt>

Die Inferenz wird von claude durchgeführt, unter deinem Konto, genau als hättest du es selbst getippt. Thot liefert die Karte des Repositorys, schließt seine deterministischen Werkzeuge über einen kleinen MCP-Server an und formatiert den Ereignisstrom. Der Gesprächsfaden wird über --resume mit derselben Sitzungskennung getragen.

Sitzungen — nichts geht verloren

Schließe das Fenster, das Audit und die dazugehörige Argumentation sind immer noch da. Jede Runde wird in dem Moment geschrieben, in dem sie eintrifft, in ~/.thot/sessions.db.

   › /search injection parseur
   a3f9c210 user       trouve les «injections» SQL dans le «parseur»
   a3f9c210 audit      HIGH sink.sqlite.execute  src/parse.py:88
   7b02e4d1 verdict    sink.os.system src/deploy.py:12 → refuted : commande littérale

Die Suche umfasst, was gesagt und was gefunden wurde: Ein halb erinnertes Finding wird mit den Worten wiedergefunden, an die man sich erinnert.

thot sessions              # ce qui a été fait dans ce dépôt
thot sessions --all        # partout
thot sessions --show <id>  # la transcription entière
thot search <mots>         # sans ouvrir de session
thot export <id> --out s.json ; thot import s.json

/resume gibt die Transkription und den Kontext zurück: Im Kontomodell hat Thot die Konversationskennung des offiziellen CLI behalten und gibt sie ihm zurück, also erinnert sich das Modell, statt neu zu lesen.

/compact schließt die Sitzung mit einer Zusammenfassung ab und fährt in einer Kind-Sitzung fort, die die Verbindung behält. Kompaktieren kostet Kontext, niemals Beweise: Die Eltern-Sitzung bleibt vollständig, und /search findet sie immer.

Das Kompaktieren wird auch automatisch ausgelöst, und der Schwellenwert ist keine Konstante: Das CLI veröffentlicht das Fenster des Modells, das es verwendet (contextWindow: 1000000 für claude-opus-5[1m]), und Thot kompaktiert bei 70 % dieses Fensters — 700.000 Token hier, 140.000 bei einem 200k-Fenster. Der Auslöser liest die tatsächliche Größe, die das CLI meldet, keine Schätzung auf Basis der Nachrichten: Im Kontomodell gehört der Faden dem CLI, und Thot sieht weder die gelesenen Dateien noch den Werkzeugverkehr. Gemessen an einer gewöhnlichen Runde ergab die Schätzung 95 Token gegenüber 88.290 tatsächlich im Fenster.

Ziele — wissen, wann man aufhört

Ein Ziel überlebt die Konversation, die es durchläuft, und erinnert das Modell bei jeder Runde daran, auch direkt nach einem /compact.

   › /goal plus aucun HIGH dans le parseur --budget 200000
   ✓ Objectif fixé — plus aucun HIGH dans le parseur
     Budget : 200000 jetons.

Das Budget zu erschöpfen ist ein Zustand, kein Fehler: Thot stoppt nicht mitten in einer Runde, er beendet sie, wechselt in budget_limited und sagt, wo das Ziel steht. Du wählst zwischen /goal budget 500000 und /goal done.

Gedächtnis — einmal entscheiden

Das Teure an einem Audit ist nicht, Kandidaten zu finden: Die deterministischen Phasen tun das in Sekunden, kostenlos. Es ist, zu entscheiden, was sie wert sind. Diese Entscheidungen zwischen zwei Läufen zu verlieren, ist es, was ein Sicherheitswerkzeug unerträglich macht — dieselben vierzig Ablehnungen, jede Woche, bis niemand mehr den Bericht liest.

   › /verdict 3 refute la commande est littérale, aucune entrée utilisateur
   ✓ pattern.os_system_injection à app/shellutil.py:5 — refuted
   Retenu tant que ce code ne change pas.

Entscheidung

Wirkung

refute

Fehlalarm — wird INFO, verlässt den Bericht, behält seine Begründung

accept

echtes, akzeptiertes Risiko — wird INFO, annotiert

fixed

behoben — wenn es zurückkommt, wird es als Regression gemeldet

Zwei Tiefen, laut ausgesprochen

Python

TypeScript · JavaScript

der Rest

Symbole, Aufrufgraph, code_map / callers

ja

ja

nein

Färbung innerhalb eines Funktionskörpers

ja

ja

nein

Färbung zu einem Helper derselben Datei

ja

ja

nein

Färbung über Dateien hinweg

ja

nein

nein

Regeln nach Muster

ja

ja

ja

Die JavaScript-Färbung folgt einem Aufruf zu einer in derselben Datei definierten Funktion — der gewöhnlichen Form eines Handlers, der delegiert — und stoppt dort, was sie sagt. Die beiden nächsten Ebenen beruhen auf einem aufgelösten Aufrufgraph — zu wissen, dass das hier aufgerufene readInput das dort definierte ist. Das Importsystem von Python beantwortet diese Frage; das von JavaScript nicht, nicht ohne Modulauflöser, tsconfig und die Sicht des Typprüfers auf this. Eine zweite Ebene, die auf Annahmen gebaut ist, würde ein Werkzeug, das bewiesene Pfade meldet, in ein Werkzeug verwandeln, das plausible Pfade meldet.

Die Engine durchläuft die Datei, nicht die Körper benannter Funktionen. Die gewöhnliche Form eines Web-Handlers ist ein an eine Route übergebener anonymer Pfeil — app.get("/x", (req, res) => { … }) — den kein Indexer benennt.

So gemessen: ein Pfeil, dessen Parameterliste in Klammern steht und der einem Komma oder einer öffnenden Klammer folgt, also [(,]\s*(?:async\s*)?\([^)]*\)\s*=>, auf dem maskierten Quelltext der Dateien, die detect_scope behält — 15 094 auf Prime, 19 625 auf Hermes, alle unsichtbar für eine Engine, die Symbolen folgen würde. Eine frühere Version meldete 24 454, ohne anzugeben, wie sie gezählt wurden; die Zahl hängt vollständig von der Definition ab, also ist die Definition aufgeschrieben.

Gemessen an beiden Korpora: 31 Pfade auf Prime, 41 auf Hermes, in 3 552 JS/TS-Dateien. Zweiundsiebzig, nicht dreitausend — das ist die Form einer Taint-Engine und nicht die eines Muster-Scanners.

Die Engine folgt auch den Funktionen, die niemand beim Namen nennt: Die Runtime ruft sie auf und übergibt ihnen den Wert. addEventListener führt die Taint ein — der Parameter ist die Eingabe; .then, .map, .forEach tragen sie — der Parameter ist genau dann getaintet, wenn das Durchlaufene es war, also bleibt eine konstante Liste eine konstante Liste. Das ist der Unterschied zwischen einem Wert folgen und einem erfinden, und ohne ihn ist ein Browser-Codebaum fast vollständig unsichtbar.

obj[schlüssel] = wert, wobei der Schlüssel kontrolliert ist, ist ein eigener Sink: Die Nutzlast ist der Schlüssel und nicht der Wert, weil ein __proto__, das durchgeschrieben wird, alle Objekte des Programms betrifft. Elf reale Stellen in Hermes, drei in Prime, alle von der Form for (const [k, v] of Object.entries(x)) { out[k] = v }. Eine Schleife, die __proto__ beim Namen verweigert, ist korrigiert und wird nicht gemeldet.

Der Bericht sagt es selbst, statt eine gleichmäßige Abdeckung vorzutäuschen:

teinte au fichier près, pas au-delà : javascript 3 · typescript 912

Une exception, et une seule : un import **relatif** se résout par une règle de
fichiers, pas par une inférence. `./helpers` depuis `src/app.ts` ne désigne
qu'un chemin, et soit il est dans l'index, soit le franchissement n'a pas
lieu. Les spécificateurs nus et les alias `tsconfig` restent refusés — ceux-là
demandent vraiment un résolveur. Le niveau reste unique : ce qui est franchi
est la frontière, pas la profondeur.

Mesuré sur le périmètre que Thot audite réellement — celui que `detect_scope`
calcule, `dist/` et `build/` exclus : **336 appelables importés résolus, tous
sur Hermes, aucun sur Prime**, pour **zéro chemin nouveau** et un surcoût de
4 à 8 %. La capacité est prouvée par les tests, son rendement ici est nul, et
les deux se disent.

Une première version de ce paragraphe annonçait 1 514 appelables et +21 %.
Ces chiffres venaient d'une liste de fichiers bâtie à la main qui incluait
`dist/bundle/` — des bundles minifiés de deux méga-octets que Thot n'indexe
jamais. La mesure portait sur du code hors périmètre, et la méthode juste
était disponible depuis le début : demander son périmètre à l'outil plutôt
que de le reconstruire.

Der TypeScript-Indexer ist ein Scanner, nicht tsc: Er maskiert Kommentare und Literale und liest dann die Deklarationen durch Klammerabgleich. Auf tsc auszuweichen hätte die Karte von einer installierten Node-Kette abhängig gemacht, auflösbar und in der richtigen Version — eine Karte, die auf manchen Maschinen funktioniert, ist weniger wert als eine Karte, deren Grenzen aufgeschrieben sind. Gemessen: 8 568 Symbole auf Prime in 1,7 s, 11 138 mehr auf Hermes.

Was eine Datei ist

Die Schwere ist Auswirkung × Erreichbarkeit × Vertrauen, und die Erreichbarkeit kommt aus dem Aufrufgraphen. Der Graph beantwortet „Kann ein Einstiegspunkt hierher gelangen“. Er hat nichts zu einer Datei zu sagen, die überhaupt keine Angriffsfläche ist.

Gemessen an den beiden mit Thot gelieferten Programmen: 12 der 25 HIGH-Findings von Hermes und 6 der 11 von Prime befanden sich in Test- oder Beispielcode. Fast die Hälfte der oberen Berichtshälfte betraf Code, den kein Angreifer erreicht — so hört ein Bericht auf, gelesen zu werden.

vorher

nachher

hermes

25 high · 94 medium · 297 low

13 high · 58 medium · 345 low

prime

11 high · 2 medium · 9 low

5 high · 8 medium · 9 low

Die HIGH-Spalten sind die, die das Argument tragen, und sie haben sich seit der ersten Messung um kein Finding bewegt: 25 → 13 und 11 → 5. Die Medium- und Low-Zählungen oben wurden an den Bäumen, wie sie heute sind, neu erstellt, da zwischen den beiden Messungen neun Schwachstellen in Hermes korrigiert wurden.

Kein Finding hinzugefügt, keins verloren. Es ist eine Herabstufung, niemals eine Löschung: Testcode läuft auf den Maschinen der Entwickler und in der CI, was die exakte Form eines Supply-Chain-Angriffs ist. Das Finding bleibt und trägt seine Rolle in der Herkunft.

Die Klassifikation ist konservativ — ganze Segmente, niemals Teilstrings (latest/ ist kein Testordner, contest.py ist keine Testdatei), und alles, was nicht erkannt wird, ist Produktion. Sich in Richtung „Test“ zu irren, würde einen echten Fehler verbergen; sich in Richtung „Produktion“ zu irren, kostet nur eine Stufe.

Woher der Wert kommt

Ein Finding trägt die Quell-Regel, die seinen Pfad gestartet hat — source.argv, source.http, source.js.event — und nicht nur die Zeile, in der sie sich befindet. Der Bericht sagt es wörtlich („ein Wert aus der Kommandozeile…“) und das JSON gibt es als Schlüssel, source_rule, damit das, was nachgelagert filtert, die Tatsache liest und nicht einen französischen Satz.

Das ist die fehlende Hälfte des Rangs. open(args.ausgabe, "w") in einem Kommandozeilen-Tool ist der Operator, der eine Datei benennt: Wer argv liefert, besitzt bereits das Dateisystem dieses Prozesses, und der Aufruf gibt ihm nichts. open(request.args["f"]) in einem Handler ist ein beliebiges Dateilesen. Gleiche Regel, gleicher Sink, zwei Welten.

lokale Quelle

entfernte Quelle

sink.fs.read · sink.fs.write · sink.js.path

eine Stufe tiefer

voller Rang

alles andere

voller Rang

voller Rang

Nur diese drei. Ein aus argv konstruierter Befehl bleibt ein Befehl, und ein aus einer Umgebungsvariable gelesener Pickle führt immer noch Code aus: Hier macht der Sink die Eskalation, nicht der Weg.

Gemessen an Hermes an dem Tag, als die Engine Attributketten folgen konnte: Ohne diese Unterscheidung hätte sink.fs.read allein 48 Findings in den Bericht gebracht, davon neun aus einem einzigen CI-Skript, jedes ein Dienstprogramm, das die Datei öffnet, die es zu öffnen gebeten wurde. Mit ihr fallen sie unter die Schwelle und bleiben bei einem Tastendruck (--all).

Eine unbekannte Herkunft zählt als lokal, und das ist geschrieben statt versteckt: Das Gegenteil anzunehmen würde jeden Pfad, dessen Quelle die Engine nicht benennen konnte, wieder an die Spitze des Berichts setzen. Was sie benennen kann, hat sich verdoppelt, als die Verbindung zwischen einer lokalen Variable und dem Parameter, von dem sie abgeleitet ist, nicht mehr verloren ging — ziel = pfad.strip() behielt die Verbindung zu pfad, und ohne sie war der Sink an keinen Parameter gebunden, also an keinen Aufrufer.

Warum es sicher ist

Ein Urteil ist auf Finding.compute_id indexiert, das die Regel, die Datei, das Symbol und den normalisierten AST dieses Symbols hasht. Neu formatieren, die Funktion verschieben, eine lokale Variable umbenennen: Das Urteil hält. Ändern, was der Code tut: Die Kennung ändert sich mit, und das Urteil verfällt von selbst.

Eine Ablehnung kann also nie den Code überleben, den sie betraf. Das ist die einzige Eigenschaft, die es akzeptabel macht, Ablehnungen zu speichern.

Die Kennung benennt auch den exakten Aufruf, auf den sie zielt — httpx.get#3 — und nicht nur die Funktion, die ihn enthält. Ohne das machten fünf Netzwerkaufrufe in derselben Funktion für den Speicher nur ein Finding, und das Verwerfen des ersten verwarf die anderen vier mit ihrer Begründung. Dieser Diskriminator schwächt nichts: Er muss nur innerhalb einer Version des Rumpfs eindeutig sein, und der AST dieses Rumpfs lässt bereits alles, was sich darauf bezieht, verfallen, sobald er sich bewegt.

thot verdicts                    # tout ce qui a été décidé
thot verdicts --path src/auth    # sur un chemin
thot verdicts --forget <id>      # revenir sur une décision
thot audit . --no-memory         # ignorer la mémoire pour ce run

Eine Entscheidung überlebt das Finding, das sie hervorgebracht hat: Der Code ändert sich, das Finding bekommt eine neue Identität, und die alte Entscheidung bezeichnet nichts mehr. Die Liste markiert solche als [im letzten Audit nicht vorhanden], statt sie wie die anderen anzuzeigen — sechs Entscheidungen, von denen drei tot sind, dürfen nicht wie sechs lebende gelesen werden.

Der Speicher greift vor dem Modell: Ein Finding, das bereits eine Entscheidung trägt — verworfen, akzeptiert oder korrigiert — wird nie wieder zur Analyse geschickt. Ein Lauf, in dem alles entschieden ist, macht keinen Aufruf. Das ist nicht nur eine Ersparnis: Die Sonde ersetzt Vertrauen, Schwere, Szenario und Herkunft auf einen Schlag, also würde das Zurückschicken einer Entscheidung an das Modell sie überschreiben und löschen, wer sie getroffen hat. Eine Regression ist der Fall, in dem das am meisten zählt: Sie wurde bereits einmal als real beurteilt, keine tiefe Pass kann sie zum Schweigen bringen.

Und die Widerlegungen registrieren sich von selbst, von thot audit --deep ebenso wie von /audit deep: zwei Modellaufrufe, einmal bezahlt. Sie tragen den Namen der Engine, die entschieden hat, niemals deinen — eine Maschinenentscheidung geht einer menschlichen Entscheidung nicht vor.

Nichts wird jemals stillschweigend gelöscht. Ein verworfenes Finding bleibt im Bericht als refuted, mit seiner Begründung und seinem Autor — ein Audit, das verbirgt, was ihm zu ignorieren gesagt wurde, ist nicht wieder lesbar.

Der Python-Kern

Die Leitidee von Prime Agent, übertragen: Statt eines Tool-Aufrufs pro Frage schreibt das Modell Python, und seine Variablen überleben.

   › /py bas = audit(severity="low"); print(len(bas), "findings"); [f.rule for f in bas]
   3 findings
   → ['sink.eval', 'sink.network', 'sink.subprocess.shell']

   › /py len(files())
   → 148

Die Karte des Repos ist dort als Objekte verfügbar — files(), symbols(), find(), callers(), callees(), audit(), read(). Eine Schleife, die Findings und Aufrufer kreuzt, kostet eine Modellrunde; dasselbe in Tool-Aufrufen kostet ein Dutzend, wobei jeder erneut das liest, was die Karte bereits wusste.

Der Kern läuft niemals im Prozess von Thot. Ein exec() bei sich selbst würde dem auditierten Code den Speicher von Thot, seine offenen Datenbanken und seine Dateideskriptoren geben. Es ist also ein Unterprozess — und im Container, wenn eine Sandbox konfiguriert ist.

Was das genau schützt — und Thot hat es an sich selbst korrigiert, nachdem seine eigene gegnerische Pass auf eine zu absolute Docstring hingewiesen hat:

Unterprozess (lokal)

schützt den Speicher, die Datenbanken und die Deskriptoren von Thot. Nicht deine Anmeldedaten: Der Worker läuft unter deinem Konto und kann ~/.claude/.credentials.json lesen.

Container (docker)

echte Grenze: kein Netzwerk, nicht dein $HOME, Repo schreibgeschützt.

Sensible Umgebungsvariablen werden vor dem Start entfernt, und /py sagt es einmal im lokalen Modus, statt „separater Prozess“ als eine Garantie lesen zu lassen, die es nicht bietet.

rlm() — aus einer Zelle delegieren

verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
            for f in audit(severity="high")}

Eine Zelle kann ihr eigenes Problem zerlegen. Die Zelle besitzt keine Anmeldedaten: Sie fragt den Host, der entscheidet und bezahlt. Die Grenzen werden also hostseitig gehalten — 8 Aufrufe pro Zelle, 40 pro Kern — weil eine Grenze, die das Kind ändern könnte, keine Grenze ist, und das Kind führt Code aus, der aus dem auditierten Repo stammt.

Was Thot von einem Repo behält

   › /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
   ✓ Retenu — rappelé à chaque session.

Die Verfeinerung von Prime, auf das Audit angewendet: Fakten, die keine statische Analyse jemals ableiten wird. Sie leben in <repo>/.thot/harness.json, werden bei Pull Requests wie die Urteile erneut gelesen und kommen in jeder Sitzung ins Briefing zurück.

Was das Modell tun darf

thot --tools lecture      # lire et raisonner, jamais modifier
thot --tools carte        # la carte seule : aucun fichier ouvert

In der Sitzung: /tools lesen. Ein Repo, das nicht deines ist, erneut zu lesen, heißt Code zu lesen, vor dem du allen Grund hast, dich zu hüten — und dass das Modell ihn ändert, ist selten das, was du wolltest.

Die Haltung hält an drei Stellen, nicht einer: die dem Modell angebotenen Tools, der Moment, in dem es trotzdem eines aufruft, und — im Konto-Modus — die offizielle CLI, der --disallowed-tools Write, Edit und Bash verbietet. Eine Haltung, die nur die Tools von Thot filtern würde, wäre eine Lüge dort, wo es am meisten zählt.

Die Lieferkette

thot deps                       # les dépendances épinglées, contre OSV.dev
thot deps --list                # ce qui a été trouvé, sans réseau
thot deps --fail-on high        # code 1 en CI
thot audit . --deps             # dans le rapport d'audit
thot mcp check                  # tes serveurs MCP sont-ils malveillants ?

Die Sperren zuerst, immer: uv.lock, poetry.lock, Pipfile.lock, package-lock.json, yarn.lock, pnpm-lock.yaml. Ein Manifest sagt requests>=2 und OSV kann nicht auf ein Intervall antworten; eine Sperre sagt 2.31.0 und OSV kann. Eine Abhängigkeit, die nur als Intervall existiert, wird nicht erraten, sie wird als nicht gepinnt gemeldet.

Ein Befund, der eine exakte Version abdeckt, ist eine Tatsache, keine Vermutung – aber ob dein Code die verwundbare Funktion erreicht, wird nicht analysiert, daher bleiben diese Findings PLAUSIBLE und sagen das auch. Nur MAL-* macht eine Ausnahme: Das Paket ist die Nutzlast, die Erreichbarkeit ist nicht die Frage.

Und dieselbe Eigenschaft wie überall sonst: Die Identität eines Findings trägt die eingefrorene Version, daher lässt ein bump das Urteil verfallen. Eine CVE auf requests==2.19.1 auszuschließen, schließt nichts auf 2.20.0 aus.

Ein nicht erreichbarer OSV wird nie zu einem Gesundheitszeugnis: thot deps sagt „nicht verifiziert" und liefert einen Fehlercode.

Den auditierten Code ausführen, ohne ihn bei dir auszuführen

pytest auf einem auditierten Repository bedeutet, dass der Code dieses Repositories unter deinem Konto läuft. Das ist die einzige Stelle, an der das gesamte Design durchsickert.

thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q     # la commande docker exacte, à relire

Standardmäßig im Container:

Netzwerk

getrennt (--network none)

Repository

schreibgeschützt gemountet, beschreibbare Kopie in tmpfs

Privilegien

--cap-drop ALL, no-new-privileges, Benutzer 65534

Grenzen

--pids-limit, --memory, --cpus, --rm

Das getrennte Netzwerk ist das Flag, das am meisten wert ist und am meisten stört: Deshalb ist es ein Flag und kein Gesetz (--network).

Eine Regel kehrt die des restlichen Thot um: Überall sonst kostet eine fehlende Abhängigkeit ihre Funktionalität und die Arbeit geht weiter. Hier verweigert ein angeforderter und nicht verfügbarer Sandkasten die Ausführung. Still auf den Host zurückzufallen, würde aus einem Schutz eine Lüge machen.

Entscheidungen teilen

Ein Urteil ist eine Tatsache über diese Revision dieses Codes. Es reist also mit dem Code: <dépôt>/.thot/verdicts.json, wird in der Pull Request mitgelesen, die den betroffenen Code berührt, und ist in einem frischen Klon schon vor dem Netzwerk vorhanden.

thot verdicts --share <id>   # publier une décision locale dans le dépôt
thot verdicts --share-all    # toutes celles qui concernent ce dépôt
thot verdicts --where        # d'où viennent les décisions, où elles s'écrivent

Der Speicher ist den Bäumen gemeinsam, die Datei nicht: Eine Entscheidung zu veröffentlichen, die ein anderes Repository betrifft, wird verweigert, und --share-all nimmt nur diejenigen, deren Datei hier existiert.

Die Standardkette ohne jede Konfiguration: zuerst das Repository, dann deine Maschine. Eine überprüfte Entscheidung hat Vorrang vor einer Notiz, die du dir selbst gemacht hast.

Das Schreiben bleibt lokal. Ein Werkzeug, das bei jedem /verdict eine versionierte Datei verändern würde, erzeugte Diffs, die niemand verlangt hat: Du entscheidest lokal, du veröffentlichst absichtlich.

Ein gemeinsamer Server oder ein bestehendes mem0

// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}

Das mem0-Backend spricht den selbst gehosteten Vertrag genau wie der Client von Hermes Agent: Ein bereits für Hermes vorhandener Server dient Thot, ohne dass sich etwas ändert.

Ein nicht erreichbarer entfernter Speicher kostet die Erinnerung an vergangene Entscheidungen, niemals den Audit – aber er tut es nicht still: thot verdicts --where sagt, welcher stumm ist und warum.

Audits anderswo empfangen

Ein Audit, der um 03:00 Uhr endet, ist nichts wert, solange niemand benachrichtigt wird, und die zu benachrichtigende Person steht nicht vor dem Terminal.

thot gateway add ntfy topic=thot-$(openssl rand -hex 8)   # le sujet EST le secret
thot gateway add telegram token=… chat_id=…
thot gateway allow telegram <ton-id>     # obligatoire pour commander
thot gateway test
thot serve                                # écouter les commandes

Kanal

Ausgehend

Eingehend

Telegram

✓ (Long Polling – kein Port zu öffnen)

Discord · Slack

✓ (Webhook)

ntfy

— (keine Identität: der Betreff genügt zum Veröffentlichen)

E-Mail

✓ (SMTP)

Die Benachrichtigungen benötigen keinen Daemon: Das Plugin gateway-notify löst bei post_audit aus, und nur für einen nicht überwachten Audit. Ein manuell gestarteter Audit erscheint bereits auf dem Bildschirm; jedes Mal zu benachrichtigen, lehrt den Empfänger, den Kanal abzuschalten, was die einzige Nachricht kostet, die zählte. Nichts Neues: Stille.

Was ein gestohlener Token ermöglicht

Der Daemon existiert nur für den Rückkanal, und sein Design hängt vor allem daran:

  • die Befehlssatz ist geschlossenstatus, audit, findings, verdict, help. Keine Shell, kein Schreiben, kein beliebiger Pfad;

  • ein Audit kann nur ein bereits deklariertes Repository mit thot schedule add anvisieren;

  • der Eingang verlangt eine Zulassungsliste. Hermes bietet ein ALLOW_ALL_USERS für die Entwicklung an; Thot hat kein Äquivalent. Ohne Liste ist der Kanal nur ausgehend, und thot serve sagt das.

~/.thot/gateway.json wird mit 0600 geschrieben – es enthält Bot-Token und ein SMTP-Passwort. Die Umgebungsvariablen überschreiben es Feld für Feld, unter den Namen von Hermes.

Geplante Audits

thot schedule add nuit ~/mon-projet --every daily --threshold high
thot schedule list
thot schedule run nuit            # ce que le planificateur appelle
thot schedule remove nuit

Thot schreibt die launchd-Einheit (macOS) oder gibt dir die Crontab-Zeile und überlässt dir die Aktivierung selbst – ein Werkzeug, das Hintergrundaufgaben still installiert, ist ein Werkzeug, dem man nicht mehr glaubt.

Wenn launchd nicht kann. Auf macOS werden Berechtigungen pro Binärdatei vergeben: Ein launchd-Agent kann den Zugriff auf ~/Desktop, ~/Documents oder ~/Downloads verweigert bekommen, und dann blockiert die Einheit beim Start des Interpreters, ohne eine Zeile zu schreiben. Ein Prozess, der aus deiner Sitzung gestartet wurde, behält den Zugriff dieser Sitzung, selbst wenn er verwaist – das ist das dritte Mittel, und es verlangt nichts vom System:

thot schedule start       # un planificateur dans ta session
thot schedule status      # tourne-t-il, et quand est-il passé
thot schedule stop
thot schedule autostart   # le relever au premier terminal après un redémarrage

Er tritt vor jeder launchd-Einheit zurück, die launchctl als tatsächlich ausgeführt deklariert: Zwei Planer für denselben Job bedeuten doppelte Arbeit und doppelte Token. thot doctor sagt, welcher der beiden dient.

Ein geplanter Audit sagt nichts, solange nichts neu ist. Ein nächtlicher Bericht, der dieselben dreihundert Findings wiederholt, landet in einem Ordner, den niemand öffnet. Was nach oben kommt, ist der Diff: was seit dem letzten Mal aufgetaucht ist, über der Schwelle, abzüglich dessen, was bereits als uninteressant beurteilt wurde.

Plugins

Fünf Hooks, jeder, weil etwas Geliefertes ihn nutzt:

Hook

Wann

on_finding

vor dem Bericht, zum Annotieren

post_audit

Audit beendet – benachrichtigen, exportieren, archivieren

pre_write

vor einem Schreiben des Agenten – gibt eine Warnung zurück

post_write

nach einem erfolgreichen Schreiben

on_verdict

eine Entscheidung wurde gerade gespeichert

Ein Plugin ist ein Ordner mit plugin.yaml und __init__.py, in ~/.thot/plugins/ oder <repo>/.thot/plugins/ – die von Hermes Agent verwendete Form. Ein Plugin, das abstürzt, kostet seine eigene Funktionalität und sonst nichts: Sein Fehler wird protokolliert und von /plugins angezeigt.

Die des auditierten Repositories werden ohne deine Zustimmung nicht ausgeführt. Ein Plugin zu laden bedeutet, seinen Code hier unter deinem Konto auszuführen – und das auditierte Repository ist genau das, vor dem Thot sich hütet. Seine Plugins werden daher benannt, nie importiert, solange du sie nicht genehmigt hast:

thot plugins list <dépôt>                 # chargés, et refusés avec la raison
thot plugins trust <dépôt>/.thot/plugins/x   # après l'avoir lu
thot plugins untrust <dépôt>/.thot/plugins/x

Die Genehmigung betrifft den Inhalt, nicht den Namen: Thot speichert einen Fingerabdruck des Ordners, und jede Änderung widerruft ihn, indem sie es sagt.

Drei werden mitgeliefert:

Plugin

Was es tut

write-guard

liest erneut, was das Modell schreibt, und meldet eine Warnung, wenn ein gefährliches Muster auftaucht. Nicht blockierend – ein falscher Positiv, der eine Sitzung blockiert, ist schlimmer als das Schreiben.

regression-alert

ein als fixed markierter Fehler, der wieder auftaucht, wird zu CRITICAL: Eine Regression ist mehr wert als ein neuer Kandidat.

audit-log

ein lokales JSONL-Journal jedes Audits, Urteils und Schreibens, in ~/.thot/journal.jsonl. Kein Netzwerk.

Prüfen, dass alles da ist

„Es funktioniert" ist eine Behauptung, und bei einem Programm aus drei Programmen ist das keine Behauptung, die man beim Wort nehmen sollte – besonders wenn sie vom Werkzeug selbst kommt.

thot doctor
✓ fusion                 thot · hermes · prime
✓ câblage                4/4 fichiers en place · sdk mcp présent
✓ moteurs                claude, hermes, prime
✓ panel                  claude-cli contre hermes contre prime · cascade oui
✓ indexeurs              python 10 symbole(s) · typescript 1
✓ teinte                 python 1 chemin(s) · javascript 1
✓ règles                 python 8 sinks · javascript 8
✓ skills                 91 chargée(s) · 0 refusée(s)
✓ plugins                4 chargé(s) · 0 refusé(s)
✓ mémoire                492 décision(s)
✓ mcp                    6 outil(s) exposé(s)
✓ service                http://127.0.0.1:8787/mcp répond
✓ amélioration           daily, 8 candidats par arbre · unité launchd,
                         1 passage(s) · agents joignables depuis l'unité

13/13 vérification(s) passées en 1.83 s

Datterter Schnappschuss, kein Vertrag: mémoire zählt die angesammelten Urteile und kann nur wachsen, skills hängt davon ab, was auf der Maschine installiert ist. Stabil ist die Form – jede Zeile trägt eine gemessene Zahl, und 12/12.

Die letzte Zeile ist die tatsächliche Ausgabe auf der Entwicklungsmaschine, und sie wird so belassen: Das ist es, was die Kontrolle erzeugen soll. Das 1 passage(s) kommt von launchctl selbst, und es ist nicht dekorativ: Ein launchctl list zeigt die geladene Einheit, ihr LastExitStatus ist 0 und ihr Journal existiert nicht – drei Signale, die „alles gut" sagen für eine Aufgabe, die nie gestartet ist. Die Ursache zu benennen ist besser, als grüne Zeilen zu zählen.

Diese Prüfung musste selbst korrigiert werden. Sie verurteilte die Aufgabe wegen der Form eines Pfads – „der Baum liegt unter ~/Desktop, also wird launchd ihn nicht lesen können" – während macOS-Berechtigungen pro Binärdatei vergeben werden: Der Interpreter der Einheit las den Baum, den /bin/sh verweigert bekam. Eine Form ist ein Verdacht, ein Durchlauf ist eine Tatsache, und genau die fragt sie nun ab.

Und eine Prüfung, die keine statische Inspektion hätte machen können:

thot doctor --agents        # un appel modèle par agent installé
✓ lecture · claude       lit un fichier par chemin absolu
✓ écriture · claude      n'a pas écrit cette fois
✓ outils · claude        10 outil(s), tous en lecture seule
✓ lecture · hermes       lit un fichier par chemin absolu
✓ écriture · hermes      peut écrire — aucun mode lecture seule
                         (`-t file` et `--safe-mode` ne restreignent pas les permissions)
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ lecture · prime        lit un fichier par chemin absolu
✓ écriture · prime       peut écrire — outil unique : un noyau IPython
✓ outils · prime         ipython

Die Schreibzeilen sind grün, obwohl sie eine unbequeme Fähigkeit ankündigen: Sie berichten, was ist, nicht, was man sich wünscht. Zwei der drei Agenten können schreiben, und kein Flag verhindert es – gemessen, indem man sie auffordert, eine Datei zu erstellen, und dann auf die Festplatte schaut. Was nicht verhindert werden kann, wird unmöglich zu übersehen gemacht: AuditResult.touched benennt, was ein Durchlauf verändert hat, und die nächtliche Schleife schreit es auf stderr.

Sie legt eine Datei in einem temporären Ordner ab und fragt ihren Inhalt ab. Sie existiert wegen eines realen Fehlers: Hermes öffnete keinen Pfad relativ zu seinem Arbeitsordner und antwortete mit einem Satz, der wie eine Ablehnung klang. Ein Drittel des Panels konnte keine Behauptung verifizieren, die auf einer zweiten Datei beruhte, und nichts anderes als das Ablegen einer Datei hätte es gezeigt.

Jede Zeile führt eine echte Operation aus und berichtet, was sie gemessen hat: nicht „skills: konfiguriert", sondern „91 geladen, 0 abgelehnt". Die Farb-Engine sucht einen Pfad in einer Stichprobe beider Sprachen, der MCP-Server antwortet auf sein eigenes Protokoll. Eine Prüfung, die nicht laufen kann, schlägt fehl, statt still zu bestehen: Eine grüne Zeile, die „nicht getestet" bedeutet, ist schlimmer als eine rote. Nichts berührt das Netzwerk oder ein Modell – thot doctor in einem Flugzeug gibt dieselbe Antwort wie in einem Büro. Ausgabe ungleich null bei Fehlschlag, um in ein && oder eine CI zu passen.

Die permanente Verbesserung

Ein Audit, der zwanzig Kandidaten argumentiert und aufhört, lässt den Rest für immer ohne Urteil. Ein Durchlauf ohne Budget läuft noch, wenn du dich wieder hinsetzt. thot improve ist das Dazwischen: begrenzte Runden, jede auf die Festplatte geschrieben, jede dort weitermachend, wo die vorherige aufgehört hat.

thot improve                      # un tour sur les trois arbres
thot improve --rounds 5           # jusqu'à ce qu'un tour ne juge plus rien
thot improve --every daily        # la boucle devient permanente

Die geschriebene Einheit trägt ihr eigenes PATH. launchd gibt einem Job /usr/bin:/bin:/usr/sbin:/sbin, cron noch weniger, und claude, hermes und node sind in keinem dieser Ordner – sie leben unter ~/.local/bin. Ohne das baute der nächtliche Durchlauf keine Engine, beurteilte nichts und endete mit Code 0: launchd verzeichnete jede Nacht einen Erfolg, auf unbestimmte Zeit. Ein solcher Job, der still scheitert, ist von einem funktionierenden Job nicht zu unterscheiden, daher endet ein tiefer Durchlauf ohne Agenten nun mit einem Fehler und sagt es.

Die Nachtversion meldet, was sie entschieden hat, nicht, was aufgetaucht ist. Der Unterschied zählt: Der Melde-Mechanismus geplanter Audits antwortet auf „Was gibt es Neues über der Schwelle", was die richtige Frage für einen Scan und die falsche für ein Urteil ist. Ein bereits im Bericht vorhandenes MEDIUM zu bestätigen, ist genau das, wofür die Schleife da ist — und es wäre niemandem gemeldet worden. Die Dateien, die ein Audit verändert hätte, werden im selben Journal vermerkt.

Ein Baum, der nichts mehr zu beurteilen hat, gibt seinen Anteil an den nächsten weiter. Gemessen am realen Korpus: thot hat einen leeren Rückstand und prime nur einen einzigen Kandidaten, also gab ein Budget von 20 pro Baum 40 für Bäume aus, die sie nicht nutzen konnten, während Hermes auf einhundertfünfzig wartete. Eine Runde von 20 wird zu einer Runde von 20, 20 und 60.

Eine dritte lässt sie schnell konvergieren: Fehlschläge werden gezählt. Ein Kandidat, dessen Agent sein Zeitlimit überschreitet oder dessen Modell sich weigert, sich festzulegen, behält seine Schwere — wird also zuerst in der nächsten Runde aufgegriffen, und in der übernächsten. Gemessen an einem Finding in einer Datei mit 1.660 Zeilen: vier Versuche bei drei Durchgängen, drei davon zahlen gegen dieselbe Wand. Nach zwei Fehlschlägen wandert er ans Ende der Warteschlange: weiterhin förderfähig, nie priorisiert. Ein Erfolg löscht den Zähler — eine Wand, die ein voller Nachmittag oder ein aufgebrauchtes Abonnement war, soll einem Finding nicht für immer folgen.

Zwei Eigenschaften lassen sie konvergieren, statt im Kreis zu laufen: Eine Widerlegung wird gespeichert, die nächste Auswahl überspringt sie also; eine Bestätigung wird bewusst nicht gespeichert — ein echter Defekt muss weiterhin erscheinen, bis jemand ihn behebt — die Schleife führt also ihren eigenen Satz bereits beurteilter Kennungen. Ohne das würde jede Runde nach der ersten ihr ganzes Budget darauf verwenden, das erneut zu argumentieren, was die erste gerade bestätigt hatte.

Sie endet mit dem, was zu tun ist, vor den Summen:

À REGARDER — 2 finding(s) :
  [hermes] plugins/platforms/a2a/tools.py:83 — confirmé · prime
      L'URL vient d'un argument d'outil, donc du modèle…
  [prime] packages/coding-agent/…/state-snapshot.ts:163 — réfutation contestée · hermes
      Le chemin dit fixe est construit depuis un identifiant non validé…

4 tour(s) · 83 jugement(s) (80 réfuté · 1 confirmé) · 157 candidat(s) sans décision

Eine Widerlegung ist Haushalt; eine Bestätigung ist eine Neuigkeit. Eine angefochtene Widerlegung auch: Es ist das Programm, das sagt, es habe sich gefangen, bevor es etwas begräbt. Sie zu zählen, ohne sie zu benennen, schickt den Leser ins Journal greppen — was genau das ist, was jedes Mal passiert ist, während eines Tages.

Sie ändert nie Code. „Verbesserung" bedeutet hier, dass das Urteil des Programms über sich selbst klarer und billiger wird: weniger Kandidaten ohne Entscheidung, mehr Entscheidungen auf der Platte, jede dem Agenten zuschreibbar, der sie getroffen hat.

Das Thermometer und die Schleife, die es nutzt

Alles Vorhergehende misst Thot mit Thot. improve fragt ein Modell, ob ein Finding real ist. evolve überwachte provenance, einen Bericht, den die Engine über ihre eigene Ausgabe berechnet. Beide sind zirkulär, und der Kreis ist nichts Akademisches: Der tiefe Durchgang hat 638 Urteile für 9 Bestätigungen bezahlt, während eine mit −100 % bewertete Regel schlief — xml_unsafe_parse meldete defusedxml, also genau das Gegenmittel, das seine eigene Meldung empfahl. Nichts im Programm konnte das sehen.

thot bench durchbricht den Kreis. Es misst Thot gegen Code, der von jemand anderem als vulnerabel oder sauber gekennzeichnet ist, zu gleichen Teilen, mit benannter Schwachstellenklasse.

thot bench ~/.thot/bench              # les suites présentes, catégorie par catégorie
thot bench ~/.thot/bench --json       # ce que la boucle d'évolution lit
thot bench ~/.thot/bench --floor info # ce que le plancher de sévérité cache

Der Korpus ist nicht eingebettet — achtzehntausend Drittanbieterdateien haben in diesem Repository nichts zu suchen — der Pfad ist also immer angegeben, und jede Suite wird gegen den Fingerabdruck ihres Manifests geprüft. Ein Korpus, dessen Beschriftungen sich unter einer Messung bewegt haben, ist schlimmer als kein Korpus: Alle Zahlen seitdem sind falsch und nichts sagt es.

Die Note ist das J von Youden, TPR − FPR. Null ist ein Münzwurf, +100 ist perfekt, und negativ bedeutet, dass die Regel invertiert ist. Präzision und Recall hätten es nicht gesagt: Eine Regel ohne einen einzigen echten Positivfall hat eine undefinierte Präzision, wird leer angezeigt und liest sich wie keine Daten — genau so überlebt eine invertierte Regel. Das J hat dieses Loch nicht.

Zwei Arten zu betrügen, und beide verlieren. Weniger zu finden — was provenance steigen ließ — lässt den TPR fallen. Alles zu melden ergibt TPR 100 %, FPR 100 %, J null: Der Korpus ist genau dafür 50/50 ausgewogen.

Gemessener Zustand, Standard-Mindestwert, drei Frameworks (django, fastapi, flask):

                        avant      après
TPR                      9.9 %     34.4 %
FPR                      0.5 %      0.0 %
J de Youden             +9.4 %    +34.4 %
catégories actives          10         24
catégories négatives         0          0

„Vorher" ist der Zustand des Programms in dem Moment, als das Thermometer zum ersten Mal existierte. „Nachher" ist derselbe Korpus, derselbe Mindestwert, derselbe Befehl. Das Hold-out bestätigt es: flask, ferngehalten, notiert +33,3 %, django, ferngehalten, +35,9 % — weniger als zwei Punkte vom Training, die Regeln funktionieren also auf Code, zu dessen Schreiben sie nicht beigetragen haben.

Nach Kategorie, was die Engine heute kann:

xxe · tlsverify · weakhash · weakrand · weakcipher · weakkeylength
hardcodedcreds · default_credentials · cleartexttransmit · errormessage
debug_code_production · cookie_no_httponly · cookie_no_samesite
securecookie · directory_listing_exposure                      +100,0 %
deserial  +98,0 %   cmdi  +93,3 %   codeinj  +86,0 %
eval_injection  +84,0 %   sqli  +82,7 %   cloud_ssrf_metadata  +78,7 %
ssrf  +64,7 %   pathtraver  +56,0 %   xss / basic_xss  +26,7 %

Null falsch positive über alle 18.300 Fälle, und keine negative Kategorie. Der Ausgangspunkt war ssrf bei −8,0 % und xxe bei −100 %.

Was diese fünfundzwanzig Punkte hervorgebracht hat, in der Reihenfolge, in der es gemessen wurde:

Änderung

J

Start

+9,4 %

Web-Routen als Einstiegspunkte erkannt

+9,6 %

Der Farbton folgt einem Wert in einem Container

+10,0 %

Ein Ternär mit konstanten Zweigen transportiert nichts

+12,4 %

Ein fullmatch mit negierter Klasse ist eine Blacklist

inkl.

SSRF-Wächter: Host-Whitelist, aufgelöster IP-Bereich

+14,3 %

socket.create_connection als Netzwerk-Sink erkannt

+14,9 %

Pfadeingrenzung und benannte Whitelist

+15,5 %

mark_safe als Sink, bleach als HTML-Neutralisierung

+16,3 %

Zwölf einzeilige Musterregeln

+27,8 %

Ein Muster zahlt den Barrierefreiheitsrabatt nicht mehr

+34,4 %

Drei dieser Änderungen wurden nach der Messung abgelehnt, und das Thermometer hat sie abgelehnt: Eine Datenbanklesung als unzuverlässige Quelle zu behandeln (436 vulnerable Fälle, 343 saubere — so viel Rauschen wie Signal), clickjacking (23 falsch positive in diesem Repository), und die fünf Regeln für LDAP/XPath/NoSQL/SSTI/EL-Injektion, deren gemessenes J genau 0,000 ist: Sie ziehen genauso stark auf die gesunde Hälfte wie auf die andere.

Präzision kauft man nicht mit einem toten Winkel

Jeder oben erkannte Wächter wurde zuerst in einer Version vorgeschlagen, die den Score erhöhte und ein ausnutzbares Loch öffnete. Eine adversariale Sonde hat sie alle gefunden:

resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
    return "blocked", 403
os.system("curl -s " + url)      # ← silencieux, et exploitable

Der Wächter ist eine korrekte SSRF-Verteidigung und sagt nichts über Shell-Metazeichen, die noch in der Zeichenkette sind. Den Farbton überall zu bereinigen kaufte 174 falsch positive weniger und einen toten Winkel bei der Befehlsinjektion.

Daher die Unterscheidung, die die Engine trägt: Ein Wächter, der den Wert einschränkt (literale Whitelist, fullmatch mit Aufzählung) weißt ihn für alles auf; ein Wächter, der das Ziel beweist (Der Host ist erlaubt, die aufgelöste Adresse ist öffentlich, der Pfad ist eingegrenzt) gilt nur für die betroffene Familie von Sinks. Dasselbe für bleach.clean, das HTML neutralisiert und x; rm -rf / intakt lässt.

Vier adversariale Sonden sind in der Testsuite und melden alle korrekt. Eine zukünftige „Verbesserung", die eines dieser Löcher wieder öffnen würde, bricht einen benannten Test.

Das verbleibende Schweigen hat zwei Ursachen, und thot bench trennt sie, weil es zwei verschiedene Arbeiten sind:

règle muette — elle existe et ne matche jamais : elle a un motif à élargir
aucune règle pour la classe : il y en a une à écrire

Die Unterscheidung entscheidet über die Reihenfolge der Ziele — ohne sie sind die stummen Kategorien bei J = 0 exakt gleichauf und die Sortierung fällt auf die alphabetische Reihenfolge zurück.

Die Fusion, dort wo sie etwas ändert

Cascade.turn wählt einen Agenten aus und ruft ihn; er holt den anderen nur, wenn der erste einen Fehler zurückgibt. Eine solche Runde ist konstruktionsbedingt auf das Beste der beiden gedeckelt: Sie kann weniger verlieren, nie mehr gewinnen. agent_apply machte dasselbe — eine Engine, im Singular.

thot evolve --fused lässt beide arbeiten, an verschiedenen Hälften desselben Problems:

thot bench ~/.thot/bench                      # où ça fait mal
thot evolve --from-bench --fused \
      --corpus ~/.thot/bench --hold-out flask # et on répare, en boucle
  • Hermes liest die Messung und schreibt eine Spezifikation. Er fasst keine Datei an. Seine Ausgabe ist eine Aussage über die Ursache: welche Regel, welche Zeile, warum diese Fälle.

  • Prime liest die Spezifikation und schreibt den Code. Ihm wird explizit gesagt, dass er sie ablehnen kann, wenn der Code ihr widerspricht — ein Ausführender, der nicht Nein sagen kann, ist ein Relais, und ein Relais fügt nichts hinzu.

  • Keiner der beiden entscheidet. Die Testsuite ist ein Mindestwert, der beschriftete Korpus ist das Urteil. Eine falsche Spezifikation, mit Überzeugung angewendet, lässt die Note fallen und wird Byte für Byte rückgängig gemacht.

Die Reihenfolge ist auch nicht willkürlich. Entwerfen-dann-bauen verifiziert sich an der Nahtstelle: Prime sieht die Argumentation von Hermes, bevor er sich darauf einlässt. Bauen-dann-neulesen erlaubt das nicht — wenn der zweite hinschaut, hat der erste bereits entschieden.

Die Ziele kommen aus der Messung, nicht aus einem getippten Satz. Bisher konnte die Schleife nur verfolgen, was ein Mensch bereits vermutete; ein aus dem Score konstruiertes Ziel ist das Programm, das sagt, wo es schwach ist, in Zahlen, die es nicht gewählt hat — und dieselben Zahlen sagen anschließend, ob die Antwort genutzt hat. Jedes Ziel trägt die Dateien, die fehlgeschlagen sind: Ein Agent, dem gesagt wird „xss ist bei 0 %", kann nur raten, ein Agent, dem drei fehlgeschlagene Dateien gegeben werden, hat ein Problem zu lösen.

Das Überlernen und was --hold-out wirklich kann

Eine Schleife, die auf einem Korpus benotet wird, hat nur eine echte Art zu betrügen: den Korpus lernen. Eine Regel, die darauf kalibriert ist, wie BenchmarkTest01126 aussieht, lässt die Note steigen und nützt niemandem, und von außen ist das ununterscheidbar von echtem Fortschritt.

--hold-out flask nimmt eine Suite aus der Hauptzahl heraus und hält sie als zweites Schutzgitter: Eine Änderung, die die Suiten bewegt, gegen die sie optimiert wurde, und nicht die, die sie nie gesehen hat, hat gesagt, was sie ist. Die beiden Zahlen werden darf_nicht_sinken gehalten.

Ihre Grenze, gemessen: Die drei Frameworks notieren innerhalb eines halben Punkts voneinander. Das fängt Überlernen an der Datei, nicht Überlernen an der Form des Benchmarks — ein generierter Korpus bleibt ein generierter Korpus, und eine Regel, die nur auf Demonstrationscode hilft, würde alle drei bestehen. Das Hold-out macht einen Betrug sichtbar; es macht den Korpus nicht repräsentativ.

Was die Schleife von einer Runde zur nächsten behält, steht in ~/.thot/evolve-log.jsonl. Ohne das, da sich die Messung in einer Runde kaum bewegt, liest die nächste Runde dieselben schlechtesten Kategorien, reicht dieselben Dateien, und erhält — vernünftigerweise — dieselbe bereits gebaute, gemessene und annullierte Spezifikation: --rounds 5 wäre ein fünfmal versuchter Versuch, fünfmal teurer, mit dem Anschein, beschäftigt zu sein.

Es ist kein Orakel. Ein Fix kann grün sein, das J steigen lassen und trotzdem schlecht bleiben — das ist Überlernen, und die Literatur zur automatischen Programmreparatur spricht nur davon. Der Korpus ist ein Beweis, dass man auf ihm Fortschritt gemacht hat. Die Schleife meldet, was sie geändert hat, damit ein Mensch anderer Meinung sein kann.

Skills — die Methoden, die Thot kennt

Eine Skill ist eine einmal geschriebene Methode: eine SKILL.md mit YAML-Frontmatter. Es ist das Format von Hermes Agent und Prime Agent, eine für einen der beiden geschriebene Skill lädt sich hier also ohne Änderung, und das Gegenteil ist wahr.

Thot bringt die vollständige Bibliothek von Hermes Agent mit (MIT — siehe NOTICE.md): 90 geladene Methoden, 117 weitere verfügbar.

thot skills list              # les 91 chargées
thot skills search pentest    # y compris la bibliothèque optionnelle
thot skills install ast-grep  # activer une optionnelle
thot skills show plan         # ce que lirait le modèle

Geladene Kategorien: audit, security, software-development, github, devops, research, mlops, productivity, creative, apple, email, media, note-taking, smart-home, social-media, autonomous-ai-agents.

Das Modell entdeckt sie mit dem Werkzeug skills — das mit einem Index von Namen antwortet, solange man ihm kein Stichwort gibt, weil zweihundert Beschreibungen kein Katalog sind — und liest die zutreffende mit skill. In einer Sitzung zeigt dir /skills dasselbe.

Eine importierte Methode, die ein hier nicht vorhandenes Werkzeug zitiert (delegate_task, browser_navigate…), wird unverändert ausgeliefert, mit einer Notiz, welche fehlen und was stattdessen zu verwenden ist. Das Vorgehen überträgt sich sogar dann, wenn sich der Werkzeugaufruf nicht überträgt.

Hinzufügen

~/.thot/skills/<nom>/SKILL.md            # partout où tu travailles
<repo>/.thot/skills/<nom>/SKILL.md       # versionné avec ce dépôt
---
name: ma-méthode
description: Ce qu'elle fait et quand s'en servir.
---

# Ma méthode

Les étapes, dans l'ordre.

Beide Anordnungen sind akzeptiert: ein flacher Ordner (Prime Agent) oder nach Kategorien gruppiert (Hermes Agent). Ein Name, der bereits existiert, ersetzt die eingebaute Version — geeignet, um eine mitgelieferte Methode anzupassen, ohne sie zu forken.

Eine vom geprüften Repository bereitgestellte Methode wird zuerst analysiert

Eine SKILL.md ist Text, der dem Modell als Anweisung übergeben wird. Die Repositories, die Thot liest, sind per Definition die, für die niemand verantwortlich ist. Ein feindliches Repository, das .thot/skills/x/SKILL.md ablegt, würde einen Teil des Briefings schreiben.

Die Wache von Hermes Agent wird hierher übertragen und gilt für alles, was aus dem Repository kommt: Injection, Exfiltration, Persistenz, Obfuskation.

   ▲ 1 skill(s) fourni(s) par ce dépôt ont été refusés — ils seraient passés
     au modèle comme instructions.
     pwn   curl vers l'extérieur ; accès à ~/.thot ; « ignore previous
           instructions »

thot skills scan <dossier> stellt dieselbe Frage auf Anfrage. Was Thot selbst ausliefert, wird nicht analysiert: Es liegt auf der Platte, weil das Programm installiert ist, nicht weil ein Repository es angefordert hat.

Benutzerdefinierte Befehle

Eine Markdown-Datei ist ein Befehl. Die Grammatik ist die von Prime Agent, Claude Code und Codex — nichts Neues zu lernen.

---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---

Relis $1 et dis-moi ce qui cloche. Ne modifie rien.

In .thot/commands/revue.md erzeugt das /revue src/app.py. Substitutionen: $1, $2…, $@, $ARGUMENTS, ${@:2}, ${@:2:3}. Ein Argument wird nie neu interpretiert. Die Befehle des Repositories durchlaufen dieselbe Wache wie seine Skills.

Drei werden mitgeliefert: /triage (den Eintrag benennen oder ohne Folge einstufen), /harden (Test, der zuerst fehlschlägt, dann Korrektur), /regress (das Audit-Diff gegen eine Git-Referenz).

MCP-Server

Der Katalog von Hermes Agent, zwanzig verifizierte Server:

thot mcp list            # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linear

Die Installation wird an das offizielle CLI delegiert, das bereits OAuth und die Token-Erneuerung besitzt — Thot hat keinen Grund, einen zweiten Tresor zu halten, der leaken könnte. Es sagt explizit, dass registriert nicht autorisiert ist, und welcher Befehl die Arbeit abschließt.

Die Werkzeuge des Modells

Die Klassiker — lesen, schreiben, bearbeiten, einen Befehl ausführen. Jedes Schreiben und jede Ausführung verlangt Bestätigung, und das ist nicht konfigurierbar.

Und vier, die nur Thot gehören, kostenlos, weil sie die Karte abfragen und nicht das Modell:

Werkzeug

Antwort

code_map

die Dateien des Projekts

find_symbol

Datei, Zeilen und Parameter einer Funktion

callers

wer was aufruft, und die Distanz zu einem Einstiegspunkt

audit

die Pfade der Färbung Quelle → Senke

Im Konto-Modus werden diese vier dem offiziellen CLI über thot.mcp_server bereitgestellt — ein MCP-Server im Nur-Lese-Modus, der nichts schreiben oder ausführen kann.

Wenn das Modell sucht, wer process_payment aufruft, fragt es den Graphen ab und erhält die vollständige Antwort — statt drei Dateien auf gut Glück zu greppen.

Nur-Audit-Modus

Der Analyse-Kern lässt sich auch ohne Modell, ohne Netzwerk, ohne Kosten verwenden:

thot init /chemin/du/repo --owner "Ton Nom"   # autorisation, une fois
thot audit /chemin/du/repo --paths            # chemins de teinte complets
thot audit . --all                            # y compris le bruit faible
thot audit . --json --out rapport.json
thot audit . --out rapport.sarif              # SARIF 2.1, format déduit du nom
thot audit . --fail-on high                   # code 1 en CI

SARIF — in eine Kette einsteigen, die bereits existiert

Ein Bericht, den keine Pipeline lesen kann, lebt in einem einzigen Terminal. GitHub code scanning, GitLab, Azure DevOps und die Editoren lesen alle SARIF 2.1, und zwei Eigenschaften von Thot sind dort mehr wert als anderswo.

Die Identität eines Findings ist Regel + Datei + Symbol + Fingerabdruck des Rumpfs, niemals die Zeile — genau das verlangt partialFingerprints. Ein Dashboard, das mit Zeilennummern gefüttert wird, öffnet alle seine Tickets erneut, sobald jemand oben in einer Datei einen Import hinzufügt; mit diesem gefüttert, nicht.

Und ein Färbungspfad ist eine Folge von Positionen, was codeFlows rendert: Der Leser klickt von der Quelle zur Senke, statt dem Werkzeug aufs Wort zu glauben. Ein Finding ohne Pfad trägt keinen codeFlows-Schlüssel — ein leerer Fluss wird als Färbungspfad ohne Schritt gerendert, was wie eine kaputte Analyse gelesen wird und nicht wie eine Musterübereinstimmung.

Ein Finding, das vom Panel widerlegt wurde, wird nicht gelöscht: Es geht mit einer begründeten suppressions mit. Ein Dashboard, das es nie sieht, kann nicht unterscheiden zwischen „niemand hat geschaut" und „jemand hat geschaut und entschieden" — und das zweite ist der ganze Sinn des Panels.

- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: thot.sarif }

Unterstützte Analyse — --deep

Die deterministische Analyse beantwortet „könnten diese Daten möglicherweise fließen?". Sie ist erschöpfend, kostenlos, und das ist nicht die Frage, die man einen Prüfer bezahlt zu entscheiden. --deep stellt die teure Frage, nur an die Kandidaten, die sie verdient haben:

thot audit . --deep                  # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50      # plus large
thot audit . --deep --parallel 8     # plus vite

Zwei Durchgänge, bewusst gegensätzlich:

  1. Die Sonde muss einen konkreten Eingang benennen, der den gefährlichen Punkt erreicht. Keine Allgemeinheit über die Schwachstellenklasse — eine URL, ein Wert, eine Wirkung.

  2. Die Widerlegung erhält dieses Szenario mit der einzigen Mission, es zu zerstören: vorgelagerte Validierung, Aufrufer, der nur Konstanten übergibt, Typ, der den angenommenen Eingang verbietet. Im Zweifel widerlegt sie.

Ein Finding überlebt nur, wenn eine zweite feindliche Lektüre desselben Codes es nicht zu töten vermag. confirmed bedeutet dann etwas.

In einer Sitzung dasselbe: /audit deep.

Die Engine wird automatisch gewählt — dein Claude-Konto über das offizielle CLI, wenn es verbunden ist (die Analysen laufen parallel, auf deinem Abonnement), sonst ein API-Schlüssel.

Was das Audit nicht lesen darf

# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/

Die eingebauten Ausschlüsse decken ab, was jedes Repository hat — node_modules, build, .venv. .thotignore deckt ab, was nur dieses Repository weiß: eingebettete Dokumentation, ein generierter Client, ein Ordner mit absichtlich kaputten Fixtures. Sie zu auditieren erzeugt keine Findings, es erzeugt Rauschen an genau der Stelle, wo die Findings wären.

Deine eigenen Regeln

Der eingebaute Katalog kennt die Standardbibliothek. Er kennt nicht den Wrapper, den dein Team um subprocess geschrieben hat, die Warteschlange, die dein Dienst konsumiert, noch den Validator, der bei dir einen Wert sicher macht. Ohne einen Ort, wo man es sagt, irrt sich jedes Audit eines echten Systems an denselben drei Stellen.

# <repo>/.thot/rules/team.yaml   — versionné avec le code
# ~/.thot/rules/*.yaml           — ce que tu sais, partout où tu travailles
sinks:
  - id: sink.team.run_shell
    patterns: [run_shell, shellutil.run_shell]
    impact: critical
    description: Wrapper shell interne (shell=True)
    match_mode: bare          # qualified | method | bare | prefix

sources:
  - id: source.queue
    patterns: [msg.payload]
    description: File de messages
    match_mode: prefix        # couvre msg.payload.decode(...)

sanitizers: [validate_host, team.escape]

Dieselbe Datei trägt die JavaScript-Regeln, unter einem Schlüssel js: — ein Team-Wrapper existiert in der Regel in beiden Sprachen, und die Deklaration zu trennen ist der Weg, auf dem eine Hälfte veraltet.

js:
  sinks:
    - id: sink.js.team
      names: [runShell, sh]     # comparés au dernier segment, ou qualifiés
      impact: critical
      description: Notre wrapper shell
      needs: [child_process]    # ne se déclenche que si le fichier l'importe
  sources:
    - id: source.js.queue
      patterns: [job.payload]
      description: File de messages
  sanitizers: [escapeArg]

Was das Modell anfordert, ist eine nicht vertrauenswürdige Eingabe

Die Quellen sind Ausdrückesys.argv, os.environ. Das deckt ein Programm ab, das man startet, und verfehlt ein Programm, das man aufruft: Das Werkzeug eines Agenten erhält seine nicht vertrauenswürdige Eingabe in benannten Parametern, die von einem Register aus dem gefüllt werden, was ein Modell angefordert hat, und kein Ausdruck erscheint irgendwo im Rumpf.

Die gemessenen Kosten, das nicht zu modellieren: vier SSRF an einem Nachmittag, alle über ein Werkzeugargument erreicht, keines von der Färbung gefunden — sie wurden von Musterregeln gefunden, die eine Form erkennen und nichts beweisen.

entry_sources:
  - id: entry.tool
    patterns: [tools.image_gen]     # les fonctions qu'un registre appelle
    parameters: [args]              # facultatif : lesquels de leurs paramètres
    description: Arguments remplis par le modèle
    match_mode: prefix

Standardmäßig leer, und bewusst: Welche Funktionen ein Register aufruft, ist eine Tatsache über ein Repository, und es zu erraten würde eine Quelle unter jeden Parameter jedes Programms legen. Gemessen an Hermes, die beiden Extreme: eine Regel, die die Pakete plugins und tools benennt, offenbart 19 bewiesene Pfade, von denen mehrere über-approximiert sind (die base_url, die ein Helper aus der Konfiguration erhält, ist nicht nicht vertrauenswürdig); eine Regel, die den Parameter args benennt, offenbart null, weil die Handler von Hermes benannte Parameter nehmen und kein Wörterbuch. Die richtige Regel benennt die tatsächlichen Einstiegspunkte — und es ist an ihren Autoren, sie zu kennen.

Eine Regel, die eine eingebaute id übernimmt, ersetzt sie — geeignet, um eine Senke herabzustufen, die das Team bewusst akzeptiert hat, ohne Thot zu patchen. Eine fehlerhaft formatierte Datei stoppt das Audit und benennt die Datei und den fehlerhaften Schlüssel, statt ein Fehlen von Findings anzunehmen.

Die Unterdrückungen

Eine Unterdrückung ist die einzige Sicherheitsaussage, die kein Werkzeug erneut liest — auch dieses hier nicht, konstruktionsbedingt. # nosec, # noqa: S310, // eslint-disable … security/…: Es ist eine Aussage über den Code, einmal geschrieben, die die Aufrufer überlebt, die sie beschrieb.

Zweimal in demselben Audit, hier, war sie falsch:

Unterdrückung

was sie behauptete

was wahr war

# nosec B310 — scheme checked above

das Schema ist kontrolliert

es stoppte file:// und nichts anderes — SSRF zum Metadaten-Dienst

# noqa: S310 (configured peers)

die URL kommt aus der Konfiguration

einer der Aufrufer liest sie aus einem Werkzeugargument, also vom Modell

Thot meldet sie daher als Klasse, in LOW, mit dem Muster daneben geschrieben. Das Finding sagt nicht „diese Zeile ist gefährlich": Es sagt „niemand hat den Grund erneut gelesen, warum sie entschuldigt wurde". Bei einem --deep-Durchgang ist es ein Agent, der prüft, ob das Muster noch hält.

Für Python werden die echten Kommentar-Token gelesen, keine Muster — ein regulärer Ausdruck unterscheidet # nosec in einem Kommentar nicht von demselben Text, der in einer Docstring zitiert wird, und die Docstring dieses Moduls zitiert zwei.

Eine Unterdrückung, die auf eine Zeile gesetzt wird, die dieses Audit meldet, ist nicht dasselbe Objekt: Es ist eine Aussage, die ein lebendes Finding widerspricht, geschrieben von jemandem, der dieselbe Zeile gelesen und anders geschlossen hat. Sie wird eine Stufe höher gemeldet und sagt es. Gemessen an Hermes: 7 von 45 — und drei der an jenem Tag gelesenen Unterdrückungen waren falsch.

Gemessen: 0 bei Thot, 0 bei Prime, 45 bei Hermes.

Kalibrierung

Präzision zählt so viel wie Erkennung. Bewusst nicht gemeldet werden:

  • subprocess.run(cmd) ohne shell=True — keine Shell liest den Befehl.

  • cursor.execute("… ?", params) — wörtliche Abfrage, gebundene Parameter.

  • Ein Wert, der durch int(), shlex.quote(), os.path.basename(), html.escape() geht — diese Aufrufe brechen die Kontaminationskette.

  • Ein Defekt, den kein Einstiegspunkt erreicht, wird automatisch herabgestuft — aber nur, wenn Einstiegspunkte gefunden wurden. Ohne einen ist der Umfang unbekannt, nicht null, und nichts wird über diese Unwissenheit begraben.

  • payload.get(...) ist nicht requests.get(...).

Größenordnung, gemessen an Hermes Agent (4 457 Python-Dateien): 98 s, 365 Findings, davon 25 high — 3 über dem Standard-Schwellenwert, sobald die Erinnerung angewendet wird.

Welchen Parameter das Argument füllt

Ein Helper, dessen ein Parameter eine Senke erreicht, macht nicht alles gefährlich, was man ihm übergibt. Die Engine paarte den Aufrufer jedoch mit der Gesamtheit der Senken des Callee, ohne zu sehen, wo das Argument landete:

def helper(safe, cmd):
    os.system(cmd)          # seul `cmd` atteint le shell

helper(sys.argv[1], "ls")   # la donnée va dans `safe` — et c'était rapporté

Die Position wird nun am Aufrufort gelesen, und der Name für ein benanntes Argument. Das tat die JavaScript-Engine auf ihrer Seite bereits.

Gemessen, sobald die Ausführung möglich war: Von 83 238 angefragten Auflösungen bei der Analyse von Thot und hermes/tools bezeichnen 90 % einen einzigen Parameter und 9,8 % keinen; 0,1 % bleiben offen (f(*rest), f(**options)) und behalten die breite Antwort. Der Callee bot im Durchschnitt 2,68 Parameter, also verschwinden fast zwei Drittel des Suchraums — die Analyse von hermes/tools sinkt von 11,4 s auf 9,5 s.

Was die Anzahl der Findings betrifft, allerdings: keinerlei Änderung. Thot, prime und hermes/tools liefern exakt dieselben 151 Kandidaten vor und nachher, davon null erfundene – die Sicherheitseigenschaft hält in der Praxis. Vier Tönungspfade werden kürzer, mehr nicht. Die oben korrigierte Form ist real und der Test beweist es, aber sie tritt in keinem der drei gemessenen Bäume auf: Dieser Fix kauft Geschwindigkeit und korrektere Pfade, nicht weniger Rauschen.

Zwei Formen sagen nichts aus: helper(*args) breitet eine unbekannte Anzahl von Werten aus, und helper(**options) benennt keinen davon. Dort behält die Engine die breite Antwort bei, die sie zuvor gab – die beibehaltene Menge ist immer enthalten in der vorherigen, also kann Verfeinern nur einen Finding entfernen, niemals einen erfinden.

Der Empfänger wird aufgrund der Signatur des Callee übersprungen, nicht aufgrund der Syntax des Aufrufs: Runner().go(x) führt zu einem go ohne Punkt, und Cls.m(obj, x) wie obj.m(x) sind beide ein Attribut. Da eine Methode fast immer gebunden aufgerufen wird, entscheidet self an der Spitze der Parameter – der ungebundene Aufruf ist die einzige Form, die einen Tick zu kurz gelesen wird.

Was der Graph nicht verfolgen kann

Ein Defekt, der über einen Pfad erreicht wird, den die Analyse nicht auflöst – ein Handler in einer Dispatch-Tabelle, eine dekorierte View, ein Aufruf auf einer Variable mit unbekanntem Typ – ist kein unerreichbarer Defekt. Thot unterscheidet die beiden:

HANDLERS = {"run": run_command}     # aucun appel : le graphe ne voit rien
@app.route("/ping")                 # enregistré à l'import par le décorateur
sandbox.run("pytest")               # plusieurs `run` répondent à ce nom

In allen drei Fällen ist die Reichweite unbekannt, nicht null, und der Finding behält eine leichte Strafe, statt begraben zu werden. Auf Hermes: dieselben 365 Findings, aber 60 steigen eine Stufe auf. Eine Funktion, die niemand aufruft und die niemand erwähnt, bleibt dagegen korrekt abgewertet – sonst würde der Filter aufhören, ein Filter zu sein.

Grenzen

Die Tönung über Dateien hinweg ist Python vorbehalten: JavaScript und TypeScript werden indexiert, innerhalb eines Funktionskörpers und bis zu einem Helper derselben Datei verfolgt, und enden dort mangels aufgelöstem Call-Graphen – die Tabelle oben sagt es Zeile für Zeile. Die Musterregeln dagegen gelten überall.

Ohne --deep ist jeder Finding PLAUSIBLE: statisch erkannt, noch nicht durch Ausführung bewiesen. Mit --deep hat ein confirmed-Finding eine gegnerische Widerlegung überstanden – das ist noch kein Ausführungsbeweis, der mit dem Repro kommt. Und das Fehlen eines Findings ist kein Beweis für das Fehlen eines Defekts: dynamisches Dispatching, Reflexion und Metaprogrammierung entgehen der Analyse.

Die Volltextsuche in Sessions hört auf, bei konstanten Kosten zu bleiben, sobald SQLite-Datenbanken betroffen sind, deren fts5 die rowid nicht zurückverfolgen kann: Die Engine sortiert dort die gesamte Treffermenge, statt bei zwanzig abzuschneiden. Gemessen an der, die CPython 3.12 mitbringt – 6 588 Anweisungen gegenüber 27 443, wenn das Korpus verfünffacht wird, während eine fähige Engine bei 2 215 und dann 2 698 bleibt. Ein Test verifiziert das und nennt die Version, wenn sie es nicht erlaubt.

Entwicklung

cd thot
uv run pytest -q

Editable installiert: Der Quellcode gilt sofort. Wenn sich dagegen pyproject.toml ändert (neue Abhängigkeit), muss uv tool install --editable --from . thot --force erneut ausgeführt werden.

Der deterministische Kern (codemap, taint, scope, scoring, store, report) hängt von keinem Agenten ab und berührt kein Netzwerk – ein Test verifiziert das und lässt die Suite fehlschlagen, falls sich das ändert.

Spec und Pläne: docs/superpowers/.

-
license - not tested
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Remote MCP for A2A dependency inspector MCP, structured receipts, audit logs, and reviewer-ready evi

  • Static MCP manifest and tool-policy security preflight with signed input-redacted receipts

  • A paid remote MCP for CodeG, built to return verdicts, receipts, usage logs, and audit-ready JSON.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nobodyohm-web/thot'

If you have feedback or need assistance with the MCP directory API, please join our Discord server