Background shells with ring buffers: fire long jobs, check in later
Agents shouldn't sit and watch a build scroll by. Rysh gives them background shell sessions with 256KB ring buffers — start, work, check back.
Watch an AI agent run a twenty-minute integration suite in a normal terminal setup and you'll see the dumbest possible use of a language model: it starts the command… and waits. The whole conversation blocks on a spinner. Twenty minutes of the most capable text-processing system ever built doing absolutely nothing, because the tool call hasn't returned yet.
Humans don't work like that. You kick off the build, switch windows, write the next function, and glance at the build every so often. The glance is the skill.
Rysh gives agents the same skill, with two tools: bash_background and bash_session.
The shape of it
bash_background starts a command in a detached background session and returns immediately — with a session ID. The agent's conversation doesn't block. It has a handle, not a wait.
bash_session is everything you do with that handle, as one tool with three actions:
read— fetch the output produced so far (and whether the process is still running)list— enumerate all background sessions this workspace has goingkill— terminate one that's gone wrong or gone long
So an agent's transcript stops looking like "ran tests (20 minutes of silence)" and starts looking like an actual engineer's loop:
bash_background: go test -tags=integration ./... → session bg_7f3a
edit: fix the codec registration in internal/msg/…
bash_session read bg_7f3a → still running, 41 tests passed so far
file_write: update CHANGELOG.md
bash_session read bg_7f3a → done: 2 failures in pane_group_test.go
The agent interleaves. It reads partial output mid-run and reacts to it. If the first failure shows up at minute three, it doesn't need minute twenty to start thinking.
Why a ring buffer
Here's the design decision worth talking about: each background session captures output into a 256KB ring buffer, managed by rysh's BackgroundSessionManager. When output exceeds 256KB, the oldest bytes fall off the front.
That sounds like a limitation. It's actually the correct data structure, for three reasons.
1. Long jobs produce unbounded output; memory is not unbounded. A verbose test suite or a chatty deploy script can emit hundreds of megabytes. Buffering all of it "just in case" is how background-job systems eat your RAM. A ring buffer makes the cost of any session constant, no matter how noisy the job is.
2. The tail is where the truth lives. For almost every long-running command, the diagnostic signal is at the end: the failure summary, the exit trace, the last error before the hang. The front of the log is boilerplate. A ring buffer is a bet that the most recent 256KB matters more than the first — and for shell output, that bet almost always pays.
3. Agents have context windows. This is the part that's specific to agentic terminals. Whatever the agent reads gets spent from a token budget. Handing an agent a 300MB log isn't thoroughness, it's a denial-of-service on its own context. A bounded buffer is a contract: reads are always cheap enough to actually do. Which means the agent checks in more often, which means it catches failures earlier. Bounded output makes agents more attentive, not less informed.
(Need the full log? That's what redirection is for — go test ./... 2>&1 | tee /tmp/full.log in the background session, and the agent can grep the file surgically. The ring buffer is the default, not a wall.)
The governance still applies
Background execution doesn't get a hall pass. The command an agent hands to bash_background goes through the same safe-by-default policy as foreground bash: read-only and idempotent commands run freely, while mutating, redirecting, or chained-unsafe commands need your approval before they start. You approve the launch; the session runs; every read after that is inspection, which is always free.
And because it's rysh, all of it is visible. The tool calls — the start, every check-in, the kill if it comes to that — appear in the pane like everything else the agent does. A background job is out of the way, not out of sight.
What this unlocks
Once agents can fire-and-check instead of fire-and-wait, a different working pattern shows up:
- Build-while-editing. The agent starts the build, keeps refactoring, and folds compile errors in as they arrive.
- Long ops under supervision. Database migrations, load tests,
docker build— started, monitored at intervals, killed on anomaly. - Parallel verification. Lint in one background session, tests in another, the agent continuing in the foreground — then a sweep of
bash_session readcalls to collect results. - You and the agent sharing a job.
bash_session listshows what's running; you can watch the same pane, see the same reads, and step in whenever.
None of this is exotic. It's just how a competent engineer already uses a terminal — encoded as tools, so the agent can do it too, inside a governed workspace you can watch.
Rysh runs on Claude with your own API key, on your own machine — the background sessions, the buffers, the logs: all local, all yours.
Fire the job. Do the work. Glance when it matters.
Building with agents in your real dev loop? We're recruiting design partners with a direct line to the founders: rysh.ai/design-partner.
More in this series: The bash allowlist: safe-by-default shell execution for agents · Loop detection: stopping an agent from calling the same tool forever