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.

N
Sub-Modules
N
Key Metric
N
Another Metric
Lang
Language

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:

  1. DSL Input: User provides a canvas as JSON with nodes (LLM, Agent, Switch, Iteration) and edges defining dependencies.
  2. dsl_migration: Legacy DSL schemas are normalized to the current format, ensuring backward compatibility with old workflows.
  3. Graph.load(): Canvas parses JSON, instantiates ComponentBase subclasses (LLM, Agent, tools) from the registry, and validates the DAG.
  4. Topological Sort: Computed at O(V+E) complexity; determines execution order such that all dependencies run before dependents.
  5. Variable System Setup: Global variables, component outputs (cpn_id@output), and nested iteration scopes are pre-registered.
  6. Greedy Execution: Canvas iterates: for each ready node (all dependencies complete), invoke component.run(inputs) and cache outputs.
  7. Iteration Handling: Iteration components wrap sub-DAGs; executed once per list item, with iteration-scoped variables re-bound each time.
  8. LLM Invocation: LLM component calls a chat model (OpenAI, Anthropic, etc.), optionally with structured output via JSON Schema.
  9. Agent Tool Loop: Agent extends LLM; LLM suggests a tool, Agent calls it from registry, feeds result back to LLM for next iteration.
  10. Switch Routing: Switch evaluates conditions; can route to multiple branches (fan-out) if multiple conditions match.
  11. Error Handling: Failures handled via max_retries (exponential backoff), exception_default_value (fallback), or exception_goto (reroute).
  12. History Recording: Every step—node start, input variables, outputs, errors—appended to Canvas.history immutably.
  13. Completion: After all nodes execute, Canvas returns outputs dict and complete history; caller inspects or re-runs with new inputs.
>Boundary: Response Boundary-->>A: Return data
Key insight: Agent Canvas owns X. {{SIBLING_MODULE}} owns Y. The protocol boundary handles the translation between them.
-->

🏗️ Design Decisions

DecisionChoice MadeAlternatives ConsideredWhy This Approach
Data structure for XChosen approachAlternative A, Alternative BRationale 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 ModeTriggerDetectionRecovery / Mitigation
Example failureWhat causes itHow it's detectedHow the code handles it, or what the user sees

⚡ Performance Profile

Hot Paths

The most performance-sensitive code paths in this module:

Resource Usage

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