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:
| Path | Is |
|---|---|
| .saggar/commands.md | The project's commands, and its startup set. Committed. |
| .saggar/jobs/*.json | One file per scheduled job. Committed. |
| .saggar/scratchpad.md | The notes pane behind |
| .saggar/state.local.json | Machine-local state. Yours, not the repository's — the
|
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:
| Tag | Lands |
|---|---|
| [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 |
| untagged | An 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.