Es gibt eine Version von „KI im Engineering", in der man das Modell bittet, das Feature für einen zu schreiben, das Ergebnis pusht und das Beste hofft. Das ist nicht diese Version.
Die Version, die ich in Produktionscodebasen ausgeliefert habe, sieht eher aus wie ein Build-System: kleine, opinionierte, möglichst deterministische Programme, die irgendwo in der Mitte zufälligerweise ein LLM aufrufen. Sie laufen bei jedem Commit, jedem PR, jedem CI-Failure, jeder Schema-Migration. Sie haben Exit-Codes. Sie haben Logs. Sie haben Kill-Switches. Engineers beschweren sich, wenn sie brechen.
Die meisten sind Claude-Code-Agents, kleine .claude/agents/*.md-
Dateien mit Prompts und Tool-Allowlists, aufgerufen aus CI oder
Git-Hooks. Ein paar sind eigenständige Node-Skripte, die direkt die
Anthropic-API treffen. Der Unterschied ist am Ende kaum noch wichtig.
Das ist, was ich beim Ausliefern von etwa dreißig solcher Agents gelernt habe.
Prinzipien
Drei Regeln, die ich am Ende ausnahmslos angewendet habe:
- Ein Agent muss billiger zu schreiben sein als die Regel, die er ersetzt. Wenn der Agent mehr Code ist als die Lint-Rule, schreib die Lint-Rule.
- Ein Agent muss laut scheitern. „Wahrscheinlich okay" ist kein akzeptabler Exit-State. Entweder erzeugt er ein klares Pass/Fail, oder er läuft nicht.
- Jeder Agent hat einen Kill-Switch. Ein Repo-Level-Config-Flag,
eine CI-Variable, ein Rename in
.claude/agents/<name>.md. Wenn er mehr Zeit kostet, als er spart, soll jeder im Team ihn abschalten können, ohne zu fragen.
Die dritte ist die schwerste. Die meisten gescheiterten Agent-Rollouts, die ich gesehen habe, verletzen sie.
Die Taxonomie
Die Agents, die ich ausgeliefert habe, fallen in vier Kategorien.
Build & Quality
Diese laufen bei jedem Commit, PR und Pre-Push. Es sind die billigen, schnellen, hochvolumigen Agents.
- Pre- und Post-Commit Prettier- und ESLint-Enforcement
- TypeScript
any-Typ-Erkennung (mit kuratierter Allowlist) - Dead-Code-Scans (ungenutzte Exports, unerreichbare Branches)
- End-of-Turn-Type-Check-Gating, der Agent weigert sich, einen Turn
als fertig zu erklären, wenn
tsc --noEmitrot ist
Das Muster: Ein deterministisches Skript erledigt den Großteil der Arbeit; der Agent liest die Failures, entscheidet, ob sie es wert sind, sichtbar zu machen, und schreibt einen Einzeiler-PR-Kommentar.
PR-Review & Delivery
Diese laufen bei jedem PR. Höhere Latenz-Toleranz, mehr LLM-getrieben.
- Automatisches PR-Review (Stil, Design-System-Konformität, fehlende Tests)
- Review-Comment-Auflösung (der Agent postet Patches, die spezifische Reviewer-Kommentare adressieren, und bittet um Bestätigung)
- PR-Description-Generierung aus Diff und Commit-Messages
- Pre-Push-Quality-Gates
- Hotfix- und Release-Checklist-Workflows
- CODEOWNERS-Sanity-Checks
Migration & Investigation
Diese laufen ad-hoc, von einem Menschen angestoßen. Die großen Einmal-Agents.
- Resolver-für-Resolver-Portierungs-Agents (das GraphQL-API-Rewrite stützte sich auf einen davon, siehe die Fallstudie)
- Bulk-Modul-Scaffolders (z. B. Portierung von zwanzig Service-Klassen auf ein neues Adapter-Pattern)
- Query-Extraction-Agents (jede Drizzle-Query in der Codebasis finden, pro Tabelle gruppieren, fehlende Tenant-Filter sichtbar machen)
- CI-Failure-Diagnostics (das fehlschlagende Log lesen, die wahrscheinlichste Ursache finden, einen Kommentar posten)
- Flake-Hunter (eine Test-Suite N-mal laufen lassen, zusammenfassen, welche Tests nicht-deterministisch waren)
- Performance-Baselines + Bundle-Size-Diffing bei jedem PR
Architektur- und Datensicherheits-Guardrails
Das sind die, auf die ich am stolzesten bin. Sie kodieren Regeln, die sonst in einer Wiki-Seite stünden und verfallen würden.
- DDL-Safety-Scanner bei jeder Schema-Migration (kein
DROP TABLE, kein nicht-gefülltesNOT NULL-ohne-Default, keine fehlenden Indizes auf FKs) - Multi-Tenant-RBAC-Scope-Validator (jede neue Query muss nach
tenant_idfiltern oder auf einer expliziten Allowlist stehen) - Audit-Trail-Wiring auf Money-Mutationen (jede Funktion, deren Name
oder Return-Type
cents,amount,priceo. ä. enthält, muss irgendwo im Call-TreelogAuditEventaufrufen) - SQL-Safety-Enforcement (string-templated SQL ist ein Build-Fehler; nur parametrisierte Queries)
- Protected-Component-Edit-Blocking (Änderungen an einer kleinen Menge hochwirksamer Dateien benötigen ein Second-Pair-of-Eyes-Label)
- Accessibility- und Translation-Drift-Scanner
Ein Agent im Detail: der SQL-Safety-Enforcer
Eine Begehung des kleinsten, dümmsten und folgenreichsten Agents, den ich ausgeliefert habe.
Das Problem: Wenn Leute SQL von Hand schreiben, greifen sie immer wieder zu String-Interpolation, wenn die parametrisierte Query-API ein Zeichen weniger ergonomisch ist. Wir hatten in achtzehn Monaten drei SQL-Injection-artige Bugs. Keiner kam in Produktion an, aber jeder kostete Reviewer-Zyklen und das Vertrauen des Teams.
Der Agent: läuft bei jedem PR. Geht den Diff durch. Für jede
neue oder geänderte Datei unter src/db/ findet er string-templated
SQL, alles, was so aussieht:
const rows = await db.execute(`
SELECT * FROM users WHERE id = ${userId}
`);
…und blockiert das Mergen, bis es geändert ist zu:
const rows = await db.execute(sql`
SELECT * FROM users WHERE id = ${userId}
`);
Der Unterschied ist ein einzelnes sql-Tag-Präfix, das unser
Query-Helper zu einer parametrisierten Query kompiliert. Der Agent
postet einen PR-Kommentar mit den betroffenen Zeilen, dem
Vorschlags-Patch und einem Link auf die internen Docs, die das Warum
erklären.
Die Agent-Definition selbst ist etwa vierzig Zeilen lang:
---
name: sql-safety
allowed-tools:
- Bash(rg:*)
- Bash(git diff:*)
trigger:
- on: pull_request
paths: ["src/db/**/*.ts", "src/server/db/**/*.ts"]
---
Du bist ein SQL-Safety-Reviewer. Für jede geänderte Datei, die zum
Trigger-Pfad passt, finde SQL-Strings, die per Template-Literal
ohne unser `sql`-Tag gebaut werden.
Berichte Findings als einen einzelnen PR-Kommentar. Poste KEINE
inline review comments, die erzeugen Rauschen. Kommentiere NICHT,
wenn keine Findings vorliegen.
Für jeden Fund nenne:
- Datei-Pfad und Zeilennummer
- den anstößigen Snippet
- den Vorschlags-Rewrite
- einen Link zu docs/internal/sql-safety.md
Wenn du nicht mit hoher Sicherheit bestimmen kannst, dass ein
String-Literal SQL ist, ignoriere ihn. False Positives erodieren das
Vertrauen in diesen Agent stärker als verpasste Treffer.
Warum es funktioniert: kleine Oberfläche, enges Mandat, deterministische Ausgaben (zweimal laufen lassen, gleicher Kommentar), explizite „False-Positive-Aversion"-Anweisung. Das Team beschwert sich, wenn er bricht. Das ist das Signal.
Rollout
Ich habe denselben Rollout jetzt drei Mal gefahren — in zwei Engineering-Organisationen und einem persönlichen Projekt. Das Muster, das funktioniert hat:
- Liefere einen Agent aus, den niemand verlangt hat, der aber einen echten Schmerz löst. (Bei mir war das zweimal hintereinander Bundle-Size-Diffing bei jedem PR, sofort sichtbarer Wert, null Risiko.)
- Warte zwei Wochen. Leute werden ihn von selbst zu anderen Repos hinzufügen.
- Liefere einen zweiten Agent aus, der einen Schmerz adressiert, nach dem in 1:1s gefragt wurde, Flake-Hunting, PR-Description-Generierung. Schreibe den Prompt mit einem Adopter zusammen.
- Verschriftliche den Kill-Switch. Dokumentiere, wie man jeden Agent abschaltet. Erwähne das jedes Mal, wenn du einen neuen Agent einführst.
- Rolle die Architektur-Guardrails als letztes aus. Das sind die, mit denen die Leute bei jedem PR leben müssen; sie müssen den anderen Agents schon vertrauen.
Die Reihenfolge ist wichtig. Ich habe zwei Versuche gesehen, die Architektur-Guardrails zuerst auszurollen, bevor jemand Vertrauen in die billigen Quality-of-Life-Agents aufgebaut hatte. Beide Male wurden die Guardrails innerhalb eines Monats abgeschaltet.
Ergebnis
| Ergebnis | Anmerkung |
|---|---|
| ~30 Agents ausgeliefert | Über 3 Produktions- + 2 persönliche Codebasen |
| SQL-Injection-artige Bugs (18 Mo. davor) | 3 |
| SQL-Injection-artige Bugs (12 Mo. danach) | 0 |
| Durchschnittliche PR-Review-Durchlaufzeit | Spürbar gesunken (zweistelliger %-Bereich) |
any-Typ-Wachstum auf größtem Repo | Gestoppt; Nettoreduktion über sechs Monate |
| Übersetzungsdrift zwischen Locales | Wird zur PR-Zeit erkannt, nicht erst beim Release |
| Engineer-berichtete Produktivität | Hoch; speziell beim Schreiben von PR-Descriptions |
Was ich anders machen würde
Ich würde Evals früher schreiben. Eine Handvoll dieser Agents hat ihr Verhalten über Modell-Upgrades verändert, und ich musste nachträglich Evals schreiben. Für die nächste Charge baue ich Eval-Sets in derselben Woche, in der ich den Agent schreibe, ein kleines Fixture-Repo mit „sollte passen"- und „sollte fehlschlagen"- Fällen, das bei jedem Modell-Bump läuft.
Ich würde Agent-Prompts ab dem ersten Tag wie Code behandeln. Code-Review, Versionsgeschichte und ein CHANGELOG. Die zwei Male, bei denen ich in der Wildnis auf eine Regression gestoßen bin, lag es daran, dass jemand einen Agent-Prompt angepasst hatte, ohne dem Team Bescheid zu sagen, und der Failure-Mode war erst eine Woche später offensichtlich.
Ich würde der Versuchung widerstehen, Agents konversationell zu machen. Die langlebigsten Agents sind die dummen, Pass/Fail, kein Smalltalk. Die konversationellen sind die ersten, deren Verhalten driftet, und die letzten, denen vertraut wird.