[{"data":1,"prerenderedAt":790},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Fdecisions\u002F008-process-architecture":4,"surround-de-\u002Fdeveloper\u002Fdecisions\u002F008-process-architecture":781},[],{"id":5,"title":6,"body":7,"description":773,"extension":774,"meta":775,"navigation":776,"path":777,"seo":778,"stem":779,"__hash__":780},"content_de\u002F3.developer\u002Fdecisions\u002F9.008-process-architecture.md","ADR-008: Prozessarchitektur des Agenten",{"type":8,"value":9,"toc":754},"minimark",[10,38,63,68,103,106,133,140,144,161,281,286,453,463,467,507,511,518,593,597,632,636,641,684,689,740,744,749],[11,12,13,21,27],"ul",{},[14,15,16,20],"li",{},[17,18,19],"strong",{},"Status:"," Angenommen",[14,22,23,26],{},[17,24,25],{},"Datum:"," 2026-03-24",[14,28,29,32,33,37],{},[17,30,31],{},"Verfeinert durch:"," ADR-0010 (Lifecycle-Modul als alleiniger Status-Writer), ADR-0014 (Step-Modul-Schleife des Walkers; Schritt ",[34,35,36],"code",{},"plan"," gestrichen)",[39,40,43],"callout",{"title":41,"type":42},"Nach der ursprünglichen Entscheidung verfeinert","info",[44,45,46,47,50,51,54,55,58,59,62],"p",{},"Die ursprüngliche Fassung dieses ADR beschrieb drei benannte Prozesstypen (Receiver \u002F Analyzer \u002F Worker), koordiniert über eine State-Mutation. Diese Form wurde vor der Produktivsetzung durch zwei spätere interne ADRs — ADR-0010 (",[34,48,49],{},"docs\u002Fadr\u002F0010-inbox-processing-lifecycle-module.md",") und ADR-0014 (",[34,52,53],{},"docs\u002Fadr\u002F0014-agent-step-module.md",") — zu einem einzigen, sich selbst einplanenden ",[17,56,57],{},"Walker"," plus einem ",[17,60,61],{},"Lifecycle-Koordinator"," zusammengeführt. Diese Seite dokumentiert das Design so, wie es tatsächlich ausgeliefert wird.",[64,65,67],"h2",{"id":66},"kontext","Kontext",[44,69,70,71,74,75,78,79,82,83,86,87,86,90,86,93,86,96,99,100,102],{},"Die Agent Pipeline verarbeitet eingehende Nachrichten in fünf Schritten: ",[17,72,73],{},"Security-Scan, Kontextabruf, Klassifikation, Entwurfserstellung und Routing",". Dies sind die Mitglieder der Union ",[34,76,77],{},"AgentStepKind"," in ",[34,80,81],{},"apps\u002Fapi\u002Fconvex\u002Fagent\u002Fsteps\u002Ftypes.ts"," (",[34,84,85],{},"security_scan",", ",[34,88,89],{},"context_retrieval",[34,91,92],{},"classify",[34,94,95],{},"draft",[34,97,98],{},"route","). Ein eigenständiger Schritt zur „Aktionsplanung“ existierte in einem frühen Entwurf, wurde aber mit ADR-0014 vor der Produktivsetzung gestrichen — der Klassifikator wechselte nie in ihn, und der Drafter schrieb eine ",[34,101,36],{},"-Zeile, deren Payload lediglich literale JSON-Konstruktion war.",[44,104,105],{},"Die einfachste Implementierung wäre eine einzelne Funktion, die alle fünf Schritte nacheinander ausführt. Das schafft jedoch Probleme:",[107,108,109,115,121,127],"ol",{},[14,110,111,114],{},[17,112,113],{},"Kein partieller Retry"," — schlägt die Entwurfserstellung fehl (LLM-Timeout, Rate Limit), startet die gesamte Pipeline erneut beim Security-Scan und verschwendet LLM-Aufrufe.",[14,116,117,120],{},[17,118,119],{},"Keine Nebenläufigkeit"," — eine lang laufende Pipeline blockiert den Start der nächsten Nachricht. Eine 10 Sekunden dauernde Entwurfserstellung verzögert alle nachfolgenden Nachrichten.",[14,122,123,126],{},[17,124,125],{},"Kein Zustand pro Schritt"," — Security-Ergebnisse, Klassifikation und Entwurf müssen jeweils an ihrer eigenen Grenze persistiert werden, damit ein Absturz mitten in der Pipeline keine fertige Arbeit verliert.",[14,128,129,132],{},[17,130,131],{},"Keine Observability"," — eine monolithische Funktion liefert eine einzige Zeitmetrik, keine Aufschlüsselung pro Schritt.",[44,134,135,136,139],{},"Das Serverless-Modell von Convex unterstützt unabhängige Funktionsausführung von Haus aus — eine ",[34,137,138],{},"internalAction"," kann eingeplant werden, sich selbst neu einplanen und läuft in einem eigenen, transaktionsfreien Kontext, während Mutations atomare Schreibvorgänge bieten. Die Architektur trennt genau entlang dieser Linie: Die LLM-\u002FRechenarbeit liegt in Actions, und jeder Datenbankschreibvorgang, der die Zustandsmaschine antreibt, liegt in einer einzigen Mutation.",[64,141,143],{"id":142},"entscheidung","Entscheidung",[44,145,146,147,150,151,153,154,150,157,160],{},"Eingehende Nachrichten mit ",[17,148,149],{},"einem sich selbst einplanenden Step-Walker"," (einer ",[34,152,138],{},") plus ",[17,155,156],{},"einem Lifecycle-Koordinator",[34,158,159],{},"internalMutation",") verarbeiten. Der Walker führt die Rechenarbeit pro Schritt aus; der Koordinator besitzt jeden Schreibvorgang auf der Zustandsmaschine.",[162,163,164,183],"table",{},[165,166,167],"thead",{},[168,169,170,174,177,180],"tr",{},[171,172,173],"th",{},"Komponente",[171,175,176],{},"Convex-Primitiv",[171,178,179],{},"Quelle",[171,181,182],{},"Verantwortung",[184,185,186,217,245],"tbody",{},[168,187,188,194,198,207],{},[189,190,191],"td",{},[17,192,193],{},"Receiver",[189,195,196],{},[34,197,159],{},[189,199,200,82,203,206],{},[34,201,202],{},"apps\u002Fapi\u002Fconvex\u002Finbox\u002Fmessages.ts",[34,204,205],{},"receiveMessage",")",[189,208,209,210,213,214,216],{},"Löst Kontakt + Thread auf, speichert die Nachricht mit ",[34,211,212],{},"processingStatus: 'received'"," und plant dann den Walker bei ",[34,215,85],{}," ein.",[168,218,219,223,227,238],{},[189,220,221],{},[17,222,57],{},[189,224,225],{},[34,226,138],{},[189,228,229,82,232,86,235,206],{},[34,230,231],{},"apps\u002Fapi\u002Fconvex\u002Fagent\u002Fwalker.ts",[34,233,234],{},"start",[34,236,237],{},"runStep",[189,239,240,241,244],{},"Führt pro Aufruf genau ein Step-Modul aus und reiht sich anschließend über ",[34,242,243],{},"scheduler.runAfter(0, …runStep)"," für den nächsten Schritt selbst wieder ein.",[168,246,247,251,255,266],{},[189,248,249],{},[17,250,61],{},[189,252,253],{},[34,254,159],{},[189,256,257,82,260,86,263,206],{},[34,258,259],{},"apps\u002Fapi\u002Fconvex\u002Finbox\u002FprocessingLifecycle.ts",[34,261,262],{},"transition",[34,264,265],{},"recordStepBegin\u002FEnd\u002FFail",[189,267,268,269,272,273,276,277,280],{},"Der alleinige Writer von ",[34,270,271],{},"inboundMessages.processingStatus",", der zugehörigen ",[34,274,275],{},"agentActions","-Zeilen und von ",[34,278,279],{},"conversationThreads.latestDraftStatus",". Erzwingt den Graphen der zulässigen Kanten.",[282,283,285],"h3",{"id":284},"wie-eine-nachricht-fließt","Wie eine Nachricht fließt",[287,288,289,293,321,325,346,350,378,382,390,424,428],"steps",{},[282,290,292],{"id":291},"empfangen","Empfangen",[44,294,295,298,299,301,302,305,306,309,310,313,314,316,317,320],{},[34,296,297],{},"internal.inbox.messages.receiveMessage"," (eine ",[34,300,159],{},", keine HTTP-Action — der eingehende MTA-Webhook landet auf dem HTTP-Router, der den Webhook-Dispatcher ausführt; dessen ",[34,303,304],{},"inbound.received","-Handler ruft diese Mutation über ",[34,307,308],{},"ctx.runMutation"," auf) löst Kontakt und Konversations-Thread auf, fügt die ",[34,311,312],{},"inboundMessages","-Zeile mit ",[34,315,212],{}," ein und plant den Walker ein: ",[34,318,319],{},"ctx.scheduler.runAfter(0, internal.agent.walker.start, { inboundMessageId })",".",[282,322,324],{"id":323},"den-walker-starten","Den Walker starten",[44,326,327,330,331,334,335,338,339,342,343,345],{},[34,328,329],{},"walker.start"," stößt die Pipeline beim ersten Schritt an: Er plant ",[34,332,333],{},"walker.runStep"," mit ",[34,336,337],{},"kind: 'security_scan'"," ein. (Der Lifecycle-Effekt ",[34,340,341],{},"schedule_pipeline_start"," ruft ",[34,344,234],{}," ebenfalls auf, wenn eine Nachricht aus der Quarantäne freigegeben oder per Cron erneut versucht wird.)",[282,347,349],{"id":348},"einen-schritt-ausführen","Einen Schritt ausführen",[44,351,352,354,355,358,359,362,363,366,367,369,370,373,374,377],{},[34,353,333],{}," ermittelt über ",[34,356,357],{},"stepModuleFor"," das Step-Modul für ",[34,360,361],{},"kind",", erfasst über ",[34,364,365],{},"recordStepBegin"," eine ",[34,368,275],{},"-Zeile, führt ",[34,371,372],{},"module.execute(ctx, input)"," aus und wendet dann die reine Entscheidung ",[34,375,376],{},"module.route(output, input, runCtx)"," an.",[282,379,381],{"id":380},"zum-nächsten-schritt-routen","Zum nächsten Schritt routen",[44,383,384,386,387,389],{},[34,385,98],{}," liefert eine von drei Formen zurück (",[34,388,81],{},"):",[11,391,392,402,418],{},[14,393,394,397,398,401],{},[34,395,396],{},"in_state"," — das Schrittende erfassen und den nächsten Schritt innerhalb desselben ",[34,399,400],{},"processingStatus"," einplanen.",[14,403,404,406,407,410,411,413,414,417],{},[34,405,262],{}," — ",[34,408,409],{},"lifecycle.transition"," aufrufen, um ",[34,412,400],{}," weiterzuschalten (was die ",[34,415,416],{},"agentAction"," des Schritts atomar abschließt), und dann optional den nächsten Schritt einplanen.",[14,419,420,423],{},[34,421,422],{},"done"," — das Schrittende erfassen und anhalten.",[282,425,427],{"id":426},"einen-endzustand-erreichen","Einen Endzustand erreichen",[44,429,430,431,433,434,437,438,441,442,444,445,448,449,452],{},"Der Schritt ",[34,432,98],{}," ist der letzte. Er überführt die Nachricht entweder nach ",[34,435,436],{},"approved"," (wenn die automatische Antwort aktiviert ist, die Konfidenz den Schwellenwert überschreitet und das Tageslimit nicht erreicht ist) oder nach ",[34,439,440],{},"draft_ready"," (wartet auf menschliche Prüfung). Bei ",[34,443,436],{}," löst der Lifecycle seinen Effekt ",[34,446,447],{},"schedule_send_approved"," aus, der die Antwort über ",[34,450,451],{},"internal.agent.agentPipeline.sendApprovedReply"," versendet.",[44,454,455,456,459,460,462],{},"Der Walker trägt den ",[34,457,458],{},"input"," des nächsten Schritts direkt weiter — es gibt keinen gemeinsamen Akkumulator (ADR-0014, Entscheidung D1). Der Lifecycle ist das einzige Modul, das ",[34,461,400],{}," anfasst, sodass ein Absturz zwischen dem Status-Patch und dem Schreibvorgang pro Schritt keine halb aktualisierte Zeile mehr hinterlassen kann.",[282,464,466],{"id":465},"warum-zwei-module-statt-einer-großen-action","Warum zwei Module statt einer großen Action",[44,468,469,470,474,475,477,478,480,481,483,484,487,488,490,491,490,494,497,498,500,501,503,504,506],{},"Manche Schritte laufen ",[471,472,473],"em",{},"innerhalb"," eines einzelnen ",[34,476,400],{}," statt an einer Zustandsgrenze — zum Beispiel laufen ",[34,479,89],{}," und ",[34,482,92],{}," beide, während die Nachricht im Zustand ",[34,485,486],{},"classifying"," ist. ",[34,489,365],{}," \u002F ",[34,492,493],{},"recordStepEnd",[34,495,496],{},"recordStepFail"," aus ",[34,499,262],{}," herauszulösen erlaubt es jedem Schritt, seine eigene ",[34,502,416],{},"-Zeile atomar zu markieren, ohne dass der Lifecycle wissen muss, welcher Schritt gerade läuft — und behält zugleich einen einzigen Eigentümer für ",[34,505,400],{}," selbst.",[282,508,510],{"id":509},"retry-semantik","Retry-Semantik",[44,512,513,514,517],{},"Retry ist ",[17,515,516],{},"über alle Schrittarten hinweg einheitlich",", nicht pro Schritt differenziert. Es gibt kein exponentielles Backoff und keinen Retry-Zähler pro Typ.",[11,519,520,543,580],{},[14,521,522,523,526,527,529,530,533,534,334,536,539,540,542],{},"Bei jeder Ausnahme in ",[34,524,525],{},"execute"," oder ",[34,528,98],{}," eines Schritts überführt der Walker die Nachricht nach ",[34,531,532],{},"failed"," und markiert die laufende ",[34,535,416],{},[34,537,538],{},"errorMessage"," als fehlgeschlagen (",[34,541,231],{},").",[14,544,545,546,549,550,553,554,557,558,560,561,564,565,568,569,572,573,576,577,579],{},"Ein Cron — ",[34,547,548],{},"retry failed agent actions",", alle 5 Minuten (",[34,551,552],{},"apps\u002Fapi\u002Fconvex\u002Fcrons.ts",") — ruft ",[34,555,556],{},"internal.inbox.processingLifecycle.retryFailedActions"," auf. Diese Mutation nimmt fehlgeschlagene ",[34,559,275],{},", deren ",[34,562,563],{},"retryCount \u003C maxRetries"," ist (wobei ",[34,566,567],{},"maxRetries = 3"," für jede Schrittart gilt), setzt jede auf ",[34,570,571],{},"pending"," zurück und bringt die zugehörige Nachricht zurück auf ",[34,574,575],{},"received",", sodass der Walker die Pipeline ab ",[34,578,85],{}," erneut durchläuft.",[14,581,582,583,586,587,589,590,592],{},"Sobald ",[34,584,585],{},"retryCount"," den Wert 3 erreicht, bleibt die Action ",[34,588,532],{}," und die Nachricht verbleibt in ihrem Zustand ",[34,591,532],{},", damit ein Mensch sie prüfen kann.",[282,594,596],{"id":595},"verzweigung-bei-mehreren-intents","Verzweigung bei mehreren Intents",[39,598,601],{"title":599,"type":600},"Noch nicht aktiv","warning",[44,602,603,604,607,608,610,611,614,615,617,618,621,622,625,626,631],{},"Das ursprüngliche ADR beschrieb einen Koordinator, der parallele Worker-Prozesse startet — einen je erkanntem Intent — und deren Ergebnisse im Routing-Schritt zusammenführt. ",[17,605,606],{},"Das ist nicht implementiert."," Der Walker ist streng sequenziell und einpfadig: ",[34,609,92],{}," erzeugt genau eine ",[34,612,613],{},"Classification",", und ",[34,616,98],{}," trifft genau eine Entscheidung (",[34,619,620],{},"apps\u002Fapi\u002Fconvex\u002Fagent\u002Fsteps\u002Froute\u002Findex.ts","). Es gibt nirgends in ",[34,623,624],{},"apps\u002Fapi\u002Fconvex\u002Fagent\u002F"," eine Fork-\u002FParallel-Spawn-Logik. Behandeln Sie die parallele Verarbeitung mehrerer Intents als Zukunftsrichtung, nicht als ausgelieferte Fähigkeit — die ",[627,628,630],"a",{"href":629},"\u002Fvision\u002Fagent-pipeline","Visionsseite zur Agent Pipeline"," skizziert, wohin es gehen könnte.",[64,633,635],{"id":634},"konsequenzen","Konsequenzen",[44,637,638],{},[17,639,640],{},"Ermöglicht:",[11,642,643,649,655,675],{},[14,644,645,648],{},[17,646,647],{},"Unabhängiger Retry pro Schritt"," — ein fehlgeschlagener Schritt wird vom Cron zurückgesetzt und erneut ausgeführt; davor abgeschlossene Schritte behalten ihre persistierte Ausgabe.",[14,650,651,654],{},[17,652,653],{},"Nebenläufige Verarbeitung"," — der Walker jeder Nachricht plant unabhängig ein, sodass im Deployment mehrere Nachrichten gleichzeitig verarbeitet werden.",[14,656,657,660,661,313,663,86,666,86,669,480,672,320],{},[17,658,659],{},"Observability pro Schritt"," — jeder Schritt schreibt eine ",[34,662,275],{},[34,664,665],{},"status",[34,667,668],{},"durationMs",[34,670,671],{},"modelUsed",[34,673,674],{},"tokenUsage",[14,676,677,680,681,683],{},[17,678,679],{},"Absturzsicherer Zustand"," — weil der Lifecycle-Koordinator der alleinige Writer von ",[34,682,400],{}," ist und Begleitfelder in derselben Mutation patcht, kann ein Absturz keine Diskrepanz zwischen Status und Daten hinterlassen.",[44,685,686],{},[17,687,688],{},"Abwägungen:",[11,690,691,725,731],{},[14,692,693,696,697,699,700,703,704,706,707,86,709,86,711,86,713,86,715,717,718,721,722,724],{},[17,694,695],{},"Der Zustand liegt über zwei Tabellen verteilt"," — die Zustandsmaschine auf Nachrichtenebene ist ",[34,698,271],{}," (siehe ",[34,701,702],{},"apps\u002Fapi\u002Fconvex\u002Fschema\u002Finbox.ts","), das Tracking pro Schritt liegt in ",[34,705,275],{},"-Zeilen (",[34,708,665],{},[34,710,585],{},[34,712,668],{},[34,714,671],{},[34,716,674],{},"). Es gibt kein einzelnes ",[34,719,720],{},"stepTimings","-Blob — Zeiten pro Schritt liest man, indem man die ",[34,723,275],{}," einer Nachricht zusammenführt.",[14,726,727,730],{},[17,728,729],{},"Die Koordinatorlogik erhöht die Komplexität"," gegenüber einer einfachen sequenziellen Funktion — der Graph der zulässigen Kanten und der Effekt-Reducer müssen sorgfältig getestet werden, damit keine Nachrichten hängen bleiben.",[14,732,733,736,737,739],{},[17,734,735],{},"Geringfügig höhere Latenz"," als bei einem einzigen Funktionsaufruf, bedingt durch den Scheduling-Overhead zwischen den Schritten (jedes ",[34,738,237],{}," reiht das nächste neu ein).",[64,741,743],{"id":742},"verwandt","Verwandt",[745,746],"link-card",{"description":747,"title":748,"to":629},"Die umfassendere Vision für den Agenten für eingehende Nachrichten, einschließlich noch nicht ausgelieferter Richtungen.","Agent Pipeline (Vision)",[745,750],{"description":751,"title":752,"to":753},"Wie Actions, Mutations und der Scheduler im gesamten Backend eingesetzt werden.","Convex-Backend","\u002Fdeveloper\u002Fconvex",{"title":755,"searchDepth":756,"depth":756,"links":757},"",2,[758,759,771,772],{"id":66,"depth":756,"text":67},{"id":142,"depth":756,"text":143,"children":760},[761,763,764,765,766,767,768,769,770],{"id":284,"depth":762,"text":285},3,{"id":291,"depth":762,"text":292},{"id":323,"depth":762,"text":324},{"id":348,"depth":762,"text":349},{"id":380,"depth":762,"text":381},{"id":426,"depth":762,"text":427},{"id":465,"depth":762,"text":466},{"id":509,"depth":762,"text":510},{"id":595,"depth":762,"text":596},{"id":634,"depth":756,"text":635},{"id":742,"depth":756,"text":743},"Warum Owlat eingehende Nachrichten mit einem sich selbst einplanenden Step-Walker plus einem Lifecycle-Koordinator verarbeitet statt mit einer sequenziellen Funktion.","md",{},true,"\u002Fdeveloper\u002Fdecisions\u002F008-process-architecture",{"title":6,"description":773},"3.developer\u002Fdecisions\u002F9.008-process-architecture","fiIJkJAmOuvbmyg9QEXu4YbaRYhYJZMEPjc07y6a0EE",[782,786],{"title":783,"path":784,"stem":785,"children":-1},"ADR-007: Austauschbarer LLM-Provider","\u002Fdeveloper\u002Fdecisions\u002F007-pluggable-llm","3.developer\u002Fdecisions\u002F8.007-pluggable-llm",{"title":787,"path":788,"stem":789,"children":-1},"Beispiele","\u002Fexamples","4.examples\u002F0.index",1786915110798]