Skip to content

unfallenwill/batonjs

Repository files navigation

BatonJS

BatonJS

npm version License: MIT Node.js >=18 TypeScript

A TypeScript-based workflow script runner for coding agents.


Why BatonJS?

A TypeScript-based workflow script runner for coding agents. Write plain async/await scripts with a small set of injected globals — the engine handles concurrency, retries, budget tracking, and SDK orchestration.

  • SDK-agnostic — Pluggable adapters for Anthropic Claude, Tencent CodeBuddy, OpenAI Codex, and Reasonix. Swap providers without rewriting workflow logic.

  • Zero boilerplateagent(), parallel(), pipeline(), phase(), log(), and budget are all you need. No class inheritance, decorators, or DSL.

  • Automatic concurrency & throttlingparallel([...]) handles hundreds of items with automatic semaphore management.

  • Built-in budget tracking — Every agent() call deducts from a shared pool; loops exit automatically when budget is exhausted.

  • Retry, structured output, sub-workflows — Exponential backoff for transient failures, JSON Schema validation for typed results, and nested workflow composition — all out of the box.


Quick Start

Prerequisites: Node.js ≥ 18

Install:

npm install batonjs

Or run directly without installing:

npx -y batonjs ./workflow.js

Write a workflow script:

// workflow.js
export const meta = {
  name: 'my-first-workflow',
  phases: [{ title: 'Analyze' }, { title: 'Summarize' }],
}

// Phase 1: Run two agents in parallel
phase('Analyze')
const [security, perf] = await parallel([
  () => agent('Check this code for security issues', { label: 'security' }),
  () => agent('Check this code for performance bottlenecks', { label: 'perf' }),
])

// Phase 2: Summarize findings
phase('Summarize')
const summary = await agent(
  `Summarize these findings:\n${JSON.stringify({ security, perf })}`,
  { label: 'summarize' },
)

log('Done!')
return { summary }

Run it:

batonjs ./workflow.js

Switch to a different SDK backend:

batonjs --sdk codex ./workflow.js
batonjs --sdk codebuddy ./workflow.js
batonjs --sdk reasonix ./workflow.js

CLI Reference

batonjs [options] <script>
Flag Description Default
--args <json> Pass arguments into the script as the args global
--budget <usd> Max spend in USD unlimited
--concurrency <n> Max concurrent agents 2
--cwd <dir> Working directory for agents .
--sdk <name> claude, codebuddy, codex, or reasonix claude
--timeout <minutes> Per-agent timeout in minutes 5
-h, --help Show help

Script API

Workflow scripts receive these injected globals:

Global Description
agent(prompt, opts?) Run an AI agent. Returns the result or null on failure
parallel(thunks) Run an array of async thunks concurrently; waits for all
pipeline(items, ...stages) Stream each item through stages independently
phase(title) Mark the current phase (for progress display)
log(message) Emit a log message
budget { total, spent(), remaining() } — real-time budget info
args Custom arguments passed via --args
workflow(ref, args?) Run a nested sub-workflow (one level max)

agent(prompt, opts?)

// Free-text response
const result = await agent('Explain this error')

// Structured output with JSON Schema
const issues = await agent('Find bugs in this code', {
  schema: {
    type: 'object',
    properties: { bugs: { type: 'array', items: { type: 'string' } } },
    required: ['bugs'],
  },
})
// issues.bugs — validated array of strings

Options:

Option Description
label Display name for progress output
phase Assign to a phase group
schema JSON Schema for structured output
model Override model for this agent

Examples

The examples/ directory contains 31 ready-to-run workflows, organized by level:

# Pattern Description
01 agent() Hello World — one agent, one result
06 schema Structured output with JSON Schema validation
07–08 parallel / pipeline Concurrency primitives
09 pipeline vs parallel When to use which
11 Multi-stage pipeline Chain stages per item
12 Loop-until-dry Keep discovering until exhausted
13 Loop-until-budget Scale depth to budget
15 Adversarial verify N skeptics try to refute a claim
16 Judge panel Diverse approaches scored and synthesized
21 Code review Multi-dimension review with verification
31 README generator Five-dimension README generation

Run any example:

batonjs ./examples/01-hello-world.js
batonjs --sdk codex ./examples/06-structured-output.js
batonjs --budget 2.0 ./examples/21-code-review.js

Programmatic API

import { Engine } from 'batonjs'

const engine = new Engine({
  scriptPath: './workflow.js',
  sdk: 'claude',       // or 'codebuddy', 'codex', or 'reasonix'
  maxBudgetUsd: 2.0,
  maxConcurrency: 5,
})

engine.on((event) => {
  if (event.kind === 'log') console.log(event.message)
})

const result = await engine.run()

if (result.ok) {
  console.log('Result:', result.value.result)
  console.log('Cost: $' + result.value.totalCostUsd.toFixed(4))
}

API Reference

BatonJS exposes a compact public surface — engine, result helpers, SDK adapters, and a rich set of types. Everything below is re-exported from the entry point.

Engine

The workflow runtime that loads a script, injects globals, and orchestrates execution.

Export Description
Engine Load a script and run it inside a sandboxed workflow environment.
EngineOptions Configuration for new Engine(): scriptPath, args, maxBudgetUsd, maxConcurrency, cwd, permissionMode, sdk, agentTimeoutMs, signal.
EngineRunResult Return type of engine.run() — always a Result<EngineResult, Error>.
EngineResult Successful run payload: { success, result, totalCostUsd, durationMs, meta }.
import { Engine } from "batonjs";
const engine = new Engine({ scriptPath: "./my-workflow.ts", maxBudgetUsd: 5.0 });
const outcome = await engine.run(); // Result<EngineResult, Error>

Quick Start · Events

Result Helpers

Predictable, allocation-friendly error handling without try/catch.

Export Description
ok Create a successful Result<T, E> value.
err Create a failed Result<T, E> error.
Result Discriminated union: { ok: true; value: T } | { ok: false; error: E }.
import { ok, err } from "batonjs";
const found = ok(42);          // Result<number, never>
const failed = err("missing"); // Result<never, string>

TypeScript Philosophy

SDK Adapters

Pluggable backends that translate BatonJS calls into provider-specific SDK operations.

Export Description
createSdkProvider Factory — returns an SdkProvider for a given backend name.
SdkName Union: 'claude' | 'codebuddy' | 'codex' | 'reasonix'.
SdkProvider Contract: { query(options): SdkQueryHandle }.
SdkQueryOptions Options for a query call: permissionMode, abortController, outputFormat, model, cwd, maxBudgetUsd, effort.
SdkQueryHandle Async-iterable handle with interrupt() and return() cleanup.
SdkResultMessage Normalised result message, discriminated on subtype: 'success' vs error variants.
EffortLevel Unified reasoning effort: 'medium' | 'high' | 'xhigh'.
import { createSdkProvider } from "batonjs";
const sdk = createSdkProvider("claude");
const handle = sdk.query({ model: "claude-sonnet-4-20250514", permissionMode: "auto" });

SDK Adapters source

Events

Synchronous, type-safe event bus for observing engine lifecycle.

Export Description
EngineEventBus Create a bus, register handlers, and emit typed events.
EngineEvent Discriminated union of all lifecycle events (workflow_start, workflow_end, phase, log, agent_start, agent_end, agent_error, budget_update, …).
EngineEventHandler Handler signature: (event: EngineEvent) => void.
import { EngineEventBus } from "batonjs";
const bus = new EngineEventBus();
bus.on("agent_start", (e) => console.log(`→ ${e.label}`));

Engine Events types

Script Globals

The runtime contract injected into every workflow script.

Export Description
ScriptGlobals Full set: agent, parallel, pipeline, phase, log, budget, args, workflow.
ScriptMeta Shape of export const meta = { name, description, phases }.
AgentOpts Options for agent(): label, phase, schema, model, maxRetries, effort.
BudgetHandle Script-facing budget: { total, spent(), remaining() }.
WorkflowRef Reference to another workflow: string or { scriptPath }.
// Inside a workflow script — globals are injected by the Engine
const result = await agent("Summarise the article", { schema: SummarySchema });
const all = await parallel([() => agent("A"), () => agent("B")]);

Script Globals types · Examples

Concurrency & Budget

Utilities for controlling parallelism and spending.

Export Description
Semaphore Counting semaphore — acquire() / release() to cap concurrency.
BudgetTracker Track cumulative USD spend — record(), tryAcquire(), adjust().
import { Semaphore, BudgetTracker } from "batonjs";
const sem = new Semaphore(4);
const budget = new BudgetTracker(10.0); // $10 USD cap

SDK Backends

Backend --sdk value Package
Anthropic Claude claude (default) @anthropic-ai/claude-agent-sdk
Tencent CodeBuddy codebuddy @tencent-ai/agent-sdk
OpenAI Codex codex @openai/codex-sdk
Reasonix reasonix CLI binary (reasonix run)

Documentation & Resources

Guides

  • Getting Started — Installation, quick start, and basic usage of the BatonJS workflow engine
  • Workflow Tutorial — Step-by-step tutorial for AI assistants to learn BatonJS concepts, API patterns, and best practices (6 chapters)
  • Examples — 31 workflow examples from hello-world to full quality gate, covering pipeline, parallel, adversarial verify, judge panel, and more

API Reference

  • API Entry Point — Public API: Engine class, Result type, SDK provider, event bus, concurrency primitives, budget tracker
  • CLI Reference — Command-line interface options: --args, --budget, --concurrency, --cwd, --sdk, --timeout
  • Script Globals — Workflow script globals: agent(), parallel(), pipeline(), phase(), log(), budget, args, workflow()
  • Engine Events — Discriminated union event types emitted during workflow execution for observability
  • SDK Adapters — Pluggable SDK backends: Anthropic Claude Agent SDK, Tencent Codebuddy Agent SDK, OpenAI Codex SDK, Reasonix

Development

  • TypeScript Philosophy — Project coding conventions: zero any, discriminated unions, Result pattern, never exhaustiveness checks
  • Contributing — Development commands (dev, build, test, lint, check) and project setup with husky + lint-staged

Project


Contributing

Contributions are welcome! To get started:

  • Bug reports — Open an issue with a minimal reproduction.
  • Feature requests — Open an issue with a clear use case.
  • Pull requests — Fork the repo, create a branch, and submit a PR. Please ensure npm run check passes before submitting.

License

This project is licensed under the MIT license.

About

A TypeScript-based workflow script runner for coding agents.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors