wiki/c-work/1-plan/2026-04-automation-unification.md
2026-04-06 00:46:32 +02:00

46 KiB

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

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:

{
    "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

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

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

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

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)

{
  "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)

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
  • 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