docs · pre-release
Menu
Day to day / Editing config in lookout

Editing config in lookout

Four panes write rather than read: the shepherd's own settings, secrets, any Flockfile field on a running sheep, and a dog's own schema. All four are reached from the dashboard, and all four file a change set rather than writing as you type.

Settings

s opens a full-screen settings view over shep.toml: the four daemon fields, the whistle gate, the style level, and the dogs table's own enable/disable, each with a SOURCE column and a note on what it takes to apply. s or Esc closes it again, back to the flock table.

Reading this screen needs nothing, gate open or closed, the same as everything else on the dashboard. Changing a value is a different story: every edit and the dog toggle need the same gate the action keys do, and refuse the same way if it's closed.

120×30, a fresh $SHEP_HOME
shep lookout   /home/ada/.shep                                                                            6 in the flock
  [daemon]
> log_level        warn                            the default  needs shep daemon reload
  log_json         false                           the default  needs shep daemon reload
  socket           /home/ada/.shep/run/shep.sock   the default  needs the shepherd stopped and started
  max_cron_sleep   not set                         the default  needs shep daemon reload

  [whistle]
  allow_control    false                           the default  needs shep whistle restarted

  [style]
  level            full                            the default  the next command reads it

  [dogs]
  space arms, Enter applies; a dog needs no reload
  NAME                                                                IN FILE  SOURCE                    RUNNING
  bark                                                                no       built-in                  not running
  metrics                                                             no       built-in                  not running
esc/s close   j/k select   g/G first/last   r refresh   space cycle   enter apply   q quit               control enabled

SOURCE reads the default when shep.toml doesn't mention the field at all, and the VALUE column shows the compiled fallback instead of anything from disk. A fresh $SHEP_HOME ships a shep.toml holding only [interpreters], so this is the state you land in the first time you open the screen: every field on it is a guess about what the daemon assumes, not a record of what anyone wrote. Once a field is set, SOURCE switches to shep.toml and VALUE shows what's actually there.

120×30, some fields set
shep lookout   /home/ada/.shep                                                                            2 in the flock
  [daemon]
> log_level        warn                            shep.toml    needs shep daemon reload
  log_json         false                           the default  needs shep daemon reload
  socket           /home/ada/.shep/run/shep.sock   shep.toml    needs the shepherd stopped and started
  max_cron_sleep   30s                             shep.toml    needs shep daemon reload

  [whistle]
  allow_control    false                           the default  needs shep whistle restarted

  [style]
  level            full                            shep.toml    the next command reads it

  [dogs]
  space arms, Enter applies; a dog needs no reload
  NAME                                                                IN FILE  SOURCE                    RUNNING
  otel                                                                no       /usr/local/bin/shep-otel  online
  ledger                                                              yes      /opt/ledger/bin/dog       not running
  bark                                                                yes      built-in                  silent
esc/s close   j/k select   g/G first/last   r refresh   space cycle   enter apply   q quit               control enabled

The [dogs] table is the one place SOURCE isn't the whole story: IN FILE says whether [daemon] enabled_dogs names the dog, SOURCE says whether it's built in or adopted from a path, and RUNNING joins both against the live flock. The three can disagree, and when they do that's the point of showing all three: otel above is running with nothing enabling it, ledger is enabled and not running, and bark is enabled, running, and still silent because it never finished a handshake. space arms a toggle on the selected dog and Enter sends it; unlike a scalar edit, this reaches the shepherd directly and needs no reload.

careful
A [daemon] edit only ever changes shep.toml. The shepherd itself can be started with SHEP_LOG_LEVEL, SHEP_SOCKET, or the matching --log-level /--socket flags, and shep's own precedence puts those above the file. Lookout has no way to see what the shepherd was booted with, so setting log_level here and running shep daemon reload can do nothing at all if the process was started with SHEP_LOG_LEVEL set, and nothing on screen will say so. [style] is not an exception: --style and $SHEP_STYLE outrank the file there too. The difference is that those layers belong to lookout's own process, so lookout can see them. SOURCE names the one in force, the last column reads written, but outranked instead of promising anything, and the confirm names the layer that will keep winning.

The confirm names exactly this risk when it's [daemon] that's armed:

180×30, armed
shep lookout   /home/ada/.shep                                                                                                                                        6 in the flock
  [daemon]
> log_level        warn                            shep.toml    needs shep daemon reload
  log_json         false                           the default  needs shep daemon reload
  socket           /home/ada/.shep/run/shep.sock   shep.toml    needs the shepherd stopped and started
  max_cron_sleep   30s                             shep.toml    needs shep daemon reload

  [whistle]
  allow_control    false                           the default  needs shep whistle restarted

  [style]
  level            full                            shep.toml    the next command reads it

  [dogs]
  space arms, Enter applies; a dog needs no reload
  NAME                                                                                                                            IN FILE  SOURCE                    RUNNING
  bark                                                                                                                            no       built-in                  not running
  metrics                                                                                                                         yes      built-in                  not running

  set log_level to info? needs shep daemon reload, and will not apply if the shepherd was booted with SHEP_LOG_LEVEL or --log-level  enter confirms, any other key cancels
set log_level to info? needs shep daemon reload, and will not apply if the shepherd was booted with SHEP_LOG_LEVEL or --log-level  enter confirms, any other key ca… control enabled

log_level, log_json, and allow_control cycle through a fixed set of values, and the cycle only ever moves a field from the default to shep.toml, never back: space proposes the next value, it's never "unset". socket and max_cron_sleep are the only two fields this screen can put back to the default, because they're free text, and clearing the editor back to an empty line is what unsets them. style level is optional too, but shep style is what owns it and can't clear it either, so this screen doesn't offer to.

Keys, all of them local to this screen while it's open:

  • s opens the screen, and closes it again
  • j/k, arrows move the cursor one row
  • g/G jump to the first or last row
  • space arms the cursor's row; pressing it again on the same field re-arms with the next candidate
  • Enter opens the text editor on socket/max_cron_sleep, or sends an armed confirm
  • Esc cancels an armed confirm, else abandons the text editor, else closes the screen; it never quits lookout
  • r re-reads shep.toml from disk

An armed candidate is cancelled, not acted on, by any movement key, by r, or by s: the same rule the dashboard's own action keys follow, where a stray keystroke right after arming backs out of the question instead of both dismissing it and doing something else. The confirm itself renders in the status bar, the same fixed row the dashboard's x/R/L confirm uses.

Secrets

S, s's neighbour, opens a full-screen view over the store the secrets page covers: the operator's own keys from secrets.json and each provider's from secrets-cache.json, one tab per environment. Esc or S again closes it.

160×48
shep lookout   /home/ada/.shep                                                                                                                    6 in the flock

 ██ SECRETS   flock-wide values a Flockfile refers to and never carries
store $SHEP_HOME/secrets.json · not encrypted · never printed to a log, never carried in a bleat · read at spawn, not now
reveal  [secrets] allow_read = true in shep.toml · change  lookout.allow_control = true
READ BY as of the roll, read 3m 4s ago
all  ci  [production]                                                                                                  3 environments in this store · ←/→
  KEY                         VALUE                         IN FORCE      SET IN                READ BY               LANDS
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ▾ operator ×2 · you set these
> DB_PASSWORD                 hunter2-not-really            production    1 of 3 · production   1 (1 online)          visible 10s ██████████
  ELSEWHERE_ONLY              not set here                  -             1 of 3 · ci           -                     -
  + new key
  ▾ vercel (dog) ×1 · pushed by a provider · read-only here
  vercel/API_TOKEN            ██████ 6 bytes                production    1 of 3 · production   -                     -
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
FOCUSED  DB_PASSWORD                                                                    WHO READS IT  DB_PASSWORD
the value leaves the screen after 10s. nothing records that you looked.                 █ catcher   online, was given a value at spawn
the store is 0600: anyone who can reveal can also delete a log.
the system clipboard is readable by every process on the desktop.
set in production · length 18 bytes · named by 1 of the flock                           named in env, args, out_file or err_file
esc/S close   ←/→ tab   z collapse   v reveal for 10s   ↵ set a value   D delete   y copy   q quit                                               control enabled

Two gates, and neither implies the other. Revealing a value needs [secrets] allow_read = true in shep.toml, off by default; changing anything (setting a key, deleting one, or adding a new one) needs the same lookout.allow_control gate the dashboard's action keys do, covered above. A read-only lookout with the reveal gate on can look but not touch; a controlling lookout without it can rotate a key it can never read back.

  • S opens the pane, and closes it again
  • ←/→ move the environment tab
  • j/k, g/G move the cursor
  • z collapses the group under the cursor, hiding its members
  • v reveals the selected row's value for ten seconds, gated on allow_read
  • Enter opens a value input on an operator row, or confirms an armed delete
  • D arms deleting the selected key; like every other armed key in lookout, it expires after ten seconds unanswered, Enter is what actually confirms it, and any other key cancels it
  • y sends an already-revealed value to the terminal over OSC 52
  • r reloads the store
  • q quits lookout

A reveal clears on far more than its own timeout: moving the selection, changing tabs, pressing Esc, reloading with r, closing the pane, quitting, or a write landing all drop it early, so a value never sits on screen describing a row it no longer belongs to.

y only says the value was sent to the terminal, never that it arrived: OSC 52 has no acknowledgement, and the system clipboard is readable by whatever else is running on the desktop, a wider exposure than the screen it just left. A provider row refuses both Enter and D outright; it's a cache of what a dog pushed, not something lookout can write back.

A row taking its value from the all slot refuses D on a named tab: removing that slot changes every environment at once, so the all tab is where you do it deliberately. A delete that finds no value to remove says so rather than reporting a success it did not have.

The config pane

e on a selected sheep opens its config over the dashboard: every Flockfile field, the value in force, and what changing it would cost. Fields sit under nine groups: process, logging, inputs, restart, readiness, shutdown, watch, cron, dogs. It writes as well, behind the same gate. Under --read-only the first key that would file an edit refuses the same way x does; the pane stays open and unchanged. A row marked = or ~ gives its own reason instead, in both states, because the flag would not have helped it.

No edit reaches the shepherd until the pane closes. An edit is filed into a change set the pane carries, not sent, and Esc writes the whole set at once when it closes the pane. u undoes the most recently filed edit, one at a time, and files nothing itself. r is not an edit: it re-reads the pane's config from the shepherd while the pane is still open, same as any other request.

A field whose grammar accepts a bare number, such as max_memory or kill_timeout, shows its resolved value rather than the digits on disk: 64 reads as 64 B, and 1600 as 1600ms, because a bare max_memory is bytes and a bare timer is milliseconds, neither of which is what most operators expect. The grammar itself is unchanged; only what the row draws is.

Two markers can sit in front of a key, and the pane's own key hint names both, so the meaning is on screen wherever the pane is:

  • * an operator has overridden it
  • ! it is parked until the next respawn, so the running child is not holding it yet

And one in the column before those, when the pane won't edit a field:

  • = shep refuses a config write to it anywhere, not just here. That is name and instances, and the cost column says read-only to match.
  • ~ the pane has no widget for the shape, and nothing more. A Flockfile writes these the usual way, so the cost column beside them reports a real cost rather than read-only. Two fields: liveness_probe and readiness_probe.
  • › Enter opens a screen, not a value. Only dogs uses it today.

Both are glyphs rather than a colour on purpose. The narrowest terminal the pane draws into has already dropped the cost column, and plain style has no colour to read, so a greyed-out row would be the only signal left in the case where it says least.

The last column is the field's class, which is what a change to it usually costs:

  • now the shepherd re-reads it at the next decision
  • next start it reaches the next process this sheep spawns
  • respawn the running child holds it, so that instance has to be replaced
  • read-only identity or flock shape rather than a runtime knob. That is name and instances; the count moves with shep stock, not from here.

Read it before you write, not after. It says what kind of field this is, and a particular write can land differently: watch says now and waits for a reload if the cwd it needs is itself still parked, and autostart says next start and takes effect immediately, because shep reads it at muster. The status bar after the write is the one that reports what actually happened, and the ! flag on the row carries it afterwards.

cwd, script, out_file and err_file get one more line on that status bar when the shepherd looks at the path and it looks wrong: a directory that isn't there, or a script it can't find. The write still lands. A deploy that hasn't created the directory yet is a real config, not a typo, so this is a warning next to the write rather than a refusal of it.

A tab row above the fields names all nine groups, dogs last. Tab walks to the next one, Shift-Tab walks back to the previous one, and both wrap. 1 through 9 jump straight to a group by its position in that row. Each resets the cursor to the group's first field.

A panel to the right of a wide enough pane explains whichever field the cursor is on: its help text, its current value, its default, an example, what it accepts and refuses, and which other fields it interacts with. Below 90 columns there's no room for the panel, so the field's help text draws inline instead, in a row above the field list, wrapped, and still following the cursor. No key toggles it either way. The cost column carries every row's cost alone down there. From 90 up the panel draws and the cost column gives way to it, since the panel already names the focused field's own cost. From 160 up the pane is wide enough for both at once.

What the keys do here that h cannot tell you in one line:

  • r re-reads the config and leaves the cursor where it was
  • space files the other value for a bool, the next one for a choice, or the next suggestion for kill_signal and cron_restart, straight into the change set. Press it again to walk another step. A suggestion is not a closed list: e still types anything else.
  • e or Enter opens the editor on a text or number field; a second e/Enter files what was typed, or says why the field won't take it and leaves the editor open on it. Two keystrokes to change a field, never one. Inside the pane the two keys do exactly the same thing, so learning either is enough.
  • e or Enter on an array field opens a list sub-screen instead: d removes the selected element, K/J move it, and e/Enter edits it, or adds one from the + new row. Each of those files the whole array on the spot; there's no second key to send it. They build on each other, and the rows show what you have filed rather than what the shepherd sent, so you can remove two elements or add several before leaving.u in here drops the array's whole entry and puts the shepherd's own back.
  • d on the field list restores the selected field to its default, filing that too rather than sending it. A field already at its default, or one shep refuses to write, files nothing.
  • u undoes the most recently filed edit, one at a time.
  • Esc closes whatever is innermost: a sub-screen back to the field list, then a question if anything the running child hasn't taken is outstanding, and last the pane itself, writing the whole change set as it closes. It never quits lookout.

Every filed edit is recorded as yours once it's sent: the key picks up the * marker here and in shep flock's CFG column, and a later shep start of the app's Flockfile leaves it alone. Nothing else in the config moves, instances included.

A field a write cannot reach in the running child parks until the next spawn, since the child was given its value at exec and holds it. If an edit still filed needs a respawn, or a field is already parked from an earlier write, Esc asks before it closes: restart now, reload, or leave it running with the edits parked. L reloads the sheep, R restarts it, c writes and leaves it running, and Esc again keeps editing and writes nothing. The restart or reload only fires once its write has landed, so it never respawns into the config it was meant to replace. The dialog shows up only with the gate open. shep reload <sheep> promotes an already-parked field from the CLI, any time after.

A sheep's env keys sit in the field list itself, beneath whichever group is on screen, rather than behind their own row. No value ever appears there, because the shepherd never sends one: every key reads (set). e or Enter on a key types a new value for it, and filing an empty one removes the key. Either on the + new row takes KEY=value.

An env write files the same way a field does, a set as much as a removal, and waits with everything else for the pane to close. The old value is overwritten in the store as soon as the write lands, and this screen cannot read a value back to put it there again, so an overwrite loses as much as a delete does.

env is spawn-time in every case, so nothing set here reaches the running child until that child is replaced.

A group row opens one too. Every instance of a multi-instance app shares one stored spec, so the pane is about the app rather than about whichever instance the cursor was over.

A sheep's dogs row

dogs has a tab of its own, last in the row, reached with 9, Tab or Shift-Tab. It names its dogs, jobs, legacy, or none, never a value. Enter or e probes every adopted dog and opens a sub-screen.

160×48, cursor on the dogs row
shep lookout   /home/ada/.shep                                                                                                                    6 in the flock

 ██ api  (sheep config)
   process   logging   inputs   restart   readiness   shutdown   watch   cron   dogs     tab next group   1…9 jump
  ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  FIELD                       VALUE                                           LANDS
>› dogs                       jobs, legacy                                    now        FOCUSED  dogs  opaque
                                                                                          Settings for one dog on this sheep alone, opaque to shep
  env
   DATABASE_URL               (set)                                                       now     jobs, legacy
   NODE_ENV                   (set)                                                       default {}
   + add a key                                                                            example (none)

                                                                                          ● now  the running sheep picks it up without stopping
                                                                                                                         pick the timing when you close the pane

                                                                                          ──────────────────────────────────────────────────────────────────────
                                                                                          VALIDATION
                                                                                          █ a table of dog name to a table of settings
                                                                                          █ a dog name shep does not know about yet
                                                                                          █ refused: a value that is not a table
                                                                                          █ refused: an empty dog name
  ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  = read-only, set it in the Flockfile   ~ no widget for this shape   ! parked, awaits a respawn   * yours   -> changed by you, not yet written

esc close   j/k select   g/G first/last   r refresh   space cycle   e edit   d back to default   u undo   h help   * yours   ! parked   q quit   control enabled

A row is set, unset, or read-only. Read-only means no dog published a schema for that table, so it names keys only, same as dogs above it. d arms removing a table. Enter confirms, any other key cancels.

jobs set, deploy unset, legacy read-only
shep lookout   /home/ada/.shep                                                                                                                    6 in the flock

  api › dogs
  deploy            -          (no table)
> jobs              set        concurrency, hours, merge, models
  legacy            read-only  url  (publishes no sheep schema)

esc back   j/k select   g/G first/last   r refresh   enter open   d remove   q quit                                                              control enabled

Enter or e on a set or unset row opens that dog's table over the sheep. It refuses while the sheep pane holds unwritten edits: Esc goes back to the sheep pane, where u undoes them one at a time and a second Esc writes them.

A nested struct in the schema becomes dotted rows here, like models.worker.model. A secret reads <set>. A list of strings or integers opens the sheep pane's list sub-screen. Esc writes the whole table and lands on the dashboard, not back on the sub-screen. If the table changed since the pane opened, nothing is written: the pane reloads it under your edits, and Esc again writes.

jobs's table on api
shep lookout   /home/ada/.shep                                                                                                                    6 in the flock

  api › jobs  (dog table)
>  concurrency                2                                                          FOCUSED  concurrency  integer
   hours.end                  17:00                                                       concurrency
   hours.start                09:00
   merge                      ask                                                         now     2
   models.worker.model        small                                                       default (none)
   models.worker.token        <set>                                                       example (none)

                                                                                          ──────────────────────────────────────────────────────────────────────
                                                                                          VALIDATION
                                                                                          █ a whole number
  shep publishes the change; jobs decides what to reload

esc close   j/k select   g/G first/last   r refresh   space cycle   e edit   d back to default   u undo   h help   * yours   ! parked   q quit   control enabled

The dog config pane

e on a dog row opens its config, over that dog's [<name>] section in dogs.toml. It works the same whether the row sits in the dashboard's own Dogs section or the settings screen's dogs table. Inside the pane, e is the field list's own edit key, the same as on a sheep's config; only Esc closes it.

The form comes from the dog. Shep asks the binary for its schema when the pane opens rather than reading one it stored, because cargo install replaces a dog with nothing watching and a stale schema mislabels which field is a credential. A built-in dog answers without being spawned at all. A dog that publishes no schema gets no pane, and a line saying where its settings do live:

pydog publishes no schema; edit dogs.toml with $EDITOR
careful
Asking runs it. Opening the pane on an adopted dog spawns that binary with --schema, waits a second for an answer, then kills it. That is a third party's code executing because you pressed a key, and it is worth knowing before you browse the config of a dog you did not write. A built-in dog is shep's own binary and is never spawned.

The pane is flat, with no section headers: a dog's schema carries no groups, and inventing some would be shep deciding how somebody else's config reads. The cost column is empty on every row, because shep does not know what a dog's field costs. The foot of the pane says so once, naming the dog:

shep publishes the change; bark decides what to reload

A field the dog marks as a credential reads <set> and never its value, and the editor on it opens empty. Typing a new one replaces it; nothing here can show you what it replaced. A table or a list gets ~, the same glyph a sheep's args gets and for the same reason.

Edits file into the same change set a sheep's pane uses, and u undoes them the same way; nothing reaches the dog's section until Esc closes the pane. A section edited meanwhile is reloaded under your edits, not overwritten. However many fields changed, the write sends the section whole, in one request, so the comments and key order in dogs.toml survive it. Applying an empty value removes the key, which puts the dog back on its own default. Neither * nor ! appears: a dog has no Flockfile to differ from, and dogs.toml is the one copy of its settings.

The explanation panel draws here too, at the same widths, with the dog's own help text, current value, default and example; only the impact sentence is missing, since shep doesn't know what a dog's field costs. There's no tab row: a dog's schema carries no groups to move between.

r re-reads the section and not the schema, so a keystroke whose job is to re-read a file never respawns somebody else's binary.