Skip to content

Embedded terminal and agent sessions

Switchyard owns interactive PTYs in the Go control plane. The browser is a bounded input/output adapter; Vue, xterm.js, HTTP, and WebSocket handlers do not resolve commands or own process lifetime.

browser xterm.js
| authenticated same-origin WebSocket
| binary input/output + JSON resize
v
terminal application service
| owner check, scrollback, lifecycle, audit
+--> trusted launch resolver --> catalog/actions/environments
+--> Unix PTY adapter --------> owned process group
+--> SQLite repository -------> metadata only

Typed launch boundary

The public create contract has no command field. A request selects exactly one reviewed capability:

Kind Resolution
shell sh, bash, or zsh login shell at the trusted checkout root
service docker compose exec for a service declared by the accepted manifest
database one of psql, mysql, redis-cli, mongosh, or sqlite3 in a declared service
agent configured Codex or Claude Code executable at the selected checkout
action an accepted manifest action explicitly classified interactive

Compose files are containment-checked against the selected project or worktree root. Non-shell actions preserve argument arrays. Shell actions must be explicit, contain one reviewed string, and cannot request sudo or doas. No generic terminal, SQL, Docker, or shell tool is exposed through MCP.

Ownership and transport

Every session is owned by the authenticated local principal that created it: the browser session ID, privileged IPC principal, or scoped agent identity. List, get, attach, and terminate operations enforce that owner. Browser PTY WebSockets require the session cookie and an exact same-origin Origin before the application attach is attempted.

Server frames are JSON text control messages (ready and exit) or binary PTY bytes. Client frames are binary PTY input or one bounded JSON resize message. Input frames are limited to 64 KiB, dimensions to 500 by 300, and subscriber queues are non-blocking. A slow browser is detached rather than allowed to block the PTY or grow memory without bound.

Persistence semantics

Closing a tab, navigating away, losing connectivity, or reloading detaches the browser. It does not terminate the process. An unattached session remains live for 30 minutes and can be reconnected from the same authenticated browser session. Explicit termination, command exit, idle expiry, or daemon shutdown ends the PTY process group. PTYs cannot resume after daemon restart; previously active metadata is marked interrupted.

Each live session retains at most 1 MiB of reconnect scrollback in daemon memory. SQLite stores only owner, project/worktree, kind/provider/target, working directory, status, timestamps, byte count, truncation flag, exit code, and redaction-safe lifecycle audit facts. It never stores input, output, commands, arguments, or environment values.

Browser safety and accessibility

xterm.js uses a 2,000-line presentation scrollback inside the 1 MiB server bound. Plain and OSC 8 links accept only HTTP(S), require Command or Control, and open with noopener,noreferrer. Screen-reader mode is enabled, keyboard focus remains inside the terminal when attached, and resize follows a debounced ResizeObserver. Loading, empty, detached, ended, error, and slow-consumer states are explicit.

Agent metadata boundary

An agent session is a terminal session with provider metadata. Switchyard records provider, project, checkout/worktree, working directory, lifecycle, visible-output byte count, and reconnect-buffer truncation. The capture policy is literally user_visible_terminal_output_only: Switchyard neither requests nor claims access to hidden reasoning or provider-private state. Rich provider SDK orchestration remains outside this reliable PTY boundary.

Use this pageLast verified Jul 17, 2026
Copy-ready MarkdownOpen sourceReport a docs issue