The capsule.commands interface gives you full control over process execution inside a capsule. You can run commands synchronously and wait for the result, launch long-running processes in the background, or stream output in real time over a WebSocket connection.
Foreground execution
capsule.commands.run() executes a shell command and blocks until it exits. It returns a CommandResult with the full stdout, stderr, exit code, and wall-clock duration.
result = capsule.commands.run("python -c 'print(42)'")
print(result.stdout) # "42\n"
print(result.stderr) # ""
print(result.exit_code) # 0
print(result.duration_ms) # 35
Options
| Parameter | Type | Default | Description |
|---|
timeout | int | None | 30 | Seconds before the command is killed. None disables the timeout. |
envs | dict[str, str] | None | None | Additional environment variables for the process. |
cwd | str | None | None | Working directory for the process. |
result = capsule.commands.run(
"python train.py",
timeout=300,
envs={"CUDA_VISIBLE_DEVICES": "0", "LOG_LEVEL": "debug"},
cwd="/app",
)
Background execution
Pass background=True to launch a process without waiting for it to finish. You get a CommandHandle immediately with the process PID and a tag.
handle = capsule.commands.run("python server.py", background=True)
print(handle.pid) # 1234
print(handle.tag) # "exec-abc123"
Pass a tag string to label a background process with a human-readable name. You can use the same tag later to find it in capsule.commands.list().
handle = capsule.commands.run(
"uvicorn app:main --port 8000",
background=True,
tag="api-server",
)
Streaming execution
capsule.commands.stream() opens a WebSocket connection and yields events as the process runs. Use this when you want to display output in real time instead of waiting for the process to finish.
import sys
for event in capsule.commands.stream("python", args=["-u", "train.py"]):
match event.type:
case "start":
print(f"process started with PID {event.pid}")
case "stdout":
print(event.data, end="")
case "stderr":
print(event.data, end="", file=sys.stderr)
case "exit":
print(f"\nexited with code {event.exit_code}")
When you pass args, the command is executed directly. When you omit args, the command string is passed to /bin/sh -c.
Stream event types
| Event type | Fields | Description |
|---|
start | pid | Process has started. |
stdout | data | A chunk of standard output. |
stderr | data | A chunk of standard error. |
exit | exit_code | Process has exited. |
error | data | A stream-level error occurred. |
Connecting to a running background process
capsule.commands.connect() attaches to an already-running background process by PID and yields its output events, exactly like stream().
# Launch a background process
handle = capsule.commands.run("python long_job.py", background=True)
# Attach to it later and stream its output
for event in capsule.commands.connect(handle.pid):
if event.type == "stdout":
print(event.data, end="")
elif event.type == "exit":
print(f"exited: {event.exit_code}")
Listing running processes
capsule.commands.list() returns all background processes currently running in the capsule as a list of ProcessInfo objects.
for proc in capsule.commands.list():
print(proc.pid, proc.tag, proc.cmd)
Each ProcessInfo has:
| Field | Type | Description |
|---|
pid | int | Process PID. |
tag | str | None | User-assigned or auto-generated tag. |
cmd | str | None | Executable path. |
args | list[str] | None | Arguments passed to the executable. |
Killing a process
Send SIGKILL to a background process by PID:
capsule.commands.kill(pid=1234)
Complete example
from wrenn import Capsule
import sys
with Capsule(template="minimal", wait=True) as capsule:
# Write a script to run
capsule.files.write("/app/task.py", """
import time
for i in range(5):
print(f"step {i}")
time.sleep(0.5)
""")
# Stream it
for event in capsule.commands.stream("python", args=["-u", "/app/task.py"]):
match event.type:
case "stdout":
sys.stdout.write(event.data)
case "exit":
print(f"done (exit {event.exit_code})")
# Launch a background server
handle = capsule.commands.run(
"python -m http.server 8080",
background=True,
cwd="/app",
tag="http-server",
)
# Confirm it is running
procs = capsule.commands.list()
print(any(p.tag == "http-server" for p in procs)) # True
# Kill it
capsule.commands.kill(pid=handle.pid)