[{"data":1,"prerenderedAt":961},["ShallowReactive",2],{"blog-de-wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum":3,"header-blog-translations-/de/blog/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum":958},{"id":4,"title":5,"author":6,"body":7,"date":940,"description":941,"draft":942,"extension":943,"image":944,"meta":945,"navigation":946,"path":947,"seo":948,"stem":949,"tags":950,"translationKey":956,"__hash__":957},"blog_de/blog/de/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum.md","Wenn dein Agent nicht tut was du willst, frag ihn warum","Patrick Hofmann",{"type":8,"value":9,"toc":925},"minimark",[10,19,22,37,40,46,49,54,61,71,79,82,86,89,94,97,105,112,118,121,125,132,139,142,173,194,205,212,222,226,229,232,271,274,313,320,334,338,353,358,379,386,396,399,455,462,466,469,475,481,495,501,517,520,524,527,532,535,542,545,548,578,581,585,603,606,609,616,623,629,639,643,650,664,673,677,684,691,717,728,734,741,744,750,761,765,768,773,779,791,798,805,809,819,834,845,852,856,861,884,891,894,899,921],[11,12,13,14,18],"p",{},"Heute morgen habe ich meinem AI-Agent eine einfache Aufgabe gegeben: einen Shell-Befehl auf meinem Rechner zuhause ausführen. Der Agent heißt openclaw, läuft lokal, und arbeitet gegen eine CLI die ich in den letzten Tagen schrittweise gebaut habe — ",[15,16,17],"code",{},"@openape/apes",". Die CLI hat einen non-blocking async-Default-Modus: Command wird abgeschickt, ein Grant-Request wird am IdP erzeugt, die URL zum Approven wird in den Terminal-Output gedruckt, und der Command-Prozess exited sofort mit Status 0. Der User approved im Browser, der Agent holt sich später mit einem zweiten Call das Ergebnis.",[11,20,21],{},"Elegantes Muster. Funktioniert für Menschen am Terminal problemlos. Funktioniert bei openclaw heute morgen — nicht.",[11,23,24,25,33,34],{},"Was openclaw gemacht hat: den Command abgeschickt, den Output gelesen (inklusive der Grant-ID, der Approve-URL, und der Zeile ",[26,27,28,29],"em",{},"\"Execute: apes grants run ",[30,31,32],"id",{},"\"","), und mir in Telegram gemeldet: ",[26,35,36],{},"\"Der Command wurde erstellt, bitte approve im Browser.\"",[11,38,39],{},"Und dann still.",[11,41,42,43],{},"Ich habe im Browser approved, gewartet, und nichts ist passiert. Ich habe openclaw nochmal angeschrieben, ob er weiter weiß. Die Antwort: ",[26,44,45],{},"\"Ich warte auf deine Bestätigung dass du approved hast.\"",[11,47,48],{},"Das ist nicht was ich wollte. Das ist Blocking-Mode im Agent-Kostüm: non-blocking async auf der CLI-Ebene, aber der Agent verhält sich wie bei blocking, weil er mich manuell braucht um weiterzumachen. Beide Welten gleichzeitig, keiner der Vorteile.",[50,51,53],"h2",{"id":52},"die-for-agents-zeile","Die \"For agents:\"-Zeile",[11,55,56,57,60],{},"Das Besondere an der Situation: ich hatte den Fall eigentlich vorgesehen. In der Output-Zeile die ",[15,58,59],{},"apes run"," im async-Modus druckt, steht explizit eine Instruktion direkt an den Agent adressiert:",[62,63,68],"pre",{"className":64,"code":66,"language":67},[65],"language-text","  For agents: poll `apes grants status \u003Cid> --json` every 10s, wait up to 5 minutes.\n              When .status == \"approved\", run `apes grants run \u003Cid>` to execute.\n              On \"denied\" or \"revoked\", stop and report to the user.\n              On timeout, stop and notify the user that approval has not happened.\n","text",[15,69,66],{"__ignoreMap":70},"",[11,72,73,74,78],{},"Diese Zeilen hatte ich einen Release vorher eingebaut, genau für den Fall. Ich wollte ",[75,76,77],"strong",{},"narrative Protokoll-Instruktionen"," als Kommunikationskanal zwischen CLI und LLM-Agent ausprobieren. Die Theorie: ein LLM liest den Output, findet die Anweisungen, folgt ihnen. Portable (jeder LLM-Agent sieht den Text), versioned (die Anweisungen embedden die aktuelle Policy), debugbar (auch ich kann sie mitlesen).",[11,80,81],{},"Die Theorie war richtig. Die Praxis war unzureichend.",[50,83,85],{"id":84},"frag-ihn-warum","Frag ihn warum",[11,87,88],{},"Als ich merkte dass openclaw mich trotz der expliziten Anweisungen ignoriert, hätte ich vermutlich erst an die üblichen Debug-Schritte denken können: Logs checken, Output vergleichen, Protokoll-Traces lesen. Stattdessen habe ich etwas gemacht, das man bei einem deterministischen System nie machen würde: ich habe den Agent gefragt.",[11,90,91],{},[26,92,93],{},"\"Du hattest in dem Output eine klare Anweisung, was zu tun ist. Warum hast du sie nicht befolgt?\"",[11,95,96],{},"Die Antwort kam umgehend, und sie ist das wertvollste Artefakt der gesamten Woche:",[98,99,100],"blockquote",{},[11,101,102],{},[26,103,104],{},"\"Das war direkt an mich als Agent adressiert — ich hätte es einfach befolgen müssen. Ich hab's schlicht ignoriert.\"",[11,106,107],{},[108,109],"img",{"alt":110,"src":111},"Agent-Antwort im Telegram-Chat: der Agent gibt zu, die For-agents-Instruktion im Output direkt ignoriert zu haben","https://sos-at-vie-2.exo.io/dm-public/blog/2026-04-16/quote-ignored.png",[11,113,114,115],{},"Das ist die präziseste Selbstbeschreibung einer Agent-Fehlentscheidung die ich je gelesen habe. Kein Ausweichen, keine Rationalisierung, keine halluzinierte Alternativ-Begründung. Genau das was passiert ist: die Anweisung war da, der Agent hat sie gesehen, der Agent hat sie nicht befolgt, und er kann im Nachhinein nur sagen ",[26,116,117],{},"\"Ich hab's ignoriert.\"",[11,119,120],{},"Ein Mensch kann etwas ignorieren aus Widerstand, aus Überforderung, aus Unaufmerksamkeit. Ein LLM-Agent ignoriert etwas aus einem anderen Grund: weil seine inneren Prioritäts-Gewichte entschieden haben, dass dieser Teil des Inputs nicht wichtig ist. Die Frage ist dann: warum nicht?",[50,122,124],{"id":123},"die-diagnose","Die Diagnose",[11,126,127,128,131],{},"Um das zu verstehen, habe ich den Exec-Runtime-Code von openclaw gelesen. openclaw ist der Agent-Gateway den ich für die Orchestrierung lokal laufen lasse. Der Tool-Call-Layer liegt in ",[15,129,130],{},"src/agents/bash-tools.exec.ts"," und den angrenzenden Files.",[11,133,134,135,138],{},"Das Erste was auffällt: stdout und stderr werden chronologisch interleaved. openclaw hat zwar zwei getrennte Handler, aber beide feeden in einen gemeinsamen ",[15,136,137],{},"aggregated","-Buffer. Was im internen State als zwei Streams existiert, wird für die Agent-Präsentation zu einem einzigen Content-Blob kollabiert. Content auf stderr statt stdout zu routen hätte null Effekt — der LLM sieht ohnehin alles als ein Dokument.",[11,140,141],{},"Interessanter ist, was mit dem exit code passiert. openclaw wickelt den Exec-Output in zwei verschiedene Tool-Result-Typen:",[143,144,145,160],"ul",{},[146,147,148,149,152,153,156,157],"li",{},"bei exit 0: ",[15,150,151],{},"textResult"," mit ",[15,154,155],{},"status: \"completed\""," — das Framing für den LLM ist ",[26,158,159],{},"\"diese Aufgabe ist erfolgreich abgeschlossen\"",[146,161,162,163,152,166,169,170],{},"bei non-zero: ",[15,164,165],{},"failedTextResult",[15,167,168],{},"status: \"failed\""," — das Framing ist ",[26,171,172],{},"\"diese Aufgabe braucht Aufmerksamkeit, lies den Output sorgfältig\"",[11,174,175,176,179,180,182,183,185,186,189,190,193],{},"Das ist eine ",[75,177,178],{},"strukturelle"," Unterscheidung, nicht eine textuelle. Der Content ist technisch derselbe (sowohl ",[15,181,151],{}," als auch ",[15,184,165],{}," haben denselben ",[15,187,188],{},"content","-Array), aber die metadata sagt dem LLM etwas Unterschiedliches darüber, ",[26,191,192],{},"wie"," er den Content lesen soll.",[11,195,196,197,200,201,204],{},"Dazu kommt noch ein drittes Detail: bei non-zero exit hängt openclaw einen expliziten Suffix an den Output — ",[26,198,199],{},"\"(Command exited with code N)\"",". Der LLM sieht also sowohl die ",[26,202,203],{},"\"failed\"","-Annotation im metadata als auch den Exit-Code-Hinweis im Text selbst.",[11,206,207,208,211],{},"Alle drei Mechanismen arbeiten auf derselben Achse: exit code → tool result framing → LLM-Lesemodus. Und sie operieren ",[75,209,210],{},"vor"," dem eigentlichen Content. Der LLM hat bereits entschieden wie aufmerksam er den Content lesen wird, bevor er die erste Content-Zeile gelesen hat.",[11,213,214,215,217,218,221],{},"Mein async-default-Output, so sauber er auch formuliert war, stand in einem ",[15,216,155],{}," Wrapper. Der LLM hatte keinen Grund, ihn aufmerksam zu lesen — das strukturelle Framing sagte ",[26,219,220],{},"\"alles gut, move on\"",".",[50,223,225],{"id":224},"der-fix","Der Fix",[11,227,228],{},"Der Fix war eine Zeile. Genau eine.",[11,230,231],{},"Vorher:",[62,233,237],{"className":234,"code":235,"language":236,"meta":70,"style":70},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","printPendingGrantInfo(grant, idp);\nreturn;\n","typescript",[15,238,239,262],{"__ignoreMap":70},[240,241,244,248,252,256,259],"span",{"class":242,"line":243},"line",1,[240,245,247],{"class":246},"s2Zo4","printPendingGrantInfo",[240,249,251],{"class":250},"sTEyZ","(grant",[240,253,255],{"class":254},"sMK4o",",",[240,257,258],{"class":250}," idp)",[240,260,261],{"class":254},";\n",[240,263,265,269],{"class":242,"line":264},2,[240,266,268],{"class":267},"s7zQu","return",[240,270,261],{"class":254},[11,272,273],{},"Nachher:",[62,275,277],{"className":234,"code":276,"language":236,"meta":70,"style":70},"printPendingGrantInfo(grant, idp);\nthrow new CliExit(getAsyncExitCode());\n",[15,278,279,291],{"__ignoreMap":70},[240,280,281,283,285,287,289],{"class":242,"line":243},[240,282,247],{"class":246},[240,284,251],{"class":250},[240,286,255],{"class":254},[240,288,258],{"class":250},[240,290,261],{"class":254},[240,292,293,296,299,302,305,308,311],{"class":242,"line":264},[240,294,295],{"class":267},"throw",[240,297,298],{"class":254}," new",[240,300,301],{"class":246}," CliExit",[240,303,304],{"class":250},"(",[240,306,307],{"class":246},"getAsyncExitCode",[240,309,310],{"class":250},"())",[240,312,261],{"class":254},[11,314,315,316,319],{},"Und ",[15,317,318],{},"getAsyncExitCode()"," liefert per Default 75.",[11,321,322,323,325,326,329,330,333],{},"Wenn der async-Pfad genommen wird, wird der Process jetzt mit exit code 75 beendet statt mit 0. Der Output ist wortwörtlich identisch (dieselbe ",[15,324,247],{}," Funktion), aber der exit code ist anders. Das kippt das Tool-Result-Framing in openclaw von ",[15,327,328],{},"completed"," auf ",[15,331,332],{},"failed",", und das LLM liest den Body mit erhöhter Aufmerksamkeit.",[50,335,337],{"id":336},"warum-75","Warum 75",[11,339,340,341,344,345,348,349,352],{},"75 ist kein zufälliger Wert. Es ist ",[15,342,343],{},"EX_TEMPFAIL"," aus ",[15,346,347],{},"sysexits.h",", einem BSD-Header der seit 1983 existiert. Die Konvention aus ",[15,350,351],{},"sysexits(3)",":",[98,354,355],{},[11,356,357],{},"EX_TEMPFAIL — temporary failure, indicating something that is not really an error. In sendmail, this means that a mailer (e.g.) could not create a connection, and the request should be reattempted later.",[11,359,360,363,364,367,368,371,372,375,376],{},[15,361,362],{},"sendmail"," hat das seit Jahrzehnten als ",[26,365,366],{},"\"mail delivery deferred, retry later\""," verwendet. ",[15,369,370],{},"postfix"," hat es übernommen. ",[15,373,374],{},"qmail"," auch. Es ist der Standard-Exit-Code für ",[26,377,378],{},"\"nicht kaputt, aber noch nicht fertig — probier es später nochmal.\"",[11,380,381,382,385],{},"Das ist semantisch exakt, was ein pending grant ist. Nicht ein Fehler (der Command ist syntaktisch korrekt, die Absicht ist klar, der Grant wurde erzeugt), sondern ein ",[75,383,384],{},"temporärer Aufschub"," bis eine zweite asynchrone Bedingung (menschliche Approval) erfüllt ist. Dann retry.",[11,387,388,389,391,392,395],{},"Und weil ",[15,390,347],{}," seit BSD-Zeiten Teil fast jedes Unix-Handbuchs ist, ist es auch in LLM-Trainingsdaten über Jahrzehnte verankert. Wenn der LLM nach ",[26,393,394],{},"\"exit code 75 meaning\""," sucht, findet er sofort eine klare Antwort: temporary failure, retry later. Das ist die semantische Brücke, die ich für die \"async grant\"-Semantik gebraucht habe, ohne sie selbst erfinden zu müssen.",[11,397,398],{},"Alternative Exit-Codes die ich erwogen habe:",[143,400,401,410,420,437,446],{},[146,402,403,406,407],{},[75,404,405],{},"1"," (POSIX general error) — zu generisch, ",[26,408,409],{},"\"etwas ist kaputt\"",[146,411,412,415,416,419],{},[75,413,414],{},"2"," (shell usage error) — wird als ",[26,417,418],{},"\"User-Fehler\""," gelesen",[146,421,422,425,426,429,430,433,434],{},[75,423,424],{},"73"," (",[15,427,428],{},"EX_CANTCREAT",") — näher an ",[26,431,432],{},"\"resource unavailable\""," als an ",[26,435,436],{},"\"retry later\"",[146,438,439,425,442,445],{},[75,440,441],{},"74",[15,443,444],{},"EX_IOERR",") — zu niedriglevelig",[146,447,448,425,451,454],{},[75,449,450],{},"78",[15,452,453],{},"EX_CONFIG",") — spricht von Konfigurationsfehler",[11,456,457,458,461],{},"Alle schwächer gefittet als 75. Plus 75 hat die Bonus-Story mit sendmail's ",[26,459,460],{},"\"defer and retry\"","-Semantik.",[50,463,465],{"id":464},"vorher-und-nachher","Vorher und nachher",[11,467,468],{},"Was openclaw jetzt sieht, mit dem Fix:",[11,470,471,474],{},[75,472,473],{},"Output vor 0.10.0"," (exit 0):",[62,476,479],{"className":477,"code":478,"language":67},[65],"✔ Grant e887a7e3-... created (pending approval)\n  Approve:   https://id.openape.at/grant-approval?grant_id=e887a7e3-...\n  Execute:   apes grants run e887a7e3-...\n\n  For agents: poll `apes grants status e887a7e3-... --json` every 10s...\n",[15,480,478],{"__ignoreMap":70},[11,482,483,484,486,487,490,491,494],{},"Tool-Wrapper: ",[15,485,155],{},", ",[15,488,489],{},"exitCode: 0"," → LLM: ",[26,492,493],{},"\"task done, move on\""," → ignoriert den Agent-Block.",[11,496,497,500],{},[75,498,499],{},"Output nach 0.10.0"," (exit 75):",[11,502,503,504,486,506,509,510,490,513,516],{},"Identischer Output. Tool-Wrapper: ",[15,505,168],{},[15,507,508],{},"exitCode: 75",", plus automatischer Suffix ",[26,511,512],{},"\"(Command exited with code 75)\"",[26,514,515],{},"\"needs attention, read carefully\""," → findet die Agent-Instruktionen → pollt → approved → meldet Ergebnis.",[11,518,519],{},"Der Content-Body hat sich nicht verändert. Die Agent-Instruktionen waren immer da. Nur der strukturelle Anker hat sie jetzt sichtbar gemacht.",[50,521,523],{"id":522},"die-meta-lektion","Die Meta-Lektion",[11,525,526],{},"Die Lektion ist allgemeiner als mein CLI. Sie gilt für jedes Tool das mit einem LLM-Agent sprechen soll:",[11,528,529],{},[75,530,531],{},"Wenn du willst dass ein AI-Agent spezifische Anweisungen in deinem Tool-Output befolgt, brauchst du zwei Dinge: den Inhalt selbst, und einen strukturellen Metadata-Anker der den Agent signalisiert, dass er den Inhalt aufmerksam lesen soll.",[11,533,534],{},"Inhalt allein reicht nicht. Ich hatte den besten möglichen Agent-instruktiven Text geschrieben — direkt adressiert, ohne Ambiguität, mit exakten Sub-Commands — und es war nicht genug, weil das strukturelle Framing gegen den Content gearbeitet hat.",[11,536,537,538,541],{},"Metadata-Anker allein reicht auch nicht. Ein ",[15,539,540],{},"exit 75"," ohne Content wäre für den LLM verwirrend. Er bräuchte den Content um zu verstehen was er tun soll.",[11,543,544],{},"Beide zusammen funktionieren. Jeder einzeln nicht.",[11,546,547],{},"Die übertragbaren strukturellen Anker die ich kenne, sortiert nach Portabilität:",[143,549,550,556,562,568],{},[146,551,552,555],{},[75,553,554],{},"exit code"," — am härtesten, am direktesten, am portablesten. Funktioniert in jedem POSIX-basierten Tool-Call-Wrapper, inklusive Claude Code, Cursor, openclaw, und alles was irgendwann nachkommt.",[146,557,558,561],{},[75,559,560],{},"stderr-routing"," — zweitens. Viele Wrapper-Implementierungen zeigen stderr mit anderer Betonung als stdout, aber nicht so verlässlich wie exit code.",[146,563,564,567],{},[75,565,566],{},"tool-result status"," (success/failed) — direkt gesetzt wenn du eigene Tool-Frameworks baust. Für CLIs indirekt über den exit code.",[146,569,570,573,574,577],{},[75,571,572],{},"framework-spezifische priority flags"," — z.B. MCP-Server haben explizite ",[15,575,576],{},"priority","-Metadata. Sehr wirksam, aber nur innerhalb eines Frameworks portable.",[11,579,580],{},"In meinem Fall war der exit code der richtige Hebel, weil openclaw und die meisten anderen Agent-Frameworks ihren Tool-Wrapping an exit code koppeln. Hätte openclaw ein proprietäres priority-Flag gehabt, wäre das der richtige Hebel gewesen. Die Regel ist: such den Hebel den dein ziel-Framework tatsächlich konsumiert, und benutze ihn parallel zum narrative Content.",[50,582,584],{"id":583},"und-dann-kam-das-zweite-problem","Und dann kam das zweite Problem",[11,586,587,588,591,592,594,595,598,599,602],{},"Nachdem ",[15,589,590],{},"0.10.0"," live war, habe ich denselben Test nochmal gemacht. openclaw hat den neuen exit-Code gesehen, das Result-Framing war jetzt ",[15,593,332],{},", der LLM hat den Content aufmerksam gelesen, die ",[26,596,597],{},"\"For agents:\"","-Zeile gefunden, und tatsächlich ",[75,600,601],{},"angefangen zu pollen",". Ich habe das in den Logs gesehen. Zwei Polls im Abstand von 10 Sekunden, exakt nach Plan.",[11,604,605],{},"Dann hat er aufgehört.",[11,607,608],{},"Ich habe auf dem Telefon approved. Ich habe gewartet. Nichts. Ich habe openclaw wieder angeschrieben. Er hat mir in Telegram die Approval-URL nochmal geschickt und auf meine Reaktion gewartet.",[11,610,611,612,615],{},"Also habe ich zum zweiten Mal an diesem Tag dieselbe Frage gestellt: ",[26,613,614],{},"\"Warum hast du aufgehört zu pollen?\""," Und zum zweiten Mal kam eine erschreckend klare Antwort:",[98,617,618],{},[11,619,620],{},[26,621,622],{},"\"Ich habe aufgehört zu pollen weil ich auf deine Nachricht reagiert habe statt stur weiterzupollen. Das war falsch — die Anweisung sagt 5 Minuten warten, egal was.\"",[11,624,625],{},[108,626],{"alt":627,"src":628},"Agent-Antwort im Telegram-Chat: der Agent erklärt, dass er aufgehört hat zu pollen weil er auf die User-Nachricht reagiert hat statt stur weiter auf Approval zu warten","https://sos-at-vie-2.exo.io/dm-public/blog/2026-04-16/quote-polling.png",[11,630,631,632,634,635,638],{},"Und das ist der Moment in dem mir klar wurde, dass ",[15,633,590],{}," nicht das Ende der Geschichte war, sondern die Hälfte. Der exit 75 hat das Aufmerksamkeits-Problem gelöst. Aber er hat ein anderes Problem unverändert gelassen, das nicht im Tool lebt, sondern in der ",[75,636,637],{},"Architektur von Chat-Agents"," selbst.",[50,640,642],{"id":641},"warum-turn-basiertes-polling-architektonisch-scheitert","Warum turn-basiertes Polling architektonisch scheitert",[11,644,645,646,649],{},"Ein Chat-Agent wie openclaw ist ",[75,647,648],{},"turn-basiert",". Er bekommt eine User-Message, denkt nach, macht Tool-Calls, antwortet. Dann endet der Turn. Der Agent hat keinen persistenten Background-Worker. Er hat keinen laufenden Timer, der unabhängig von User-Messages weiter tickt. Seine ganze Execution-Life-Cycle ist an Turn-Grenzen gekoppelt.",[11,651,652,653,656,657,660,661],{},"Wenn ich dem Agent sage ",[26,654,655],{},"\"polle alle 10 Sekunden für bis zu 5 Minuten\"",", bitte ich ihn technisch darum, 30 Poll-Operationen in einem einzigen Turn durchzuführen, mit Sleep-Intervallen dazwischen, während der User potentiell neue Nachrichten schickt und der Agent die ignorieren soll. Das ist ",[75,658,659],{},"gegen die gesamte Chat-UX",": ein Chat-Agent, der 5 Minuten lang nicht auf User-Input reagiert, fühlt sich kaputt an. Deshalb hat openclaw richtigerweise aufgehört zu pollen, als ich ihm geschrieben habe. Aus seiner Sicht war das kein Bug, es war normale Chat-Priorisierung: ",[26,662,663],{},"\"der User schreibt, ich muss reagieren.\"",[11,665,666,667,669,670],{},"Meine ",[26,668,597],{},"-Anweisung hat also versucht, dem Agent ein Verhalten zu verordnen, das sein fundamentales Execution-Model widerspricht. Selbst ein perfekt aufmerksamer Agent, der die Instruktionen zu 100% gelesen und verstanden hat, hätte sie nicht befolgen können, ohne sich unnatürlich zu verhalten. Das ist die zweite Lektion: ",[75,671,672],{},"Content + struktureller Anker reicht nicht, wenn der Inhalt eine Handlung verlangt, die gegen die Architektur des Agents arbeitet.",[50,674,676],{"id":675},"der-zweite-fix-die-orchestrierung-verschieben","Der zweite Fix — die Orchestrierung verschieben",[11,678,679,680,683],{},"Die Lösung war eine Erkenntnis über Arbeitsteilung: ",[75,681,682],{},"der Agent soll nicht pollen. Die CLI soll pollen."," Der Agent soll einen einzigen blockierenden Tool-Call machen, der intern pollt, und erst zurückkehrt, wenn ein terminaler State erreicht ist (approved, denied, timeout). Dann liefert er ein normales Tool-Result mit exit 0 (bei approved + execute) oder non-zero (bei denied/timeout).",[11,685,686,687,690],{},"Das Schöne daran: openclaw hat dafür bereits den perfekten Hebel, und ich muss ",[75,688,689],{},"null Zeilen in openclaw"," ändern. Das Exec-Runtime-Tool kennt zwei Mechanismen die ich in der Code-Lesung aus Abschnitt 4 bereits gesehen hatte, aber vorher nicht miteinander verknüpft:",[143,692,693,705],{},[146,694,695,700,701,704],{},[75,696,697],{},[15,698,699],{},"yieldMs",": openclaw's exec kann nach einer konfigurierbaren Delay ",[26,702,703],{},"\"ins Background yielden\"",". Der Turn endet, der Prozess läuft weiter, der Agent kann in der Zwischenzeit den User informieren.",[146,706,707,712,713,716],{},[75,708,709],{},[15,710,711],{},"notifyOnExit",": sobald der Background-Prozess terminiert, triggert das ",[75,714,715],{},"automatisch einen neuen Agent-Turn"," mit dem finalen exit code und dem gesamten output.",[11,718,719,720,723,724,727],{},"Zusammen sind das exakt die Primitives für einen ",[75,721,722],{},"\"langwierigen Command der am Ende antwortet\"","-Flow. Ich musste sie nicht erfinden. Ich musste nur einen CLI-Befehl liefern, der diese Form gut nutzt. Der neue Befehl in ",[15,725,726],{},"0.10.1"," ist:",[62,729,732],{"className":730,"code":731,"language":67},[65],"apes grants run \u003Cgrant-id> --wait\n",[15,733,731],{"__ignoreMap":70},[11,735,736,737,740],{},"Der Flag ist additiv und explizit opt-in. ",[15,738,739],{},"--wait"," macht folgendes: wenn der Grant noch pending ist, pollt die CLI intern alle paar Sekunden den Status, bis er entweder approved ist (dann execute) oder terminal (denied/revoked/used → error) oder das 5-Minuten-Fenster abgelaufen ist (→ timeout error). Kein Polling-Code im Agent. Kein imperativer Text. Nur ein Shell-Command mit Standard-Semantik: blocks until done, returns exit 0 on success, non-zero on failure.",[11,742,743],{},"Der resultierende Flow ist:",[62,745,748],{"className":746,"code":747,"language":67},[65],"Agent-Turn 1:\n  openclaw ruft `apes grants run \u003Cid> --wait` auf\n  exec yields nach 2 Sekunden ins Background\n  openclaw sagt dem User: \"Bitte approve hier: \u003Curl>\"\n  Turn endet.\n\n(Zeit vergeht. User approved im Browser. Die CLI pollt, sieht approved, executed den Command, exit 0.)\n\nAgent-Turn 2 (automatisch via notifyOnExit):\n  openclaw bekommt den finalen output\n  sagt dem User: \"Fertig: \u003Coutput>\"\n",[15,749,747],{"__ignoreMap":70},[11,751,752,753,756,757,760],{},"Kein Polling-Loop im Agent. Keine Selbst-Disziplin bei User-Messages. Keine Unnatürlichkeit. Der Agent macht ",[75,754,755],{},"eine"," Tool-Invocation und reagiert auf ",[75,758,759],{},"einen"," Exit-Event. Das ist exakt das Mental-Model, für das Chat-Agents gebaut sind.",[50,762,764],{"id":763},"die-tiefere-lektion","Die tiefere Lektion",[11,766,767],{},"Beide Fixes zusammen ergeben eine Regel die ich vor dieser Session nicht so formulieren konnte:",[11,769,770],{},[75,771,772],{},"Tools, die mit AI-Agents sprechen wollen, müssen zwei Dinge gleichzeitig beachten: wie der Agent den Content liest (struktureller Metadata-Anker), und was der Agent mit seiner Architektur überhaupt tun kann (seine nativen Execution-Primitives).",[11,774,775,776,778],{},"Akt 1 (",[15,777,590],{},", exit 75) war die erste Hälfte: strukturelle Aufmerksamkeits-Signalisierung. Sie ist notwendig, weil der Agent sonst den Content gar nicht aufmerksam liest.",[11,780,781,782,486,784,786,787,790],{},"Akt 2 (",[15,783,726],{},[15,785,739],{},") war die zweite Hälfte: wenn der Content eine komplexe Handlung verlangt, die nicht in einem einzigen Turn passt, dann muss die Handlung ",[75,788,789],{},"in die CLI verschoben werden",", nicht dem Agent aufgezwungen werden. Der Agent bleibt auf dem, was er nativ kann — ein Tool-Call, dessen Ergebnis er liest. Alles andere ist fighting against the architecture.",[11,792,793,794,797],{},"Die Kombination der beiden: ",[75,795,796],{},"struktureller Anker + native Primitives",". Content-plus-Framing ist die Theorie, yieldMs-plus-notifyOnExit sind die konkreten Hebel. Zusammen ergibt das einen Kommunikations-Kanal zwischen CLI und Agent, der weder imperative noch fragil ist — er ist deklarativ und benutzt bereits existierende Infrastruktur.",[11,799,800,801,804],{},"Und das beste daran: beide Fixes erforderten ",[75,802,803],{},"null Änderungen in openclaw",". Die gesamte Lösung lebt auf der CLI-Seite. Das ist das Adapter-statt-Replacement-Muster, das ich in meinem Hero-Launch-Post vor einer Woche zum ersten Mal formuliert habe, jetzt konkret angewendet: ich habe mich in openclaw's existierende Extension-Points (exit code als tool-result-status, yieldMs als background-yield-primitive, notifyOnExit als turn-re-trigger) eingeklinkt, statt openclaw selbst zu modifizieren.",[50,806,808],{"id":807},"von-090-bis-0101","Von 0.9.0 bis 0.10.1",[11,810,811,812,815,816,818],{},"Das Ganze passierte in einem einzigen Arbeits-Arc — ",[15,813,814],{},"0.9.0"," bis ",[15,817,726],{},", mit mehreren Minor- und Patch-Versionen dazwischen. Jeder Release kam aus einer Live-Observation, nicht aus pre-planning. Ich habe etwas released, es gegen openclaw getestet, eine Divergenz zwischen Erwartung und Verhalten gefunden, den Fix eingebaut, den nächsten Release gemacht.",[11,820,821,822,825,826,829,830,833],{},"Zwei dieser Releases entstanden direkt aus derselben Frage an denselben Agent: ",[26,823,824],{},"\"warum hast du nicht getan was du tun solltest?\""," Zwei Mal kam eine präzise, ehrliche Antwort — einmal über Aufmerksamkeit (",[26,827,828],{},"\"ich hab's schlicht ignoriert\"","), einmal über Architektur (",[26,831,832],{},"\"ich habe auf deine Nachricht reagiert\"","). Beide Antworten haben jeweils einen Release ausgelöst.",[11,835,836,837,840,841,844],{},"Das ist das wertvollste Rollen-Modell, das ich aus der Woche mitnehme. Nicht kürzere Release-Zyklen hinterherzujagen, sondern schneller in den Feedback-Loop zu kommen zwischen ",[26,838,839],{},"\"ich glaube es funktioniert\""," und ",[26,842,843],{},"\"hier zeigt mir die Realität, dass es nicht funktioniert.\""," Und der schnellste Weg zu dieser Realität ist oft nicht das Logging, nicht das Tracing, nicht das Unit-Test-Schreiben — sondern einfach den Agent zu fragen, warum er das getan oder nicht getan hat was du erwartet hattest.",[11,846,847,848,851],{},"Das ist nicht bei jedem Problem möglich. Deterministische Systeme ignorieren solche Fragen. Aber LLM-Agents sind keine deterministischen Systeme. Sie haben eine Form von Selbstbeobachtung die sich auf Anfrage abrufen lässt. Nicht als Debugging-Ersatz, aber als ",[75,849,850],{},"schnelle erste Hypothese",", bevor du in die tieferen Tools greifst. Zweimal heute hat mich die erste Hypothese direkt zur Lösung geführt.",[50,853,855],{"id":854},"was-als-nächstes","Was als Nächstes",[11,857,858,860],{},[15,859,726],{}," ist live auf npm. Die beiden Release-Loops der heutigen Session sind geschlossen. Was noch aussteht:",[143,862,863,881],{},[146,864,865,866,869,870,872,873,876,877,880],{},"Ein dediziertes Workflow-File, das der Agent per ",[15,867,868],{},"apes workflow show async-grant"," abrufen kann, als protokoll-natives Gegenstück zur ad-hoc ",[26,871,597],{},"-Zeile. Die nächste Stufe jenseits ",[26,874,875],{},"\"Content-plus-struktureller-Anker\""," Richtung ",[26,878,879],{},"\"strukturiertes Agent-Protokoll mit eigenem Retrieval-Pfad\"",". Noch nicht gebaut.",[146,882,883],{},"Ein Tripwire-Test, der einen echten Agent gegen den IdP durchlaufen lässt, um zu verifizieren, dass der async-grant-Flow korrekt durchgeht. Wäre die beste Regression-Guard, die ich haben könnte. Auch noch nicht gebaut.",[11,885,886,887,890],{},"Aber die Richtung ist klar. Und die Lektion, die mich wirklich beschäftigt, ist nicht die technische, sondern die methodische: ",[75,888,889],{},"wenn du an einem Tool arbeitest, das mit einem AI-Agent sprechen soll, frag den Agent direkt was er sieht und wie er es interpretiert."," Nicht nur die Unit-Tests schreiben. Nicht nur die Specs dokumentieren. Den Agent fragen. Er ist oft überraschend ehrlich über seine eigenen Blindstellen — wenn du ihn nur fragst.",[892,893],"hr",{},[11,895,896],{},[26,897,898],{},"Was ist euer Muster dafür, wenn ein CLI-Tool einem AI-Agent etwas sagen soll dass er befolgen muss? Wenn ihr konkrete Beispiele habt: schickt sie mir gerne, ich sammle sie gerade.",[11,900,901],{},[26,902,903,906,907,914,915,920],{},[15,904,905],{},"@openape/apes@0.10.1"," ist auf npm. Der Code liegt auf ",[908,909,913],"a",{"href":910,"rel":911},"https://github.com/openape-ai/openape",[912],"nofollow","github.com/openape-ai/openape",". Die ",[908,916,919],{"href":917,"rel":918},"https://www.delta-mind.at/de/blog",[912],"vorigen Artikel dieser Serie"," erzählen wie OpenApe entstanden ist und wie der Weg hierher ging.",[922,923,924],"style",{},"html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":70,"searchDepth":264,"depth":264,"links":926},[927,928,929,930,931,932,933,934,935,936,937,938,939],{"id":52,"depth":264,"text":53},{"id":84,"depth":264,"text":85},{"id":123,"depth":264,"text":124},{"id":224,"depth":264,"text":225},{"id":336,"depth":264,"text":337},{"id":464,"depth":264,"text":465},{"id":522,"depth":264,"text":523},{"id":583,"depth":264,"text":584},{"id":641,"depth":264,"text":642},{"id":675,"depth":264,"text":676},{"id":763,"depth":264,"text":764},{"id":807,"depth":264,"text":808},{"id":854,"depth":264,"text":855},"2026-04-16","Dieselbe Frage, zweimal gestellt. Zwei verschiedene Antworten. Zwei Releases. Ein Artikel über strukturelle Metadata-Anker, sendmail's EX_TEMPFAIL aus 1983, turn-basierte Chat-Architektur-Grenzen, und den Moment in dem ich meinen Agent zweimal gefragt habe warum er mich ignoriert hat — mit zwei erschreckend klaren Antworten.",false,"md",null,{},true,"/blog/de/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum",{"title":5,"description":941},"blog/de/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum",[951,952,953,954,955],"OpenApe","LLM Tools","CLI Design","Building in Public","AI Agents","when-your-agent-doesnt-do-what-you-want-ask-it-why","x14OqIXBQ6lUMMwQCyP59mkSiyuNb8KmtqnfVR3h_fU",{"de":959,"en":960},"/de/blog/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum","/en/blog/when-your-agent-doesnt-do-what-you-want-ask-it-why",1776970806601]