Every session, every machine
Every machine you reach keeps its own sessions. alto ls --all asks them all at once and lists every session in one place, each named by its machine and id.
alto ls --allstudio, this one (2) studio:3 working claude sanford/alto ~/dev/alto 2h studio:7 idle shell - ~ 5mwin (2) win:2 needs-you claude sanford/alto C:\dev\alto 40m win:4 done cargo sanford/web C:\dev\web 1hgpu-1, not answering (No answer. It may be asleep or offline); last seen 12m ago (1) gpu-1:1 working? codex sanford/train /home/agent/train 3dEach line is a session’s address, what it’s doing, its agent or program, the git repository its folder is in (named by its remote, as owner/name), its folder and how long it has run.
A machine that doesn’t answer doesn’t hold up the others. It keeps the sessions it listed last, marked as last seen and with a ? after their state, because they may have moved on since.
In the window: the switcher
Section titled “In the window: the switcher”⌘P (Ctrl⇧Space on Linux and Windows), or Shell → Go to Session…, brings up the same list over the tab you’re in: every running session on every machine, those needing you first, then the most recent; then a new tab on each machine, and in each pool.
Typing narrows it. Each word must match something shown (the agent or program, the tab’s title, the machine, the folder, the repository), or be a selector word as for --in below: win claude, repo:alto, status:needs-you os:windows.
| Keys | Does |
|---|---|
| ↑ ↓, Page Up Page Down | Move through the list |
| Enter | Go to the session’s tab, or open it in a new tab |
| ⌘Enter | Open it beside the pane you’re in, as a split |
| Esc | Close the list |
The mouse works too: the pointer marks a row, a click opens it (⌘-click as a split), and the wheel scrolls.
It’s drawn in the terminal’s own font and size, and it never waits on the network: the window keeps every machine’s sessions as last asked (every 5 seconds, every 2 while the list is open). A machine that isn’t answering keeps its sessions in the list, dimmed, “last seen 4m ago”.
⌘⇧J (Ctrl⇧J) goes straight to the next session needing you, wherever it is; again for the one after.
In the menu beside +, each machine’s › lists its running sessions to open, as well as its profiles.
The fleet view
Section titled “The fleet view”alto fleet on a terminal opens a full-screen view of your whole fleet, to look over and act on: in groups that fold, filtered as you type, several at once. ⌘⇧L (Ctrl⇧L) opens it in a tab of its own; it runs the same over ssh or in any other terminal. Piped, or with --list or --json, alto fleet prints the counts instead.
It has six tabs, along the top with their counts; 1 to 6 or Tab move between them, and alto fleet --tab NAME opens on one:
- Overview, where it opens: a box for each of what needs you (the agents asking, and what they ask), anything wrong with its fix (a machine not answering, one on an older build, remote access off), your fleet (every session in one bar by state, each machine with its sessions as a mark apiece, the pools and the devices with access) and what happened lately. Enter on a line goes there: a machine opens its sessions.
- Needs you: every session waiting for a person, oldest first, with what it asks. Answer from the list: . then a allows (the agent’s highlighted choice), x denies, for one or every one selected.
- Sessions: everything running, grouped by machine to start with. A working agent that says how far along it is shows a bar.
- Machines: each with its state, its sessions, how full it is (where it takes only so many), its system and its build (an older one marked ↑).
- Devices: every device with access to this one, grouped by how it gets in; block, let back in or forget them, or show a code to pair someone else’s.
- Activity: what happened on every machine, newest first.
How the fleet is doing shows at a glance in the same marks everywhere: ● needs you in yellow, ○ working in cyan (it breathes while the agent works), ✓ done in green, · idle, and a machine not answering in red. Beside a session’s state, ⏸ says agents can’t type there (paused, or waiting for someone to watch) and ◉ with a number says how many are attached to it. Each list sits in a box with how it’s grouped and the filter on its top edge and how many it shows on its bottom; each group’s heading counts its sessions, and they hang from it on a line, so it’s plain which machine each is on. A machine not answering keeps what it last said, its rows dimmed and its heading saying when it was last seen. The keys that matter just then are along the bottom.
The row you’re on, and what’s selected (marked ▍ on the left), are shaded from your terminal’s own colors, so the view suits any theme.
Everything works by keyboard and by mouse:
| To | Keyboard | Mouse |
|---|---|---|
| Move | ↑ ↓ (and j k), Page Up Page Down, Home End | the wheel; the pointer shades the row under it |
| Add or remove one | Space | Ctrl-click (⌘-click in Alto’s tabs) |
| Select a range | Shift + ↑ ↓ | Shift-click, or drag down the rows |
| Select a group | Space on its heading | click the heading |
| Select all shown | Ctrl-A | |
| Clear the selection, then the filter | Esc | click empty space |
| Fold or unfold a group | ← → | click its ▸ ▾ |
| Open | Enter | double-click |
| Actions | ., then a letter, or ↑ ↓ and Enter | right-click |
| Filter | type, or / | click the filter |
| Another tab | 1 to 6, Tab, Shift-Tab | click its name |
| Group by | g, then a letter, or ↑ ↓ and Enter | click “Group by” |
| Every key | ? |
Typing anything that isn’t one of the list’s own keys (j k g y q . ? Space) goes to the filter, which then has every key until Enter, Esc, ↑ or ↓. The filter takes the same words as the switcher: win claude, status:idle, repo:alto. Typing on the Overview filters Sessions, so the view is the switcher too.
Opening a session attaches to it in place; detaching (Ctrl-</kbd>) comes back to the list. The actions: for sessions, open, allow or deny what they ask, fetch their branches, pause agents’ typing in them and resume it (as alto pause and resume), and end them; for a machine, a new session there, and for this one, drain and undrain; for devices, block, allow and forget, and pairing; and copying the addresses of what’s selected (y). Ending sessions asks first: y ends them; Enter or Esc keeps them running. The view doesn’t list its own session.
One address for every session
Section titled “One address for every session”A session is machine:id wherever you name one:
alto attach win:2alto send -s win:2 "yes" --key Enteralto capture gpu-1:1alto run build-2:3 -- cargo testalto kill studio:7alto fetch win:2 # its branch, if it was started with --repowin:2 means the same as --on win 2. The machine is its Tailscale name, or the name it was paired as.
alto attach with no id, outside a session, offers every machine’s running sessions to pick from, newest first, with “New session” at the top.
Narrowing it down
Section titled “Narrowing it down”--in takes words about the machine and about the session, all of which must match:
alto ls --in 'agent:claude os:windows' # Claude Code on Windows machinesalto ls --in 'status:needs-you' # everything waiting on youalto ls --in 'repo:alto' # every session in that repository, whereveralto ls --in 'pool:gpu status:working'alto ls --in 'by:agent' # what agents started, rather than peoplealto ls --in 'watched:no' # what nobody is attached to| Word | Matches |
|---|---|
status:STATE |
working, needs-you, done, idle or ended |
agent:NAME |
the agent a session runs (claude, codex), or else its program (vim, cargo; shell for the login shell) |
repo:NAME |
the repository’s whole name (sanford/alto) or its last part (alto) |
by:WHO |
who started it: agent (any agent), person, or a name: an agent’s (claude), the account it was started from here, a Tailscale login (sam@example.com, or just sam), or the device it was asked from. Sessions from before this was kept match none. |
watched:WHO |
who’s attached now: yes (anyone), no (nobody), person, agent, or a mode (observer, peer, moderator). Sessions from before this was said match none. |
os:, arch:, has:, tag:, kind:, gpu, pool:, a name |
the session’s machine, as in Putting work where it fits |
Grouping
Section titled “Grouping”alto ls --all --group-by repoalto ls --all --group-by agentalto ls --in 'has:claude' --group-by osGroup by machine (the default), pool, kind, os, tag, repo, agent, status or none. A machine with several tags, or in several pools, puts its sessions in each.
--count counts them instead of listing them: per machine, or by any of the same keys.
alto ls --count # per machine: 5 sessions: 2 working, 1 needs you, 1 donealto ls --count --group-by agent # claude 3 sessions: 3 workingFor scripts
Section titled “For scripts”--json on any of these gives:
machines: each with its state (online,not answering,drainedorfull), its kind and what it says about itself, and when it was last seen if it isn’t answering;sessions: each with itsaddress,machine,state,agent,repo,folderandlast_seen, and the whole session asls --jsongives it;groups: each group’s name, count and sessions’ addresses.
alto ls --in 'status:needs-you' --json | jq -r '.sessions[].address'Machines
Section titled “Machines”alto devices says what each machine is: OS and version, chip, CPUs, memory, GPUs, whether it’s a VM, a container or a phone, the agents and tools a session there can run, its build, and how fast it answered. alto ls --waiting lists only what needs you, on every machine, newest first.