feat(bash): background/detached shells via the background registry - #233
Merged
Conversation
Adds a `background: true` arg to the bash tool so long-running commands
(builds, servers, watchers) run detached instead of blocking the turn, and
wires the result + a live status-bar count end to end.
Design: rather than a parallel registry, extend the existing BackgroundStore
with a TaskKind { Subagent, Shell } tag so shells reuse the proven completion-
delivery pipeline (notifications -> prepend/follow-up -> output relay) that the
model already understands, while the status bar counts each kind separately.
- background.rs: TaskKind tag on BackgroundTask; insert(id, kind);
running_count_kind(kind); per-kind shell cap (MAX_CONCURRENT_BG_SHELLS=8).
Removed the now-unused total running_count().
- bash.rs: BashTool gains an injected bg_store + with_bg_store(); call() branches
on background: permission/sandbox check as usual, then spawn detached, register
as Shell, return an id immediately, and notify() the captured output on
completion. Schema exposes the new arg to the model. Degrades to synchronous
when no store is wired (headless).
- builder.rs: inject the shared bg_store into BashTool (rig + loop paths).
- task.rs: subagent cap + insert now use TaskKind::Subagent.
- status.rs: render() takes Option<&BackgroundStore> and shows agents:N (subagents)
and shells:N (shells); mod.rs call sites pass the store.
- Available to the model: new bash arg in the schema; completions delivered
automatically; task_status works on a shell id too.
Tests: end-to-end background bash (registers a Shell, returns an id, delivers
output, count returns to 0); status-bar per-kind badge counts. Full
feature-matrix suite green at -D warnings (2144 passed).
yogthos
pushed a commit
that referenced
this pull request
May 30, 2026
…ded + read/kill by id) Replaces the timeout-bounded, push-once background-shell design (#233) with Claude Code's proven model: background bash runs UNBOUNDED and the model reads its output and stops it explicitly by id. - New BackgroundShellStore (bg_shell.rs): per-shell live output buffer (unread, drained on read; hard-capped so a never-read flood can't OOM), status (Running/Exited/Killed/Failed), and the drain JoinHandle. Process-global (like the subagent /kill registry) so the bash tool, the new tools, the status bar, and session cleanup share one instance without threading it through every signature; tests inject their own store. - bash background=true: spawn_streaming_shell runs the command detached with no timeout (optional `timeout` = auto-kill-after-N), streaming stdout/stderr into the store; returns a shell id immediately. Permission + sandbox checks run before the spawn as before. PgKillGuard SIGKILLs the process group on abort. - New model-facing tools (mirroring Claude Code): `bash_output` (read new output + status by id) and `kill_shell` (kill by id). Registered in both tool paths, added to BUILTIN_TOOL_NAMES, exposed in the schema. - Reverted #233's TaskKind piggyback on BackgroundStore (push-once delivery doesn't fit a long-lived process): back to subagents-only + running_count(). - Status bar: agents:N from BackgroundStore.running_count(), shells:N from the shell store. /tasks now lists background shells with status. Session swap/end kills all background shells. Tests: store unit tests (drain/cap/kill/finish-first-wins/list), an end-to-end unbounded background bash (streams output, exits clean, count returns to 0), and status-bar per-kind badges. Full feature-matrix suite green at -D warnings (2150 passed).
allen-munsch
pushed a commit
to allen-munsch/dirge
that referenced
this pull request
Jun 3, 2026
…ll-registry feat(bash): background/detached shells via the background registry
allen-munsch
pushed a commit
to allen-munsch/dirge
that referenced
this pull request
Jun 3, 2026
…ded + read/kill by id) Replaces the timeout-bounded, push-once background-shell design (dirge-code#233) with Claude Code's proven model: background bash runs UNBOUNDED and the model reads its output and stops it explicitly by id. - New BackgroundShellStore (bg_shell.rs): per-shell live output buffer (unread, drained on read; hard-capped so a never-read flood can't OOM), status (Running/Exited/Killed/Failed), and the drain JoinHandle. Process-global (like the subagent /kill registry) so the bash tool, the new tools, the status bar, and session cleanup share one instance without threading it through every signature; tests inject their own store. - bash background=true: spawn_streaming_shell runs the command detached with no timeout (optional `timeout` = auto-kill-after-N), streaming stdout/stderr into the store; returns a shell id immediately. Permission + sandbox checks run before the spawn as before. PgKillGuard SIGKILLs the process group on abort. - New model-facing tools (mirroring Claude Code): `bash_output` (read new output + status by id) and `kill_shell` (kill by id). Registered in both tool paths, added to BUILTIN_TOOL_NAMES, exposed in the schema. - Reverted dirge-code#233's TaskKind piggyback on BackgroundStore (push-once delivery doesn't fit a long-lived process): back to subagents-only + running_count(). - Status bar: agents:N from BackgroundStore.running_count(), shells:N from the shell store. /tasks now lists background shells with status. Session swap/end kills all background shells. Tests: store unit tests (drain/cap/kill/finish-first-wins/list), an end-to-end unbounded background bash (streams output, exits clean, count returns to 0), and status-bar per-kind badges. Full feature-matrix suite green at -D warnings (2150 passed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the deferred background-shell feature: a
backgroundmode for thebashtool so shells run detached and are tracked + counted like background subagents, with results delivered to the model.Design
Rather than a separate registry (which would duplicate the entire delivery pipeline), I extended the existing
BackgroundStorewith aTaskKind { Subagent, Shell }tag. Shells reuse the proven completion round-trip —notify→prepend_pending_notifications/ follow-up hook → output relay — that the model already understands, while the status bar counts each kind separately.Changes
background.rs—TaskKindtag onBackgroundTask;insert(id, kind);running_count_kind(kind); a per-kind shell cap (MAX_CONCURRENT_BG_SHELLS = 8). Removed the now-unused totalrunning_count().bash.rs—BashToolgains an injectedbg_store+with_bg_store().call()branches onbackground: same permission + sandbox checks, then spawns detached, registers asShell, returns a shell id immediately, andnotify()s the captured output (incl. non-zero exit) on completion. The JSON schema exposes the new arg. Degrades to synchronous when no store is wired (headless).builder.rs— injects the sharedbg_storeintoBashTool(both the rig and loop tool paths).task.rs— subagent cap + insert now useTaskKind::Subagent(so shells don't eat subagent slots and vice-versa).status.rs/mod.rs—render()takesOption<&BackgroundStore>and showsagents:N(subagents) andshells:N(shells), each only when non-zero; call sites pass the store.Available to the model
backgroundboolean in thebashschema (also flows throughdynamic_tool_search).task_statusalso works on a shell id.cancel_allaborts the spawned task, and the bashPgKillGuardSIGKILLs the detached process group on drop.Lifecycle / safety notes
timeout(default 600s in background mode vs 120s sync); a long-running server is killed at the timeout — pass a largetimeoutfor those. (True unbounded daemons would need an explicit kill-by-id surface — out of scope here.)Tests
Shell, returns an id immediately, deliversechooutput via the store, and the running count returns to 0.agents:N/shells:N, hidden at zero).-D warnings(2144 passed).