Der nackte Agent: Was schiefgeht, wenn das Modell nicht geschützt wird.
Erstellen Sie denselben ungeschützten Reiseagenten im Claude Agent SDK und LangChain Deep Agents und beobachten Sie anschließend drei Ausfälle, um herauszufinden, welche Steuermechanismen fehlen.
Quellen: Harness Engineering: The Naked Agent: Why Your Framework Hands You a Loop, Not a Harness — I
Ein Kunde bittet einen Agenten, einen Flug umzubuchen. Das Modell klingt selbstsicher, der Tool-Aufruf ist erfolgreich, und die Antwort besagt, alles sei erledigt. Dann bemerkt jemand, dass das Datum der 32. März war.
In diesem Moment bricht die Illusion. Es mangelte dem Modell nicht an Intelligenz. Was fehlte, war alles, was damit zusammenhing – schließlich wusste kein Komponente, wann sie eingreifen sollte. Was folgt, zeigt einen „nackten“ Agenten: ohne Validatoren, ohne Speicher, ohne Wiederherstellungsmöglichkeiten, ohne Sicherheitsnetz. Sein Versagen macht sichtbar, wo Produktionsagenten Fehler machen, und zeigt, dass die Umgebung um ein Sprachmodell wichtiger ist als das Modell selbst. Derselbe vereinfachte Agent wird zweimal erstellt – mit dem Claude Agent SDK und mit LangChain Deep Agents – und versagt anschließend auf drei konkrete Arten, wobei jede Fehlerart auf eine Kontrollfunktion hinweist, die ein echter Agent bereitstellen muss. Das Ergebnis ist eine Methode, um zwischen einem Problem des Modells und einem Problem der Umgebung zu unterscheiden.
Der Vorfall am 32. März
Stellen Sie sich einen Reisebuchungsagenten vor, der die Nachricht „32. März“ von einem echten Benutzer erhält. Das Modell übermittelte diesen Text direkt an die Flug-API. Nach etwa vierzig Minuten sowie fünfzig Dollar an Modellaufrufen musste schließlich jemand einem Kunden, dem versichert worden war, alles sei geregelt, eine nicht existierende Buchung erklären.
Das Modell hat nichts erzeugt. Es hat genau das weitergegeben, was ihm gegeben wurde, denn das Framework hatte keine Möglichkeit, zu entscheiden, ob es das tun sollte. Das fasst den Fall zusammen. Die Intelligenz stammt vom Modell, die Disziplin vom Framework – daher liegt bei Fehlverhalten eines Produktionsagenten die Schuld fast immer in der Disziplinsschicht. Das Einsetzen eines stärkeren Modells oder die Verbesserung der Anweisung liefert keine Kontrolle, die ursprünglich nicht vorhanden war, und der Aufbau von Kontrollmechanismen beginnt damit, zu erkennen, was deren Fehlen kostet. Deshalb ist der Ausgangspunkt einfach: ein fragiler Agent, zwei Frameworks, kein Framework.
Beweise dafür, dass die Laufzeit dominiert
Die Benchmark-Daten stützen diese These. Forscher an der Princeton University erstarrten das Modell bei GPT-4 Turbo und änderten nur den Interface-Code, der es mit der Codebasis verbindet. Der Anteil der mit SWE-bench gelösten Probleme stieg von 3,8 % auf 12,47 % – fast das Dreifache – während die Gewichte unverändert blieben. Die Arbeit mit dem Titel „SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering“ wurde 2024 veröffentlicht und im selben Jahr bei NeurIPS angenommen. Der Gewinn wurde durch das Interface-Design erzielt, nicht durch die Intelligenz des Modells.
Die zugrundeliegende Disziplin trägt einen älteren Namen: mechanische Sympathie – das Schreiben von Software, die mit dem darunterliegenden Hardware- oder Plattformsystem zusammenarbeitet anstatt dagegen. Harness Engineering erweitert diesen Ansatz auf ein neues Substrat, das Sprachmodell, mit seinem Kontextfenster und seinem Aufmerksamkeitsbudget. Ein Limit wird vom Modell festgelegt, und Harness entscheidet darüber, wie konsequent das System diesem Limit nahekommt.
Die Veränderung der Denkweise beeinflusst, was man nach einem Fehler unternimmt:
- Früher: Nach einer falschen Antwort wird die Anfrage angepasst, es werden weitere Einschränkungen hinzugefügt, ein größeres Modell ausprobiert und darauf gehofft.
- Heute: Nach einer falschen Antwort wird ermittelt, welche Steuerung fehlt, und diese Regel wird aus der Anfrage entfernt und in die Ausführungsphase übertragen.
Anfragen überzeugen, während Mechanismen zur Durchsetzung dienen.
Der Kreislauf in jedem Framework
Sämtliche Agent-Frameworks basieren auf einem fünfstufigen Kreislauf: Die Werkzeuge werden dem Modell zur Verfügung gestellt, es erfolgt eine Rückmeldung über die Werkzeugnutzung, das Werkzeug wird ausgeführt, das Ergebnis zurückgegeben und anschließend der nächste Schritt eingeleitet. Anthropics Dokumentation zur Werkzeugnutzung, OpenAI’s Responses API sowie LangChain’s Agent-Runtime reduzieren sich letztendlich auf dasselbe Prinzip. SDKs kommen und gehen, doch die Struktur bleibt unverändert.
Die einfache Version verwendet ein rohes Provider-SDK, zwei Reise-Werkzeuge und keinerlei Sicherheitsmechanismen:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
TOOLS = [
{"name": "search_flights",
"description": "Search flights between two cities for a date.",
"input_schema": {"type": "object", "properties": {
"origin": {"type": "string"}, "destination": {"type": "string"},
"date": {"type": "string", "description": "YYYY-MM-DD"}},
"required": ["origin", "destination", "date"]}},
{"name": "book_flight",
"description": "Book a specific flight.",
"input_schema": {"type": "object", "properties": {
"flight_id": {"type": "string"}, "passenger_name": {"type": "string"}},
"required": ["flight_id", "passenger_name"]}},
]
def run_naked(user_msg: str) -> str:
messages = [{"role": "user", "content": user_msg}]
while True: # ① no iteration cap
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=messages,
)
if resp.stop_reason != "tool_use":
return resp.content[0].text
call = next(b for b in resp.content if b.type == "tool_use")
result = dispatch(call.name, call.input)
# ② direct side effect, no check
messages.extend([
# ③ whole history, every turn
{"role": "assistant", "content": resp.content},
{"role": "user", "content": [{"type": "tool_result",
"tool_use_id": call.id, "content": result}]},
])
Lesen Sie es unter Berücksichtigung von drei potenziellen Fehlern:
- Die Schleife
while True:ist unbegrenzt. Ein fehlerhafter Plan ruft das Modell weiterhin auf, bis das Budget aufgebraucht ist, da keine Iterationen gezählt werden. dispatchübermittelt die vom Modell bereitgestellten Argumente direkt an einen Nebeneffekt, ohne vorherige Überprüfung – dadurch gelangt ein Wert, der zwar korrekt interpretiert werden kann, aber nicht existieren darf, an die Live-API.- Die gesamte Nachrichtengeschichte wird bei jedem Schritt erweitert und erneut gesendet, wodurch der Kontext stillschweigend aufgebläht wird, bis die Qualität nachlässt.
Keine der Sicherheitseigenschaften, die man sich wünschen würde, befinden sich innerhalb dieses Loops. Sie liegen in noch nicht geschriebenem Code. Auch die Verwendung eines Frameworks schließt diese Lücken nicht. Das Claude Agent SDK sowie Deep Agents bieten einen besser strukturierten Loop mit einem angemessenen Tool-Protokoll und einer übersichtlichen Ausführungspfad, liefern aber standardmäßig weder Validatoren noch Speicher, über den man nachdenken könnte, noch Mechanismen zur Wiederherstellung. Der Loop ist das, was vorhanden ist, während die Verwaltung weiterhin die Aufgabe des Entwicklers bleibt.
Hier ist derselbe „nackte“ Agent in jedem Framework.
Die Version des Claude Agent SDK
Das SDK übernimmt die Verwaltung des Loops für Sie. Tools werden mit dem @tool-Decorator deklariert, als in-Prozess-MCP-Server bereitgestellt und über query() ausgeführt. Was fehlt, ist jegliche Kontrolle: Argumente werden nur gegen das Schema überprüft, nichts wird zwischen den Aufrufen gespeichert, und es gibt keine Wiederherstellungsmöglichkeiten.
import asyncio
from claude_agent_sdk import (
query, ClaudeAgentOptions, tool,
create_sdk_mcp_server, AssistantMessage, ResultMessage,
)
@tool("search_flights", "Search flights between two cities for a date.",
{"origin": str, "destination": str, "date": str})
async def search_flights(args):
# ① no check that date exists
hits = flights_api.search(**args)
return {"content": [{"type": "text", "text": str(hits)}]}
@tool("book_flight", "Book a specific flight.",
{"flight_id": str, "passenger_name": str})
async def book_flight(args):
# ② destructive, ungated
confirmation = flights_api.book(**args)
return {"content": [{"type": "text", "text": confirmation}]}
server = create_sdk_mcp_server("travel", tools=[search_flights, book_flight])
async def main():
options = ClaudeAgentOptions(
mcp_servers={"travel": server},
allowed_tools=["mcp__travel__search_flights",
"mcp__travel__book_flight"],
)
async for msg in query(prompt="Rebook this customer for March 32nd.",
options=options):
if isinstance(msg, AssistantMessage):
for b in msg.content:
if hasattr(b, "text"):
print(b.text)
elif isinstance(msg, ResultMessage):
print("done:", msg.subtype)
# ③ no state survives this run
Drei Dinge fallen auf:
search_flightsakzeptiert alles, was das Modell vorschlägt und zum Schema passt, sodass auch ungültige Datumsangaben in den Tool-Code gelangen.book_flightist zerstörerisch und wird nicht geschützt. Der Dekorator integriert es in die Schleife, ohne Prüfmechanismen oder Validatoren.- Nachdem
ResultMessageeingetroffen ist undquery()zurückgegeben hat, endet die Sitzung, und nichts aus diesem Vorgang bleibt für den nächsten Aufruf erhalten.
Das SDK liefert eine saubere Schleifenstruktur. Es hat keine Kontrolle über ungültige Datumsangaben, doppelte Abrechnungen oder Aspekte, die gespeichert werden sollten.
Die Deep Agents-Version
Deep Agents strukturiert den Loop auf andere Weise. Die herkömmlichen LangChain-Tools werden in create_deep_agent() übergeben, welches ein kompiliertes Graph zurückgibt, das mit invoke aufgerufen wird. Die Struktur bleibt gleich: Es gibt keinen Validator, und da kein Checkpointer bereitgestellt wird, vergisst das Graph alles zwischen den Aufrufen.
from langchain.tools import tool
from deepagents import create_deep_agent
@tool
def search_flights(origin: str, destination: str, date: str) -> str:
"""Search flights between two cities for a date (YYYY-MM-DD)."""
return str(flights_api.search(origin, destination, date))
# ① no date check
@tool
def book_flight(flight_id: str, passenger_name: str) -> str:
"""Book a specific flight."""
return flights_api.book(flight_id, passenger_name)
# ② ungated side effect
agent = create_deep_agent(
# ③ the loop, no controls
model="anthropic:claude-sonnet-4-6",
tools=[search_flights, book_flight],
)
result = agent.invoke({"messages": [{"role": "user",
"content": "Rebook this customer for March 32nd."}]})
print(result["messages"][-1].content)
# Ask a follow-up in a second invoke, and it starts from zero: no thread,
# no memory.
Nochmals drei Punkte:
- Der Körper von
search_flightsruft die API mit dem rohendate-String auf, und es gibt nichts, was bestätigt, dass das Datum gültig ist, bevor die Anfrage gesendet wird. book_flightführt die Buchung sofort aus, sobald das Modell sie auswählt, ohne Zwischenschritt zwischen Vorschlag und Nebeneffekt.create_deep_agentgibt ein Graph zurück, das den Loop ausführt und sonst kaum etwas. Ohne Checkpointer gibt es weder Threads noch Speicher zwischen den Aufrufen.
Drei Arten, wie ein „nackter“ Agent versagt
Zwei Frameworks boten zwei saubere Loops sowie keinerlei Disziplin – so sieht ein „nackter“ Agent im Jahr 2026 aus. Es handelt sich dabei auch nicht um einen Scheinargument. Es ist vielmehr die Form von Dutzenden Agent-Demos, die wöchentlich auf GitHub veröffentlicht werden. Unter echtem Traffic versagt er auf drei Arten, und jedes Versagen entspricht einer Kontrolle, die ein ordentliches Framework hinzufügt.
Unmögliche Eingabe. Bei der Angabe „32. März“ führt das Modell das übliche Vorgehen aus und füllt das date-Feld mit einem korrekt formatierten, aber bedeutungslosen String. Das Schema bestätigt lediglich, dass date ein String ist, kann jedoch nicht feststellen, ob der angegebene Tag tatsächlich existiert. book_flight wird anschließend mit einem imaginären Reiseplan ausgelöst, und das Modell gibt an, dass alles erfolgreich war.
Es wird book_flight mit einer erfundenen Flugnummer und einem Passagiernamen aufgerufen, und es wird „Buchung abgeschlossen.“ zurückgegeben. Die Aktion wurde ausgeführt, ohne dass im Lauf des Programms überprüft wurde, ob dies tatsächlich notwendig war.
book_flight(flight_id=”AC-PHANTOM”, passenger_name=”J. Moffatt”)
# -> “Booked.” The action fired. Nothing in the loop asked whether it should.
Der Fehler entstand aufgrund eines fehlenden Handlungsrahmens – es gab keinen Validierer, der zwischen dem von dem Modell vorgeschlagenen Vorgehen und dem tatsächlich Geschehenen stand. Die Überbrückung dieses Mangels ist Aufgabe von Tool-Verträgen und Validierern.
Übermäßiger Kontext. Die einfache Schleife sendet bei jedem Schritt die gesamte Historie erneut. Ein langer Austausch bezüglich Neubuchungen, der voller Suchergebnisse, Vertragstexte und Wiederholungsversuche ist, kann fünfzigtausend Token umfassen, ohne dass etwas davon entfernt wird. Was in der Literatur als Kontextverfall bezeichnet wird, tritt ein, und der Agent ignoriert Schritte, die er zuvor in derselben Ausführung sauber ausgeführt hat. Es wird kein Fehler ausgelöst. Der Agent wird einfach stillschweigend schlechter.
Hier fehlten Arbeitsgedächtnis und Kontextzusammenstellung, die zum Bereich Gedächtnis und dauerhafter Zustand sowie zur Kontextzusammenstellung gehören.
Abgefederte Fehler. Ein Tool gibt einen Fehlerinhalt zurück, und das Modell verfasst – häufiger, als man erwarten würde – eine höfliche Zusammenfassung, die über den Fehler hinweggeht. Eine Analyse von Produktionsvorfällen beschrieb die beteiligten Systeme als selbstbewusste Lügner: Eine Datenbank, die keine Zeilen zurückgibt, wird als faktische Antwort statt als Fehler interpretiert. Die richtige Wahl des Tools war nie das Schwierige – vielmehr geht es darum, ein Tool zu erkennen, das tatsächlich funktioniert, von einem, das überzeugend so tut, als würde es funktionieren. Hier wurden keine Rückkopplungsschleifen bei den Ergebnissen beobachtet, und deren Bereitstellung gehört zur Überwachbarkeit sowie zur Erkennung von Abweichungen.
Betrachten Sie diese Fälle als diagnostisches Muster und nicht als Liste peinlicher Situationen. In allen drei Fällen hat das Modell das getan, was Modelle tun, und das umgebende System hat es nicht erkannt. In keinem Fall handelte es sich um ein Modell, das nicht intelligent genug war.
Eine funktionierende Schleife für die Ingenieurwissenschaften
Ein vierstufiger Vorgang wiederholt sich bei der Erstellung jeder Steuerung:
- Führen Sie die Harness-Funktion sowie den von ihr gesteuerten Fehler ein.
- Implementieren Sie sie in beiden Frameworks als fokussierten Codeausschnitt.
- Triggern Sie den Fehler, um zu beweisen, dass die Steuerung Last trägt.
- Prüfen Sie, wie der Harness reagiert.
In den obigen Abschnitten wurde dieser Vorgang dreimal durchgeführt, ohne ihn zu benennen: einen ungeschützten Schnitt erkennen, den dadurch verursachten Fehler auslösen und die darauf verweisende Steuerung benennen. Anschließend werden die Schnitte nacheinander geschlossen.
Probieren Sie es selbst aus
Die Problematik lässt sich bereits ohne weiteres Lesen erkennen. In einem Editor zeigen einige schnelle Experimente dies:
- Weisen Sie in einem der Frameworks den „nackten“ Agenten auf eine Anweisung hin, den Kunden für den 32. März umzubuchen, und beobachten Sie, wie das unmögliche Datum in die Funktion
book_flightgelangt.
book_flight {"is_error": true, ...} zurückgeben und überprüfen Sie, ob die endgültige Zusammenfassung den Fehler berücksichtigt oder ihn überspringt.claude-agent-sdk sowie eine Version von deepagents oder langchain aus, da beide APIs zwischen Versionen wechseln, und betrachten Sie die Modellnamen (claude-sonnet-4-6, anthropic:claude-sonnet-4-6) als Ersatz für das aktuell verwendete Modell.Jedes Framework bietet einen ordentlichen, gut gestalteten Loop – keines davon bietet jedoch ein „Harness“. Das ist eine Eigenschaft der Situation und kein Mangel der Tools; hier liegt die eigentliche Arbeit. Claude Code, OpenCode, Codex und Deep Agents zählen alle zu Agent-Harnesses, und ein Entwickler, der mit ihnen Schwierigkeiten hat, unterscheidet sich von dem, der zuverlässige Ergebnisse erzielt, indem er die Kontrollmechanismen entwickelt, die der Loop weglässt. Das Modell war bereits seit längerer Zeit der einfachere Teil. Zuverlässigkeit entsteht durch Disziplin, die darauf aufbaut, durch wiederholte Misserfolge, und der erste natürliche Schritt ist die Aktionsgrenze – die erste Regel, um den Prompt zu verlassen und im Laufzeitumfeld zu agieren.