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.

Use case

The OpenAI Agents SDK adapter routes one-shot commands through plain exec, but sometimes an agent needs a real PTY: TUI programs, progress bars, stateful shell sessions, or anything that calls isatty(). This recipe shows how to reach the full declaw PTY module from an adapter session so you get the best of both worlds — the Agents SDK tool loop and interactive terminal access when you need it.

What you’ll learn

  • Creating an OpenAI adapter session with DeclawSandboxClient
  • Reaching the PTY surface via session._inner._sbx.pty
  • Sending keystrokes, reading output, and resizing with the async API
  • Running a progress bar that would be garbled through a non-PTY pipe
  • Verifying env passthrough and shell state persistence across sends

Prerequisites

pip install "declaw[openai-agents]"
An OPENAI_API_KEY is not required for this example. It exercises the PTY surface directly without making any LLM calls.

Code walkthrough

Create the session

from declaw.openai import DeclawSandboxClient, DeclawSandboxClientOptions

client = DeclawSandboxClient()
session = await client.create(options=DeclawSandboxClientOptions(
    template="base",
    timeout=120,
    envs={"PTY_DEMO": "declaw-openai-pty"},
))

Open a PTY through the adapter

The adapter session exposes the underlying AsyncSandbox so you can use the PTY module directly:
pty = session._inner._sbx.pty
buf = bytearray()
handle = await pty.create(on_data=lambda c: buf.extend(c), timeout=30)

Four quick probes

# 1. Env passthrough
await handle.send_stdin(b"printenv PTY_DEMO\n")

# 2. Shell state persists across sends
await handle.send_stdin(b"X=42; echo X=$X\n")
await handle.send_stdin(b"echo still X=$X\n")

# 3. ANSI progress bar (garbled without a real PTY)
await handle.send_stdin(
    b"for i in $(seq 1 20); do "
    b"printf '\\r[%-20s] %d%%' $(printf '#%.0s' $(seq 1 $i)) $((i*5)); "
    b"sleep 0.05; done; echo\n"
)

# 4. Resize
await handle.resize(PtySize(cols=100, rows=30))
await handle.send_stdin(b"stty size\n")

Clean up

await handle.kill()
await client.delete(session=session)

Running it

export DECLAW_API_KEY="your-api-key"
export DECLAW_DOMAIN="api.declaw.ai"
python cookbook/examples/openai-agents-pty/main.py

Expected output

pty pid=7

== PTY output ==
declaw-openai-pty
=== state ===
X=42
still X=42
=== progress ===
[####################] 100%
=== size ===
30 100
The progress bar renders as a single in-place line because the PTY supports \r cursor return — a plain exec pipe would have printed 20 separate lines.

Full source

See cookbook/examples/openai-agents-pty/main.py in the repo.