{"slug": "spool-now-runs-claude-code-loops-on-anthropic-s-cloud", "title": "Spool now runs Claude Code loops on Anthropic's cloud", "summary": "Spool released v0.6.0, adding username-and-password sign-in, role-based access control, per-user private DM conversations, and audit records of who created or changed a loop, according to the GitHub release notes for enes-alatas/spool. The release also adds a spool-hook guard that refuses gh or git calls carrying a credential and blocks kills that reach other loops' processes, plus backup and restore scripts for the data directory and workstation volumes. The release notes list 23 changes, including CI actions pinned by commit SHA and a Dependabot bump of 7 updates.", "body_md": "## Repository navigation\n\n# Releases: enes-alatas/spool\n\nReleases · enes-alatas/spool\n\n## Release list\n\n## v0.6.0\n\n## What's Changed\n\n- feat(users): the hub has users who sign in with a username and password by [@enes-alatas](https://github.com/enes-alatas) in[#676](https://github.com/enes-alatas/spool/pull/676)\n- feat(web): users sign in with a username and password and change a one-time password by [@enes-alatas](https://github.com/enes-alatas) in[#678](https://github.com/enes-alatas/spool/pull/678)\n- feat(httpapi): roles bound what a user may call, and members read no owner DM by [@enes-alatas](https://github.com/enes-alatas) in[#681](https://github.com/enes-alatas/spool/pull/681)\n- docs(readme): the README names what v0.5.1 ships, and every release gets a README pass by [@enes-alatas](https://github.com/enes-alatas) in[#687](https://github.com/enes-alatas/spool/pull/687)\n- feat(web): a member sees only the actions their role allows by [@enes-alatas](https://github.com/enes-alatas) in[#688](https://github.com/enes-alatas/spool/pull/688)\n- feat(loop): the prompt no longer calls control_room private by [@enes-alatas](https://github.com/enes-alatas) in[#683](https://github.com/enes-alatas/spool/pull/683)\n- fix(httpapi): a member reads no pending sender's pairing code by [@enes-alatas](https://github.com/enes-alatas) in[#692](https://github.com/enes-alatas/spool/pull/692)\n- feat(httpapi): owners manage the hub's users on the API by [@enes-alatas](https://github.com/enes-alatas) in[#693](https://github.com/enes-alatas/spool/pull/693)\n- feat(httpapi): the hub records who changed a loop by [@enes-alatas](https://github.com/enes-alatas) in[#697](https://github.com/enes-alatas/spool/pull/697)\n- feat(hook): spool-hook refuses a gh or git call that carries a credential by [@enes-alatas](https://github.com/enes-alatas) in[#699](https://github.com/enes-alatas/spool/pull/699)\n- feat(hook): spool-hook refuses kills that reach other loops' processes by [@enes-alatas](https://github.com/enes-alatas) in[#701](https://github.com/enes-alatas/spool/pull/701)\n- docs(readme): the demo video shows v0.5.1's room by [@enes-alatas](https://github.com/enes-alatas) in[#694](https://github.com/enes-alatas/spool/pull/694)\n- feat(route): every allowed sender holds a private conversation on dm: by [@enes-alatas](https://github.com/enes-alatas) in[#705](https://github.com/enes-alatas/spool/pull/705)\n- feat(web): an owner manages the hub's users from Settings by [@enes-alatas](https://github.com/enes-alatas) in[#706](https://github.com/enes-alatas/spool/pull/706)\n- feat(web): the room shows who created and changed a loop by [@enes-alatas](https://github.com/enes-alatas) in[#717](https://github.com/enes-alatas/spool/pull/717)\n- feat(httpapi): a linked user reads the private conversations that are theirs by [@enes-alatas](https://github.com/enes-alatas) in[#709](https://github.com/enes-alatas/spool/pull/709)\n- feat(scripts): a backup and restore of the data directory and workstation volumes by [@enes-alatas](https://github.com/enes-alatas) in[#713](https://github.com/enes-alatas/spool/pull/713)\n- ci: every action is pinned by commit SHA by [@enes-alatas](https://github.com/enes-alatas) in[#716](https://github.com/enes-alatas/spool/pull/716)\n- fix(scripts): the secret guards refuse an awk without regex intervals by [@enes-alatas](https://github.com/enes-alatas) in[#724](https://github.com/enes-alatas/spool/pull/724)\n- feat(web): a user links their Telegram or Slack account from Settings by [@enes-alatas](https://github.com/enes-alatas) in[#727](https://github.com/enes-alatas/spool/pull/727)\n- docs: an upgrade runbook with its rollback by [@enes-alatas](https://github.com/enes-alatas) in[#725](https://github.com/enes-alatas/spool/pull/725)\n- ci: Bump the actions group across 1 directory with 7 updates by [@dependabot](https://github.com/dependabot) [bot] in[#726](https://github.com/enes-alatas/spool/pull/726)\n- feat(httpapi): serve a loop's private conversations per person by [@enes-alatas](https://github.com/enes-alatas) in[#734](https://github.com/enes-alatas/spool/pull/734)\n- feat(web): a loop's page shows the private conversations the reader may read by [@enes-alatas](https://github.com/enes-alatas) in[#735](https://github.com/enes-alatas/spool/pull/735)\n- docs: run the hub as a systemd service behind a TLS proxy by [@enes-alatas](https://github.com/enes-alatas) in[#736](https://github.com/enes-alatas/spool/pull/736)\n- ci: golangci-lint runs a pinned version, not latest by [@enes-alatas](https://github.com/enes-alatas) in[#739](https://github.com/enes-alatas/spool/pull/739)\n- ci: a daily advisory scan files each new advisory as a chore issue by [@enes-alatas](https://github.com/enes-alatas) in[#740](https://github.com/enes-alatas/spool/pull/740)\n- ci: a PR touching security-sensitive paths is labelled for review by [@enes-alatas](https://github.com/enes-alatas) in[#743](https://github.com/enes-alatas/spool/pull/743)\n- feat(docker): a rebuilt image reaches existing workstations and the proxy by [@enes-alatas](https://github.com/enes-alatas) in[#741](https://github.com/enes-alatas/spool/pull/741)\n- feat(route): a link code is the one way a person becomes an allowed sender by [@enes-alatas](https://github.com/enes-alatas) in[#746](https://github.com/enes-alatas/spool/pull/746)\n- docs(adr): a release ships built artifacts, and the operator verifies them by [@enes-alatas](https://github.com/enes-alatas) in[#749](https://github.com/enes-alatas/spool/pull/749)\n- feat(web): link a chat account through a dialog that waits for YES by [@enes-alatas](https://github.com/enes-alatas) in[#755](https://github.com/enes-alatas/spool/pull/755)\n- feat(release): a tag builds and attaches the spool binaries, with checksums and attestations by [@enes-alatas](https://github.com/enes-alatas) in[#757](https://github.com/enes-alatas/spool/pull/757)\n- feat(web): web addresses in messages and the timeline are links by [@enes-alatas](https://github.com/enes-alatas) in[#760](https://github.com/enes-alatas/spool/pull/760)\n- test(itest): the rebuilt-proxy row waits only for turns its messages began by [@enes-alatas](https://github.com/enes-alatas) in[#762](https://github.com/enes-alatas/spool/pull/762)\n- feat: a release publishes the images by digest, and the hub never pulls by [@enes-alatas](https://github.com/enes-alatas) in[#765](https://github.com/enes-alatas/spool/pull/765)\n- chore(docker): pin the workstation base by digest, and let Dependabot move it by [@enes-alatas](https://github.com/enes-alatas) in[#768](https://github.com/enes-alatas/spool/pull/768)\n- feat(web): a Fleet row shows its loop's mission on one line by [@enes-alatas](https://github.com/enes-alatas) in[#769](https://github.com/enes-alatas/spool/pull/769)\n- feat(httpapi): a loop's owner is chosen as a user, by their link on its surface by [@enes-alatas](https://github.com/enes-alatas) in[#771](https://github.com/enes-alatas/spool/pull/771)\n- ci(release): a release is smoke-tested on a fresh runner along the README's path by [@enes-alatas](https://github.com/enes-alatas) in[#772](https://github.com/enes-alatas/spool/pull/772)\n- feat(web): a loop's owner is picked as a user, by their link on its surface by [@enes-alatas](https://github.com/enes-alatas) in[#774](https://github.com/enes-alatas/spool/pull/774)\n- fix(route): a refused cross-conversation reply names no other conversation by [@enes-alatas](https://github.com/enes-alatas) in[#776](https://github.com/enes-alatas/spool/pull/776)\n- docs(security): SECURITY.md describes the hub as it is: users, sessions, link codes, verifying a release by [@enes-alatas](https://github.com/enes-alatas) in[#779](https://github.com/enes-alatas/spool/pull/779)\n- docs(security): a threat model of the hub, and a gate that keeps it current by [@enes-alatas](https://github.com/enes-alatas) in[#781](https://github.com/enes-alatas/spool/pull/781)\n- fix(httpapi): open streams read access outside the publish, and follow role changes by [@enes-alatas](https://github.com/enes-alatas) in[#785](https://github.com/enes-alatas/spool/pull/785)\n- docs(adr): ADR-0051 counts strangers' wrong codes per surface, and shows a pause by [@enes-alatas](https://github.com/enes-alatas) in[#791](https://github.com/enes-alatas/spool/pull/791)\n- feat(web): the first-run Chat surface step links the operator's account by [@enes-alatas](https://github.com/enes-alatas) in[#796](https://github.com/enes-alatas/spool/pull/796)\n- docs(readme): a five-step quick start, and link codes where pairing codes were by [@enes-alatas](https://github.com/enes-alatas) in[#802](https://github.com/enes-alatas/spool/pull/802)\n- docs(conventions): a removed or renamed flow takes the copy describing it along by [@enes-alatas](https://github.com/enes-alatas) in[#794](https://github.com/enes-alatas/spool/pull/794)\n- feat(httpapi): the hub serves Settings → Access: users with their links, block, remove a link, ignored strangers by [@enes-alatas](https://github.com/enes-alatas) in[#803](https://github.com/enes-alatas/spool/pull/803)\n- docs(threat-model): only an allowed sender binds a loop's fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#806](https://github.com/enes-alatas/spool/pull/806)\n- fix(httpapi): a sign-in's cookie lasts as long as an unused session by [@enes-alatas](https://github.com/enes-alatas) in[#807](https://github.com/enes-alatas/spool/pull/807)\n- feat(httpapi): the first-run page can be finished without a chat surface by [@enes-alatas](https://github.com/enes-alatas) in[#808](https://github.com/enes-alatas/spool/pull/808)\n- feat(web): the first-run page can be finished without a chat surface by [@enes-alatas](https://github.com/enes-alatas) in[#805](https://github.com/enes-alatas/spool/pull/805)\n- fix(telegram): wait longer for a send's answer, and log a retry that may have posted twice by [@enes-alatas](https://github.com/enes-alatas) in[#809](https://github.com/enes-alatas/spool/pull/809)\n- docs(readme): a short README, with the reference detail moved into docs/ by [@enes-alatas](https://github.com/enes-alatas) in[#812](https://github.com/enes-alatas/spool/pull/812)\n- fix(web): the first-run cards outline their second buttons in white by [@enes-alatas](https://github.com/enes-alatas) in[#813](https://github.com/enes-alatas/spool/pull/813)\n- fix(route): strangers' wrong codes pause linking on one surface, and an admin lifts it by [@enes-alatas](https://github.com/enes-alatas) in[#814](https://github.com/enes-alatas/spool/pull/814)\n- feat(loop): a loop says what it is working on with a [doing: …] trailer by [@enes-alatas](https://github.com/enes-alatas) in[#816](https://github.com/enes-alatas/spool/pull/816)\n- chore(web): the demo shows the first-run link flow by [@enes-alatas](https://github.com/enes-alatas) in[#815](https://github.com/enes-alatas/spool/pull/815)\n- fix(operations): the runbook says which lines need root, and a root restore leaves the keys to the hub by [@enes-alatas](https://github.com/enes-alatas) in[#784](https://github.com/enes-alatas/spool/pull/784)\n- feat(web): the Fleet row and the loop page show what a loop is doing by [@enes-alatas](https://github.com/enes-alatas) in[#821](https://github.com/enes-alatas/spool/pull/821)\n- feat(loop): the hub tells a loop's owner once when it keeps failing or its workstation stays down by [@enes-alatas](https://github.com/enes-alatas) in[#820](https://github.com/enes-alatas/spool/pull/820)\n- docs(telegram): a loop's reaction to a teammate shows in no group by [@enes-alatas](https://github.com/enes-alatas) in[#823](https://github.com/enes-alatas/spool/pull/823)\n- chore(ci): a merge queue in CI, in place of \"branches up to date\" by [@enes-alatas](https://github.com/enes-alatas) in[#822](https://github.com/enes-alatas/spool/pull/822)\n- fix(surface): a sender who isn't allowed costs the hub one log line an hour by [@enes-alatas](https://github.com/enes-alatas) in[#825](https://github.com/enes-alatas/spool/pull/825)\n- feat(web): the control room shows a loop's open problems by [@enes-alatas](https://github.com/enes-alatas) in[#826](https://github.com/enes-alatas/spool/pull/826)\n- test: pin the surface seam's guarantees that no test covered by [@enes-alatas](https://github.com/enes-alatas) in[#827](https://github.com/enes-alatas/spool/pull/827)\n- fix(telegram): a loop's reaction to a teammate's post stays on the hub in a supergroup too by [@enes-alatas](https://github.com/enes-alatas) in[#828](https://github.com/enes-alatas/spool/pull/828)\n- feat(web): Settings → Access lists users with their links and blocks them by [@enes-alatas](https://github.com/enes-alatas) in[#829](https://github.com/enes-alatas/spool/pull/829)\n- feat: an edited message reaches its loops as a correction that names the ref it edits by [@enes-alatas](https://github.com/enes-alatas) in[#832](https://github.com/enes-alatas/spool/pull/832)\n- chore(ci): the merge queue's order on a pinned noticeboard by [@enes-alatas](https://github.com/enes-alatas) in[https://gi](https://gi) ...\n\n[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.6.0)\n\n## v0.5.1\n\n## What's Changed\n\n- feat: the hub reads the Claude plan's usage from its loops' streams by [@enes-alatas](https://github.com/enes-alatas) in[#654](https://github.com/enes-alatas/spool/pull/654)\n- feat(web): each Fleet row shows its share of today's fleet spend by [@enes-alatas](https://github.com/enes-alatas) in[#656](https://github.com/enes-alatas/spool/pull/656)\n- fix(docker): every docker wake catches the proxy up with the fleet's hosts by [@enes-alatas](https://github.com/enes-alatas) in[#658](https://github.com/enes-alatas/spool/pull/658)\n- feat(web): the Fleet page shows the plan's 5-hour and 7-day usage by [@enes-alatas](https://github.com/enes-alatas) in[#659](https://github.com/enes-alatas/spool/pull/659)\n- test(itest): the live egress row counts only turns begun after each reach by [@enes-alatas](https://github.com/enes-alatas) in[#660](https://github.com/enes-alatas/spool/pull/660)\n- feat(web): a first-run step the hub is working on lists its phases by [@enes-alatas](https://github.com/enes-alatas) in[#663](https://github.com/enes-alatas/spool/pull/663)\n- fix: the toolchain moves to go1.26.9 for its standard-library fixes by [@enes-alatas](https://github.com/enes-alatas) in[#666](https://github.com/enes-alatas/spool/pull/666)\n- chore(web): bump source-map-js to 1.2.2 past its advisory by [@enes-alatas](https://github.com/enes-alatas) in[#669](https://github.com/enes-alatas/spool/pull/669)\n- ci: a feat PR that changes one half of the product names the other by [@enes-alatas](https://github.com/enes-alatas) in[#670](https://github.com/enes-alatas/spool/pull/670)\n- feat(httpapi): the onboarding read says what each waiting pillar is doing by [@enes-alatas](https://github.com/enes-alatas) in[#664](https://github.com/enes-alatas/spool/pull/664)\n- feat(loop): above the plan cap every loop sleeps until the window resets by [@enes-alatas](https://github.com/enes-alatas) in[#672](https://github.com/enes-alatas/spool/pull/672)\n- feat(web): the room sets the plan cap and shows a capped fleet by [@enes-alatas](https://github.com/enes-alatas) in[#671](https://github.com/enes-alatas/spool/pull/671)\n- fix(itest): the docker MCP-broker row skips on a host without docker by [@enes-alatas](https://github.com/enes-alatas) in[#673](https://github.com/enes-alatas/spool/pull/673)\n\n**Full Changelog**: `v0.5.0...v0.5.1`\n\n## v0.5.0\n\n## What's Changed\n\n- fix(loop): a rehomed loop is not_provisioned until its workstation is built by [@enes-alatas](https://github.com/enes-alatas) in[#641](https://github.com/enes-alatas/spool/pull/641)\n- feat(store): secrets sealed at rest under a hub key by [@enes-alatas](https://github.com/enes-alatas) in[#642](https://github.com/enes-alatas/spool/pull/642)\n- feat(loop): an env-var change reaches an awake loop from its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#644](https://github.com/enes-alatas/spool/pull/644)\n- feat(redact): the operator token and the hub key are redacted by [@enes-alatas](https://github.com/enes-alatas) in[#645](https://github.com/enes-alatas/spool/pull/645)\n\n**Full Changelog**: `v0.4.3...v0.5.0`\n\n## v0.4.3\n\n## What's Changed\n\n- refactor(egress): remove a loop's own egress entries by [@enes-alatas](https://github.com/enes-alatas) in[#633](https://github.com/enes-alatas/spool/pull/633)\n- feat(loop): the loop view says when a rehome is pending by [@enes-alatas](https://github.com/enes-alatas) in[#635](https://github.com/enes-alatas/spool/pull/635)\n- feat(web): a bare loop's Workstation panel moves it into a docker container by [@enes-alatas](https://github.com/enes-alatas) in[#637](https://github.com/enes-alatas/spool/pull/637)\n- fix(itest): the empty-context check no longer races a new loop's first tick by [@enes-alatas](https://github.com/enes-alatas) in[#638](https://github.com/enes-alatas/spool/pull/638)\n\n**Full Changelog**: `v0.4.2...v0.4.3`\n\n## v0.4.2\n\n## What's Changed\n\n- feat: every change to a connection is on an append-only record by [@enes-alatas](https://github.com/enes-alatas) in[#608](https://github.com/enes-alatas/spool/pull/608)\n- feat: rotating a connection's value retires the old one and ends the sessions that held it by [@enes-alatas](https://github.com/enes-alatas) in[#612](https://github.com/enes-alatas/spool/pull/612)\n- feat: revoking a connection takes it from every loop, refuses it from then on, and keeps it redacted by [@enes-alatas](https://github.com/enes-alatas) in[#613](https://github.com/enes-alatas/spool/pull/613)\n- fix(telegram): a loop's reaction to a teammate's message reaches a supergroup by [@enes-alatas](https://github.com/enes-alatas) in[#615](https://github.com/enes-alatas/spool/pull/615)\n- feat(web): a connection's record, rotate and revoke in the control room by [@enes-alatas](https://github.com/enes-alatas) in[#616](https://github.com/enes-alatas/spool/pull/616)\n- feat(web): a loop's Telegram notes say where a reaction to a teammate shows by [@enes-alatas](https://github.com/enes-alatas) in[#619](https://github.com/enes-alatas/spool/pull/619)\n- chore: remove the per-loop secrets routes; private env-var connections replace them by [@enes-alatas](https://github.com/enes-alatas) in[#618](https://github.com/enes-alatas/spool/pull/618)\n- feat: the fleet's extra egress hosts are stored and apply without a restart by [@enes-alatas](https://github.com/enes-alatas) in[#620](https://github.com/enes-alatas/spool/pull/620)\n- feat(web): Settings has an Egress section, the fleet's allowlist by [@enes-alatas](https://github.com/enes-alatas) in[#621](https://github.com/enes-alatas/spool/pull/621)\n- feat: secrets a loop holds don't reach teammates or hide in encodings; ADR-0045 states the posture by [@enes-alatas](https://github.com/enes-alatas) in[#625](https://github.com/enes-alatas/spool/pull/625)\n- feat(httpapi): the hub brokers a loop's http MCP servers by [@enes-alatas](https://github.com/enes-alatas) in[#627](https://github.com/enes-alatas/spool/pull/627)\n- feat: a bare loop can be rehomed into a docker workstation by [@enes-alatas](https://github.com/enes-alatas) in[#631](https://github.com/enes-alatas/spool/pull/631)\n\n**Full Changelog**: `v0.4.1...v0.4.2`\n\n## v0.4.1\n\n## What's Changed\n\n- fix(httpapi): a docker loop that cannot reach the loop listener is refused by [@enes-alatas](https://github.com/enes-alatas) in[#477](https://github.com/enes-alatas/spool/pull/477)\n- docs(release): a release is titled with its tag alone by [@enes-alatas](https://github.com/enes-alatas) in[#486](https://github.com/enes-alatas/spool/pull/486)\n- feat: channels exist in the hub, with the fleet channel named group by [@enes-alatas](https://github.com/enes-alatas) in[#488](https://github.com/enes-alatas/spool/pull/488)\n- feat(web): a Channels page creates channels and chooses their loops by [@enes-alatas](https://github.com/enes-alatas) in[#490](https://github.com/enes-alatas/spool/pull/490)\n- docs: a loop reaches only Spool's MCP server by [@enes-alatas](https://github.com/enes-alatas) in[#494](https://github.com/enes-alatas/spool/pull/494)\n- fix(runtime): Spool builds on macOS, keeping Pdeathsig on Linux by [@enes-alatas](https://github.com/enes-alatas) in[#498](https://github.com/enes-alatas/spool/pull/498)\n- fix(loop): a loop whose claude cannot reach the hub is down, and its work waits by [@enes-alatas](https://github.com/enes-alatas) in[#496](https://github.com/enes-alatas/spool/pull/496)\n- fix(httpapi): a Docker Desktop workstation reaches the loop listener by [@enes-alatas](https://github.com/enes-alatas) in[#509](https://github.com/enes-alatas/spool/pull/509)\n- feat(cmd): a docker hub puts its loop listener where workstations reach it by [@enes-alatas](https://github.com/enes-alatas) in[#511](https://github.com/enes-alatas/spool/pull/511)\n- feat(web): name a claude that can't reach the hub, and offer no Power on for it by [@enes-alatas](https://github.com/enes-alatas) in[#510](https://github.com/enes-alatas/spool/pull/510)\n- feat(loop): a loop posts to its channels by name by [@enes-alatas](https://github.com/enes-alatas) in[#512](https://github.com/enes-alatas/spool/pull/512)\n- test(uifixture): the fixture seeds channels besides the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#513](https://github.com/enes-alatas/spool/pull/513)\n- feat(telegram): a group is bound to a channel per loop, and carries it both ways by [@enes-alatas](https://github.com/enes-alatas) in[#516](https://github.com/enes-alatas/spool/pull/516)\n- docs: a Report a problem form, triage rule, and CONTRIBUTING for outside reports by [@enes-alatas](https://github.com/enes-alatas) in[#522](https://github.com/enes-alatas/spool/pull/522)\n- feat(loop): the loop view says what the session reaches through MCP by [@enes-alatas](https://github.com/enes-alatas) in[#525](https://github.com/enes-alatas/spool/pull/525)\n- chore(ci): an issue from outside the team is labelled and welcomed by [@enes-alatas](https://github.com/enes-alatas) in[#528](https://github.com/enes-alatas/spool/pull/528)\n- feat(web): the loop page lists its rooms and binds one to a channel by [@enes-alatas](https://github.com/enes-alatas) in[#530](https://github.com/enes-alatas/spool/pull/530)\n- feat: reactions exist in the hub — ADR-0040, store and bus kind by [@enes-alatas](https://github.com/enes-alatas) in[#536](https://github.com/enes-alatas/spool/pull/536)\n- feat(web): the loop page shows what the session reaches through MCP by [@enes-alatas](https://github.com/enes-alatas) in[#537](https://github.com/enes-alatas/spool/pull/537)\n- feat(telegram): reactions travel both ways between a loop's bot and the hub by [@enes-alatas](https://github.com/enes-alatas) in[#538](https://github.com/enes-alatas/spool/pull/538)\n- fix(web): no next wake is shown while a loop is in a turn by [@enes-alatas](https://github.com/enes-alatas) in[#539](https://github.com/enes-alatas/spool/pull/539)\n- feat(loop): a loop reacts with send_message and hears of reactions on its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#540](https://github.com/enes-alatas/spool/pull/540)\n- feat(web): one slider sets a loop's min wake, tick and max wake by [@enes-alatas](https://github.com/enes-alatas) in[#545](https://github.com/enes-alatas/spool/pull/545)\n- feat(slack): reactions travel both ways between a loop's app and the hub by [@enes-alatas](https://github.com/enes-alatas) in[#544](https://github.com/enes-alatas/spool/pull/544)\n- feat(web): the control room shows a message's reactions by [@enes-alatas](https://github.com/enes-alatas) in[#547](https://github.com/enes-alatas/spool/pull/547)\n- chore(egress): a workstation can pip install from PyPI by [@enes-alatas](https://github.com/enes-alatas) in[#546](https://github.com/enes-alatas/spool/pull/546)\n- feat: polls exist in the hub — ADR-0041, store, bus kind and the close by [@enes-alatas](https://github.com/enes-alatas) in[#555](https://github.com/enes-alatas/spool/pull/555)\n- feat(telegram): a loop's poll is a native Telegram poll, and its votes reach the hub by [@enes-alatas](https://github.com/enes-alatas) in[#557](https://github.com/enes-alatas/spool/pull/557)\n- feat(web): a loop's channels are tabs of its page, and Activity filters by channel by [@enes-alatas](https://github.com/enes-alatas) in[#556](https://github.com/enes-alatas/spool/pull/556)\n- feat(slack): a loop's poll is its app's post with buttons, and clicks are votes by [@enes-alatas](https://github.com/enes-alatas) in[#558](https://github.com/enes-alatas/spool/pull/558)\n- feat: a loop polls, votes and closes with send_message, and votes ride with its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#560](https://github.com/enes-alatas/spool/pull/560)\n- fix(telegram): an unanswered send is retried on a fresh connection within seconds by [@enes-alatas](https://github.com/enes-alatas) in[#562](https://github.com/enes-alatas/spool/pull/562)\n- feat: the control room renders polls with their tallies by [@enes-alatas](https://github.com/enes-alatas) in[#563](https://github.com/enes-alatas/spool/pull/563)\n- feat: the hub pins a PreToolUse hook that refuses the host's shared-state commands by [@enes-alatas](https://github.com/enes-alatas) in[#565](https://github.com/enes-alatas/spool/pull/565)\n- feat: a loop dismisses a lost send, and is reminded of it until it does by [@enes-alatas](https://github.com/enes-alatas) in[#566](https://github.com/enes-alatas/spool/pull/566)\n- feat: connections can be created, listed, described and deleted, secret write-only by [@enes-alatas](https://github.com/enes-alatas) in[#568](https://github.com/enes-alatas/spool/pull/568)\n- feat(web): show when a loop dismissed or left a lost send by [@enes-alatas](https://github.com/enes-alatas) in[#569](https://github.com/enes-alatas/spool/pull/569)\n- fix(telegram): the HTTP/1 transport offers only http/1.1 in its TLS handshake by [@enes-alatas](https://github.com/enes-alatas) in[#571](https://github.com/enes-alatas/spool/pull/571)\n- feat: a connection is attached to and detached from loops, and an attached one can't be deleted by [@enes-alatas](https://github.com/enes-alatas) in[#573](https://github.com/enes-alatas/spool/pull/573)\n- feat: a connection's env-var kind replaces env-credential by [@enes-alatas](https://github.com/enes-alatas) in[#575](https://github.com/enes-alatas/spool/pull/575)\n- feat(web): Connections page and loop-page connections panel by [@enes-alatas](https://github.com/enes-alatas) in[#577](https://github.com/enes-alatas/spool/pull/577)\n- feat: per-loop secrets are env-var connections, and an attached env-var is in the wake env by [@enes-alatas](https://github.com/enes-alatas) in[#578](https://github.com/enes-alatas/spool/pull/578)\n- fix(web): connections say env variables reach their loop, and long names fit the panel by [@enes-alatas](https://github.com/enes-alatas) in[#583](https://github.com/enes-alatas/spool/pull/583)\n- feat: GET /api/onboarding reports readiness per pillar by [@enes-alatas](https://github.com/enes-alatas) in[#584](https://github.com/enes-alatas/spool/pull/584)\n- feat(web): first-run page for the three onboarding pillars by [@enes-alatas](https://github.com/enes-alatas) in[#586](https://github.com/enes-alatas/spool/pull/586)\n- feat: the harness pillar checks the Claude login with one haiku answer by [@enes-alatas](https://github.com/enes-alatas) in[#587](https://github.com/enes-alatas/spool/pull/587)\n- feat(web): the first-run Harness card sets up and checks the login in place by [@enes-alatas](https://github.com/enes-alatas) in[#590](https://github.com/enes-alatas/spool/pull/590)\n- feat(slack): a Slack channel is a room bound to a channel per loop by [@enes-alatas](https://github.com/enes-alatas) in[#591](https://github.com/enes-alatas/spool/pull/591)\n- feat(web): the first-run Loops and Chat surface cards open their steps in place by [@enes-alatas](https://github.com/enes-alatas) in[#592](https://github.com/enes-alatas/spool/pull/592)\n- test: two docker itests stop racing other hubs on the shared daemon by [@enes-alatas](https://github.com/enes-alatas) in[#594](https://github.com/enes-alatas/spool/pull/594)\n- feat(loop): an attached mcp-server is a server in the loop's mcp-config by [@enes-alatas](https://github.com/enes-alatas) in[#598](https://github.com/enes-alatas/spool/pull/598)\n- feat(web): a Slack loop's channels are rooms on its page by [@enes-alatas](https://github.com/enes-alatas) in[#596](https://github.com/enes-alatas/spool/pull/596)\n- feat(docker): an attached MCP server opens its host to its loop alone by [@enes-alatas](https://github.com/enes-alatas) in[#602](https://github.com/enes-alatas/spool/pull/602)\n- feat: a connection can be private to one loop, and is shared one way by [@enes-alatas](https://github.com/enes-alatas) in[#603](https://github.com/enes-alatas/spool/pull/603)\n- feat(web): the loop page's secrets are its private connections by [@enes-alatas](https://github.com/enes-alatas) in[#605](https://github.com/enes-alatas/spool/pull/605)\n\n**Full Changelog**: `v0.4.0...v0.4.1`\n\n## v0.4.0\n\n## What's Changed\n\n- fix(web): the Undelivered pane restacks by its own width, not the viewport's by [@enes-alatas](https://github.com/enes-alatas) in[#417](https://github.com/enes-alatas/spool/pull/417)\n- fix(loop): a refused Claude login is a named down reason, not silence by [@enes-alatas](https://github.com/enes-alatas) in[#416](https://github.com/enes-alatas/spool/pull/416)\n- feat(surface): the owner hears once that a refused login stopped their loops by [@enes-alatas](https://github.com/enes-alatas) in[#421](https://github.com/enes-alatas/spool/pull/421)\n- fix(web): a refused Claude login shows its cause and its fix by [@enes-alatas](https://github.com/enes-alatas) in[#422](https://github.com/enes-alatas/spool/pull/422)\n- feat(surface/slack): each loop's Slack app holds a Socket Mode connection by [@enes-alatas](https://github.com/enes-alatas) in[#423](https://github.com/enes-alatas/spool/pull/423)\n- fix(telegram): a native reply reaches its author whichever bot ingests it by [@enes-alatas](https://github.com/enes-alatas) in[#425](https://github.com/enes-alatas/spool/pull/425)\n- fix(web): the New loop runtime select says why it is locked by [@enes-alatas](https://github.com/enes-alatas) in[#428](https://github.com/enes-alatas/spool/pull/428)\n- feat(surface/slack): what a human says to a loop's app reaches the loop by [@enes-alatas](https://github.com/enes-alatas) in[#430](https://github.com/enes-alatas/spool/pull/430)\n- fix(web): New loop locks the workspace field for a docker loop by [@enes-alatas](https://github.com/enes-alatas) in[#432](https://github.com/enes-alatas/spool/pull/432)\n- fix(web): a Slack post in the fleet channel says it came via Slack by [@enes-alatas](https://github.com/enes-alatas) in[#433](https://github.com/enes-alatas/spool/pull/433)\n- feat(surface/slack): a loop's words reach Slack: group mirrors, owner_dm, threads by [@enes-alatas](https://github.com/enes-alatas) in[#434](https://github.com/enes-alatas/spool/pull/434)\n- feat(surface/slack): turned-away senders, the login notice and channel_name on Slack by [@enes-alatas](https://github.com/enes-alatas) in[#435](https://github.com/enes-alatas/spool/pull/435)\n- fix(web): Slack attach and channel copy say what actually happened by [@enes-alatas](https://github.com/enes-alatas) in[#439](https://github.com/enes-alatas/spool/pull/439)\n- fix(loop): a refused login keeps one tick, not a backlog of them by [@enes-alatas](https://github.com/enes-alatas) in[#436](https://github.com/enes-alatas/spool/pull/436)\n- fix: a running hub settles every send it accepted by [@enes-alatas](https://github.com/enes-alatas) in[#440](https://github.com/enes-alatas/spool/pull/440)\n- fix(cmd): a hub that stops serving exits 1 by [@enes-alatas](https://github.com/enes-alatas) in[#443](https://github.com/enes-alatas/spool/pull/443)\n- fix(store): commits wait on the WAL, not on an fsync; a perf smoke asserts the envelope by [@enes-alatas](https://github.com/enes-alatas) in[#444](https://github.com/enes-alatas/spool/pull/444)\n- docs(readme): Quick start is a numbered path that works end to end by [@enes-alatas](https://github.com/enes-alatas) in[#448](https://github.com/enes-alatas/spool/pull/448)\n- feat(surface): a stopping hub fails its surfaces' held sends before the store closes by [@enes-alatas](https://github.com/enes-alatas) in[#449](https://github.com/enes-alatas/spool/pull/449)\n- feat(attach): a photo or file sent to a loop reaches it as a path it can read by [@enes-alatas](https://github.com/enes-alatas) in[#454](https://github.com/enes-alatas/spool/pull/454)\n- feat(attach): a loop sends a file with its message by [@enes-alatas](https://github.com/enes-alatas) in[#455](https://github.com/enes-alatas/spool/pull/455)\n- fix(web): a context trend bar keeps its width at any turn count by [@enes-alatas](https://github.com/enes-alatas) in[#459](https://github.com/enes-alatas/spool/pull/459)\n- feat(web): glyph and wordmark match the README banner by [@enes-alatas](https://github.com/enes-alatas) in[#461](https://github.com/enes-alatas/spool/pull/461)\n- feat(slack): files cross Slack both ways by [@enes-alatas](https://github.com/enes-alatas) in[#458](https://github.com/enes-alatas/spool/pull/458)\n- chore: the README opens with a demo video, rendered by make demo by [@enes-alatas](https://github.com/enes-alatas) in[#463](https://github.com/enes-alatas/spool/pull/463)\n- feat(httpapi): the control room reads, serves and sends attachments by [@enes-alatas](https://github.com/enes-alatas) in[#467](https://github.com/enes-alatas/spool/pull/467)\n- feat(web): messages carry files in the control room by [@enes-alatas](https://github.com/enes-alatas) in[#469](https://github.com/enes-alatas/spool/pull/469)\n- docs(conventions): every slice of a multi-PR issue is its own sub-issue by [@enes-alatas](https://github.com/enes-alatas) in[#465](https://github.com/enes-alatas/spool/pull/465)\n- docs(conventions): rare procedures move to skills by [@enes-alatas](https://github.com/enes-alatas) in[#473](https://github.com/enes-alatas/spool/pull/473)\n- chore(deps-dev): Bump brace-expansion from 1.1.18 to 1.1.21 in /web in the npm_and_yarn group across 1 directory by [@dependabot](https://github.com/dependabot) [bot] in[#468](https://github.com/enes-alatas/spool/pull/468)\n- docs(conventions): a bare #n may sit anywhere in a code comment by [@enes-alatas](https://github.com/enes-alatas) in[#478](https://github.com/enes-alatas/spool/pull/478)\n- feat(web): allowing a pending sender takes the code they were sent by [@enes-alatas](https://github.com/enes-alatas) in[#482](https://github.com/enes-alatas/spool/pull/482)\n\n## New Contributors\n\n- [@dependabot](https://github.com/dependabot) [bot] made their first contribution in[#468](https://github.com/enes-alatas/spool/pull/468)\n\n**Full Changelog**: `v0.3.0...v0.4.0`\n\n## v0.3.0\n\n## What's Changed\n\n- chore: make ui-shots takes the screenshot set from fixture data by [@enes-alatas](https://github.com/enes-alatas) in[#260](https://github.com/enes-alatas/spool/pull/260)\n- chore(secret-guard): a handle and a chat id are quotes of the group by [@enes-alatas](https://github.com/enes-alatas) in[#259](https://github.com/enes-alatas/spool/pull/259)\n- feat(web): the Fleet row says how many of a loop's messages never arrived by [@enes-alatas](https://github.com/enes-alatas) in[#262](https://github.com/enes-alatas/spool/pull/262)\n- fix(itest): two suites, two egress walls by [@enes-alatas](https://github.com/enes-alatas) in[#265](https://github.com/enes-alatas/spool/pull/265)\n- fix(httpapi): a saved mission takes the loop's next session, not its next hour by [@enes-alatas](https://github.com/enes-alatas) in[#271](https://github.com/enes-alatas/spool/pull/271)\n- feat(web): a loop's mission can be edited from its page by [@enes-alatas](https://github.com/enes-alatas) in[#268](https://github.com/enes-alatas/spool/pull/268)\n- feat(httpapi): the fleet's undelivered sends have somewhere to be read by [@enes-alatas](https://github.com/enes-alatas) in[#273](https://github.com/enes-alatas/spool/pull/273)\n- feat(web): the sends that failed get a tab of their own by [@enes-alatas](https://github.com/enes-alatas) in[#274](https://github.com/enes-alatas/spool/pull/274)\n- feat(httpapi): a failed send can be retried or dismissed, and stops counting by [@enes-alatas](https://github.com/enes-alatas) in[#276](https://github.com/enes-alatas/spool/pull/276)\n- feat(route): a loop's resend closes the failure it replaces by [@enes-alatas](https://github.com/enes-alatas) in[#278](https://github.com/enes-alatas/spool/pull/278)\n- feat(web): a failed send can be retried or set aside, and says which by [@enes-alatas](https://github.com/enes-alatas) in[#279](https://github.com/enes-alatas/spool/pull/279)\n- feat(httpapi): one loop's undelivered sends can be asked for alone by [@enes-alatas](https://github.com/enes-alatas) in[#290](https://github.com/enes-alatas/spool/pull/290)\n- docs(adr): the fleet channel is the hub's; surfaces are attached and mirror it by [@enes-alatas](https://github.com/enes-alatas) in[#291](https://github.com/enes-alatas/spool/pull/291)\n- fix(web): an Undelivered row says where it went and why, together by [@enes-alatas](https://github.com/enes-alatas) in[#292](https://github.com/enes-alatas/spool/pull/292)\n- feat(telegram): nothing the operator writes leaves the hub by [@enes-alatas](https://github.com/enes-alatas) in[#293](https://github.com/enes-alatas/spool/pull/293)\n- feat(web): Undelivered is a pane of the loop page, not a destination by [@enes-alatas](https://github.com/enes-alatas) in[#294](https://github.com/enes-alatas/spool/pull/294)\n- docs(conventions): Activity stays out of every shot, and says it is a decision by [@enes-alatas](https://github.com/enes-alatas) in[#296](https://github.com/enes-alatas/spool/pull/296)\n- feat(route): a loop outside the fleet channel has no group by [@enes-alatas](https://github.com/enes-alatas) in[#295](https://github.com/enes-alatas/spool/pull/295)\n- feat(httpapi): the fleet channel has endpoints of its own by [@enes-alatas](https://github.com/enes-alatas) in[#297](https://github.com/enes-alatas/spool/pull/297)\n- chore: the model lists know Opus 5.5 and Fable 5.1 by [@enes-alatas](https://github.com/enes-alatas) in[#299](https://github.com/enes-alatas/spool/pull/299)\n- feat(web): the fleet channel is a tab of the Fleet page by [@enes-alatas](https://github.com/enes-alatas) in[#300](https://github.com/enes-alatas/spool/pull/300)\n- feat: a message says whether it is on the surface too by [@enes-alatas](https://github.com/enes-alatas) in[#301](https://github.com/enes-alatas/spool/pull/301)\n- fix(web): the missing-Claude-token banner is about contained loops only by [@enes-alatas](https://github.com/enes-alatas) in[#307](https://github.com/enes-alatas/spool/pull/307)\n- feat(httpapi): a new loop starts outside the fleet channel when it is alone by [@enes-alatas](https://github.com/enes-alatas) in[#306](https://github.com/enes-alatas/spool/pull/306)\n- chore(claude): TestedVersion is 2.1.281, the CLI the fleet runs on by [@enes-alatas](https://github.com/enes-alatas) in[#309](https://github.com/enes-alatas/spool/pull/309)\n- feat(web): loop surfaces — attach Telegram after creation, fleet-channel membership by [@enes-alatas](https://github.com/enes-alatas) in[#308](https://github.com/enes-alatas/spool/pull/308)\n- docs: add theme-aware Spool banner and badges by [@enes-alatas](https://github.com/enes-alatas) in[#310](https://github.com/enes-alatas/spool/pull/310)\n- docs: reduce README banner width by [@enes-alatas](https://github.com/enes-alatas) in[#313](https://github.com/enes-alatas/spool/pull/313)\n- feat(httpapi): the operator can reply in the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#311](https://github.com/enes-alatas/spool/pull/311)\n- feat(web): the fleet channel says what stays on the hub and what failed to mirror by [@enes-alatas](https://github.com/enes-alatas) in[#312](https://github.com/enes-alatas/spool/pull/312)\n- feat(web): reply to any message in the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#317](https://github.com/enes-alatas/spool/pull/317)\n- fix(workstation): a rebuild installs the newest Claude Code by [@enes-alatas](https://github.com/enes-alatas) in[#315](https://github.com/enes-alatas/spool/pull/315)\n- feat(web): a favicon set and the spool glyph in the banner's proportion by [@enes-alatas](https://github.com/enes-alatas) in[#318](https://github.com/enes-alatas/spool/pull/318)\n- feat(prompt): a loop is taught only the conversations it has by [@enes-alatas](https://github.com/enes-alatas) in[#321](https://github.com/enes-alatas/spool/pull/321)\n- docs(conventions): an epic's children are attached as sub-issues by [@enes-alatas](https://github.com/enes-alatas) in[#324](https://github.com/enes-alatas/spool/pull/324)\n- chore(web): upgrade vite, vitest and react-router past their advisories by [@enes-alatas](https://github.com/enes-alatas) in[#325](https://github.com/enes-alatas/spool/pull/325)\n- docs(adr): ADR-0010 points to ADR-0018, which narrowed its Engine API clause by [@enes-alatas](https://github.com/enes-alatas) in[#327](https://github.com/enes-alatas/spool/pull/327)\n- docs(conventions): a commit subject has one scope and no closing keyword by [@enes-alatas](https://github.com/enes-alatas) in[#328](https://github.com/enes-alatas/spool/pull/328)\n- chore(ci): itest skips content it already passed on this PR by [@enes-alatas](https://github.com/enes-alatas) in[#326](https://github.com/enes-alatas/spool/pull/326)\n- feat(web): the model list offers the four family aliases and no versions by [@enes-alatas](https://github.com/enes-alatas) in[#329](https://github.com/enes-alatas/spool/pull/329)\n- feat(loop): a refused model holds the loop until the model is edited by [@enes-alatas](https://github.com/enes-alatas) in[#330](https://github.com/enes-alatas/spool/pull/330)\n- feat(web): the control room's text uses no em dashes by [@enes-alatas](https://github.com/enes-alatas) in[#333](https://github.com/enes-alatas/spool/pull/333)\n- feat(web): a refused model shows in the room, with what the CLI said by [@enes-alatas](https://github.com/enes-alatas) in[#335](https://github.com/enes-alatas/spool/pull/335)\n- chore(web): move the control room to react-router 7 by [@enes-alatas](https://github.com/enes-alatas) in[#336](https://github.com/enes-alatas/spool/pull/336)\n- docs: every new dependency is interviewed; binary ones also take an ADR by [@enes-alatas](https://github.com/enes-alatas) in[#337](https://github.com/enes-alatas/spool/pull/337)\n- docs(adr): the hub resolves model aliases with a CLI run that can reach nothing by [@enes-alatas](https://github.com/enes-alatas) in[#338](https://github.com/enes-alatas/spool/pull/338)\n- feat: the model list says what each name runs as on this hub by [@enes-alatas](https://github.com/enes-alatas) in[#340](https://github.com/enes-alatas/spool/pull/340)\n- feat(web): model dropdowns show what each alias runs as by [@enes-alatas](https://github.com/enes-alatas) in[#339](https://github.com/enes-alatas/spool/pull/339)\n- ci: the checks summary reports coverage of the PR's changed lines by [@enes-alatas](https://github.com/enes-alatas) in[#343](https://github.com/enes-alatas/spool/pull/343)\n- feat(web): Surfaces offers Attach Slack, with the loop's app manifest by [@enes-alatas](https://github.com/enes-alatas) in[#346](https://github.com/enes-alatas/spool/pull/346)\n- fix(itest): the hub's two listeners never draw the same port by [@enes-alatas](https://github.com/enes-alatas) in[#342](https://github.com/enes-alatas/spool/pull/342)\n- fix(web): a disabled rule mutes its text, not its opacity by [@enes-alatas](https://github.com/enes-alatas) in[#355](https://github.com/enes-alatas/spool/pull/355)\n- fix(web): a rule's buttons wrap under its title on a narrow screen by [@enes-alatas](https://github.com/enes-alatas) in[#358](https://github.com/enes-alatas/spool/pull/358)\n- fix(httpapi): a loop's model cannot read as a CLI flag by [@enes-alatas](https://github.com/enes-alatas) in[#357](https://github.com/enes-alatas/spool/pull/357)\n- fix(web): the loop page's model and pacing controls say what they set by [@enes-alatas](https://github.com/enes-alatas) in[#359](https://github.com/enes-alatas/spool/pull/359)\n- fix(web): Access says when it is loading or could not load senders by [@enes-alatas](https://github.com/enes-alatas) in[#361](https://github.com/enes-alatas/spool/pull/361)\n- fix(httpapi): a wrong method under /api/ is 405, not the control room by [@enes-alatas](https://github.com/enes-alatas) in[#362](https://github.com/enes-alatas/spool/pull/362)\n- fix(web): Settings says it could not load, not that no token is set by [@enes-alatas](https://github.com/enes-alatas) in[#364](https://github.com/enes-alatas/spool/pull/364)\n- fix(httpapi): a write whose subject vanished answers 404, not 500 by [@enes-alatas](https://github.com/enes-alatas) in[#365](https://github.com/enes-alatas/spool/pull/365)\n- fix(web): the loop page says when its timeline could not load by [@enes-alatas](https://github.com/enes-alatas) in[#367](https://github.com/enes-alatas/spool/pull/367)\n- fix(web): the loop page says when a loop is missing or failed to load by [@enes-alatas](https://github.com/enes-alatas) in[#369](https://github.com/enes-alatas/spool/pull/369)\n- refactor(httpapi): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#370](https://github.com/enes-alatas/spool/pull/370)\n- feat(web): the header's New loop is quiet white text, not a filled button by [@enes-alatas](https://github.com/enes-alatas) in[#371](https://github.com/enes-alatas/spool/pull/371)\n- fix(sched): a tick due during a suspend fires once the machine resumes by [@enes-alatas](https://github.com/enes-alatas) in[#373](https://github.com/enes-alatas/spool/pull/373)\n- fix(web): the room's errors are announced to a screen reader by [@enes-alatas](https://github.com/enes-alatas) in[#374](https://github.com/enes-alatas/spool/pull/374)\n- feat(loop): a rotation tells the loop why it is happening by [@enes-alatas](https://github.com/enes-alatas) in[#376](https://github.com/enes-alatas/spool/pull/376)\n- feat(ci): a browser smoke walks the control room on every web change by [@enes-alatas](https://github.com/enes-alatas) in[#375](https://github.com/enes-alatas/spool/pull/375)\n- refactor(sqlite): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#377](https://github.com/enes-alatas/spool/pull/377)\n- test(uifixture): the fixture hub signs in with a synthetic operator token by [@enes-alatas](https://github.com/enes-alatas) in[#379](https://github.com/enes-alatas/spool/pull/379)\n- fix(web): the header switches to the narrow bar by text size, not width alone by [@enes-alatas](https://github.com/enes-alatas) in[#380](https://github.com/enes-alatas/spool/pull/380)\n- refactor(loop): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#382](https://github.com/enes-alatas/spool/pull/382)\n- fix(httpapi): health says the hub is up and nothing about the host by [@enes-alatas](https://github.com/enes-alatas) in[#385](https://github.com/enes-alatas/spool/pull/385)\n- feat(web): Surfaces and Access gain the Slack surface's control-room half by [@enes-alatas](https://github.com/enes-alatas) in[#384](https://github.com/enes-alatas/spool/pull/384)\n- docs(readme): Spool leads with what it makes easier by [@enes-alatas](https://github.com/enes-alatas) in[#387](https://github.com/enes-alatas/spool/pull/387)\n- feat(store): a loop can hold a Slack identity and a Slack allowlist by [@enes-alatas](https://github.com/enes-alatas) in[#390](https://github.com/enes-alatas/spool/pull/390)\n- docs(conventions): an issue's body is its spec of record by [@enes-alatas](https://github.com/enes-alatas) in[#3](https://github.com/enes-alatas/spool/issues/3) ...\n\n[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.3.0)\n\n## v0.2.0\n\n## What's Changed\n\n- build(workstation): official spool-workstation image by [@enes-alatas](https://github.com/enes-alatas) in[#23](https://github.com/enes-alatas/spool/pull/23)\n- docs(conventions): require self-contained comments by [@enes-alatas](https://github.com/enes-alatas) in[#25](https://github.com/enes-alatas/spool/pull/25)\n- feat: Claude auth for workstations via setup-token by [@enes-alatas](https://github.com/enes-alatas) in[#26](https://github.com/enes-alatas/spool/pull/26)\n- feat: per-loop secret env vars by [@enes-alatas](https://github.com/enes-alatas) in[#29](https://github.com/enes-alatas/spool/pull/29)\n- docs(conventions): \"fresh main\" means fetched, not the local checkout by [@enes-alatas](https://github.com/enes-alatas) in[#28](https://github.com/enes-alatas/spool/pull/28)\n- docs: GitHub is loop-native work, not a Spool surface by [@enes-alatas](https://github.com/enes-alatas) in[#32](https://github.com/enes-alatas/spool/pull/32)\n- feat(web): workstation panel on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#35](https://github.com/enes-alatas/spool/pull/35)\n- fix(runtime): drop bare.Start's kill-on-cancel watcher by [@enes-alatas](https://github.com/enes-alatas) in[#34](https://github.com/enes-alatas/spool/pull/34)\n- chore(web): untrack tsconfig.tsbuildinfo by [@enes-alatas](https://github.com/enes-alatas) in[#40](https://github.com/enes-alatas/spool/pull/40)\n- feat(web): context occupancy trend on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#49](https://github.com/enes-alatas/spool/pull/49)\n- docs(conventions): delegated decisions are per-decision and quoted in the record by [@enes-alatas](https://github.com/enes-alatas) in[#41](https://github.com/enes-alatas/spool/pull/41)\n- fix(telegram): dedup group and DM messages per bot, not per chat by [@enes-alatas](https://github.com/enes-alatas) in[#48](https://github.com/enes-alatas/spool/pull/48)\n- feat(loop): operator power controls for a loop's workstation by [@enes-alatas](https://github.com/enes-alatas) in[#53](https://github.com/enes-alatas/spool/pull/53)\n- feat(web): workstation power controls on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#50](https://github.com/enes-alatas/spool/pull/50)\n- feat(httpapi): context usage on the loop view by [@enes-alatas](https://github.com/enes-alatas) in[#54](https://github.com/enes-alatas/spool/pull/54)\n- feat(web): context fill against the model's window by [@enes-alatas](https://github.com/enes-alatas) in[#56](https://github.com/enes-alatas/spool/pull/56)\n- feat(web): one fleet alert for a missing Claude token by [@enes-alatas](https://github.com/enes-alatas) in[#57](https://github.com/enes-alatas/spool/pull/57)\n- fix(loop): rotate a session that can no longer be resumed by [@enes-alatas](https://github.com/enes-alatas) in[#59](https://github.com/enes-alatas/spool/pull/59)\n- docs: record the proactive context rotation strategy (ADR-0022) by [@enes-alatas](https://github.com/enes-alatas) in[#62](https://github.com/enes-alatas/spool/pull/62)\n- docs: separate coordination from human-facing traffic (ADR-0023) by [@enes-alatas](https://github.com/enes-alatas) in[#63](https://github.com/enes-alatas/spool/pull/63)\n- feat(loop): proactive context rotation before the degradation zone by [@enes-alatas](https://github.com/enes-alatas) in[#64](https://github.com/enes-alatas/spool/pull/64)\n- feat: fleet rules — storage, CRUD API, and prompt injection by [@enes-alatas](https://github.com/enes-alatas) in[#72](https://github.com/enes-alatas/spool/pull/72)\n- feat(web): fleet rules panel by [@enes-alatas](https://github.com/enes-alatas) in[#58](https://github.com/enes-alatas/spool/pull/58)\n- docs: define private DMs and explicit group delivery by [@enes-alatas](https://github.com/enes-alatas) in[#78](https://github.com/enes-alatas/spool/pull/78)\n- docs: settle the loop sending contract (ADR-0026) by [@enes-alatas](https://github.com/enes-alatas) in[#81](https://github.com/enes-alatas/spool/pull/81)\n- feat: conversation identity and per-loop MCP tokens by [@enes-alatas](https://github.com/enes-alatas) in[#82](https://github.com/enes-alatas/spool/pull/82)\n- feat: hub MCP endpoint and the explicit send core by [@enes-alatas](https://github.com/enes-alatas) in[#83](https://github.com/enes-alatas/spool/pull/83)\n- feat: fakeclaude scripted MCP sends by [@enes-alatas](https://github.com/enes-alatas) in[#84](https://github.com/enes-alatas/spool/pull/84)\n- feat: runner wiring for explicit sends by [@enes-alatas](https://github.com/enes-alatas) in[#85](https://github.com/enes-alatas/spool/pull/85)\n- feat(telegram): explicit delivery in, selective wake out by [@enes-alatas](https://github.com/enes-alatas) in[#86](https://github.com/enes-alatas/spool/pull/86)\n- feat: web composer destination picker + read-only Activity by [@enes-alatas](https://github.com/enes-alatas) in[#87](https://github.com/enes-alatas/spool/pull/87)\n- test: ADR-0025 scenario close-out and docs refresh by [@enes-alatas](https://github.com/enes-alatas) in[#88](https://github.com/enes-alatas/spool/pull/88)\n- fix(loop): name an over-window payload for what it is by [@enes-alatas](https://github.com/enes-alatas) in[#90](https://github.com/enes-alatas/spool/pull/90)\n- feat(web): semantic colour tokens for the control room by [@enes-alatas](https://github.com/enes-alatas) in[#92](https://github.com/enes-alatas/spool/pull/92)\n- feat: operator control to rotate a session on demand by [@enes-alatas](https://github.com/enes-alatas) in[#93](https://github.com/enes-alatas/spool/pull/93)\n- fix(loop): context is the last API call's occupancy, not the turn's summed usage by [@enes-alatas](https://github.com/enes-alatas) in[#95](https://github.com/enes-alatas/spool/pull/95)\n- feat(web): responsive control-room navigation by [@enes-alatas](https://github.com/enes-alatas) in[#96](https://github.com/enes-alatas/spool/pull/96)\n- feat: explicit message references and native group replies by [@enes-alatas](https://github.com/enes-alatas) in[#97](https://github.com/enes-alatas/spool/pull/97)\n- fix(web): a fleet card's stats wrap instead of clipping by [@enes-alatas](https://github.com/enes-alatas) in[#99](https://github.com/enes-alatas/spool/pull/99)\n- feat: a loop's owner is configured, and owner_dm reaches them by [@enes-alatas](https://github.com/enes-alatas) in[#102](https://github.com/enes-alatas/spool/pull/102)\n- feat(web): choose a loop's owner and see whether it can DM them by [@enes-alatas](https://github.com/enes-alatas) in[#103](https://github.com/enes-alatas/spool/pull/103)\n- feat: an identity catalog that says who a loop can address by [@enes-alatas](https://github.com/enes-alatas) in[#104](https://github.com/enes-alatas/spool/pull/104)\n- fix: the retry ladder survives a failure-driven rotation by [@enes-alatas](https://github.com/enes-alatas) in[#109](https://github.com/enes-alatas/spool/pull/109)\n- feat(web): Fleet is one list of loops, not a grid of cards by [@enes-alatas](https://github.com/enes-alatas) in[#105](https://github.com/enes-alatas/spool/pull/105)\n- feat(web): the control room scales with the reader's text size by [@enes-alatas](https://github.com/enes-alatas) in[#111](https://github.com/enes-alatas/spool/pull/111)\n- feat: [@ALL](https://github.com/ALL) addresses the group's eligible loops by[@enes-alatas](https://github.com/enes-alatas) in[#110](https://github.com/enes-alatas/spool/pull/110)\n- fix(web): a state dot is an accent, not a second voice by [@enes-alatas](https://github.com/enes-alatas) in[#113](https://github.com/enes-alatas/spool/pull/113)\n- refactor: one threshold rule, one threshold read, one fill signal by [@enes-alatas](https://github.com/enes-alatas) in[#114](https://github.com/enes-alatas/spool/pull/114)\n- refactor(web): name the styles three pages were repeating inline by [@enes-alatas](https://github.com/enes-alatas) in[#115](https://github.com/enes-alatas/spool/pull/115)\n- fix(loop): a rotation survives the orchestrator restarting by [@enes-alatas](https://github.com/enes-alatas) in[#116](https://github.com/enes-alatas/spool/pull/116)\n- fix(web): centre the page column under its own header by [@enes-alatas](https://github.com/enes-alatas) in[#121](https://github.com/enes-alatas/spool/pull/121)\n- fix(web): read an absent context fill as unreported, not as zero-length by [@enes-alatas](https://github.com/enes-alatas) in[#125](https://github.com/enes-alatas/spool/pull/125)\n- feat(httpapi): read a loop's timeline from its newest end by [@enes-alatas](https://github.com/enes-alatas) in[#127](https://github.com/enes-alatas/spool/pull/127)\n- feat(web): open the loop timeline on its newest window by [@enes-alatas](https://github.com/enes-alatas) in[#128](https://github.com/enes-alatas/spool/pull/128)\n- test(itest): script a contained loop the way a bare one is scripted by [@enes-alatas](https://github.com/enes-alatas) in[#129](https://github.com/enes-alatas/spool/pull/129)\n- fix(web): give a form page the column its content asks for by [@enes-alatas](https://github.com/enes-alatas) in[#133](https://github.com/enes-alatas/spool/pull/133)\n- test(itest): port the m2 scenarios and the untested fakeclaude directives by [@enes-alatas](https://github.com/enes-alatas) in[#135](https://github.com/enes-alatas/spool/pull/135)\n- fix(web): draw hairlines for the screen they land on by [@enes-alatas](https://github.com/enes-alatas) in[#137](https://github.com/enes-alatas/spool/pull/137)\n- fix(web): let Fleet's STATE heading be a heading by [@enes-alatas](https://github.com/enes-alatas) in[#138](https://github.com/enes-alatas/spool/pull/138)\n- feat(web): draw the scrollbars in the room's own vocabulary by [@enes-alatas](https://github.com/enes-alatas) in[#139](https://github.com/enes-alatas/spool/pull/139)\n- refactor(telegram): the ingest-election margin becomes a value by [@enes-alatas](https://github.com/enes-alatas) in[#140](https://github.com/enes-alatas/spool/pull/140)\n- feat(web): clamp a long mission, with the rest one click away by [@enes-alatas](https://github.com/enes-alatas) in[#142](https://github.com/enes-alatas/spool/pull/142)\n- test(itest): port the m4 storm-guard and m6 worktree scenarios by [@enes-alatas](https://github.com/enes-alatas) in[#144](https://github.com/enes-alatas/spool/pull/144)\n- feat(web): say what happened when a loop's session or machine changed by [@enes-alatas](https://github.com/enes-alatas) in[#145](https://github.com/enes-alatas/spool/pull/145)\n- fix(telegram): keep the bot token out of transport errors by [@enes-alatas](https://github.com/enes-alatas) in[#155](https://github.com/enes-alatas/spool/pull/155)\n- docs: secrets stay out of evidence, not just out of code by [@enes-alatas](https://github.com/enes-alatas) in[#156](https://github.com/enes-alatas/spool/pull/156)\n- fix(telegram): a send that fails is retried, then recorded — never dropped by [@enes-alatas](https://github.com/enes-alatas) in[#148](https://github.com/enes-alatas/spool/pull/148)\n- fix(datadir): spool's data is private to the account that runs it by [@enes-alatas](https://github.com/enes-alatas) in[#159](https://github.com/enes-alatas/spool/pull/159)\n- feat(web): walk back through a loop's history, without losing the thread by [@enes-alatas](https://github.com/enes-alatas) in[#160](https://github.com/enes-alatas/spool/pull/160)\n- fix(store): a single-field write stops reverting its neighbours by [@enes-alatas](https://github.com/enes-alatas) in[#163](https://github.com/enes-alatas/spool/pull/163)\n- feat(web): rotate a loop's bot token from the loop page by [@enes-alatas](https://github.com/enes-alatas) in[#165](https://github.com/enes-alatas/spool/pull/165)\n- fix(web): credential fields stop showing and stop offering themselves by [@enes-alatas](https://github.com/enes-alatas) in[#169](https://github.com/enes-alatas/spool/pull/169)\n- fix(web): dress every control on the element, not on a wrapper by [@enes-alatas](https://github.com/enes-alatas) in[#170](https://github.com/enes-alatas/spool/pull/170)\n- feat(loop): tell a running session when its standing instructions change by [@enes-alatas](https://github.com/enes-alatas) in[#174](https://github.com/enes-alatas/spool/pull/174)\n- feat(web): the rotation thresholds are settings, so Settings edits them by [@enes-alatas](https://github.com/enes-alatas) in[#172](https://github.com/enes-alatas/spool/pull/172)\n- chore(web): a unit test tier for the control room, on Vitest by [@enes-alatas](https://github.com/enes-alatas) in[#176](https://github.com/enes-alatas/spool/pull/176)\n- feat(loop): a loop hears which of its own messages never arrived by [@enes-alatas](https://github.com/enes-alatas) in[#177](https://github.com/enes-alatas/spool/pull/177)\n- fix(web): a disabled control mutes its text, and drops what well it has by [@enes-alatas](https://github.com/enes-alatas) in[#178](https://github.com/enes-alatas/spool/pull/178)\n- fix(httpapi): saving a loop's settings stops reverting a concurrent bot write by [@enes-alatas](https://github.com/enes-alatas) in[#179](https://github.com/enes-alatas/spool/pull/179)\n- chore(ci): spend Actions minutes on the jobs a change can break by [@enes-alatas](https://github.com/enes-alatas) in[https://github](https://github) ....\n\n[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.2.0)", "url": "https://wpnews.pro/news/spool-now-runs-claude-code-loops-on-anthropic-s-cloud", "canonical_source": "https://github.com/enes-alatas/spool/releases", "published_at": "2026-10-26 16:56:05+00:00", "updated_at": "2026-10-26 17:11:31.378912+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["Spool", "enes-alatas", "GitHub", "Claude Code", "Anthropic", "Telegram", "Slack", "Dependabot"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/spool-now-runs-claude-code-loops-on-anthropic-s-cloud", "markdown": "https://wpnews.pro/news/spool-now-runs-claude-code-loops-on-anthropic-s-cloud.md", "text": "https://wpnews.pro/news/spool-now-runs-claude-code-loops-on-anthropic-s-cloud.txt", "jsonld": "https://wpnews.pro/news/spool-now-runs-claude-code-loops-on-anthropic-s-cloud.jsonld"}}