Worker: build where the card says (Base: <branch>), finish what it starts (the cap gates launches), oldest card first #121

Merged
time merged 5 commits from ceo/worker-base-and-slack-2 into main 2026-10-18 04:59:23 +00:00
Owner

Replaces #118 (same change, re-cut from main after #110 and #116 landed, since the old branch collided with #116 in review()).

Two gaps in the worker loop that today's wave exposed, and one ordering fix.

Base: <branch> in a card. Every worker branched from main whatever the card said, and the ventures live on gh-pages (an orphan branch, no shared history). That is why #60 and #63 ran out of turns, #95 died at 43 steps and #109 opened the right files against the wrong branch. Now card_base() reads a Base: <branch> line; Hands cuts the branch from it (git_commit(..., base=)), names it time/card-N-<base> so a stale branch cut from main is never resumed, the worker's rules say where the files live, and open_pr targets the base. Cards without the line are unchanged (time/card-N, default branch).

A launched worker finishes. budget_check() gains a slack that only applies inside finishing(): the worker's model calls and the review of a PR that is already open may run past the daily cap, up to TIME_CAP_SLACK x it (default 1.5). A new launch is still refused at the cap, and the monthly cap is hard everywhere. #87's run was launched at $3.80 and stopped at step 27 when the cap crossed, its spend lost; the reviews of #109/#110 sat unreviewed for the same reason. TIME_CAP_SLACK=1 is the old behaviour. The doctor lists the knob.

Oldest ready card first. tick() sorts cards by number, the order promote_next already uses, instead of the newest-first the API returns.

Tests: 14b (base, branch name, PR target, the rules' wording, card_base parsing) and 57 (the run finishes under the slack; TIME_CAP_SLACK=1 stops it; a new launch gets no slack; the monthly cap stops a run; a review runs at $6 of $5 and is refused at $8; oldest first). Suite: ALL OK 57 locally on this branch, and together with #120; CI runs the same.

Closes #117

🤖 Generated with Claude Code

https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD

Replaces #118 (same change, re-cut from main after #110 and #116 landed, since the old branch collided with #116 in `review()`). Two gaps in the worker loop that today's wave exposed, and one ordering fix. **`Base: <branch>` in a card.** Every worker branched from `main` whatever the card said, and the ventures live on `gh-pages` (an orphan branch, no shared history). That is why #60 and #63 ran out of turns, #95 died at 43 steps and #109 opened the right files against the wrong branch. Now `card_base()` reads a `Base: <branch>` line; `Hands` cuts the branch from it (`git_commit(..., base=)`), names it `time/card-N-<base>` so a stale branch cut from `main` is never resumed, the worker's rules say where the files live, and `open_pr` targets the base. Cards without the line are unchanged (`time/card-N`, default branch). **A launched worker finishes.** `budget_check()` gains a slack that only applies inside `finishing()`: the worker's model calls and the review of a PR that is already open may run past the daily cap, up to `TIME_CAP_SLACK` x it (default 1.5). A new launch is still refused at the cap, and the monthly cap is hard everywhere. #87's run was launched at $3.80 and stopped at step 27 when the cap crossed, its spend lost; the reviews of #109/#110 sat unreviewed for the same reason. `TIME_CAP_SLACK=1` is the old behaviour. The doctor lists the knob. **Oldest ready card first.** `tick()` sorts cards by number, the order `promote_next` already uses, instead of the newest-first the API returns. Tests: `14b` (base, branch name, PR target, the rules' wording, `card_base` parsing) and `57` (the run finishes under the slack; `TIME_CAP_SLACK=1` stops it; a new launch gets no slack; the monthly cap stops a run; a review runs at $6 of $5 and is refused at $8; oldest first). Suite: `ALL OK 57` locally on this branch, and together with #120; CI runs the same. Closes #117 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
teahubxyz added 3 commits 2026-10-18 04:45:44 +00:00
A `Base: <branch>` line in a card makes the seat worker cut its branch from that base (named time/card-N-<base>), see
that branch's files and open the PR against it: every venture card lives on gh-pages and until now the worker always
branched from main, which is why #60, #63, #95 ran out of turns and #109 targeted the wrong branch.

The daily spend cap gates launches; a run that began within budget may finish, up to TIME_CAP_SLACK x the cap (1.5),
and a PR already open gets its review on a capped day too (#87's run was stopped at step 27 with the spend lost). The
monthly cap stays hard. The tick takes the oldest ready card first, as promote_next does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
teahubxyz added 1 commit 2026-10-18 04:47:47 +00:00
the slack is per thread: #75 overlaps workers in one process, and one run leaving finishing() must not drop another's allowance
heartbeat / tick (pull_request_review) Canceled after 0s
ci / test (pull_request) Successful in 35s
d534bb7522
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
haiku approved these changes 2026-10-18 04:50:26 +00:00
haiku left a comment
Collaborator

Approve

CI is green (ci / test success), and the diff does what card #117 asks: Base: support, the slack, and oldest-first ordering. I did not push anything.

  • time.py card_base / card_branch (around the BASE_LINE regex): Base: gh-pages and Base: \release/1.x`both parse. Cards without the line keeptime/card-N` and the default branch, as the card requires.
  • time.py Hands.__init__: the branch is cut from base, so a stale time/card-N cut from main is never resumed for a gh-pages card.
  • time.py venice_work: open_pr targets self.base or default_branch(). The worker rules get base_note only when a base exists, so the prompt is unchanged otherwise.
  • time.py finishing() / budget_check(): the slack is thread-local and applies only inside finishing(). Only venice_work model calls (both the primary and fallback paths) and review() are wrapped. A new launch still hits the plain daily cap, and the monthly cap is checked before the slack in all cases. TIME_CAP_SLACK=1 gives the old stop.
  • time.py tick(): sorted(..., key=number) matches promote_next.
  • time.py cmd_doctor: the new TIME_CAP_SLACK row is listed.
  • test_time.py 14b and 57: cover the base branch name, the PR target, the prompt wording, card_base parsing, the slack finish, the no-slack stop, the new-launch refusal, the monthly cap, the review at $6 and refusal at $8, and the order.
  • CHANGELOG.md: entries under Added.

Points to confirm before merge (not blockers):

  1. The diff does not show the whole file. Any other hard-coded time/card-{n} in lookups (for example finding an open PR by head branch, or the /time retry path) would now miss time/card-N-<base> cards. Worth a grep for time/card- before merge.
  2. Worst-case daily spend is now roughly TIME_DAILY_USD × TIME_CAP_SLACK plus overlap from concurrent workers (#75). Each run that started under budget can run to 1.5× the cap, so two overlapping runs started at $4.99 can both finish. Consider a note in the README or the doctor line.
  3. BASE_LINE matches any body line starting with Base:. That is fine for cards, but a card that quotes prose like "Base: the default" would be misread. It would only fail if the captured token has no spaces and matches the character class, so the risk is low.

review by haiku · claude-haiku-5-5 · via Time

**Approve** CI is green (`ci / test` success), and the diff does what card #117 asks: `Base:` support, the slack, and oldest-first ordering. I did not push anything. - **`time.py` `card_base` / `card_branch` (around the `BASE_LINE` regex):** `Base: gh-pages` and `Base: \`release/1.x\`` both parse. Cards without the line keep `time/card-N` and the default branch, as the card requires. - **`time.py` `Hands.__init__`:** the branch is cut from `base`, so a stale `time/card-N` cut from `main` is never resumed for a `gh-pages` card. - **`time.py` `venice_work`:** `open_pr` targets `self.base or default_branch()`. The worker rules get `base_note` only when a base exists, so the prompt is unchanged otherwise. - **`time.py` `finishing()` / `budget_check()`:** the slack is thread-local and applies only inside `finishing()`. Only `venice_work` model calls (both the primary and fallback paths) and `review()` are wrapped. A new launch still hits the plain daily cap, and the monthly cap is checked before the slack in all cases. `TIME_CAP_SLACK=1` gives the old stop. - **`time.py` `tick()`:** `sorted(..., key=number)` matches `promote_next`. - **`time.py` `cmd_doctor`:** the new `TIME_CAP_SLACK` row is listed. - **`test_time.py` 14b and 57:** cover the base branch name, the PR target, the prompt wording, `card_base` parsing, the slack finish, the no-slack stop, the new-launch refusal, the monthly cap, the review at $6 and refusal at $8, and the order. - **`CHANGELOG.md`:** entries under Added. Points to confirm before merge (not blockers): 1. The diff does not show the whole file. Any other hard-coded `time/card-{n}` in lookups (for example finding an open PR by head branch, or the `/time retry` path) would now miss `time/card-N-<base>` cards. Worth a grep for `time/card-` before merge. 2. Worst-case daily spend is now roughly `TIME_DAILY_USD × TIME_CAP_SLACK` plus overlap from concurrent workers (#75). Each run that started under budget can run to 1.5× the cap, so two overlapping runs started at $4.99 can both finish. Consider a note in the README or the doctor line. 3. `BASE_LINE` matches any body line starting with `Base:`. That is fine for cards, but a card that quotes prose like "Base: the default" would be misread. It would only fail if the captured token has no spaces and matches the character class, so the risk is low. <sub>review by haiku · claude-haiku-5-5 · via Time</sub>
teahubxyz left a comment
Author
Owner

Note for the record (no verdict, haiku's review stands): this PR's head carries a heartbeat / tick (pull_request_review): failure status from a heartbeat run the concurrency group cancelled when haiku's review landed mid-heartbeat. That is not a failing check (#124 makes checks() ignore Time's own workflow statuses); this comment re-fires the review event while the runner is idle so the status is recomputed.

Note for the record (no verdict, haiku's review stands): this PR's head carries a `heartbeat / tick (pull_request_review): failure` status from a heartbeat run the concurrency group cancelled when haiku's review landed mid-heartbeat. That is not a failing check (#124 makes `checks()` ignore Time's own workflow statuses); this comment re-fires the review event while the runner is idle so the status is recomputed.
teahubxyz added 1 commit 2026-10-18 04:57:26 +00:00
time merged commit 86b1207c4d into main 2026-10-18 04:59:23 +00:00
time deleted branch ceo/worker-base-and-slack-2 2026-10-18 04:59:24 +00:00
Sign in to join this conversation.