Repository navigation
feat(directory): add a per-project Lobby cell beside the directory - #335
Merged
Merged
Conversation
This was referenced Oct 15, 2026
iamnbutler
force-pushed
the
lobby-project-id
branch
2 times, most recently
from
October 15, 2026 04:06
c3e9801 to
8410e5f
Compare
iamnbutler
force-pushed
the
lobby-cell
branch
from
October 15, 2026 04:19
16afb44 to
7a3de2d
Compare
iamnbutler
force-pushed
the
lobby-project-id
branch
from
October 15, 2026 10:18
8410e5f to
73ee783
Compare
The directory's Worker gains a Lobby Durable Object, one per project ID, at GET /lobbies/<project> behind the same ACE_SECRET. Hosts hold one socket per project: they replace their owner's presence, which lives only in hibernation attachments, and send channel lifecycle events, which the lobby numbers with one seq per project and keeps as a 30-day, 2,000-event resume buffer. A host resuming from a pruned or unknown seq is told to read listings again. Pings are auto-answered and an alarm evicts sockets silent for 90 s. The Directory object and its contract are unchanged. Refs #17, #14. Why: services/directory/src/lobby.ts#const MIGRATIONS | Durable Object SQLite refuses PRAGMA user_version, so the schema version lives in the kv `version` key; never edit a shipped migration Why: services/directory/src/lobby.ts#method webSocketMessage | seq lives in kv apart from the rows so pruning every event never reuses a seq a host resumes from Why: services/directory/src/lobby.ts#type Attachment | presence must never reach SQLite; it lives only in socket attachments
Re-arm the 60 s sweep when a host joins a lobby whose alarm waits on the oldest event, prune by the lobby's own receipt time, fit presence into the 2 KiB hibernation attachment, replace sockets only within one host process, and answer a malformed project ID with 400. Why: services/directory/src/lobby.ts#method fetch | a joining host must re-arm the sweep when the alarm is further than SWEEP away, or silent sockets stay here until the oldest event expires Why: services/directory/src/lobby.ts#function fit | serializeAttachment throws above 2 KiB, so presence must be trimmed to fit before every serialize Why: services/directory/src/lobby.ts#method webSocketMessage | the events.at column is the lobby's receipt time, never the host's clock, since retention and resync depend on it
Each sweep re-arms the alarm, which Durable Objects bill as one row write, so a 60 s sweep cost 1,440 writes a day per project with a host connected. Every 5 minutes costs 288. Silent sockets still close after 90 s without a ping, now within about 6.5 minutes. Why: services/directory/src/lobby.ts#const SWEEP | the alarm re-arm is the lobby's only steady storage write; pings and presence must never write, and the sweep must not run more often than the write budget allows
iamnbutler
force-pushed
the
lobby-cell
branch
from
October 15, 2026 10:19
7a3de2d to
8d1a362
Compare
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of four stacked PRs for project lobbies (base: #334). Refs #17, #14.
Adds the
LobbyDurable Object to the directory's Worker: one per project, named by the shared project ID, atGET /lobbies/<project>behind the sameACE_SECRET. TheDirectoryobject, its routes, and its stored data are unchanged. Wire types live in@ace/directory/protocolso hosts import them instead of copying.hellocarries a per-process ID. A reconnect replaces only the same process's stale socket, so an installed and a source Ace on one machine each keep theirs instead of evicting each other.seqper project. A host sayshellowith the newestseqit applied and gets the events after it, thensynced. When those events are gone (pruned, from before the lobby's storage, or a first visit)syncedcarriesresync: trueand the host reads listings again.setAlarm()as one row written, and the free tier allows 100,000 a day. Re-arming the sweep is the lobby's only steady write: 288 a day per project while any host is connected (the sweep was every 60 s, 1,440 a day, before the rebase). Pings,hello, and presence never write; presence lives only in socket attachments. A lifecycle event writes 3 rows (theseqkv key, the row, and itsatindex entry), and pruning it later writes 2 more. An empty lobby writes one alarm when its last host leaves and one when a host returns.PRAGMA user_version(SQLITE_AUTHin miniflare), so the schema version is counted in the object'sversionkv key and migrations run in one transaction.seqlives in kv too, so pruning every row never reuses a seq.Deploying needs only
wrangler deployofservices/directory: it adds theLOBBYbinding and av2migration withnew_sqlite_classes: ["Lobby"]. No new secrets. Not deployed by this PR. The liveace-directoryWorker's last applied migration tag isv1, read from the Workers scripts API, sov2applies cleanly.Smoke-tested with
wrangler dev(miniflare): hello/resume/resync atsince0, current, older, and ahead of the log; replay of missed events; presence fan-out and removal on close; ping/pong; eviction of a silent socket after about 120 s; 401 without the secret; the directory'sPUT /hostsandGET /unchanged.After review:
atwas stored and broadcast.silent120 s later.Rebased onto
mainafter #391. The directory's write-minimizingpublishis unchanged; this PR only adds the/lobbiesroute and theLobbyexport beside it. Smoke-tested again from the top of the stack withwrangler dev: a host that saidhelloand then stayed silent was closed assilent300 s later, at the first 5-minute sweep.Built in Ace
No. This was built with Claude Code outside Ace by the overnight watcher, because this Mac's Ace Canary is a participant in the two-person study and can't be used for development tonight. See #5.