Problem: WebSocket Does Not Replace Webhooks for Proactive Message Delivery in Multi-Tenant Environments
Background
Our previous issue (#2602) proposed an HTTP Streaming Channel with webhook-based proactive message delivery. The issue was closed with the maintainer noting "Now we have official websocket channel 💖" (PR #2946/#2964).
After weeks of production testing migrating our multi-tenant PirinAI infrastructure from HTTP streaming to the official WebSocket channel, we've confirmed that the WebSocket channel does not solve the proactive messaging problem for managed multi-tenant environments.
What We Tried
1. Full Migration to WebSocket Channel
We moved our entire infrastructure — Discord bot gateway managing per-user nanobot containers — from a custom HTTP streaming plugin to the official WebSocket channel. The WS channel handles interactive request-response streaming beautifully (token-by-token delta events work great).
But it fundamentally breaks for proactive messages:
- Cron jobs fire inside the nanobot container with no active WebSocket subscriber. The
message() tool looks up self._subs[chat_id], finds nothing, and the message is silently dropped.
- Heartbeat evaluations have the same problem — agent-initiated sends hit an empty subscriber list.
- The agent's
evaluate_response() is the only fallback, but it uses a local LLM that aggressively classifies most outputs as "routine" and suppresses them. Even when it decides to send, there's no WS subscriber to deliver to.
2. OpenAI API Channel Test
Before WS migration, we also tested nanobot's OpenAI-compatible API server:
- Purely request-response — no server-initiated push mechanism
- Session management is one-sided; cron/heartbeat sessions can't push results back to the gateway
The Core Problem
Both the WebSocket and OpenAI API channels are designed around a pull / request-response model:
| Delivery Pattern |
Proactive Send |
Response Streaming |
| WebSocket |
❌ No subscriber = no delivery |
✅ Great for active connections |
| OpenAI API |
❌ No push mechanism |
✅ Per-request streaming |
| HTTP Streaming + Webhook |
✅ Webhook POST to gateway |
✅ SSE per request |
In our multi-tenant setup, the gateway owns the Discord/Telegram connections. Nanobot containers are isolated agents. When a cron job fires:
- The container needs to push results to the gateway
- The gateway delivers to the platform channel
WebSocket only works with a persistent connection, which doesn't exist for automated cron/heartbeat sessions.
Current Hack Workaround
We implemented a fragile polling mechanism in our gateway:
- On WebSocket connect events, scan the container's cron session JSONL files for recent assistant messages without corresponding
message() tool calls
- Extract and forward those to Discord
- Downsides: Only triggers on new WS connections. Idle periods = missed messages. Adds latency and complexity.
The Cleaner Approach (Our Proposal)
An HTTP streaming channel with webhook delivery that:
- Exposes POST + SSE endpoints for interactive streaming
- Adds a configurable webhook URL — when cron/heartbeat/agent-initiated sends fire and no WS subscriber exists, the response is POSTed to the webhook URL
- Falls back gracefully — WS subscriber present = stream directly. No subscriber = use webhook. The gateway handles both.
Request
Could the WebSocket channel (or a companion plugin) be extended to support:
- Webhook fallback — when
send() finds no subscribers, POST to a configured webhook URL instead of silently dropping?
- Or exposing a hook that external gateways can register for cron/heartbeat/unsolicited send events?
We have a production-tested external plugin and are happy to contribute a clean PR.
Related
Happy to collaborate on implementation.
Problem: WebSocket Does Not Replace Webhooks for Proactive Message Delivery in Multi-Tenant Environments
Background
Our previous issue (#2602) proposed an HTTP Streaming Channel with webhook-based proactive message delivery. The issue was closed with the maintainer noting "Now we have official websocket channel 💖" (PR #2946/#2964).
After weeks of production testing migrating our multi-tenant PirinAI infrastructure from HTTP streaming to the official WebSocket channel, we've confirmed that the WebSocket channel does not solve the proactive messaging problem for managed multi-tenant environments.
What We Tried
1. Full Migration to WebSocket Channel
We moved our entire infrastructure — Discord bot gateway managing per-user nanobot containers — from a custom HTTP streaming plugin to the official WebSocket channel. The WS channel handles interactive request-response streaming beautifully (token-by-token delta events work great).
But it fundamentally breaks for proactive messages:
message()tool looks upself._subs[chat_id], finds nothing, and the message is silently dropped.evaluate_response()is the only fallback, but it uses a local LLM that aggressively classifies most outputs as "routine" and suppresses them. Even when it decides to send, there's no WS subscriber to deliver to.2. OpenAI API Channel Test
Before WS migration, we also tested nanobot's OpenAI-compatible API server:
The Core Problem
Both the WebSocket and OpenAI API channels are designed around a pull / request-response model:
In our multi-tenant setup, the gateway owns the Discord/Telegram connections. Nanobot containers are isolated agents. When a cron job fires:
WebSocket only works with a persistent connection, which doesn't exist for automated cron/heartbeat sessions.
Current Hack Workaround
We implemented a fragile polling mechanism in our gateway:
message()tool callsThe Cleaner Approach (Our Proposal)
An HTTP streaming channel with webhook delivery that:
Request
Could the WebSocket channel (or a companion plugin) be extended to support:
send()finds no subscribers, POST to a configured webhook URL instead of silently dropping?We have a production-tested external plugin and are happy to contribute a clean PR.
Related
Happy to collaborate on implementation.