PTY Tools
Terminal session management via six PTY (pseudo-terminal) tools
PTY (pseudo-terminal) tools let Anthracode manage long-running terminal processes like dev servers, REPLs, build watchers, and interactive commands. Unlike the standard bash tool which runs a command and waits for completion, PTY tools support persistent sessions with incremental output reading.
Since 1.15.12-anthra128, PTY sessions run through an out-of-process host by default. A native PTY crash can kill the host process, but the main agent keeps running and can report diagnostics instead of disappearing mid-session.
The standard bash tool is ideal for commands that start, run, and finish (e.g., npm install, git status, ls). For long-running processes, PTY tools provide:
- Persistent sessions: A dev server started with
pty_startkeeps running until explicitly stopped - Incremental output: Read only new output since your last read using the
sincecursor - Input injection: Send keystrokes, Ctrl+C, or text to a running process
- Real parallelism: Multiple terminal sessions can run simultaneously
- Crash isolation: Native PTY code runs in a separate host process (
ANTHRACODE_PTY_ISOLATION, default on)
PTY tools use the terminal_start and terminal_stop permission keys. Tools that start or execute commands (pty_start, pty_exec) require terminal_start; pty_stop requires terminal_stop. The remaining tools (pty_list, pty_read, pty_send) have no permission gate — they operate on already-running sessions.
Run a command in a PTY and wait for it to complete. Returns all output when the process exits.
{ "permission": { "terminal_start": "allow" }}| Parameter | Description |
|---|---|
command | Executable to run (e.g., "npm", "python") |
args | Array of arguments (e.g., ["install"]) |
cwd | Working directory (optional) |
timeout_ms | Max wait time before killing the process (default: 60000, max: 300000) |
The PTY spawns the executable directly — no shell expansion. Use args instead of embedding arguments in the command string.
Use for: npm install, git commands, test runners, builds — anything that completes on its own.
Start a persistent terminal session. Returns a session ID that other PTY tools use to interact with the process.
{ "permission": { "terminal_start": "allow" }}| Parameter | Description |
|---|---|
command | Executable to run |
args | Array of arguments |
cwd | Working directory (optional) |
title | Human-readable label for this session (optional) |
# Start a dev serverpty_start({ command: "npm", args: ["run", "dev"] })
# Start a Python REPLpty_start({ command: "python3", title: "python repl" })After starting, use pty_read to let the process initialize, then poll with pty_read for ongoing output. Send input with pty_send. Stop with pty_stop.
Each call creates a new process — do not call pty_start again for a task already running. Use pty_list to check existing sessions first.
Read output from a running terminal session. Supports incremental polling via the since cursor.
| Parameter | Description |
|---|---|
id | Session ID from pty_list or pty_start |
tail | Return only the last N lines (first read only) |
wait_ms | Wait before reading (max: 30000). Use 500–2000 for polling |
since | Return only output after this cursor position |
1. First read: pty_read(id="abc", wait_ms=1000) → Get initial output, save the cursor value from the response
2. Next reads: pty_read(id="abc", wait_ms=500, since=42) → Get ONLY new output, update cursor
3. Repeat step 2 until the response shows [session: exited]Using since= avoids resending duplicate output on every poll — critical for long sessions. Use tail= only on the first read if you want to limit initial output size.
The response always reports [session: running/exited] and [cursor: N].
Send keyboard input to a running terminal session.
| Parameter | Description |
|---|---|
id | Session ID from pty_list or pty_start |
input | Text or escape sequences to send |
| Sequence | Effect |
|---|---|
\x03 | Ctrl+C (interrupt) |
\x04 | Ctrl+D (Unix EOF) |
\x1a | Ctrl+Z (Windows EOF/exit) |
\x1b | Escape |
\r | Enter / submit command on every platform |
\n | Newline character (not reliable for submitting on Windows) |
\t | Tab |
# Send Ctrl+C to stop a running processpty_send(id="abc", input="\x03")
# Type a command and press Enterpty_send(id="abc", input="npm run build\r")
# Windows EOF to exit a REPLpty_send(id="abc", input="\x1a")After sending input, call pty_read with wait_ms to read the response. To submit a command, send carriage return (\r) or CRLF (\r\n). Do not rely on a lone newline (\n) in Windows shells — it may be absorbed as text.
List all active terminal sessions. Use this to find session IDs or check which sessions are still running.
No parameters required. Returns a list of session IDs with their current status.
Stop a terminal session and free its resources.
{ "permission": { "terminal_stop": "allow" }}| Parameter | Description |
|---|---|
id | Session ID from pty_list or pty_start |
Kills the process and removes the session. Use this when a long-running process is no longer needed.
When a command launched via pty_exec doesn’t complete within its timeout, the system returns the partial output along with a sessionId. The process continues running in the background, and the agent can poll for more output using pty_read.
pty_exec({ command: "npm", args: ["test"] })→ Returns partial output + sessionId="abc123"→ Call pty_read(id="abc123", wait_ms=500) to resume following the outputThis prevents the agent from getting stuck waiting for commands that take a long time between output chunks.
For the standard bash tool, when command output exceeds the inline limit (2000 lines or 51200 bytes), the output is automatically written to a file. The agent can then:
- Use
readwithoffsetandlimitto read specific sections - Use
grepto search the full content - Avoid truncation commands like
Select-Object -First— the full output is always available
When a PTY session is stopped, Anthracode uses tree-kill to ensure all child processes are terminated, not just the parent. This prevents orphaned processes (e.g., a dev server started by npm run dev will fully stop, including all its child processes).