Scripting sessions
Alto’s commands start, read, type into, wait for and end sessions from a script or an agent: real sessions you can also see in any terminal, the app or your phone.
id=$(alto new -- npm run dev) # start one; prints its idalto wait "$id" --text 'ready on port \d+' --timeout 60alto capture "$id" --last 20 # the last lines, as plain textalto send -s "$id" --key C-c # press keysalto kill "$id"Waiting
Section titled “Waiting”alto wait 3 --until needs-you # idle, working, needs-you, done, or promptalto wait 3 --text 'error|panic' --timeout 2mExits 0 when it happens (printing what it found), 124 after --timeout, 125 if the session ends. --text is a regular expression over what the program prints from now on; for what’s already on screen, use capture --screen.
Running a command and getting its result
Section titled “Running a command and getting its result”alto run 3 -- cargo test # prints only its output, exits with its statusalto run 3 --json -- make lint # {output, exit, truncated, took, timed_out}run types the command at the session’s prompt, where it shows, and waits for it by the shell’s own marks. See What run gives back.
Typing
Section titled “Typing”alto send -s 3 'git status' --enteralto send -s 3 --key Escape # leave insert modealto send -s 3 ':wq' --enterKeys go by name: Enter, Escape, Tab, Up, PageDown, F5, q, with C-, M- or S- before them (C-c, M-x, S-Tab). Repeat --key for several, pressed in order.
On another computer
Section titled “On another computer”Name a session on another machine as machine:id, with any command that takes one; --on MACHINE does the same, and is how new and ls are pointed there:
alto --on studio new -- ./train.shalto capture studio:4 --last 50alto run win:2 -- cargo build # PowerShell on Windows gives run its exit status tooalto new --in 'os:linux gpu' -- ./train.sh # or let Alto pick the machineSee Every session, every machine and Remote access with Tailscale.
Following what happens
Section titled “Following what happens”alto events # a line each, as it happens, until stoppedalto events --json --type needs-you # {"v":1,"type","ts","device","text","session","address"}alto events --all # every device you reachalto events -s studio:4 --since 10m --once # what happened in one session lately, then stopSessions starting, ending, needing you and done; devices connecting, pairing, being forgotten or blocked. Each device keeps its last thousand. With --json, a {"type":"stream.ready"} line comes first. Lines capture gives back are whole, however the terminal wrapped them.
Asking a session what it offers
Section titled “Asking a session what it offers”alto describe 3 # its kind (agent, shell or job), its state, and its methodsalto describe studio:3 --json # each method with what it takes and gives, as JSON Schemaalto call 3 capture --args '{"last":20}'alto call studio:3 run --args '{"command":"cargo test","timeout":"10m"}' --jsonecho '{"text":"y","enter":true}' | alto call 3 send --args -Every session has the methods the session commands are: send, capture, wait, run, pause, resume and kill, each taking what its command takes, by the same names, and running the same code. Arguments are checked before anything happens, so a call refused changes nothing. With --json, a call prints {"ok": …} or {"error": {"code", "message"}}; the codes are no-such-method, bad-args, paused, timed-out, gone and failed. It exits 2 for no such method or wrong arguments, 124 when it timed out, 125 when it couldn’t, and run with its command’s status.
Answering an agent without its screen
Section titled “Answering an agent without its screen”When an agent asks something (a permission prompt, a question), describe shows it as data, in its state’s asking: an id, the kind (permission, question, auth), the words, and the choices. Answer it from anywhere:
alto answer 3 allow # the question it's asking nowalto answer studio:3 deny --message "not on main"alto call 3 answer --args '{"question":"e129c8b095c2d9a9","choice":"SQLite"}'An answer names the question it’s for, and is refused (stale, exit 125) once the agent is asking something else, so it never lands on the wrong one. Claude Code’s prompt stays usable at the terminal while it waits: answer it either place, and the first answer wins. Codex’s answer is typed at its prompt (y, or Escape for deny), so it waits while a person types there, and is refused while agents are paused there. The text of a question is the agent’s: shown, never typed anywhere.
Messages to an agent
Section titled “Messages to an agent”An agent’s session (kind agent) also has message: words for the agent, typed at its prompt once it’s idle, in turn, marked with who sent them and how to reply. The agent replies with alto reply CALL from its own session, and the reply comes back as data:
alto call studio:3 message --args '{"text":"how is the migration going?"}' --wait --timeout 10malto call studio:3 message --args '{"text":"tests pass?","reply":"{\"type\":\"object\",\"properties\":{\"pass\":{\"type\":\"boolean\"}},\"required\":[\"pass\"]}"}' --wait --json# {"ok":{"call":"c1a2b3c4","state":"replied","reply":{"pass":true}}}reply, a schema in the same subset written as JSON, is the shape the reply must have; a reply that isn’t is refused, and so is one from any session but the one asked. Without --wait, the call’s id comes back and the message waits its turn. --wait keeps asking until the reply comes, through a link that drops and comes back, and exits 124 after --timeout or 125 if the session ends first. Messages are kept by the agent’s device, so its service restarting loses none. Each message, delivery and reply is an event (alto events: message, delivered, replied).
Offering a method of your own
Section titled “Offering a method of your own”A program in a session can offer methods of its own, which describe lists and call calls, from anywhere:
alto offer progress --about "how far the build is" \ --takes '{"type":"object","properties":{"detail":{"type":"boolean"}}}' \ -- ./progress.sh &alto call studio:3 progress --args '{"detail":true}'Each call runs the handler with the arguments as JSON on its standard input, and its standard output, JSON, is the reply. It runs as you, where you ran alto offer; the session itself runs nothing. A handler that takes longer than --limit (20 seconds) is stopped and the call fails as timed out. The offer lasts until the process that started it ends, or with --keep, until the session does; built-in names and names offered already are refused, and schemas are the same small subset describe shows. A caller needs send on the session to call it, unless the offer asks for less or more: --needs read for a method that only tells (status, progress), --needs approve for one that answers for the person (see Access). When the session moves to a new build (alto reload), the offer is made again there by itself.
A program can also say what it is and how it’s getting on, which describe, alto report and ls --json show:
alto report --kind build --set stage=tests --set target=x86alto report --set target= # an empty value removes it--kind is one word (build, server) in place of what the session would say (agent, shell or job); --set keeps up to 32 values of 256 characters each. They go when the program does: the shell back at its prompt, or an agent saying it ended. The same tools are in alto mcp as describe and call (The MCP server).
Every command that shows something takes --json: ls (with --all, --waiting or --group-by too), report, run, describe, call, events, work, workspace, devices, service, settings, hooks and fonts. Exit statuses are fixed and listed in alto --help: service status exits 3 while the service isn’t running, run and wait 124 on --timeout and 125 when they couldn’t.
A separate set of sessions
Section titled “A separate set of sessions”ALTO_HOME=/some/folder keeps a separate service, sessions and settings in that folder, for trying things without touching your own:
export ALTO_HOME=$(mktemp -d)alto new -- sleep 600alto ls