docs · pre-release
Menu
Interfaces / Lookout

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.

careful

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.

120×30
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.

120×30, with 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.

100×14, filter applied
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.

51×14
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:

28×8
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.

120×30, shepherd dead
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:

120×30, gate closed
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:

100×14, armed
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:

100×14, accepted
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.

Where to go next