323 lines
8.9 KiB
Markdown
323 lines
8.9 KiB
Markdown
# Canvas Reasoner: Auto-Inferring Workflow Connections
|
||||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
The **CanvasReasonerActivity** uses LLM reasoning to automatically suggest connections between workflow activities when users drop new nodes onto the canvas. It analyzes input/output compatibility and detects connection problems.
|
|||
|
|
|
|||
|
|
## Connection Logic
|
|||
|
|
|
|||
|
|
### How It Works
|
|||
|
|
|
|||
|
|
1. **Analyze Node Schemas**
|
|||
|
|
- Get each activity's input/output fields from knowledge base
|
|||
|
|
- Activities are classified as:
|
|||
|
|
- **Generators**: No inputs, has outputs (e.g., API call, trigger)
|
|||
|
|
- **Processors**: Has inputs and outputs (e.g., analyze code, security scan)
|
|||
|
|
- **Sinks/Terminals**: Has inputs, no outputs (e.g., notification, approval)
|
|||
|
|
|
|||
|
|
2. **LLM Reasoning**
|
|||
|
|
- Pass all nodes + their schemas to reasoning model
|
|||
|
|
- Ask LLM to suggest edges based on:
|
|||
|
|
- Type compatibility (string→string, object→object)
|
|||
|
|
- Logical execution order
|
|||
|
|
- Data flow requirements
|
|||
|
|
- Common workflow patterns
|
|||
|
|
|
|||
|
|
3. **Validate Suggestions**
|
|||
|
|
- Check all suggested edges exist in node map
|
|||
|
|
- Skip self-loops
|
|||
|
|
- Remove duplicates
|
|||
|
|
|
|||
|
|
4. **Compatibility Checking**
|
|||
|
|
- For each suggested edge: `source → target`
|
|||
|
|
- Verify source produces outputs
|
|||
|
|
- Verify target accepts inputs
|
|||
|
|
- Check output/input type compatibility
|
|||
|
|
- Flag incompatible connections
|
|||
|
|
|
|||
|
|
5. **Identify Issues**
|
|||
|
|
- Collect all incompatible edges
|
|||
|
|
- Identify disconnected nodes (no edges in/out)
|
|||
|
|
- Generate user alerts for problems
|
|||
|
|
|
|||
|
|
## Connection Impossibility Detection
|
|||
|
|
|
|||
|
|
### Why Connections Fail
|
|||
|
|
|
|||
|
|
1. **Missing Outputs**
|
|||
|
|
```
|
|||
|
|
NotifyStatusActivity → AnalyzeCodeActivity
|
|||
|
|
⚠️ NotifyStatusActivity produces no outputs
|
|||
|
|
Reason: Notification is terminal activity (sink)
|
|||
|
|
Solution: Add an intermediate processor that has outputs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **Missing Inputs**
|
|||
|
|
```
|
|||
|
|
CloneRepoActivity → ApproveWorkflowActivity
|
|||
|
|
⚠️ ApproveWorkflowActivity accepts no inputs
|
|||
|
|
Reason: Approval is a terminal activity (sink)
|
|||
|
|
Solution: ApproveWorkflowActivity only works as final step
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **Type Mismatch**
|
|||
|
|
```
|
|||
|
|
LLMInferenceActivity (output: string) → DeploymentPreCheckActivity (input: object)
|
|||
|
|
⚠️ String output cannot satisfy object input requirement
|
|||
|
|
Reason: Incompatible data types
|
|||
|
|
Solution: Use LLM transformation node to convert string→object
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **Semantic Incompatibility**
|
|||
|
|
```
|
|||
|
|
NotifyStatusActivity → CloneRepoActivity
|
|||
|
|
⚠️ No logical connection between these activities
|
|||
|
|
Reason: Notification cannot be input to clone operation
|
|||
|
|
Solution: Ensure data flow makes semantic sense
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Incompatibility Data Structure
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"incompatible_edges": [
|
|||
|
|
{
|
|||
|
|
"source": "node-1",
|
|||
|
|
"target": "node-2",
|
|||
|
|
"reason": "Source activity produces no outputs",
|
|||
|
|
"source_needs": "any output",
|
|||
|
|
"target_needs": "path, depth",
|
|||
|
|
"suggestion": "Use LLM transformation to map outputs to inputs"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"disconnected_nodes": ["node-5", "node-8"],
|
|||
|
|
"user_alerts": [
|
|||
|
|
"⚠️ node-1 → node-2: Source activity produces no outputs. Use LLM transformation to map outputs to inputs",
|
|||
|
|
"🔌 Node 'NotifyStatus-1' has no connections. Consider adding edges or removing it."
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## User Alerts
|
|||
|
|
|
|||
|
|
### Alert Types
|
|||
|
|
|
|||
|
|
1. **Incompatibility Warnings** (⚠️)
|
|||
|
|
```
|
|||
|
|
⚠️ source → target: reason. suggestion.
|
|||
|
|
```
|
|||
|
|
- Highlighted in red on canvas
|
|||
|
|
- Shows in error sidebar
|
|||
|
|
- Prevents workflow execution until fixed
|
|||
|
|
|
|||
|
|
2. **Disconnection Warnings** (🔌)
|
|||
|
|
```
|
|||
|
|
🔌 Node 'label' has no connections. Consider adding edges or removing it.
|
|||
|
|
```
|
|||
|
|
- Highlighted in yellow
|
|||
|
|
- Nodes with no input/output edges
|
|||
|
|
- May be valid (first step, last step) or indicate design error
|
|||
|
|
|
|||
|
|
3. **Type Mismatch Info** (ℹ️)
|
|||
|
|
```
|
|||
|
|
ℹ️ To connect source → target, use transformer to map: {source_outputs} → {target_inputs}
|
|||
|
|
```
|
|||
|
|
- Suggestion to use intermediate LLM node
|
|||
|
|
- Provides mapping information
|
|||
|
|
|
|||
|
|
## Frontend Integration
|
|||
|
|
|
|||
|
|
### Canvas UI Feedback
|
|||
|
|
|
|||
|
|
When CanvasReasonerActivity returns incompatibilities:
|
|||
|
|
|
|||
|
|
1. **Visual Markers**
|
|||
|
|
- Incompatible suggested edges: ❌ red dashed line (don't auto-add)
|
|||
|
|
- Disconnected nodes: ⚠️ yellow border
|
|||
|
|
|
|||
|
|
2. **Sidebar Alerts**
|
|||
|
|
```
|
|||
|
|
🚨 Connection Issues (3)
|
|||
|
|
|
|||
|
|
⚠️ CloneRepo → ApproveWorkflow
|
|||
|
|
Reason: ApproveWorkflow is terminal (no outputs)
|
|||
|
|
Suggestion: Place ApproveWorkflow at end of workflow
|
|||
|
|
|
|||
|
|
⚠️ LLMInference → DeploymentPreCheck
|
|||
|
|
Reason: Type mismatch (string ≠ object)
|
|||
|
|
Suggestion: Add LLM transformation node
|
|||
|
|
|
|||
|
|
🔌 SecurityScan-1 has no incoming edges
|
|||
|
|
Suggestion: Connect CloneRepo → SecurityScan
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **User Actions**
|
|||
|
|
- ✅ Accept suggestions (green edges)
|
|||
|
|
- ❌ Reject incompatible edges
|
|||
|
|
- 🔧 Add transformer nodes
|
|||
|
|
- 🗑️ Remove disconnected nodes
|
|||
|
|
|
|||
|
|
### API Response Example
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"suggested_edges": [
|
|||
|
|
{"source": "clone-1", "target": "analyze-1"},
|
|||
|
|
{"source": "analyze-1", "target": "security-1"},
|
|||
|
|
{"source": "security-1", "target": "report-1"}
|
|||
|
|
],
|
|||
|
|
"reasoning": "Standard code review workflow: clone → analyze → scan → report",
|
|||
|
|
"confidence": 0.92,
|
|||
|
|
"incompatible_edges": [
|
|||
|
|
{
|
|||
|
|
"source": "report-1",
|
|||
|
|
"target": "approve-1",
|
|||
|
|
"reason": "ReportGenerator has no outputs (terminal activity)",
|
|||
|
|
"suggestion": "ApproveWorkflow can only be a final step"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"disconnected_nodes": [],
|
|||
|
|
"user_alerts": [
|
|||
|
|
"⚠️ report-1 → approve-1: ReportGenerator has no outputs (terminal activity). ApproveWorkflow can only be a final step"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Knowledge Base Schema
|
|||
|
|
|
|||
|
|
Each activity in `activity_knowledge_base.json` defines:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"name": "CloneRepoActivity",
|
|||
|
|
"inputs": {
|
|||
|
|
"repo": {"type": "string", "required": true},
|
|||
|
|
"branch": {"type": "string", "required": false}
|
|||
|
|
},
|
|||
|
|
"outputs": {
|
|||
|
|
"path": {"type": "string"},
|
|||
|
|
"commit": {"type": "string"}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Classification Rules
|
|||
|
|
|
|||
|
|
- **Generator** (0 inputs): trigger, API call, schedule
|
|||
|
|
- **Processor** (1+ inputs, 1+ outputs): analysis, transformation, scan
|
|||
|
|
- **Sink** (1+ inputs, 0 outputs): notification, approval, archive
|
|||
|
|
- **Bypass** (0 inputs, 0 outputs): rare - usually error
|
|||
|
|
|
|||
|
|
## Common Patterns
|
|||
|
|
|
|||
|
|
### ✅ Valid Chains
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
CloneRepo → Analyze → SecurityScan → Report
|
|||
|
|
(generator) → (processor) → (processor) → (sink)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Trigger → LLMInference → Decision → (Branch: Notify OR Approve)
|
|||
|
|
(gen) → (processor) → (processor) → (sink)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ❌ Invalid Chains
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Notify → CloneRepo ❌
|
|||
|
|
(sink) → (generator) - backward flow
|
|||
|
|
|
|||
|
|
CloneRepo → CloneRepo → Analyze ❌
|
|||
|
|
self-loop - no benefit
|
|||
|
|
|
|||
|
|
Analyze → Approve → Notify ❌
|
|||
|
|
Approve is terminal (sink), can't output to Notify
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Edge Cases
|
|||
|
|
|
|||
|
|
### Multiple Outputs → Single Input
|
|||
|
|
```
|
|||
|
|
SecurityScan → Report
|
|||
|
|
SecurityScan outputs: [issues, metrics, severity]
|
|||
|
|
Report inputs: [report_data]
|
|||
|
|
|
|||
|
|
LLM must infer: bundle all outputs into single report_data object
|
|||
|
|
Confidence: 0.7 (requires transformation)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Terminal Activities
|
|||
|
|
- **ApproveWorkflowActivity**: Must be last (blocks workflow)
|
|||
|
|
- **NotifyStatusActivity**: Can be mid-workflow (async notify)
|
|||
|
|
- **ArchiveResultsActivity**: Should be last (persistence)
|
|||
|
|
|
|||
|
|
### Data Transformation
|
|||
|
|
When source outputs don't match target inputs:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# User can insert transformer node:
|
|||
|
|
LLMInference → [LLMTransformer] → DeploymentPreCheck
|
|||
|
|
|
|||
|
|
# Transformer:
|
|||
|
|
# - Input: LLMInference.output (string)
|
|||
|
|
# - Output: DeploymentPreCheck.requirements (object)
|
|||
|
|
# - Action: Call LLM to convert format
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Testing Incompatibility Detection
|
|||
|
|
|
|||
|
|
### Test Case 1: Terminal Activity as Source
|
|||
|
|
```go
|
|||
|
|
source := db.WorkflowNode{ID: "n1", Type: "notify-status", Label: "Notify"}
|
|||
|
|
target := db.WorkflowNode{ID: "n2", Type: "clone-repo", Label: "Clone"}
|
|||
|
|
|
|||
|
|
warnings := CheckConnectionCompatibility(source, target)
|
|||
|
|
// Should warn: NotifyStatusActivity produces no outputs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Test Case 2: Type Mismatch
|
|||
|
|
```go
|
|||
|
|
source := db.WorkflowNode{ID: "n1", Type: "llm-inference", ...}
|
|||
|
|
target := db.WorkflowNode{ID: "n2", Type: "deployment-check", ...}
|
|||
|
|
|
|||
|
|
warnings := CheckConnectionCompatibility(source, target)
|
|||
|
|
// Should warn: string output ≠ object input
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Test Case 3: Disconnected Node
|
|||
|
|
```go
|
|||
|
|
nodes := []db.WorkflowNode{n1, n2, n3}
|
|||
|
|
edges := []db.WorkflowEdge{{Source: "n1", Target: "n2"}}
|
|||
|
|
|
|||
|
|
disconnected := IdentifyDisconnectedNodes(nodes, edges)
|
|||
|
|
// Should return ["n3"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Future Enhancements
|
|||
|
|
|
|||
|
|
1. **Automatic Transformer Insertion**
|
|||
|
|
- Detect incompatibilities
|
|||
|
|
- Auto-suggest LLM transformer nodes
|
|||
|
|
- Chain transformers if needed
|
|||
|
|
|
|||
|
|
2. **Confidence Scoring**
|
|||
|
|
- Increase when types match perfectly
|
|||
|
|
- Decrease for semantic mismatches
|
|||
|
|
- Factor in activity dependencies
|
|||
|
|
|
|||
|
|
3. **Learning from History**
|
|||
|
|
- Track successful workflows
|
|||
|
|
- Remember user edits to suggestions
|
|||
|
|
- Improve LLM prompts over time
|
|||
|
|
|
|||
|
|
4. **Multi-Path Analysis**
|
|||
|
|
- Suggest multiple connection topologies
|
|||
|
|
- Show cost/efficiency of each
|
|||
|
|
- Rank by execution time/cost
|
|||
|
|
|
|||
|
|
5. **Dry-Run Validation**
|
|||
|
|
- Execute suggested workflow in simulation
|
|||
|
|
- Catch runtime errors early
|
|||
|
|
- Show data flow through each node
|