Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The layover command

Eight commands. --config (or -c) is global and defaults to layover.toml in the working directory, so it can go before or after the subcommand.

serve, run and autostart make that path absolute before doing anything else, and every path a run is handed — its Hangar, the mcp.json its CLI is pointed at, the {prompt} file — is built from it. A child runs in its agent's work_dir, not in the directory the Tower was started from, so a relative path would be resolved against the wrong folder and the run would fail before it began. The paths serve prints are the absolute ones, so the output names the factory it is actually running.

layover --help
layover <command> --help

validate

layover validate                       # layover.toml in this directory
layover validate --config f.toml       # somewhere else
layover validate --strict              # warnings fail too

Reports everything wrong with a factory definition and exits non-zero if anything would stop it starting. Warnings — an agent with no description, a schedule that outruns its own timeout, an agent beyond the hop budget — are printed but do not fail unless --strict.

Worth running in CI over your factory definition. An unattended factory that discovers a typo three agents deep has already spent money to find out.

explain

layover explain

Describes the factory in prose: its agents, what each one is for, its pipelines and their triggers, and the route map as a list of edges. The quickest way to check that what you wrote is what you meant.

Under each agent is what it runs on, read from its runner's command with its own model, effort and context filled in — so a value its runner cannot carry does not appear:

Agents
  developer [read-write] Implements the work item and repairs what review rejects
      runs on the CLI's default model · effort xhigh · default context

A route scoped to workflows carries its scope on its line, and once any route is scoped each pipeline also says which agents its chains can reach over the routes they may use:

Pipelines
  eagle-eye [every 7200s] -> azurix
      reaches: azurix, eagle, golddigger, sherlock
...
Routes
  eagle -> azurix, sherlock, golddigger  [pipelines = eagle-eye]

A factory with no scoped route prints exactly what it always did.

graph

layover graph                                # text
layover graph --svg > factory.svg            # a drawing
layover graph --pipeline eagle-eye           # one workflow

The route map as a diagram. The SVG is the same renderer the dashboard uses, so it needs no browser and no JavaScript.

--pipeline draws one workflow over the routes its chains may use — global routes and those scoped to it — which is the diagram the dashboard shows for that workflow. Without it the whole factory is drawn, and a scoped edge is labelled with the pipelines that may use it (in SVG, a scoped class and a tooltip).

prompt

layover prompt analyst
layover prompt tester --pipeline development
layover prompt tester --pipeline development --flag run_e2e=true

Renders an agent's prompt exactly as a run would receive it, with @include directives resolved and conditional sections resolved against the flags. This is the only way to see what an agent will actually be told before it costs anything to find out.

What the agent runs on is printed beside it, on stderr, so the prompt itself can still be piped or diffed:

`eagle` runs on claude-opus-5.5 · effort xhigh · long context, through runner `copilot-analysis`

serve

layover serve                          # http://127.0.0.1:7878
layover serve --addr 127.0.0.1:8080
layover serve --history .layover/history
layover serve --watch-only             # dashboard only, start nothing
layover serve --no-auth                # open to anything that can reach the port

It prints the address with a token in it:

Layover dashboard on http://127.0.0.1:7878/?token=01M2XGEZB8…
The token is in that address; the page keeps it in a cookie afterwards.

Copy that once. Loopback alone was a sufficient boundary while this surface only read history; it stopped being one when the thing behind it began spending money. --no-auth turns it off for a machine only you can reach.

This is the lights-out command, and what autostart registers. It does four things in one process:

Fires schedulesA pipeline with a trigger starts on its own, on time even while other runs are going, and skips a tick whose previous wave is still queued or running
Runs the queueWhatever is waiting — from a schedule, from POST /flights, or sent by another agent — up to max_concurrent_runs at once, the next starting as a slot frees
Hosts MCPEvery run gets the endpoint and a token, so layover_send reaches a real queue
Serves the dashboardThe route map per workflow, run history, cost and the Reserve, help requests, learnings, and each agent's report

They share one process because they share one factory definition, one queue and one set of live tokens. Splitting them would mean keeping three copies of that agreeing.

It also prunes history past its 90-day horizon on startup, and settles what the last Tower left behind: a run that was alive when that Tower went away is stopped if it is still going — it can no longer reach Layover — written to history as interrupted, and restarted where its agent's recovery policy allows. A run another living Tower is watching is left alone.

--watch-only leaves out the first three and serves the dashboard alone. That is what you want when pointing a second window at a factory another process is already running: two Towers over one factory directory would race for its queue.

A Ground Stop is a pause. Engage it and nothing new starts; release it and the next tick fires as usual.

autostart

layover autostart --show               # print it, read it first
layover autostart                      # write it
layover autostart --output ~/svc.xml   # write it somewhere specific

Generates your platform's own autostart artefact — a Scheduled Task, a launchd agent or a systemd user unit — and prints the one command that registers it. It writes nothing if the factory does not load. See Install.

run

layover run                  # run everything queued
layover run --dry-run        # say what would run, start nothing

Drains the queue once and stops, running up to max_concurrent_runs agents at once. Each flight is authorised against the route map and the safety rails, spawned, watched, and written to history; agents can call back over MCP, so a chain sent by one run is picked up by the same command.

Before it reads the queue it settles runs a Tower that went away left behind, as serve does — except with --dry-run, which starts nothing and so stops nothing either.

A flight is taken off the queue before it runs, so a factory that dies mid-run does not repeat the work on restart — an agent that opened a pull request and was interrupted before its outcome was recorded would otherwise open a second one.

A Ground Stop refuses the command outright, and one engaged mid-drain starts nothing more and ends what is running.

It is not the lights-out command — that is serve. run is for when you want to watch one batch of work go through, and for scripting Layover from something else that already has a scheduler.

doctor

layover doctor                      # the last 7 days
layover doctor --window last_24h    # a narrower look
layover doctor --window all_time    # everything still on disk

Reads a factory's recorded history and reports anything a person should look at. Exits non-zero when something found would fail an unattended run, which is the point: it turns "did that soak pass?" into a command rather than a judgement made by squinting at a dashboard two days later.

The failures it looks for are the quiet ones — the ones that look like nothing from the outside:

FindingWhy it is invisible otherwise
A stalled chainEvery run in it reports success. A stall and a finished chain look identical on a list
Runs reporting no costThe total reads not reported or carries a +, which says that it is unknown and not why. The finding names each runner the silent runs ran on and what to change: the output flag its CLI needs, a rate card row for a Codex model, or — when the command is already right — that the runs ended before printing a cost or predate Layover reading one
A schedule that never firedA schedule that is not firing looks exactly like one with nothing to do
Open help requestsThe channel that reaches a person is the one nobody is there to read
Layovers nothing will collectWork an agent set down to come back to, in a factory where no pipeline resumes
A Ground Stop left engagedThe factory is up, the dashboard is green, and nothing is running
The Reserve refusing workA factory that may not spend any more looks exactly like one with nothing to do
Work that waited with a slot freeQueued longer than timeout_sec while fewer than max_concurrent_runs runs were alive, it looks like a busy factory. It is one where nothing was starting the work

Findings come in three weights. A fault means work was lost or money cannot be accounted for; a warning means something is wrong and a person should look; a note is worth knowing and does not fail anything. Only the first two affect the exit code — a check that failed on every curiosity is one people stop running.

Windows are the same set the dashboard offers: today, last_24h, last_7d, last_30d, last_90d, month_to_date, all_time.

It will not invent a verdict

A factory with no history in the window is reported as exactly that, and exits zero. Nothing has run, so nothing has passed and nothing has failed — and a schedule that has not fired is not a finding about that schedule when nothing at all has fired. Widen --window if you expected history and see none.