Tracking issue for giving bullpen a GUI without building one.
bullpen has no graphical surface and no cheap path to one — ratatui and crossterm are its only UI dependencies, and adding an HTTP server plus a frontend would fight the crate-map discipline the architecture is built on. jig already has the workbench: a React frontend embedded in the Go binary, served on loopback by jig serve, with queue / fleet / runs / worktrees views. It currently drives Claude Code and Codex. Teaching it to drive bullpen is an adapter, not a UI project.
Tracked here rather than in jig because jig is a public repo and bullpen is not.
Where the work lands
In jig, not here:
internal/runtime/bullpen/adapter.go — implements runtime.Runtime
cmd/jig/runtime.go — a case in selectRuntime, plus the name in runtimeNames
jig's seam is two methods. Probe(ctx) → RuntimeCapability and StartOrContinue(ctx, session, prompt, opts) → Handle, where Handle yields Events() until the send ends, then Result(), with Kill() and ProcessGroupID().
Why bullpen fits
bullpen's Event variants map almost one-to-one onto jig's event vocabulary:
| bullpen |
jig |
AssistantText { text } |
EventText |
ToolStart { id, name, input } |
EventToolCall |
ToolEnd { id, name, output, is_error } |
EventToolCall / EventLog |
TurnDone { usage } |
Result.Usage |
Resume maps cleanly too: jig's Session.NativeID is a bullpen session id, minted on the first send and passed to run -r afterwards. CanResume: true.
Decisions already made
ReportsCost: false. bullpen's Usage carries input_tokens and output_tokens only — no USD, no context window. Tokens can be reported honestly; cost cannot, so jig's spend accounting will not govern bullpen phases until bullpen learns per-provider pricing.
- No tool allowlist initially. jig passes
Options.Tools; bullpen builds Registry::standard() with no way to restrict it. Precedent exists — jig's own codex adapter returns ErrToolAllowlistUnsupported.
- Skip credential seeding. jig seeds auth into an ephemeral HOME for its other adapters, but
selectRuntime has an explicit seedAuth=false escape hatch "for a CLI that authenticates through its environment instead of its HOME". Provider keys on the role's env allowlist are simpler and touch no credentials.
Depends on
Not strictly blocking: JIG_SCRIPTED_RUNTIME loads a fake adapter that overrides the selector, so the adapter can be built and tested against a scripted fake before #9 lands.
Sizing
jig's existing adapters are 867 lines + 808 test (codex) and 668 + 599 (claudecode). Expect that range — this is a week of work across two repos and two languages, not an afternoon.
Note on scope
This does not settle whether bullpen builds M5 (its own durable workflow engine) or cedes that layer to jig. Those overlap heavily and the question is open; this issue only makes bullpen runnable in a UI that exists today.
Tracking issue for giving bullpen a GUI without building one.
bullpen has no graphical surface and no cheap path to one —
ratatuiandcrosstermare its only UI dependencies, and adding an HTTP server plus a frontend would fight the crate-map discipline the architecture is built on. jig already has the workbench: a React frontend embedded in the Go binary, served on loopback byjig serve, with queue / fleet / runs / worktrees views. It currently drives Claude Code and Codex. Teaching it to drive bullpen is an adapter, not a UI project.Tracked here rather than in jig because jig is a public repo and bullpen is not.
Where the work lands
In jig, not here:
internal/runtime/bullpen/adapter.go— implementsruntime.Runtimecmd/jig/runtime.go— acaseinselectRuntime, plus the name inruntimeNamesjig's seam is two methods.
Probe(ctx) → RuntimeCapabilityandStartOrContinue(ctx, session, prompt, opts) → Handle, whereHandleyieldsEvents()until the send ends, thenResult(), withKill()andProcessGroupID().Why bullpen fits
bullpen's
Eventvariants map almost one-to-one onto jig's event vocabulary:AssistantText { text }EventTextToolStart { id, name, input }EventToolCallToolEnd { id, name, output, is_error }EventToolCall/EventLogTurnDone { usage }Result.UsageResume maps cleanly too: jig's
Session.NativeIDis a bullpen session id, minted on the first send and passed torun -rafterwards.CanResume: true.Decisions already made
ReportsCost: false. bullpen'sUsagecarriesinput_tokensandoutput_tokensonly — no USD, no context window. Tokens can be reported honestly; cost cannot, so jig's spend accounting will not govern bullpen phases until bullpen learns per-provider pricing.Options.Tools; bullpen buildsRegistry::standard()with no way to restrict it. Precedent exists — jig's own codex adapter returnsErrToolAllowlistUnsupported.selectRuntimehas an explicitseedAuth=falseescape hatch "for a CLI that authenticates through its environment instead of its HOME". Provider keys on the role's env allowlist are simpler and touch no credentials.Depends on
BULLPEN_HOME. Shipped. Without it, jig's ephemeral HOME gives every phase an empty store and resume breaks silently two phases into a run.run --json. In progress. Defines the wire format the adapter parses. Writing the parser first means guessing and rewriting.Not strictly blocking:
JIG_SCRIPTED_RUNTIMEloads a fake adapter that overrides the selector, so the adapter can be built and tested against a scripted fake before #9 lands.Sizing
jig's existing adapters are 867 lines + 808 test (codex) and 668 + 599 (claudecode). Expect that range — this is a week of work across two repos and two languages, not an afternoon.
Note on scope
This does not settle whether bullpen builds M5 (its own durable workflow engine) or cedes that layer to jig. Those overlap heavily and the question is open; this issue only makes bullpen runnable in a UI that exists today.