CHANGELOG: the cloud lane
ci / test (pull_request) Successful in 44s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
This commit is contained in:
teahubxyzandClaude Opus 5.5 committed 2026-10-19 16:42:43 +00:00
1 parent 0461be8c71
commit 11232e1573
1 file changed
+1
+1
View File
@@ -28,6 +28,7 @@
- The second fix round escalates to a different model from the first round: `claude:opus` with the Claude seats (it was `venice:kimi code`, and grok 4.7 before that). `TIME_VENTURE_ESCALATE` overrides it. - The second fix round escalates to a different model from the first round: `claude:opus` with the Claude seats (it was `venice:kimi code`, and grok 4.7 before that). `TIME_VENTURE_ESCALATE` overrides it.
### Added ### Added
- The cloud lane: Claude Code cloud sessions build cards on the owner's cloud-session credit, beside the API worker. With `cloud: {"per_day": N, "sizes": ["M"]}` in `time/router.json`, Astra's live run labels the best-ranked actionable cards of those sizes `cloud`, until N cards are in the lane, before the API worker's picks; a `cloud` card is lined up, so it is never queued for the API worker. A launcher on hb-5 (a user agent in the desktop session, where the claude.ai sign-in lives; it reads the public issue list and holds no Teahub credentials) starts one `claude --cloud` session per card, with the card as the prompt, on the GitHub copy. The session pushes a `claude/card-N-*` branch; sync.py brings it home as a PR that closes the card (adding `Closes #N` when the commits don't), moves the card from `cloud` to `ready` with a note that carries no beat, and the heartbeat adopts the PR, so the council reviews and lands it like any card PR. A card the commits close leaves the lane too, whatever the branch is called. `per_day: 0`, the default, is no lane.
- Time brings a PR branch up to date with its base (#123). An approved PR Gitea reports as not `mergeable` (or whose merge call answers 409) gets `git merge origin/<base>` (the PR's own base) pushed as one commit by Time, `card #N: merge <base>`; CI runs on the new head and landing follows on a later beat (a `ci-dispatched` or `updated` beat now reaches `land()` again). A conflict is not guessed at: the merge is aborted, the branch left as it was, and the conflicting files and `<<<<<<<` blocks (trimmed) are posted on the card and the PR with the `needs-human` label; `/time retry` hands them to the worker as the reviewer's text. Known limit: the worker's `write_file` of a resolved file is a plain commit, not the merge, so Time's next try can conflict again. - Time brings a PR branch up to date with its base (#123). An approved PR Gitea reports as not `mergeable` (or whose merge call answers 409) gets `git merge origin/<base>` (the PR's own base) pushed as one commit by Time, `card #N: merge <base>`; CI runs on the new head and landing follows on a later beat (a `ci-dispatched` or `updated` beat now reaches `land()` again). A conflict is not guessed at: the merge is aborted, the branch left as it was, and the conflicting files and `<<<<<<<` blocks (trimmed) are posted on the card and the PR with the `needs-human` label; `/time retry` hands them to the worker as the reviewer's text. Known limit: the worker's `write_file` of a resolved file is a plain commit, not the merge, so Time's next try can conflict again.
- A card waiting on a person holds no `ready` slot (#135). `promote_next` counts toward `TIME_PARALLEL` only the `ready` cards that are moving; one whose last real beat is `needs-human`, `error` or `stuck` (an objection, or a failure Time won't retry again) keeps its label, so `/time retry` still finds it, but frees its slot. Three such cards held every slot twice, the second time for ten hours on 2026-10-19 while 22 cards sat `queued`. The run log prints `promote: ready=N moving=M`. - A card waiting on a person holds no `ready` slot (#135). `promote_next` counts toward `TIME_PARALLEL` only the `ready` cards that are moving; one whose last real beat is `needs-human`, `error` or `stuck` (an objection, or a failure Time won't retry again) keeps its label, so `/time retry` still finds it, but frees its slot. Three such cards held every slot twice, the second time for ten hours on 2026-10-19 while 22 cards sat `queued`. The run log prints `promote: ready=N moving=M`.
- Sync (`sync.py`, `time/sync.json`, the `sync` workflow): a repo's copies on other forges follow its home, under one rule, one home per repo and one writer per branch. The home's `main` (the `out` branches) goes to every copy fast-forward only; a copy with commits of its own is reported in a `needs-human` issue and never overwritten, unless the `force` dispatch input replaces its history once. A copy's branches matching its `import` patterns (`claude/*` from cloud sessions) are the copy's to write: they come back to the home under the same name with a PR, commit messages in the body (so a `Closes #N` adopts the card), and Time's council reviews and lands them; a branch already in `main`, or one whose PR was closed at the same commit, is left alone. Gitea's own mirrors don't fit: a push mirror force-pushes every branch (it would delete the copy's work branches) and a pull mirror makes the home read-only. Tokens are secrets named in the file, scrubbed from every message; a copy can instead use an SSH deploy key (`ssh_key`: write access to that one repo and nothing else), with the host's keys pinned, never learned on first use. Gitea refuses secret names starting `GITHUB_` or `GITEA_`, so the names start `SYNC_`. The first copy is `github.com/teahubxyz/time`, so cloud sessions clone a current engine; it pushes with the deploy key in `SYNC_GITHUB_KEY`, and GitHub Actions is off on it. The same script syncs Gitea with Gitea, run from whichever side can reach both. - Sync (`sync.py`, `time/sync.json`, the `sync` workflow): a repo's copies on other forges follow its home, under one rule, one home per repo and one writer per branch. The home's `main` (the `out` branches) goes to every copy fast-forward only; a copy with commits of its own is reported in a `needs-human` issue and never overwritten, unless the `force` dispatch input replaces its history once. A copy's branches matching its `import` patterns (`claude/*` from cloud sessions) are the copy's to write: they come back to the home under the same name with a PR, commit messages in the body (so a `Closes #N` adopts the card), and Time's council reviews and lands them; a branch already in `main`, or one whose PR was closed at the same commit, is left alone. Gitea's own mirrors don't fit: a push mirror force-pushes every branch (it would delete the copy's work branches) and a pull mirror makes the home read-only. Tokens are secrets named in the file, scrubbed from every message; a copy can instead use an SSH deploy key (`ssh_key`: write access to that one repo and nothing else), with the host's keys pinned, never learned on first use. Gitea refuses secret names starting `GITHUB_` or `GITEA_`, so the names start `SYNC_`. The first copy is `github.com/teahubxyz/time`, so cloud sessions clone a current engine; it pushes with the deploy key in `SYNC_GITHUB_KEY`, and GitHub Actions is off on it. The same script syncs Gitea with Gitea, run from whichever side can reach both.