Agenten-Strecke
Technische Architektur der Agenten-Strecke für eingehende Mail — Schrittmodule, der Walker, Sicherheitsprüfung, Threading und menschliche Prüfung.
Architektur der Agenten-Strecke
Die Agenten-Strecke ist das technische Herzstück von Owlats Entwicklung von einer Plattform für ausgehende E-Mail hin zu einem System für Kommunikationsintelligenz. Jede eingehende E-Mail durchläuft eine mehrstufige Verarbeitungskette, die prüft, Kontext abruft, klassifiziert, entwirft und entweder an den automatischen Versand oder an eine Warteschlange zur menschlichen Prüfung routet.
Die Kern-Strecke ist gebaut und in Betrieb: die Schrittmodule und der Walker (apps/api/convex/agent/), der Empfänger für eingehende Nachrichten (apps/api/convex/inbox/messages.ts), das vollständige Schema (apps/api/convex/schema/inbox.ts) und die Postfach-Oberfläche (apps/web/app/pages/dashboard/inbox/*) sind allesamt ausgeliefert.
Das meiste, was dieses Dokument einst als Zukunftsmusik markiert hat, ist inzwischen verdrahtet: der LLM-Klassifikator für Prompt-Injections (Muster + Best-Effort-LLM der Guard-Stufe), das Zusammenfassen von Nachrichten (Coalescing, per Opt-in), das Routing mit abgestufter Autonomie samt lebendiger Feedback-Schleife und drei Circuit Breakern sowie der Wissensgraph und die relevanten Quelldateien, die das Kontext-Briefing des Agenten speisen. Zukunft bleibt die Verzweigung bei mehreren Absichten / parallele Worker — der Walker führt weiterhin eine einzelne lineare Kette aus. Jede Stelle ist im Text entsprechend markiert. Für die Wissensseite siehe Wissensgraph, für den Leitfaden aus Betreibersicht KI-Agent & Autonomie.
Architektur der Schrittmodule
Die Strecke ist nicht eine große Funktion. Gemäß ADR-0014 ist jede Stufe ein Agenten-Schrittmodul — ein kleines Objekt mit zwei reinen Funktionen — und ein einzelner Walker führt sie der Reihe nach aus. Die Schrittmodule liegen unter apps/api/convex/agent/steps/<kind>/index.ts; der Walker ist apps/api/convex/agent/walker.ts.
Ein Schrittmodul (apps/api/convex/agent/steps/types.ts) sieht so aus:
interface AgentStepModule<K, In, Out> {
readonly kind: K
readonly llm?: { tier: 'fast' | 'capable' } // present iff the step calls an LLM
execute(ctx, input): Promise<AgentStepResult<Out>> // does the work
route(output, input, runCtx): AgentRoute // decides what happens next
}
route() gibt eine von drei Formen zurück:
| Route | Bedeutung |
|---|---|
in_state | Im aktuellen processingStatus bleiben, den nächsten Schritt einplanen. |
transition | processingStatus in einen neuen Zustand überführen (optional einen Folgeschritt einplanen). |
done | Ende — die Strecke ist für diese Nachricht abgeschlossen. |
Der Walker (runStep) legt eine agentActions-Zeile an, ruft module.execute() auf, wendet module.route() an und plant den nächsten Schritt selbst über ctx.scheduler.runAfter(0, …) ein. Jede Ausnahme innerhalb eines Schritts überführt die Nachricht nach failed, wobei die fehlgeschlagene Aktion festgehalten wird.
Der Walker führt eine einzige lineare Schrittkette pro Nachricht aus. Die Klassifizierung erzeugt ein einziges Classification-Objekt, und das Routing ist in_state / transition / done — es gibt keine parallele Verzweigung „Worker A / Worker B“. Die Verzweigung bei mehreren Absichten (weiter unten) bleibt ein Zukunftsentwurf.
Die fünf Schritte
Die Schrittarten sind exakt: security_scan, context_retrieval, classify, draft, route (AgentStepKind in agent/steps/types.ts). Ein plan-Schritt war entworfen, wurde aber mit ADR-0014 vor der Produktion verworfen — es gibt keine Stufe zur Aktionsplanung.
Schritt 1: Sicherheitsprüfung
apps/api/convex/agent/steps/security_scan/index.ts — der Einstiegsschritt, ausgeführt, während processingStatus auf received/security_check steht. Er prüft die eingehende Nachricht auf Prompt-Injection, eingeschmuggelte Anweisungen und Spam. Die deterministische Schicht nutzt detectInjection, detectSmuggling und calculateSpamScore aus patterns.ts; darüber hinaus fängt ein LLM-Injection-Klassifikator der Guard-Stufe (getLLMProvider('guard') + runLlmObject) neuartige oder verschleierte Formulierungen ab, welche die regulären Ausdrücke verfehlen. Die beiden Urteile werden ODER-verknüpft — eine Markierung mit hoher Konfidenz auf einer der beiden Seiten gilt als Injection — und der LLM-Aufruf fällt offen aus (jeder Modellfehler fällt auf das reine Musterurteil zurück, sodass ein wackliges Modell die Strecke nie blockiert).
Routing:
- Injection erkannt (Muster oder Guard-LLM), Konfidenz ≥ 0,7 →
quarantined(bis zur menschlichen Freigabe zurückgehalten). - Spam-Score ≥ 80 →
archived(Grundspam). - Sauber, Agent aktiviert → Übergang nach
classifyingundcontext_retrievaleinplanen. - Sauber, Agent deaktiviert → die Prüfung festhalten und anhalten (
done).
Die Modellstufe guard (apps/api/convex/lib/llmProvider.ts) ist inzwischen verdrahtet: Neben der Regex-Prüfung führt security_scan/index.ts einen runLlmObject-Klassifikator aus, der { isInjection, confidence, reason } zurückgibt. Ein LLM-Urteil mit hoher Konfidenz (≥ 0,8) markiert eine Injection auch dann, wenn kein Muster gegriffen hat (injectionType: llm_prompt_injection). Die Erkennung ist damit nicht mehr vollständig deterministisch — sie besteht aus Mustern plus einem Best-Effort-LLM, das bei jedem Modellfehler offen ausfällt.
Schritt 2: Kontextabruf
apps/api/convex/agent/steps/context_retrieval/index.ts — läuft in_state, während processingStatus auf classifying steht. Er stellt eine Briefing-Zeichenkette zusammen, welche die LLM-Schritte konsumieren, und zwar aus:
- Kontaktprofil — Name, Sprache, Zeitzone aus
contacts - Jüngste Aktivität — die letzten paar
contactActivities - Konversationsverlauf — frühere Nachrichten im selben Thread (
inboundMessagesnach Thread) - Wissensgraph — semantisch relevante typisierte Einträge über
internal.knowledge.retrieval.semanticSearch(der Abschnitt[KNOWLEDGE], begrenzt durchknowledgeEntryLimit) - Relevante Quelldateien — semantisch relevante hochgeladene Dokumente über
internal.semanticFileProcessing.semanticSearch(der Abschnitt[RELEVANT FILES], begrenzt durchfileLimit) - Die aktuelle Nachricht — Von / An / Betreff / Text
Das Briefing ist tokenbudgetiert (≈ 4 Zeichen/Token, Budget von 4000 Token) und kennt drei Verdichtungsstufen, festgehalten in inboundMessages.contextTier:
| Stufe | Auslöser | Vorgehen |
|---|---|---|
normal | Passt ins Budget | Gesamten Kontext wortgetreu weitergeben. |
compacted | Bis zum 3-Fachen des Budgets | Das Ende des zusammengestellten Kontexts behalten (das jüngste Material). |
emergency | Über dem 3-Fachen des Budgets | Nur die Kontaktzeile und die aktuelle Nachricht behalten. |
Der Kontextabruf fragt nun den Wissensgraphen und den Dateiindex ab. Ist der Abfragetext lang genug, führt er eine semantische Vektorsuche über knowledgeEntries (internal.knowledge.retrieval.semanticSearch) und über hochgeladene Dateien (internal.semanticFileProcessing.semanticSearch) aus und faltet die Treffer in die Briefing-Abschnitte [KNOWLEDGE] und [RELEVANT FILES] hinein — hinter demselben Tokenbudget. Sowohl knowledgeEntryLimit als auch fileLimit sind jetzt aktiv. Zur Abrufseite siehe Wissensgraph.
Schritt 3: Klassifizierung
apps/api/convex/agent/steps/classify/index.ts — nutzt die schnelle Modellstufe über getLLMProvider('classify') und runLlmObject für typsichere strukturierte Ausgabe (generateObject unter der Haube). Er erzeugt genau eine Klassifizierung:
{
category: 'support' | 'sales' | 'billing' | 'feature_request'
| 'complaint' | 'spam' | 'internal' | 'other',
priority: 'urgent' | 'normal' | 'low',
sentiment: 'positive' | 'neutral' | 'negative',
intent: 'question' | 'complaint' | 'request' | 'information'
| 'escalation' | 'acknowledgment',
confidence: number, // 0–1
}
Routing: spam → archived; complaint oder urgent → direkt nach draft_ready (den Entwurfsschritt überspringen, menschliche Prüfung erzwingen); alles Übrige → Übergang nach drafting und Einplanen des draft-Schritts. Die Klassifizierung wird in inboundMessages.classification abgelegt.
Schritt 4: Entwurfserzeugung
apps/api/convex/agent/steps/draft/index.ts — nutzt die leistungsfähige Modellstufe über getLLMProvider('draft') und runLlmText. Der Entwurf ist in agentConfig.toneDescription, agentConfig.signatureTemplate und dem zusammengestellten Kontext verankert. Als Defense in Depth prüft der Entwurfsschritt den zusammengestellten Kontext erneut auf Injection-Muster (Thread-Verlauf, der stromaufwärts nicht einzeln geprüft wurde); ein Treffer mit hoher Konfidenz lässt den Schritt fehlschlagen. Die Ausgabe wird zusammen mit einem confidenceScore in inboundMessages.draftResponse / draftSubject abgelegt.
Schritt 5: Routing
apps/api/convex/agent/steps/route/index.ts — der abschließende Schritt. Er entscheidet über automatische Freigabe versus menschliche Prüfung in drei Stufen, die sicherste zuerst:
- Sicherheitsschranke der Circuit Breaker. Jeder offene Breaker (
internal.agentHealth.getCircuitBreakersInternal) erzwingthuman_review— eine beeinträchtigte Strecke versendet nie automatisch, unabhängig von der Konfiguration. - Abgestufte Autonomie (wenn das Flag
ai.autonomyaktiv ist).internal.autonomy.checkPermissionInternalwendet die kategoriebezogenenautonomyRulesan (Schwellwert + Tageslimit + Breaker-Prüfung). Eine Kategorie ohne Regel wird nie automatisch freigegeben; eine erlaubte belastet den kategoriebezogenen Tageszähler. - Klassischer globaler Rückfall (Autonomie-Flag aus). Das einzelne
agentConfig-Singleton —isAutoReplyEnabled,confidenceThresholdund die Felder für das Tageslimit.
Bei auto_approve erfolgt der Übergang nach approved; der Effekt schedule_send_approved des Lebenszyklus versendet die Antwort dann über internal.agent.agentPipeline.sendApprovedReply. Andernfalls erfolgt der Übergang nach draft_ready (wartet auf menschliche Prüfung).
Der Routing-Schritt zieht nicht mehr nur agentConfig heran. Er liest inzwischen die Circuit Breaker und — hinter dem Flag ai.autonomy — die kategoriebezogenen autonomyRules über internal.autonomy.checkPermissionInternal; der klassische globale Schwellwert ist nur noch die Rückfallstufe. Siehe Abgestufte Autonomie weiter unten.
Integration eingehender E-Mail
Ablauf MTA → Convex
Eingehende Mail für das gemeinsame Agenten-Postfach kommt über den üblichen MTA-Webhook an, nicht über eine eigene Eingangsroute:
Inbound SMTP → owlat-mta
→ POST /webhooks/mta (apps/api/convex/http.ts → providerFeedbackWebhook('mta'),
apps/api/convex/webhooks/providerFeedbackHttp.ts)
→ webhook pipeline verifies HMAC, parses the `inbound.received` event
(apps/api/convex/webhooks/adapters/mta.ts)
→ dispatcher routes inbound.received → internal.inbox.messages.receiveMessage
POST /webhooks/mta transportiert Zustellereignisse und das Ereignis inbound.received für das Agenten-Postfach. Die separate Route POST /webhooks/mta-mailbox gehört zu Postbox (persönliche Mail pro Benutzerin und Benutzer) und läuft über mail/webhook.ts, nicht über die Agenten-Strecke.
receiveMessage (apps/api/convex/inbox/messages.ts, eine internalMutation) tut anschließend Folgendes:
- Legt die Absenderin oder den Absender als
contacts-Zeile an bzw. aktualisiert sie (Kanalemail, Quelleinbound). - Ermittelt oder erzeugt über die Threading-Kaskade eine
conversationThreads-Zeile. - Fügt die
inboundMessages-Zeile mitprocessingStatus: 'received'ein. - Hält eine Kontaktaktivität
inbound_receivedfest. - Plant
internal.agent.walker.startein, was die Strecke beisecurity_scananstößt.
Threading
Das Threading (verantwortet vom Thread-Modul, das aus receiveMessage aufgerufen wird) ist eine Kaskade aus drei Strategien:
In-Reply-To-Header → den Thread der referenzierten Nachricht finden.References-Header → den Thread irgendeiner referenzierten Nachricht finden.- Rückfall: normalisierter Betreff (Re:/Fwd: entfernt, kleingeschrieben) +
contactIdentifier.
Ein passender Thread wird wieder geöffnet, falls er geschlossen war; messageCount / lastMessageAt des Threads werden atomar gepflegt.
Schema-Erweiterungen
Die Postfach-/Agenten-Tabellen sind in apps/api/convex/schema/inbox.ts definiert.
inboundMessages
Speichert jede eingehende E-Mail samt ihrem Verarbeitungszustand.
| Feld | Typ | Beschreibung |
|---|---|---|
messageId | string | SMTP-Message-ID |
from, to, subject | string | Envelope-Daten |
textBody, htmlBody | string? | Nachrichteninhalt |
inReplyTo, references | string? | Threading-Header |
headers, attachmentMeta | string? | Rohe Header / Anhang-Metadaten (JSON) |
threadId | id → conversationThreads | Konversations-Thread |
contactId | id → contacts | Verknüpfter Kontakt |
processingStatus | enum | received, security_check, quarantined, classifying, drafting, draft_ready, approved, sent, rejected, archived, failed |
securityFlags | object? | Ergebnisse der Musterprüfung (Injection, Spam-Score, Konfidenz) |
classification | object? | Ergebnis des Klassifikators (Kategorie, Priorität, Stimmung, Absicht, Konfidenz) |
draftResponse, draftSubject | string? | Vom Agenten erzeugter Entwurf |
confidenceScore | number? | Gesamtkonfidenz für die Routing-Entscheidung |
contextTier | enum? | normal / compacted / emergency |
assignedTo | string? | Menschliche Prüfinstanz (BetterAuth-Benutzer-ID) |
errorMessage | string? | Fehlerdetails der Strecke |
receivedAt | number | Zeitstempel des Nachrichteneingangs |
processedAt | number? | Zeitstempel, zu dem die Strecke fertig war (gesetzt, sobald die Verarbeitung abgeschlossen ist) |
conversationThreads
Fasst zusammengehörige Nachrichten zu Konversationen zusammen.
| Feld | Typ | Beschreibung |
|---|---|---|
subject, normalizedSubject | string | Anzeigebetreff / Abgleichsschlüssel |
contactId | id → contacts | Verknüpfter Kontakt |
contactIdentifier | string | Kanalneutraler Anzeigebezeichner — E-Mail für E-Mail/generisch, Telefonnummer/Handle für SMS/WhatsApp/Chat (in ADR-0032 aus contactEmail umbenannt) |
status | enum | open, waiting, resolved, closed |
assignedTo | string? | Zugewiesenes Teammitglied |
latestDraftStatus | enum? | pending / approved / rejected / sent — für schnelles Filtern der Warteschlange |
messageCount, firstMessageAt, lastMessageAt, createdAt | number | Thread-Metadaten |
agentActions
Eine Zeile pro Schrittausführung der Strecke (nicht pro geplanter Aktion).
| Feld | Typ | Beschreibung |
|---|---|---|
inboundMessageId | id → inboundMessages | Quellnachricht |
actionType | enum | security_scan, context_retrieval, classify, draft, route (die Schrittarten) |
status | enum | pending, running, completed, failed, skipped |
input, output | string? | Eingabe / Ausgabe des Schritts (JSON) |
errorMessage, retryCount | string? / number | Fehlernachverfolgung |
startedAt, completedAt, durationMs | number? | Zeitmessung |
modelUsed, tokenUsage | string? / object? | LLM-Nutzung (für classify / draft) |
agentConfig
Das Singleton für die betriebliche Feinjustierung. Der Hauptschalter ist das Feature-Flag ai.agent (isAgentEnabled in agent/agentPipeline.ts ruft isFeatureEnabled(ctx, 'ai.agent') auf), nicht eine Spalte dieser Tabelle.
| Feld | Typ | Beschreibung |
|---|---|---|
isAutoReplyEnabled | boolean | Automatischen Versand ohne menschliche Prüfung erlauben |
confidenceThreshold | number (0–1) | Mindestkonfidenz für die automatische Freigabe |
toneDescription | string? | Kommunikationsstil der Organisation |
signatureTemplate | string? | E-Mail-Signatur für Agentenentwürfe |
maxDailyAutoReplies | number? | Tageslimit für automatische Antworten |
dailyAutoReplyCount, dailyAutoReplyResetAt | number? | Fortlaufende Zähler für das Tageslimit |
coalesceWindowMs | number? | Debounce-Fenster für das Zusammenfassen. Opt-in: Ein positiver Wert aktiviert das Zusammenfassen; nicht gesetzt bzw. 0 (der Standard) verarbeitet jede Nachricht sofort |
Modell-Routing
Verschiedene Schritte der Strecke haben verschiedene Anforderungen. Die Klassifizierung braucht Tempo und strukturierte Ausgabe; die Entwurfserzeugung braucht Qualität und Nuance. Der Anbieter-Resolver (apps/api/convex/lib/llmProvider.ts) routet jede Aufgabe an eine von zwei Modellstufen.
Modellauswahl je Aufgabe
getLLMProvider(task) löst einen OpenAI-kompatiblen Client und die Modell-ID für die Stufe der jeweiligen Aufgabe auf:
// apps/api/convex/lib/llmProvider.ts
export function getLLMProvider(task: LLMTask = 'draft') {
return getClient()(modelIdForTier(taskTier(task)))
}
Die Auflösung der Stufe steckt in taskTier() (apps/api/convex/lib/llmProvider.ts):
| Aufgabe | Stufe | Genutzt von |
|---|---|---|
classify | fast | Klassifizierungsschritt |
extract | fast | Wissensextraktion |
guard | fast | LLM-Injection-Klassifikator der Sicherheitsprüfung |
summarize | fast | Zusammenfassung |
draft | capable | Schritt der Entwurfserzeugung |
plan | capable | (reserviert — der plan-Schritt wurde verworfen) |
Konfiguration
Siehe ADR-007 für die Anbieter-Abstraktion und ADR-009 für die Routing-Entscheidung. Die Standardwerte sind gpt-4o-mini (fast) und gpt-4o (capable), gemäß apps/api/convex/lib/llmProvider.ts.
| Variable | Beschreibung | Standard |
|---|---|---|
LLM_MODEL | Rückfallmodell für alle Aufgaben | — |
LLM_MODEL_CAPABLE | Modell für die Stufe draft (capable) | Fällt zurück auf LLM_MODEL, dann gpt-4o |
LLM_MODEL_FAST | Modell für Aufgaben der schnellen Stufe (classify, extract, guard, summarize) | Fällt zurück auf LLM_MODEL, dann gpt-4o-mini |
Wer selbst hostet und ein einzelnes Ollama-Modell betreibt, kann nur LLM_MODEL setzen — beide Stufen fallen darauf zurück. Organisationen mit GPU-Budget können aufteilen: ein kleines Modell für die Klassifizierung und ein größeres für das Entwerfen.
Quarantäne
Von der Sicherheitsprüfung markierte Nachrichten werden nicht stillschweigend verworfen. Sie werden mit processingStatus: 'quarantined' und einem securityFlags-Objekt gespeichert und erscheinen in einer eigenen Admin-Ansicht (apps/web/app/pages/dashboard/inbox/quarantine.vue), in der ein Mensch sie prüfen und freigeben kann. Die Freigabe einer Nachricht aus der Quarantäne überführt sie zurück nach received (die Quelle release_quarantine des Lebenszyklus), wodurch die Strecke ab security_scan erneut läuft.
Prüf-Warteschlange
Die Prüf-Warteschlange ist die Schnittstelle für den Menschen in der Schleife — eine Sicht auf inboundMessages, gefiltert nach processingStatus = 'draft_ready', kein separates System.
Oberfläche
/dashboard/inbox— Thread-Liste mit Filtern./dashboard/inbox/[threadId]— vollständige Konversation mit dem Agentenentwurf und den Bedienelementen Freigeben / Bearbeiten / Ablehnen./dashboard/inbox/review(review.vue) — fokussierte Prüf-Warteschlange der Vorgänge, die Aufmerksamkeit brauchen./dashboard/inbox/quarantine(quarantine.vue) — sicherheitsmarkierte Nachrichten.
Aktionen
- Freigeben — versendet den Entwurf unverändert; Status →
approved, dannsentnach bestätigter Übergabe an den Anbieter. - Bearbeiten und freigeben — den Entwurf ändern, dann senden.
- Ablehnen — Status →
rejected. - Neu zuweisen — an ein anderes Teammitglied routen.
Den vollständigen Arbeitsablauf finden Sie in den Leitfäden aus Betreibersicht Team-Postfach und KI-Agent & Autonomie.
Abgestufte Autonomie
Die Autonomie- und Betriebsschicht (Tabellen in schema/inbox.ts) ist inzwischen in die aktive Routing-Entscheidung eingebunden, hinter dem Feature-Flag ai.autonomy.
| Tabelle | Zweck | Status |
|---|---|---|
autonomyRules | Kategoriebezogener autoApproveThreshold + Tageslimit maxDailyAutoActions + Schalter isEnabled | Aktiv — der route-Schritt liest sie über internal.autonomy.checkPermissionInternal (Stufe 2); Crons setzen die Zähler zurück und adjustThresholds justiert sie wöchentlich |
autonomyFeedback | Menschliche Signale aus Freigabe/Ablehnung/Bearbeitung zur Schwellwertjustierung | Aktiv — Freigeben/Ablehnen/Bearbeiten in inbox/mutations.ts rufen internal.autonomy.recordFeedback auf und speisen damit sowohl den wöchentlichen adjustThresholds-Cron als auch den Breaker rejection_spike |
agentMetrics | Metriken über ein gleitendes Fenster | queue_depth, processing_latency, error_rate, auto_approve_ratio, rejection_rate und llm_cost werden sämtlich von agentHealth.ts:rollupMetrics befüllt. classification_accuracy wird ebenfalls von rollupMetrics befüllt, als Näherung über die mittlere selbstberichtete Konfidenz — einen Strom menschlich gelabelter Referenzwerte gibt es weiterhin nicht |
agentCircuitBreakers | Automatisch auslösende Sicherung bei llm_failure / confidence_degradation / rejection_spike | Alle drei werden bei jedem Rollup mit Hysterese ausgewertet (open → half_open → closed); der route-Schritt verweigert die automatische Freigabe, solange irgendein Breaker offen ist |
knowledgeBackfillJobs | Einmalige Extraktion historischen Wissens | Aktiv — wird angestoßen, wenn das Flag ai.agent von false auf true kippt (agent/knowledgeBackfill.ts) |
Der Routing-Schritt entscheidet in drei Stufen — Sicherheitsschranke der Circuit Breaker, kategoriebezogene autonomyRules (hinter dem Flag ai.autonomy), dann der klassische globale Schwellwert aus agentConfig. Menschliche Entscheidungen zu Freigabe / Ablehnung / Bearbeitung fließen als autonomyFeedback zurück; der wöchentliche adjustThresholds-Cron nutzt sie, um kategoriebezogene Schwellwerte zu straffen oder zu lockern, und das Health-Rollup nutzt sie für den Breaker rejection_spike. Ist ai.autonomy aus, greift allein die klassische globale Steuerung über agentConfig (Schalter für automatische Antworten, Konfidenzschwelle, Tageslimit).
Zusammenfassen von Nachrichten
E-Mail-Threads treffen oft in Schüben ein — eine CC-Kette mit drei Antworten in 30 Sekunden. Das Zusammenfassen (Coalescing) faltet einen solchen Schub in einen einzigen Streckenlauf auf der jüngsten Nachricht zusammen, statt die Strecke je Nachricht auszuführen.
apps/api/convex/agent/coalescing.ts ist implementiert. shouldCoalesce ist ein Debounce: Es storniert einen processCoalescedBatch-Job pro Thread und plant ihn coalesceWindowMs in die Zukunft neu ein (eine coalesceBatches-Zeile pro Thread), sodass der Stapel erst dann feuert, wenn der Thread ein volles Fenster lang ruhig bleibt. processCoalescedBatch wählt anschließend die neueste noch mit received markierte Nachricht als Anführerin, verdrängt die älteren (archiviert mit Grund coalesced, deren Inhalt den Agenten weiterhin über den Thread-Verlauf erreicht) und führt die Strecke einmal für die Anführerin aus.
Es ist Opt-in: Das Zusammenfassen ist nur aktiv, wenn agentConfig.coalesceWindowMs einen positiven Wert hat. Nicht gesetzt (der Standard) bedeutet, dass jede Nachricht sofort verarbeitet wird, ohne zusätzliche Latenz.
Verzweigung bei mehreren Absichten (Zukunft)
Ein künftiger Entwurf lässt eine Nachricht mit mehreren Absichten (etwa „prüfen Sie meinen Rechnungsstatus, und wir hätten gern einen Dark Mode“) in parallele Worker-Zweige verzweigen, von denen jeder seinen eigenen Entwurf erzeugt, wobei die Prüf-Warteschlange beide verknüpft mit derselben eingehenden Nachricht anzeigt.
Der Walker führt eine einzelne lineare Schrittkette aus, und classify erzeugt ein einziges Classification-Objekt — heute gibt es keinen Mechanismus für Verzweigung / Worker A / Worker B. Zur Richtung der Prozessarchitektur siehe ADR-008.