Skip to main content

Commands, startups, and jobs

What a project needs — the dev server, the tests, the deploy, the set of terminals you open it with — lives in the repository beside the code, not in an app database. It reviews in a diff, clones with the project, and is legible to whoever, or whatever, opens the repo next.

The .saggar directory

Each project gets one, and what belongs in it is a real distinction rather than a coin flip:

PathIs
.saggar/commands.mdThe project's commands, and its startup set. Committed.
.saggar/jobs/*.jsonOne file per scheduled job. Committed.
.saggar/scratchpad.md

The notes pane behind ⌘/. Committed.

.saggar/state.local.json

Machine-local state. Yours, not the repository's — the .local. convention, and what the gitignore nudge covers.

The commands file

A project's Commands tab turns .saggar/commands.md into one-click buttons. A click opens a terminal in the project and runs the command, landing on the menu as an ordinary session so the status engine watches it — a dev server is long-lived far more often than it is one-shot, which is why this isn't ⌘P's self-closing panel. The commands join the palette for the project on screen.

The grammar is one small rule:

A bullet carrying a backticked span is a command. The span is what runs, the text before it names it, and the text after it annotates it.

Everything else is prose and ignored — headings, intros, bullets without backticks — and a fenced code block is skipped whole. So a commands file written for humans parses as one for Saggar, and a pasted example never becomes a button. Editing happens in the tab, saved as you go and re-parsed live, so a bullet becoming a button is something you watch.

# Commands

Everything you need to work on this service.

- Dev server `npm run dev` — port 3000
- Tests `npm test`
- Typecheck `npm run typecheck`

## Startup

- Agent `claude` [primary]
- Dev server `npm run dev` [monitor]
- Tests `npm run test:watch` [monitor]
- Scratch shell `` [companion]

Startups

The terminals a project opens with, in one press: an agent in the main pane, the backend on the menu, the tests in the monitor dock. It is a ## Startup section of the same file rather than a second one, so the set is committed with the code and a teammate cloning the repository gets it.

A bullet may carry one trailing tag saying where its terminal goes. These are exactly the dispositions Saggar already had, so a startup declares where things land and invents nothing new to put them in:

TagLands
[primary]In the main pane — the one you type into
[companion]

In the split beside it (see split view)

[monitor]In the monitor dock, read-only
untaggedAn ordinary menu terminal

Pressing it twice is safe. A command already running is adopted and re-placed rather than started again — two dev servers fighting over one port is a second-press failure, and those are the ones that reach users. A second [primary], a repeated command, and a runaway section past the caps are each skipped with a reason rather than silently obeyed. A startup always lands you in a terminal: most sets are things you watch rather than type into, so when nothing claims the main pane it gets a plain shell, which is also what gives a lone [companion] a left-hand side.

Nothing ever starts on its own. The project landing offers the set when a project has one declared and nothing live yet, so restore wins; the Commands tab carries the button always; and the palette has the verb. Saggar . forking four processes on a repository's say-so is not a thing a folder should be able to decide. The commands go out staggered, so six cold starts don't land in the same instant.

Scheduled jobs

A project's Jobs tab holds repeat jobs on cron syntax — minute hour day-of-month month day-of-week, plus the @hourly, @daily, @weekly and @monthly macros, lists, ranges, and */n steps. Each job is one JSON file under .saggar/jobs/, so it lives beside the code it runs and reviews in a diff.

When a job's time comes it opens a fresh terminal window and runs its command — only while Saggar is open. This is not a launch daemon and does not pretend to be one. A fire time that passed while Saggar was shut fires once on the next launch, not once per missed minute. Jobs edit in place, and a schedule change measures from the moment of the edit, so a fire time the old schedule left behind doesn't replay.