This example builds a two-stage article pipeline using Vibes’ Graph API: an outline node followed by a writing node. The Graph class manages state transitions between BaseNode instances - each node receives the current state, does its work, and either transitions to the next node with next() or terminates with output().
What you’ll learn
- Defining graph nodes with
BaseNode
- State transitions using the
next() free function
- Terminal output using the
output() free function
- Composing a
Graph from nodes and running it
Prerequisites
- Vibes installed (
npm:@vibesjs/sdk)
ANTHROPIC_API_KEY environment variable set
Always import next and output as free functions from @vibesjs/sdk. Do not call them as instance methods (e.g., prefixed with this.) - they are standalone functions, not methods on BaseNode, and calling them as such will throw at runtime.
Complete example
import { Agent, BaseNode, Graph, next, output } from "npm:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";
// --- State type ---
type PipelineState = {
topic: string;
outline?: string;
article?: string;
};
// --- Agents ---
const outlineAgent = new Agent({
model: anthropic("claude-haiku-4-5-20251001"),
systemPrompt: "Create a concise 3-point outline for a short article. Return only the outline.",
});
const writeAgent = new Agent({
model: anthropic("claude-sonnet-4-6"),
systemPrompt: "Write a short 3-paragraph article based on the outline provided.",
});
// --- Graph nodes ---
class OutlineNode extends BaseNode<PipelineState, string> {
readonly id = "outline";
async run(state: PipelineState) {
const result = await outlineAgent.run(`Create an outline for: ${state.topic}`);
// Transition to the "write" node with updated state
return next<PipelineState, string>("write", { ...state, outline: result.output });
}
}
class WriteNode extends BaseNode<PipelineState, string> {
readonly id = "write";
async run(state: PipelineState) {
const result = await writeAgent.run(
`Outline:\n${state.outline}\n\nWrite the article.`
);
// Terminal - return the final article
return output<PipelineState, string>(result.output);
}
}
// --- Compose and run ---
const graph = new Graph<PipelineState, string>([
new OutlineNode(),
new WriteNode(),
]);
const article = await graph.run({ topic: "The future of AI agents" }, "outline");
console.log(article);
Run it
deno run --allow-net --allow-env graph-workflow.ts
How it works
State type
PipelineState carries all data through the graph. Each node receives the full state and returns a new state (immutable - always spread with { ...state, field: value }).
next(nodeId, newState)
Transitions to the named node. The nodeId must match the id of another node in the graph. The newState becomes the input state for that node. You can call it with or without explicit type parameters:
// With explicit generics (recommended for type safety)
return next<PipelineState, string>("write", newState);
// Without generics (TypeScript infers the types)
return next("write", newState);
output(value)
Terminates the graph and returns value as the final result. The type parameter string (second generic on BaseNode<TState, TOutput>) must match.
// With explicit generics
return output<PipelineState, string>(result.output);
// Without generics (TypeScript infers from BaseNode type parameters)
return output(result.output);
new Graph([nodes])
Pass all nodes as an array. The second argument to graph.run() is the ID of the starting node.
Next steps