From a local, single-user instrument to a deployed, auth-gated, multi-user research room — reachable from mainland China without a VPN. This page records the agreed plan: the reply model, the data model, the phased build, and how each piece is tested before it ships.
127.0.0.1:8010state.json)When a human posts: if the panel is idle, it answers immediately. If the panel is mid-turn, the message joins a queue; when the turn finishes, the whole queue drains into a single next turn. Each AI character then addresses one or many of the people who posted, by name.
Because a turn fires at most once per turn-completion, ten people typing during one turn cost one reply, not ten — spend scales with the panel's pace, not the crowd's. No @-mention gate is needed; the panel always engages.
@human-alice is a message aimed at another person. It's written to the conversation like any line — so the panel sees it the next time it speaks, a small ambient influence — but it does not fire a turn of its own. “Panel stays out” means “doesn't trigger a turn,” not “never sees it.”How the voices inside one turn are sized and ordered — who leads, who adds a point, who concurs in a line — is the DECIDE gate. Busy-state batching decides when a turn fires; DECIDE shapes what it says.
state.json is the panel's memory — everything in it is read back into the model. So presence signals must never land there.
“Panel responding…” is free — the server already knows it's holding the room lock, so it can announce the in-flight turn with zero extra signalling. Only the human “is typing…” needs a small client signal (debounced keystrokes + a timeout to clear it).
Browsers receive a live stream over Server-Sent Events: one long-lived connection the server pushes to. The human's message is a normal POST; only the things to show back — others' messages, the panel's reply, presence — are pushed.
| transport | direction | fit here |
|---|---|---|
| polling | client re-asks every ~2s | simplest, but laggy and wasteful; a typing indicator at 2s feels broken |
| SSE | server → client push | exactly this shape — clients only need to receive; instant; plain HTTP through Caddy |
| WebSocket | full duplex | overkill — humans already POST normally; nothing needs a two-way pipe |
A small SQLite database carries the relational layer; the conversations themselves stay in the flat files they already live in.
state.json.tester.Phase A — identity & ownership (the next build). Each step is runnable and verifiable on its own — no big-bang.
| step | delivers | you can test |
|---|---|---|
| A0 · recon | read the current server; confirm exact routes & signatures before editing | grounding |
| A1 · DB | lib/db.py — SQLite schema + init_db() | create DB → insert a user → read back |
| A2 · auth core | lib/auth.py — argon2, session cookie, current-user / member / admin deps, invite + cap checks | signup w/ good & bad invite, login, wrong password, logout |
| A3 · gate API | auth on every /api/*; signup/login/logout/me routes | /api/rooms with no cookie → 401; after login → 200 |
| A4 · ownership | create() writes owner; list() → "rooms I'm in"; member add/remove/list | A makes a room, B can't see it until added |
| A5 · identity | say(text, user) stamps the real name → 「Alice」:; per-human log; turn_events row per turn; SP clause | Alice's turn shows her name in input + transcript + a stats row |
| A6 · login UI | lib/login.html + auth gate + member UI in room-ui.html | full browser flow: signup → room → add member → logout |
| A7 · migration | lib/migrate_users.py — register existing rooms under the seed admin | rehearse on a copy; all old rooms appear under you, nothing lost |
Phase B — live co-presence (after A is proven): decouple post from turn (the queue) · @human-X routing · the SSE stream · the RAM-only presence channel · multi-human labels, colours, and “is typing…”.
Phase C — stats (any time after A1): admin-only /api/admin/stats (GROUP BY over turn_events) + a house-style lib/admin.html.
All logic, with real API turns — the home network exits via a Singapore-supported route, so no mock or VPN is needed.
Several real, logged-in users on phones + laptops over the LAN — a live group-chat test at zero cloud cost.
The same stack on the Ali ECS box; only TLS + the China path are added on top. See the topology →
This work lives on its own branch in a copy of the repo kept outside the cloud-sync folder, so the live instrument you test on every day is never disturbed. Git — not file-sync — is the bridge between the two: git and a sync tool both grabbing the same .git is how repos corrupt.