Deploying
The runtime needs Bun. It uses Bun.file, Bun.CryptoHasher, bun:sqlite, Bun.SQL, and Bun.spawn. Node-based hosts can't run it, including Next.js on Node, AWS Lambda's Node runtimes, and Cloudflare Workers. Any host that runs a Bun process works: a VM, a container (oven/bun), or a PaaS that runs Docker images.
The simplest way to run pipelines as a service is halo serve: an HTTP API, webhooks, schedules, approval links, and Studio in one process. The options below are for running pipelines yourself.
Option 1: run the CLI
For scheduled or one-off jobs, run the CLI directly:
bun run index.ts run workflows/triage.halo --arg repo=acme/api --json --no-telemetry- Set the provider keys (
ANTHROPIC_API_KEY, …) in the environment. - Without a terminal, a run that reaches
|! yieldor~> listenpauses. It is saved to.halo/runs/<runId>.json(with its trace and call history in<runId>.log.jsonl, which each later pause only appends to) and prints thehalo resumecommand. Use--auto-approvefor unattended jobs that should never wait. --jsonprints the output, status, and fullcontext(cost, calls, trace) for your logs.- The exit code is 1 when validation fails, an error is thrown, or a policy blocks the run. Halted and paused runs exit with 0, so check
statusin the JSON.
Option 2: embed the runtime in a Bun server
import { compile, FileRunStore, HaloRuntime } from "halo";
const { ast, ok, diagnostics } = compile(await Bun.file("workflows/support.halo").text());
if (!ok) throw new Error(JSON.stringify(diagnostics));
const runtime = new HaloRuntime({
runStore: new FileRunStore("/var/lib/halo/runs"), // paused runs survive restarts
vectorPath: "/var/lib/halo/vectors.sqlite",
}).load(ast);
Bun.serve({
port: 3000,
routes: {
"/tickets": {
POST: async (req) => {
const { ticket } = await req.json();
const result = await runtime.run("HandleTicket", { ticket });
// status "paused": a human must answer; store result.runId with the ticket
return Response.json({ status: result.status, runId: result.runId, output: result.output });
},
},
"/runs/:id/answer": {
POST: async (req) => {
const result = await runtime.signal(req.params.id, "yield", await req.json());
return Response.json({ status: result.status, output: result.output });
},
},
},
});- Load the program once and reuse the runtime across requests. Runs don't share state.
- With a
FileRunStore, any process that loads the same.haloprogram can resume a paused run. Use one directory on shared storage, or implementRunStoreover your database. The defaultMemoryRunStoreloses paused runs on restart. signal()throws aSignalError(RUN_NOT_FOUND,SIGNAL_MISMATCH). Map it to a 404 or 409.- Pass
{ signal: AbortSignal }toruntime.runto cancel runs when clients disconnect. Use@timeouton pipelines and agents to bound run time. - Call
runtime.close()on shutdown to release SQLite and Postgres handles.
Long runs
Multi-agent pipelines can take minutes. If your HTTP host has a request time limit, accept the request, run the pipeline in the background (or in a queue worker), and report result.status and output when it finishes. @checkpoint writes a snapshot after each stage to .halo/checkpoints/ (appending to its log, so a long run stays cheap to checkpoint), so a crashed run can continue with runtime.resume(path) or halo run --checkpoint <path>.
Configuration
| Variable | Used for |
|---|---|
ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY/GOOGLE_API_KEY, OPENROUTER_API_KEY, TOGETHER_API_KEY, REPLICATE_API_TOKEN | Model providers |
HALO_API_KEY | Fallback for any provider without its own key |
OLLAMA_HOST | Ollama server (default http://localhost:11434) |
Whatever your tools read with Env.X | e.g. GITHUB_TOKEN, SLACK_BOT_TOKEN, RESEND_API_KEY, DATABASE_URL |
There is no project config file. Configure the runtime in code (RuntimeConfig) or with CLI flags (--router, --api-key).
These files are written relative to the working directory unless you configure other paths:
| Path | Written by | Configure with |
|---|---|---|
.halo/runs/ | paused runs (CLI) | FileRunStore(dir) |
.halo/checkpoints/ | @checkpoint | checkpointDir |
.halo/vectors.sqlite | VectorStore | vectorPath |
.halo/telemetry.db | the CLI (Studio) | --no-telemetry |
File stores write wherever they are declared.
Costs and safety
MaxSpend(usd)caps the cost of each run. There is no monthly or cross-run budget. UsegetCostTracker()or your own accounting for that.- Costs are estimates from built-in price tables.
- Run
halo validate --strictin CI so that policies the runtime can't enforce fail the build instead of being silently ignored.