ADR-canvas-003
Graph Node system β `kind` field, dual storage, snapshot semantics
Context
Researchers build complex agent subgraphs (e.g. a VLMβPromptβLLMβDecision chain) and want to reuse them across multiple graphs without re-wiring each time. The existing "Save Graph" feature saves editable templates that open in tabs β useful for top-level graphs, but not for frozen reusable building blocks. Dragging a saved graph onto the canvas was supported but confusing: edits to the source propagated nowhere (copy semantics), yet the UI didn't communicate this. There was no way to distinguish "this is a template I'm actively developing" from "this is a finished module I want to stamp into other graphs." Additionally, both kinds shared the same workspace/graphs/ directory and the same filename slug, so saving a graph and a node with the same name overwrote each other.
Decision
(1) kind field on GraphDefinition: "graph" (default, editable template β opens in tab, not draggable) or "node" (frozen composite archive β draggable onto canvas, not openable). (2) Dual storage: workspace/graphs/ for graphs, workspace/graph_nodes/ for nodes. Same slug can exist in both directories. GET /api/graphs scans both and returns a merged list. POST routes by kind. GET/PUT/DELETE search both via _find_graph(). (3) Explorer split: two collapsible sections in the sidebar β "Graphs" (upper, FolderOpen icon, amber) and "Graph Nodes" (lower, Puzzle icon, indigo). Both support VS Code-style tree expansion. (4) "Save as Node" button: new canvas toolbar button that opens SaveGraphDialog with kind="node". (5) Snapshot semantics (existing, now documented): drag serializes the full GraphDefinition as JSON into the drag payload. JSON.parse on drop creates a deep copy. No reference or link back to the source file. Edits to the source have zero effect on existing canvas instances or saved parent graphs. (6) Backward compat: graphs without a kind field default to "graph". The kind field is only serialized to JSON when not the default value.
Alternatives
(a) Reference-based composites (store a graph ID, resolve at execution time) β rejected, adds complexity (version management, broken references) and conflicts with flatten-before-execute. (b) Single directory with node__ prefix for graph node filenames β initially implemented, then replaced by dual directories for cleaner separation. (c) Separate API routes (/api/graph-nodes) β rejected, unnecessary duplication when a single kind field suffices.
Rationale
Copy semantics + dual storage is the simplest model that satisfies the core need: "archive a graph as a reusable block that won't change under me." No version management, no reference resolution, no sync issues. The kind field is minimal metadata that cleanly separates the two use cases without changing the GraphDefinition schema in any breaking way. Dual directories prevent filename collisions and make filesystem browsing intuitive. The Explorer split gives clear visual separation matching the conceptual distinction.
Affected docs
architecture.md, glossary.md, blueprint.md, roadmap.md, design-docs/graph-system.md