A serializable, human-authored DAG format for AI agent workflows.
Flows are JSON documents describing a graph of nodes (prompts, branches,
custom steps) connected by edges. They are designed to be edited in a visual
editor, persisted on disk, and executed by an external runtime such as
metalcraft.
This crate provides:
- The reference Rust types (
FlowDefinition,FlowNode,FlowEdge,SavedFlow) that parse and emit the wire format losslessly. - A generic BFS walker over a flow graph.
- Validation for spec conformance.
- Optional filesystem-backed CRUD (
fsfeature, on by default). - Optional flow execution logging (
logfeature).
See SPEC.md for the formal wire-format specification.
A SavedFlow says what work is this. A ScheduledFlow says when it runs,
as whom, and with what inputs — and points at a flow by id:
{
"id": "sf_9c31a4",
"flow_id": "morning-brief",
"enabled": true,
"schedule": { "type": "cron", "cron": "0 0 8 * * *", "timezone": "America/Detroit" },
"created_at": "2026-08-27T00:00:00Z",
"updated_at": "2026-08-27T00:00:00Z"
}One flow can carry many schedules (08:00 short, 18:00 long) without the graph knowing. And a flow nothing points at cannot fire — so installing a pack or downloading a flow starts no background work as a property of the format, rather than as a rule every install path has to remember.
Pre-v3 documents kept scheduling inside the flow; metalcraft_flows::extract
reads it back out. See SPEC §1.3 and §6.
[dependencies]
metalcraft-flows = "0.5"use metalcraft_flows::{FlowDefinition, FlowNode, FlowEdge, FlowNodeType, CoreNodeType};
use serde_json::json;
let flow = FlowDefinition {
nodes: vec![
FlowNode {
id: "entry".into(),
node_type: FlowNodeType::Core(CoreNodeType::Entry),
data: json!({}),
position: [0.0, 0.0],
},
FlowNode {
id: "say-hello".into(),
node_type: FlowNodeType::Core(CoreNodeType::Prompt),
data: json!({ "prompt": "Say hello to the user." }),
position: [250.0, 0.0],
},
],
edges: vec![
FlowEdge {
id: "e1".into(),
source: "entry".into(),
target: "say-hello".into(),
source_handle: None,
target_handle: None,
},
],
};
let json = serde_json::to_string_pretty(&flow).unwrap();
println!("{json}");The spec supports vendor-namespaced custom node types so third-party tooling can add new step kinds without forking:
use metalcraft_flows::FlowNodeType;
let nt: FlowNodeType = serde_json::from_str("\"slack:send_message\"").unwrap();
assert!(matches!(nt, FlowNodeType::Custom(_)));See SPEC.md §5.2 for the vendor namespace rules.
| Feature | Default | Description |
|---|---|---|
fs |
yes | Enables save_flow, load_flow, list_flows, delete_flow. |
log |
no | Enables FlowLogEntry plus append_flow_log / load_flow_logs. |
To depend on the spec types only, opt out of defaults:
metalcraft-flows = { version = "0.1", default-features = false }MIT — see LICENSE.