loadFlow
Load a flow from a JSON or YAML description file with schema validation and nested flow support.
loadFlow reads a standalone flow description file (JSON or YAML), validates it against a strict schema, resolves agent references from a caller-provided registry, and returns a live flow ready to call. For content already in memory, loadFlowFromString accepts a raw string directly.
import { createAgent, loadFlow } from "@comma-agents/core";
const writer = createAgent({ name: "writer", model: "openai/gpt-4o", systemPrompt: "Write content." });
const reviewer = createAgent({ name: "reviewer", model: "openai/gpt-4o", systemPrompt: "Review content." });
const flow = await loadFlow("./flows/review-pipeline.yaml", {
agents: { writer, reviewer },
});
const result = await flow.call("Write a function that adds two numbers");
console.log(result.text);Unlike strategy files, which define both agents and flows together, flow description files define only the orchestration — agent instances are provided by the caller.
Flow Description File
A description file defines a single flow — its name, type, steps, and type-specific options. The schema is strict: unknown fields are rejected.
YAML
name: review-pipeline
type: sequential
steps:
- agent: writer
- agent: reviewer
- agent: editorJSON
{
"name": "review-pipeline",
"type": "sequential",
"steps": [
{ "agent": "writer" },
{ "agent": "reviewer" },
{ "agent": "editor" }
]
}FlowDescription
Zod-validated schema for flow description files. Supports the built-in sequential, cycle, and broadcast types plus registered custom flow types.
Prop
Type
Flow Types
Sequential
Runs steps one after another. Each step receives the previous step's output.
name: pipeline
type: sequential
steps:
- agent: writer
- agent: reviewerCycle
Repeats steps for a fixed number of iterations. Supports an optional observer agent that runs after each cycle.
name: refinement-loop
type: cycle
steps:
- agent: writer
- agent: critic
cycles: 3
observer: editorSet cycles: "Infinity" for an unbounded loop (use with an observer or external abort to stop).
Broadcast
Fans out the same message to all steps in parallel and joins the results.
name: multi-perspective
type: broadcast
steps:
- agent: optimist
- agent: critic
- agent: realist
separator: "\n---\n"Registered Custom Flows
After registering a custom flow type, use its name in the type field and place its options inside config:
name: first-approved-review
type: first-match
steps:
- agent: reviewer-a
- agent: reviewer-b
config:
marker: APPROVEDloadFlow validates config with the schema provided by the registration. Unknown types and invalid configuration fail during loading. See Custom Flows for registration examples.
Nested Flows
A step can be a full flow definition instead of an agent reference, enabling arbitrarily deep composition:
{
"name": "research-and-review",
"type": "sequential",
"steps": [
{
"name": "parallel-research",
"type": "broadcast",
"steps": [
{ "agent": "researcher-a" },
{ "agent": "researcher-b" }
]
},
{ "agent": "reviewer" }
]
}The broadcast sub-flow runs two researchers in parallel, then the sequential flow pipes their combined output to the reviewer.
LoadFlowOptions
Options passed to loadFlow or loadFlowFromString. The agents registry is required — it maps agent names referenced in steps to live Agent instances.
Prop
Type
loadFlowFromString
When the description content is already in memory (e.g., from a database, WebSocket, or test fixture), use loadFlowFromString directly:
import { loadFlowFromString, createAgent } from "@comma-agents/core";
const writer = createAgent({ name: "writer", model: "openai/gpt-4o" });
const reviewer = createAgent({ name: "reviewer", model: "openai/gpt-4o" });
const yaml = `
name: review-pipeline
type: sequential
steps:
- agent: writer
- agent: reviewer
`;
const flow = loadFlowFromString(yaml, "yaml", {
agents: { writer, reviewer },
});
const result = await flow.call("Review this code...");The format parameter must be "json" or "yaml". loadFlow auto-detects format from the file extension (.json, .yaml, .yml).
Flow Hooks
Inject hooks into the loaded flow via the flowHooks option:
const flow = await loadFlow("./flows/pipeline.yaml", {
agents: { writer, reviewer },
flowHooks: {
beforeStep: [async ({ stepName }) => console.log(`Starting ${stepName}`)],
afterStep: [async ({ stepName, result }) => {
console.log(`${stepName} done: ${result.text.slice(0, 60)}`);
}],
},
});You can also attach hooks after loading with hookIntoFlow:
import { hookIntoFlow } from "@comma-agents/core";
hookIntoFlow(flow, {
beforeFlow: [async (message) => console.log("Flow started:", message)],
});Combining with loadAgent
Use loadAgent to load agents from description files, then pass them to loadFlow:
import { loadAgent, loadFlow } from "@comma-agents/core";
const writer = await loadAgent("./agents/writer.yaml");
const reviewer = await loadAgent("./agents/reviewer.yaml");
const editor = await loadAgent("./agents/editor.yaml");
const flow = await loadFlow("./flows/review-pipeline.yaml", {
agents: { writer, reviewer, editor },
});
const result = await flow.call("Write a tutorial on closures");This keeps agent and flow definitions as separate, composable files.
Error Handling
Both loadFlow and loadFlowFromString throw StrategyValidationError for:
- Unsupported file extensions (only
.json,.yaml,.yml) - Missing files
- Malformed JSON or YAML
- Schema validation failures (missing
name,type, orsteps, unknown fields, wrong types) - Missing agent references (agent name not found in the provided registry)
import { loadFlow, StrategyValidationError } from "@comma-agents/core";
try {
const flow = await loadFlow("./flows/broken.yaml", { agents: {} });
} catch (error) {
if (error instanceof StrategyValidationError) {
console.error("Invalid flow description:", error.message);
}
}