Skip to content

Daemon fast-send: sub-second chat delivery via the gateway relay socket - #85

Merged
obaid merged 1 commit into
mainfrom
feature/daemon-fast-send
Aug 24, 2026
Merged

obaid merged 1 commit into
mainfrom
feature/daemon-fast-send

Conversation

@obaid

@obaid obaid commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #84's streaming fix, attacking the other half of chat latency: time-to-first-token, which was ~10–15s of pure transport (queue pickup + SSH handshake + remote CLI startup) before the gateway saw the message.

provisiond already holds an authenticated loopback WebSocket to the gateway (the relay that streams deltas up). Sends now ride the same socket: the web request drops the message into a per-server Redis outbox and blocks ~600ms on the daemon's ack; the daemon long-polls the outbox, fires chat.send over its socket, and acks. The queued job retries the fast path with a longer window, then falls back to the unchanged SSH path — chat.send is idempotent by key, so every failure mode degrades to today's behavior rather than losing a message.

Measured (live browser vs E2E droplet)

before after
POST → send ack n/a (queued) ~600ms
POST → first streamed delta ~17.8s 7.0s (model TTFT dominates)

Notes

  • Text-only messages; attachments keep the SSH staging path (they need on-box files).
  • The outbox long-poll holds one FPM worker per polling daemon (wait capped at 15s) — pool sizing note in the controller docblock.
  • daemon v0.5.0 (chat-send-v1), 41 vitest tests; 7 new Pest tests; full suite 1147 green.
  • Deployed and verified on the E2E droplet end-to-end.

Time-to-first-token was dominated by transport, not the model: queue
pickup + a fresh SSH handshake + remote CLI startup cost 10-15s before
the gateway even saw the message. provisiond already holds an open
loopback WebSocket to the gateway, so sends now ride it:

- Redis outbox per server; provisiond (v0.5.0, capability chat-send-v1)
  long-polls GET /chat/outbox, fires chat.send over the relay socket
  within ~100ms, and acks via POST /chat/outbox/ack
- DaemonChatSendService drops the send into the outbox and blocks
  briefly on the ack; ChatController attempts it inline in the web
  request (2s ack window) so the happy path skips the queue entirely,
  and SendAgentChatMessageJob retries it (4s window) before falling
  back to the unchanged SSH path — chat.send is idempotent by key, so
  a late daemon send after fallback is harmless
- Scope: text-only messages; attachments keep the SSH staging path
- Stale outbox entries (>30s, from requests that already fell back)
  are skipped at poll time

Measured from a live browser against the E2E droplet: POST acked in
~600ms, first streamed delta at 7.0s total (was 17.8s) — the remainder
is model TTFT, no longer transport.
@obaid
obaid merged commit 9e2a6dc into main Aug 24, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant