Proc
Background processes that start ready and never zombie.
Skills that need a dev server or long-running helper all duplicate the same
~250 lines of fragile lifecycle bash: spawn with nohup, poll a port,
write a PID file, grep logs for "started", remember TERM-then-KILL, work
around process reapers. Across the skills we analyzed, this was the
buggiest code they carried.
steer proc owns that lifecycle. Bookkeeping lives in
<workspace>/.steer/proc/<name>/: meta.json (pid, command, readiness)
and the captured log. start --cwd <dir> sets the working directory.
When to use it
- The skill starts anything that outlives a single command: a dev server, a preview, a worker that later steps talk to.
- Anthropic's webapp-testing
ships
scripts/with_server.py, 105 lines of exactly this lifecycle, and it shows where hand-rolls crack: it terminates the process it spawned rather than the group (a wrapper likenpm run devdies, the server it started survives), and it pipes server output to a buffer nobody drains (a chatty server stalls mid-startup).steer proclogs to a file and stops the whole group. - Skip it for commands that finish on their own.
procis for the things you would otherwise track with a PID file.
CLI
steer proc start web --ready-port 5173 -- npm run dev
steer proc start api --ready-log "Listening on" --timeout 60 -- ./server
steer proc status [web] [--json]
steer proc logs web -n 100
steer proc stop webEverything after -- is the command. Readiness:
--ready-port N: wait until the port accepts connections--ready-log TEXT: wait until the text appears in the captured log- neither: steer checks that the process survives its first half-second
If the process exits immediately or misses the readiness deadline, steer stops it and reports the log tail, so the agent sees why instead of a silent zombie.
✓ Started 'web' (pid 73901) (port 5173 open)
log: /work/acme/.steer/proc/web/log
stop with: steer proc stop webGuarantees
- Detached: processes run in their own session and survive the shell that started them.
- Stop kills the group:
npm run dev→ node children die too; TERM → wait → KILL escalation. - No double starts: starting a running name is refused with the existing pid.
- Recycled-PID guard: status compares the running command against what was started; a reused pid reads as stopped, and steer only ever signals processes it started.
- Logs captured: stdout+stderr land in the log file;
steer proc logstails it.
Python
from steer.proc import start, status, logs, stop, ProcError
info = start("web", ["npm", "run", "dev"], workspace=".", ready_port=5173)
try:
...
finally:
stop("web", workspace=".")In a SKILL.md
steer new --with proc generates:
Start helpers through the bundled runtime so nothing leaks or zombies:
python3 scripts/steer.py proc start dev --ready-port 5173 -- npm run dev
python3 scripts/steer.py proc status dev
python3 scripts/steer.py proc stop dev # always stop what you started