Best on a larger screen — the animation and controls have more room to breathe.
Inputs

Background Tasks

A background task is a command an agent harness runs in a detached shell instead of inside the agent's turn. The call returns a handle immediately, the process keeps running while the agent works on something else, and a completion notification enters the agent's context at the next turn boundary — never mid-generation.

The reason this matters has nothing to do with speed. A model cannot do two things at once inside one turn; it emits one call, waits for the result, and reads it. So a 40-second test suite called the ordinary way costs the session 40 seconds of silence — the agent isn't slow, it's held. Detaching the command doesn't make it finish sooner. It gives those 40 seconds back, and the only currency you can spend them on is work that doesn't need the command's answer.

That last clause is the whole lesson, and it is where most people's intuition about "async" quietly breaks.

How Background Tasks Work

Three mechanics, and each one shows up in the wall clock.

The launch returns a handle, not a result. Bash(..., run_in_background=True) starts a shell, hands back an id — bash_1 in this lesson — and ends in about a second. The agent's turn is free from that point on. Whatever the command is doing, it is doing somewhere the agent's turn cannot see.

The output goes to a buffer. Nobody is watching the detached process, so its stdout accumulates. When the agent later calls BashOutput(bash_1), it gets everything written since the last read. This is why you don't lose the test failures you never watched scroll past — but it is also why an early read returns an empty buffer rather than blocking until there is something to say.

Completion is a message, and messages arrive at turn boundaries. When the process exits, the harness has news for the agent. It cannot deliver it mid-generation: the model is a function over a conversation, and you cannot append to a conversation the model is already halfway through producing a reply to. So the notification waits for the boundary — the moment the current turn ends and the next one assembles its context. If the agent happens to be idle, that boundary is immediate and the harness wakes it. If the agent is mid-turn, the process has been finished for some seconds before the agent has any way of knowing.

Put those together and the arithmetic is unromantic. Blocking costs you the command's runtime. Detaching costs you a launch, plus a turn to read the output when the news arrives, and buys you whatever work you could do in the meantime. Whether that is a win is a subtraction, not a principle.

Background Tasks Step by Step

The default run is a session with four things to do: run pytest -q (40 seconds), then land three CHANGELOG-style edits that touch files the suite never reads, then report. The cost model on the canvas is fixed and visible: a model turn is 8 seconds, an edit is 5, a launch is 1, a read is 1.

t = 0–8s — the model plans. It sees a 40-second command and three edits that don't depend on it, and decides to detach.

t = 8–9s — launch. run_in_background returns bash_1. One second. The shell lane's bar starts growing at t = 9 and will run to t = 49.

t = 9–22s — the first edit. The agent takes a new turn (8s), edits CHANGELOG.md (5s). Watch both lanes in this beat: the agent's bar and bash_1's bar advance together. That overlap, repeated through all three edits, is the entire saving.

t = 22–35s, 35–48s — the other two edits. Same shape. By t = 48 the work list is empty except the final report, and the suite still has a second to run.

t = 49s — bash_1 exits. Exit code 0, 42 lines buffered. Look at the agent lane at this instant: nothing changes. The agent is one second into a turn it started at t = 48, and a turn in progress cannot be interrupted. A notification card appears over the shell lane, and as the turn runs on, a dashed segment opens to its right — the process is finished and the agent does not know.

t = 56s — delivery. The turn ends, the next one assembles, and the notification lands with it. The process had been done for seven seconds. That gap is not a bug in the harness and not a slow queue; it is what "the model reads its context at the start of a turn" means when you write it as a clock.

t = 56–65s — the read. A turn (8s) plus BashOutput(bash_1) (1s) moves those 42 lines into the conversation.

t = 65–73s — the answer. Total: 73 seconds.

Now the control. Flip the dispatch to a blocking call and nothing else: the same 40-second suite runs inside turn 1, the agent sits in it until t = 48, and the three edits — unchanged, still independent — happen afterwards, one turn each. That run finishes at 95 seconds. The ghost line on the canvas marks it, and the detached run lands 22 seconds to its left.

What Backgrounding Actually Buys

The saving is at most min(command runtime, independent work available) − overhead, and every term in that expression is something you control. The bound is exact unless the exit just misses a turn boundary — then the read waits one more turn, which is why the default run saves 22 seconds, not 29.

The ceiling is your own work, not the command. In this workload the agent has 39 seconds of independent work after the launch: three turns and three edits. Stretch the suite to 50 seconds and the saving reaches 29 — those 39 minus the 10-second overhead — and stops. Stretch it to 120 and it is still 29. Past a certain point the agent finishes its edits and falls asleep, and a longer command only lengthens the nap — the canvas draws it as a gray stretch in the agent lane, and the scoreboard's agent idle row prices it. Detaching a two-hour job does not buy you two hours; it buys you exactly as much as you had queued up to do.

The floor is the overhead. Detaching costs a launch (1s) and a turn to read the output (8 + 1). Below a 10-second command those ten seconds are more than the overlap could ever return. At exactly 10 seconds the two dispatches tie; at 5 seconds, backgrounding loses by 5. Backgrounding a fast command is a pure tax.

Delivery timing shows up in the total. Slide the command from 35 seconds to 40 and the saving falls from 25 to 22 — five more seconds of command pushed the exit one second past a turn boundary, so the read slipped a whole turn later. Apart from that dip, the saving rises second for second until the edits run out, then sits flat from 50 seconds on.

Edge Cases

  • The follow-up needs the output. Point those same three edits at the code the suite is complaining about and backgrounding has nothing to overlap with. The agent detaches, discovers its next step is blocked, spends a turn on an early BashOutput that returns an empty buffer, then sleeps until the process exits anyway. Same workload, same command: 105 seconds against blocking's 95. Detaching work you immediately need is strictly worse than waiting for it. Here the bill is 10 seconds, the launch plus the wasted poll turn; a 10-second command loses 17, because its exit misses the poll by a second and the read waits for the next turn boundary.
  • The agent is already asleep when the process exits. Then there is no staleness gap — the harness wakes it at the exit second. That is the long-command case: the agent ran out of work before the command did, so the saving already sits at its ceiling and the extra runtime is spent asleep.
  • A read before anything is written. BashOutput does not block. On a process that hasn't printed yet it returns nothing and reports the shell as still running, which is information — but it cost a full turn to get.
  • The process never exits. A server, a watcher, a hung command: there is no completion notification, ever. The agent's only way to learn anything is to poll, and each poll is a turn. Background a long-running process on purpose, not by accident.
  • Several shells at once. Nothing above assumes one. Each detached command runs on its own clock and each sends its own notification; they queue at the same turn boundaries. The ceiling argument doesn't change — the agent is still single-threaded, so more shells only help if you have more independent work to feed them.

Common Mistakes

  • Treating "async" as "faster". The command takes what it takes. Backgrounding moves it out of the agent's turn; it does not shorten it. If nothing useful can run alongside, the wall clock is the same or worse.
  • Reading an empty buffer as a clean result. A detached process that hasn't finished has printed nothing yet. "No output" means "not done", not "no problems" — check the shell's status, not the emptiness of the text.
  • Polling in a loop. Every poll is a full turn of model latency for one line of status. Two polls cost more than most commands people background. Let the completion notification do its job.
  • Expecting the notification to interrupt. It cannot. The agent reacts at turn boundaries, so any plan that depends on reacting the instant something finishes is not implementable on this machinery — at best you get boundary-grained reaction.
  • Forgetting that reading costs a turn. The notification says the process exited; it does not carry the output. Budget the read turn when you decide whether detaching pays.

A Note on Simplification

This lesson is an explainer, not a model of any particular harness's scheduler. The durations are authored constants chosen to be legible — a real model turn varies from under a second to tens of seconds with the request, tools differ wildly in cost, and output streams rather than arriving in one 42-line lump. The run also assumes one background shell, a single agent working alone, and no interleaved user turns, where a real session usually has a human in the loop whose own turns are where notifications land. What is faithful is the shape: a detached call returns a handle immediately, output buffers until it is read, completion is delivered as a message at a turn boundary, and the saving is bounded by the independent work available.

For the surrounding machinery, the agent loop is the turn structure this lesson puts on a clock, the task system is how work gets ordered and survives a crash, and subagents split a job across contexts where this one splits it across time. If the two-lane picture looked familiar, process scheduling is the same idea one level down: a single CPU, work that blocks, and a timeline that only pays off when something else is ready to run.