Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bb887bd
docs(queue): a steady job books what it holds, not what past runs gre…
SpoBo Oct 16, 2026
1dbcd9b
fix: outside_admit admits again when mem_max is at or above pause_at
SpoBo Oct 16, 2026
862f5e1
test: a model of dev stacks, their runs, and a turbo run's lints
SpoBo Oct 16, 2026
e2abb63
fix(queue): the progress rule starts only a job that fits, and holds …
SpoBo Oct 16, 2026
bf4bf0a
feat: a long-lived job books room for the work it expects, and that w…
SpoBo Oct 16, 2026
0cb5a39
feat: a stack waits a few seconds for the work it expects, and work q…
SpoBo Oct 16, 2026
7defbe5
test: the stall model asserts the scenarios that clear
SpoBo Oct 16, 2026
3a77071
feat: without a declaration, a stack books the work its history says …
SpoBo Oct 16, 2026
744ec32
no-mistakes(review): Bound history guesses; respect open guesses; out…
SpoBo Oct 16, 2026
460f763
no-mistakes(review): Keep came-true guesses booked; lone job may use …
SpoBo Oct 16, 2026
ea52172
no-mistakes(test): Book a pool's typical start need for declared stac…
SpoBo Oct 16, 2026
8d5e037
no-mistakes(test): Align stack-holds-turn test with ruled lone-job RA…
SpoBo Oct 16, 2026
c87d101
no-mistakes(document): Clarify lone-stack ceiling and guess-link word…
SpoBo Oct 16, 2026
50795e8
test: the stack-and-run CLI test books little memory, so it passes on…
SpoBo Oct 16, 2026
965cdaa
no-mistakes(review): Define "still a guess" once; came-true guesses c…
SpoBo Oct 16, 2026
0e96cc0
fix: a second run does not link to a guess the first run made come true
SpoBo Oct 16, 2026
79beb9e
ci(release): a tag with a suffix publishes a pre-release, never latest
SpoBo Oct 16, 2026
ff7e3b5
chore: release 0.11.0-rc.1
SpoBo Oct 16, 2026
3a73eed
no-mistakes(review): Explain's memory line leaves out a stack's own g…
SpoBo Oct 16, 2026
db82278
no-mistakes(document): Reflow history-guess paragraphs in README and …
SpoBo Oct 16, 2026
2c5c3c6
feat(top): show the version at the right of the key bar
SpoBo Oct 17, 2026
4ae9d40
chore: release 0.11.0-rc.2
SpoBo Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ build or the tests need it.
A local `rust-objcopy ... libLLVM.dylib` warning on macOS comes from the
local toolchain. It does not affect the release: CI builds the binaries.

### Pre-release

A version with a suffix, such as `0.11.0-rc.1`, makes a pre-release. The
steps are the same, with the suffix in `Cargo.toml`, the `CHANGELOG.md`
section and the tag (`v0.11.0-rc.1`). The workflow marks a tag with a `-`
as a pre-release and never as latest. Check it after the workflow:

```sh
gh release view vNEW -R SpoBo/taskguard --json isPrerelease --jq .isPrerelease # true
gh release view -R SpoBo/taskguard --json tagName --jq .tagName # the last full release
```

A pre-release can be tagged on a branch other than `master` when the user
asks for it; the workflow builds the tagged commit.

### Dry run

To test changes to `release.yml` without a release, start the workflow by
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,19 @@ jobs:
exit 1
fi
printf '\nPrebuilt binaries for macOS (Apple silicon, Intel) and Linux (x86_64, ARM, static musl). Each archive holds the `taskguard` binary; a `.sha256` file sits next to it.\n' >> notes.md
# A tag with a suffix, such as v0.11.0-rc.1, is a pre-release: it is
# never marked latest, so whoever installs the latest release keeps
# the last full one.
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
run: |
pre=()
case "${{ github.ref_name }}" in
*-*) pre=(--prerelease --latest=false) ;;
esac
gh release create "${{ github.ref_name }}" dist/* \
--repo "${{ github.repository }}" \
--title "taskguard ${{ github.ref_name }}" \
--notes-file notes.md
--notes-file notes.md \
"${pre[@]}"
75 changes: 74 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ and the versions follow [semver](https://semver.org).

## [Unreleased]

## [0.11.0-rc.2] - 2026-10-17

### Added

- `taskguard top` shows its version at the right of the key bar, in the
room the keys leave: `taskguard VERSION`, or `vVERSION` when narrow.

## [0.11.0-rc.1] - 2026-10-16

### Added

- `taskguard dump PATTERN | --cmd "COMMAND" | --run ID` prints everything
Expand All @@ -21,6 +30,68 @@ and the versions follow [semver](https://semver.org).
Use it to find out why a job reserved what it did.
- Each run now stores how its need was made (`runs.need_why`), so `dump`
can explain an old run too, not only the next one.
- A long-lived job can declare the work that will run on it:
`--expect WORK` (repeatable) or `TASKGUARD_EXPECT=WORK,WORK`, where WORK is
a pool name, a job key or a command pattern. The stack books room for that
work (learned as the work learns its own need; for a pool, the need its
jobs typically started with) and starts only with room for both. A job in the same checkout that matches is its work: it goes one
priority above the stack, takes over the booked room when it starts, and
waits while its stack still waits. `taskguard status` shows `(for stack
KEY)` on the work and `(expects WORK, books SIZE)` on the stack. Without a
declaration, only what the history infers applies (`infer_work`, below).
- A long-lived job with no declared work books the work its history says
follows it: work of one kind (a pool, or a key outside any pool) that
queued in its checkout within `infer_work_within` (900) seconds of its
start on at least `infer_work_min` (3) of its last `infer_work_of` (5)
runs there. It books that work's usual need, shown as `(expects WORK from
history, ...)` until work queues for it, for twice its usual delay and ten
minutes at most, and drops it once work for the stack starts. While the
guess is open the stack waits `expect_wait` for the work, and matching work
links to it and goes first; an expired or started guess links nothing, and
work linked to it drops back to its own priority. A guess whose work has queued and
linked to it came true: it is declared work from then on, also for the
rule for nothing running, and does not expire while that work waits. The
guess never stops the rule for nothing running. Another stack respects it
while it is open, also one started with no declared work, so such a stack
waits at most until the guess expires or its work starts; a stack that
still fits next to it starts at once. A declaration wins; `--expect none`
expects nothing; `infer_work = false` turns it off.
- `expect_wait` (5 seconds by default, 0 turns it off): a long-lived job that
expects work, declared or from its history, waits that long before it
starts for the work to queue, so a stack and a run started together start
with room for both. A stack that expects nothing starts at once.

### Changed

- With nothing running, or nothing but long-lived jobs past their start-up,
the first job in line that fits starts, up to all of RAM, rather than the
first in line whatever the memory reading says. Next to long-lived jobs, a
long-lived job, such as a second dev stack, starts that way only under
`mem_max`, or, when only programs outside taskguard keep it out, up to the
ceiling `outside_admit` allows. With nothing of taskguard's running, it
starts up to all of RAM, as before. A job that does not fit holds nobody
back, so the jobs behind it that fit start. At macOS warning this rule
still decides when only long-lived jobs run.
- A waiting job that would not fit even once every job that ends by itself
has ended holds nobody back, whatever its priority or reservation: it can
only start once a long-lived job is stopped or other programs free memory,
and the small jobs behind it start meanwhile.
- A stack with work queued for it, and work whose stack runs, hold their turn
as a strict reservation, also while they do not fit yet, rather than letting
jobs that fit start meanwhile (backfill).

### Fixed

- A second dev stack is no longer started over memory while the first one's
run waits: the rule for "nothing runs but long-lived jobs" started the head
of the line whatever the readings said, and skipped itself at macOS
warning, so the run waited until its caller gave up.
- The NEXT line names a long-lived job that holds the room as one that must
be stopped, not as one that will finish.
- `outside_admit` works again when `mem_max` is at or above `pause_at`
(92% by default), as with `mem_max = 95`. It capped memory at `pause_at`,
under the limit, so it could never let a job start; the cap is now
halfway from `mem_max` to all of RAM in that case.

## [0.10.0] - 2026-10-16

Expand Down Expand Up @@ -336,7 +407,9 @@ and the versions follow [semver](https://semver.org).
needs, and starts it when the machine has room for it.
- Prebuilt binaries for macOS and Linux on each version tag.

[Unreleased]: https://github.com/SpoBo/taskguard/compare/v0.10.0...HEAD
[Unreleased]: https://github.com/SpoBo/taskguard/compare/v0.11.0-rc.2...HEAD
[0.11.0-rc.2]: https://github.com/SpoBo/taskguard/compare/v0.11.0-rc.1...v0.11.0-rc.2
[0.11.0-rc.1]: https://github.com/SpoBo/taskguard/compare/v0.10.0...v0.11.0-rc.1
[0.10.0]: https://github.com/SpoBo/taskguard/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/SpoBo/taskguard/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/SpoBo/taskguard/compare/v0.7.1...v0.8.0
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "taskguard"
version = "0.10.0"
version = "0.11.0-rc.2"
edition = "2024"
rust-version = "1.89"
description = "A sem that learns what each job needs: start a command only when the machine has room for its CPU and memory"
Expand Down
Loading
Loading