Lookout
shep lookout (alias dash) is a live terminal dashboard over the shepherd. It reads the flock two ways at once: a subscription to the event bus so the screen moves as things happen, and a re-list every two seconds so a dropped event can't leave it quietly wrong.
Lookout needs a real terminal. With stdout redirected (piped to a file or another program) it refuses outright rather than writing escape sequences somewhere nobody will read them.
Start it
$ shep lookout $ shep lookout --allow-control
--allow-control opens the action gate, covered below. It's off by default.
The flock table
Every frame below is real: rendered headlessly through ratatui'sTestBackend by a pinned snapshot test, not mocked up for this page. A > gutter to the left of ID marks the selected sheep. j/k move the selection one row at a time; g/G jump to the first and last row. Everything below the table describes whichever sheep is selected.
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M up 6d 3h ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge 1 web online 48212 0 2.9% 178.0M 1h 26m edge > 2 api online 48219 1 7.1% 241.0M 1h 28m edge 3 billing-reconciliation-worker online 48230 0 0.8% 96.0M 1h 29m - 4 cron online 48233 0 0.1% 8.0M 1h 31m - 5 metrics online 48240 0 0.4% 11.0M 1h 32m - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── sheep 2 api online pid 48219 restarts 1 uptime 1h 28m cpu 7.1% mem 241.0M fold edge out /home/ada/.shep/logs/api-2-out.log err /home/ada/.shep/logs/api-2-err.log ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── bleats api … 1 earlier line not shown out GET /healthz 200 3ms out GET /v1/orders 200 44ms out POST /v1/orders 201 88ms out GET /v1/orders/8821 200 9ms out connection pool: 14/50 in use q quit j/k select g/G first/last r refresh read-only
Multi-instance apps
An app with more than one instance gets a group row above its slots, the same rollup shep flock shows: web ×3 in NAME, STATUS as the shared word or a count per state when the instances disagree, RESTARTS/CPU/MEM summed and UPTIME the minimum across them. The group row is selectable with j/k like any other row, and the cursor survives the two-second poll on either kind of row.
An action key on a selected group row is the one place in lookout a single keypress reaches more than one process, so the confirm states how many it's about to touch:
stop all 3 instances of web? enter confirms, any other key cancels
Enter sends the action against the whole app by name, not against one pinned id, and the detail pane on a group row shows the app-level summary rather than a single process's fields. The bleats feed can't follow a group, since there's no single log to re-read for it: it names the app and asks you to select one instance instead.
The three panes underneath
The host strip is the line above the table: load average, host memory, the flock's own summed CPU and memory, and how long the machine's been up. Below the table, the detail pane restates the selected row in full sentences and adds what the row can't fit: the untruncated name and both log paths.
The detail pane also shows the selected sheep's lambs: a count of the descendants Describe's process-table walk found, each one's pid and executable name, and how long ago that reading was taken. It's fetched once when the selection actually moves onto a running sheep, not on the table's own two-second listing, so the age stamp is real: hold the cursor on one sheep and the lamb list gets stale rather than silently refreshing behind a timestamp that still says "just now". A stopped sheep has no pid to walk from, and the pane says that plainly rather than reporting zero lambs.
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M up 6d 3h ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge 1 web online 48212 0 2.9% 178.0M 1h 26m edge > 2 api online 48219 1 7.1% 241.0M 1h 28m edge 3 billing-reconciliation-worker online 48230 0 0.8% 96.0M 1h 29m - 4 cron online 48233 0 0.1% 8.0M 1h 31m - 5 metrics online 48240 0 0.4% 11.0M 1h 32m - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── sheep 2 api online pid 48219 restarts 1 uptime 1h 28m cpu 7.1% mem 241.0M fold edge lambs 3 parent-pid descendants, read 9m 53s ago 48220 node 48221 node 48222 node out /home/ada/.shep/logs/api-2-out.log err /home/ada/.shep/logs/api-2-err.log ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── bleats api … 1 earlier line not shown out GET /healthz 200 3ms out GET /v1/orders 200 44ms out POST /v1/orders 201 88ms out GET /v1/orders/8821 200 9ms out connection pool: 14/50 in use q quit j/k select / filter x stop R restart L reload control enabled
The bleats feed is the one pane that isn't built from the flock listing: it re-reads the selected sheep's out/err log files from disk on every refresh, rather than subscribing to the bus. That's a deliberate cost: a busy flock pays one bounded 64 KiB read per file per refresh, instead of making the dashboard the highest-volume subscriber on the bus for a pane most refreshes don't even redraw. When it can't show everything, it says so instead of guessing. Lines it read and dropped are counted exactly, and bytes below its window are reported in bytes, because nothing counted the lines inside them:
bleats api … 525 earlier lines not shown, and 3.8M before them never read bleats api out then err from the log files, re-read with each listing this sheep has not written a log in this $SHEP_HOME
Filtering the table
/ opens a query box in the status bar; typing narrows the table to sheep whose name contains it, case-insensitively, as you type.Enter applies the query and closes the box, Escclears it back to the whole flock, and both leave the cursor on a real row: if the sheep it was on drops out of the narrowed set, the selection moves to whatever now sits in the same position rather than pointing at a row the table has stopped drawing. The title always carries the flock's true size beside the filtered count, so a narrow query can't be read as a small flock, and the host strip above the table keeps summing the whole flock too: its flock cpu/flock mem numbers in the frame below still total all six sheep, not just the two rows the filter leaves on screen, because the strip describes the machine, not the table's current view.
shep lookout /home/ada/.shep 2 of 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M … ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge > 1 web online 48212 0 2.9% 178.0M 1h 26m edge filter "web" / edit esc clear read-only
When the terminal is small
Panes drop before columns do, least-diagnostic-first: below 24 rows the detail pane goes (everything on it but the log paths already sits in the row above it). Below 18 the feed goes with it, and below 14 the host strip goes too, leaving only the flock table. Within the table, narrow widths drop columns in this order: FOLD, then RESTARTS and PID together, then MEM, then CPU, then UPTIME, leavingID NAME STATUS as the floor.
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12… ID NAME STATUS CPU UPTIME ─────────────────────────────────────────────────── > 0 web online 3.4% 1h 25m 1 web online 2.9% 1h 26m 2 api online 7.1% 1h 28m 3 billing… online 0.8% 1h 29m 4 cron online 0.1% 1h 31m 5 metrics online 0.4% 1h 32m q quit j/k select g/G first/last r… read-only
Below 33 columns or 6 rows, lookout refuses to draw rather than risk overlapping garbage. The refusal message is short enough to survive the terminal it's warning about:
too small need 33x6
If the shepherd goes away
Lookout re-dials five times (at 250/500/1000/2000/4000 ms, about 7.75 seconds total) before it gives up. Once the ladder is exhausted it shows a frozen banner, stops polling, and leaves the last known values on screen. The uptime column stops advancing with everything else: a dashboard whose clock kept ticking while the rest froze would be lying about a specific sheep by name. Lookout never exits on its own. You quit with q.
shep lookout /home/ada/.shep 6 in the flock the shepherd has died — these values are frozen as of 2026-08-14 14:32:07 host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 6.7% flock mem 371.0M up 6d 3h ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 15m edge 1 web online 48212 0 2.9% 178.0M 1h 16m edge > 2 api errored - 14 - - 1h 18m edge 3 billing-reconciliation-worker waiting-restart - 3 - - 1h 19m - 4 cron stopped - 0 - - 1h 21m - 5 metrics online 48240 0 0.4% 11.0M 1h 22m - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── sheep 2 api errored pid - restarts 14 uptime 1h 18m cpu - mem - fold edge out /home/ada/.shep/logs/api-2-out.log err /home/ada/.shep/logs/api-2-err.log ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── bleats api … 1 earlier line not shown out GET /healthz 200 3ms out GET /v1/orders 200 44ms out POST /v1/orders 201 88ms out GET /v1/orders/8821 200 9ms out connection pool: 14/50 in use q quit j/k select g/G first/last r refresh read-only
Actions
--allow-control (or shep set lookout.allow_control true) opens the gate; it's closed by default, and closed means every action key refuses with a plain sentence rather than silently doing nothing:
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M up 6d 3h ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge 1 web online 48212 0 2.9% 178.0M 1h 26m edge > 2 api online 48219 1 7.1% 241.0M 1h 28m edge 3 billing-reconciliation-worker online 48230 0 0.8% 96.0M 1h 29m - 4 cron online 48233 0 0.1% 8.0M 1h 31m - 5 metrics online 48240 0 0.4% 11.0M 1h 32m - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── sheep 2 api online pid 48219 restarts 1 uptime 1h 28m cpu 7.1% mem 241.0M fold edge out /home/ada/.shep/logs/api-2-out.log err /home/ada/.shep/logs/api-2-err.log ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── bleats api … 1 earlier line not shown out GET /healthz 200 3ms out GET /v1/orders 200 44ms out POST /v1/orders 201 88ms out GET /v1/orders/8821 200 9ms out connection pool: 14/50 in use read-only: actions need --allow-control read-only
With the gate open, three keys arm an action on the selected sheep:x stops it, R restarts it, Lreloads it. There's no start key; lookout only ever acts on a sheep already in the flock. Arming doesn't send anything by itself, it puts a question in the status bar naming the verb and the exact sheep:
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M … ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge 1 web online 48212 0 2.9% 178.0M 1h 26m edge > 2 api online 48219 1 7.1% 241.0M 1h 28m edge 3 billing-reconci… online 48230 0 0.8% 96.0M 1h 29m - 4 cron online 48233 0 0.1% 8.0M 1h 31m - 5 metrics online 48240 0 0.4% 11.0M 1h 32m - restart api (id 2)? enter confirms, any other key cancels control enabled
Enter confirms and sends; any other key cancels and does nothing else, so a stray j right after arming can't both dismiss the prompt and move the cursor. An armed prompt nobody answers expires after 10 seconds on its own. Once sent, the bar shows the request in flight until the shepherd answers, either with what it did:
shep lookout /home/ada/.shep 6 in the flock host load 2.31 4.10 3.88 / 10 cores host mem 12.4G / 32.0G flock cpu 14.7% flock mem 716.0M … ID NAME STATUS PID RESTARTS CPU MEM UPTIME FOLD ──────────────────────────────────────────────────────────────────────────────────────────────────── 0 web online 48211 0 3.4% 182.0M 1h 25m edge 1 web online 48212 0 2.9% 178.0M 1h 26m edge > 2 api online 48299 2 7.1% 241.0M 1h 18m edge 3 billing-reconci… online 48230 0 0.8% 96.0M 1h 29m - 4 cron online 48233 0 0.1% 8.0M 1h 31m - 5 metrics online 48240 0 0.4% 11.0M 1h 32m - restart api (id 2): the shepherd restarted it control enabled
or with its own refusal, forwarded rather than rewritten, so the sentence on screen is the daemon's, not lookout's guess at one. Besides the closed gate, arming also refuses if the link isn't live, if nothing is selected, or if another action is already out: one at a time, and the ladder is checked in that order every time.
This is a fat-finger catch, not a security boundary: lookout runs as your own process under your own uid, so the shepherd has no way to refuse a keystroke it can't tell apart from you typingshep stop yourself. Every color on screen is also redundant with text, so NO_COLOR and a 16-color terminal lose decoration, never information.