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.changedCallback-Pattern fuer reaktive Synchronisation- Handler laedt Workflow neu und prueft
activevor Execution - Execution-Logs als Audit Trail (capped auf 50 Eintraege)
- Creator-User Tracking via
sysCreatedByfuer Execution-Kontext
Von v2 beibehalten:
IntervalTriggerfuer 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:
- Lade Mandate-Rollen (via
UserMandate->UserMandateRole) - Lade Feature-Instanz-Rollen (via
FeatureAccess->FeatureAccessRole) - Lade
AccessRulesfuer alle gefundenen Rollen - Hoechste Prioritaet gewinnt bei DATA-Permissions
- View wird OR-verknuepft ueber alle Rollen der hoechsten Prioritaetsstufe
- 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 |
Links
- Detail-Spec (Business): z-archive/b-reference/automation-business-spec.md
- Detail-Spec (Datenmodell): z-archive/b-reference/automation-data-model.md
- Referenz (Ist-Zusammenfassung): b-reference/gateway/automation.md
Abschluss
- b-reference/gateway/automation.md aktualisiert
- TOPICS.md aktualisiert
- Dieses Dokument -> z-archive/ verschoben