Voxcaster — persistent interactive PTY sessions
Voxcaster gives you real pseudo-terminal sessions that outlive a single tool
call. Unlike the Bash tool — which blocks for the lifetime of the command,
gives no interactive stdin, and offers no TTY — a Voxcaster session keeps running
in the background. You spawn it, then read its output, send it input, wait for it
to finish, or kill it, across many turns.
When to reach for Voxcaster (and when not to)
Use Voxcaster when a process lives over time:
- Dev servers (
npm run dev, cargo run, flask run, vite) you want up while you keep working
- Watchers (
cargo watch, jest --watch, tsc -w, nodemon)
- Long builds/tests where you want to do other things and be told when they exit
- Interactive programs that need stdin: REPLs (
python, node, irb), ssh, psql, gdb, anything with a prompt
- Anything that emits a TTY-only experience (progress bars, colored output, paging)
- Any command you'd otherwise background with
& and then poll with sleep + re-check — Voxcaster replaces that anti-pattern
Keep using the plain Bash tool for quick, one-shot commands that return
promptly (ls, git status, grep, a fast build). Voxcaster's value is
persistence and interactivity; don't add its overhead to a command that finishes
in a second.
The tools
| Tool |
Use it for |
pty_spawn |
Start a process. command + args array (never a shell string). Returns a pty_<id>. |
pty_read |
Read the scrollback (offset/limit by line, pattern to glob-filter, raw for ANSI, format:"json" to parse). |
pty_write |
Send stdin to a running session (answer a prompt, type a REPL command — include \n). |
pty_wait |
Block until the process exits or timeout_seconds elapses; returns exit code. The guaranteed completion path. |
pty_list |
Enumerate sessions and their status (running / exited / killed). |
pty_kill |
Stop a session (cleanup:true also frees its buffer). |
Every tool takes an optional format: "text" | "json". Use json when you want
to parse fields (exit code, line counts, status) reliably; text is the readable
default.
Core patterns
Start something and keep working. Spawn it, then move on. Read its output
later with pty_read when you need to check progress.
pty_spawn(command="npm", args=["run","dev"], title="dev server")
# ... do other work ...
pty_read(id="pty_…", pattern="*Local:*") # find the URL it printed
Run a job and learn when it's done — hands-free. Set notify_on_exit: true.
If the session was launched with Claude Code channels enabled (e.g. via the
verity launcher), Voxcaster pushes a <channel source="voxcaster" exit_code=…>
event into your session when the process exits, so you react without polling.
When channels aren't active, this is silently skipped — so for guaranteed
completion, use pty_wait.
pty_spawn(command="cargo", args=["test"], notify_on_exit=true, title="tests")
# either wait for the <channel> exit event, OR:
pty_wait(id="pty_…", timeout_seconds=300)
Never sleep-and-poll. If you want to block on completion, call pty_wait —
don't spawn then loop sleep + pty_read. pty_wait returns the moment the
process exits (or your timeout hits).
Interactive input. Drive a REPL or answer a prompt with pty_write (remember
the newline), then pty_read the response.
pty_spawn(command="python", args=["-i"], title="repl")
pty_write(id="pty_…", data="import sys; print(sys.version)\n")
pty_read(id="pty_…", limit=5)
Find something in noisy output. pty_read(pattern="*error*") glob-filters the
scrollback instead of returning thousands of lines.
What to know
- argv only, no shell string.
pty_spawn takes a command + args array.
To use shell features (pipes, &&, redirection) run them through a shell:
command="bash", args=["-c","make && ./run"] (or cmd /C … on Windows).
- Command policy. A built-in floor always blocks catastrophic commands
(recursive root deletion, disk formatting, pipe-to-shell, fork bombs, etc.). If
a spawn is denied "by policy", that's the floor or the operator's
VOXCASTER_POLICY — don't try to route around it; tell the user.
- Session-bound lifetime. Sessions live as long as the Voxcaster server (the
current agent session). They're reaped on shutdown — they do not survive a
session restart. Don't assume a
pty_… id from a previous session still exists;
pty_list to confirm.
- Line-addressed output. Reads are by line (
offset/limit), ANSI is
stripped by default (raw:true to keep it).
- Spawn latency. On some Windows hosts a PTY spawn can take a couple of
seconds to initialize — give
pty_wait a sensible timeout and don't assume an
instant exit.
Quick decision rule
If you're about to run something with the Bash tool and you think "this will keep
running" or "I'll need to send it input" or "I want to be told when it finishes"
— stop and use pty_spawn instead.
1---2name: voxcaster3description: Run and manage long-running, background, or interactive processes via the Voxcaster MCP tools (pty_spawn / pty_write / pty_read / pty_wait / pty_list / pty_kill) instead of the blocking Bash tool. Use this skill for any command that doesn't just run once and return — anything that stays alive, runs in the background, waits for typed input, or should notify when it finishes. Trigger it when the user wants to: start a dev server (vite, npm run dev, flask, cargo run) and keep working; run a file-watcher that reruns on save (cargo watch, jest --watch, tsc -w, nodemon); kick off a slow build or test suite and be pinged the moment it exits while doing other work; tail or monitor logs in the background and flag matching lines; or drive an interactive session that prompts for input (ssh, psql, REPLs, sudo password). Also reach for it when a command "froze the terminal" because it never exits, or the user says "keep it running", "in the background", "let me know when it's done", "watch the tests", "tail the logs", or 4---56# Voxcaster — persistent interactive PTY sessions78Voxcaster gives you **real pseudo-terminal sessions** that outlive a single tool9call. Unlike the `Bash` tool — which blocks for the lifetime of the command,10gives no interactive stdin, and offers no TTY — a Voxcaster session keeps running11in the background. You spawn it, then read its output, send it input, wait for it12to finish, or kill it, across many turns.1314## When to reach for Voxcaster (and when not to)1516Use Voxcaster when a process **lives over time**:1718- Dev servers (`npm run dev`, `cargo run`, `flask run`, `vite`) you want up while you keep working19- Watchers (`cargo watch`, `jest --watch`, `tsc -w`, `nodemon`)20- Long builds/tests where you want to do other things and be told when they exit21- Interactive programs that need stdin: REPLs (`python`, `node`, `irb`), `ssh`, `psql`, `gdb`, anything with a prompt22- Anything that emits a TTY-only experience (progress bars, colored output, paging)23- Any command you'd otherwise background with `&` and then poll with `sleep` + re-check — Voxcaster replaces that anti-pattern2425Keep using the plain `Bash` tool for **quick, one-shot** commands that return26promptly (`ls`, `git status`, `grep`, a fast build). Voxcaster's value is27persistence and interactivity; don't add its overhead to a command that finishes28in a second.2930## The tools3132| Tool | Use it for |33|------|-----------|34| `pty_spawn` | Start a process. `command` + `args` array (never a shell string). Returns a `pty_<id>`. |35| `pty_read` | Read the scrollback (`offset`/`limit` by line, `pattern` to glob-filter, `raw` for ANSI, `format:"json"` to parse). |36| `pty_write` | Send stdin to a running session (answer a prompt, type a REPL command — include `\n`). |37| `pty_wait` | Block until the process exits or `timeout_seconds` elapses; returns exit code. The guaranteed completion path. |38| `pty_list` | Enumerate sessions and their status (`running` / `exited` / `killed`). |39| `pty_kill` | Stop a session (`cleanup:true` also frees its buffer). |4041Every tool takes an optional `format: "text" | "json"`. Use `json` when you want42to parse fields (exit code, line counts, status) reliably; `text` is the readable43default.4445## Core patterns4647**Start something and keep working.** Spawn it, then move on. Read its output48later with `pty_read` when you need to check progress.4950```51pty_spawn(command="npm", args=["run","dev"], title="dev server")52# ... do other work ...53pty_read(id="pty_…", pattern="*Local:*") # find the URL it printed54```5556**Run a job and learn when it's done — hands-free.** Set `notify_on_exit: true`.57If the session was launched with Claude Code channels enabled (e.g. via the58`verity` launcher), Voxcaster pushes a `<channel source="voxcaster" exit_code=…>`59event into your session **when the process exits**, so you react without polling.60When channels aren't active, this is silently skipped — so for guaranteed61completion, use `pty_wait`.6263```64pty_spawn(command="cargo", args=["test"], notify_on_exit=true, title="tests")65# either wait for the <channel> exit event, OR:66pty_wait(id="pty_…", timeout_seconds=300)67```6869**Never sleep-and-poll.** If you want to block on completion, call `pty_wait` —70don't spawn then loop `sleep` + `pty_read`. `pty_wait` returns the moment the71process exits (or your timeout hits).7273**Interactive input.** Drive a REPL or answer a prompt with `pty_write` (remember74the newline), then `pty_read` the response.7576```77pty_spawn(command="python", args=["-i"], title="repl")78pty_write(id="pty_…", data="import sys; print(sys.version)\n")79pty_read(id="pty_…", limit=5)80```8182**Find something in noisy output.** `pty_read(pattern="*error*")` glob-filters the83scrollback instead of returning thousands of lines.8485## What to know8687- **argv only, no shell string.** `pty_spawn` takes a `command` + `args` array.88 To use shell features (pipes, `&&`, redirection) run them *through* a shell:89 `command="bash", args=["-c","make && ./run"]` (or `cmd /C …` on Windows).90- **Command policy.** A built-in floor always blocks catastrophic commands91 (recursive root deletion, disk formatting, pipe-to-shell, fork bombs, etc.). If92 a spawn is denied "by policy", that's the floor or the operator's93 `VOXCASTER_POLICY` — don't try to route around it; tell the user.94- **Session-bound lifetime.** Sessions live as long as the Voxcaster server (the95 current agent session). They're reaped on shutdown — they do **not** survive a96 session restart. Don't assume a `pty_…` id from a previous session still exists;97 `pty_list` to confirm.98- **Line-addressed output.** Reads are by line (`offset`/`limit`), ANSI is99 stripped by default (`raw:true` to keep it).100- **Spawn latency.** On some Windows hosts a PTY spawn can take a couple of101 seconds to initialize — give `pty_wait` a sensible timeout and don't assume an102 instant exit.103104## Quick decision rule105106If you're about to run something with the Bash tool and you think "this will keep107running" or "I'll need to send it input" or "I want to be told when it finishes"108— stop and use `pty_spawn` instead.