Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Background Processes

shell runs a command and waits for it, which is right for a build or a test run and wrong for a dev server, a file watcher, or anything else that is meant to keep going. For those the model has three tools:

ToolLevelDoes
background2Starts a command in the worktree and returns a job number, at once
job_status0The job's state and the last lines of its output; with no number, a list
job_kill2Stops the job and everything it started

background asks as shell does: it runs its command through a shell, so its patterns are exact commands with no wildcard. background(npm run dev) remembers exactly that dev-server command. job_kill asks too, since stopping a process the model started is still stopping a process. job_status only reads.

Execution resources

A locked background call waits before spawning and before reporting a running job. The job's supervisor retains its resources after the launch call returns, until termination and cleanup. A long-lived job can block a later command in the same ref; there is no reentrant exemption.

job_status and job_kill do not reacquire a target's resources implicitly. If their own configured rules overlap the target's held set, they fail instead of waiting on themselves. /job-kill ID requests user-authorized cancellation independently of those rules, even while publication waits on the job.

What a job is

A job runs in the ref's worktree with the same environment a shell command gets. Locked jobs are children of a dedicated supervisor, in their own process group; the job registry owns their control channel. background waits a moment before answering so a command that dies at once (command not found, a bad flag) is reported as dead with its output, not as started. Its answer is the same report job_status gives:

job 0  running  0:02  npm run dev
  > vite v5.0.0 dev server running at:
  > http://localhost:5173/
job_status {"id": 0} reads more; job_kill {"id": 0} stops it.

Output is kept in memory, the last 2000 lines of each job, with stderr lines marked ! . The model reads a tail (50 lines unless it asks for more), as you would with tail. Nothing is written to disk and nothing streams into the conversation; a job's line in the conversation is the background call that started it, and its output appears when the model asks for it.

Ending

Every job ends with the session. Leaving heph requests process cleanup. For locked jobs, a surviving Linux supervisor also handles heph crashes by stopping and reaping descendants before releasing resources. Killing that supervisor itself or deliberately escaping supervision is outside this guarantee. Within the session, job_kill stops one, and its output stays readable afterwards. At most eight jobs run at once; the ninth is refused until one is stopped.