Agent Canvas — DAG-based workflow orchestration
DAG-based workflow orchestration
Stateful component framework for multi-step AI workflows with LLM integration, tool calling, and error resilience.
🎯 Why This Module Exists
The Agent Canvas provides a structured, DAG-based execution model for complex multi-step workflows. Without it, workflows would degrade to imperative scripts, losing visibility into data dependencies and control flow. The Canvas enforces a clean separation between DSL (what to do) and Components (how to do it), allowing LLM applications to be built, debugged, and versioned as structured data rather than code. It enables introspection, serialization, and auditable execution histories. — Replace with 150+ words explaining what problem this module solves, why it exists as a separate module (not merged into something else), and what the system would look like without it. Use narrative voice: "Without this module, every component would need to..."
💡 Real-world analogy
Like a programming language's execution engine: the Canvas = interpreter; DSL JSON = bytecode; components = built-in functions; variables = stack frames; iteration = loop constructs. The key difference: all execution is traced and idempotent, designed for non-deterministic LLM outputs where retries and fallbacks are essential. — A vivid metaphor mapping this module's core concept to everyday life. 3-5 sentences.
Trait-1 Trait-2 Trait-3
Architecture
graph LR
subgraph DSL["DSL & Serialization"]
JSON["JSON Format"]
MIGRATE["dsl_migration
Backward Compat"]
end
subgraph CORE["Execution Engine"]
GRAPH["Graph
Base Class"]
CANVAS["Canvas
DSL Loader"]
SCHEDULE["Topological
Scheduling"]
end
subgraph COMPONENTS["Component System"]
BASE["ComponentBase
Lifecycle"]
LLM["LLM
Chat Models"]
AGENT["Agent
Tool Calling"]
BEGIN["Begin"]
SWITCH["Switch
Routing"]
end
subgraph EXT["External Integration"]
TOOLS["Tool Registry
24 Tools"]
SANDBOX["Code
Sandbox"]
end
subgraph STATE["State Management"]
VAR["Variable
Resolution"]
HISTORY["History
Tracking"]
end
JSON --> MIGRATE
MIGRATE --> GRAPH
GRAPH --> CANVAS
CANVAS --> SCHEDULE
SCHEDULE --> BASE
BASE --> LLM
BASE --> AGENT
BASE --> SWITCH
BASE --> TOOLS
TOOLS --> SANDBOX
CANVAS --> HISTORY
VAR --> HISTORY
Data Flow
graph TD
A["DSL JSON Input"] --> B["dsl_migration
Normalize Schema"]
B --> C["Graph.load()
Parse & Instantiate"]
C --> D["Topological Sort
Compute Execution Order"]
D --> E["Execute Nodes
in Dependency Order"]
E --> F["Resolve Variables
at Runtime"]
F --> G["Cache Outputs
for Downstream"]
G --> H["Record History
for Debugging"]
H --> I["Return Results
& History"]
style A fill:#e8f5e9
style I fill:#e8f5e9
🚶 Step-by-Step Narrated Walkthrough
Follow a single user submits a canvas with LLM + tool invocation nodes as it flows through the entire Agent Canvas pipeline:
- DSL Input: User provides a canvas as JSON with nodes (LLM, Agent, Switch, Iteration) and edges defining dependencies.
- dsl_migration: Legacy DSL schemas are normalized to the current format, ensuring backward compatibility with old workflows.
- Graph.load(): Canvas parses JSON, instantiates ComponentBase subclasses (LLM, Agent, tools) from the registry, and validates the DAG.
- Topological Sort: Computed at O(V+E) complexity; determines execution order such that all dependencies run before dependents.
- Variable System Setup: Global variables, component outputs (cpn_id@output), and nested iteration scopes are pre-registered.
- Greedy Execution: Canvas iterates: for each ready node (all dependencies complete), invoke component.run(inputs) and cache outputs.
- Iteration Handling: Iteration components wrap sub-DAGs; executed once per list item, with iteration-scoped variables re-bound each time.
- LLM Invocation: LLM component calls a chat model (OpenAI, Anthropic, etc.), optionally with structured output via JSON Schema.
- Agent Tool Loop: Agent extends LLM; LLM suggests a tool, Agent calls it from registry, feeds result back to LLM for next iteration.
- Switch Routing: Switch evaluates conditions; can route to multiple branches (fan-out) if multiple conditions match.
- Error Handling: Failures handled via max_retries (exponential backoff), exception_default_value (fallback), or exception_goto (reroute).
- History Recording: Every step—node start, input variables, outputs, errors—appended to Canvas.history immutably.
- Completion: After all nodes execute, Canvas returns outputs dict and complete history; caller inspects or re-runs with new inputs.
Key insight: Agent Canvas owns X. {{SIBLING_MODULE}} owns Y. The protocol boundary handles the translation between them.-->
🏗️ Design Decisions
| Decision | Choice Made | Alternatives Considered | Why This Approach |
|---|---|---|---|
| Data structure for X | Chosen approach | Alternative A, Alternative B | Rationale explaining WHY — what would go wrong with alternatives |
🔬 Algorithm Spotlight
Topological Scheduling + Variable Resolution
The Problem: What specific problem does this algorithm solve?
The Approach: How does it work, step by step, in plain English?
Why This Algorithm: What alternatives exist and why weren't they chosen? What are the time/space complexity trade-offs?
graph TD
A[Input] --> B{Decision Point}
B -->|Case 1| C[Fast Path]
B -->|Case 2| D[Complex Path]
C --> E[Output]
D --> F[Sub-step] --> E
style C fill:#e8f5e9
style D fill:#fff3e0
Trace through an example
Given: Example input
Step 1: What happens first and why
Step 2: What happens next — note the key decision here
Result: Expected output, and what would happen if the algorithm were naive
⚠️ What Could Go Wrong
| Failure Mode | Trigger | Detection | Recovery / Mitigation |
|---|---|---|---|
| Example failure | What causes it | How it's detected | How the code handles it, or what the user sees |
⚡ Performance Profile
Hot Paths
The most performance-sensitive code paths in this module:
- Path 1: Description — O(n) / O(1) — why this matters
- Path 2: Description — complexity — trade-off made
Resource Usage
- Memory: What's allocated, when it's freed, and any caching strategies
- I/O: What external resources are accessed and how often
Sub-Modules
Canvas & Execution
Graph class, Canvas subclass, DSL parsing, topological scheduling, variable resolution, history tracking.
- O(V+E) scheduling
- Lazy variable resolution
- Immutable history
- Backward compatibility via dsl_migration
Component System
ComponentBase lifecycle, 22 component types (LLM, Agent, Switch, Iteration, Loop), parameter validation, output caching.
- Stateful component model
- Registry-based lookup
- Parameter validation
- UI form generation
Tools & Integration
Tool framework, 24 built-in tools (code_exec, tavily, retrieval, SQL, crawler, email), plugin system, sandbox isolation.
- Extensible tool registry
- Code execution sandbox
- Retry + fallback error handling
- Resource limits
Directory Map
Source tree for Agent Canvas
agent/
├── canvas.py ← Graph base, Canvas subclass, DSL loader
├── component/ ← Component system (22 types)
│ ├── base.py ← ComponentBase, ComponentParamBase
│ ├── llm.py ← LLM chat model integration
│ ├── agent_with_tools.py ← Agent subclass with tool calling
│ ├── iteration.py ← Iteration component (list processor)
│ ├── loop.py ← Loop component (structural repeat)
│ ├── switch.py ← Switch (conditional routing)
│ ├── begin.py ← Begin (workflow entry)
│ ├── message.py ← Message (string formatting)
│ ├── invoke.py ← Invoke (direct tool call)
│ └── ...15 more component types
├── tools/ ← Tool integrations
│ ├── base.py ← ToolBase class
│ ├── code_exec.py ← Python sandbox execution
│ ├── tavily.py ← Web search integration
│ ├── retrieval.py ← RAG database query
│ ├── sql_executor.py ← SQL database query
│ ├── web_crawler.py ← HTML extraction
│ ├── email.py ← SMTP client
│ ├── api_request.py ← Generic HTTP client
│ └── ...16 more tool integrations
├── sandbox/ ← Code execution sandbox
├── templates/ ← Pre-built DSL templates
├── plugin/ ← Third-party plugin loader
├── dsl_migration.py ← Legacy DSL backward compatibility
└── __init__.py ← Module exports