Composable AI workflows that don't suck.
Hypergraph is an ergonomic workflow engine for building AI agents and data pipelines. It provides simple, composable primitives that make complex orchestration feel natural.
LangGraph:
// 150+ lines of boilerplate
const StateAnnotation = Annotation.Root({...});
async function nodeA(state) {...}
async function nodeB(state) {...}
// ... more ceremony ...
const graph = new StateGraph(StateAnnotation)
.addNode("a", nodeA)
.addNode("b", nodeB)
.addEdge(START, "a")
.addEdge("a", "b")
.compile();Hypergraph:
// 30 lines, crystal clear
const workflow = graph("my-workflow")
.add(
node("a", processA),
node("b", processB)
)
.connect("a", "b")
.setEntry("a")
.setExit("b");The difference? Hypergraph optimizes for readability and developer experience without sacrificing power.
npm install hypergraph
# or
bun add hypergraphimport { graph, node } from "hypergraph";
const square = (x: number) => x ** 2;
const addTen = (x: number) => x + 10;
const workflow = graph<number>("math")
.add(
node("square", square),
node("addTen", addTen)
)
.connect("square", "addTen")
.setEntry("square")
.setExit("addTen");
const result = await workflow.run(5); // 35Transform data with simple functions:
import { graph, node } from "hypergraph";
type State = { value: number };
const workflow = graph<State>("example")
.add(
node("double", (s) => ({ ...s, value: s.value * 2 })),
node("log", (s) => {
console.log(s.value);
return s;
})
)
.connect("double", "log")
.setEntry("double")
.setExit("log");
await workflow.run({ value: 10 }); // Logs: 20Call language models without boilerplate:
import { ChatOpenAI } from "@langchain/openai";
import { graph, ai } from "hypergraph";
const model = new ChatOpenAI({ model: "gpt-4o" });
type State = {
topic: string;
joke?: string;
tweet?: string;
};
const workflow = graph<State>("joke-writer")
.add(
ai("writeJoke", {
model,
prompt: (s) => `Write a joke about: ${s.topic}`,
update: (s, joke) => ({ ...s, joke })
}),
ai("writeTweet", {
model,
prompt: (s) => `Turn this into a tweet: ${s.joke}`,
update: (s, tweet) => ({ ...s, tweet })
})
)
.connect("writeJoke", "writeTweet")
.setEntry("writeJoke")
.setExit("writeTweet");
const result = await workflow.run({ topic: "programming" });
console.log(result.tweet);Run multiple operations simultaneously:
import { graph, ai, parallel } from "hypergraph";
type State = {
review: string;
sentiment?: object;
keyIssues?: string[];
competitors?: string[];
};
const workflow = graph<State>("review-analyzer")
.add(
parallel(
"analysis",
[
ai("sentiment", {
model,
prompt: (s) => `Analyze sentiment: ${s.review}`,
parse: "json",
update: (s, sentiment) => ({ ...s, sentiment })
}),
ai("issues", {
model,
prompt: (s) => `Extract issues: ${s.review}`,
parse: "json",
update: (s, keyIssues) => ({ ...s, keyIssues })
}),
ai("competitors", {
model,
prompt: (s) => `Find competitors: ${s.review}`,
parse: "json",
update: (s, competitors) => ({ ...s, competitors })
})
],
// Reducer: merge all results
(results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
)
)
.setEntry("analysis")
.setExit("analysis");
// Runs 3 AI calls in parallel, ~3x faster than sequential
const result = await workflow.run({ review: "..." });Performance: Sequential would take ~6 seconds, parallel takes ~2 seconds.
Group sequential operations into logical units:
import { graph, node, chain } from "hypergraph";
const workflow = graph("pipeline")
.add(
node("start", validateInput),
chain("processing", [
node("normalize", normalizeData),
node("enrich", enrichWithAPI),
node("transform", transformFormat)
]),
node("save", saveToDatabase)
)
.connect("start", "processing")
.connect("processing", "save")
.setEntry("start")
.setExit("save");Why? Clean logical grouping without cluttering your graph with intermediate connections.
Dynamic branching based on state:
import { graph, node } from "hypergraph";
type State = {
action: string;
result?: string;
};
const workflow = graph<State>("agent")
.add(
node("decide", (s) => ({ ...s, action: "search" })),
node("search", (s) => ({ ...s, result: "search results" })),
node("calculate", (s) => ({ ...s, result: "42" })),
node("done", (s) => s)
)
.connect(
"decide",
["search", "calculate", "done"],
(s) => s.action // Returns which node to route to
)
.connect("search", "done")
.connect("calculate", "done")
.setEntry("decide")
.setExit("done");
await workflow.run({ action: "search" }); // Routes to search nodeProcess customer reviews with parallel AI analysis:
import { ChatOpenAI } from "@langchain/openai";
import { graph, ai, parallel } from "hypergraph";
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
type ReviewState = {
review: string;
sentiment?: { score: number; label: string };
issues?: string[];
competitors?: string[];
insights?: string[];
summary?: string;
};
const analyzer = graph<ReviewState>("review-analyzer")
.add(
// Stage 1: Parallel analysis (4 AI calls simultaneously)
parallel(
"analyze",
[
ai("sentiment", {
model,
prompt: (s) => `Analyze sentiment of: ${s.review}. Return JSON.`,
parse: "json",
update: (s, sentiment) => ({ ...s, sentiment })
}),
ai("issues", {
model,
prompt: (s) => `Extract issues from: ${s.review}. Return JSON array.`,
parse: "json",
update: (s, issues) => ({ ...s, issues })
}),
ai("competitors", {
model,
prompt: (s) => `Find competitors in: ${s.review}. Return JSON array.`,
parse: "json",
update: (s, competitors) => ({ ...s, competitors })
}),
ai("insights", {
model,
prompt: (s) => `Generate insights for: ${s.review}. Return JSON array.`,
parse: "json",
update: (s, insights) => ({ ...s, insights })
})
],
(results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
),
// Stage 2: Generate executive summary
ai("summarize", {
model,
prompt: (s) => `
Create executive summary:
Sentiment: ${s.sentiment?.label}
Issues: ${s.issues?.join(", ")}
Competitors: ${s.competitors?.join(", ")}
Insights: ${s.insights?.join("; ")}
`,
update: (s, summary) => ({ ...s, summary })
})
)
.connect("analyze", "summarize")
.setEntry("analyze")
.setExit("summarize");
const result = await analyzer.run({
review: "Great product but battery life is poor. Competitors do better..."
});
console.log(result.summary);Output:
Sentiment: { score: 0.2, label: "neutral" }
Issues: ["Poor battery life", "Price too high"]
Competitors: ["Dell XPS", "MacBook Pro"]
Insights: ["Improve battery optimization", "Review pricing strategy"]
Summary: "Mixed review citing battery concerns. Competitors mentioned..."
Performance: 4 parallel AI calls + 1 sequential = ~3 seconds total (vs ~10 seconds sequential)
The real power is combining primitives:
const workflow = graph("complex-pipeline")
.add(
node("validate", validateInput),
parallel("processing", [
chain("data-flow", [
node("fetch", fetchData),
node("transform", transformData)
]),
chain("ai-flow", [
ai("analyze", {...}),
ai("summarize", {...})
])
]),
node("merge", mergeResults),
ai("final", {
model,
prompt: (s) => `Generate report: ${JSON.stringify(s)}`,
update: (s, report) => ({ ...s, report })
})
)
.connect("validate", "processing")
.connect("processing", "merge")
.connect("merge", "final")
.setEntry("validate")
.setExit("final");Parallel chains running simultaneously, then merged and passed to a final AI step. This would be 100+ lines in LangGraph.
| Feature | Hypergraph | LangGraph |
|---|---|---|
| Lines of code | ~30 lines | ~150+ lines |
| Parallel execution | parallel([...]) (explicit) |
Multiple addEdge(START, ...) (implicit) |
| AI abstraction | ai() helper |
Manual model.invoke() every time |
| JSON parsing | parse: "json" built-in |
Manual parsing with try/catch |
| Sequential grouping | chain([...]) |
N/A (wire manually) |
| Conditional routing | .connect(src, [dest], router) |
addConditionalEdges() with maps |
| Type safety | TypeScript inference | Zod schemas required |
| Readability | Self-documenting | Requires mental graph building |
Writing a basic workflow shouldn't require 50 lines of boilerplate.
Parallel execution, conditional routing, and composition should feel natural.
parallel() explicitly shows concurrent execution. No guessing from edge patterns.
Start simple with node(), add ai() when needed, compose with parallel() and chain() as complexity grows.
The API should feel like writing normal code, not fighting a framework.
- Streaming support for AI nodes
- Built-in observability/tracing
- Retry logic and error handling
- Persistent state management
- Visual graph debugger
- More LLM provider integrations
Hypergraph is in early development. Contributions, issues, and feedback are welcome!
MIT
Built because LangGraph made simple things too complex. Inspired by great API design from libraries like Express, Lodash, and D3.