docs · pre-release
Menu
Configuration / Folds

Folds

A fold is a label, nothing more. It doesn't isolate a sheep's filesystem, network, or resources. It just gives a group of sheep a name you can select, list, and read off the flock table, so backend and jobs don't have to be regexes you keep retyping.

Setting one

Add fold = "<name>" to any app in a Flockfile. A sheep with no fold field simply has none. It prints - in the table and null under --format json.

Flockfile.toml
[[app]]
name   = "web"
script = "./server"
fold   = "backend"

[[app]]
name   = "worker"
script = "./worker"
fold   = "backend"

[[app]]
name   = "cron"
script = "./cron"
fold   = "jobs"

Listing one

shep fold <name> lists exactly that fold, nothing else in the flock. It's shep flock pre-filtered, plus one thing the plain flock table never shows: a Lambs section under any sheep that has spawned children of its own, one per sheep, listing every parent-pid descendant by PID and name:

$ shep fold backend
ID  NAME    STATUS  PID    RESTARTS  EXIT  CFG  CPU  MEM   UPTIME  FOLD     SMIT
0   web     online  64778  0         -     -    -    4.2M  1s      backend  -
1   worker  online  64782  0         -     -    -    4.2M  1s      backend  -

Lambs of web (id 0) — parent-pid descendants of 64778, which is not exactly the set a stop kills
PID    NAME
64780  sleep
64781  sleep

Lambs of worker (id 1) — parent-pid descendants of 64782, which is not exactly the set a stop kills
PID    NAME
64784  sleep
64785  sleep

A sheep with no children of its own gets no Lambs section. See JSON output for how the same data surfaces as the lambs array under --format json.

A fold name that matches nothing is the same not_found error any other selector gives you, exit code 3, not an empty list:

$ shep fold nonexistent
error[not_found]: the daemon reported NotFound: selector matched no registered sheep

Seeing every fold at once

Press F in lookout to see every fold together. The flock table gathers under a header per fold, each summing its members' restarts, CPU and memory and taking the shortest of their uptimes, with a bar for the fold's share of total flock memory. Sheep in no fold get a no fold header, and dogs keep their own, since a dog is never in a fold.

A fold header is a selectable row, so x, R and L act on every member behind a confirm that names the count. z collapses one. F again goes back to the flat list.

The no fold header is the exception: it can't be selected or acted on, because no selector names every sheep that is in no fold. Act on those sheep individually, or give them a fold.

Selecting one from any verb

Every verb accepts fold:<name> in place of a name, an id, a /regex/, a glob, name:slot (web:2, one instance of a multi-instance app), or all. That's stop, restart, reload, delete, bleats, trigger, start, and the rest. It reaches every sheep carrying that fold in one call:

$ shep stop fold:backend
ID  NAME    STATUS   PID    RESTARTS  EXIT     CFG  CPU  MEM    UPTIME  FOLD     SMIT
2   cron    online   16564  0         -        -    -    32.0K  0s      -        -
0   web     stopped  -      0         SIGTERM  -    -    -      0s      backend  -
1   worker  stopped  -      0         SIGTERM  -    -    -      0s      backend  -

The listing is the whole flock, not just the fold. Every lifecycle verb prints it that way, so cron is there and still online. See terminal output.

Folds on start

shep start takes selectors like every other verb, and it also takes the things only it can take: a script path, a Flockfile, or - for Flockfile JSON on stdin. That makes some targets ambiguous, so it resolves one in a fixed order and the first tier that matches wins.

1. a sheepby id or by name
2. a foldfold:backed, or just backed
3. a Flockfileby its extension
4. a pathstarted as a script

So shep start backed starts the fold even when a file called backed is sitting right there in the current directory. Write ./backed when you mean the file. That works whatever your sheep are called: a sheep name may never contain a path separator, so a target carrying one is always a path.

Only sheep the flock already has can be reached this way. A fold is a field on a registered sheep, so there is nothing for a fold to register. A sheep that is already running is reported and left alone; restart is the verb that replaces one.

$ shep start fold:backed
ID  NAME    STATUS  PID    RESTARTS  EXIT  CFG  CPU  MEM    UPTIME  FOLD    SMIT
0   golbat  online  19303  1         -     -    -    32.0K  0s      backed  -
1   koji    online  19304  1         -     -    -    32.0K  0s      backed  -
2   rotom   online  19260  0         -     -    -    32.0K  0s      front   -

$ shep start fold:typo
error[not_found]: no sheep is in a fold called typo

A selector that matched nothing is not_found, exit 3, the same as everywhere else. It does not fall back to looking for a file called fold:typo.

note
Selector parsing checks shapes in a fixed order: the literal word all, then a fold: prefix, then /regex/ slashes, then a numeric id, then a glob, then name:slot, and only then a plain name. A sheep named 2 is the practical edge of this: typing 2 as a selector always means "id 2", never "the sheep named 2", even if those aren't the same sheep. fold: is checked early enough that a fold can be named anything, including all or a number, and fold:<that name> still reaches it. A glob like web* is checked before name:slot, so it matches every instance of an app rather than being read as a name with a stray colon; and a colon is no longer legal in a sheep name at all, so web:2 can only ever mean slot 2 of web.

What a fold is not

Folds carry no uniqueness constraint and no nesting: a name is a flat string, and two sheep can share one with nothing else in common. There is no shep fold create or config listing every fold that exists; a fold exists exactly as long as some sheep's fold field names it, and disappears the moment the last one does. If you're picturing a Kubernetes namespace or a cgroup, that's the wrong mental model. The closest real comparison is a tag.