20 KiB
Draft Flowchart System — Complete Reference
The draft flowchart system bridges user-facing workflow design (planning phase) and the runtime agent graph (execution phase). During planning, the queen agent creates a flowchart that the user reviews. On approval, decision nodes are dissolved into runtime-compatible structures, and the original flowchart is preserved for live status overlay during execution.
Architecture Overview
Planning Phase Build Gate Runtime Phase
─────────────────────────────────────────────────────────────────────────────
Queen LLM confirm_and_build() Graph Executor
│ │ │
▼ ▼ ▼
save_agent_draft() ┌──────────────────────┐ Node execution
│ │ dissolve_decision_nodes│ with status
▼ │ │ │
DraftGraph (SSE) ────► │ Decision diamonds │ ▼
│ │ merged into │ Flowchart Map
▼ │ predecessor criteria │ inverts to
Frontend renders │ │ overlay status
Flowchart with │ Original draft │ on original
with diamond │ preserved │ flowchart
decisions │ │
└──────────────────────┘
Key files:
- Backend:
core/framework/tools/queen_lifecycle_tools.py— draft creation, dissolution - Backend:
core/framework/tools/flowchart_utils.py— type definitions, classification, persistence - Backend:
core/framework/server/routes_graphs.py— REST endpoints - Frontend:
core/frontend/src/components/DraftGraph.tsx— SVG flowchart renderer - Frontend:
core/frontend/src/api/types.ts— TypeScript interfaces - Frontend:
core/frontend/src/pages/workspace.tsx— state management and conditional rendering
1. JSON Schemas
Tool: save_agent_draft — Input Schema
{
"type": "object",
"required": ["agent_name", "goal", "nodes"],
"properties": {
"agent_name": {
"type": "string",
"description": "Snake_case name for the agent (e.g. 'lead_router_agent')"
},
"goal": {
"type": "string",
"description": "High-level goal description for the agent"
},
"description": {
"type": "string",
"description": "Brief description of what the agent does"
},
"nodes": {
"type": "array",
"description": "Graph nodes. Only 'id' is required; all other fields are optional hints.",
"items": { "$ref": "#/$defs/DraftNode" }
},
"edges": {
"type": "array",
"description": "Connections between nodes. Auto-generated as linear if omitted.",
"items": { "$ref": "#/$defs/DraftEdge" }
},
"terminal_nodes": {
"type": "array",
"items": { "type": "string" },
"description": "Node IDs that are terminal (end) nodes. Auto-detected from edges if omitted."
},
"success_criteria": {
"type": "array",
"items": { "type": "string" },
"description": "Agent-level success criteria"
},
"constraints": {
"type": "array",
"items": { "type": "string" },
"description": "Agent-level constraints"
}
}
}
Node Schema (DraftNode)
{
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"description": "Kebab-case node identifier (e.g. 'enrich-lead')"
},
"name": {
"type": "string",
"description": "Human-readable display name. Defaults to id if omitted."
},
"description": {
"type": "string",
"description": "What this node does (business logic). Used for auto-classification."
},
"node_type": {
"type": "string",
"enum": ["event_loop", "gcu"],
"default": "event_loop",
"description": "Runtime node type. 'gcu' maps to browser automation."
},
"flowchart_type": {
"type": "string",
"enum": [
"start", "terminal", "process", "decision",
"io", "document", "database", "subprocess", "browser"
],
"description": "Flowchart symbol type. Auto-detected if omitted."
},
"tools": {
"type": "array",
"items": { "type": "string" },
"description": "Planned tool names (hints for scaffolder, not validated)"
},
"input_keys": {
"type": "array",
"items": { "type": "string" },
"description": "Expected input memory keys"
},
"output_keys": {
"type": "array",
"items": { "type": "string" },
"description": "Expected output memory keys"
},
"success_criteria": {
"type": "string",
"description": "What success looks like for this node"
},
"decision_clause": {
"type": "string",
"description": "For decision nodes only: the yes/no question to evaluate (e.g. 'Is amount > $100?'). During dissolution, this becomes the predecessor node's success_criteria."
}
}
}
Edge Schema (DraftEdge)
{
"type": "object",
"required": ["source", "target"],
"properties": {
"source": {
"type": "string",
"description": "Source node ID"
},
"target": {
"type": "string",
"description": "Target node ID"
},
"condition": {
"type": "string",
"enum": ["always", "on_success", "on_failure", "conditional", "llm_decide"],
"default": "on_success",
"description": "Edge traversal condition"
},
"description": {
"type": "string",
"description": "Human-readable description of when this edge is taken"
},
"label": {
"type": "string",
"description": "Short label shown on the flowchart edge (e.g. 'Yes', 'No', 'Retry')"
}
}
}
Output: Enriched Draft Graph Object
After save_agent_draft processes the input, it stores and emits an enriched draft with auto-classified flowchart metadata. This is the structure sent via the draft_graph_updated SSE event and returned by GET /api/sessions/{id}/draft-graph.
{
"agent_name": "lead_router_agent",
"goal": "Enrich and route incoming leads",
"description": "Automated lead enrichment and routing agent",
"success_criteria": ["Lead score calculated", "Correct tier assigned"],
"constraints": ["Apollo enrichment required before routing"],
"entry_node": "intake",
"terminal_nodes": ["route"],
"nodes": [
{
"id": "intake",
"name": "Intake",
"description": "Fetch contact from HubSpot",
"node_type": "event_loop",
"tools": ["hubspot_get_contact"],
"input_keys": ["contact_id"],
"output_keys": ["contact_data", "domain"],
"success_criteria": "Contact data retrieved",
"decision_clause": "",
"sub_agents": [],
"flowchart_type": "start",
"flowchart_shape": "stadium",
"flowchart_color": "#8aad3f"
},
{
"id": "check-tier",
"name": "Check Tier",
"description": "",
"node_type": "event_loop",
"decision_clause": "Is lead score > 80?",
"flowchart_type": "decision",
"flowchart_shape": "diamond",
"flowchart_color": "#d89d26"
}
],
"edges": [
{
"id": "edge-0",
"source": "intake",
"target": "check-tier",
"condition": "on_success",
"description": "",
"label": ""
},
{
"id": "edge-1",
"source": "check-tier",
"target": "enrich",
"condition": "on_success",
"description": "",
"label": "Yes"
},
{
"id": "edge-2",
"source": "check-tier",
"target": "route",
"condition": "on_failure",
"description": "",
"label": "No"
}
],
"flowchart_legend": {
"start": { "shape": "stadium", "color": "#8aad3f" },
"terminal": { "shape": "stadium", "color": "#b5453a" },
"process": { "shape": "rectangle", "color": "#b5a575" },
"decision": { "shape": "diamond", "color": "#d89d26" }
}
}
Enriched fields (added by backend to every node during classification):
| Field | Type | Description |
|---|---|---|
flowchart_type |
string |
The resolved flowchart symbol type |
flowchart_shape |
string |
SVG shape identifier for the frontend renderer |
flowchart_color |
string |
Hex color code for the symbol |
Flowchart Map Object
Returned by GET /api/sessions/{id}/flowchart-map after confirm_and_build() dissolves decision nodes:
{
"map": {
"intake": ["intake", "check-tier"],
"enrich": ["enrich"],
"route": ["route"]
},
"original_draft": { "...original draft graph before dissolution..." }
}
map: Keys are runtime node IDs, values are lists of original draft node IDs that the runtime node absorbed.original_draft: The complete draft graph as it existed before dissolution, preserved for flowchart display.- Both fields are
nullif no dissolution has occurred yet.
2. Flowchart Types
| Type | Shape | Color | SVG Primitive | Description |
|---|---|---|---|---|
start |
stadium | #8aad3f spring pollen |
<rect rx={h/2}> |
Entry point / start terminator |
terminal |
stadium | #b5453a propolis red |
<rect rx={h/2}> |
End point / stop terminator |
process |
rectangle | #b5a575 warm wheat |
<rect rx={4}> |
General processing step (default) |
decision |
diamond | #d89d26 royal honey |
<polygon> 4-point |
Branching / conditional logic |
io |
parallelogram | #d06818 burnt orange |
<polygon> skewed |
Data input or output |
document |
document | #c4b830 goldenrod |
<path> wavy bottom |
Document / report generation |
database |
cylinder | #508878 sage teal |
<path> + <ellipse> |
Database / data store |
subprocess |
subroutine | #887a48 propolis gold |
<rect> + inner <line> |
Predefined process / sub-agent |
browser |
hexagon | #cc8850 honey copper |
<polygon> 6-point |
Browser automation (GCU node) |
3. Auto-Classification Priority
When flowchart_type is omitted from a node, the backend classifies it automatically using this priority (function classify_flowchart_node in flowchart_utils.py):
- Explicit override — if
flowchart_typeis set and valid, use it (old type names are remapped automatically) - Node type —
gcunodes becomebrowser - Position — first node becomes
start - Terminal detection — nodes in
terminal_nodes(or with no outgoing edges) becometerminal - Branching structure — nodes with 2+ outgoing edges with different conditions become
decision - Sub-agents — nodes with
sub_agentsbecomesubprocess - Tool heuristics — tool names match known patterns:
- DB tools (
query_database,sql_query,read_table, etc.) →database - Doc tools (
generate_report,create_document, etc.) →document - I/O tools (
send_email,post_to_slack,fetch_url,display_results, etc.) →io
- DB tools (
- Description keyword heuristics:
"database","data store","persist"→database"report","document","summary"→document"deliver","send","notify"→io
- Default —
process(blue rectangle)
4. Decision Node Dissolution
When confirm_and_build() is called, decision nodes (flowchart diamonds) are dissolved into runtime-compatible structures by _dissolve_decision_nodes(). Decision nodes are a planning-only concept — they don't exist in the runtime graph.
Algorithm
For each decision node D (in topological order):
1. Find predecessors P via incoming edges
2. Find yes-target and no-target via outgoing edges
- Yes: edge with label "Yes"/"True"/"Pass" or condition "on_success"
- No: edge with label "No"/"False"/"Fail" or condition "on_failure"
- Fallback: first outgoing = yes, second = no
3. Get decision clause: D.decision_clause || D.description || D.name
4. For each predecessor P:
- Append clause to P.success_criteria
- Remove edge P → D
- Add edge P → yes_target (on_success)
- Add edge P → no_target (on_failure)
5. Remove D and all its edges from the graph
6. Record absorption: flowchart_map[P.id] = [P.id, D.id]
Edge Cases
| Case | Behavior |
|---|---|
| Decision at start (no predecessor) | Converted to a process node with success_criteria = clause; outgoing edges rewired to on_success/on_failure |
| Chained decisions (A → D1 → D2 → B) | Processed in order. D1 dissolves into A. D2's predecessor is now A, so D2 also dissolves into A. Map: A → [A, D1, D2] |
| Multiple predecessors | Each predecessor gets its own copy of the yes/no edges |
| Existing success_criteria on predecessor | Appended with "; then evaluate: <clause>" |
| Decision with >2 outgoing edges | First classified yes/no pair is used; remaining edges are preserved |
Example
Input (planning flowchart):
[Fetch Billing Data] → <Amount > $100?> → Yes → [Generate PDF Receipt]
→ No → [Draft Email Receipt]
Output (runtime graph):
[Fetch Billing Data] → on_success → [Generate PDF Receipt]
→ on_failure → [Draft Email Receipt]
success_criteria: "Amount > $100?"
Flowchart map:
{
"fetch-billing-data": ["fetch-billing-data", "amount-gt-100"],
"generate-pdf-receipt": ["generate-pdf-receipt"],
"draft-email-receipt": ["draft-email-receipt"]
}
The runtime Level 2 judge evaluates the decision clause against the node's conversation. NodeResult.success = true routes via on_success (yes), false routes via on_failure (no).
5. Frontend Rendering
Component: DraftGraph.tsx
An SVG-based flowchart renderer that operates in two modes:
- Planning mode — renders the draft graph with flowchart shapes during the planning phase
- Runtime overlay mode — renders the original (pre-dissolution) draft with live execution status when
flowchartMapandruntimeNodesprops are provided
Props
interface DraftGraphProps {
draft: DraftGraphData; // The draft graph to render
onNodeClick?: (node: DraftNode) => void; // Node click handler
flowchartMap?: Record<string, string[]>; // Runtime → draft node mapping
runtimeNodes?: GraphNode[]; // Live runtime graph nodes with status
}
Layout Engine
The layout algorithm arranges nodes in layers based on graph topology:
- Layer assignment: Each node's layer = max(parent layers) + 1. Root nodes are layer 0.
- Column assignment: Within each layer, nodes are sorted by parent column average and centered.
- Node sizing:
nodeW = min(360, availableWidth / maxColumns)— nodes fill available space up to 360px. - Container measurement: A
ResizeObservermeasures the actual container width so SVG viewBox coordinates match CSS pixels 1:1.
Constants:
NODE_H = 52px (node height)
GAP_Y = 48px (vertical gap between layers)
GAP_X = 16px (horizontal gap between columns)
MARGIN_X = 16px (left/right margin)
TOP_Y = 28px (top padding)
Shape Rendering
The FlowchartShape component renders each flowchart shape as SVG primitives. Each shape receives:
x, y, w, h— bounding box in SVG unitscolor— the hex color from the flowchart typeselected— hover state (increases fill opacity from 18% to 28%, brightens stroke)
All shapes use strokeWidth={1.2} to prevent overflow on hover.
Edge Rendering
Forward edges (source layer < target layer):
- Rendered as cubic bezier curves from source bottom-center to target top-center
- Fan-out: when a node has multiple outgoing edges, start points spread across 40% of node width
- Labels shown at the midpoint (from
edge.label, or condition/description fallback)
Back edges (source layer >= target layer):
- Rendered as dashed arcs that loop right of the graph
- Each back edge gets a unique offset to prevent overlap
Node Labels
Each node displays two lines of text:
- Primary: Node name (font size 13, truncated to fit
nodeW - 28px) - Secondary: Node description or flowchart type (font size 9.5, truncated to fit
nodeW - 24px)
Truncation uses avgCharWidth = fontSize * 0.58 to estimate available characters.
Tooltip
An HTML overlay (not SVG) positioned below hovered nodes, showing:
- Node description
- Tools list (
Tools: tool_a, tool_b) - Success criteria (
Criteria: ...)
Legend
A dynamic legend at the bottom of the SVG listing all flowchart types used in the current draft, with their shape and color.
Runtime Status Overlay
When flowchartMap and runtimeNodes are provided, the component computes per-node statuses:
- Invert the map:
flowchartMapmapsruntime_id → [draft_ids]; inversion givesdraft_id → runtime_id - Map runtime status: For each runtime node, classify status as
running(amber),complete(green),error(red), orpending(no overlay) - Render overlays:
- Glow ring: A pulsing amber
<rect>around running nodes, solid green/red for complete/error - Status dot: A small
<circle>in the top-right corner with animated radius for running nodes
- Glow ring: A pulsing amber
- Header: Changes from "Draft / planning" to "Flowchart / live"
// Status color mapping
const STATUS_COLORS = {
running: "#F59E0B", // amber — pulsing glow
complete: "#22C55E", // green — solid ring
error: "#EF4444", // red — solid ring
pending: "", // no overlay
};
Workspace Integration (workspace.tsx)
The workspace always renders a single <DraftGraph> component, selecting the best available draft:
<DraftGraph
draft={activeAgentState?.originalDraft ?? activeAgentState?.draftGraph ?? null}
loading={activeAgentState?.queenPhase === "planning" && !activeAgentState?.draftGraph}
flowchartMap={activeAgentState?.flowchartMap ?? undefined}
runtimeNodes={currentGraph.nodes}
/>
The graph panel is user-resizable (drag handle on the right edge, 15%–50% of viewport width, default 30%).
State management:
draftGraph: Set bydraft_graph_updatedSSE event during planning; cleared on phase changeoriginalDraft+flowchartMap: Fetched fromGET /api/sessions/{id}/flowchart-mapwhen phase transitions away from planning. For template/legacy agents,originalDraftis generated at load time viagenerate_fallback_flowchart().
6. Events & API
SSE Event: draft_graph_updated
Emitted when save_agent_draft completes. The full draft graph object is the event data payload.
event: message
data: {"type": "draft_graph_updated", "stream_id": "queen", "data": { ...draft graph object... }, ...}
REST Endpoints
GET /api/sessions/{session_id}/draft-graph
Returns the current draft graph from planning phase.
{"draft": <DraftGraph object>}
// or
{"draft": null}
GET /api/sessions/{session_id}/flowchart-map
Returns the flowchart-to-runtime mapping and original draft (available after confirm_and_build()).
{
"map": { "runtime-node-id": ["draft-node-a", "draft-node-b"], ... },
"original_draft": { ...original DraftGraph before dissolution... }
}
// or
{"map": null, "original_draft": null}
7. Phase Gate
The draft graph is part of a two-step gate controlling the planning → building transition:
save_agent_draft()— creates the draft, classifies nodes, emitsdraft_graph_updated- User reviews the rendered flowchart (with decision diamonds, edge labels, color-coded shapes)
confirm_and_build()— dissolves decision nodes, preserves original draft, builds flowchart map, setsbuild_confirmed = trueinitialize_and_build_agent()— checksbuild_confirmedbefore proceeding; passes the dissolved (decision-free) draft to the scaffolder for pre-population
The scaffolder never sees decision nodes — it receives a clean graph with only runtime-compatible node types where branching is expressed through success_criteria + on_success/on_failure edges.