Skip to content

Implement graph-to-API serialization for workflow canvas save/load #1100

Description

@geoffjay

Summary

Build the serialization layer that converts between React Flow graph state (nodes, edges, positions) and the workflow REST API format (CreateWorkflowRequest / WorkflowResponse). This enables saving canvas compositions as real workflows and loading existing workflows back into the canvas.

Context

The workflow API is designed around individual workflows: one trigger config, one agent, one prompt template. The visual canvas represents multiple workflows as a connected graph. The serialization layer must bridge these two models:

  • Save (graph to API): Each trigger-to-agent edge in the graph becomes one CreateWorkflowRequest
  • Load (API to graph): Each WorkflowResponse becomes a trigger node + agent node + edge, with layout positions reconstructed or auto-generated

Data model mapping

Canvas Graph                    REST API
-----------                     --------
TriggerNode --PromptEdge--> AgentNode    =    WorkflowConfig {
  triggerConfig                                   trigger_config: TriggerConfig,
                  promptTemplate                  prompt_template: String,
                  pollIntervalSecs                poll_interval_secs: u64,
                                 agentId          agent_id: Uuid,
                                                  name: String,
                                                  enabled: bool,
                                                  tool_policy: ToolPolicy,
                                               }

Implementation Details

1. Graph-to-API serializer

Create ui/src/components/workflows/canvas/serialization.ts:

/**
 * Convert React Flow graph state into an array of workflow API requests.
 * Each trigger->agent edge becomes one CreateWorkflowRequest.
 */
export function graphToWorkflows(
  nodes: Node<TriggerNodeData | AgentNodeData>[],
  edges: Edge<PromptEdgeData>[],
): CreateWorkflowRequest[];

/**
 * Convert a set of workflow API responses into React Flow graph state.
 * Reconstructs nodes, edges, and layout positions.
 */
export function workflowsToGraph(
  workflows: Workflow[],
  agents: Agent[],
): { nodes: Node[]; edges: Edge[] };

2. Serialization rules

Graph to API:

  • Each edge with a trigger source and agent target creates one CreateWorkflowRequest
  • Workflow name derived from: "[trigger_type]-to-[agent_name]" (user can override)
  • trigger_config from trigger node data
  • agent_id from agent node data
  • prompt_template from edge data
  • poll_interval_secs from edge data
  • enabled from trigger node data
  • tool_policy defaults to agent's policy (can be overridden)
  • Validation: reject graphs with unconnected nodes, missing required config fields

API to Graph:

  • Group workflows by agent_id to deduplicate agent nodes
  • Create one trigger node per workflow
  • Create one agent node per unique agent_id
  • Create one edge per workflow connecting its trigger to its agent
  • Populate edge data with prompt_template and poll_interval_secs
  • Generate layout positions (simple grid or dagre layout)

3. Position metadata

Store canvas layout positions as part of the serialized state:

interface CanvasLayout {
  nodes: Record<string, { x: number; y: number }>;  // nodeId -> position
  viewport: { x: number; y: number; zoom: number };
}

Since the workflow API doesn't have a layout field, positions can be:

  • Stored in localStorage keyed by a hash of workflow IDs
  • Or auto-generated on load using a simple layout algorithm

4. Validation

export interface SerializationError {
  type: "unconnected_trigger" | "unconnected_agent" | "missing_config" | "invalid_edge";
  nodeId?: string;
  edgeId?: string;
  message: string;
}

export function validateGraph(
  nodes: Node[],
  edges: Edge[],
): SerializationError[];

5. Tests

  • Round-trip test: create graph -> serialize -> deserialize -> compare
  • Single workflow serialization
  • Multi-workflow graph with shared agent
  • Validation catches unconnected nodes
  • Validation catches missing required trigger config fields
  • Edge cases: empty graph, single node, cycles

Files to Create

  • ui/src/components/workflows/canvas/serialization.ts
  • ui/src/components/workflows/canvas/serialization.test.ts
  • ui/src/components/workflows/canvas/validation.ts (optional, can be in serialization.ts)

Acceptance Criteria

  • graphToWorkflows() converts canvas graph to valid CreateWorkflowRequest[]
  • workflowsToGraph() converts Workflow[] to React Flow nodes/edges
  • Shared agents are deduplicated (one node per unique agent)
  • Validation detects unconnected nodes and missing config
  • Layout positions are stored and restored (localStorage)
  • Round-trip serialization preserves all workflow data
  • Comprehensive tests for serialization, deserialization, and validation

Blocked By

Stack Base

Stack on: feature/autonomous-pipeline after #1096, #1097, and #1098 merge.

Metadata

Metadata

Assignees

No one assigned

    Labels

    complexity:largeLarge scope: 200+ lines, multiple filesenhancementNew feature or requestfrontendFrontend application codetriagedIssue has been triaged, ready for planning or implementationuiUser interface and frontend

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions