Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

Stdio keeps stdin open on a process inside the sandbox so you can send data incrementally and receive stdout/stderr as it’s produced. Unlike commands.run, which waits for the process to exit before returning output, stdio lets you:
  • Send input line by line — pipe data into cat, wc, jq, a database CLI, or any process that reads from stdin interactively.
  • Receive output as it arrives — via callbacks or an iterator, not as one blob at the end.
  • Separate stdout and stderr — independent callbacks for each stream.
  • Close stdin to signal EOF — the process sees end-of-file on its stdin, just like pressing Ctrl-D in a terminal.
  • Kill long-running processes — terminate a process mid-execution and retrieve the exit code.
Stdio is the right tool for REPLs, MCP servers, database shells, language servers, and any process that reads from stdin in a loop.

When to use Stdio vs Commands vs PTY

Use commands.run when…Use stdio.start when…Use pty.create when…
Command takes all input upfront and exitsCommand reads from stdin interactivelyCommand needs a real terminal (ANSI, raw mode)
You only need the final resultYou need to send data mid-executionYou need keystroke-level input
pip install, go build, pytestcat, wc, jq, database CLI, MCP servervim, htop, ssh, gh auth login
Default to commands.run. Use stdio.start when you need to pipe data into a running process. Use pty.create only when the command requires a real terminal.

Architecture

stdio.start uses four REST endpoints plus one SSE stream:
OperationMethod / route
Start processPOST /sandboxes/{id}/stdio — returns a cmd_id
Send stdinPOST /sandboxes/{id}/stdio/{cmd_id}/stdin
Close stdinPOST /sandboxes/{id}/stdio/{cmd_id}/stdin/close
Kill processDELETE /sandboxes/{id}/stdio/{cmd_id}
Live outputGET /sandboxes/{id}/stdio/{cmd_id}/stream (SSE, base64-encoded frames)
The SSE stream emits event: stdout and event: stderr frames with base64-encoded data, plus a final event: exit frame with the exit code.

Quick start

from declaw import Sandbox

sbx = Sandbox.create()
try:
    # Start a process with an open stdin pipe
    chunks = []
    proc = sbx.stdio.start("cat", on_stdout=lambda d: chunks.append(d))

    # Send data and close stdin
    proc.send_stdin("hello from stdio!\n")
    proc.close_stdin()

    # Wait for the process to exit
    result = proc.wait(timeout=10)
    print(b"".join(chunks).decode().strip())  # "hello from stdio!"
    print(result.exit_code)                    # 0
finally:
    sbx.kill()

Multi-round interaction

Send multiple lines of input to a process that reads in a loop:
replies = []
proc = sbx.stdio.start(
    "sh -c 'while read line; do echo \"reply: $line\"; done'",
    on_stdout=lambda d: replies.append(d),
)

for i in range(5):
    proc.send_stdin(f"message {i}\n")

proc.close_stdin()
result = proc.wait(timeout=10)
print(b"".join(replies).decode())
# reply: message 0
# reply: message 1
# ...

Environment variables and working directory

proc = sbx.stdio.start(
    "sh -c 'echo $GREETING from $(pwd)'",
    envs={"GREETING": "hello"},
    cwd="/tmp",
    on_stdout=lambda d: print(d.decode().strip()),
)
proc.wait()
# hello from /tmp

Killing a process

import time

proc = sbx.stdio.start("sleep 300")
time.sleep(1)
killed = proc.kill()          # True
result = proc.wait()
print(result.exit_code)       # -1

Next steps