# Automation Unification -- Konsolidierung v1/v2/Workspace ## Beschreibung und Kontext PowerOn hat drei teilweise ueberlappende Automatisierungssysteme: Automation v1 (Template+Cron), Automation2 (n8n-Style Graph-Editor), und AI Workspace (Chat-Agent mit Tools). Alle nutzen die gleiche Action Library (`workflows/methods/` + `ActionExecutor`), aber unterschiedliche Persistenz, Scheduler und Execution Engines. **Business-Treiber:** Inkonsistenzen zwischen den Systemen (zwei DB-Schemas, zwei Scheduler-Patterns, zwei Engines) erhoehen den Wartungsaufwand und erschweren Feature-Paritaet. **Risiko bei Nicht-Umsetzung:** Wachsende Divergenz, doppelter Aufwand bei neuen Aktionen, inkonsistentes Verhalten fuer Endbenutzer. **Leitprinzipien:** - Keine Migration von Altdaten -- Automation v1 ist nicht produktiv, wird nicht migriert - Automation v1 bleibt bestehen, bis es durch das neue Feature abgeloest ist - Klare Datenmodelle mit definierten State Machines als stabile, erweiterbare Grundlage - Klare Trennung von Mandant/Feature/Feature-Instanz und zugehoeriger RBAC-Logik - Skalierbare AI-Tool-Architektur mit Toolboxes und Sub-Agents fuer 50-100+ Tools --- ## Ist-Analyse ### Drei bestehende Systeme | System | Modell | Staerke | Schwaeche | |--------|--------|---------|-----------| | **Automation v1** | Template + Cron + Placeholders | Stabiles, bewaehrtes Scheduling mit Callback-basiertem Sync, inkrementeller Job-Registrierung, Execution-Logs | Kein visuelles Editing, kein Branching, starres Template-Modell | | **Automation2** | Graph-Editor (n8n-style) + Run/Task-Modell | Visuelles Modellieren, Branching, Loops, Human-in-the-loop, Pause/Resume, diverse Node-Typen | Kein AI-Chat im Editor, kein UDB-Zugang, Scheduler-Sync weniger robust als v1 | | **AI Workspace** | Chat-driven AI Agent mit Tools | Natural-language Interaktion, 40+ Core Tools + Action Tools, Streaming, RAG | Kein persistiertes Workflow-Modell, kein Scheduling | ### Wer nutzt die Method/Action Library? Die `workflows/methods/` Library (methodAi, methodOutlook, methodSharepoint, etc.) ist die de facto gemeinsame Basis. Alle Systeme nutzen `ActionExecutor.executeAction()`: | Consumer | Pfad zur Action Library | |----------|------------------------| | **Workspace AI Agent** | `ActionToolAdapter` registriert `dynamicMode=True` Actions als Agent-Tools -> LLM waehlt Tool -> `ActionExecutor` | | **Automation2 Graph Engine** | `ActionNodeExecutor` liest `_method`/`_action` aus Node-Definition -> `ActionExecutor` | | **Automation v1** | `WorkflowProcessor` -> `modeAutomation` -> `ActionExecutor` | | **Workspace Dynamic Mode** | `WorkflowProcessor` -> `modeDynamic` -> AI plant Tasks -> `ActionExecutor` | ### Wer nutzt die AI Tools? | Tool-Typ | Registrierung | Nutzung | |----------|--------------|---------| | **Core Tools** (40: readFile, webSearch, sendMail, etc.) | `_registerCoreTools()` | Workspace Agent, CommCoach Agent | | **Action Tools** (dynamicMode Actions) | `ActionToolAdapter.registerAll()` | Workspace Agent (toolSet-uebergreifend) | | **Graph Node Types** (ai.prompt, email.checkEmail, etc.) | `STATIC_NODE_TYPES` in `nodeDefinitions/` | Automation2 `executeGraph` | ### Identifizierte Inkonsistenzen | # | Inkonsistenz | |---|-------------| | **I-1** | Zwei Datenmodelle: `AutomationDefinition` (v1) vs `Automation2Workflow` (v2) -- kein gemeinsames Workflow-Konzept | | **I-2** | Zwei Datenbanken: `poweron_automation` vs `poweron_automation2` | | **I-3** | Zwei Scheduler: v1 (inkrementell, callback `automation.changed`) vs v2 (full wipe + re-register, callback `automation2.workflow.changed`) | | **I-4** | Zwei Execution Engines: `WorkflowManager` + `WorkflowProcessor` vs `executionEngine.executeGraph` | | **I-5** | Kein AI-Chat im Graphical Editor | | **I-6** | `WorkspaceInput` ist nicht als wiederverwendbare Komponente extrahiert | | **I-7** | UDB nur in Workspace und CommCoach, nicht im Graphical Editor | | **I-8** | AI Tools skalieren nicht: 50+ Tools ohne Gruppierungslogik, alle werden dem LLM gleichzeitig exponiert | --- ## Ziel-Architektur: Vier Saeulen ``` +----------------------------------------------------------------------+ | POWERON UNIFIED PLATFORM | | | | +--------------+ +-----------------------+ +------------------+ | | | AI Service | | Graphical Editor | | Automation | | | | (Saeule 1) | | (Saeule 2) | | Scheduler | | | | | | | | (Saeule 3) | | | | - AI Gateway | | - Visual Flow Builder | | | | | | - Agent Loop | | - Node Palette | | - Cron Engine | | | | - Toolboxes | | - AI Chat Sidebar | | - Event Trigger | | | | - Sub-Agents | | - UDB Integration | | - Webhook Entry | | | | - Streaming | | - Tracing/Debug Log | | - Run History | | | | - Failover | | - Test Runner | | - Monitoring | | | | - Neutraliz. | | - Version Management | | - Notifications | | | +------+--------+ +-----------+-------------+ +--------+---------+ | | | | | | | +------v------------------------v---------------------------v----------+ | | | SAEULE 4: UNIFIED ACTION LIBRARY (Toolboxes) | | | | | | | | Toolbox "ai" | Toolbox "email" | Toolbox "files" | | | | ai.process | outlook.readEmails | context.extract | | | | ai.webResearch | outlook.searchEmails | context.neutralize | | | | ai.summarize | outlook.draftEmail | file.create | | | | ai.translate | outlook.sendDraft | context.transform | | | | | | | | | | Toolbox "sharepoint"| Toolbox "clickup" | Toolbox "jira" | | | | sharepoint.findDoc | clickup.searchTasks | jira.connect | | | | sharepoint.readDocs | clickup.createTask | jira.exportTickets | | | | sharepoint.upload | clickup.updateTask | jira.importTickets | | | | | | | | | | Toolbox "workflow" | Toolbox "trustee" | [future toolboxes] | | | | readWorkflowGraph | trustee.extract | slack.sendMessage | | | | addNode, removeNode | trustee.process | teams.postChannel | | | | connectNodes | trustee.syncAcctng | google.readDrive | | | +----------------------------------------------------------------------+ | +------------------------------------------------------------------------+ ``` ### Saeule 1: AI Service (besteht, zu erweitern) **Status:** Kern bereit (`serviceAi` + `serviceAgent` + `aicore`). **Erweiterung:** Toolbox-Architektur fuer 50-100+ Tools (siehe Abschnitt Toolboxes). ### Saeule 2: Graphical Editor (Feature "graphicalEditor") **Status:** Basis vorhanden (Automation2FlowEditor), zu erweitern. | # | Funktionalitaet | Status | |---|-----------------|--------| | 1 | **Visual Flow Builder** -- Canvas, Nodes, Connections, Branching, Loops | besteht | | 2 | **Node Palette** -- abgeleitet aus Toolbox-Registry, jede Action kann als Node exponiert werden | besteht | | 3 | **AI Chat Sidebar** -- Agent mit Toolbox `"workflow"` fuer Graph-Manipulation | neu | | 4 | **UDB Integration** -- UnifiedDataBar im Editor | neu | | 5 | **Tracing/Debug Log** -- RunStepLog mit Input-Snapshot, Duration, Error pro Node | zu erweitern | | 6 | **Test Runner** -- Visual Highlighting, Step-by-Step, AI-assisted Debugging | zu erweitern | | 7 | **Version Management** -- Draft/Published Lifecycle | neu | ### Saeule 3: Automation Scheduler (zu konsolidieren) **Von v1 uebernehmen:** - Inkrementeller Sync (nur geaenderte Jobs registrieren/entfernen, nicht full wipe) - Persistierter Job-Handle (`eventId`) auf dem Workflow fuer Debugging - `automation.changed` Callback-Pattern fuer reaktive Synchronisation - Handler laedt Workflow neu und prueft `active` vor Execution - Execution-Logs als Audit Trail (capped auf 50 Eintraege) - Creator-User Tracking via `sysCreatedBy` fuer Execution-Kontext **Von v2 beibehalten:** - `IntervalTrigger` fuer einfache Wiederholungen - Main-Loop-Bridging (`call_soon_threadsafe`) fuer Thread-Safety - Delayed Startup Sync (5s) fuer DB-Readiness - Striktes Cron-Parsing (5/6 Felder) ### Saeule 4: Unified Action Library mit Toolboxes **Status:** Infrastruktur besteht (`workflows/methods/` + `methodDiscovery` + `ActionExecutor`). Zu erweitern um Toolbox-Konzept (siehe eigener Abschnitt). --- ## Fokus und kritische Details - Scheduler-Robustheit: v1 hat bewaehrtes inkrementelles Pattern, v2 macht Full-Wipe-Re-Register - Tool-Skalierung: 40+ Core-Tools + dynamische Actions -- flache Tool-Listen skalieren nicht - Human-in-the-Loop: Pause/Resume nur in v2 implementiert - Keine Migration von v1-Altdaten (nicht produktiv) --- ## Ziel und Nicht-Ziele - **Ziel:** Einheitliche Plattform mit v1-Scheduler-Robustheit und v2-Graph-Faehigkeiten - **Ziel:** Toolbox-Architektur fuer skalierbare Tool-Verwaltung (requestToolbox, availableToolboxes) - **Ziel:** Einheitliches Workflow/Version/Run Datenmodell (Draft -> Published, Runs, Tracing) - **Explizit NICHT:** Migration von v1-Daten; Abloesung des AI Workspace Chat-Modells --- ## Mandant/Feature/RBAC-Architektur ### Hierarchie ``` System (PowerOn Platform) | +-- System Template Rollen (isSystemRole=true, mandateId=null, featureCode=null) | "admin", "user", "viewer" | -> kopiert bei Mandant-Erstellung via copySystemRolesToMandate() | +-- Feature Template Rollen (isSystemRole=false, mandateId=null, featureCode=X) | "workspace-admin", "workspace-user", "workspace-viewer" | "graphicalEditor-admin", "graphicalEditor-user", ... | -> kopiert bei Feature-Instanz-Erstellung via _copyTemplateRoles() | +-- Mandant (Tenant) +-- Mandate-Rollen (kopiert von System Templates) | mandateId=X, featureInstanceId=null, featureCode=null | "admin", "user", "viewer" | -> UserMandateRole <- UserMandate <- User | +-- Feature-Instanz A (z.B. "AI Workspace Produktion") | +-- Feature-Instanz-Rollen (kopiert von Feature Templates) | | mandateId=X, featureInstanceId=Y, featureCode="workspace" | | "workspace-admin", "workspace-user", "workspace-viewer" | | -> FeatureAccessRole <- FeatureAccess <- User | +-- Daten (Workflows, Files, Chats, ...) | +-- Feature-Instanz B (z.B. "Graphical Editor Dev") +-- Feature-Instanz-Rollen +-- Daten (Workflows, Runs, Tasks, ...) ``` ### Zwei getrennte Template-Rollen-Systeme **System Template Rollen (fuer Mandanten)** | Feld | Wert | |------|------| | `isSystemRole` | `true` | | `mandateId` | `null` | | `featureInstanceId` | `null` | | `featureCode` | `null` | | `roleLabel` | `"admin"`, `"user"`, `"viewer"` | | Kopier-Mechanismus | `copySystemRolesToMandate()` in `interfaceBootstrap.py` | | Ausgeloest bei | Mandant-Erstellung | **Feature Template Rollen (fuer Feature-Instanzen)** | Feld | Wert | |------|------| | `isSystemRole` | `false` | | `mandateId` | `null` | | `featureInstanceId` | `null` | | `featureCode` | z.B. `"workspace"`, `"automation2"` | | `roleLabel` | z.B. `"workspace-admin"`, `"workspace-user"` | | Kopier-Mechanismus | `_copyTemplateRoles()` in `interfaceFeatures.py` | | Ausgeloest bei | Feature-Instanz-Erstellung | **Wichtig:** Templates werden **nur bei Erstellung** kopiert. Spaetere Aenderungen werden **nicht** automatisch propagiert. ### RBAC-Resolution (Priority-System) | Rollen-Typ | Scope | RBAC Priority | |------------|-------|---------------| | **Global/Template** | `mandateId=null, featureInstanceId=null` | 1 (niedrigste) | | **Mandate-Rolle** | `mandateId=X, featureInstanceId=null` | 2 | | **Instanz-Rolle** | `mandateId=X, featureInstanceId=Y` | 3 (hoechste) | **Resolution:** 1. Lade Mandate-Rollen (via `UserMandate` -> `UserMandateRole`) 2. Lade Feature-Instanz-Rollen (via `FeatureAccess` -> `FeatureAccessRole`) 3. Lade `AccessRules` fuer alle gefundenen Rollen 4. **Hoechste Prioritaet gewinnt** bei DATA-Permissions 5. **View wird OR-verknuepft** ueber alle Rollen der hoechsten Prioritaetsstufe 6. **Item-Spezifitaet** innerhalb einer Stufe: exact > prefix > generic ### RBAC-Datenmodell ```python class Role(PowerOnModel): mandateId: Optional[str] # null = Global/Template featureInstanceId: Optional[str] # null = nicht Instanz-spezifisch featureCode: Optional[str] # z.B. "workspace", "graphicalEditor" roleLabel: str # z.B. "workspace-admin" isSystemRole: bool class AccessRule(PowerOnModel): roleId: str # FK -> Role context: AccessRuleContext # DATA | UI | RESOURCE item: Optional[str] # null=generic, oder spezifisch view: bool read: Optional[AccessLevel] # "n" | "o" | "m" | "a" create: Optional[AccessLevel] update: Optional[AccessLevel] delete: Optional[AccessLevel] class AccessLevel(str, Enum): NONE = "n" OWN = "o" MANDATE = "m" ALL = "a" ``` ### AccessRule-Kontexte | Kontext | Item-Beispiel | Prueft | |---------|---------------|--------| | `UI` | `ui.feature.graphicalEditor.editor` | Seitenleiste, Navigation, Buttons | | `RESOURCE` | `resource.feature.graphicalEditor.execute` | Ausfuehrungsberechtigungen | | `DATA` | Auto-generiert aus Tabellenname + featureCode | CRUD auf Datenbank-Ebene | --- ## AI-Tool-Architektur: Toolboxes und Sub-Agents ### Problem: Tool-Explosion Aktuell werden ~50 Tools dem LLM in einem einzigen Prompt exponiert. Bei 100+ Tools: - LLM-Context wird ueberladen -> schlechtere Tool-Auswahl - Irrelevante Tools fuer den aktuellen Kontext -> Confusion und Halluzinationen - Keine Feature-isolierte Tool-Bereitstellung ### Konzept: Toolboxes Eine **Toolbox** ist eine thematisch gebuendelte Gruppe von Tools mit einem klaren Scope: ``` Toolbox-Registry | +-- Toolbox "core" (immer verfuegbar) | readFile, listFiles, searchInFileContent, writeFile, deleteFile, | renameFile, copyFile, listFolders, createFolder, deleteFolder, | webSearch, readUrl, detectLanguage, translateText | +-- Toolbox "ai" (AI-Operationen) | summarizeContent, describeImage, generateImage, textToSpeech, | speechToText, renderDocument, createChart, executeCode, neutralizeData | +-- Toolbox "datasources" (Externe Verbindungen) | listConnections, browseDataSource, searchDataSource, | downloadFromDataSource, uploadToExternal, browseContainer, | readContentObjects, extractContainerItem | +-- Toolbox "email" (Outlook-Integration, requiresConnection="outlook") | sendMail, outlook.readEmails, outlook.searchEmails, outlook.draftEmail | +-- Toolbox "sharepoint" (requiresConnection="sharepoint") | sharepoint.findDoc, sharepoint.read, sharepoint.upload, ... | +-- Toolbox "clickup" (requiresConnection="clickup") | clickup.searchTasks, clickup.createTask, ... | +-- Toolbox "jira" (requiresConnection="jira") | jira.connect, jira.exportTickets, ... | +-- Toolbox "workflow" (Graph-Manipulation fuer Editor-Chat) | readWorkflowGraph, addNode, removeNode, connectNodes, | setNodeParameter, listAvailableNodeTypes, validateGraph, | listWorkflowHistory, readWorkflowMessages | +-- Toolbox "trustee" (featureCode="trustee") | trustee.extract, trustee.process, trustee.syncAccounting | +-- Toolbox "chatbot" (featureCode="chatbot") chatbot.queryDatabase ``` ### Toolbox-Bereitstellung: Initiales Set + dynamische Eskalation **Kern-Idee:** Der Agent startet mit einem kompakten Tool-Set. Er kennt den **Katalog aller verfuegbaren Toolboxes**. Wenn er Spezial-Tools braucht, kann er eine Toolbox **anfordern** -- und bekommt sie in der naechsten Runde. ``` Runde 1: Tools: core (readFile, webSearch, ...) + requestToolbox System-Prompt: "Verfuegbare Toolboxes: email, sharepoint, clickup, ..." User: "Lies meine letzten Emails und fasse sie zusammen" LLM: -> requestToolbox("email") [braucht Email-Tools] Runde 2: Tools: core + email (sendMail, outlook_readEmails, ...) + requestToolbox LLM: -> outlook_readEmails(connectionRef, folder="Inbox", limit=10) Runde 3: LLM: -> (Text-Antwort mit Zusammenfassung) -> COMPLETED ``` **`requestToolbox` Meta-Tool:** ```python { "name": "requestToolbox", "description": "Request additional specialized tools for the current task.", "parameters": { "toolboxId": { "type": "string", "enum": [...] # Dynamisch: nur verfuegbare Toolboxes }, "reason": {"type": "string"} } } ``` ### Toolbox-Datenmodell ```python class ToolboxDefinition(BaseModel): id: str # z.B. "core", "email", "sharepoint" label: Dict[str, str] # Multilingual: {"en": "Email", "de": "E-Mail"} description: str featureCode: Optional[str] # null = Feature-unabhaengig tools: List[str] # Tool-Namen in dieser Toolbox isDefault: bool = False # Immer aktiv (z.B. "core") requiresConnection: Optional[str] # "outlook" -> auto-aktiviert bei Connection class ToolboxRegistry: _toolboxes: Dict[str, ToolboxDefinition] _toolRegistry: ToolRegistry def registerToolbox(self, toolbox: ToolboxDefinition): ... def getActiveToolboxes(self, featureCode, userConnections, explicitToolboxes): ... def getToolsForToolboxes(self, activeToolboxIds) -> List[ToolDefinition]: ... class AgentConfig(BaseModel): maxRounds: int = 25 maxCostCHF: Optional[float] = None initialToolboxes: List[str] = ["core"] availableToolboxes: List[str] = [] temperature: Optional[float] = None ``` ### Toolbox-Zuordnung pro Feature | Feature | `initialToolboxes` | `availableToolboxes` (anforderbar) | |---------|--------------------|------------------------------------| | **Workspace** | `["core"]` | `["ai", "datasources", "email", "sharepoint", "clickup", "jira", "workflow"]` -- gefiltert nach User-Connections | | **Graphical Editor Chat** | `["core", "workflow"]` | `["ai"]` | | **CommCoach** | `["core"]` | `[]` (keine Eskalation erlaubt) | | **Chatbot** | `["core"]` | `[]` | **Automatische Toolbox-Verfuegbarkeit:** Welche Toolboxes in `availableToolboxes` erscheinen, haengt von den aktiven User-Connections ab. Keine Outlook-Verbindung -> `"email"` erscheint nicht. ### Sub-Agents (Feature Data Agents) ``` User Prompt im Workspace | v Main Agent (Toolboxes: core, ai, datasources, ...) | +-- Tool: queryFeatureInstance("trustee", instanceId, question) | | | v | Sub-Agent (Feature Data Agent) | Eigene ToolRegistry: browseTable, queryTable | Eigenes System-Prompt mit Schema-Wissen | Limitiert: maxRounds=5, maxCost=0.10 CHF | | | v | Antwort zurueck an Main Agent | +-- Tool: readFile(fileId) -- Core Tool, direkt +-- Tool: outlook_readEmails(connectionRef) -- Action Tool, direkt ``` **Faustregel:** Toolbox wenn die Tools generisch sind. Sub-Agent wenn Feature-spezifisches Kontext-/Schema-Wissen noetig ist. ### State Machine: AI Agent Run (mit Toolbox-Eskalation) ``` +----------+ | IDLE | +----+-----+ | runAgent(prompt, initialToolboxes, availableToolboxes) v +----------+ +---->| THINKING |<-------------------------------+ | +----+-----+ | | | | | +-- LLM Response (Text only) ----------+---> COMPLETED | | | | +-- LLM Response (ToolCalls) | | | | | v | | +--------------------+ | | | EXECUTING_TOOLS | | | +----+---------------+ | | | | | +-- requestToolbox(id) --+ | | | v | | | +---------------------+ | | | | TOOLBOX_ESCALATION | | | | | Toolbox aktiviert | | | | | Tools erweitert | | | | +----------+----------+ | | | | | | +-- Sub-Agent call --+ | | | | v | | | | +-------------+| | | | |SUB_AGENT_CALL|| | | | +------+------+| | | | | | | | +-- Regular tools-+-------+-------------+ | (results -> next round) | | maxRounds / maxCost reached v +----------+ | COMPLETED | (oder FAILED / CANCELLED) +----------+ ``` --- ## Unified Workflow Datenmodell (Ziel) ### Entitaeten ```python class WorkflowStatus(str, Enum): DRAFT = "draft" PUBLISHED = "published" ARCHIVED = "archived" class Workflow(PowerOnModel): id: str mandateId: str featureInstanceId: str label: str description: Optional[str] tags: List[str] = [] isTemplate: bool = False templateSourceId: Optional[str] # geklont von diesem Template currentVersionId: Optional[str] # aktive/published Version active: bool = True # Scheduler-enabled eventId: Optional[str] # APScheduler Job-ID (v1-Pattern) class WorkflowVersion(PowerOnModel): id: str workflowId: str # FK -> Workflow versionNumber: int # auto-increment status: WorkflowStatus # draft | published | archived graph: Dict[str, Any] # { nodes: [...], connections: [...] } invocations: List[Dict[str, Any]] # Entry-Points (manual, schedule, webhook, form) publishedAt: Optional[float] publishedBy: Optional[str] ``` ### State Machine: WorkflowVersion.status ``` +-------+ create --->| DRAFT |<--- edit (graph mutation via API oder AI Tools) +---+---+ | publish v +-----------+ unpublish->| PUBLISHED | (->DRAFT) +-----+-----+ | archive v +-----------+ | ARCHIVED |--> re-publish (-> PUBLISHED) +-----------+ Invariante: Pro Workflow maximal 1 Version mit status=PUBLISHED. Scheduler nutzt immer die PUBLISHED Version. ``` ### Run-Modell ```python class RunStatus(str, Enum): PENDING = "pending" RUNNING = "running" PAUSED = "paused" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" class WorkflowRun(PowerOnModel): id: str workflowId: str # FK -> Workflow versionId: str # FK -> WorkflowVersion status: RunStatus trigger: Dict[str, Any] # { type: "manual"|"schedule"|"webhook"|..., metadata } startedAt: float completedAt: Optional[float] nodeOutputs: Dict[str, Any] # Outputs pro Node-ID currentNodeId: Optional[str] # Paused bei diesem Node resumeContext: Dict[str, Any] # Kontext fuer Resume error: Optional[str] costTokens: Optional[int] # Aggregierte Token-Kosten costCredits: Optional[float] # Aggregierte Credit-Kosten ``` ### State Machine: WorkflowRun.status ``` +---------+ executeGraph-->| RUNNING | +----+----+ | +----------------+----------------+ | | | v v v +--------+ +----------+ +--------+ | PAUSED | |COMPLETED | | FAILED | +---+----+ +----------+ +--------+ | | resume(taskResult | emailReceived) v +---------+ | RUNNING |---> COMPLETED | FAILED | PAUSED | CANCELLED +---------+ Pause-Gruende: - input.* Node -> HumanTask erstellt - email.checkEmail -> EmailWait (Background Poller) Cancel: - Manuell durch User - Timeout bei HumanTask (expiresAt) ``` ### RunStepLog und HumanTask ```python class StepStatus(str, Enum): RUNNING = "running" COMPLETED = "completed" FAILED = "failed" SKIPPED = "skipped" class RunStepLog(PowerOnModel): id: str runId: str # FK -> WorkflowRun nodeId: str # Node-ID im Graph nodeType: str # z.B. "ai.prompt", "email.checkEmail" status: StepStatus inputSnapshot: Dict[str, Any] # Parameters + Upstream-Daten bei Execution output: Optional[Dict[str, Any]] error: Optional[str] startedAt: float completedAt: Optional[float] durationMs: Optional[int] tokensUsed: Optional[int] retryCount: int = 0 class TaskStatus(str, Enum): PENDING = "pending" COMPLETED = "completed" CANCELLED = "cancelled" EXPIRED = "expired" class HumanTask(PowerOnModel): id: str runId: str # FK -> WorkflowRun workflowId: str # FK -> Workflow (Convenience-FK) nodeId: str nodeType: str # input.approval, input.form, etc. config: Dict[str, Any] # Node-Parameter (Formular-Felder, Approval-Text) assigneeId: str status: TaskStatus result: Optional[Dict[str, Any]] expiresAt: Optional[float] ``` ### ER-Diagramm ``` +------------------+ +----------------------+ | Workflow | 1---N | WorkflowVersion | |------------------| |----------------------| | id | | id | | mandateId | | workflowId (FK) | | featureInstanceId| | versionNumber | | label | | status | | description | | graph {} | | tags [] | | invocations [] | | isTemplate | | publishedAt | | currentVersionId +---1-->| publishedBy | | active | +----------+-----------+ | eventId | | 1:N +------------------+ +----------v-----------+ | WorkflowRun | |----------------------| | id | | workflowId (FK) | | versionId (FK) | | status | | trigger {} | | nodeOutputs {} | | currentNodeId | | resumeContext {} | | costTokens | | costCredits | +----------+-----------+ 1:N | | 1:N +-------------+ +-----------+ v v +------------------+ +------------------+ | RunStepLog | | HumanTask | |------------------| |------------------| | runId (FK) | | runId (FK) | | nodeId | | workflowId (FK) | | nodeType | | nodeId | | status | | config {} | | inputSnapshot {} | | assigneeId | | output {} | | status | | durationMs | | result {} | | tokensUsed | | expiresAt | | retryCount | +------------------+ +------------------+ ``` --- ## Graph-Struktur und Node-Definitionen ### Graph-Schema (innerhalb WorkflowVersion.graph) ```json { "nodes": [ { "id": "node-uuid", "type": "trigger.schedule", "position": { "x": 100, "y": 200 }, "parameters": { "cronExpression": "0 8 * * 1-5" } }, { "id": "node-uuid-2", "type": "ai.prompt", "position": { "x": 300, "y": 200 }, "parameters": { "prompt": "Analysiere die Emails..." } } ], "connections": [ { "source": "node-uuid", "target": "node-uuid-2", "sourceOutput": 0, "targetInput": 0 } ] } ``` ### Node-Type zu Method/Action Mapping | Node Type | `_method` | `_action` | `_paramMap` | |-----------|-----------|-----------|-------------| | `trigger.manual` | -- | -- | -- (TriggerExecutor) | | `trigger.schedule` | -- | -- | -- (TriggerExecutor) | | `trigger.form` | -- | -- | -- (TriggerExecutor) | | `flow.ifElse` | -- | -- | -- (FlowExecutor) | | `flow.switch` | -- | -- | -- (FlowExecutor) | | `flow.loop` | -- | -- | -- (FlowExecutor) | | `input.*` | -- | -- | -- (InputExecutor -> HumanTask) | | `ai.prompt` | `ai` | `process` | `prompt->aiPrompt` | | `ai.webResearch` | `ai` | `webResearch` | `query->prompt` | | `ai.summarizeDocument` | `ai` | `summarizeDocument` | -- | | `ai.translateDocument` | `ai` | `translateDocument` | `targetLanguage->targetLanguage` | | `ai.generateDocument` | `ai` | `generateDocument` | `prompt->prompt` | | `email.checkEmail` | `outlook` | `readEmails` | `connectionId->connectionReference` | | `email.searchEmail` | `outlook` | `searchEmails` | `connectionId->connectionReference` | | `email.draftEmail` | `outlook` | `composeAndDraft...` | `connectionId->connectionReference` | | `sharepoint.*` | `sharepoint` | entsprechend | `connectionId->connectionReference` | | `clickup.*` | `clickup` | entsprechend | `connectionId->connectionReference` | | `file.create` | `file` | `create` | `template->template` | ### Execution-Routing ``` executeGraph(graph, services, ...) | +-- parseGraph() -> nodes, connections, nodeIds +-- validateGraph() -> Konsistenzpruefung +-- topoSort(nodes, connectionMap) -> geordnete Node-Liste | +-- Pro Node: | +-- nodeType.startsWith("trigger.") -> TriggerExecutor +-- nodeType.startsWith("flow.") -> FlowExecutor | ifElse: evaluiert Bedingung, setzt active path | switch: evaluiert Match, setzt active path | loop: iteriert mit _loopState +-- nodeType.startsWith("input.") -> InputExecutor | -> erstellt HumanTask, setzt Run auf PAUSED +-- alles andere -> ActionNodeExecutor +-- _getNodeDefinition(nodeType) -> _method, _action, _paramMap +-- Parameter-Mapping (Node-Params -> Action-Params) +-- Upstream-Daten mergen (documents von vorherigen Nodes) +-- ActionExecutor.executeAction(method, action, params) ``` --- ## Konsolidierter Scheduler (Ziel) ```python class WorkflowScheduler: def start(self, eventUser): eventManager.start() self._syncScheduledWorkflows(eventUser) self._registerDelayedSync(eventUser, delaySeconds=5) callbackRegistry.register("workflow.changed", lambda _: self._syncScheduledWorkflows(eventUser)) def _syncScheduledWorkflows(self, eventUser): """Inkrementeller Sync (v1-Pattern).""" workflows = self._getAllSchedulableWorkflows() for wf in workflows: jobId = f"workflow.{wf.id}" if wf.active and wf.currentVersionId: version = self._getPublishedVersion(wf.currentVersionId) cronKwargs = self._extractSchedule(version) if cronKwargs: handler = self._createHandler(wf, version, eventUser) eventManager.registerCron( jobId=jobId, func=handler, cronKwargs=cronKwargs, replaceExisting=True # v1-Pattern: atomic replace ) if wf.eventId != jobId: self._updateEventId(wf.id, jobId) else: self._removeJob(jobId) if wf.eventId: self._updateEventId(wf.id, None) def _createHandler(self, wf, version, eventUser): """Handler: Reload + Active-Check + Execute (v1-Pattern).""" async def _handler(): current = self._getWorkflow(wf.id) if not current or not current.active: return services = self._buildServices(current, eventUser) result = await executeGraph(graph=version.graph, services=services, ...) self._appendExecutionLog(current.id, result) # capped audit trail return self._wrapAsync(_handler) # Thread-Bridge (v2-Pattern) ``` --- ## Use Cases ### UC-1: Workflow im Workspace erstellen/bearbeiten User beschreibt im Workspace-Chat einen gewuenschten Workflow -> Agent nutzt Toolbox `"workflow"` -> generiert Graph -> speichert als Draft -> User oeffnet im Graphical Editor. Voraussetzung: Die `"workflow"` Toolbox muss im Workspace aktivierbar sein. Die Tools validieren jeden Graph-Mutationsschritt: - `addNode(type, parameters)` -> prueft: existiert der Node-Typ? Sind die Parameter valide? - `connectNodes(source, target)` -> prueft: sind die I/O-Typen kompatibel? Keine Zyklen? - `validateGraph()` -> vollstaendige Validierung inkl. Trigger-Pruefung ### UC-2: AI Chat im Graphical Editor Sidebar mit Chat. Agent nutzt Toolbox `"workflow"` mit System-Prompt, der den aktuellen Graph kennt. Er kann Nodes hinzufuegen, entfernen, umkonfigurieren und den Graph erklaeren. Gleicher `serviceAgent.runAgent()` Pfad wie Workspace, aber mit `toolboxes: ["core", "workflow"]` und Graph als Kontext im System-Prompt. ### UC-3: Workflow testen mit Tracing Log User klickt "Test Run" -> Graph wird ausgefuehrt -> jeder Node wird visuell hervorgehoben (Status-Farben) -> Tracing Log zeigt pro Node: Input, Output, Dauer, Fehler. Bei Fehlern: User bespricht den Fehler im AI Chat -> Agent liest RunStepLog und schlaegt Korrekturen vor. **Technisch:** `RunStepLog` pro Node mit Timestamps, Input-Snapshot, Duration, Error-Stack. SSE/WebSocket fuer Live-Updates. Run-Modes: "Full Run" und "Step-by-Step" (Pause nach jedem Node). ### UC-4: Workflow automatisieren (Scheduler) User konfiguriert Schedule im Editor (via trigger.schedule Node oder Invocation) -> konsolidierter Scheduler registriert Cron-Job -> Run-History wird persistiert -> bei Fehlern Notifications. --- ## Backend Code-Struktur (Ziel) ``` gateway/modules/ | +-- features/ | +-- graphicalEditor/ <-- NEUES FEATURE (ersetzt automation2) | | +-- mainGraphicalEditor.py | | | FEATURE_CODE = "graphicalEditor" | | | UI_OBJECTS = ["ui.feature.graphicalEditor.editor", | | | "ui.feature.graphicalEditor.workflows", | | | "ui.feature.graphicalEditor.tasks"] | | | RESOURCE_OBJECTS = ["resource.feature.graphicalEditor.execute", | | | "resource.feature.graphicalEditor.schedule"] | | | TEMPLATE_ROLES = ["graphicalEditor-admin", | | | "graphicalEditor-user", | | | "graphicalEditor-viewer"] | | +-- routeFeatureGraphicalEditor.py (prefix: /api/workflows) | | +-- interfaceFeatureGraphicalEditor.py (DB: poweron_workflows) | | +-- datamodelFeatureGraphicalEditor.py | | +-- nodeDefinitions/ | | triggers.py, flow.py, input.py, ai.py, email.py, sharepoint.py, ... | | | +-- workspace/ <-- BLEIBT (AI Chat mit UDB) | +-- automation/ <-- BLEIBT bis Abloesung | +-- commcoach/ <-- BLEIBT | +-- chatbot/ <-- BLEIBT | +-- trustee/ <-- BLEIBT | +-- serviceCenter/services/ | +-- serviceAgent/ | | +-- mainServiceAgent.py <-- Core Tools + Toolbox-Tagging | | +-- agentLoop.py <-- Toolbox-Filtering statt toolSet | | +-- toolRegistry.py <-- BLEIBT | | +-- toolboxRegistry.py <-- NEU: Toolbox-Verwaltung | | +-- actionToolAdapter.py <-- BLEIBT (+ Toolbox-Zuordnung) | | +-- featureDataAgent.py <-- BLEIBT (Sub-Agent Pattern) | | +-- datamodelAgent.py <-- toolboxes: List[str] statt toolSet | +-- workflows/ | +-- methods/ <-- BLEIBT (Unified Action Library) | +-- processing/ <-- BLEIBT (ActionExecutor, methodDiscovery) | +-- automation2/ <-- Graph Engine BLEIBT | +-- scheduler/ <-- NEU (konsolidierter Scheduler) | | +-- mainScheduler.py | +-- automation/ <-- BLEIBT bis Abloesung | +-- workflowManager.py <-- BLEIBT (fuer Workspace Dynamic Mode) ``` --- ## Frontend Code-Struktur (Ziel) ``` frontend_nyla/src/ | +-- components/ <-- SHARED COMPONENTS | +-- ChatBar/ <-- NEU (extrahiert aus WorkspaceInput) | | ChatBar.tsx Props: onSend, onStop, onVoice, | | fileAttachments, showProviderSelector, ... | +-- ChatStream/ <-- NEU (extrahiert aus workspace/ChatStream) | | ChatStream.tsx SSE-driven message stream, wiederverwendbar | +-- UnifiedDataBar/ <-- BLEIBT | +-- FlowEditor/ <-- RENAMED von Automation2FlowEditor | +-- editor/ | FlowEditor.tsx + AI Chat Panel | FlowCanvas.tsx + Visual Run Tracing | EditorChatPanel.tsx <-- NEU | +-- pages/views/ | +-- workspace/ | | WorkspacePage.tsx Nutzt ChatBar, ChatStream, UDB | +-- graphicalEditor/ <-- RENAMED von automation2 | GraphicalEditorPage.tsx FlowEditor + UDB + ChatPanel | WorkflowsListPage.tsx | TasksPage.tsx | +-- api/ workflowApi.ts <-- Konsolidiert (ersetzt automation + automation2 API) ``` --- ## Empfehlungen und offene Punkte ### Starke Konzepte (umsetzen) | Konzept | Bewertung | |---------|-----------| | **Toolbox-Architektur** | Skaliert von 50 auf 100+ Tools ohne LLM-Ueberlastung. Klare Isolierung. | | **Sub-Agents pro Feature** | Bewaehrtes Pattern (Trustee), erweiterbar. Haelt Main Agent schlank. | | **Validierungs-Layer fuer Workflow-Modellierung** | Graph-Mutation nur ueber validierte Tools. | | **v1-Scheduling-Patterns in v2 uebernehmen** | Inkrementeller Sync ist robuster als Full-Wipe. | | **State Machines als Grundlage** | Klare Zustaende, klare Uebergaenge. Erweiterbar. | ### Was User brauchen | User Need | Feature | Prioritaet | |-----------|---------|-----------| | **Schneller Einstieg** | Workflow-Templates fuer haeufige Use Cases | Hoch | | **Vertrauen** | Tracing Log + Test Runner | Hoch | | **Effizienz** | AI-gestuetzte Workflow-Erstellung per Chat | Hoch | | **Fehlertoleranz** | Retry-Policies, Pause/Resume, klare Fehlermeldungen | Hoch | | **Kontext** | UDB im Editor -- Zugriff auf Files/Sources bei Konfiguration | Hoch | | **Zusammenarbeit** | Workflows teilen im Mandant, Rollen-basierter Zugriff | Mittel | | **Monitoring** | Dashboard: laufende Automationen, Fehlerrate, Kosten | Mittel | | **Flexibilitaet** | Custom Script Nodes (Python-Sandbox), AI Decision Nodes | Mittel | | **Mobile** | Notifications + Approval-Tasks per Mobile | Phase 2 | ### Offene Architektur-Fragen | Frage | Empfehlung | |-------|------------| | **Toolbox-Selektion: statisch oder dynamisch?** | Hybrid: Basis pro Feature statisch, Connection-basierte dynamisch. | | **Sub-Agent Tiefe: verschachtelt?** | Maximal 1 Level tief. Kein Sub-Sub-Agent. | | **Graph-Execution und WorkflowManager zusammenfuehren?** | Nein, getrennt. `executeGraph` fuer Editor, `WorkflowManager` fuer Workspace Dynamic Mode. | | **Feature-Code: `automation2` -> `graphicalEditor`?** | Ja, Rename in Phase 1. | --- ## Phasen-Plan ### Phase 1: Foundation - Unified Workflow Datenmodell (Workflow + WorkflowVersion + WorkflowRun + RunStepLog + HumanTask) - Toolbox-Registry implementieren (Abloesung `_CORE_ONLY_TOOLS`) - ChatBar-Komponente extrahieren - Feature Rename: `automation2` -> `graphicalEditor` - Scheduler konsolidieren (v1 Patterns in v2 Engine) ### Phase 2: Editor Enhancement - AI Chat Sidebar im Editor (Toolbox `"workflow"` + Graph-Manipulation-Tools) - UDB Integration im Editor - WorkflowVersion Lifecycle (Draft/Published) - Enhanced RunStepLog + Visual Tracing ### Phase 3: Productization - Workflow Templates / Marketplace - Monitoring Dashboard - Retry Policies pro Node - Notifications bei Scheduler-Fehlern - Automation v1 Feature entfernen (wird nicht mehr benoetigt) ### Phase 4: Advanced - AI Decision Node (`ai.decide`) - Custom Script Node (Python Sandbox) - Dynamische Toolbox-Aktivierung basierend auf Connections - Sub-Agent Pattern fuer weitere Features --- ## Betroffene Module - Gateway: `features/automation/`, `features/automation2/`, `workflows/`, `serviceCenter/services/serviceAgent/` - Frontend: `components/Automation2FlowEditor/`, `pages/views/automation2/` - DB-Migration: ja (neues Workflow/Version/Run Schema) - Andere: Scheduler (APScheduler/eventManager), RBAC Template-Rollen ## Entscheidungen | Datum | Entscheidung | Begruendung | |-------|-------------|------------| | 2026-04-05 | v1-Scheduler-Pattern in v2 uebernehmen | Robuster (inkrementell, Job-Handle, Reload+active-Check) | | 2026-04-05 | Toolbox-Konzept statt flache Tool-Listen | Skalierbarkeit bei 50-100+ Tools | | 2026-04-05 | Keine v1-Datenmigration | v1 nicht produktiv | | 2026-04-05 | Feature-Code `automation2` -> `graphicalEditor` | Klarer Name fuer User und Entwickler | | 2026-04-05 | Graph-Execution und WorkflowManager getrennt lassen | Verschiedene Orchestrierungsmodelle | | 2026-04-05 | Sub-Agent maximal 1 Level tief | Komplexitaet kontrollierbar | ## Umsetzungs-Checkliste - [ ] Vereinheitlichtes Workflow/WorkflowVersion/WorkflowRun Schema - [ ] v1-Scheduler-Pattern in v2 Engine - [ ] Toolbox Registry + requestToolbox Meta-Tool - [ ] Feature-Code Rename `automation2` -> `graphicalEditor` - [ ] Frontend: ChatBar + ChatStream extrahieren - [ ] Frontend: UDB + Chat im Graph-Editor - [ ] RBAC Template-Rollen konsolidieren - [ ] RunStepLog + Visual Tracing - [ ] Neutralisierung: keine Aenderung (nutzt gleiche Services) - [ ] Billing-Impact: pruefen (Automation-Runs zaehlen?) ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|---------------------------|------| | 1 | Given ein Automation2-Workflow mit Schedule, When der Scheduler neu startet, Then werden nur geaenderte Jobs aktualisiert (inkrementell wie v1) | must | | 2 | Given ein Agent mit 50+ Tools, When der Agent startet, Then sind nur core-Toolboxes geladen und Spezial-Toolboxes werden on-demand nachgeladen | should | | 3 | Given ein Workflow-Run, When ein Human-Task-Node erreicht wird, Then pausiert der Run und wartet auf User-Input | must | | 4 | Given ein Workflow-Graph im Editor, When der User im AI-Chat "Fuege einen Email-Check hinzu" tippt, Then fuegt der Agent einen email.checkEmail Node ein und verbindet ihn korrekt | should | | 5 | Given ein Workflow mit Published Version, When der User den Graph editiert, Then wird ein neuer Draft erstellt und die Published Version bleibt aktiv | must | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1 | integration | ja | gateway/tests/test_automation2_scheduler.py | pending | | T2 | 2 | integration | ja | gateway/tests/test_agent_toolbox.py | pending | | T3 | 3 | api | ja | gateway/tests/test_workflow_pause_resume.py | pending | | T4 | 4 | integration | ja | gateway/tests/test_editor_chat_graph.py | pending | | T5 | 5 | unit | ja | gateway/tests/test_workflow_versioning.py | pending | ## Glossar | Begriff | Definition | |---------|-----------| | **Toolbox** | Thematisch gebuendelte Gruppe von AI-Tools, kontextabhaengig aktiviert | | **Sub-Agent** | Spezialisierter Mini-Agent mit eigener Tool-Registry, aufgerufen vom Main Agent | | **Workflow** | Persistiertes Automatisierungsmodell mit Graph-Struktur | | **WorkflowVersion** | Immutable Snapshot eines Workflow-Graphen (draft, published, archived) | | **WorkflowRun** | Einzelne Ausfuehrung einer WorkflowVersion | | **RunStepLog** | Detaillierter Log pro Node-Execution innerhalb eines Runs | | **HumanTask** | Aufgabe fuer menschliche Eingabe, erstellt bei Pause eines Runs | | **Node** | Ausfuehrungsschritt im Graph (Trigger, Action, Flow-Control, Input/Human) | | **Method** | Integrations-Kategorie (z.B. methodOutlook) mit mehreren Actions | | **Action** | Spezifische Operation innerhalb einer Method (z.B. outlook.readEmails) | | **Invocation** | Entry-Point fuer einen Workflow (Manual, Schedule, Webhook, Form, Event) | | **UDB** | Unified Data Bar -- Multi-Tab-Panel fuer Chats, Files und Sources | | **ChatBar** | Wiederverwendbare Prompt-Input-Komponente | | **Feature Container** | Einheitliches Verzeichnis-Pattern fuer ein Feature | ## Links - Detail-Spec (Business): z-archive/b-reference/automation-business-spec.md - Detail-Spec (Datenmodell): z-archive/b-reference/automation-data-model.md - Referenz (Ist-Zusammenfassung): b-reference/gateway/automation.md ## Abschluss - [ ] b-reference/gateway/automation.md aktualisiert - [ ] TOPICS.md aktualisiert - [ ] Dieses Dokument -> z-archive/ verschoben