MCPcopy Index your code
hub / github.com/barnum-circus/barnum

github.com/barnum-circus/barnum @v0.4.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.4.0 ↗ · + Follow
668 symbols 2,266 edges 114 files 139 documented · 21%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Barnum

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.

Barnum is an attempt to enable LLMs to perform dramatically more complicated, ambitious tasks. With Barnum, you define an asynchronous workflow, which is effectively a state machine. This makes it easy to reason about the possible states and actions that your agents will be asked to perform, and the steps can be independent and small.

With Barnum, it's easy to have each agentic step receive only the context it needs. If an agent is asked to both analyze a file for refactoring opportunities and implement the refactors, you're forcing it 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. This progressive disclosure of context means agents can more reliably handle tasks of increasing complexity.

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 combinators like pipe (sequential) and forEach (fan-out):

// run.ts
import { runPipeline, pipe } from "@barnum/barnum/pipeline";
import { listFiles, refactor, typeCheck, fix, commit, createPR } from "./handlers/steps.js";

runPipeline(
  listFiles.forEach(pipe(refactor, typeCheck, fix, commit, createPR)),
);

See the full working version: demos/simple-workflow

listFiles runs once and returns an array of filenames. forEach fans out — each filename flows through refactor → typeCheck → fix → commit → createPR, with each file processed in parallel.

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.

Why not just write this in JavaScript?

This example is simple. You could probably ask your favorite LLM to one-shot the orchestration script, and it would do a decent job.

When the workflow grows in complexity, you might reach for plan mode or write a markdown file describing the steps. That works for a while. But what happens when the plan has 40 steps across 15 files with conditional branches, retries on failure, parallel fan-out, and a review loop? Good luck getting an agent to faithfully and reliably execute that plan.

And in practice, you do want the complicated version. You don't want listFiles done by an agent — it's deterministic, just read the filesystem. You don't want a single refactor step — you want the agent to refactor, then evaluate the result, then type-check, then fix errors in a loop until it's clean:

const refactorWithRetry = pipe(
  refactor,
  evaluate,
  loop((recur) =>
    pipe(typeCheck, classifyErrors).branch({
      HasErrors: pipe(forEach(fix).drop(), recur),
      Clean: drop,
    })
  ),
  commit,
  createPR,
);

runPipeline(
  listFiles.forEach(refactorWithRetry),
);

Now type errors are fixed in a loop — the agent keeps fixing until the code is clean. And this is still a simplified version. A real workflow might add review steps, worktree isolation, retry-on-timeout, or error escalation.

The problem isn't that any individual piece is hard. The problem is that expressing a precise, complicated asynchronous workflow in prose or ad-hoc scripts is fragile. A programming language geared towards orchestration is what you actually want — one where loop, branch, tryCatch, forEach, and pipe are first-class constructs with type-safe composition.

Demos

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
cd demos/simple-workflow
pnpm install
pnpm run demo

Repertoire

The Repertoire showcases advanced patterns — tryCatch with retry, withTimeout, worktree isolation, LLM-powered code review loops, and more.

How Barnum works

Extension points exported contracts — how you extend this code

IntoUtf8Bytes (Interface)
Describes types that can be viewed as a `[u8]`. [5 implementers]
crates/intern/src/string.rs
UntypedHandlerDefinition (Interface)
Runtime-only handler definition shape — erases generic type info.
libs/barnum/src/handler.ts
IsEnabled (Interface)
Marker interface to allow supported types. Additional primitive types can be supported by adding an `IsEnabled` decl. [1 …
crates/intern/src/idhasher.rs
RunPipelineOptions (Interface)
(no doc)
libs/barnum/src/run.ts
InternId (Interface)
The `InternId` trait is applied to the identifiers of interned data, and `InternId::Intern` is the type of the data that
crates/intern/src/intern.rs
InvokeAction (Interface)
(no doc)
libs/barnum/src/ast.ts
Lookup (Interface)
(no doc) [1 implementers]
crates/intern/src/intern.rs
ChainAction (Interface)
(no doc)
libs/barnum/src/ast.ts

Core symbols most depended-on inside this repo

constant
called by 355
libs/barnum/src/builtins/scalar.ts
pipe
called by 250
libs/barnum/src/pipe.ts
toAction
called by 222
libs/barnum/src/ast.ts
runPipeline
called by 184
libs/barnum/src/run.ts
chain
called by 113
libs/barnum/src/chain.ts
createHandler
called by 86
libs/barnum/src/handler.ts
branch
called by 66
libs/barnum/src/ast.ts
identity
called by 64
libs/barnum/src/builtins/scalar.ts

Shape

Function 419
Method 154
Class 52
Enum 23
Interface 20

Languages

Rust67%
TypeScript33%

Modules by API surface

libs/barnum/src/ast.ts62 symbols
crates/intern/src/intern.rs54 symbols
crates/barnum_builtins/src/lib.rs53 symbols
crates/barnum_ast/src/flat.rs51 symbols
crates/barnum_event_loop/src/lib.rs35 symbols
crates/intern/src/atomic_arena.rs33 symbols
crates/intern/src/string.rs30 symbols
crates/barnum_engine/src/effects.rs25 symbols
crates/barnum_engine/src/test_helpers.rs24 symbols
crates/barnum_engine/src/lib.rs18 symbols
crates/barnum_ast/src/lib.rs18 symbols
crates/intern/src/small_bytes.rs16 symbols

For agents

$ claude mcp add barnum \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page