Building and Evaluating Agent Harnesses · Teil 1

Ein Agent-Harness entwerfen: von der Aufgabe zur Architektur

Automatische Übersetzung

Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.

Ein Agent-Harness ist der Anwendungscode, der die Entscheidungen eines Models in Arbeit umsetzt: Er liefert Context, führt erlaubte Actions aus, hält den State und prüft, wann die Aufgabe erledigt ist. Wer einen Agent für die eigene Anwendung baut, entscheidet beim Entwurf dieses Codes, wie viel Freiheit die Aufgabe braucht und was die Anwendung unabhängig von der Antwort des Models garantieren muss.

Diese Entscheidungen kommen vor dem Framework. Ein Support-Assistent, ein Coding Agent und ein Dokumentklassifikator brauchen unterschiedliche Wege, zu handeln, sich von Fehlern zu erholen und Erfolg nachzuweisen. Gibt man allen dreien denselben Loop, denselben Memory Store und dieselbe Sammlung von Tools, verdeckt das genau die Anforderungen, die sie unterscheiden.

Dieser Artikel verfolgt diese Entscheidungen von einer Aufgabenbeschreibung bis zu einer ersten Implementierung. Er setzt den Artikel Harness Engineering für AI Agents fort, der die Aufgaben eines Harness einführt. Hier arbeiten wir durch, wie man sie für ein konkretes Projekt auswählt.

Warum das Model ein Harness braucht

Ein Model kann eine Erstattung vorschlagen. Etwas anderes muss das Konto identifizieren, die Bestellung laden, entscheiden, ob die vorgeschlagene Operation erlaubt ist, den Payment-Service aufrufen und festhalten, was passiert ist. Läuft dieser Service in einen Timeout, muss die Anwendung außerdem entscheiden, ob ein erneuter Versuch sicher ist. Diese Aufgaben bestehen auch dann, wenn das Model den nächsten Schritt sehr gut wählt.

LangChain verwendet eine weite Definition eines Harness, die Code, Konfiguration und Ausführungslogik rund um das Model einschließt. In der Praxis gehört einiges davon vielleicht schon zu Ihrer Anwendung: Authentifizierung, Datenbankzugriff, eine Job Queue oder ein Freigabesystem. Ein Harness zu entwerfen heißt auch zu entscheiden, wie der Agent diese Einrichtungen nutzt. Man muss sie nicht innerhalb eines Agent-Frameworks neu bauen.

Die kleinste Version macht vielleicht einen Model-Aufruf, validiert die Ausgabe und gibt ein Ergebnis zurück. Eine größere lässt das Model Dateien untersuchen, Programme ausführen und seine Arbeit über viele Turns überarbeiten. Beide brauchen eine Antwort auf dieselbe Frage: Welche Teile dieser Aufgabe können wir delegieren, und woran erkennen wir, dass sie korrekt erledigt wurden?

Um das konkret zu machen, betrachten wir einen Kundensupport-Assistenten, der Erstattungen, Lieferfragen und Kontoänderungen bearbeitet. Wir verwenden ihn als Entwurfsbeispiel. Seine Anforderungen führen uns zu einer bestimmten Architektur; eine andere Aufgabe sollte zu anderen Entscheidungen führen.

Erst das Ergebnis beschreiben, dann den Agent

„Erstattungsanfragen bearbeiten“ lässt den Großteil des Engineerings offen. Angenommen, ein Kunde bittet um eine Erstattung für eine gelieferte Bestellung. Ein erfolgreiches Ergebnis erfordert eine Erstattung für die richtige Bestellung und den richtigen Betrag, eine Erklärung an den Kunden und keine fremden Kontoänderungen. Liegt die Anfrage außerhalb der Policy, kann eine nützliche Ablehnung das richtige Ergebnis sein. Lässt sich die Bestellung nicht identifizieren, sollte der Assistent nach der fehlenden Information fragen.

Das sind verschiedene Ergebnisse. Würde man jede Unterhaltung, die ohne Fehler endet, als Erfolg werten, fielen sie in ein einziges irreführendes Signal zusammen.

Wir müssen auch wissen, wer fragt. Eine Bestellnummer in einer Nachricht belegt nicht, wem diese Bestellung gehört. Das authentifizierte Konto muss von der Anwendung kommen, und der Service, der die Erstattung durchführt, muss prüfen, dass die Operation zu diesem Konto gehört. Das Model kann helfen, die Anfrage zu interpretieren; es kann die Berechtigung des Aufrufers nicht dadurch herstellen, dass es eine plausible Kennung erzeugt.

Damit haben wir den Anfang eines Task Contracts: die Änderungen, die als Erfolg zählen, die Änderungen, die verboten sind, und die Situationen, die eine Rückfrage oder eine Übergabe erfordern. Er zeigt auch Anforderungen, die ein Prompt allein nicht erfüllen kann. Ein maximaler Erstattungsbetrag braucht einen Service, der ihn durchsetzt. Ein Ziel für die Antwortzeit braucht eine Deadline. Die Anforderung, morgen weiterzumachen, braucht dauerhaften State.

An diesem Punkt wissen wir genug, um zu fragen, ob ein Agent Loop überhaupt nützlich ist.

Wie viel vom Ablauf kennen wir schon?

Folgt jede Anfrage derselben Abfolge – Bestellnummer extrahieren, Bestellung abrufen, Anspruch berechnen und das Ergebnis erklären –, können wir diese Abfolge direkt schreiben. Das Model kann die Sprache am Anfang und am Ende übernehmen. Es braucht keinen weiteren Aufruf, um zu entscheiden, ob es eine Bestellung abruft, die der Ablauf ohnehin immer verlangt.

Ein expliziter Workflow wird nützlich, wenn der Ablauf feste Verzweigungen oder Wartezustände hat. Eine Erstattung braucht vielleicht ab einem Schwellenwert eine Freigabe. Eine Adressänderung ist vielleicht nur vor dem Versand erlaubt. Diese Stufen können wir als State Machine oder Graph darstellen, wobei Anwendungscode entscheidet, welche Übergänge erlaubt sind.

Ein Agent Loop gibt dem Model mehr Spielraum. In einer gemischten Support-Unterhaltung beginnt der Kunde vielleicht mit einer Beschwerde über die Lieferung, erwähnt dann, dass der falsche Artikel ankam, und bittet schließlich um einen Umtausch. Die nächste sinnvolle Abfrage hängt davon ab, was der Assistent herausfindet. Ein Loop lässt ihn eine Action wählen, das Ergebnis prüfen und erneut wählen. Diese Flexibilität bringt mehr mögliche Pfade, die man prüfen muss, darunter unnötige Aufrufe, wiederholte Abfragen und vorzeitiges Beenden.

LangGraphs Leitfaden zu Workflows und Agents unterscheidet zwischen vorab festgelegten Ausführungspfaden und dynamisch gewählten Schritten. Ein Graph kann beides ausdrücken. „Graph-basiert“ und „agentic“ sind keine Architekturen, die sich gegenseitig ausschließen.

Dieselbe Erstattungsanfrage auf drei Arten gezeichnet. Eine feste Pipeline führt Anfrage lesen, Bestellung abrufen, Policy prüfen und Antwort schreiben in fester Reihenfolge aus. Ein expliziter Workflow fügt eine Verzweigung hinzu, die der Code entscheidet: Eine Erstattung über dem Limit wartet auf Freigabe. Ein Agent Loop lässt das Model jeden Tool Call anhand des letzten Ergebnisses wählen und entscheiden, wann es antwortet.Dieselbe Erstattungsanfrage auf drei Arten gezeichnet. Eine feste Pipeline führt Anfrage lesen, Bestellung abrufen, Policy prüfen und Antwort schreiben in fester Reihenfolge aus. Ein expliziter Workflow fügt eine Verzweigung hinzu, die der Code entscheidet: Eine Erstattung über dem Limit wartet auf Freigabe. Ein Agent Loop lässt das Model jeden Tool Call anhand des letzten Ergebnisses wählen und entscheiden, wann es antwortet.

Ein nützlicher Kompromiss für unseren Support-Assistenten: Ein Agent sammelt Informationen und bereitet eine vorgeschlagene Änderung vor, dann geht dieser Vorschlag an einen festen Schritt für Validierung und Ausführung. Das Model kann innerhalb der Unterhaltung erkunden, während gewöhnlicher Code über die Bedingungen für einen Schreibvorgang entscheidet. Wissen wir bereits, dass diese Trennung nötig ist, können wir zuerst den äußeren Workflow bauen.

Alternativ beginnen wir mit einem begrenzten Agent Loop, dessen Tools diese Bedingungen durchsetzen. Das hält die Orchestration klein, während wir lernen, welche Pfade die Aufgabe tatsächlich braucht. Die Wahl hängt davon ab, ob explizite Stufen, Wartezeiten für Freigaben und Recovery-Regeln bereits Anforderungen sind.

In einem Agent Loop ruft das Model nur lesende Lookup-Tools auf und endet mit einer vorgeschlagenen Erstattung, die die Anwendung speichert. Ein fester Code-Schritt prüft Inhaber, Betrag und Policy. Bestehen die Prüfungen, führt der Code die Erstattung aus; das ist der einzige Schritt mit Schreibzugriff. Scheitern sie, fragt der Assistent beim Kunden nach oder übergibt.In einem Agent Loop ruft das Model nur lesende Lookup-Tools auf und endet mit einer vorgeschlagenen Erstattung, die die Anwendung speichert. Ein fester Code-Schritt prüft Inhaber, Betrag und Policy. Bestehen die Prüfungen, führt der Code die Erstattung aus; das ist der einzige Schritt mit Schreibzugriff. Scheitern sie, fragt der Assistent beim Kunden nach oder übergibt.

Es gibt keinen Grund, mehrere Agents einzuführen, nur um mehr Kästen zu zeichnen. Getrennte Agents werden nützlich, wenn Teilaufgaben unterschiedlichen Context oder unterschiedliche Permissions brauchen oder unabhängig voneinander laufen können. Sie brauchen dann auch einen Weg, Ergebnisse zusammenzuführen und widersprüchliche Actions aufzulösen. Zwei Worker, die beide dieselbe Bestellung erstatten können, erzeugen ein Koordinationsproblem, das ein einzelner Worker nicht hatte.

Links: Ein Liefer-Agent und ein Billing-Agent können beide die Refund-API aufrufen, also wird eine Bestellung zweimal erstattet. Rechts: Beide Agents dürfen nur lesen und liefern Befunde zurück; ein Code-Schritt führt sie zusammen und macht einen einzigen Refund-Aufruf.Links: Ein Liefer-Agent und ein Billing-Agent können beide die Refund-API aufrufen, also wird eine Bestellung zweimal erstattet. Rechts: Beide Agents dürfen nur lesen und liefern Befunde zurück; ein Code-Schritt führt sie zusammen und macht einen einzigen Refund-Aufruf.

In welcher Sprache soll der Agent handeln?

Nachdem wir entschieden haben, wer den nächsten Schritt steuert, müssen wir noch wählen, wie eine Action ausgedrückt wird. Das sind unabhängige Entscheidungen: Ein Workflow kann einen Code schreibenden Agent enthalten, und ein offener Loop kann nur wenige schmale Tools verwenden.

Für den Support-Assistenten ist eine benannte Operation wie issue_refund(order_id, amount_cents) eine naheliegende Schnittstelle. Ihr Schema macht die angeforderte Action explizit. Das Tool kann bei Erfolg eine Erstattungskennung zurückgeben oder eine strukturierte Erklärung, warum die Anfrage abgelehnt wurde. Diesen Vertrag können wir prüfen und testen, ohne dass das Model die Implementierung einer Erstattung erzeugen muss.

Manche Aufgaben brauchen keine Tools. Ein Klassifikator, dessen gesamter relevanter Input im Prompt steht, kann ein validiertes Label zurückgeben. Retrieval wird nützlich, wenn die Aufgabe Informationen außerhalb dieses Inputs braucht. Schreibvorgänge werden erst nützlich, wenn die Aufgabe verlangt, etwas zu ändern. Jede Fähigkeit sollte einen Grund haben, zu existieren.

Generierter Code bietet eine andere Action-Sprache. Angenommen, ein Assistent soll eine Tabelle untersuchen, Zeilen nach Kunde gruppieren und mehrere Summen berechnen. Python kann diese Arbeit in einem Programm ausdrücken und Zwischenwerte in der Ausführungsumgebung halten. Jede kleine Operation als eigenen Tool Call auszudrücken, könnte mehr Austausch mit dem Model erfordern.

Hugging Faces smolagents zeigt beide Ansätze. ToolCallingAgent erzeugt strukturierte Tool Calls. CodeAgent erzeugt Python, das die bereitgestellten Tools aufrufen, Werte speichern und Operationen kombinieren kann. Ein Code Agent verwendet weiterhin Tools; er drückt ihre Kombination als Programm aus.

Diese Ausdruckskraft ändert, was wir betreiben müssen. Wir müssen nun Syntaxfehler, Ausführungslimits, Abhängigkeiten und den Zugriff auf Dateien oder Netzwerke behandeln. Ob die eingesparten Model-Aufrufe diese Kosten aufwiegen, hängt von Aufgabe und Model ab. Das ist ein Vergleich, den man durchführen muss, kein automatischer Vorteil von Code.

Mit Tool Calls macht das Model drei Aufrufe, und jedes Zwischenergebnis fließt in seinen Context zurück. Mit generiertem Code schreibt das Model ein Programm, das in einer Sandbox läuft, dieselben Operationen aufruft und nur die Summen zurückgibt.Mit Tool Calls macht das Model drei Aufrufe, und jedes Zwischenergebnis fließt in seinen Context zurück. Mit generiertem Code schreibt das Model ein Programm, das in einer Sandbox läuft, dieselben Operationen aufruft und nur die Summen zurückgibt.

Mehrere Plattformen unterstützen eine Mischform: schmale Business-Tools plus ein Tool für Codeausführung in einer Sandbox. OpenAIs Responses API, die Claude API, die Gemini API und AWS Bedrock AgentCore bieten Codeausführung als eingebautes Tool an, das neben den eigenen Funktionen läuft. Anthropics Programmatic Tool Calling geht einen Schritt weiter: Code in der Sandbox kann die erlaubten Tools aufrufen, und nur ausgewählte Ergebnisse gehen an das Model zurück. Anthropics Empfehlung behält direkte Tool Calls als Standard bei und nutzt den Code-Pfad für große Ergebnisse und Ketten abhängiger Aufrufe. Es gibt auch reine Code-Designs, etwa CodeAgent von smolagents und Cloudflares Code Mode.

Für unseren Erstattungsassistenten würde ich mit typisierten Business-Operationen beginnen. Braucht eine spätere Aufgabe umfangreiche Berechnungen, können wir eingeschränkte Berechnung hinzufügen, ohne diesem Code direkte Befugnis über Kundenkonten zu geben.

MCP beantwortet eine weitere, getrennte Frage: Wie soll die Anwendung sich mit Tools verbinden? Eine lokale Funktion reicht, wenn die Implementierung zu einer einzigen Anwendung gehört. MCP kann helfen, wenn Tools separat bereitgestellt oder von mehreren Clients geteilt werden. Es liefert ein Integrationsprotokoll; Validierung und Zugriffskontrolle bleiben Aufgabe der beteiligten Anwendung und des Servers, wie die MCP-Tools-Spezifikation beschreibt.

Einschränkungen dort setzen, wo Actions passieren

Eine Tool-Beschreibung sagt dem Model, wann eine Operation nützlich ist. Die Implementierung muss entscheiden, ob dieser konkrete Aufruf laufen darf. Bei einer Erstattung heißt das: Der Service, der den Schreibvorgang ausführt, prüft das authentifizierte Konto, die Bestellung, den Betrag, die geltende Policy und jede nötige Freigabe.

Manche Prüfungen brauchen Interpretation

Angenommen, der Kunde fragt: „Kann ich diese Bestellung zurückgeben?“, und der Agent schlägt eine Erstattung vor. Die Bestellung gehört dem Kunden, der Betrag ist gültig und die Rückgabefrist läuft noch. Alle diese Prüfungen können bestehen, während die gewünschte Action unklar bleibt: Der Kunde fragt vielleicht nach dem Anspruch, statt jetzt eine Erstattung anzufordern. Wir müssen die Nachricht interpretieren, bevor wir entscheiden, was als Nächstes passiert.

Hier lohnt es sich, drei Aufgaben zu trennen. Gewöhnlicher Code prüft Inhaberschaft, Beträge und Daten. Das Haupt-Language-Model führt die Unterhaltung und erklärt das Ergebnis. Ein separates Entscheidungsmodel kann eine enge semantische Frage beurteilen, etwa ob der Kunde die vorgeschlagene Änderung tatsächlich angefordert hat. Diese Trennung liefert eine Entscheidung, die wir unabhängig vom Rest der Unterhaltung testen können.

Jev, ein Entscheidungsmodel von TypeSafe, ist ein Kandidat für diese Rolle. Es nimmt übergebenen State und typisierte Fragen entgegen und gibt strukturierte Antworten zurück. Für dieses Beispiel liefert sein Fragetyp Noul eine Wahrscheinlichkeit für eine Ja/Nein-Aussage. Ich würde getrennt fragen, ob der Kunde die Erstattung ausdrücklich angefordert hat und ob eine spätere Nachricht diese Anfrage zurückgezogen hat. TypeSafe empfiehlt, jede Frage spezifisch zu formulieren und die Antworten im Code zu kombinieren; mehrere unabhängige Fragen können sich einen API-Aufruf teilen. Hält man die beiden Urteile getrennt, lässt sich eine falsche Interpretation leichter finden.

Die Anwendung kombiniert diese Ergebnisse dann mit ihren Pflichtprüfungen. Unsichere oder widersprüchliche Antworten können zu einer Rückfrage oder einer menschlichen Prüfung führen; auch ein Timeout des Klassifikators braucht einen expliziten Fehlerpfad. Ein positives semantisches Urteil kann keine Inhaberschaftsprüfung aufheben und keine nötige Bestätigung ersetzen. Jevs Wahrscheinlichkeit ist eine Model-Ausgabe, kein Autorisierungsnachweis und keine unabhängige Prüfung der Korrektheit.

Eine vorgeschlagene Erstattung geht an Pflichtprüfungen im Code und an ein Entscheidungsmodel, das fragt, ob der Kunde sie angefordert hat und ob eine spätere Nachricht sie zurückgezogen hat. Code kombiniert beides: Eine fehlgeschlagene Prüfung führt zur Ablehnung, eine unklare Antwort oder ein Fehler zu einer Rückfrage oder Prüfung, und nur wenn alle Prüfungen bestehen, folgt die Erstattung.Eine vorgeschlagene Erstattung geht an Pflichtprüfungen im Code und an ein Entscheidungsmodel, das fragt, ob der Kunde sie angefordert hat und ob eine spätere Nachricht sie zurückgezogen hat. Code kombiniert beides: Eine fehlgeschlagene Prüfung führt zur Ablehnung, eine unklare Antwort oder ein Fehler zu einer Rückfrage oder Prüfung, und nur wenn alle Prüfungen bestehen, folgt die Erstattung.

Für eine solche Prüfung würde ich die vorgeschlagene Action, die relevanten Kundennachrichten in Reihenfolge und alle für die Entscheidung nötigen Tool Results übergeben und ihre Quellen unterscheidbar halten. Übergibt man nur die letzte Nachricht, kann eine Stornierung verloren gehen; übergibt man die gesamte Kontohistorie, kann die relevante Anfrage darin untergehen. Frage, Context und Fallback-Verhalten sind alle Teil des Designs. Ein kleines Allzweck-Model mit Structured Output ist ein weiterer Kandidat. Nutzen und Kosten beider muss man auf denselben Entscheidungen vergleichen.

Zugriff unabhängig von Model-Urteilen beschränken

Die Quelle einer Anweisung zählt. Eine Kundennachricht oder ein abgerufenes Dokument kann Anweisungen enthalten. Dieser Text darf keine neuen Permissions gewähren und nicht die maßgebliche Policy der Anwendung ersetzen. Fordert ein Dokument den Agent auf, alle Kundendatensätze zu exportieren, sollte dem System die Permission dafür fehlen, egal wie überzeugend das Dokument die Anfrage formuliert.

Hierher gehört auch die Entscheidung über die Sandbox. Kann das Model nur geprüfte Funktionen mit engen Permissions aufrufen, bringt eine allgemeine Code-Sandbox vielleicht wenig. Die Funktionen brauchen trotzdem Autorisierung, Validierung, Timeouts und sichere Datenbankoperationen.

Sobald wir generiertes Python, Shell-Befehle oder ausführbare Datei-Edits zulassen, müssen wir vor dem Aktivieren entscheiden, was diese Ausführung erreichen kann. Welche Verzeichnisse sind beschreibbar? Ist Netzwerkzugriff nötig? Welche Credentials sind verfügbar? Wie viel CPU-Zeit, Speicher und Ausgabe darf ein Lauf verbrauchen? Ein separater Prozess allein beantwortet diese Fragen nicht.

Hugging Faces Leitfaden zur sicheren Ausführung beschreibt eingeschränkte lokale Ausführung und stärker isolierte Alternativen. Ihre Einschränkungen unterscheiden sich. Ein Container mit weitreichenden Host-Mounts und mächtigen Credentials kann genau die Ressourcen offenlegen, die wir schützen wollten. Testen Sie einen versuchten verbotenen Lesezugriff, Schreibzugriff und Netzwerk-Request zusammen mit einem erfolgreichen Programm.

Einen Code Runner können wir aufschieben, solange unsere Aufgabe keinen braucht. Seine Isolation muss bereit sein, wenn wir Codeausführung erlauben. Und Isolation kann den Missbrauch einer API nicht verhindern, die wir absichtlich bereitstellen: Der Erstattungsservice muss seine eigenen Regeln trotzdem durchsetzen.

Was bleibt erhalten, wenn ein Lauf mittendrin abbricht?

Nehmen wir nun an, der Assistent sendet eine Erstattung ab, der Service legt sie an, und die Antwort geht verloren. Das Model sieht einen Timeout. Wiederholt es den Aufruf, entsteht vielleicht eine zweite Erstattung; meldet es einen Fehler, erzählt es dem Kunden vielleicht etwas, das nicht mehr stimmt.

Dieses Problem verbindet State, Retries und Recovery. Wir brauchen eine Operationskennung, die der Anwendung gehört, vor dem Absenden gespeichert und bei der Recovery derselben beabsichtigten Action wiederverwendet wird. Der Service muss diese Kennung erkennen und das vorhandene Ergebnis zurückgeben, statt die Action erneut auszuführen. AWS’ Leitfaden zu idempotenten APIs erklärt dieses Muster. Bietet der Service keine solche Garantie, braucht die Recovery einen Weg, zu prüfen, was passiert ist, oder vor einem weiteren Versuch eine menschliche Entscheidung einzuholen.

Das Harness speichert die Operations-ID op-7f3, sendet die Erstattung, und der Service legt Erstattung r-1 an. Die Antwort geht verloren, und das Harness sieht einen Timeout. Es wiederholt den Aufruf mit derselben ID, der Service gibt die vorhandene Erstattung r-1 zurück, und der Kunde erfährt, dass genau eine Erstattung existiert.Das Harness speichert die Operations-ID op-7f3, sendet die Erstattung, und der Service legt Erstattung r-1 an. Die Antwort geht verloren, und das Harness sieht einen Timeout. Es wiederholt den Aufruf mit derselben ID, der Service gibt die vorhandene Erstattung r-1 zurück, und der Kunde erfährt, dass genau eine Erstattung existiert.

Die Unterhaltung zu speichern reicht nicht. Sie hält vielleicht fest, dass das Model eine Erstattung angefordert hat, sagt aber nichts darüber, ob der externe Service sie ausgeführt hat. Ein Checkpoint hilft, die Ausführung fortzusetzen; er macht externe Effekte nicht rückgängig und macht ihre Wiederholung nicht sicher.

Für diesen Assistenten haben drei Arten von State unterschiedliche Aufgaben. Die Unterhaltung enthält die Anfrage des Kunden und die bisher gesammelten Fakten. Die Bestell- und Erstattungsdatensätze beschreiben den Geschäftszustand. Ein dauerhafter Ausführungsdatensatz verfolgt ausstehende Operationen und Freigaben, damit ein neu gestarteter Worker sicher weitermachen kann. Sie können sich Infrastruktur teilen, sollten einander aber nicht ersetzen.

Sessionübergreifendes Memory ist eine weitere Entscheidung. Muss sich der Assistent nächste Woche an eine Präferenz erinnern, brauchen wir Regeln, wem die Präferenz gehört, wer sie ändern darf und wie sie gelöscht werden kann. Ist jede Aufgabe unabhängig, ist ein Memory-Service vielleicht unnötig. Mehr aufbewahrter Text bedeutet auch mehr Gelegenheiten, veraltete oder irrelevante Informationen wiederzuverwenden.

Dasselbe gilt für Zusammenfassungen. Wenn eine Unterhaltung über ihren nutzbaren Context hinauswächst, brauchen wir vielleicht Retrieval oder Zusammenfassung. Eine Zusammenfassung, die eine Bestellnummer, eine Freigabebedingung oder eine offene Operation verliert, kann den Ablauf zerstören. Halten Sie den wesentlichen operativen State in expliziten Feldern, und testen Sie, was die Kompression übersteht, bevor Sie sich darauf verlassen.

Dem Loop einen Weg zum Anhalten geben

Ein flexibler Loop kann immer noch etwas finden, das er untersuchen will. Wir brauchen deshalb eine Stopp-Policy, die Erfolg, fehlende Informationen, erschöpfte Ressourcen und Fehler abdeckt. Das Erreichen eines Limits sollte einen wahrheitsgemäßen Status liefern: Die Erstattung kann abgeschlossen, nicht versucht oder noch ungewiss sein. „Der Agent hat angehalten“ ist für den Kunden oder den nächsten Worker zu wenig Information.

Ein Limit für Model-Aufrufe begrenzt nur einen Teil der Ausführung. Ein Tool kann hängen. Ein Retry kann Arbeit wiederholen. Ein Summarizer oder Guard kann zusätzliche Model-Aufrufe machen. Die Anwendung braucht Limits für verstrichene Zeit, Versuche, Ausgabegröße und Ausgaben, die die tatsächlich aufgerufenen Komponenten einschließen. Auch ein Abbruch muss Arbeit berücksichtigen, die bereits an einen externen Service gesendet wurde.

Die Wahl des Models gehört in diesen Entwurf, weil sie beeinflusst, was der Loop innerhalb dieser Limits tun kann. Testen Sie das geforderte Action-Format auf repräsentativen Aufgaben. Berücksichtigen Sie nutzbaren Context, Latency, Kosten und wohin Aufgabendaten gesendet werden dürfen. Ein lokal betriebenes Model und eine gehostete API können dieselbe Schnittstelle implementieren und dabei unterschiedliche Kapazitäts- und Betriebsgrenzen setzen.

Wählen Sie ein erstes Model und einen Endpoint, halten Sie deren Einstellungen fest und lassen Sie sie unverändert, während Sie eine Änderung am Harness evaluieren. Sonst stammt ein schnellerer Lauf vielleicht von einem anderen Provider oder Model statt von der Änderung, die wir testen wollten.

Den Abschluss evaluieren, auch in Fällen, die nichts tun sollen

Wir können jetzt eine Anfrage ausführen, müssen aber noch entscheiden, ob das Ergebnis nützlich ist. Für das Erstattungsbeispiel gibt es mindestens drei Beobachtungen: was der Assistent gesagt hat, welche Actions er versucht hat und was sich im Service geändert hat. Jede erkennt Fehler, die den anderen entgehen können.

„Ich habe Ihre Bestellung erstattet“ kann mit einer unveränderten Datenbank einhergehen. Die richtige Erstattung kann mit einer fremden Adressänderung einhergehen. Eine unveränderte Datenbank kann eine korrekte Ablehnung bedeuten, eine nützliche Rückfrage, einen Absturz oder einen Assistenten, der nichts getan hat. Der Endzustand allein kann diese letzten vier Fälle nicht unterscheiden.

Vier Läufe lassen die Datenbank unverändert: eine gültige Ablehnung, eine Rückfrage, ein Absturz und ein Lauf, der nichts getan hat, aber behauptet, die Erstattung sei erledigt. Nur die Antwort und die Actions unterscheiden sie.Vier Läufe lassen die Datenbank unverändert: eine gültige Ablehnung, eine Rückfrage, ein Absturz und ein Lauf, der nichts getan hat, aber behauptet, die Erstattung sei erledigt. Nur die Antwort und die Actions unterscheiden sie.

Die Support-Simulation zu diesem Artikel macht diese Grenze konkret. Unter ihren zwölf eigenen Aufgaben erwarten sieben keine Datenbankänderung. Übergibt man jedem Checker eine unveränderte Ausgangsdatenbank, besteht man daher 7 von 12 State-Prüfungen. Das ist eine Eigenschaft der Prüfungen, keine gemessene Support-Erfolgsquote. Die Aufgabendefinitionen untersuchen Unterschiede in der finalen Datenbank; sie bewerten weder die Erklärung noch die Zwischen-Actions. Ein Schreibvorgang, der später rückgängig gemacht wird, kann in diesem Vergleich ebenfalls verschwinden.

Nehmen Sie in Ihr erstes Evaluations-Set eine erfolgreiche Action auf, eine gültige Ablehnung, eine Anfrage, der wesentliche Informationen fehlen, und einen Fehler während der Ausführung. Definieren Sie neben dem Endzustand auch die erwartete Antwort und die erlaubten Actions. Ein Rückfragefall sollte die richtige Frage verlangen; ein Ablehnungsfall sollte eine nützliche Erklärung verlangen. Diese Fälle zeigen, ob der Agent die Aufgabe versteht und ob die Anwendung sie durchsetzt.

Auch die Intent-Prüfung mit Jev braucht eigene Fälle. Geben Sie ihr vorgeschlagene Actions mit unabhängig festgelegten erwarteten Entscheidungen: eine ausdrückliche Erstattungsanfrage, eine Frage nach dem Anspruch, eine zurückgezogene Anfrage und eine Anweisung, die in einem Tool Result steckt. Der letzte Fall testet auch, ob der Context-Aufbau die Unterscheidung zwischen Kundenabsicht und Text aus Tools bewahrt. Zählen Sie unangemessene Actions, die sie zulässt, und legitime Actions, die sie blockiert, und verfolgen Sie dann die vollständige Unterhaltung nach einer Blockade. Einen Schreibvorgang zu verhindern hilft nur bei einem Teil der Aufgabe, wenn der Assistent danach in einer Schleife läuft oder den Kunden nie um Klärung bittet. Wählen Sie Schwellenwerte auf Entwicklungsbeispielen, frieren Sie sie vor dem Held-out-Vergleich ein, und protokollieren Sie Fragenversion, übergebenen Context, zurückgegebene Wahrscheinlichkeiten, angewandte Regel, Fehler und Kosten. Die unten aufgezeichnete Demo hat keine Ablehnung durch Jev ausgelöst, kann diesen Nutzen also nicht belegen.

Protokollieren Sie genug, um einen Fehler zu diagnostizieren: Aufgabe, Model- und Harness-Versionen, vorgeschlagene Actions, Ausführungsergebnisse, Endzustand, Fehler, Latency und Kosten. Halten Sie Secrets und unnötige personenbezogene Daten aus diesem Protokoll heraus. Geben Sie dem Kandidaten seinen Aufgaben-Input und die Regeln, die er braucht, und halten Sie Referenzantworten und Held-out-Testfälle unzugänglich. Auch der Evaluator und seine Nachweise müssen außerhalb der Kontrolle des Kandidaten bleiben. Kann ein künftiger Optimizer das Harness bearbeiten, darf er die Prüfungen oder Ergebnisse, die seine Änderungen bewerten, nicht umschreiben können.

Diese Nachweise geben optionalen Komponenten einen Zweck. Fügen Sie einen Router hinzu, wenn Traces zeigen, dass die Aufgabenauswahl eine eigene Stufe braucht. Probieren Sie eine Zusammenfassung, wenn lange Historien einen bestimmten Fehler verursachen. Testen Sie einen Guard gegen verbotene Actions, einschließlich der Fälle, die er erlauben sollte. Nötige Zugriffskontrollen folgen aus den Beschränkungen der Aufgabe; optionale Ergänzungen brauchen Nachweise, dass ihr Nutzen ihre Kosten rechtfertigt.

Den Entwurf in eine erste Implementierung überführen

Für unser Support-Beispiel haben wir jetzt einen begründeten Ausgangspunkt: einen begrenzten Loop, um unterschiedliche Anfragen zu interpretieren, schmale Tools für Kontooperationen, maßgebliche Prüfungen in den Services, die schreiben, und explizite Datensätze für ausstehende Actions. Generierten Code oder persistentes Gesprächs-Memory brauchen wir noch nicht.

Ich würde zuerst einen vollständigen Pfad bauen. Legen Sie ein kleines Testkonto und ein erwartetes Erstattungsergebnis an. Implementieren Sie die Lookup- und Erstattungsoperationen einschließlich ihrer Ablehnungspfade. Verbinden Sie das Model mit diesen Operationen. Spielen Sie dann die Fälle Ablehnung, fehlende Information und unterbrochener Schreibvorgang durch. So zeigen sich fehlende Anforderungen, bevor eine größere Aufgabensammlung sie schwerer sichtbar macht.

Ein Loop ohne Framework kann in diesem Stadium ausreichen. Der Grundablauf: Context zusammenstellen, die nächste Action anfordern, sie validieren und ausführen, dann das Ergebnis an das Model zurückgeben. Wer diesen Loop selbst schreibt, ist aber auch für Nachrichtenkonvertierung, Abbruch, State und Tracing zuständig. Ein vorhandenes Framework wiederzuverwenden ist nützlich, wenn es diese Arbeit spart, ohne die Entscheidungen zu verbergen, die wir kontrollieren müssen.

Für diese Serie ist LangChains create_agent ein bequemer Ausgangspunkt, weil es den Model/Tool-Loop und Hooks zum Ändern seines Verhaltens bereitstellt. Es läuft bereits auf LangGraph. Später auf einen expliziten LangGraph-Workflow umzusteigen heißt, mehr Stufen rund um diesen Agent selbst zu steuern; es heißt nicht, einem System, das vorher keinen hatte, einen Graph hinzuzufügen.

Der erste Entwurf muss nicht jede oben besprochene Fähigkeit enthalten. Er braucht genug, um seine Aufgabe innerhalb ihrer Beschränkungen zu erledigen, plus Nachweise, die zeigen, wo er scheitert. Bevor ich die Implementierung schreibe, würde ich die Entscheidungen auf eine Seite schreiben:

FrageFestzuhaltende Entscheidung
Welche Aufgabe erledigen wir?Der Aufrufer, das erwartete Ergebnis, gültige Ablehnung oder Übergabe und verbotene Effekte
Wer wählt den nächsten Schritt?Bekannte Stufen im Code; an das Model delegierte Entscheidungen; Begründung für jede
Welche Prüfungen brauchen Interpretation?Die semantische Frage, ihr Context, das Kandidaten-Model und das Verhalten bei Unsicherheit
Was darf das Model anfordern?Keine Tools, typisierte Operationen, generierter Code oder eine Kombination
Wer autorisiert und führt es aus?Erlaubte Ressourcen, durchsetzender Service, Freigaberegeln und etwaige Sandbox-Einschränkungen
Was muss eine Unterbrechung überstehen?Gesprächs-State, Geschäftsdatensätze, ausstehende Operationen und Recovery-Verhalten
Welche Limits gelten?Model-/Provider-Beschränkungen, Datenverarbeitung, Aufrufe, Zeit, Ausgaben und Abbruch
Was belegt den Abschluss?Prüfungen von Antwort, Actions und State; wem ihre Nachweise gehören
Was haben wir weggelassen?Optionale Komponenten und der Fehler oder die neue Anforderung, die jede rechtfertigen würde

Eine brauchbare Antwort ist so konkret, dass sie die Implementierung ändert. „Braucht Memory“ ist vage. „Muss eine freigegebene Erstattung nach einem Neustart des Workers fortsetzen, ohne eine zweite Erstattung anzulegen“ sagt uns, was wir persistieren und welchen Fehler wir testen müssen.

Dies ist Teil 1 von Building and Evaluating Agent Harnesses. Teil 2 führt die Frage nach dem Control Flow weiter: Wann verbessert ein expliziter Workflow die Bearbeitung einer Aufgabe so weit, dass sich seine zusätzlichen Stufen lohnen? Wir bauen den kleinsten begründeten Workflow um den Agent und vergleichen ihn mit dem Loop. Router, parallele Worker und Zusammenfassungen sind Kandidaten für eine Untersuchung, kein vorab festgelegtes Ziel. Spätere Teile trennen den Evaluator vom Kandidaten und messen die Streuung über wiederholte Läufe.

Optional: die Implementierung und ihre Grenzen untersuchen

Die begleitende Demo ist eine kleine, lauffähige Version des Support-Assistenten. Sie läuft in zwei Umgebungen:

  • Eigene Support-Aufgaben: zwölf Aufgaben, jede gegen eine frische SQLite-Datenbank mit Kunden und Bestellungen.
  • τ³-bench retail: acht Aufgaben aus einem öffentlichen Benchmark, in dem ein simulierter Kunde mit dem Agent spricht und Datenbankprüfungen das Ergebnis bewerten.

Dieser Abschnitt beschreibt, was die Demo enthält, was sie absichtlich weglässt, was die Ergebnisse zeigen und wie man sie ausführt. Sie können ihn überspringen und den Entwurfsprozess oben trotzdem nutzen.

Was die Demo enthält

Ein LangChain-Agent wählt die Aufrufe. Er hat fünf Konto-Tools (look_up_account, list_orders, issue_refund, update_address und set_preference) und einen MCP-Server für Policy- und FAQ-Abfragen. Ein In-Memory-Checkpointer hält den Ausführungs-State. Es gibt keinen Code Runner und kein Memory über Sessions hinweg.

Die Demo zeichnet zwei Konfigurationen auf:

SpecKomponenten
plainSummarization, ein Limit von 20 Model-Aufrufen pro Lauf und 2 Retries bei einem Tool-Fehler
baseAlles aus plain, plus Schema-Guided Reasoning und ein Jev Write Guard

Schema-Guided Reasoning (SGR) lässt das Model statt eines nativen Tool Calls ein strukturiertes Objekt für den nächsten Schritt zurückgeben. Der Jev Guard läuft vor jedem Schreibvorgang und stellt eine zusammengesetzte Frage: Würde dieser Aufruf die Policy verletzen oder über das hinausgehen, worum der Kunde gebeten hat? Die zwei getrennten Fragen weiter oben in diesem Artikel, zur Anfrage und zu ihrem Widerruf, sind ein vorgeschlagenes Redesign. Die aufgezeichneten Läufe haben sie nicht verwendet.

base fügt SGR und Jev zusammen hinzu, daher können die Ergebnisse der eigenen Aufgaben nicht zeigen, welches der beiden einen Unterschied verursacht hat.

Der Model-Knoten trägt Summarization, das Limit für Model-Aufrufe und Schema-Guided Reasoning. Der Tools-Knoten trägt Tool Retry und den Jev Write Guard. Unter τ³-bench hält der Graph vor dem Tools-Knoten an, daher laufen nur die Komponenten auf der Model-Seite.Der Model-Knoten trägt Summarization, das Limit für Model-Aufrufe und Schema-Guided Reasoning. Der Tools-Knoten trägt Tool Retry und den Jev Write Guard. Unter τ³-bench hält der Graph vor dem Tools-Knoten an, daher laufen nur die Komponenten auf der Model-Seite.

Was die Demo absichtlich weglässt

  • Die Tools prüfen die Datenintegrität, überlassen die Geschäfts-Policy aber dem Agent, damit ein Experiment einen Schreibvorgang aufdecken kann, den die Policy verbietet. Ein echter Service würde die Policy beim Schreiben durchsetzen.
  • Es gibt kein aufgabenweites Limit für Zeit oder Ausgaben, und wiederholte Schreibvorgänge haben keinen Idempotency Key.
  • Der Evaluator läuft im selben Prozess wie der Agent.

Diese Ergebnisse testen daher weder die Durchsetzung in Produktion noch die Sicherheit von Retries noch den Schutz der Nachweise vor Manipulation.

Ergebnisse auf den eigenen Aufgaben

Jede Zeile ist ein Lauf pro Aufgabe; Model und Endpoint-Preise sind mit den Ergebnissen aufgezeichnet. Eine State-Prüfung vergleicht die finale Datenbank mit der erwarteten. Kosten und Latency sind Mittelwerte pro Aufgabe; die Kosten schließen Jev ein, wo es läuft.

SpecModelState-PrüfungenKostenLatency
baseopenai/gpt-6-luna12/12$0.0007311.9 s
plainopenai/gpt-6-luna12/12$0.000335.8 s
basez-ai/glm-5.3-flash12/12$0.001019.7 s
basexiaomi/mimo-v2.6-flash7/12$0.0008718.6 s
  • Mit gpt-6-luna bestanden beide Specs alle zwölf Aufgaben. base kostete etwa das 2,2-Fache und dauerte etwa doppelt so lang.
  • Jev ließ jeden geprüften Schreibvorgang zu: zwölf in den Läufen mit gpt-6-luna und GLM und vier im MiMo-Lauf. Es hat nie einen Schreibvorgang blockiert, daher testen diese Läufe nicht, ob es einen schlechten stoppt.
  • Die fünf Fehler von MiMo waren Formatfehler in der SGR-Ausgabe. Es gibt keinen plain-Lauf mit MiMo, daher können wir nicht sagen, ob natives Tool Calling besser abschneiden würde.
  • Summarization lief nie. Der größte Request in den sechs aufgezeichneten Läufen (diesen vier und den zwei τ³-bench-Läufen unten) hatte 8.351 Input-Tokens, unter dem Auslöser von 12.000 Tokens. Diese Ergebnisse können nicht zeigen, ob Summarization hilft.

Ergebnisse auf τ³-bench

τ³-bench bewertet einen Lauf, indem es die Tool Calls des Agents in einer frischen Umgebung erneut abspielt, daher muss der Benchmark die Tools selbst ausführen. Der Adapter pausiert den Graph vor seinem Tools-Knoten, gibt die Tool Calls zurück und schreibt die Ergebnisse des Benchmarks zurück, bevor er fortfährt.

Ein Turn des τ³-Adapters. Der Model-Knoten läuft. Ist der Graph vor dem Tools-Knoten pausiert, gibt der Adapter die Tool Calls zurück, τ³-bench führt sie aus, und der Adapter schreibt die Ergebnisse in den Checkpoint und setzt fort. Andernfalls gibt er die Textantwort zurück.Ein Turn des τ³-Adapters. Der Model-Knoten läuft. Ist der Graph vor dem Tools-Knoten pausiert, gibt der Adapter die Tool Calls zurück, τ³-bench führt sie aus, und der Adapter schreibt die Ergebnisse in den Checkpoint und setzt fort. Andernfalls gibt er die Textantwort zurück.

Das hat zwei Folgen. Erstens stammen Prompt, Tools und Policy aus τ³-bench, daher sind diese Ergebnisse nicht mit denen der eigenen Aufgaben vergleichbar, selbst bei derselben Spec-Datei. Zweitens laufen Tool Retry und Jev hier nie: Der einzige Unterschied zwischen base und plain ist SGR.

Beide Läufe verwenden openai/gpt-6-luna für den Agent und für den simulierten Kunden. Die Simulations-Latency schließt die Arbeit des Simulators ein. Agent-Kosten und Simulations-Latency sind Mittelwerte pro Aufgabe; die Simulator-Kosten sind die Summe über alle acht Aufgaben.

SpecBestandenAgent-KostenSimulations-LatencySimulator-Kosten, gesamt
base6/8$0.0023859.0 s$0.00453
plain7/8$0.0013638.9 s$0.00471
  • Die acht Aufgaben stammen aus dem Retail-Test-Split und haben keine natürlichsprachlichen Assertions, daher entscheiden allein die Datenbankprüfungen über den Reward.
  • Beide Specs beendeten alle acht Aufgaben ohne Adapter-Fehler. Jeder Fehler war ein fehlender Schreibvorgang. base scheiterte an den Aufgaben 9 und 26. plain übergab Aufgabe 27 an einen Menschen, statt den Umtausch abzuschließen.
  • Ein zweiter base-Lauf bestand ebenfalls 6/8, scheiterte aber an den Aufgaben 9 und 17. Die fehlschlagenden Aufgaben wechseln zwischen Läufen, daher ist ein Unterschied von einer Aufgabe kein Grund, plain vorzuziehen. Diese Läufe messen diese Streuung nicht.

Die aufgezeichneten Zahlen prüfen

Die gespeicherten Reports und Traces enthalten die Ergebnisse pro Aufgabe, Token-Zahlen, Konfigurationen, Paketversionen und aufgezeichneten Preise. Für die 393 Chat-Aufrufe des Agents in den sechs aufgezeichneten Läufen stimmten die aus Tokens berechneten Kosten in jeder Antwort mit dem abgerechneten Betrag überein. Diese Prüfung schließt Jev und die Aufrufe des simulierten Kunden aus. Die Preise sind historisch, kein Angebot für Ihren Lauf.

Selbst ausführen

Installieren Sie uv und verwenden Sie die getaggte Version unten. Die Tests laufen offline, sobald die Abhängigkeiten installiert sind. Die beiden Umgebungsläufe machen kostenpflichtige Model-Aufrufe über OpenRouter und brauchen einen API-Key.

git clone https://github.com/slavadubrov/agent-harness-lab-public
cd agent-harness-lab-public
git checkout v0.1.2-a1
cp .env.example .env # set OPENROUTER_API_KEY
make test
make a1-custom SPEC=harness/spec/plain.yaml
make a1-tau3 SPEC=harness/spec/plain.yaml

Jeder Lauf ersetzt die Ergebnisse unter reports/article-a1/. Vergleichen Sie sie mit git diff mit der committeten Baseline. Das Makefile installiert aus der eingefrorenen uv.lock.

Um ein anderes Model oder ein anderes Tool-Set auszuprobieren, kopieren Sie eine Spec unter harness/spec/, ändern diese Felder und übergeben ihren Pfad über SPEC. Einstellungen für Provider und eigene Komponenten akzeptieren frei wählbare Keys, daher kann die Validierung nicht jeden verschachtelten Tippfehler erkennen.

Sie können den Entwurfsprozess aus diesem Artikel nutzen, ohne die Demo auszuführen. Die Demo macht eine Reihe von Entscheidungen überprüfbar; Ihr eigener Task Contract entscheidet, welche davon in Ihr Harness gehören.