Agent infrastructure · Process lifecycle · 8 min read
Disconnect the terminal. Keep the agent running.
A terminal session and a sandbox have different lifetimes. Use that difference deliberately: start a detached process, persist its state in the workspace, reconnect from another process, and let one controller own final deletion.
01 · Three lifetimes
Do not make the terminal your scheduler.
A PTY exists until the WebSocket or shell exits. A child started in that foreground session may receive a hangup when the PTY disappears. The sandbox container is different: it remains running until an explicit delete, and processes properly detached from the PTY can remain with it.
Terminal
Human-scale and interactive. Safe to disconnect, but not a durable process supervisor.
Worker process
Detached from stdin, writing logs and checkpoints to /workspace.
Sandbox
The billing and isolation boundary. It survives both the launcher and terminal.
02 · A minimal worker
Put progress on disk, not only in memory.
The example consumes a newline-delimited task file and records an offset after every completed item. It is intentionally small. A production queue should provide atomic claims, retries, visibility timeouts, and idempotent task handlers; the sandbox itself is only the execution boundary.
# worker.py
import json
import time
from pathlib import Path
queue = Path("/workspace/tasks.jsonl")
checkpoint = Path("/workspace/checkpoint.txt")
offset = int(checkpoint.read_text() or 0) if checkpoint.exists() else 0
while True:
lines = queue.read_text().splitlines() if queue.exists() else []
for line in lines[offset:]:
task = json.loads(line)
print(f"processing {task['id']}", flush=True)
# Replace this with an agent invocation or finite task handler.
time.sleep(2)
offset += 1
checkpoint.write_text(str(offset))
time.sleep(5)03 · Launch and leave
Detach every inherited stream.
nohup handles a hangup signal, but that is only
part of detaching. Redirect stdin away from the API call and send stdout and stderr to a file.
Record the PID so later controllers can distinguish “the sandbox exists” from “the worker is
alive.”
from boltzlabs import Sandbox
WORKER = open("worker.py").read()
# Do not use a context manager: this machine is meant to outlive this launcher.
sb = Sandbox(
environment="python",
machine="small",
name="background-worker",
internet=True,
)
# There is no upload primitive. Run a small Python snippet that writes the file.
sb.run(
f"from pathlib import Path; Path('/workspace/worker.py').write_text({WORKER!r})",
language="python",
).check()
sb.exec(
"nohup python3 -u /workspace/worker.py "
"</dev/null >/workspace/worker.log 2>&1 "
"& echo $! >/workspace/worker.pid"
).check()
print(sb.id)with block? A context manager deletes the sandbox when the launcher exits. That is exactly right for a finite
job and exactly wrong when the process is supposed to outlive its launcher.04 · Operate it
Reconnect by name and inspect finite snapshots.
Sandbox exec captures output after a finite command completes; it is not a continuous log stream. Use short status and tail commands for polling, or connect a PTY when a human needs to observe the workspace directly.
bzlabs exec background-worker 'pid=$(cat /workspace/worker.pid); kill -0 "$pid" && echo running'
bzlabs exec background-worker 'tail -n 50 /workspace/worker.log'
bzlabs exec background-worker 'cat /workspace/checkpoint.txt'import shlex
from boltzlabs import sandbox
sb = sandbox("background-worker")
task = '{"id":"index-docs","repo":"https://github.com/example/docs.git"}'
sb.exec(
f"printf '%s\n' {shlex.quote(task)} >> /workspace/tasks.jsonl"
).check()The Python SDK can attach to the same named sandbox from a different process. Keep the name unique among running sandboxes and treat the workspace as the handoff point.
import boltzlabs
sb = boltzlabs.sandbox("background-worker")
print(sb.status)
print(sb.exec("tail -n 20 /workspace/worker.log").stdout)
# Cleanup belongs in the controller that owns the job lifecycle.
sb.delete()05 · Stop and clean up
Deletion is the terminal state.
Stopping a worker leaves its logs available for review and leaves the meter running. Deleting the sandbox removes the container and workspace. Export anything you need before that step; there is no snapshot or restore API.
# Ask the process to stop, wait briefly, then destroy the machine.
bzlabs exec background-worker 'kill -TERM "$(cat /workspace/worker.pid)" 2>/dev/null || true'
bzlabs rm background-workerCurrent operational boundary
BoltzLabs does not currently supervise or retry the detached process, stream its stdout, or enforce the accepted idle-timeout and maximum-lifetime fields. Run an external reconciler if jobs must be restarted or garbage-collected without human intervention.