Skip to main content
Background commands run without blocking your code. Use them for servers, long-running tasks, and processes you want to interact with later.

Starting a Background Command

from moru import Sandbox

sandbox = Sandbox.create()

# Start a command in the background
handle = sandbox.commands.run("python3 -m http.server 8080", background=True)

print(f"Server started with PID: {handle.pid}")

# Your code continues immediately
print("Doing other work...")

# Later, you can interact with the command

Command Handle

The CommandHandle provides methods to interact with the running command:
MethodDescription
wait()Block until command completes
kill()Terminate the command
send_stdin(data) / sendStdin(data)Send input to the command
disconnect()Stop receiving events without killing
PropertyDescription
pidProcess ID
stdoutAccumulated stdout so far
stderrAccumulated stderr so far
exit_code / exitCodeExit code (after completion)

Waiting for Completion

# Start long-running task
handle = sandbox.commands.run("npm install", background=True)

# Wait for it to complete
result = handle.wait()

print(f"Exit code: {result.exit_code}")
print(f"Output: {result.stdout}")

Killing a Background Command

# Start a server
handle = sandbox.commands.run("python3 server.py", background=True)

# Do some work...
import time
time.sleep(5)

# Kill the server
killed = handle.kill()
print(f"Killed: {killed}")  # True

Sending Input (stdin)

# Start an interactive process
handle = sandbox.commands.run(
    "python3 -c 'while True: print(input().upper())'",
    background=True,
    stdin=True
)

# Send input
handle.send_stdin("hello\n")
time.sleep(0.1)
handle.send_stdin("world\n")

# Check output
time.sleep(0.1)
print(handle.stdout)  # HELLO\nWORLD\n

handle.kill()

Streaming Output

Stream output from a background command:
def on_output(data):
    print(f"[stdout] {data}", end="")

def on_error(data):
    print(f"[stderr] {data}", end="")

handle = sandbox.commands.run(
    "for i in $(seq 1 5); do echo $i; sleep 1; done",
    background=True,
    on_stdout=on_output,
    on_stderr=on_error
)

# Output streams in real-time while you do other work
time.sleep(6)

print(f"\nFinal exit code: {handle.exit_code}")

Disconnecting

Stop receiving events without killing the process:
handle = sandbox.commands.run("python3 server.py", background=True)

# Stop receiving output but keep process running
handle.disconnect()

# Process is still running
result = sandbox.commands.run("pgrep -f 'python3 server.py'")
print(f"Still running: {result.exit_code == 0}")

Listing Running Commands

# Start some background commands
sandbox.commands.run("sleep 100", background=True)
sandbox.commands.run("python3 -m http.server 8080", background=True)

# List all running commands
processes = sandbox.commands.list()

for proc in processes:
    print(f"PID: {proc.pid}, Command: {proc.cmd} {proc.args}")

Killing by PID

# List processes and kill specific ones
processes = sandbox.commands.list()

for proc in processes:
    if "http.server" in proc.cmd:
        killed = sandbox.commands.kill(proc.pid)
        print(f"Killed PID {proc.pid}: {killed}")

Reconnecting to a Command

Connect to a running command you started earlier:
# Start a command
handle = sandbox.commands.run("python3 long_task.py", background=True)
pid = handle.pid

# Later, or in another part of your code
handle = sandbox.commands.connect(pid)

# Now you can wait or interact with it
result = handle.wait()

Web Server Example

from moru import Sandbox
import requests

sandbox = Sandbox.create()

# Write a simple server
sandbox.files.write("/home/user/server.py", """
from http.server import HTTPServer, SimpleHTTPRequestHandler
print("Starting server on port 8080...")
HTTPServer(("0.0.0.0", 8080), SimpleHTTPRequestHandler).serve_forever()
""")

# Start the server
handle = sandbox.commands.run("python3 /home/user/server.py", background=True)

# Wait for server to start
import time
time.sleep(2)

# Get the public URL
host = sandbox.get_host(8080)
print(f"Server available at: https://{host}")

# Make a request
response = requests.get(f"https://{host}")
print(f"Response status: {response.status_code}")

# Clean up
handle.kill()
sandbox.kill()

Next Steps