automation concept

This commit is contained in:
ValueOn AG 2026-04-05 02:01:15 +02:00
parent 9be05652b9
commit 14dbd418a6
2 changed files with 1734 additions and 0 deletions

View file

@ -0,0 +1,740 @@
# PowerOn Automation Unification — Business Specification
**Version:** 2.0
**Datum:** 2026-04-05
**Status:** Draft
---
## 1. Executive Summary
PowerOn enthält heute drei teilweise überlappende Systeme für die Automatisierung von Workflows. Die Ziel-Architektur konsolidiert diese zu einer einheitlichen Platform mit klaren Grenzen zwischen Services, Features und Shared Libraries.
**Leitprinzipien:**
- Keine Migration von Altdaten — Automation v1 ist nicht produktiv, wird nicht migriert
- Automation v1 bleibt bestehen, bis es durch das neue Feature abgelöst ist
- Klare Datenmodelle mit definierten State Machines als stabile, erweiterbare Grundlage
- Klare Trennung von Mandant/Feature/Feature-Instanz und zugehöriger RBAC-Logik
- Skalierbare AI-Tool-Architektur mit Toolboxes und Sub-Agents für 50100+ Tools
---
## 2. Ist-Analyse
### 2.1 Drei bestehende Systeme
| System | Modell | Stärke | Schwäche |
|--------|--------|--------|----------|
| **Automation v1** | Template + Cron + Placeholders | Stabiles, bewährtes 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 |
### 2.2 Wer nutzt die Method/Action Library?
Die `workflows/methods/` Library (methodAi, methodOutlook, methodSharepoint, etc.) ist bereits 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 wählt 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` |
### 2.3 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-übergreifend) |
| **Graph Node Types** (ai.prompt, email.checkEmail, etc.) | `STATIC_NODE_TYPES` in `nodeDefinitions/` | Automation2 `executeGraph` |
**Benötigen die AI Tools die Method/Actions?** Ja, direkt. `ActionToolAdapter` transformiert jede `dynamicMode=True` Action in ein Agent-Tool. Und die Automation2-Nodes mappen über `_method`/`_action` auf dieselbe Library.
### 2.4 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 |
---
## 3. Ziel-Architektur
### 3.1 Vier Säulen
```
┌──────────────────────────────────────────────────────────────────────┐
│ POWERON UNIFIED PLATFORM │
│ │
│ ┌─────────────┐ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ AI Service │ │ Graphical Editor │ │ Automation │ │
│ │ (Säule 1) │ │ (Säule 2) │ │ Scheduler │ │
│ │ │ │ │ │ (Säule 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 │ │
│ └──────┬───────┘ └──────────┬─────────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌──────▼──────────────────────▼───────────────────────────▼─────────┐ │
│ │ SÄULE 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 │ │
│ │ ai.generate │ │ │ │
│ │ │ │ │ │
│ │ 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 "trustee" │ Toolbox "chatbot" │ [future toolboxes] │ │
│ │ ────────────────── │ ────────────── │ │ │
│ │ trustee.extract │ chatbot.queryDB │ slack.sendMessage │ │
│ │ trustee.process │ │ teams.postChannel │ │
│ │ trustee.syncAcctng │ │ google.readDrive │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```
### 3.2 Säule 1: AI Service (besteht, zu erweitern um Toolbox-Architektur)
**Status:** Kern bereit (`serviceAi` + `serviceAgent` + `aicore`).
**Erweiterung: Toolbox-Architektur für 50100+ Tools** → siehe Abschnitt 5.
### 3.3 Säule 2: Graphical Editor (Feature "graphicalEditor")
**Status:** Basis vorhanden (Automation2FlowEditor), zu erweitern.
**Funktionalität:**
1. **Visual Flow Builder** (besteht): Canvas, Nodes, Connections, Branching, Loops
2. **Node Palette** (besteht): Abgeleitet aus Toolbox-Registry — jede Toolbox-Action kann als Node exponiert werden
3. **AI Chat Sidebar** (neu): Agent mit Toolbox `"workflow"` für Graph-Manipulation
4. **UDB Integration** (neu): UnifiedDataBar im Editor
5. **Tracing/Debug Log** (zu erweitern): RunStepLog mit Input-Snapshot, Duration, Error pro Node
6. **Test Runner** (zu erweitern): Visual Highlighting, Step-by-Step, AI-assisted Debugging
7. **Version Management** (neu): Draft/Published Lifecycle
### 3.4 Säule 3: Automation Scheduler (zu konsolidieren)
**Status:** v1-Scheduling hat bewährte Patterns, die direkt in v2 übernommen werden.
**Von v1 übernehmen:**
- Inkrementeller Sync (nur geänderte Jobs registrieren/entfernen, nicht full wipe)
- Persistierter Job-Handle (`eventId`) auf dem Workflow für Debugging
- `automation.changed` Callback-Pattern für reaktive Synchronisation
- Handler lädt Workflow neu und prüft `active` vor Execution
- Execution-Logs als Audit Trail (capped auf 50 Einträge)
- Creator-User Tracking via `sysCreatedBy` für Execution-Kontext
**Von v2 beibehalten:**
- `IntervalTrigger` für einfache Wiederholungen
- Main-Loop-Bridging (`call_soon_threadsafe`) für Thread-Safety
- Delayed Startup Sync (5s) für DB-Readiness
- Striktes Cron-Parsing (5/6 Felder)
### 3.5 Säule 4: Unified Action Library mit Toolboxes
**Status:** Infrastruktur besteht (`workflows/methods/` + `methodDiscovery` + `ActionExecutor`). Zu erweitern um Toolbox-Konzept.
→ Detailliert in Abschnitt 5.
---
## 4. Mandant/Feature/RBAC-Architektur
### 4.1 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 (kopiert von Feature Templates)
│ │ │ mandateId=X, featureInstanceId=Z, featureCode="graphicalEditor"
│ │ │ "graphicalEditor-admin", "graphicalEditor-user", ...
│ │ └── Daten (Workflows, Runs, Tasks, ...)
│ │
│ └── Feature-Instanz C (z.B. "Trustee Buchhaltung")
│ ├── Feature-Instanz-Rollen
│ └── Daten (Positionen, Belege, ...)
└── Feature Catalog (Global)
├── Feature "workspace" (code, label, icon)
├── Feature "graphicalEditor" (code, label, icon)
├── Feature "trustee" (code, label, icon)
└── ...
```
### 4.2 Zwei getrennte Template-Rollen-Systeme
Es gibt **zwei getrennte** Template-Systeme — diese sind **nicht** dieselben:
**System Template Rollen (für Mandanten)**
| Feld | Wert |
|------|------|
| `isSystemRole` | `true` |
| `mandateId` | `null` |
| `featureInstanceId` | `null` |
| `featureCode` | `null` |
| `roleLabel` | `"admin"`, `"user"`, `"viewer"` |
| Kopier-Mechanismus | `copySystemRolesToMandate()` in `interfaceBootstrap.py` |
| Ausgelöst bei | Mandant-Erstellung |
| Ziel-Rolle | `mandateId=X`, `featureInstanceId=null`, `isSystemRole=false` + kopierte AccessRules |
**Feature Template Rollen (für Feature-Instanzen)**
| Feld | Wert |
|------|------|
| `isSystemRole` | `false` |
| `mandateId` | `null` |
| `featureInstanceId` | `null` |
| `featureCode` | z.B. `"workspace"`, `"automation2"` |
| `roleLabel` | z.B. `"workspace-admin"`, `"workspace-user"`, `"workspace-viewer"` |
| Kopier-Mechanismus | `_copyTemplateRoles()` in `interfaceFeatures.py` |
| Ausgelöst bei | Feature-Instanz-Erstellung |
| Ziel-Rolle | `mandateId=X`, `featureInstanceId=Y`, `isSystemRole=false` + kopierte AccessRules |
**Wichtig:** Templates werden **nur bei Erstellung** kopiert. Spätere Änderungen werden **nicht** automatisch propagiert. (Ausnahme: `_syncTemplateRolesToDb()` synct neue UI-Objekte auf bestehende Mandate-Rollen mit gleichem `roleLabel`.)
### 4.3 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 (höchste) |
**Resolution:**
1. Lade Mandate-Rollen (via `UserMandate``UserMandateRole`)
2. Lade Feature-Instanz-Rollen (via `FeatureAccess``FeatureAccessRole`)
3. Lade `AccessRules` für alle gefundenen Rollen
4. **Höchste Priorität gewinnt** bei DATA-Permissions (Instanz schlägt Mandant schlägt Global)
5. **View wird OR-verknüpft** über alle Rollen der höchsten Prioritätsstufe
6. **Item-Spezifität** innerhalb einer Stufe: exact > prefix > generic
### 4.3 AccessRule-Kontexte
| Kontext | Item-Beispiel | Prüft |
|---------|---------------|-------|
| `UI` | `ui.feature.graphicalEditor.editor` | Seitenleiste, Navigation, Buttons |
| `RESOURCE` | `resource.feature.graphicalEditor.execute` | Ausführungsberechtigungen |
| `DATA` | Auto-generiert aus Tabellenname + featureCode | CRUD auf Datenbank-Ebene |
### 4.4 Feature-Container-Pattern
Jedes Feature folgt einem einheitlichen Container-Pattern:
```
features/<featureName>/
├── main<FeatureName>.py ◄── Feature-Definition
│ ├── FEATURE_CODE = "<featureName>"
│ ├── UI_OBJECTS = [...] ◄── RBAC UI-Objekte
│ ├── RESOURCE_OBJECTS = [...] ◄── RBAC Resource-Objekte
│ ├── TEMPLATE_ROLES = [...] ◄── Rollen-Templates mit AccessRules
│ ├── REQUIRED_SERVICES = [...] ◄── Service-Dependencies
│ ├── registerFeature(catalog) ◄── Registriert Objekte + synct Rollen
│ └── getFeatureDefinition() ◄── Katalog-Metadaten
├── routeFeature<FeatureName>.py ◄── FastAPI Router (HTTP)
│ └── router = APIRouter(prefix="/api/<featureName>")
├── interfaceFeature<FeatureName>.py ◄── DB-Interface (CRUD + RBAC)
│ └── getInterface(user, mandateId, featureInstanceId)
├── datamodelFeature<FeatureName>.py ◄── Pydantic-Modelle
└── [optional] service<Name>/ ◄── Feature-spezifische Services
└── mainService<Name>.py
```
**Automatische Discovery:** `registry.py` scannt `features/*/routeFeature*.py` und `features/*/main*.py`. Routers werden automatisch gemountet, Features automatisch im Katalog registriert. Kein manuelles Wiring nötig.
---
## 5. AI-Tool-Architektur: Toolboxes und Sub-Agents
### 5.1 Problem: Tool-Explosion
Aktuell werden ~50 Tools dem LLM in einem einzigen Prompt exponiert. Bei 100+ Tools:
- LLM-Context wird überladen → schlechtere Tool-Auswahl
- Irrelevante Tools für den aktuellen Kontext → Confusion und Halluzinationen
- Keine Feature-isolierte Tool-Bereitstellung
### 5.2 Konzept: Toolboxes
Eine **Toolbox** ist eine thematisch gebündelte Gruppe von Tools mit einem klaren Scope. Toolboxes ersetzen das bisherige flache `_CORE_ONLY_TOOLS`-Set.
```
Toolbox-Registry
├── Toolbox "core" (immer verfügbar)
│ ├── 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)
│ ├── sendMail
│ └── outlook.readEmails, outlook.searchEmails, outlook.draftEmail
├── Toolbox "sharepoint" (SharePoint-Integration)
│ └── sharepoint.findDoc, sharepoint.read, sharepoint.upload, ...
├── Toolbox "clickup" (ClickUp-Integration)
│ └── clickup.searchTasks, clickup.createTask, ...
├── Toolbox "jira" (Jira-Integration)
│ └── jira.connect, jira.exportTickets, ...
├── Toolbox "workflow" (Graph-Manipulation für Editor-Chat)
│ ├── readWorkflowGraph, addNode, removeNode
│ ├── connectNodes, setNodeParameter
│ ├── listAvailableNodeTypes, validateGraph
│ └── listWorkflowHistory, readWorkflowMessages
├── Toolbox "trustee" (Feature-spezifisch)
│ └── trustee.extract, trustee.process, trustee.syncAccounting
└── Toolbox "chatbot" (Feature-spezifisch)
└── chatbot.queryDatabase
```
### 5.3 Toolbox-Bereitstellung: Initiales Set + dynamische Eskalation
**Kern-Idee:** Der Agent startet mit einem kompakten, eingeschränkten Tool-Set. Er kennt aber den **Katalog aller verfügbaren Toolboxes**. Wenn er für eine Aufgabe Spezial-Tools braucht, kann er eine Toolbox **anfordern** — und bekommt sie in der nächsten Runde bereitgestellt.
**Warum nicht alle Tools sofort?**
- Bei 50100+ Tools wird der LLM-Context überladen → schlechtere Tool-Auswahl
- Irrelevante Tools führen zu Confusion und Halluzinationen
- Die meisten Interaktionen brauchen nur 1015 Core-Tools
**Mechanismus: `requestToolbox` Meta-Tool**
```
Runde 1:
Tools: core (readFile, webSearch, ...) + requestToolbox
System-Prompt enthält: "Verfügbare Toolboxes: email, sharepoint, clickup,
jira, workflow, ai, datasources, trustee, ..."
User: "Lies meine letzten Emails und fasse sie zusammen"
LLM: → requestToolbox("email") [braucht Email-Tools]
Runde 2:
Tools: core + email (sendMail, outlook_readEmails, outlook_searchEmails, ...)
+ requestToolbox
LLM: → outlook_readEmails(connectionRef, folder="Inbox", limit=10)
Runde 3:
LLM: → (Text-Antwort mit Zusammenfassung)
→ COMPLETED
```
**`requestToolbox` Tool-Definition:**
```python
{
"name": "requestToolbox",
"description": "Request additional specialized tools for the current task. "
"Call this when you need capabilities beyond your current tools.",
"parameters": {
"type": "object",
"properties": {
"toolboxId": {
"type": "string",
"description": "ID of the toolbox to activate",
"enum": [...] # Dynamisch: nur verfügbare Toolboxes
},
"reason": {
"type": "string",
"description": "Why you need this toolbox"
}
},
"required": ["toolboxId"]
}
}
```
**AgentConfig:**
```python
class AgentConfig(BaseModel):
maxRounds: int = 25
maxCostCHF: Optional[float] = None
initialToolboxes: List[str] = ["core"] # Initiales Set
availableToolboxes: List[str] = [] # Was angefordert werden darf
temperature: Optional[float] = None
```
**Konfiguration 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"]` | `[]` |
**Vorteile:**
- **Lean Context:** Die meisten Chats brauchen nur Core-Tools → schnellere, präzisere Antworten
- **Selbst-navigierend:** LLM entscheidet selbst, wann Spezial-Tools nötig sind
- **Kontrolliert:** `availableToolboxes` limitiert, was der Agent anfordern darf (RBAC)
- **Skaliert:** 100+ Tools kein Problem — der LLM sieht immer nur was er gerade braucht
- **Transparent:** `requestToolbox("email", reason="User fragt nach Emails")` ist nachvollziehbar im Tracing
**Automatische Toolbox-Verfügbarkeit basierend auf Connections:**
Welche Toolboxes in `availableToolboxes` erscheinen, hängt von den aktiven User-Connections ab. Hat der User keine Outlook-Verbindung, erscheint `"email"` gar nicht in der Liste — der Agent kann sie auch nicht anfordern.
### 5.4 Sub-Agents (Feature Data Agents)
Für datenintensive Features mit eigenem Schema (z.B. Trustee Buchhaltung) existiert bereits das Sub-Agent-Pattern:
```
User Prompt im Workspace
Main Agent (Toolboxes: core, ai, datasources, ...)
├── Tool: queryFeatureInstance("trustee", instanceId, question)
│ │
│ ▼
│ Sub-Agent (Feature Data Agent)
│ Eigene ToolRegistry: browseTable, queryTable
│ Eigenes System-Prompt mit Schema-Wissen
│ Limitiert: maxRounds=5, maxCost=0.10 CHF
│ │
│ ▼
│ Antwort zurück an Main Agent
├── Tool: readFile(fileId)
│ (Core Tool, direkt)
└── Tool: outlook_readEmails(connectionRef)
(Action Tool, direkt)
```
**Empfehlung: Sub-Agent-Pattern ausweiten auf:**
- **Trustee** (besteht): browseTable, queryTable auf Buchhaltungsdaten
- **Workflow** (neu): Graph-Manipulation als Sub-Agent im Editor-Chat
- **Future Features** mit eigenem Datenmodell: Gleicher Pattern
**Vorteil:** Der Main Agent bleibt schlank. Feature-spezifisches Datenwissen ist im Sub-Agent gekapselt. Der LLM des Main Agents sieht nur ein einzelnes Tool (`queryFeatureInstance`) statt 10+ tabellenspezifische Tools.
### 5.5 State Machine: AI Agent Run (mit Toolbox-Eskalation)
```
┌──────────┐
│ IDLE │
└────┬─────┘
│ runAgent(prompt, initialToolboxes, availableToolboxes)
┌──────────┐
┌────►│ THINKING │◄──────────────────────────────────┐
│ └────┬─────┘ │
│ │ │
│ ├── LLM Response (Text only) ──────────────┼──► COMPLETED
│ │ │
│ └── LLM Response (ToolCalls) │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ EXECUTING_TOOLS │ │
│ └────┬───────────────┘ │
│ │ │
│ ├── requestToolbox(id) ───┐ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ TOOLBOX_ESCALATION │ │
│ │ │ Toolbox wird aktiviert│ │
│ │ │ Tools der nächsten │ │
│ │ │ Runde erweitert │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ ├── Sub-Agent call ───┐ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐│ │
│ │ │SUB_AGENT_CALL ││ │
│ │ └──────┬───────┘│ │
│ │ │ │ │
│ ├── Regular tools ─┼────────┼────────────────┘
│ │ (results → next round)
│ maxRounds / maxCost reached
┌──────────┐
│ COMPLETED │ (oder FAILED / CANCELLED)
└──────────┘
```
---
## 6. Use Cases
### UC-1: Datenquellen einbinden → besteht, keine Änderung
### UC-2: AI Workspace mit UDB → besteht, keine Änderung
### UC-3: Workflow im Workspace erstellen/bearbeiten
**Beschreibung:** User beschreibt im Workspace-Chat einen gewünschten Workflow → Agent nutzt Toolbox `"workflow"` → generiert Graph → speichert als Draft → User öffnet im Graphical Editor.
**Voraussetzung:** Die `"workflow"` Toolbox muss im Workspace aktivierbar sein. Die Tools validieren jeden Graph-Mutationsschritt:
- `addNode(type, parameters)` → prüft: existiert der Node-Typ? Sind die Parameter valide?
- `connectNodes(source, target)` → prüft: sind die I/O-Typen kompatibel? Keine Zyklen?
- `validateGraph()` → vollständige Validierung inkl. Trigger-Prüfung
### UC-4: AI Chat im Graphical Editor
**Beschreibung:** Der Graphical Editor hat eine Sidebar mit Chat. Der Agent nutzt Toolbox `"workflow"` mit speziellem System-Prompt, der den aktuellen Graph kennt. Er kann Nodes hinzufügen, entfernen, umkonfigurieren und den Graph erklären.
**Architektur:** Gleicher `serviceAgent.runAgent()` Pfad wie Workspace, aber:
- Anderes ToolSet: `toolboxes: ["core", "workflow"]`
- System-Prompt enthält den aktuellen Graph als Kontext
- Frontend: Extrahierte `ChatBar`-Komponente + `ChatStream`
### UC-5: Workflow testen mit Tracing Log
**Beschreibung:** User klickt "Test Run" → Graph wird ausgeführt → 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 den RunStepLog und schlägt Korrekturen vor.
**Technisch:**
- `RunStepLog` pro Node mit Timestamps, Input-Snapshot, Duration, Error-Stack
- SSE/WebSocket für Live-Updates des Node-Status
- Run-Modes: "Full Run" und "Step-by-Step" (Pause nach jedem Node)
### UC-6: Workflow automatisieren (Scheduler)
**Beschreibung:** User konfiguriert Schedule im Editor (via trigger.schedule Node oder Invocation) → konsolidierter Scheduler registriert Cron-Job → Run-History wird persistiert → bei Fehlern Notifications.
---
## 7. State Machines
### 7.1 Workflow Lifecycle
```
┌───────┐
create ──►│ DRAFT │◄──── edit (neuer graph)
└───┬───┘
│ publish
┌───────────┐
unpublish│ PUBLISHED │◄──── edit → erzeugt neuen Draft,
│ └───────────┘ Published bleibt aktiv
│ │
│ │ archive (manuell oder nach Deaktivierung)
│ ▼
│ ┌───────────┐
│ │ ARCHIVED │
│ └───────────┘
│ │
└───────┘ re-publish (aus Archiv zurückholen)
```
**Regeln:**
- Ein Workflow hat genau **eine** published Version (oder keine)
- Scheduled Runs nutzen immer die published Version
- Edit erzeugt immer einen neuen Draft — die published Version bleibt stabil
- Archive ist reversibel
### 7.2 WorkflowRun Lifecycle
```
┌─────────┐
executeGraph──►│ RUNNING │
└────┬────┘
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌────────┐
│ PAUSED │ │COMPLETED │ │ FAILED │
└────┬────┘ └──────────┘ └────────┘
│ resume (human task complete / email received)
┌─────────┐
│ RUNNING │ (Weiter ab pausiertem Node)
└────┬────┘
├──► COMPLETED
├──► FAILED
├──► PAUSED (nächster Input-Node)
└──► CANCELLED (manuell abgebrochen)
```
**Pause-Gründe:**
- `input.*` Node → HumanTask erstellt → wartet auf User-Eingabe
- `email.checkEmail` → wartet auf neue Email (Background Poller)
### 7.3 HumanTask Lifecycle
```
┌─────────┐
│ PENDING │ (erstellt bei Pause)
└────┬────┘
├── User completes → COMPLETED (Run wird resumed)
├── User cancels → CANCELLED (Run wird cancelled)
└── Timeout → EXPIRED (Run failsafe: cancel oder default)
```
### 7.4 Scheduler Job Lifecycle
```
┌─────────────┐
│ UNREGISTERED │ (Workflow exists, kein Schedule)
└──────┬──────┘
│ activate + publish mit schedule invocation
┌─────────────┐
│ REGISTERED │ (APScheduler Job aktiv, eventId gesetzt)
└──────┬──────┘
├── Cron fires → executeGraph → WorkflowRun
├── Workflow deactivated → UNREGISTERED (Job entfernt, eventId=null)
├── Schedule geändert → Job replaced (replaceExisting=true)
└── Workflow gelöscht → UNREGISTERED (Job entfernt)
```
---
## 8. Empfehlungen und offene Punkte
### 8.1 Starke Konzepte (umsetzen)
| Konzept | Bewertung |
|---------|-----------|
| **Toolbox-Architektur** | Skaliert von 50 auf 100+ Tools ohne LLM-Überlastung. Klare Isolierung pro Thema. |
| **Sub-Agents pro Feature** | Bewährtes Pattern (Trustee), erweiterbar. Hält Main Agent schlank. |
| **Validierungs-Layer für Workflow-Modellierung** | Graph-Mutation nur über validierte Tools. LLM kann keinen inkonsistenten Graph erzeugen. |
| **v1-Scheduling-Patterns in v2 übernehmen** | Inkrementeller Sync ist robuster als Full-Wipe. Execution-Logs sind essentiell für Audit. |
| **State Machines als Grundlage** | Klare Zustände, klare Übergänge. Erweiterbar ohne Breaking Changes. |
### 8.2 Was User brauchen, um das als zentrales Arbeitsinstrument zu nutzen
| User Need | Feature | Priorität |
|-----------|---------|-----------|
| **Schneller Einstieg** | Workflow-Templates für häufige Use Cases (z.B. "Email-Attachment → AI Analyse → SharePoint Upload") | Hoch |
| **Vertrauen** | Tracing Log + Test Runner — User muss sehen, was der Workflow tut | Hoch |
| **Effizienz** | AI-gestützte Workflow-Erstellung per Chat | Hoch |
| **Fehlertoleranz** | Retry-Policies, Pause/Resume, klare Fehlermeldungen | Hoch |
| **Kontext** | UDB im Editor — Zugriff auf Files/Sources bei der Konfiguration | Hoch |
| **Zusammenarbeit** | Workflows teilen im Mandant, Rollen-basierter Zugriff | Mittel |
| **Monitoring** | Dashboard: laufende Automationen, nächste Runs, Fehlerrate, Kosten | Mittel |
| **Flexibilität** | Custom Script Nodes (Python-Sandbox), AI Decision Nodes | Mittel |
| **Mobile** | Notifications + Approval-Tasks per Mobile | Phase 2 |
### 8.3 Offene Architektur-Fragen
| Frage | Empfehlung |
|-------|------------|
| **Toolbox-Selektion: statisch oder dynamisch?** | Hybrid: Basis-Toolboxes pro Feature statisch konfiguriert, Connection-basierte Toolboxes dynamisch aktiviert. |
| **Sub-Agent Tiefe: verschachtelt?** | Maximal 1 Level tief. Main Agent → Sub-Agent. Kein Sub-Sub-Agent (zu komplex, unkontrollierbar). |
| **Graph-Execution und WorkflowManager: zusammenführen?** | Nein, getrennt lassen. `executeGraph` für den Graphical Editor, `WorkflowManager` bleibt für den dynamischen Workspace-Modus. Verschiedene Orchestrierungsmodelle. |
| **Feature-Code: `automation2` → `graphicalEditor`?** | Ja, Rename in Phase 1. Klarer Name für User und Entwickler. |
---
## 9. Phasen-Plan
### Phase 1: Foundation
- Unified Workflow Datenmodell (Workflow + WorkflowVersion + WorkflowRun + RunStepLog + HumanTask)
- Toolbox-Registry implementieren (Ablösung `_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 benötigt)
### Phase 4: Advanced
- AI Decision Node (`ai.decide`)
- Custom Script Node (Python Sandbox)
- Dynamische Toolbox-Aktivierung basierend auf Connections
- Sub-Agent Pattern für weitere Features
---
## 10. Glossar
| Begriff | Definition |
|---------|-----------|
| **Toolbox** | Thematisch gebündelte Gruppe von AI-Tools, die kontextabhängig aktiviert werden |
| **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 Ausführung einer WorkflowVersion |
| **RunStepLog** | Detaillierter Log pro Node-Execution innerhalb eines Runs |
| **HumanTask** | Aufgabe für menschliche Eingabe, erstellt bei Pause eines Runs |
| **Node** | Ausführungsschritt 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 für einen Workflow (Manual, Schedule, Webhook, Form, Event) |
| **UDB** | Unified Data Bar — Multi-Tab-Panel für Chats, Files und Sources |
| **ChatBar** | Wiederverwendbare Prompt-Input-Komponente |
| **Feature Container** | Einheitliches Verzeichnis-Pattern für ein Feature (main, route, interface, datamodel) |

View file

@ -0,0 +1,994 @@
# PowerOn Automation Unification — Datenmodell & Architektur
**Version:** 2.0
**Datum:** 2026-04-05
**Referenz:** [Automation Business Spec.md](./Automation%20Business%20Spec.md)
---
## 1. Plattform-Architektur: Schichten
```
┌──────────────────────────────────────────────────────────────────┐
│ FRONTEND (React/TS) │
│ │
│ Shared Components: ChatBar, ChatStream, UnifiedDataBar, │
│ FlowEditor, FormGenerator, FolderTree │
│ │
│ Feature Pages: WorkspacePage, GraphicalEditorPage, │
│ CommcoachDossierView, ChatbotView, ... │
└──────────────────────────────┬────────────────────────────────────┘
│ HTTP/SSE/WebSocket
┌──────────────────────────────▼────────────────────────────────────┐
│ GATEWAY (FastAPI) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 1: FEATURES (Feature-Instanz-gebunden) │ │
│ │ │ │
│ │ features/workspace/ features/graphicalEditor/ │ │
│ │ features/commcoach/ features/chatbot/ │ │
│ │ features/trustee/ features/realEstate/ │ │
│ │ features/neutralization/ features/teamsbot/ │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ nutzt │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ Layer 2: SERVICES (Feature-unabhängig, pro Request) │ │
│ │ │ │
│ │ serviceAgent serviceAi serviceKnowledge serviceBilling │ │
│ │ serviceChat serviceExtraction serviceGeneration │ │
│ │ serviceMessaging serviceWeb serviceSubscription │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ nutzt │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ Layer 3: SHARED INFRASTRUCTURE │ │
│ │ │ │
│ │ workflows/methods/ (Unified Action Library / Toolboxes)│ │
│ │ workflows/processing/ (ActionExecutor, methodDiscovery) │ │
│ │ workflows/automation2/ (Graph Execution Engine) │ │
│ │ interfaces/ (DB-Abstraktion + RBAC) │ │
│ │ aicore/ (Provider Plugins + Model Selector) │ │
│ │ datamodels/ (Pydantic Models) │ │
│ │ security/ (RBAC Engine) │ │
│ │ shared/ (Utilities, eventManagement) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
---
## 2. Feature-Container-Pattern (Ist-Zustand, bewährt)
### 2.1 Verzeichnisstruktur
```
features/<featureName>/
├── main<FeatureName>.py ◄── Feature-Definition + Registrierung
│ FEATURE_CODE: str Feature-Identifikator
│ UI_OBJECTS: List[Dict] RBAC-fähige UI-Objekte
│ RESOURCE_OBJECTS: List[Dict] RBAC-fähige Resource-Objekte
│ TEMPLATE_ROLES: List[Dict] Rollen-Templates mit AccessRules
│ REQUIRED_SERVICES: List[Dict] Service-Dependencies (optional)
│ registerFeature(catalog) → bool Registriert Objekte, synct Rollen in DB
│ getFeatureDefinition() → Dict Katalog-Metadaten (Label, Icon)
│ get<Feature>Services(user, ...) → Hub Service-Hub-Factory (optional)
├── routeFeature<FeatureName>.py ◄── HTTP API
│ router = APIRouter(prefix="/api/<featureName>")
│ [templateRouter] = APIRouter(...) Zusätzliche Router (optional)
├── interfaceFeature<FeatureName>.py ◄── Datenbankzugriff
<Feature>Objects CRUD + RBAC-Filterung
│ getInterface(user, mandateId, featureInstanceId) → Singleton
├── datamodelFeature<FeatureName>.py ◄── Pydantic-Datenmodelle
│ class <Model>(PowerOnModel): ... Feature-spezifische Entitäten
├── nodeDefinitions/ (optional) ◄── Graph-Node-Typen (für graphicalEditor)
│ triggers.py, flow.py, ai.py, ...
└── service<Name>/ (optional) ◄── Feature-interne Services
└── mainService<Name>.py
```
### 2.2 Automatische Discovery
```python
# system/registry.py — Kein manuelles Wiring nötig
discoverFeatureContainers()
# Scannt features/*/routeFeature*.py → Liste von Feature-Dirs
loadFeatureRouters(app)
# Importiert routeFeature*.py, mountet router + *Router
loadFeatureMainModules()
# Importiert main*.py, cached
registerAllFeaturesInCatalog(catalogService)
# Für jedes main-Module:
# getFeatureDefinition() → catalogService.registerFeatureDefinition()
# registerFeature() → UI/Resource-Objekte + Template-Rollen sync
```
### 2.3 Service-Hub-Wiring
```python
# serviceHub/__init__.py — Dynamisches Service-Wiring
class ServiceHub:
def __init__(self, user, workflow, mandateId, featureInstanceId):
# ServiceCenterContext erstellen
self._serviceCenterContext = ServiceCenterContext(...)
# Core Interfaces laden
self.interfaceDbApp = getAppInterface(user, mandateId)
self.interfaceDbChat = getChatInterface(user, mandateId, featureInstanceId)
# Feature-Interfaces dynamisch laden (glob features/*/interfaceFeature*.py)
self._loadFeatureInterfaces()
# Feature-Services dynamisch laden (glob features/*/service*/mainService*.py)
self._loadFeatureServices()
def __getattr__(self, name):
# Lazy: shared services via getService(name, context) bei Erstzugriff
```
---
## 3. RBAC-Architektur
### 3.1 Zwei getrennte Template-Rollen-Systeme
**WICHTIG:** Es gibt zwei getrennte Template-Systeme — diese sind **nicht** dieselben:
```
┌────────────────────────────────────────────────────────────────────┐
│ TEMPLATE-ROLLEN (Global) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ System Templates (für Mandanten) Priority 1 │ │
│ │ isSystemRole=true, mandateId=null, featureCode=null │ │
│ │ │ │
│ │ "admin" → Mandant-Administration │ │
│ │ "user" → Standard-User │ │
│ │ "viewer" → Nur-Lesen │ │
│ │ │ │
│ │ → kopiert bei Mandant-Erstellung (copySystemRolesToMandate)│ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Feature Templates (für Feature-Instanzen) Priority 1 │ │
│ │ isSystemRole=false, mandateId=null, featureCode="workspace"│ │
│ │ │ │
│ │ "workspace-admin" → alle UI/RESOURCE/DATA Rechte │ │
│ │ "workspace-user" → eingeschränkte DATA Rechte │ │
│ │ "workspace-viewer" → nur View │ │
│ │ │ │
│ │ → kopiert bei Feature-Instanz-Erstellung (_copyTemplateRoles)│ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ MANDATE-ROLLEN Priority 2 │
│ mandateId=X, featureInstanceId=null, featureCode=null │
│ │
│ "admin" (kopiert von System Template) │
│ "user" (kopiert von System Template) │
│ "viewer" (kopiert von System Template) │
│ → zugewiesen via UserMandate → UserMandateRole │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ INSTANZ-ROLLEN Priority 3 │
│ mandateId=X, featureInstanceId=Y, featureCode="workspace" │
│ │
│ "workspace-admin" (kopiert von Feature Template) │
│ "workspace-user" (kopiert von Feature Template) │
│ "workspace-viewer" (kopiert von Feature Template) │
│ → zugewiesen via FeatureAccess → FeatureAccessRole │
└────────────────────────────────────────────────────────────────────┘
```
### 3.2 Datenmodell RBAC
```python
class Role(PowerOnModel):
# Scope bestimmt die Priorität:
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 # System-Rollen nicht löschbar
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" # kein Zugriff
OWN = "o" # nur eigene Records
MANDATE = "m" # alle Records im Mandant
ALL = "a" # alle Records (System-Admin)
```
### 3.3 User-Zuweisung
```
User ──► UserMandate ──► UserMandateRole ──► Role (Mandate-Scope)
User ──► FeatureAccess ──► FeatureAccessRole ──► Role (Instanz-Scope)
```
```python
class UserMandate(PowerOnModel):
userId: str
mandateId: str
class UserMandateRole(PowerOnModel):
userMandateId: str # FK → UserMandate
roleId: str # FK → Role (mandateId=X, featureInstanceId=null)
class FeatureAccess(PowerOnModel):
userId: str
featureInstanceId: str
enabled: bool
class FeatureAccessRole(PowerOnModel):
featureAccessId: str # FK → FeatureAccess
roleId: str # FK → Role (mandateId=X, featureInstanceId=Y)
```
### 3.4 Resolution-Algorithmus
```
1. Sammle roleIds:
- Mandate-Rollen: UserMandate → UserMandateRole → roleId
- Instanz-Rollen: FeatureAccess → FeatureAccessRole → roleId
2. Lade AccessRules für alle roleIds (context + item filter)
3. Bestimme Priorität pro Rolle:
- Priority 3: role.featureInstanceId gesetzt (Instanz)
- Priority 2: role.mandateId gesetzt (Mandant)
- Priority 1: beides null (Global/Template)
4. DATA-Permissions: Nur Rules der HÖCHSTEN Priorität zählen
View: OR über alle Rules der höchsten Priorität
Item-Spezifität: exact > prefix > generic (innerhalb Priorität)
5. SQL WHERE Clause: buildRbacWhereClause(permissions, user, table)
```
---
## 4. Unified Workflow Datenmodell
### 4.1 Entitäten
```python
# Ziel: datamodels/datamodelWorkflowUnified.py oder
# features/graphicalEditor/datamodelFeatureGraphicalEditor.py
class WorkflowStatus(str, Enum):
DRAFT = "draft"
PUBLISHED = "published"
ARCHIVED = "archived"
class Workflow(PowerOnModel):
"""Workflow-Metadaten und Ownership."""
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):
"""Immutable Snapshot eines Workflow-Graphen."""
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]
```
### 4.2 State Machine: WorkflowVersion.status
```
┌───────┐
create ───►│ DRAFT │◄─── edit (graph mutation via API oder AI Tools)
└───┬───┘
│ publish
┌───────────┐
unpublish─►│ PUBLISHED │
(→ DRAFT) └─────┬─────┘
│ archive
┌───────────┐
│ ARCHIVED │──► re-publish (→ PUBLISHED)
└───────────┘
Invariante: Pro Workflow maximal 1 Version mit status=PUBLISHED.
Scheduler nutzt immer die PUBLISHED Version.
```
### 4.3 Run-Modell
```python
class RunStatus(str, Enum):
PENDING = "pending"
RUNNING = "running"
PAUSED = "paused"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
class WorkflowRun(PowerOnModel):
"""Einzelne Ausführung einer WorkflowVersion."""
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 für Resume
error: Optional[str] # Top-Level Fehler
costTokens: Optional[int] # Aggregierte Token-Kosten
costCredits: Optional[float] # Aggregierte Credit-Kosten
```
### 4.4 State Machine: WorkflowRun.status
```
┌─────────┐
executeGraph──►│ RUNNING │
└────┬────┘
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌────────┐
│ PAUSED │ │COMPLETED │ │ FAILED │
└───┬────┘ └──────────┘ └────────┘
│ resume(taskResult | emailReceived)
┌─────────┐
│ RUNNING │───► COMPLETED | FAILED | PAUSED | CANCELLED
└─────────┘
Pause-Gründe:
- input.* Node → HumanTask erstellt
- email.checkEmail → EmailWait (Background Poller)
Cancel:
- Manuell durch User
- Timeout bei HumanTask (expiresAt)
```
### 4.5 RunStepLog und HumanTask
```python
class StepStatus(str, Enum):
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
class RunStepLog(PowerOnModel):
"""Detaillierter Log pro Node-Execution."""
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]] # Node-Output
error: Optional[str]
startedAt: float
completedAt: Optional[float]
durationMs: Optional[int]
tokensUsed: Optional[int] # AI-Calls in diesem Step
retryCount: int = 0
class TaskStatus(str, Enum):
PENDING = "pending"
COMPLETED = "completed"
CANCELLED = "cancelled"
EXPIRED = "expired"
class HumanTask(PowerOnModel):
"""Aufgabe für menschliche Eingabe bei Pause."""
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] # Timeout
```
### 4.6 State Machine: HumanTask.status
```
┌─────────┐
│ PENDING │ (erstellt bei Run-Pause)
└────┬────┘
├── complete(result) → COMPLETED (Run wird resumed mit result)
├── cancel() → CANCELLED (Run wird cancelled)
└── expiresAt passed → EXPIRED (Run wird cancelled oder Default)
```
### 4.7 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
└──────────────────┘ ┌──────────▼───────────┐
│ WorkflowRun │
├──────────────────────┤
│ id │
│ workflowId (FK) │
│ versionId (FK) │
│ status │
│ trigger {} │
│ startedAt │
│ completedAt │
│ nodeOutputs {} │
│ currentNodeId │
│ resumeContext {} │
│ error │
│ costTokens │
│ costCredits │
└──────────┬───────────┘
1:N │ │ 1:N
┌─────────────┘ └──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ RunStepLog │ │ HumanTask │
├──────────────────┤ ├──────────────────┤
│ id │ │ id │
│ runId (FK) │ │ runId (FK) │
│ nodeId │ │ workflowId (FK) │
│ nodeType │ │ nodeId │
│ status │ │ nodeType │
│ inputSnapshot {} │ │ config {} │
│ output {} │ │ assigneeId │
│ error │ │ status │
│ startedAt │ │ result {} │
│ completedAt │ │ expiresAt │
│ durationMs │ └──────────────────┘
│ tokensUsed │
│ retryCount │
└──────────────────┘
```
---
## 5. AI-Tool-Architektur: Toolbox-Datenmodell
### 5.1 Toolbox-Definition
```python
# Ziel: serviceCenter/services/serviceAgent/toolboxRegistry.py
class ToolboxDefinition(BaseModel):
"""Definition einer thematischen Tool-Gruppe."""
id: str # z.B. "core", "email", "sharepoint", "workflow"
label: Dict[str, str] # Multilingual: {"en": "Email", "de": "E-Mail"}
description: str
featureCode: Optional[str] # null = Feature-unabhängig, "trustee" = Feature-spezifisch
tools: List[str] # Tool-Namen in dieser Toolbox
isDefault: bool = False # Immer aktiv (z.B. "core")
requiresConnection: Optional[str] # "outlook", "sharepoint" → auto-aktiviert bei Connection
class ToolboxRegistry:
"""Verwaltet Toolboxes und deren Zuordnung zu Tools."""
_toolboxes: Dict[str, ToolboxDefinition]
_toolRegistry: ToolRegistry # Referenz auf die bestehende ToolRegistry
def registerToolbox(self, toolbox: ToolboxDefinition): ...
def getActiveToolboxes(
self,
featureCode: str,
userConnections: List[str], # Aktive Connection-Typen des Users
explicitToolboxes: List[str], # Explizit aktivierte Toolboxes
) -> List[str]: ...
def getToolsForToolboxes(
self,
activeToolboxIds: List[str],
) -> List[ToolDefinition]: ...
```
### 5.2 AgentConfig (erweitert mit Eskalation)
```python
class AgentConfig(BaseModel):
maxRounds: int = 25
maxCostCHF: Optional[float] = None
initialToolboxes: List[str] = ["core"] # Tools bei Runde 1
availableToolboxes: List[str] = [] # Via requestToolbox anforderbar
temperature: Optional[float] = None
```
### 5.3 Dynamische Toolbox-Eskalation
**Kern-Idee:** Der Agent startet mit einem kompakten Initial-Set (`initialToolboxes`). Er kennt den Katalog aller `availableToolboxes`. Wenn er Spezial-Tools braucht, ruft er `requestToolbox(id)` auf — die Tools werden in der nächsten Runde bereitgestellt.
```
Runde 1:
Aktive Tools: core (readFile, webSearch, ...) + requestToolbox
System-Prompt: "Verfügbare Toolboxes: email, sharepoint, ai, ..."
User: "Lies meine letzten Emails und fasse sie zusammen"
LLM: → requestToolbox("email")
Runde 2:
Aktive Tools: core + email (outlook_readEmails, ...) + requestToolbox
LLM: → outlook_readEmails(connectionRef, folder="Inbox")
Runde 3:
LLM: → (Text-Antwort mit Zusammenfassung) → COMPLETED
```
**requestToolbox Meta-Tool:**
```python
{
"name": "requestToolbox",
"description": "Request specialized tools for the current task.",
"parameters": {
"toolboxId": {
"type": "string",
"enum": [...] # Dynamisch: nur availableToolboxes
},
"reason": {"type": "string"}
}
}
```
### 5.4 Toolbox-Registrierung (Architektur)
```
App Startup
├── _registerCoreTools(registry) → 40 Core-Tools in ToolRegistry
├── ActionToolAdapter.registerAll(registry) → dynamicMode Actions als Tools
└── ToolboxRegistry.registerAll()
├── Toolbox "core" → readFile, listFiles, webSearch, ... (isDefault=true)
├── Toolbox "ai" → summarizeContent, generateImage, ...
├── Toolbox "datasources" → browseDataSource, downloadFromDataSource, ...
├── Toolbox "email" → sendMail, outlook_readEmails, ... (requiresConnection="outlook")
├── Toolbox "sharepoint" → sharepoint_findDocumentPath, ... (requiresConnection="sharepoint")
├── Toolbox "clickup" → clickup_searchTasks, ... (requiresConnection="clickup")
├── Toolbox "jira" → jira_connectJira, ... (requiresConnection="jira")
├── Toolbox "workflow" → readWorkflowGraph, addNode, connectNodes, ...
├── Toolbox "trustee" → trustee_extractFromFiles, ... (featureCode="trustee")
└── Toolbox "chatbot" → chatbot_queryDatabase, ... (featureCode="chatbot")
Per Agent-Run:
├── Feature-Instanz Config → initialToolboxes + availableToolboxes
├── User Connections → filtert availableToolboxes (nur was User nutzen kann)
├── Runde 1: getToolsForToolboxes(initialToolboxes) + requestToolbox
└── Nach requestToolbox("email"):
Runde 2+: getToolsForToolboxes(initialToolboxes + ["email"]) + requestToolbox
```
### 5.5 Sub-Agent-Architektur
```python
# Bestehendes Pattern: featureDataAgent.py
# Aufruf-Kette:
# Main Agent → Tool "queryFeatureInstance" → _queryFeatureInstance()
# → runFeatureDataAgent(question, featureInstanceId, ...)
# → Eigene ToolRegistry (browseTable, queryTable)
# → runAgentLoop(prompt, registry, config=AgentConfig(maxRounds=5))
# → Antwort zurück → ToolResult → Main Agent
```
**Sub-Agents sind komplementär zu Toolboxes:**
- **Toolbox-Eskalation:** Agent bekommt Tools direkt und nutzt sie selbst (z.B. Email-Tools)
- **Sub-Agent:** Agent delegiert an spezialisierten Mini-Agent mit eigenem Wissen (z.B. Trustee DB-Schema)
Faustregel: Toolbox wenn die Tools generisch sind. Sub-Agent wenn Feature-spezifisches Kontext-/Schema-Wissen nötig ist.
### 5.6 Toolbox-Zuordnung pro Feature
| Feature | `initialToolboxes` | `availableToolboxes` (anforderbar) |
|---------|--------------------|------------------------------------|
| **Workspace** | `["core"]` | `["ai", "datasources", "email", "sharepoint", "clickup", "jira"]` — gefiltert nach User-Connections |
| **Graphical Editor Chat** | `["core", "workflow"]` | `["ai"]` |
| **CommCoach** | `["core"]` | `[]` (keine Eskalation) |
| **Chatbot** | (Eigenes Tool-System) | — |
---
## 6. Graph-Struktur und Node-Definitionen
### 6.1 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 und erstelle eine Zusammenfassung"
}
}
],
"connections": [
{
"source": "node-uuid",
"target": "node-uuid-2",
"sourceOutput": 0,
"targetInput": 0
}
]
}
```
### 6.2 Node-Definition-Schema
```python
# Jede Node-Definition ist ein Dict mit folgender Struktur:
{
"id": "ai.prompt", # Unique Node-Type-ID
"category": "ai", # Kategorie (für Palette-Gruppierung)
"label": {"en": "Prompt", "de": "Prompt"}, # Multilingual
"description": {"en": "...", "de": "..."},
"parameters": [
{
"name": "prompt",
"type": "string",
"required": True,
"description": {"en": "AI prompt", "de": "KI-Prompt"}
}
],
"inputs": 1, # Anzahl Eingänge
"outputs": 1, # Anzahl Ausgänge
"meta": {"icon": "mdi-robot", "color": "#9C27B0"},
# Interne Felder (nicht an Frontend exponiert):
"_method": "ai", # Method für ActionExecutor
"_action": "process", # Action für ActionExecutor
"_paramMap": {"prompt": "aiPrompt"}, # Parameter-Mapping Node → Action
}
```
### 6.3 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` |
### 6.4 Execution-Routing
```
executeGraph(graph, services, ...)
├── parseGraph() → nodes, connections, nodeIds
├── validateGraph() → Konsistenzprüfung
├── topoSort(nodes, connectionMap) → geordnete Node-Liste
└── Pro Node:
├── nodeType.startsWith("trigger.") → TriggerExecutor
│ → runEnvelope passthrough
├── 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
│ → raise PauseForHumanTaskError
└── nodeType.startsWith("ai." | "email." | "sharepoint." | ...) → ActionNodeExecutor
├── _getNodeDefinition(nodeType) → _method, _action, _paramMap
├── Parameter-Mapping (Node-Params → Action-Params)
├── Upstream-Daten mergen (documents von vorherigen Nodes)
└── ActionExecutor.executeAction(method, action, params)
```
---
## 7. Scheduler-Architektur (konsolidiert)
### 7.1 Konsolidierter Scheduler (Ziel)
```python
# Ziel: workflows/scheduler/mainScheduler.py
# Übernimmt die besten Patterns aus v1 und v2
class WorkflowScheduler:
"""Konsolidierter Scheduler für Workflow-Automationen."""
def start(self, eventUser):
"""Startup: Initial Sync + Delayed Sync + Callback Registration."""
eventManager.start()
self._syncScheduledWorkflows(eventUser)
self._registerDelayedSync(eventUser, delaySeconds=5)
callbackRegistry.register("workflow.changed",
lambda _: self._syncScheduledWorkflows(eventUser))
def stop(self, eventUser):
"""Shutdown: Cleanup."""
pass # APScheduler handles job cleanup
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:
# Register/Replace Job
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
)
# Persist eventId on Workflow (v1-Pattern: debugging)
if wf.eventId != jobId:
self._updateEventId(wf.id, jobId)
else:
# Deactivated: Remove Job + Clear eventId
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():
# Reload Workflow (v1-Pattern: Zustand könnte sich geändert haben)
current = self._getWorkflow(wf.id)
if not current or not current.active:
return
# Execute published version
services = self._buildServices(current, eventUser)
result = await executeGraph(
graph=version.graph,
services=services,
workflowId=current.id,
...
)
# Execution Log (v1-Pattern: capped audit trail)
self._appendExecutionLog(current.id, result)
# Thread-Bridge (v2-Pattern)
return self._wrapAsync(handler)
```
### 7.2 Scheduler State Machine
```
Workflow.active=false ──► UNREGISTERED (kein APScheduler Job)
│ activate + published version mit schedule invocation
Workflow.active=true ──► REGISTERED (eventId gesetzt, Job aktiv)
├── Cron fires → handler() → executeGraph → WorkflowRun
├── Workflow deactivated → remove job → UNREGISTERED (eventId=null)
├── Schedule geändert → replaceExisting=true → REGISTERED (neuer Trigger)
└── Workflow gelöscht → remove job → (Workflow gelöscht)
```
---
## 8. 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
│ │ │ router prefix: /api/workflows
│ │ │ Endpoints: CRUD Workflows, Versions, Execute, Runs, Tasks, NodeTypes, Chat
│ │ │
│ │ ├── interfaceFeatureGraphicalEditor.py
│ │ │ DB: poweron_workflows (oder poweron_automation2 renamed)
│ │ │ Tables: Workflow, WorkflowVersion, WorkflowRun, RunStepLog, HumanTask
│ │ │
│ │ ├── datamodelFeatureGraphicalEditor.py
│ │ │ Workflow, WorkflowVersion, WorkflowRun, RunStepLog, HumanTask
│ │ │ WorkflowStatus, RunStatus, StepStatus, TaskStatus (State Machines)
│ │ │
│ │ └── nodeDefinitions/
│ │ triggers.py, flow.py, input.py, ai.py, email.py, sharepoint.py, clickup.py, file.py
│ │
│ ├── workspace/ ◄── BLEIBT (AI Chat mit UDB)
│ ├── automation/ ◄── BLEIBT bis Ablösung durch graphicalEditor
│ ├── 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 (execution, executors, graphUtils)
│ ├── scheduler/ ◄── NEU (konsolidierter Scheduler)
│ │ └── mainScheduler.py
│ ├── automation/ ◄── BLEIBT bis Ablösung
│ └── workflowManager.py ◄── BLEIBT (für Workspace Dynamic Mode)
└── ...
```
---
## 9. Frontend Code-Struktur (Ziel)
```
frontend_nyla/src/
├── components/ ◄── SHARED COMPONENTS
│ ├── ChatBar/ ◄── NEU (extrahiert aus WorkspaceInput)
│ │ ├── ChatBar.tsx Props: onSend, onStop, onVoice,
│ │ │ fileAttachments, showProviderSelector,
│ │ │ showVoice, placeholder
│ │ ├── ChatBar.module.css
│ │ └── index.ts
│ │
│ ├── ChatStream/ ◄── NEU (extrahiert aus workspace/ChatStream)
│ │ ├── ChatStream.tsx SSE-driven message stream, wiederverwendbar
│ │ └── index.ts
│ │
│ ├── UnifiedDataBar/ ◄── BLEIBT
│ │
│ ├── FlowEditor/ ◄── RENAMED von Automation2FlowEditor
│ │ ├── editor/
│ │ │ ├── FlowEditor.tsx + AI Chat Panel (ChatBar + ChatStream)
│ │ │ ├── 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 automationApi + automation2Api)
│ └── ...
└── ...
```
---
## 10. Zusammenfassung: Architektur-Prinzipien
| Prinzip | Umsetzung |
|---------|-----------|
| **Feature-Container-Pattern** | Jedes Feature folgt dem gleichen Verzeichnis-Pattern mit automatischer Discovery. Kein manuelles Wiring. |
| **RBAC auf zwei Ebenen** | Mandate-Rollen (generell) + Instanz-Rollen (feature-spezifisch). Höchste Priorität gewinnt. |
| **State Machines als Grundlage** | WorkflowVersion, WorkflowRun, HumanTask und Scheduler haben klar definierte Zustände und Übergänge. |
| **Unified Action Library** | Eine Method/Action-Library für alle Consumers (Agent Tools, Graph Nodes, Workflow Processor). |
| **Toolboxes statt flache Tool-Liste** | Thematisch gruppierte Tools, kontextabhängig aktiviert. Skaliert auf 100+ Tools. |
| **Sub-Agents für Feature-Daten** | Main Agent bleibt schlank. Feature-spezifisches Wissen in dedizierten Sub-Agents. |
| **Keine Migration** | Direkte Implementierung auf Automation2-Basis. v1 bleibt bis Ablösung. |
| **Scheduler konsolidiert** | Beste Patterns aus v1 (inkrementell, eventId, reload+check) und v2 (interval, thread-bridge). |