Introduction
Barnum is a programming language for asynchronous programming that is geared towards making it easy to precisely orchestrate agents.
Why?
LLMs are incredibly powerful tools. They are being asked to perform increasingly complicated, long-lived tasks. Unfortunately, the naive way to work with agents quickly hits limits. When their context becomes too full, they become forgetful and make the wrong decisions. You can't rely on them to faithfully execute a complicated, multi-step plan.
🦁 A choreographed show
Barnum workflows are state machines. Transitions are declared up front, steps are independent and small, and the possible states are easy to reason about. No hoping the agent stays on track.
🐘 The right performer for each act
Each agentic step receives only the context it needs. If an agent is asked to both analyze a file for refactoring opportunities and implement the refactors, it has to hold both tasks in context at once. With Barnum, analysis and implementation are separate steps. The implementing agent only sees the refactor description — not the analysis instructions.
🐯 No one goes off script
Each handler executes in its own isolated subprocess. No handler sees another handler's context. The agent performing the refactor has no idea that a type-check step follows — it just receives a filename and a prompt.
A simple example
Handlers are the building blocks of a Barnum workflow. Today, handlers are either built-in primitives or exported TypeScript async functions. (Support for other languages is planned.)
// handlers/steps.ts
import { createHandler } from "@barnum/barnum/runtime";
import { z } from "zod";
export const listFiles = createHandler({
outputValidator: z.array(z.string()),
handle: async () => {
return readdirSync("src/").filter(f => f.endsWith(".ts"));
},
}, "listFiles");
export const refactor = createHandler({
inputValidator: z.string(),
handle: async ({ value: file }) => {
await callAgent({
prompt: `Refactor ${file} to replace all class-based React components with functional components using hooks.`,
allowedTools: ["Read", "Edit"],
});
},
}, "refactor");
// ... typeCheck, fix, commit, createPR
You compose handlers into a workflow using postfix methods like .then() (sequential) and .iterate() / .map() (fan-out):
// run.ts
import { runPipeline } from "@barnum/barnum/pipeline";
import { listFiles, refactor, typeCheck, fix, commit, createPR } from "./handlers/steps.js";
runPipeline(
listFiles
.iterate()
.map(refactor.then(typeCheck).then(fix).then(commit).then(createPR))
.collect(),
);
listFiles runs once and returns an array of filenames. .iterate() enters Iterator, .map() fans out — each filename flows through refactor → typeCheck → fix → commit → createPR, with each file processed in parallel. .collect() gathers results back into an array.
Each handler executes in its own isolated Node.js subprocess. The Rust runtime manages the state machine: it tracks which handlers are pending, dispatches them, collects results, and advances the workflow. No handler sees another handler's context. The agent performing the refactor has no idea that a type-check step follows — it just receives a filename and a prompt.
Learn more
- Quickstart — install, write handlers, compose a workflow, run it
- Patterns — the building blocks: parallel execution, branching, looping, error handling, timeout, racing, context and variables, early return
- Repertoire — real-world workflows: adversarial review, code review, codebase migration, incident triage, and more
- Builtins reference — every combinator with its TypeScript type signature and postfix availability
- CLI reference — how to run workflows, binary resolution,
callClaude() - Architecture — the TypeScript AST, Rust compiler, algebraic effect handlers, and validation system
Demos
Browse the demos for complete working examples:
| Demo | Description |
|---|---|
simple-workflow | List files, then refactor/typecheck/fix/commit/PR each one in parallel |
retry-on-error | Fallible pipeline with tryCatch, withTimeout, and loop for retry |
convert-folder-to-ts | Convert JS files to TypeScript with an LLM, iterating on type errors |
identify-and-address-refactors | Discover refactoring opportunities, implement them in worktrees, review with an LLM |