Bridges, one server, and the last steps
The next SmolPaws is a set of standalone channel bridges attached to one TypeScript OpenHands agent-server. Any bridge can be started on its own by a LaunchAgent; it boots the server if the server is not there. This page is the current map and the remaining checklist.
STATUS: WHATSAPP ON SHARED :8790 2026-09-17 epic smolpaws-zlo bead smolpaws-kxa
smolpaws/smolpaws docs/bridges.md, docs/whatsapp/README.md, src/coordinator/DESIGN.md. When they disagree with this page, they win; this page is a snapshot.1Process topology
The shared execution core is the transpiled TypeScript server composed by apps/relay-server. The product host binds scheduler and media tools; the package keeps its upstream-shaped API. Bridge launchers verify the product host and start one detached supervisor when needed. That supervisor restarts a failed server child independently of bridge lifetimes. Implementation #172.
launchd (macOS)
├─ com.smolpaws.bridge.whatsapp ─┐
├─ com.smolpaws.bridge.slack ────┼─ scripts/run-local-bridge.sh <bridge>
└─ com.smolpaws.bridge.discord ──┘ │
├─ GET http://127.0.0.1:8790/health
├─ if down: start the shared product-server supervisor
└─ exec npm --prefix apps/<bridge> run start
│
apps/<bridge>: platform socket → RelayRuntime (SQLite intake/outbox)
→ POST /api/conversations/{id}/events (run)
→ GET /api/conversations/{id}/events/search
→ DeliveryTarget → platform send
│
apps/relay-server → packages/openhands-agent-server :8790
KeepAlive restarts a crashed bridge without touching the server.| Rule | Why |
|---|---|
| Any bridge can boot the server; none stops it. | "Start the WhatsApp bridge and it just works", also when Slack is down. |
One durable SQLite relay store per bridge and a separate store for native API work. They share the scheduler, not work claims. The native host uses its own override, SMOLPAWS_AGENT_SERVER_RELAY_DB_PATH. WhatsApp and Discord conversation IDs derive directly from their lanes; Slack retains its existing namespace. | A fresh relay DB is not permission to reuse an old server EventLog: its owner tag must match. Canary isolation must cover server persistence too. See history and state handoff. |
| The server knows nothing about channels. | Delivery, ordering, retries, idempotency live in the Message Relay; the server stays upstream-shaped and re-vendorable. |
| Every new conversation gets a real working directory and the SmolPaws identity context. | Before this, paws on Slack introduced itself as a generic assistant and its terminal tool ran in a directory that did not exist. |
2What the bridges share
Slack pioneered the relay shape; the loop it ran is now src/coordinator/relayRuntime.ts (RelayRuntime) and every bridge is a thin skin over it: a platform socket, a pure handler (who is allowed, what counts as addressed, how a batch becomes a prompt, which lane it belongs to), a delivery target, and an entrypoint that sets the conversation defaults.
| Concern | Where |
|---|---|
| Durable intake, lane→conversation binding, outbox sync, bounded dispatch | src/coordinator/relayRuntime.ts |
| What is deliverable | bridgeResponseExtractor combines explicit send_message actions, terminal replies and safe ConversationErrorEvent notices. WhatsApp, Slack and Discord share it in code. WhatsApp’s restarted canary is verified on #188 · 02a8187; the running Slack process has not yet reloaded this extractor. Deliveries keep their durable event identity. Error handling and verification. |
| Run limits and continuation | The normal limit is 500 steps per run. The temporary 12-step canary override was removed on September 16; production WhatsApp now uses the shared :8790 host with the normal limit. A new prompt continues saved history with a fresh run budget; the operator does not replay completed tools or rewind delivery cursors. |
| Workspace + identity context | src/shared/relayConversationDefaults.ts selects the workspace. Merged context update #186 moves file loading into apps/relay-server/src/context.ts: trusted lane → configured files → per-conversation snapshot → SDK always-on skills. The 32,768-character launch suffix cap remains. Design and rollout status. |
| Launch and supervision | scripts/run-local-bridge.sh, launchd/com.smolpaws.bridge.plist, scripts/install-bridge-launchagent.sh <bridge> |
3Implementation and deployment are separate
| Channel | Implemented path | Evidence / remaining work |
|---|---|---|
Slack paws | Standalone Message Relay | Historical live transport canary with a deterministic LLM, plus later deployment reports. Verify the current process, provider and soak; those reports are not live telemetry. Slack evidence. |
apps/whatsapp, shared relay, text and media/voice delivery | Production cutover September 17: Main, OpenHands and Hunting use the shared :8790 product host with their existing conversations. The temporary :8791 host is retired and legacy WhatsApp stays disabled. Text, image, voice and scheduled-reply evidence from the September 15–16 canary remains historical. Current storage and cutover verification. | |
| Discord | apps/discord, standalone relay | Rewrite merged in #170; fail-closed allowlist preserved. Live validation remains. |
| GitHub, email | Workers → legacy /turns runner | Remaining consumers of the legacy server; migrate before retiring it. |
| Heartbeat | Shared product-host startup | #178 shares the bridge launcher’s product-host check and supervised startup. Heartbeat uses an absolute workspace and supports session-key authentication when configured. Each tick appends a stable event to its daily conversation and starts a run only when that event is new. See the startup contract and tests. |
4WhatsApp startup has a state handoff
An existing device link can be reused. One account must have one active socket owner. Before using the launcher on an existing ledger, establish the cursor baseline, explicit state paths, one-chat allowlist and rollback handoff. The actual old JSON progress is now imported once into the shared message-identity journal; rollback also exports task changes and requires the updated legacy host.
The bridge README describes pairing and configuration. The readiness plan owns operational prerequisites. A service swap alone is not a complete migration.
5The remaining sequence
- implemented — shared relay runtime, standalone WhatsApp/Discord bridges, workspace defaults, configured always-on context files and deterministic real-server tests.
- deployed host verified — canonical SDK, real-provider multi-tool execution and continuation after process restart passed (
kxa.7). - controlled test passed — iPad text, image, voice, scheduled reply and queued-media reconnect confirmed; final echoes repaired (
kxa.5). - WhatsApp promoted — Main, OpenHands and Hunting use the normal bridge and shared
:8790host, with their existing conversations and one socket owner. Observe ordinary replies and schedules after the cutover; retain the migration backup. - remaining ingress — complete other ingress migrations, then retire
apps/agent-serverwhen no active consumer remains.
Detailed acceptance and Beads ownership: WhatsApp readiness.
6Keeping the engine current
Maintenance has three responsibilities: the SDK discovery watcher, bounded SDK pin advancement (docs/DRIFT_AUTOMATION.md), and server re-vendoring plus server-unit review (packages/openhands-agent-server/docs/REVENDOR_AUTOMATION.md). The runbooks describe how to execute them; this audit did not verify active cloud schedules. Provider compatibility also needs SDK-owned fixes between pin advances. See Maintaining the OpenHands TypeScript transpiles.
ChatGPT subscription authentication follows the same ownership split: SDK OAuth storage, refresh and transport; server device-login routes and profile restoration. See the subscription authentication boundary.
7Pages this supersedes
The older channel pages describe the legacy root process and the /turns adapters; they now carry a banner pointing here. WhatsApp as a bridge is the design this implements. The SDK-swap board and the original seven-step plan are history.