Skip to content

fix(skills/bounding-heavy-commands): show commands that wait for memory in the check - #91

Merged
sourcehawk merged 10 commits into
mainfrom
fix/memory-waiters-hidden-holding-slots
Oct 6, 2026
Merged

sourcehawk merged 10 commits into
mainfrom
fix/memory-waiters-hidden-holding-slots

Conversation

@sourcehawk

@sourcehawk sourcehawk commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Description

Fixes #80

A bounded command that waited for free memory held its slots, but --check showed no line for it, because the held lines come from the scopes of running commands. So --check could show taken slots with no held line to explain them, and the user could not see what held the queue. Now the wrapper holds a wait file while it waits for memory, and --check prints a waiting line for each such command.

This PR is visibility only. An earlier version also gave the slots back while a command waited for memory, so that a smaller command that fits could start. That part was dropped to keep the line fair: a large waiter would lose its turn, and smaller commands could take the memory each time it came free, so the large one could wait without end. The case where the queue was blocked with no bounded command running already ends with exit 75 since #85. The wait that is left lasts until a running bounded command frees its memory, and holding the slots there keeps the line fair.

Changes

  • bounded_run.py: while it waits for memory, the wrapper holds a locked wait-<unit>.lock file with the budget, the slots that it holds, the start time, the directory, and the command (no process ID). It writes the file once, before the first sleep of the wait, through a new on_wait callback of wait_for_memory, and removes it when the wait ends, before the command starts.
  • --check prints bounded-run: check waiting unit=<name> for=memory budget=<n>M slots=<n> age=<n>s dir=<directory> command=<command> for each wait file, also without the hard cap. It removes a wait file that no process holds.
  • SKILL.md: the check-line formats; how to read a waiting line (a command that had its turn, keeps its slots, and waits for the memory that a held command frees when it ends, because programs outside the queue hold most of it); the report after a long wait; the lock-file rule (a deleted wait file hides a waiter from --check); and the verbatim subagent rule now name the waiting lines.

Related

Testing

The script tests pass: 374 tests, run through the bounded wrapper at --memory 512M. The wait_for_memory tests of main are unchanged. A process test runs a 6G waiter beside a 2G holder on 5 slots. --check prints free-slots=1 and a waiting line with the waiter's unit, budget, slots=3, directory, and command. When the holder ends, the waiter exits 75 and leaves no wait file. Unit tests cover the on_wait callback, the wait file's fields and cleanup, the removal of the file before the command starts, and the waiting line of --check.

The skill text went through RED and GREEN with isolated subagents. With the final text, 3 of 3 agents that read a check with one held line and one waiting line said that the waiting command holds its slots and keeps its turn, and that it starts when the held command ends.

🤖 Generated with Claude Code

sourcehawk and others added 3 commits October 6, 2026 10:12
…waits for memory

The wrapper took its slots, and then it waited until the free memory held its budget. While it waited, it kept the slots but had no scope. So `--check` showed no line for it, and a smaller command that fit in the free memory waited behind slots that nothing used.

Now the wrapper gives back its slots while the free memory does not hold its budget. When the memory looks free again, it takes slots through the line and reads the memory again under the memory lock before it starts. So two waiters that see the same free memory cannot both start on it. The wait has the same no-memory exit 75 when no other bounded command runs.

While it waits for memory, the wrapper holds a wait file locked in the lock directory. The file names the budget, the slots, the start time, the directory, and the command. It holds no process ID. `--check` prints one `check waiting ... for=memory` line for each wait file, also without the hard cap, and removes a wait file that no process holds.

Fixes #80.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mand that waits for memory

The wrapper now gives back its slots while it waits for free memory, and `--check` prints a `check waiting ... for=memory` line for each such command. The skill did not say how to read that line.

The skill now gives the line format. It says that a waiting command holds no slot and has no place in the line, and that the line means the programs outside the queue hold the memory, not the slots. The report after a long wait, and the rule that a dispatcher gives its subagents, now name the waiting lines too. The lock files now carry no claim that they are empty, because a wait file names its command.

In a baseline, agents that read a check with two waiting lines took them as an order in the line ("head of the line", "behind us"). One blamed the only running bounded command for the shortage. With the new text, each agent read the lines correctly and told its dispatcher that programs outside the queue hold the memory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a stale name

A waiter puts its wait file at the same name each time it gives back its slots. A check can lock a stale file at that name, or create an empty one there in the moment after the waiter removed its old file. The waiter's rename can then put its new, held file at the name before the check removes the name, so the check deleted the live file and the waiter dropped out of `--check`.

The check now removes the name only when the file at the name is still the file that it locked. Otherwise it skips the name in this pass. Run files need no such change: each unit makes its run file once and never makes it again under the same name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Waiter ages can reset, and the lock-file guidance inaccurately generalizes slot-lock behavior to wait files.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Fixes memory waiters blocking smaller queued commands by releasing slots and exposing waiters through --check.

Changes:

  • Releases and reacquires slots around memory waits.
  • Adds locked waiter metadata and waiting check lines.
  • Documents and tests the updated queue behavior.
File Description
skills/​bounding-heavy-commands/​SKILL.md Documents memory waiting and check output.
skills/​bounding-heavy-commands/​scripts/​bounded_run.py Implements slot release and waiter tracking.
skills/​bounding-heavy-commands/​scripts/​tests/​test_bounded_run.py Covers wait files, reporting, and queue progress.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread skills/bounding-heavy-commands/scripts/bounded_run.py
Comment thread skills/bounding-heavy-commands/SKILL.md Outdated
sourcehawk and others added 2 commits October 6, 2026 10:23
…cross its waits

A waiter wrote a new start time to its wait file each time it gave back its slots. When the memory went away again after the slots came back, the `age` on its `check waiting` line started again at 0, and a command that waited for a long time looked new.

The wrapper now keeps the time of its first wait for memory and writes it to each new wait file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rule about the lock files said that a deleted lock file makes two commands hold one slot. That is true of a slot file only. A deleted wait file hides a command that waits for memory from `--check`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:23
@sourcehawk

Copy link
Copy Markdown
Owner Author

Review round 1 (review 5426322724, head 282db8d). Review base: 282db8d.

  • Preserve the wait duration across wait-file rewrites: applied in b2f088b, with a regression test.
  • Clarify what deleting a wait file does, as opposed to a slot file: applied in 1ae738a.

The review had no suppressed comments.

Comment gate (comment_growth.py 282db8d): 0 flagged, 1 listed.

Block Lines (base → now) Verdict Judge
bounded_run.py, comment on waiting_since 0 → 1 (ADDED) Keep: it gives the reason that the start time lives outside the wait-file write, which the code at that line cannot show self (no flag)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The process test can intermittently check the queue before the waiter has published its wait file.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Synchronize after creating the wait file

skills/​bounding-heavy-commands/​scripts/​tests/​test_bounded_run.py:3181

This synchronization point fires before the waiter releases its slots and creates the wait file: wait_for_memory logs ; waiting before calling release(). The following --check can therefore run first and intermittently miss the expected waiting line. Wait for the wait-*.lock file to appear before returning from this helper.

…mory-waiter tests

The test helper waited for the "; waiting" line of the large command. The wrapper prints that line before it gives back its slots and writes its wait file, so a check that ran at once could miss the `waiting` line. The helper now also waits until the wait file exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcehawk

Copy link
Copy Markdown
Owner Author

Review round 2 (review 5426377966, head 1ae738a). Review base: 282db8d.

The review posted no inline findings. Its body has one "Previously missed" finding, which has no thread to reply in:

  • Synchronize after creating the wait file (test_bounded_run.py:3181): applied in d39bd63. The memory_waiter helper now waits until the wait-*.lock file exists, as well as the "; waiting" line. The wrapper prints that line before it gives back its slots and writes the file, so a --check that ran at once could miss the waiting line.

Comment gate (comment_growth.py 282db8d): 0 flagged, 2 listed.

Block Lines (base → now) Verdict Judge
test_bounded_run.py, comment before the wait-file poll 0 → 1 (ADDED) Keep: it gives the order of events in the wrapper that makes the second wait necessary, which the test cannot show self (no flag)
bounded_run.py, comment on waiting_since 0 → 1 (ADDED) Keep, unchanged since round 1 self (no flag)

Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation matches issue #80 and includes thorough coverage of the new concurrency and cleanup behavior.

Review effort: Balanced
Findings: None

…ry wait's docstring

The docstring of `wait_for_memory` said that the call returns at once when the free memory cannot be read. Since the wait gives back its slots, the call first takes them back with `hold`, which can wait in the line. It also said that NoMemory leaves the slots as they are, which a caller can read as "still held". NoMemory can come after the call gave the slots back.

The docstring now says that the call returns without a wait for memory when the free memory cannot be read, and that NoMemory can come after the call gave the slots back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:35
@sourcehawk

Copy link
Copy Markdown
Owner Author

Review round 3 (review 5426419391, head d39bd63): "Approval recommended", no findings, inline or in the body.

Before handing back, I ran the comment gate against origin/main. It flagged two docstrings that this PR grew. A fresh evaluator judged both:

Block Lines (base → now) Sentence Verdict Source / caller code Judge
module usage text (text that users read) 90 → 94 "one waiting line for each bounded command that waits for memory" KEEP check() prints the waiting lines between the held and budget lines; a parser must expect them evaluator
module usage text 90 → 94 the check waiting format line KEEP the format string in memory_waiters evaluator
module usage text 90 → 94 "A command that waits for memory holds none of the slots of its waiting line, …" KEEP give_back_slots releases before it writes the wait file evaluator
wait_for_memory docstring 8 → 14 "Returns at once when the free memory cannot be read." (from main) CUT, false now: after a give-back the call runs hold() first. Replaced in e20798e with "When the free memory cannot be read, the call returns without a wait for memory." (KEEP) fits = available is None or … then return evaluator
wait_for_memory docstring 8 → 14 "NoMemory leaves the slots as they are." CUT, false: the slots can already be given back. Replaced in e20798e with "NoMemory can come after the call gave the slots back." (KEEP) the NoMemory handler cannot assume that the slots are still held evaluator
wait_for_memory docstring 8 → 14 "…so the caller gives back only the slots that it still holds." ASK, no source: removed evaluator
wait_for_memory docstring 8 → 14 "…so that a smaller command can start with them", "…and reads the memory again before it returns", "hold can wait in the line for minutes" Proposals only (CUT/MOVE as descriptions of the body). Not applied: this is true text that the PR wrote before the review evaluator

The PR body is reconciled: it now has the kept start time, the wait-file sentence of the lock-file rule, and 379 tests.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation satisfies the issue criteria with comprehensive unit and process-level regression coverage.

Review effort: Balanced
Findings: None

@sourcehawk sourcehawk changed the title fix(skills/bounding-heavy-commands): stop memory waiters from holding slots fix(skills/bounding-heavy-commands): show commands that wait for memory in the check Oct 6, 2026
sourcehawk and others added 2 commits October 6, 2026 15:39
… waits for memory

Giving back the slots while a command waits for memory breaks the fairness of the line. The large waiter loses its turn, and smaller commands that fit can take the memory each time it comes free, so the large one can wait without end. The case that blocked the queue with no bounded command running already ends with exit 75. The wait that is left lasts until a running bounded command frees its memory, and holding the slots there keeps the line fair.

A command that waits for memory now keeps its slots and its turn, as before this branch. `wait_for_memory` has the contract and tests that it had on main, plus one `on_wait` callback that runs before each sleep. The wrapper uses it to write its wait file once, and removes the file when the wait ends. So `--check` still prints a `waiting` line, and its `slots` are the slots that the waiter holds. Because the wait file is now written once for each wait, the start time needs no copy and the reader needs no check that the name still holds the file that it locked.

The process test now checks that `free-slots` counts the slots of the waiter beside a holder, and the test that a smaller command starts before the waiter is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill said that a command that waits for memory gives its slots back, leaves the line, and lets smaller commands start before it. The wrapper no longer does that.

The skill now says that a `waiting` line is a command that had its turn and keeps its slots while it waits, so it does not lose its turn to a command behind it. It waits for the memory that a running bounded command of a `held` line frees when it ends, because the programs outside the queue hold most of the memory. It starts when that memory is free, or exits 75 when no bounded command runs any more. The report after a long wait and the rule for subagents say that the machine is short of memory, not of slots.

With this text, isolated agents that read a check with one `held` line and one `waiting` line each said that the waiting command holds its slots and keeps its turn, and that it starts when the held command ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 14:40
@sourcehawk

Copy link
Copy Markdown
Owner Author

Scope change (ce84ad9, fdbf4b9): this PR is now visibility only. A command that waits for memory keeps its slots and its turn, as on main. The give-back of slots was dropped to keep the line fair; the PR body says why. The new commits are added on top, with no force-push, so the review history stays.

Comment gate against origin/main: 2 flagged. A fresh evaluator judged the new sentences:

Block Sentence Verdict Source / caller code Judge
module usage text (text that users read) "A waiting line is a command that holds its slots and its place in the line, and waits for memory that a running bounded command will free." CUT, false: the wait file is written after reserve() returns, when the waiter has already released line.lock. Replaced with "A waiting line is a command that holds its slots and waits for memory." (KEEP) reservation = reserve(...) held until reservation.release(); mark_waiting runs only from on_wait evaluator
module usage text "…that a running bounded command will free." ASK, no source (read_running can be None): removed evaluator
wait_for_memory docstring "on_wait is called, with lock released, before each sleep of the wait." KEEP unlock(); on_wait(); sleep(...); the caller must make it safe to call many times and must not need the lock evaluator

The same correction went into SKILL.md. The waiter "keeps its slots while it waits, so it does not lose its turn to a command behind it in the line". GREEN ran again on that text: 3 of 3 isolated agents said the waiter holds its slots and keeps its turn.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The guidance has inaccurate waiting outcomes, and the PR closes #80 without meeting all acceptance criteria.

Review effort: Balanced
Findings: 1 Low severity

Open (1)

Comment thread skills/bounding-heavy-commands/SKILL.md Outdated
…mand, which has a held line only under the cap

The skill said that a waiting command waits for the memory of a running command of a `held` line, and that the waiting commands start when the `held` commands end. Without the hard cap there are no `held` lines, but `--check` still prints the `waiting` lines.

The skill now says that the waiter waits for a running bounded command, and that this command has a `held` line where the hard cap is available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 14:44
@sourcehawk

Copy link
Copy Markdown
Owner Author

Review round (review 5430138547, head fdbf4b9): 1 inline finding, none in the body.

  • Clarify the waiting dependency where no hard cap means no held lines: applied in 1fedfd5, at lines 68 and 162. Line 196 already did not depend on a held line; the thread reply says why.

The review overview also says that the PR closes #80 without meeting all its acceptance criteria. That is deliberate: criterion 2 (a smaller command that fits starts before a waiter) was dropped to keep the line fair, as the Description says. The decision about #80 is with the maintainer.

Comment gate: this round changed no code comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It closes issue #80 while explicitly leaving one of that issue’s acceptance criteria unmet.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity PR closes #80 while its acceptance criterion remains unresolved

skills/​bounding-heavy-commands/​SKILL.md:68

This now codifies the opposite of issue #80's second acceptance criterion: the waiter keeps its slots, so a smaller command that fits can still be blocked. Because the PR says Fixes #80, merging it would close the issue while that required behavior remains unresolved. Either update/supersede that acceptance criterion before merge, or stop closing #80 and track this visibility-only change separately.

@sourcehawk

Copy link
Copy Markdown
Owner Author

Review round (review 5430194018, head 1fedfd5): no inline findings. The body has one "Previously missed" finding, which has no thread:

@sourcehawk

Copy link
Copy Markdown
Owner Author

Follow-up to the "Previously missed" finding of review 5430194018 (PR closes #80 while its acceptance criterion remains unresolved): #80 is updated. Its criterion 2 is dropped, and #80 (comment) records why and links ce84ad9. The criteria are now the waiting line of --check, and the skill text about how to read it. This PR meets both, so Fixes #80 stays. The PR body already matches the new criteria, so it does not change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation satisfies the visibility requirements with appropriate cleanup, documentation, and cross-platform test coverage.

Review effort: Balanced
Findings: None

@sourcehawk
sourcehawk merged commit 00a07d2 into main Oct 6, 2026
7 checks passed
@sourcehawk
sourcehawk deleted the fix/memory-waiters-hidden-holding-slots branch October 6, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A command that holds slots while it waits for memory blocks the queue, and the check does not show it

2 participants