A durable agent harness in Rust.
Agents as managed, resumable workers — not fire-and-forget processes.
A bullpen is a roster of warmed-up relievers you call in, pull back, and send out again. That is the whole thesis: agent runs should survive the process that started them.
Every step is durable the moment it happens. Sessions live in one SQLite
database (WAL), not in the memory of whatever process launched them. Kill
bullpen mid-run — crash, kill -9, power loss — and the next invocation
recovers: interrupted tool calls are marked, the transcript is closed
cleanly, and the session resumes where it stopped.
No daemon, no supervisor. A background session is just a detached
bullpen run coordinating through the store. The dashboard is a read-and-
dispatch view over that store — close it and the work keeps going, because
nothing was ever supervising it.
The sandbox is a feature, not a footnote. --sandbox confines writes to
the workspace on every platform and, on macOS, runs shell commands and their
children under Seatbelt. --sandbox-strict also cuts network.
cargo install --path crates/cliRust 1.97+ (pinned in rust-toolchain.toml, so rustup fetches the right
one automatically).
Point it at a provider — any one of these is enough:
export ANTHROPIC_API_KEY=sk-ant-... # streams tokens live
bullpen login openrouter # official OAuth PKCE
bullpen login codex # ChatGPT subscription, device-code flowAlready have the Codex CLI logged in? bullpen borrows its session from
~/.codex/auth.json read-only — nothing to configure, and it never
refreshes that token so it can't invalidate the other tool's login.
Then work:
cd your-project
bullpen run "find the failing test and explain why it fails"
bullpen run --sandbox "refactor the retry logic" # confine writes
bullpen run -v "..." # tool activity on stderr
bullpen run --json "..." # NDJSON event stream on stdoutSessions are resumable by id prefix, with the provider they were created with:
bullpen sessions # what have I got
bullpen sessions --json # same, machine-readable
bullpen run -r 6ee4acc9 "now write the fix"bullpen run --bg "audit the auth module" # detached, returns immediately
bullpen run --bg --worktree "refactor it" # …in its own git worktree
bullpen agents # the dashboard in the GIF above
bullpen logs 6ee4acc9 # tail a background sessionbullpen agents groups sessions by state — Working (running, live pid),
Failed (running, dead pid — it crashed), Completed, Idle — and
lets you dispatch from the input line, Space to peek at output, Esc to
quit. Quitting stops nothing.
Plain --bg sessions share your checkout, so two of them edit the same
files. --worktree gives a session a git worktree of its own on a
run-unique bullpen/<id> branch, under $BULLPEN_HOME/worktrees/<session>;
the path shows up in bullpen sessions, in bullpen sessions --json, and
in the peek panel, and bullpen run -r <id> returns to it from anywhere. Outside a
git repository the flag fails rather than quietly sharing the checkout.
Nothing removes a worktree or its branch — not on success, not on
failure, not later. A worktree can hold the only copy of what an agent did,
so cleaning up is yours to decide.
The model can delegate bounded work to child agents through the agent
tool: inspect for read-only reconnaissance, work for the full toolset.
Children are ordinary sessions — durable, budgeted, listed by
bullpen sessions, resumable — with deterministic identities, so a replayed
delegation reattaches to its child instead of running it twice.
| Provider | Wire format | Auth | Verified |
|---|---|---|---|
anthropic |
Anthropic messages | ANTHROPIC_API_KEY |
wire-level tests |
codex |
OpenAI Responses (SSE) | bullpen login codex, or borrow the Codex CLI |
live, incl. tools + resume |
openrouter |
OpenAI chat-completions | bullpen login openrouter or OPENROUTER_API_KEY |
live, incl. tools |
glm |
Anthropic-compatible | GLM_API_KEY |
config-only |
kimi |
Anthropic-compatible | KIMI_API_KEY |
config-only |
Adapters are organized by wire format rather than vendor, which is why compatible hosts are configuration instead of code.
Built-in tools: bash, read_file, write_file, edit_file, grep,
glob — plus agent when the pen is enabled.
~/.bullpen/bullpen.db — SQLite in WAL mode, holding an append-only entry
tree (the conversation) plus a separate execution log (the orchestration).
Delete every execution record and you still have a complete, valid
conversation.
Reading it while sessions run needs the immutable flag, since WAL databases can't be opened read-only without their shared-memory file:
sqlite3 "file:${BULLPEN_HOME:-$HOME/.bullpen}/bullpen.db?immutable=1" "select id, status from sessions"Set BULLPEN_HOME to move the whole directory — database, auth.json,
background logs, and --worktree checkouts (worktrees/<session-id>) land
directly in it, with no .bullpen segment appended.
v0. Honest about what that means:
| ✅ Shipped | Durable execution + crash recovery · the pen (durable subagents) · write-confinement sandbox with Seatbelt on macOS · agent view (dispatch, peek, live state) · 5 providers |
| 🚧 Next | Interactive attach to a live session · needs-input state · notifications · compaction |
| 📋 Planned | Landlock confinement on Linux · a durable workflow engine (steps in SQLite, resumable from any step) |
Outside --sandbox, tools run with the process's full authority. Run it
somewhere you would trust the model to act.
ARCHITECTURE.md is the source of truth. The one-sentence
version: a policy-free core loop (bullpen-agent) that knows nothing about
vendors, config, or UI, with everything else composed around it at the edge
— plus a single durable store instead of per-process state.
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --checkAll three gate CI on Linux and macOS. The demo above is reproducible —
docs/media/bullpen-agents.tape drives the real binary through
VHS.
MIT
