23 KiB
AI Agent & Knowledge Store
Überblick
Der AI-Agent ist der Service serviceAgent (AgentService) im ServiceCenter. Er orchestriert einen ReAct-Loop mit nativer Function Calling-Unterstützung: pro Runde ruft das Modell serviceAi auf; bei Tool-Calls werden Handler über die Tool Registry ausgeführt und Ergebnisse wieder in den Konversationskontext gegeben. Er ist die zentrale Schicht für den Workspace (Chat, Streaming, Tools, Dateien, externe Quellen).
Darunter liegt serviceAi als Low-Level-Gateway (Billing-Preflight, Provider-/Modellwahl, Neutralisierung vor Modellaufruf). serviceKnowledge liefert RAG: Indexierung extrahierter Inhalte, semantische Suche (pgvector) und buildAgentContext für kontextuelle Injektion vor jeder Agent-Runde.
Der AI-Core (gateway/modules/aicore/) kapselt Provider-Plugins und die Modellwahl; der Agent nutzt ihn indirekt über serviceAi (u. a. OperationTypeEnum.AGENT, Embeddings für den Knowledge Store).
Agent Core
ServiceCenter-Integration
| Registry-Key | Klasse | objectKey |
Dependencies (laut registry.py) |
|---|---|---|---|
agent |
AgentService |
service.agent |
ai, chat, utils, extraction, billing, streaming, knowledge |
knowledge |
KnowledgeService |
service.knowledge |
ai |
Pro Request propagiert der ServiceCenterContext u. a. userId, mandateId, featureInstanceId und optional requireNeutralization (siehe Invarianten).
Ablauf (Ist-Code)
- Einstieg:
AgentService.runAgent(mainServiceAgent.py) — baut die Tool Registry, optional Anreicherung des Prompts mit Datei-Metadaten/FileContentIndex, startetrunAgentLoop(agentLoop.py). - Loop:
runAgentLoop—ConversationManager+ Systemprompt; pro Runde: optional RAG viabuildRagContextFn, Budget-Check, Progressive Summarization bei Bedarf (ConversationManager, Modell-OperationDATA_ANALYSE), dannAiCallRequestmitOperationTypeEnum.AGENT,messages,tools. - Tool Registry:
registerCoreToolsincoreTools/registerCore.pydelegiert an domänenspezifische Module (_workspaceTools,_connectionTools,_dataSourceTools,_documentTools,_mediaTools,_featureSubAgentTools,_crossWorkflowTools);ActionToolAdapterregistriert alle Workflow-Actions mitdynamicMode=Trueals zusätzliche Tools (method_action→ internexecuteAction). - Parallele Ausführung: In
_executeToolCallswerden alsreadOnly=Truemarkierte Tool-Calls parallel (asyncio.gather), schreibende/übrige sequentiell — vermeidet Race Conditions bei zustandsändernden Tools. - Budget:
AgentConfig.maxCostCHF— vor jeder Runde Abgleich mit Workflow-Kosten; bei Überschreitung StatusbudgetExceededund abschliessender Fortschritts-Text (FINAL). - Rundenlimit:
AgentConfig.maxRounds(Default 25); bei Erreichen währendrunning→ StatusmaxRoundsReachedund Fortschritts-Zusammenfassung. - Degradation: Schlägt RAG-Injektion fehl, wird nur geloggt (non-blocking); der Agent läuft ohne diesen Kontext weiter. Fehlgeschlagene RoundMemory-Persistenz ebenfalls non-blocking.
- Streaming: Optional
aiCallStreamFn— Chunks alsCHUNK-Events; Abschluss u. a.AGENT_SUMMARYmit Kosten- und Round-Metriken; Trace kann über Knowledge-Entities persistiert werden (_persistTrace).
Konfiguration (datamodelAgent.AgentConfig, Ist)
| Feld | Bedeutung |
|---|---|
maxRounds |
Max. Schleifenrunden (Default 25, 1–100) |
maxCostCHF |
Optionales Workflow-Budget in CHF |
toolSet |
Aktives Tool-Set (Default "core") |
temperature |
Optional für den Agent-AI-Call |
System-Prompt: temporaler Kontext (Ist)
buildSystemPrompt (conversationManager.py) injiziert beim Bauen des System-Prompts einen Datums-/Zeitblock (_buildTemporalContext). Der Sub-Agent-Prompt in featureDataAgent._buildSchemaContext macht dasselbe. Ohne diesen Block halluzinieren LLMs das aktuelle Datum aus ihrem Training-Cutoff (typisch: »Juni 2025«), was bei relativen Zeit-Filtern (»letzten Monat«, »Q1«) sofort zu falschen SQL-Filtern und falschen Antworten führt.
Datenfluss der Zeitzone:
- Browser:
Intl.DateTimeFormat().resolvedOptions().timeZone(z. B."Europe/Zurich"). - Frontend:
frontend_nyla/src/api.tsAxios-Interceptor sendet den IANA-Namen alsX-User-Timezone-Header. - Gateway:
_requestContextMiddleware(app.py) liest den Header und schreibt ihn via_setRequestTimezonein eineContextVar. - Konsumenten:
getRequestNow()/getRequestTimezone()ausmodules/shared/timeUtils.pyliefern die Werte für den Prompt-Block (gleiche Pattern wie_setLanguage/_getLanguage). - Fallback: Header fehlt oder ist ungültig →
UTC. Niemals server-seitige Hardcoded-TZ.
Storage bleibt UTC (getUtcTimestamp, getIsoTimestamp). Nur user-sichtbare Now-Werte (Agent-Prompt, formatierte Display-Strings) gehen über getRequestNow().
Toolbox Registry
Datei: serviceCenter/services/serviceAgent/toolboxRegistry.py
Statt alle Tools flach zu exponieren, gruppiert die Toolbox Registry Tools in thematische Toolboxes. Der Agent startet schlank und kann Spezial-Toolboxes zur Laufzeit nachfordern.
| Toolbox | Anzahl Tools | Default | Requires Connection |
|---|---|---|---|
core |
20 | Ja | -- |
ai |
9 | Ja | -- |
datasources |
8 | Ja | -- |
email |
5 | Nein | microsoft |
sharepoint |
3 | Nein | microsoft |
clickup |
3 | Nein | clickup |
jira |
3 | Nein | jira |
workflow |
9 | Nein | -- (featureCode: graphicalEditor) |
trustee |
1 | Nein | -- (featureCode: trustee) |
Aktivierung (_activateToolboxes in mainServiceAgent.py):
- Default-Toolboxes sind immer aktiv
- Connection-abhaengige Toolboxes werden aktiviert, wenn der User eine passende Connection hat
- Tools aus inaktiven Toolboxes werden aus der Registry entfernt
workflow-Tools werden explizit viaworkflowTools.getWorkflowToolDefinitionsregistriert
requestToolbox Meta-Tool:
- Erlaubt dem Agent, zur Laufzeit inaktive Toolboxes anzufordern
- Schema:
{ toolboxId: enum[inactive IDs], reason: string } - Handler in
mainServiceAgent._registerRequestToolbox() - Nach erfolgreichem Aufruf refresht
agentLoop.pydietoolDefinitionsfuer die naechste Runde - Nur Toolboxes, die aktuell nicht aktiv sind, erscheinen als Optionen
RBAC (Architekturentscheid)
Keine separate Tool-Level-RBAC: Zugriff wird über Datenbank-RBAC (z. B. File-Queries), OAuth/Connections bei externen Quellen und serviceAi (Billing, Provider-/Modell-RBAC) sowie can_access_service für service.agent durchgesetzt.
Agent Tools
Prinzip
Tools sind registrierte Handler mit JSON-Schema für Argumente, readOnly-Flag (Parallelisierung) und optional toolSet. Tool-Ausgaben sind Rohdaten für das Modell; Neutralisierung vor dem LLM erfolgt zentral in serviceAi, nicht in den Tools.
Zusätzlich zu den unten genannten Kern-Tools existieren dynamische Tools aus dem Workflow-System: ActionToolAdapter liest methodDiscovery.methods und registriert jede Action mit dynamicMode=True unter einem zusammengesetzten Namen ({methodShort}_{actionName}); im Adapter sind diese derzeit alle als nicht-readOnly registriert.
Tool-Generierung aus dem Catalog (Typed Action Architecture, 2026-04):
ActionToolAdapterleitet das JSON-Schema fuer jeden Tool-Aufruf direkt aus der typisierten Action-Signatur ab (Pflicht-Felder, Typen, Default-Werte,FeatureInstanceRef-Discriminator). Es gibt keine handgepflegte Heuristik mehr -- Editor, AI-Agent und Adapter konsumieren denselben Catalog (/api/automation2/catalog). Aenderungen an einer Action-Signatur schlagen automatisch auf das Tool-Schema durch; veraltete oder gedriftete Felder werden vom Snapshot-Testtest_staticNodesHaveNoDriftAgainstLiveMethodsblockiert (_KNOWN_ADAPTER_DRIFTS = frozenset()). Details:wiki/c-work/3-validate/2026-04-typed-action-architecture.md.
Toolbox-Zuordnung: Kern-Tools sind den Toolboxes core, ai und datasources zugeordnet (siehe Toolbox Registry oben). Connection-abhaengige Tools (email, sharepoint, clickup, jira) werden nur aktiviert, wenn der User eine passende Connection hat. Workflow-Editing-Tools (workflow Toolbox) werden separat via workflowTools.py registriert.
Kern-Tools (registriert via registerCoreTools → coreTools/; Stand 2026-04 inkl. UDM-Helfer)
Workspace / Dateien
| Tool | Kurzbeschreibung |
|---|---|
readFile |
Dateiinhalt lesen (optional zeilenweise) |
listFiles |
Dateien filtern (Ordner, Tags, Suche) |
searchInFileContent |
Suche im Dateiinhalt |
writeFile |
Datei anlegen / anhängen / überschreiben |
deleteFile |
Datei löschen |
renameFile |
Umbenennen |
moveFile |
Verschieben (Ordner) |
tagFile |
Tags setzen |
copyFile |
Unabhängige Kopie |
replaceInFile |
Ersetzen im Text |
Ordner
| Tool | Kurzbeschreibung |
|---|---|
listFolders |
Ordner auflisten |
createFolder |
Ordner anlegen |
deleteFolder |
Ordner löschen (optional rekursiv) |
renameFolder |
Ordner umbenennen |
moveFolder |
Ordner verschieben |
Web & Sprache
| Tool | Kurzbeschreibung |
|---|---|
webSearch |
Websuche |
readUrl |
Inhalt einer bekannten URL laden |
translateText |
Übersetzung (Voice/Translation-Pipeline) |
textToSpeech |
TTS |
speechToText |
Transkription Audio-Datei |
detectLanguage |
Spracherkennung für Text |
Externe Datenquellen / Mail
| Tool | Kurzbeschreibung |
|---|---|
listConnections |
User-Connections auflisten |
browseDataSource |
Externe Quelle durchsuchen |
searchDataSource |
Suche in Datenquelle |
downloadFromDataSource |
Download → FileItem (inkl. Vererbung neutralize auf Datei, siehe Invarianten) |
uploadToExternal |
Upload zu externer Quelle |
sendMail |
E-Mail senden |
Dokumente / Content-Objekte
| Tool | Kurzbeschreibung |
|---|---|
browseContainer |
Struktur-Index (Seiten, Abschnitte, …) |
readContentObjects |
Gezielt Content-Objekte lesen |
extractContainerItem |
Element aus Container extrahieren |
summarizeContent |
KI-Zusammenfassung |
getUdmStructure |
UDM-JSON: Überblick (Knoten, Struktur, Block-Zahlen); udmJson als stringifiziertes Objekt |
walkUdmBlocks |
UDM traversieren: alle ContentBlock-Knoten mit Pfad und Kurz-Preview |
filterUdmByType |
UDM: alle Blöcke mit gegebenem contentType (z. B. table, image) |
describeImage |
Vision-Analyse |
renderDocument |
Dokument rendern |
generateImage |
Bildgenerierung |
createChart |
Chart erzeugen |
Feature / Workflow
| Tool | Kurzbeschreibung |
|---|---|
queryFeatureInstance |
Abfrage anderer Feature-Instanz (setzt bei Bedarf requireNeutralization für Sub-Calls). Sub-Agent hat browseTable, queryTable und aggregateTable (SUM/COUNT/AVG/MIN/MAX mit GROUP BY). DB-Connection-Pooling und Result-Caching (5 Min TTL). System-Prompt wird aus drei Schichten gebaut: (1) generischer Header mit Tabellen + Pydantic-Schema (_buildSchemaContext), (2) generische Regeln inkl. kein SUM/AVG auf bereits aggregierten Saldo-/Total-Feldern (closingBalance, openingBalance, debitTotal, creditTotal, …), (3) optionale feature-spezifische Domain-Hints via getAgentDomainHints() in mainXxx.py — z. B. KMU-Kontoplan-Präfixe (1xxx Aktiven, 102x Bank, 100x Kasse), periodMonth=0-Konvention und kanonische Query-Patterns für Trustee. Round-/Cost-Budget wird vom Parent-Agent geerbt (AgentConfig.maxRounds → Tool-Context → runFeatureDataAgent). |
listWorkflowHistory |
Workflow-Historie |
readWorkflowMessages |
Nachrichten eines Workflows lesen |
Sicherheit / Ausführung
| Tool | Kurzbeschreibung |
|---|---|
neutralizeData |
Anonymisierter Text (non-destructive) |
executeCode |
Sandboxed Code-Ausführung (siehe sandboxExecutor.py) |
AI-Core (Provider-Abstraction)
| Komponente | Datei / Rolle |
|---|---|
| Model Registry | aicoreModelRegistry.py — registriert Provider-Plugins und Modelle |
| Model Selector | aicoreModelSelector.py — Auswahl nach Operationstyp, Promptgrösse, Restriktionen, Ranking |
Provider-Plugins (Dateien unter gateway/modules/aicore/)
| Plugin-Modul | Typische Rolle |
|---|---|
aicorePluginAnthropic.py |
Claude-Modelle |
aicorePluginOpenai.py |
GPT, Embeddings, Bild — modellabhaengiges Payload-Tuning: GPT-5.x und o-Serie (o1/o3/o4) sind Reasoning-Modelle und akzeptieren weder max_tokens (-> immer max_completion_tokens) noch ein custom temperature (-> Feld bei diesen Modellen weggelassen, OpenAI erzwingt sonst HTTP 400 unsupported_value) |
aicorePluginMistral.py |
Mistral Chat / Embed |
aicorePluginPerplexity.py |
Sonar / Recherche |
aicorePluginTavily.py |
Web-Suche |
aicorePluginPrivateLlm.py |
Private LLM |
aicorePluginInternal.py |
Interne Extraktion/Generierung/Rendering |
Operation Types (datamodelAi.OperationTypeEnum, Auszug)
u. a. plan, dataAnalyse, dataGenerate, dataExtract, imageAnalyse, imageGenerate, neutralizationText, neutralizationImage, webSearch, webCrawl, agent, embedding, speechTeams.
Der Agent-Loop verwendet AGENT für Hauptrunden und DATA_ANALYSE für Summarization-Calls; Embeddings laufen über die Knowledge-Service-Pipeline (callEmbedding).
Knowledge Store (RAG)
Datenmodelle (datamodelKnowledge.py)
| Modell | Zweck |
|---|---|
| FileContentIndex | Strukturindex pro Datei (ohne LLM): structure, objectSummary, status, Spiegelung von Mandat/Instanz über scope (personal, featureInstance, mandate, global), Neutralisierungs-Felder isNeutralized, neutralizationStatus |
| ContentChunk | Persistente Chunks mit Embedding (vector(1536)), data, contextRef, contentType |
| RoundMemory | Pro Runde: file_ref, Tool-Ergebnisse, Entscheidungen — mit Embedding für semantische Wiederverwendung trotz ConversationManager-Kürzung |
| WorkflowMemory | Workflow-scoped Key-Value-Cache (Entities, Fakten, inkl. optional Embedding) |
Zugriff über interfaceDbKnowledge (FileContentIndex, ContentChunk, RoundMemory, WorkflowMemory).
Indexierung
KnowledgeService.indexFile — nach Extraktion (Content-Objekte): übernimmt Scope aus FileItem als Single Source of Truth; bei FileItem.neutralize=True werden Inhalte vor dem Speichern neutralisiert; Chunking (u. a. DEFAULT_CHUNK_TOKENS / Zeichen-Heuristik), Embedding via AI-Service, Persistenz von Index + Chunks; optional Billing-Reconciliation für Mandats-Speicher.
Semantische Suche & Kontext
buildAgentContext — priorisierte Schichten (Ist-Code, vereinfacht):
- RoundMemory
file_ref(„Known Files“) - Instance/personal/mandate/global —
semanticSearchmit Query-Embedding (Limit/Score wie im Code) - RoundMemory semantisch (
semanticSearchRoundMemory) - Workflow-Entities (
getWorkflowEntities) - Mandate-Scope — geteilte Mandats-Dokumente („Shared Knowledge“)
- Optional Cross-Workflow-Hints (
workflowHintItems)
Rückgabe: formatierter String für Injektion in den Agent-Systemkontext. Wenn Embedding fehlschlägt, liefert buildAgentContext einen leeren String (Agent arbeitet ohne diesen RAG-Block).
Erweiterte Hilfen (z. B. readSection, Caching) für selektives Lesen sind im selben Service dokumentiert.
Teamsbot-Integration (Hybrid-Routing, kein eigenes Toolset)
Der Teamsbot ruft den selben AgentService.runAgent über das ServiceCenter auf — es gibt kein Teamsbot-spezifisches Toolset. Aufrufer ist gateway/modules/features/teamsbot/service.py::_runAgentForMeeting mit AgentConfig(maxRounds=5, maxCostCHF=0.10, toolSet="core", initialToolboxes=["core","web"], excludeActionTools=True).
| Trigger | Pfad | Wer ruft runAgent? |
|---|---|---|
| Operator schickt Director Prompt (One-Shot oder Persistent) via Regie-Panel | routeFeatureTeamsbot.submitDirectorPrompt → service.submitDirectorPrompt → asyncio.create_task(_processDirectorPrompt) → _runAgentForMeeting |
direkt, umgeht SPEECH_TEAMS |
SPEECH_TEAMS setzt needsAgent=true + agentReason (z. B. „recherchier das im Internet") |
service._analyzeAndRespond erkennt needsAgent, ruft _runAgentForMeeting mit taskBrief = agentReason |
Eskalation aus dem schnellen Pfad |
FINAL-Delivery: Der FINAL-Event-Text wird im Teamsbot-Service über die bestehenden Kanäle (TTS + sendChatMessage) ins Meeting gespielt — der Agent „spricht" nicht selbst, sondern liefert nur den Text. Damit braucht es kein Teamsbot-spezifisches Tool wie sendChat oder readAloud im Agent.
Workflow-ID-Konvention: workflowId = f"teamsbot:{sessionId}" — RoundMemory und RAG akkumulieren pro Meeting, getrennt von anderen Sessions.
Persistente Direktiven: service._buildPersistentDirectorContext rendert aktive persistent-Direktiven als OPERATOR_DIRECTIVES-Block in den SPEECH_TEAMS-Kontext, damit sie auch ohne erneuten Director-Prompt-Aufruf wirken (z. B. „Antworte immer in Englisch").
Siehe b-reference/teams-bot/architecture.md für die vollständige Hybrid-Architektur und Director-Prompt-Lifecycle.
Schlüssel-Dateien
| Datei | Rolle |
|---|---|
gateway/modules/serviceCenter/registry.py |
Registrierung agent, knowledge, Dependencies, objectKey |
gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py |
AgentService, runAgent, Prompt-Enrichment, Registry-Orchestrierung |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/registerCore.py |
Orchestrator: delegiert Tool-Registrierung an Domänen-Module |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_workspaceTools.py |
Dateien, Ordner, Web, Übersetzung |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_connectionTools.py |
Externe Connections, Upload, Mail |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py |
DataSource Browse/Search/Download |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_documentTools.py |
Container, Content-Objects, Vision |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py |
Rendering, TTS, STT, Bildgenerierung, Charts, Neutralize, Code |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_featureSubAgentTools.py |
Feature Data Sub-Agent (queryFeatureInstance) |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_crossWorkflowTools.py |
Workflow-Historie, Messages, _CORE_ONLY_TOOLS-Tagging |
gateway/modules/serviceCenter/services/serviceAgent/agentLoop.py |
ReAct-Loop, Budget, RAG-Injektion, Tool-Dispatch, Summaries |
gateway/modules/serviceCenter/services/serviceAgent/toolRegistry.py |
Registrierung, Dispatch, readOnly, Function-Calling-Format |
gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py |
Kontextfenster, Summarization, Systemprompt |
gateway/modules/serviceCenter/services/serviceAgent/datamodelAgent.py |
AgentConfig, Events, Trace-Modelle |
gateway/modules/serviceCenter/services/serviceAgent/actionToolAdapter.py |
Workflow-Actions → Agent-Tools (Schema-Generierung; Param-Validierung erfolgt zentral im ActionExecutor) |
gateway/modules/workflows/processing/shared/parameterValidation.py |
Universelle Action-Parameter-Validierung + Coercion (Required-Enforcement, Ref-Schema → id-String, Primitive-Coercion); aufgerufen aus ActionExecutor.executeAction für alle Aufrufpfade (Agent, Workflow-Graph, REST) |
gateway/modules/connectors/connectorDbPostgre.py |
DB-Connector; Read-Methoden raisen DatabaseQueryError bei echten Query-Fehlern (Postgres-Adapt, UndefinedTable/Column, OperationalError, …); Empty Result Sets bleiben []/None |
gateway/modules/serviceCenter/services/serviceAgent/toolboxRegistry.py |
Toolbox-Definitionen, requestToolbox Meta-Tool-Schema |
gateway/modules/serviceCenter/services/serviceAgent/workflowTools.py |
Workflow-Editing-Tools (readWorkflowGraph, addNode, ...) |
gateway/modules/serviceCenter/services/serviceAgent/sandboxExecutor.py |
executeCode-Sandbox |
gateway/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py |
Index, buildAgentContext, Workflow-/Round-Memory-Helfer |
gateway/modules/interfaces/interfaceDbKnowledge.py |
DB-Zugriff Knowledge / RAG |
gateway/modules/datamodels/datamodelKnowledge.py |
FileContentIndex, ContentChunk, RoundMemory, WorkflowMemory |
gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py |
Zentrales Neutralisierungs-Gate vor LLM, Billing, Provider-Aufruf |
gateway/modules/aicore/aicoreModelRegistry.py |
Provider-/Modell-Registry |
gateway/modules/aicore/aicoreModelSelector.py |
Modellauswahl |
gateway/modules/aicore/aicorePlugin*.py |
Provider-Implementierungen |
gateway/modules/datamodels/datamodelAi.py |
OperationTypeEnum, AiCallRequest, Optionen |
Regeln / Invarianten
- Neutralisierung vor dem Modell: Alle an ein LLM gehenden Inhalte (Prompt, Kontext, Messages) werden ausschliesslich in
serviceAientsprechend Policy neutralisiert bzw. blockiert — nicht in den Agent-Tools. - Tools liefern Rohdaten: Kein zweites Neutralisieren in Tools; Ausnahme ist bewusste Data-at-rest-Neutralisierung beim Indexieren (
indexFilebeiFileItem.neutralize=True). - DataSource → FileItem:
_downloadFromDataSourcevererbt dasneutralize-Flag der DataSource auf erzeugte Dateien;queryFeatureInstancekann Sub-Agent-Calls mitrequireNeutralization=Trueauslösen, wenn die Feature-Datenquelle das vorsieht. - Kein Tool-RBAC: Autorisierung über Datenbank-, Connection- und Service-Schicht.
- Parallele Tools nur bei
readOnly=True: Schreibende Tools nicht parallel zueinander aus demselben Batch. - RAG optional: Embedding- oder DB-Fehler im Kontextpfad führen nicht zwingend zum Abbruch des Agent-Runs (RAG wird übersprungen/entfällt).
- Billing: AI-Calls und eingebettete Pfade (Embeddings, Summaries) laufen über bestehende Billing-/Subscription-Checks in
serviceAibzw. aufrufenden Schichten.
Detaillierte Neutralisierungs-Kette und Compliance: wiki/compliance/Neutralisierung.md.
Architektur-Roadmap (Unified Workspace, ProviderConnector 1:n, Phasenplan): wiki/concepts/AI-Agent-Architecture-Konzept.md — als Zielbild lesen, nicht als Ist-Abbild aller Codepfade.