The owner's $250 cloud-session credit (Max, expires 2026-11-04 11:59 PM PT) covers Claude Code cloud sessions started "from web, desktop, CLI, or mobile" (terms; Routines, Remote Control and Chat are excluded). This makes it a second build lane beside the API worker.
How a card moves
Astra's live run labels the best-ranked actionable cards of the lane's sizes (M) cloud, until per_day (3) are in the lane. This happens before the API worker's picks. A cloud card counts as lined up, so it's never queued for the API worker.
A launcher on hb-5 starts one claude --cloud session per cloud card, on the GitHub copy, with the card as the prompt.
It runs as a user agent in the desktop session, which is where the claude.ai sign-in lives; SSH sessions can't read that keychain.
It reads Teahub's public issue list and holds no Teahub credentials.
The launcher lives on the Mac, not in this repo.
The session pushes claude/card-N-*. Here, sync.py brings the branch home as a PR that closes the card, adding Closes #N when the commits don't. It then moves the card from cloud to ready, leaving a note without a beat.
The heartbeat adopts the PR (the card is ready with no beats and an open PR closes it). The council reviews and time lands it.
A card the commits close leaves the lane too, whatever the session called its branch.
Changes
router.py: cloud config (per_day, sizes ⊆ S/M; default per_day: 0, no lane); pick_cloud(); cloud added to LINED_UP; the table shows "to the cloud today" and "cloud lane".
astra.py: cloud picks first, then the API picks from the rest. It labels cloud in live mode and says "To the cloud lane:" in the log; the header shows the lane's fill; the beat carries cloud / would_cloud.
sync.py: card_of(), hand_back(), Closes #N in the PR body, and hand-back for every card the branch or commits name.
Tests: 80 covers config, pick_cloud, the lane being full, the table, and live/dry Astra with the lane. 83 covers card_of, PR bodies, hand-back (and a card not in the lane staying put), and a branch the session named itself.
test_time.py is green locally on current main (24 sections; the havre step is skipped without npm here, and CI runs it).
Pacing: the lane holds 3 cards at a time, and the launcher starts at most 3 sessions a day and stops before the credit lapses. Per-session cost isn't visible to Time; the owner's balance after the first day sets the pace.
The owner's $250 cloud-session credit (Max, expires 2026-11-04 11:59 PM PT) covers Claude Code cloud sessions started "from web, desktop, CLI, or mobile" ([terms](https://support.claude.com/en/articles/17152539-cloud-sessions-bonus-credit-promotion); Routines, Remote Control and Chat are excluded). This makes it a second build lane beside the API worker.
**How a card moves**
1. Astra's live run labels the best-ranked actionable cards of the lane's sizes (`M`) `cloud`, until `per_day` (3) are in the lane. This happens before the API worker's picks. A `cloud` card counts as lined up, so it's never queued for the API worker.
2. A launcher on hb-5 starts one `claude --cloud` session per `cloud` card, on the GitHub copy, with the card as the prompt.
- It runs as a user agent in the desktop session, which is where the claude.ai sign-in lives; SSH sessions can't read that keychain.
- It reads Teahub's public issue list and holds no Teahub credentials.
- The launcher lives on the Mac, not in this repo.
3. The session pushes `claude/card-N-*`. Here, `sync.py` brings the branch home as a PR that closes the card, adding `Closes #N` when the commits don't. It then moves the card from `cloud` to `ready`, leaving a note without a beat.
4. The heartbeat adopts the PR (the card is `ready` with no beats and an open PR closes it). The council reviews and `time` lands it.
5. A card the commits close leaves the lane too, whatever the session called its branch.
**Changes**
- `router.py`: `cloud` config (`per_day`, `sizes` ⊆ S/M; default `per_day: 0`, no lane); `pick_cloud()`; `cloud` added to `LINED_UP`; the table shows "to the cloud today" and "cloud lane".
- `astra.py`: cloud picks first, then the API picks from the rest. It labels `cloud` in live mode and says "To the cloud lane:" in the log; the header shows the lane's fill; the beat carries `cloud` / `would_cloud`.
- `sync.py`: `card_of()`, `hand_back()`, `Closes #N` in the PR body, and hand-back for every card the branch or commits name.
- `time/router.json`: `"cloud": {"per_day": 3, "sizes": ["M"]}`.
- Tests: 80 covers config, `pick_cloud`, the lane being full, the table, and live/dry Astra with the lane. 83 covers `card_of`, PR bodies, hand-back (and a card not in the lane staying put), and a branch the session named itself.
`test_time.py` is green locally on current main (24 sections; the havre step is skipped without npm here, and CI runs it).
Pacing: the lane holds 3 cards at a time, and the launcher starts at most 3 sessions a day and stops before the credit lapses. Per-session cost isn't visible to Time; the owner's balance after the first day sets the pace.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The owner's $250 cloud-session credit (Max, expires 2026-11-04 11:59 PM PT) covers Claude Code cloud sessions started "from web, desktop, CLI, or mobile" (terms; Routines, Remote Control and Chat are excluded). This makes it a second build lane beside the API worker.
How a card moves
M)cloud, untilper_day(3) are in the lane. This happens before the API worker's picks. Acloudcard counts as lined up, so it's never queued for the API worker.claude --cloudsession percloudcard, on the GitHub copy, with the card as the prompt.claude/card-N-*. Here,sync.pybrings the branch home as a PR that closes the card, addingCloses #Nwhen the commits don't. It then moves the card fromcloudtoready, leaving a note without a beat.readywith no beats and an open PR closes it). The council reviews andtimelands it.Changes
router.py:cloudconfig (per_day,sizes⊆ S/M; defaultper_day: 0, no lane);pick_cloud();cloudadded toLINED_UP; the table shows "to the cloud today" and "cloud lane".astra.py: cloud picks first, then the API picks from the rest. It labelscloudin live mode and says "To the cloud lane:" in the log; the header shows the lane's fill; the beat carriescloud/would_cloud.sync.py:card_of(),hand_back(),Closes #Nin the PR body, and hand-back for every card the branch or commits name.time/router.json:"cloud": {"per_day": 3, "sizes": ["M"]}.pick_cloud, the lane being full, the table, and live/dry Astra with the lane. 83 coverscard_of, PR bodies, hand-back (and a card not in the lane staying put), and a branch the session named itself.test_time.pyis green locally on current main (24 sections; the havre step is skipped without npm here, and CI runs it).Pacing: the lane holds 3 cards at a time, and the launcher starts at most 3 sessions a day and stops before the credit lapses. Per-session cost isn't visible to Time; the owner's balance after the first day sets the pace.
🤖 Generated with Claude Code
https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
cloudcards are lined up, the table marks them) 8223388b8b