Session control & the CLI
Termio’s orchestration API — status hooks that let agents report what they’re doing, and the termio sessions CLI that lets you (or another agent) spawn, drive, and supervise sessions from the shell.
Two related, opt-in features sit behind the live statuses you see in the sidebar:
status hooks that let agents report what they’re doing, and the termio
command-line tool that lets you — or another agent — drive sessions from the
shell. Neither is required to use Termio; both make a fleet of agents easier to
run.
Live agent status
Termio can install small status hooks into your agents’ own config files (Claude Code, Codex, Cursor, and the plugin-based agents). When an agent starts working, finishes, or stops to ask a question, the hook reports that to Termio, so the sidebar’s status dots reflect exactly what’s happening instead of being guessed from output.
Turn it on from Settings ▸ Agents ▸ Live agent status (Termio also offers it once on first launch). Toggling it off removes the hooks again, leaving any hooks you added yourself untouched.
The switch says whether you want the feature; each machine installs it for its own agents. Open a machine under Settings ▸ Machines and use Set up this device — or Reinstall hooks — to put the hooks on that box. That is what gives an agent running on a VPS the same status dots as one running here.
Session control
Settings ▸ Agents ▸ Session control turns on the termio sessions
orchestration API below, and teaches your agents it exists by installing a
termio agent skill into each agent’s skills folder (~/.claude/skills,
~/.codex/skills). The skill loads on demand — an agent carries only its
one-line description until a task actually involves driving sibling sessions —
and Termio re-asserts it on every launch, so app updates propagate and
hand-edits heal automatically. Toggling it off removes the skill again.

Running an agent Termio doesn’t auto-configure? The same skill is published at termio.sh/skill.md and installable straight from the repo:
npx skills add termio-sh/termio --skill termioBoth toggles only write files on your Mac — hook entries in agent configs and the skill file. Nothing leaves your machine: status reports and session commands travel over a local socket to the Termio app, and nowhere else.
The termio command-line tool
Turn on Settings ▸ Machines ▸ This Mac ▸ Command-line tool. It symlinks a termio
command onto your PATH (at /usr/local/bin/termio; macOS asks for permission
once), and turning it off removes the link again.
Open a project
termio # open the current directory as a project
termio ~/code/myapp # open a specific folderRun it in any directory and Termio brings that folder into the sidebar — the shell equivalent of Open Project.
The orchestration model
The termio sessions family is Termio’s orchestration API: it lets one agent —
or your own scripts — see and steer sibling sessions in the same project. Before
the verb reference, the design rules everything below follows:
- Project-scoped by default. Every command resolves the caller to its own project (via the session id the PTY carries, or the working directory) and can only see and drive siblings there — never sessions in unrelated projects.
- One address, one target. Sessions are addressed by
termio://session/<uuid>deep links minted at creation and printed bylist(a bare id or unique id-prefix works too). The link names the pane, never its mutable contents, so a copied address survives the session promoting, demoting, or being renamed — and stays self-describing when pasted anywhere. - Transcript as truth. An agent’s result is read from its own structured
transcript (the path and line range the replies hand back), never scraped off
the screen. The screen is the result channel only for plain
runterminals, which have no transcript. - Waiting is explicit. No command blocks unless you pass
--wait, and--waitalways means the same thing: wait for the turn’s outcome, fail fast when no outcome can come, and split the result across exit codes. - Signal, never kill. The supervision plane observes and reports —
watchevents, thestalledalarm — but never terminates or auto-answers a session. Acting on a signal is always the supervisor’s decision.
Drive sessions
Add --json to any command for machine-readable output.
| Command | What it does |
|---|---|
termio sessions list | List the sessions in this project with their live status. |
termio sessions watch | Block and stream one line per status change — the push alternative to polling list. |
termio sessions spawn "<prompt>" | Start a new agent session on the prompt; replies immediately with its session link. |
termio sessions run "<command>" | Start a new plain terminal session typing that shell command — a dev server, a test run — in a visible pane, no LLM. |
termio sessions send <link> "<text>" | Type text into an existing session and submit it with a real Return keypress — a prompt to drive it, or a menu choice ("1", "yes") to answer a permission prompt. |
termio sessions read <link> | Print the session’s current screen without focusing it (--lines N keeps the tail) — the result channel for run sessions. |
termio sessions close <link> | Close one or more session tabs. |
termio sessions focus <link> | Bring a session to the front in the app. |
<link> is the termio://session/<uuid> address list prints (a bare id,
unique id-prefix, or session title also works). Commands are scoped to the
current project automatically, and termio sessions <verb> --help prints
focused help for any one verb.

answer is a deprecated alias of send (meaningful for agent sessions only), and send with no target behaves like
spawn — both kept so existing scripts keep working. Prefer spawn and send.
Spawning without waiting
spawn returns the moment the session exists: the reply carries the new
session’s link, and the prompt itself is typed in once the agent finishes
booting. Add
--agent <id> (e.g. codex, grok, pi) to choose the agent; it defaults to
the caller’s own kind.
Running plain commands
Not everything in a fleet is an agent. run starts a terminal session on a
shell command — a dev server, a test watcher, a build — in a visible pane you
can watch and take over, and read prints any session’s current screen
without focusing it. A plain command has no transcript, so its screen is its
result channel; agent results keep flowing through transcripts.
termio sessions run "pnpm test"
termio sessions read 1a2b3c4d --lines 40Waiting for the outcome: --wait
Waiting is always explicit, and it means the same thing everywhere: --wait
waits for the outcome of the turn, never for plumbing. Both send and spawn
accept it (with an optional --timeout <ms>, default 300000, clamped
1000–600000):
termio sessions send termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44 "run the tests" --wait
termio sessions spawn "summarize the failing CI job" --wait --timeout 120000The call blocks until the turn settles — the session was seen working and has
rested off it — and the reply carries the final status, the transcript path,
and the cursor..cursor_end line range the response landed in, so the caller
reads exactly the new content with its own file tools. Two special cases:
- A session that stops to ask something short-circuits the wait immediately:
the reply is
status: "needs-you"with the on-screen question inprompt— answer it with anothersend. - A plain terminal with no status signal falls back to screen settling: the screen changed after the send, then went still.
Waits also fail fast instead of burning the timeout when no outcome can come:
- A prompt that shows no effect within 5 seconds — no status move, no screen
change — errors as
prompt_stalled: the input was eaten (an agent still booting, a program that ignores typed text), and no turn is coming. - A session that closes mid-wait errors as
session_closed; an agent that exits back to its shell mid-wait errors asagent_gone.
On timeout the reply still comes back (timed_out: true) with whatever the
current status is; the session keeps running. The exit code splits the three
outcomes so scripts can branch without parsing: 0 settled, 1 error
(including stalled/vanished), 3 timed out.
Supervising with watch
watch is built so an agent can supervise a fleet without polling:
- Snapshot on attach. It first prints one line per session with its
current status (tagged
"snapshot":truein--json), so a supervisor attaching late still learns a session is already waiting on input. Skip it with--no-snapshot. - Filtered by default. Live events default to the two states a supervisor
acts on,
doneandneeds-you; widen with--state working,idle,done,needs-you. - Stall alarm, opt-in.
--state stalledadds a watch-plane signal for the unattended runaway: a session stillworkingthat has made no repo change and next-to-no transcript growth for 20+ minutes. Sustained output — a long build streaming logs — suppresses it. The event carries the detector’s reasoning inevidence("working 42m, no repo change, transcript +3 lines"), fires once per quiet stretch, and re-arms when progress resumes. Termio only signals; it never kills — the session’s real status staysworkingand the sidebar is untouched. Not in the default filter. - Actionable events. A
needs-youevent carries the on-screen question inprompt; adoneevent carries the session’stranscriptpath andcursor_end— enough to answer, or to read the result, without another round-trip. - Heartbeat. In
--jsonmode the app writes{"heartbeat":true}after 30 seconds of silence, so a quiet stream is distinguishable from a dead one. - Exit codes.
0after Ctrl-C (the normal end of supervision),2when the stream closed from the app side — “I chose to stop” and “Termio went away” are different outcomes.
Spawned sessions know their caller
When one Termio session spawns another, the delivered prompt opens with a short
provenance envelope: it names the caller’s link and allows exactly one
back-channel — a mid-task question, or a one-line completion ping, via
termio sessions send <caller-link> …. Nothing else travels that way: no
conversation, no delegating tasks back. Results stay transcript-as-truth — the
supervisor reads the worker’s transcript, with watch as the backstop. A
spawn from a plain shell (outside any Termio session) gets no envelope.
Fail loud
The CLI is built for callers that trust exit codes, not prose. Every one-shot
command exits 1 on an error or an empty reply, and gives up after a
15-second client timeout rather than hanging on a busy app (override with the
TERMIO_CLI_TIMEOUT environment variable; a --wait call widens the client
timeout automatically to outlive the server-side wait).
JSON contract
Agents code against --json output, so its shapes are pinned, not
reverse-engineered. Every JSON reply carries "schema_version": 1; fields may
be added within a version, never renamed or removed. Optional fields are
omitted when unknown (never emitted as "" or null).
Every error, on any verb, has one shape — and the exit code is nonzero:
{"ok": false, "error": "<code>", "message": "<human-readable next step>", "schema_version": 1}The stable codes: disabled, no_scope, bad_op, bad_request, no_text,
no_target, not_found, ambiguous, wrong_agent, bad_agent, no_agent,
start_failed, not_live — plus, on --wait only, prompt_stalled,
session_closed, and agent_gone.
Success replies, per verb (schema_version and "ok": true omitted below for
brevity):
| Verb | Reply fields |
|---|---|
list | project, sessions: [{link, id, title, agent, status, description, transcript?}] — link is the canonical address, id its 8-char short form |
spawn / run | target, title, created: true, queued: true — the payload is still being delivered |
send | target, title, transcript?, cursor? — read the response from cursor onward |
read | target, title, screen — the current viewport, right-trimmed, trailing blank rows dropped |
send/spawn/run --wait | target, title, status, timed_out, created?, transcript?, cursor?, cursor_end?, prompt? |
close | closed, title |
focus | focused |
The two wait verbs share one reply shape (--wait means the same thing
everywhere): the final status, and the transcript line range
cursor..cursor_end holding the response. prompt appears only on a
needs-you outcome — the question the session is showing on screen.
watch streams newline-delimited JSON events rather than one reply:
{"schema_version": 1, "link": "termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44",
"status": "done", "title": "…", "cwd": "…", "snapshot": true, "prompt": "…",
"transcript": "…", "cursor_end": 42}cwd is omitted until the shell reports one; snapshot marks the on-attach
roster lines; prompt rides on needs-you events, transcript +
cursor_end on done events (exactly as in wait replies), and evidence on
stalled events — the stall detector’s reasoning, e.g.
"working 42m, no repo change, transcript +3 lines". stalled appears only
on the watch stream, never as a session’s status in list or wait replies.
After 30 seconds of silence the app writes {"heartbeat": true}.
This is what lets one agent hand work to another — spawn a task in a sibling
session, then watch (or poll list) until it reports done and read the
result. Watching several at once, termio sessions watch prints the link the
moment any of them turns done or needs-you — one agent supervising a
fleet, all on your own machine.
Git worktrees
Termio reads your repository’s worktrees straight from git and shows them as a nested branch under the project, so parallel agent work stays isolated.
Appearance
Set your terminal theme and font, tune the cursor and window, and find the rest of Termio’s settings — all in a familiar macOS settings window.