A Cursor-style coding agent backed by Claude and a persistent declaw
sandbox. The agent has five tools for everyday work (shell,
read_file, write_file, ls) plus one special tool:
pty_interactive, which hands your local TTY to the sandbox when
the agent decides it needs human input.
Use case
Sometimes an agent hits a wall that requires a human: an OAuth
login, a sudo password, a TUI license agreement, or a vim session.
Instead of failing or asking you to switch to another terminal, the
agent calls pty_interactive with a reason, you approve with y/n,
and your terminal drops into a live shell inside the sandbox. When
the interactive command finishes, control returns to the agent loop.
What you’ll learn
- Defining tool schemas for the Anthropic Messages API
- Running a multi-turn agent loop with tool-use stop reason
- PTY handoff pattern: raw-mode local TTY forwarding with
SIGWINCH
- Capturing PTY output in a thread-safe
bytearray and feeding it back to the agent
- Trimming captured output to keep the agent’s context bounded
Prerequisites
pip install declaw anthropic
export ANTHROPIC_API_KEY="your-anthropic-key"
This example requires a real TTY (sys.stdin.isatty()). It will
not work inside a Jupyter notebook or piped stdin.
| Tool | Description |
|---|
shell(cmd) | Non-interactive command. Returns stdout/stderr/exit_code. |
pty_interactive(cmd, reason) | Interactive command. Prompts user y/n, then forwards local TTY to sandbox. |
read_file(path) | Read a file from the sandbox. |
write_file(path, content) | Write a file to the sandbox. |
ls(path) | List a sandbox directory. |
How PTY handoff works
When the agent calls pty_interactive:
- The script prints the agent’s reason and the command in a
box and asks
Proceed? [y/N].
- On
y, sbx.pty.create() opens a PTY session. The local TTY
switches to raw mode (tty.setraw), and a SIGWINCH handler
propagates window resizes.
- Every local keystroke is forwarded via
handle.send_stdin().
Every byte from the sandbox streams to stdout via on_data and
is also captured in a bytearray.
Ctrl-D sends exit\n to close the remote shell cleanly.
- The captured output (trimmed to ~4 KB) is returned as the tool
result so the agent knows what happened during the session.
handle = sbx.pty.create(size=size, on_data=tee, timeout=1800)
signal.signal(signal.SIGWINCH,
lambda *_: handle.resize(_local_term_size()))
tty.setraw(fd)
while True:
data = os.read(fd, 1024)
if data == b"\x04":
handle.send_stdin(b"exit\n")
break
handle.send_stdin(data)
Running it
export DECLAW_API_KEY="your-api-key"
export DECLAW_DOMAIN="api.declaw.ai"
export ANTHROPIC_API_KEY="your-anthropic-key"
python cookbook/examples/agent-with-pty/main.py
You are prompted for a goal. Try something like:
> install the gh CLI and log me in to my GitHub account
The agent will use shell to install gh, then call
pty_interactive(cmd="gh auth login", reason="GitHub OAuth flow requires browser interaction"). You approve, complete the login in
the live terminal, press Ctrl-D, and the agent continues.
Full source
See cookbook/examples/agent-with-pty/main.py in the repo.