Alle Artikel

Verdammt harte Arbeit: Wie KI unseren Code standardkonform hält

Maxime Gréau Principal Software Engineer

Wie eine Flotte von KI-Agenten Hunderte von Modulen standardkonform hält – und die sicheren Änderungen automatisch zusammenführt.

Ein großes Monorepo standardkonform zu halten, lässt sich nicht so skalieren, wie man es sich erhofft. Die Richtlinien existieren – Konventionen zur Fehlerbehandlung, freigegebene Bibliotheken, wie ein GitHub-Actions-Workflow gepinnt wird – und unsere Entwickler kennen sie genau. Aber jeder Standard muss bei seiner Weiterentwicklung dennoch aktuell gehalten und bei jeder Änderung auf Hunderte von Modulen angewendet werden. Bei dieser Größenordnung kann kein Team manuell Schritt halten: Die Dokumentation hinkt dem Code hinterher, eine Konvention weicht ab, ein Workflow verstößt in einer Ecke gegen die Richtlinien, auf die diese Woche niemand geschaut hat.

Anstatt also Entwicklungszeit darauf zu verwenden, dieselben Standards manuell neu anzuwenden, haben wir ein System entwickelt, das diese Arbeit für uns übernimmt. Jeder Standard existiert als maschinenlesbares Artefakt, und eine Flotte von Agenten, die auf DriftlessAF – unserem Framework für den agentenbasierten Abgleich – aufbaut, sorgt kontinuierlich dafür, dass die Codebasis diesen Standards angenähert wird.

Über unsere Repositorys hinweg hat dieses System in den letzten 8 Wochen mehr als 4.700 Pull Requests zur Standardkorrektur eröffnet und 75 % derjenigen automatisch zusammengeführt, die es als risikoarm eingestuft hat – völlig ohne menschliches Zutun. In diesem Beitrag wird erläutert, wie das funktioniert und warum die Sicherheit nicht dadurch entsteht, dass man der KI mehr vertraut, sondern dass man sie niemals allein mergen lässt: Jeder automatische Merge erfordert die Übereinstimmung zwischen dem Urteil der KI und deterministischen Prüfungen.

Schreiben Sie den Standard einmal. Wenden Sie ihn überall an.

Die Umstellung lässt sich einfach formulieren, hat aber große Auswirkungen: Ein Standard ist kein Absatz in einem Dokument, sondern ein Artefakt, das ein Agent laden kann. Man verfasst ihn einmal, und er wirkt in zwei Richtungen. Er überprüft neuen Code beim Eintreffen und gleicht jede eingehende Änderung mit dem Standard ab, bevor sie gemergt wird. Und er durchforstet die bestehende Codebasis und eröffnet überall dort eine Fehlerbehebung, wo ein Modul vom Standard abgewichen ist. Ein einziges verfasstes Artefakt, das kontinuierlich auf jede eingehende Änderung und jedes bereits vorhandene Modul angewendet wird.

Figure 1: One authored standard, reconciled across every module in parallel. Most pass; the system flags the one that drifted.

Eine Flotte von spezialisierten Agenten: Reviewer, Skillfixer, Autofixer, LogAnalyzer, Judge

Wir setzen viele spezialisierte Agenten ein, von denen jeder genau eine Aufgabe und eine Definition von „Fertig“ hat. Ein Reviewer findet Verstöße gegen die geladenen Standards, und ein Skillfixer behebt sie. Wenn eine Änderung das CI beschädigt, repariert ein separater Autofixer sie und stützt sich dabei auf einen LogAnalyzer, der unübersichtliche Build-Logs auf die eigentliche Ursache herunterbricht. Ein Judge bewertet diese Arbeit offline, damit wir erkennen können, wenn sich ein Standard oder ein Prompt verschlechtert. Und der Confidence-Bot bewertet, wie sicher ein fertiger Pull Request zusammengeführt werden kann. Dies ist das Signal, mit dem risikoarme Änderungen ohne menschliches Eingreifen übernommen werden können. Reconciler verbinden sie miteinander.

Teil 1 – Skills: Ein Standard, der ausgeführt wird

Das Artefakt, als das wir jeden Standard codieren, ist ein Skill. So weiß ein Agent, was „korrekt“ bedeutet, bevor er Code überprüft oder korrigiert.

Was ein Skill ist und warum er nicht nur ein Prompt ist

Ein Skill ist ein strukturierter Befehlssatz, den ein Agent bei Bedarf lädt. Er folgt der offenen Spezifikation von agentskills.io, und der Aufbau ist bewusst einfach gehalten: ein Name, eine Beschreibung, die als Auslöser dient, ein Hauptteil mit direkten Anweisungen und konkreten Richtig-und-Falsch-Beispielen sowie optionale Referenzdateien für weiterführende Details. Hier ist der wesentliche Inhalt eines unserer Skills, go-standards:

---
name: go-standards
description: Enforces Go conventions for this repository. Use when writing,
  reviewing, or fixing Go code — syntax preferences, library choices,
  error handling, and package design.
---

# Go Standards

## Error wrapping

Always wrap errors with context using %w. Never discard the original error.

Correct:
    return fmt.Errorf("read config %q: %w", path, err)

Wrong:
    return errors.New("failed to read config")

**Audit:** grep for `errors.New` in code paths that already hold an `err`.

Was dies zu mehr als nur einem cleveren Prompt macht, ist die Art und Weise, wie es geladen wird. Skills nutzen die schrittweise Offenlegung: Der Agent sieht vorab nur den Namen und die Beschreibung der einzelnen Skills (jeweils etwa hundert Tokens) und liest die vollständigen Anweisungen erst, wenn ein Skill relevant ist, sowie die tiefer gehenden Referenzdateien nur bei Bedarf. Er zieht also die Go-Konventionen heran, wenn er sich Go ansieht, die GitHub-Actions-Härtungsregeln, wenn er einen Workflow prüft, die Muster für Sicherheitsüberprüfungen, wenn er Authentifizierungscode untersucht, und sonst nichts.

Und go-standards ist nur ein Skill von vielen – gha-standards dafür, wie Workflows gepinnt und berechtigt werden, go-security-review für die Muster, nach denen ein Sicherheitsingenieur sucht. Verschiedene Arten von „korrekt“ – Stil, CI-Sicherheit, Sicherheitsniveau – dieselbe Form des Artefakts, angewendet von denselben Agenten. Die Standardisierung eines neuen Bereichs bedeutet, einen neuen Skill zu schreiben und nicht ein neues Werkzeug zu entwickeln.

Skills sind Code: versioniert, überprüft und per Eval getestet

Wir behandeln einen Skill mit derselben Sorgfalt wie den Anwendungscode, den er steuert. Ein Standard, der automatisch auf Hunderte von Modulen angewendet wird, muss fehlerfrei sein. Daher durchläuft eine Änderung an einem Skill denselben Pull Request, dasselbe Review und dieselbe CI wie jede andere Änderung. Es gibt dafür sogar einen eigenen Skill: skill-authoring codiert, wie ein guter Skill aussieht, und der Reviewer nutzt ihn, um jede neue Datei SKILL.md genauso zu überprüfen, wie er Go-Code überprüft.

Diese CI leistet zwei Dinge:

Prüfung

Was überprüft wird

Was abgefangen wird

Spezifikationskonformität

Der formale Vertrag – gültiges Frontmatter, eine Beschreibung mit einer echten Trigger-Klausel, eine sinnvolle Verzeichnisstruktur, ein Textkörper innerhalb des Zeilenlimits.

Ein Skill, der nicht geladen oder ausgelöst werden kann.

Golden Evals

Dass der Skill funktioniert – bei Dateien mit bekannten Verstößen markiert der mit diesem Skill ausgestattete Reviewer-Agent genau die richtigen Zeilen und schlägt bei sauberem Code nicht an.

Ein Skill, der zwar ausgelöst wird, aber unzureichend prüft.

Die Bewertung in dieser zweiten Zeile wird von einem Agenten durchgeführt – unserem Judge –, der die Ausgabe des Reviewers mit dem erwarteten Ergebnis abgleicht.

Diese zweite Zeile ist am wichtigsten. Ein Eval ist ein Testfall für das Verhalten: eine Datei mit bekannten Problemen, kombiniert mit dem Review, das ein korrekter Agent erstellen sollte. Evals sind der Grund, warum wir einem Agenten überhaupt vertrauen können, da Agenten nicht-deterministisch sind. Derselbe Skill kann von einem Durchlauf zum nächsten leicht unterschiedliche Reviews erzeugen. Deshalb reicht eine Handvoll Fälle auch nicht aus: In einer Suite mit vier Fällen lässt ein einzelner instabiler (flaky) Fehlschlag den Skill unter seinen Schwellenwert fallen, und der Score kann eine echte Regression nicht von einem unglücklichen Durchlauf unterscheiden. Wir halten zehn oder mehr Fälle pro Skill bereit, damit der Schwellenwert widerspiegelt, ob der Skill tatsächlich funktioniert, und nicht, wie ein einzelner Durchlauf zufällig ausgegangen ist.

Skills befinden sich in einem zentralen Source-of-Truth-Repository, und sobald dort eine Änderung landet, überträgt ein automatisierter Workflow diese auf jedes andere Repo und jeden Bot, der sie nutzt. Was in der Produktion läuft, ist genau das, was wir überprüft und getestet haben – niemals eine veraltete Kopie.

Kontinuierliche Anwendung von Skills – die Reviewer- und Fixer-Agenten

Ein Skill bewirkt erst dann etwas, wenn ein Agent ihn anwendet, und der Bot, der unsere Skills anwendet, ist skillup – ein Reconciler, der die Codebasis überwacht und bei Änderungen an einer relevanten Datei nacheinander zwei Agenten ausführt. Der Reviewer-Agent lädt die Skills, die zu den geänderten Dateien passen, und meldet Ergebnisse bezogen auf bestimmte Zeilen zurück, die als GitHub Check Run veröffentlicht werden. Der skillfixer-Agent geht noch einen Schritt weiter: Er erstellt die konforme Änderung und öffnet einen Pull Request.

Das bedeutet einen grundlegenden Wandel darin, was Reviewing ausmacht. Anstatt PR für PR manuell dasselbe Urteil zu fällen, verfasst man einmal eine Richtlinie, die den Geist dieser Reviews erfasst, und das System wendet sie überall an. Nur so lässt sich diese Art von Arbeit skalieren.


Teil 2 – Der Agenten-Loop: Updates in großem Umfang einbringen

In Teil 1 wurde ein verfasster Standard so vertrauenswürdig gemacht, dass er überall angewendet werden kann. Hier ist die zweite Hälfte des Problems: diese Änderungen im Entwicklungstempo des Repos anzuwenden und einzubringen. Zwei Dinge müssen erfüllt sein: Die Mechanismen zum Öffnen der PRs müssen dauerhaft sicher laufen können, und der Komponente, die sie zusammenführt, muss man sicher vertrauen können.

Die Codebasis bei jeder Änderung konvergieren lassen

DriftlessAF liefert uns Reconciler, und genau diese Engine setzen wir für das Standard-Problem ein. Ein Reconciler arbeitet zustandsbasiert (level-based): Er feuert nicht einfach einmalig bei einem Ereignis und hofft, dass es geklappt hat – er prüft kontinuierlich, ob die Codebasis dem Soll-Zustand entspricht, und schließt die Lücke, wenn dies nicht der Fall ist. Auf Standards angewendet lautet die Frage nicht: „Hat jemand in diesem PR gegen go-standards verstoßen?“, sondern: „Entspricht jedes Modul immer noch go-standards?“. Diese Frage wird kontinuierlich bei jeder Änderung gestellt.

Das macht das System robust und unterscheidet es von einem One-Shot-Script. Wenn ein Fix-Durchlauf auf einen instabilen Modellaufruf oder ein Timeout stößt, ist nichts verloren; der nächste Durchlauf gleicht dieselbe Lücke aus. Wenn jemand eine Konvention wieder einführt, die wir bereits bereinigt hatten, fängt der nächste Durchlauf dies genauso ab wie beim ersten Mal. Die Standards entwickeln sich weiter, der Code ändert sich ständig, und der Reconciler sorgt einfach dafür, dass beides immer wieder konvergiert.

Wir erreichen dies über die gesamte Flotte hinweg, da darunter dieselbe Maschinerie läuft. skillup (der Reviewer und Skillfixer, der Standards durchsetzt), autofix (der Fixer, der defekte CI repariert und dabei auf einen LogAnalyzer-Agenten zum Lesen der Logs zurückgreift) und der Confidence-Bot sind allesamt Reconciler, die auf demselben Fundament aufbauen. Haltbarkeit, Wiederholungsversuche und Nebenläufigkeitssteuerung werden somit einmal gelöst und wiederverwendet. Jeder von ihnen bündelt einen oder mehrere Agenten hinter einem einzigen Dienst. Ein Muster, viele Anwender.

Figure 2: The agent loop. Reviewer and skillfixer act on every change; a stack of deterministic CI checks gates it, with an autofix sub-loop that repairs and re-runs on failure. Only a deterministic gate decides what auto-merges.

Wenn CI fehlschlägt, repariert der Fixer-Agent es – und zwar nur das, was defekt ist

Reconciler öffnen viele PRs, und manche davon lassen die CI fehlschlagen. Hier kommt der Autofixer ins Spiel – der Fixer-Agent in unserem autofix-Bot. Er geht von den fehlerhaften Prüfungen und den geänderten Dateien aus und untersucht das Problem von dort aus, genau wie ein Mensch es tun würde.

Sein erster Schritt ist normalerweise der Blick in die Logs. Aber CI-Logs bestehen aus Tausenden von Zeilen Build-Ausgaben, Test-Meldungen und Compiler-Rauschen, und der eigentliche Fehler liegt irgendwo mittendrin in wenigen Zeilen. Deshalb liest der Fixer sie nicht direkt. Er ruft den LogAnalyzer-Agenten als Tool auf, und dessen einzige Aufgabe ist es, diese Textwand in eine oder zwei strukturierte Ursachen (Root Causes) zu verwandeln: einen Fehlertyp, eine Nachricht, eine Datei und Zeile, den fehlgeschlagenen Befehl und einen Kontextausschnitt samt Erklärung, warum dies von Bedeutung ist. Meistens reicht eine einfache Regel aus: Die Fehler unmittelbar vor einem Exit-Code ungleich null sind in der Regel die Ursache. Das Ziel ist es, die ein oder zwei Fehler herauszufiltern, auf die es wirklich ankommt.

{
  "summary": "Build failed: reference to a function that was renamed.",
  "failures": [
    {
      "type": "build",
      "severity": "error",
      "error_message": "undefined: validateConfig",
      "location": { "file_path": "config/loader.go", "line": 42 },
      "failing_command": "go build ./...",
      "context": [
        {
          "content": "config/loader.go:42:9: undefined: validateConfig",
          "why_relevant": "Last error before `go build` exited non-zero; validateConfig was renamed to validate."
        }
      ]
    }
  ]
}

Sobald die Ursache identifiziert ist, liest der Fixer die relevanten Dateien, bestimmt die Fehlerbehebung und implementiert die Änderung. Er repariert nur das, was defekt ist. Er refaktoriert keinen funktionierenden Code, „verbessert“ keine Dinge, die ihm nebenbei auffallen, und behält den bestehenden Stil bei.

Der Fixer gibt den vollständigen Dateiinhalt, eine Commit-Nachricht im Format von Conventional Commits und eine kurze Post-Mortem-Analyse zurück. Er gibt zurück, was geholfen hat, was im Weg stand und was gefehlt hat. Wir speisen dieses Post-Mortem wieder in die Prompts ein, sodass eine Fehlerklasse, mit der der Agent heute zu kämpfen hat, im nächsten Monat besser bewältigt wird.

Das Herzstück – der Confidence-Bot bewertet die Merge-Sicherheit

Zusammen erzeugen diese Agenten weit mehr korrekte PRs, als ein Mensch manuell prüfen und mergen kann. Um dieses Volumen bewältigen zu können, brauchen wir einen verlässlichen Weg, um zu erkennen, welche Änderungen sicher genug sind, um ohne zeilenweise menschliche Prüfung gemergt zu werden.

Deshalb haben wir den Confidence-Bot entwickelt: einen Reconciler, dessen einzige Aufgabe darin besteht, eine Frage zu beantworten: Wie sicher kann dieser PR mit minimalem menschlichem Review gemergt werden? Dieser Bot stuft jeden PR als „high“, „medium“ oder „low“ ein. Die Einstufung setzt sich aus mehreren unabhängigen Prüfungen zusammen, sodass keine von ihnen – insbesondere nicht das LLM – eine riskante Änderung im Alleingang auf high heben kann.

Der Score kombiniert eine Handvoll günstiger, deterministischer Signale über die Änderung – wie groß sie ist, wie viele Dateien sie betrifft, wie riskant die Pfade sind – mit einem LLM-Signal, das das Diff liest und Absicht, Kohärenz und Verhältnismäßigkeit anhand der Standards aus Teil 1 beurteilt. Die deterministischen Signale haben das meiste Gewicht. Das Modell ist zwar der gewichtigste Einzelfaktor, macht aber dennoch weniger als die Hälfte des Gesamtwerts aus. Genug, um eine Einstufung nach oben oder unten zu bewegen, aber niemals genug, um sie allein zu entscheiden.

Zwei Entscheidungen sorgen dafür, dass die Bewertung als sicheres Freigabekriterium taugt. Manche Pfade können niemals ein „high“ erzielen. Eine Änderung, die sensible Bereiche wie Produktionsinfrastruktur oder Authentifizierung berührt, wird gedeckelt und geht immer an ein menschliches Review, ganz gleich, was das Modell meint. Zudem agiert der Bot konservativ: Im Zweifelsfall vergibt er eine niedrigere Punktzahl, da ein fälschlicherweise zuversichtliches high viel folgenreicher ist als ein übervorsichtiges low. Jede Einstufung ist zudem auditierbar. Jeder PR erhält eine Aufschlüsselung darüber, welche Signale zu dem Ergebnis geführt haben, sodass der Score niemals eine Blackbox ist.

Der Loop kalibriert sich selbst – Reverts schulen das System

Ein statisches Bewertungssystem driftet ab. Deshalb verfügt der Confidence-Bot über ein Gedächtnis – einen Vektorindex (RAG) vergangener PRs und vor allem darüber, was tatsächlich mit ihnen geschehen ist: gemergt, geschlossen oder per Revert zurückgenommen. Bevor er einen neuen PR bewertet, ruft er die ähnlichsten vergangenen PRs ab. Er übergibt sie dem LLM-Reviewer als Kalibrierungsanker und liefert dem Modell so konkrete Präzedenzfälle dafür, wie das Repo tatsächlich mit Änderungen dieser Art umgegangen ist.

Reverts spielen hierbei die größte Rolle. Wenn eine neue Änderung einer zuvor rückgängig gemachten ähnelt, sieht der Reviewer dies (sowie den hinterlegten Grund) und stuft den neuen PR niedriger ein. Das Negativbeispiel erfüllt seinen Zweck.

Hier schließen Menschen den Kreis. Wenn ein Engineer einen automatisch gemergten PR zurücknimmt und den Grund dafür angibt, verknüpft der Bot diesen Revert mit dem Original und speichert die Begründung zusammen mit dem negativen Ergebnis. Ein begründeter Revert liefert das stärkste Signal: Der Reviewer sieht sowohl, dass eine ähnliche Änderung rückgängig gemacht wurde, als auch warum. Ein Revert ohne Begründung wird zwar immer noch als negativ gewertet, aber erst aus der Begründung lernt das System für künftige Bewertungen.

Der einzige Akteur, der mergt, ist deterministisch

Hier ziehen wir eine klare Grenze: Der Confidence-Score ist beratend. Der Bot, der den Merge durchführt, ist ein separater, deterministischer Genehmigungs-Bot – kein Modell, kein Ermessen, nur Richtlinie. Und er ist an zwei Bedingungen geknüpft, die beide erfüllt sein müssen:

  1. Eine high-Einstufung, die an den exakten Commit-SHA gebunden ist. Das ist der subtile Punkt. Das Tier-Label bleibt haften – es verbleibt bei neuen Pushes auf dem PR, bis der Bot ihn neu bewertet. Ein Label allein könnte also dazu führen, dass ein Score, der für einen alten, intakten Commit berechnet wurde, für einen neuen, fehlerhaften bürgt. Stattdessen wird die Einstufung auch als Check Run auf dem Head-Commit veröffentlicht. Ein Check Run ist an den SHA gebunden, für den er erstellt wurde; ein neuer Commit hat keine Einstufung, bis der Bot ihn neu bewertet. Ein veraltetes Rating kann einen fehlerhaften Commit rein technisch nicht durchlassen.

  2. Jeder erforderliche CI-Check ist bei demselben Commit grün.

Nur wenn beides zutrifft, führt der Approver den Merge durch.

Ein Standard wird einmal verfasst und getestet, die Agenten wenden ihn an und bewerten das Ergebnis, und eine deterministische Richtlinie übernimmt das Mergen. Die KI übernimmt die Beurteilung; der Merge selbst ist eine Prüfung auf Commit-SHA und grüne CI. Genau das ermöglicht es dem gesamten Loop, ohne menschliches Eingreifen zu laufen.

Wie es in der Produktion aussieht: die Zahlen

In den letzten 8 Wochen hat skillup repo-übergreifend 4.746 Pull Requests zur Standardkorrektur geöffnet. Der Confidence-Bot – den wir im Laufe dieses Zeitraums eingeführt haben – hat bisher 3.044 davon bewertet; nur diejenigen mit geringem Risiko qualifizieren sich für den automatischen Merge. Von den 1.117 stufte er 840 (75,2 %) als risikoarm ein und mergte sie ganz ohne menschliches Zutun.

Wir gehen davon aus, dass diese 75 % von beiden Seiten aus weiter steigen werden. Der Confidence-Bot wird mit längerer Laufzeit immer besser: Er lernt aus seiner eigenen Bewertungshistorie und daraus, was mit den von ihm bewerteten PRs geschieht. Reverts liefern das stärkste Signal. Ein Merge, der bestehen bleibt, bestätigt nur schwach, dass eine Einstufung richtig war – aber ein rückgängig gemachter automatischer Merge ist der handfeste Beweis dafür, dass eine als sicher eingestufte Änderung es nicht war. Der Bot merkt sich diese Fälle und bewertet ähnliche Änderungen beim nächsten Mal vorsichtiger. Eine bessere Kalibrierung bedeutet, dass mehr der risikoarmen PRs sicher von selbst gemergt werden können, und das kurzfristige Ziel ist es, 100 % von ihnen automatisch zu mergen. Gleichzeitig fügen wir der CI weitere deterministische Prüfungen hinzu. Jeder neue Check, der für eine bestimmte Änderungsklasse bürgen kann, verschiebt von vornherein mehr PRs in die Kategorie „geringes Risiko“. Der Anteil der Änderungen, die der Loop auf beiden Achsen sicher einbringen kann, wächst stetig.


Über das Monorepo und Standards hinaus

Wir haben dies für unser Control-Plane-Monorepo entwickelt, aber nichts daran ist monorepo-spezifisch. Dieselben Reconciler und Agenten laufen auch auf unseren Data-Plane-Repositories, unterwerfen sie denselben Standards und mergen die sicheren Änderungen auf dieselbe Weise. Ein verfasster Standard erreicht jedes von der Flotte überwachte Repo, nicht nur ein einzelnes.

Und Standards sind nur ein kleiner Teil dessen, was die Flotte leistet. Die drei Skills in diesem Beitrag – go-standards, gha-standards, go-security-review – sind Teil von fast 100 Skills, auf die die Agenten zurückgreifen; die meisten haben nichts mit Code-Stil zu tun. Sie bilden Paketierung über ein Dutzend Sprachen hinweg, CVE-Behebung, Image-Erstellung und -Testing und mehr ab. Der Reviewer, Fixer, LogAnalyzer, Judge und Confidence Scorer sind allgemeine Bausteine von DriftlessAF; überall in der Chainguard Factory erledigen dieselben Komponenten all diese Aufgaben. Der in diesem Beitrag beschriebene Loop ist das Muster, nicht die Grenze.

Danksagung

Nichts davon ist das Werk einer einzelnen Person. Vielen Dank an all die großartigen Engineering-Talente bei Chainguard: Taylor Bloom, Joe Borg, Jonathan Lange, James Rawlings, Matt Moore und andere, für die Zusammenarbeit an diesen Agenten – für ihren Aufbau, ihr Deployment und die dahinter liegenden Daten-Pipelines, mit denen wir messen können, was sie tun. Unsere Codebasis über viele Repos hinweg auf Standard zu halten, ist eine Teamleistung des gesamten Engineerings, und sie wird dank der Menschen dahinter immer besser.

Kontaktieren Sie uns, wenn Sie mehr darüber erfahren möchten, wie unsere Agenten arbeiten und wie die Lösungen von Chainguard Ihnen helfen können.

Share this article

Start building with Chainguard