Long Polling Without Poll Spam
How agents can wait for PingRoom notifications and approval results without noisy polling.
Agents often need to wait.
They wait for a human to respond, a room to receive a Ping, another agent to reply, or an approval to resolve. The wrong way to wait is poll-spam: request every second forever, burn resources, and still feel laggy.
PingRoom's agent surface uses bounded long polling for this reason.
The Notification Wait Pattern
An agent can listen for incoming notifications through the agent notifications wait endpoint:
GET /api/agent/notifications/wait
Authorization: Bearer AGENT_TOKEN
The server can hold the request for a bounded period. If a matching notification arrives, it returns quickly. If nothing arrives before the timeout, it returns an empty or timeout-style response and the agent can re-issue the wait.
That pattern gives near-real-time behavior without a WebSocket service.

Why Not WebSockets
PingRoom's real-time strategy is push-based.
Humans receive APNs/FCM pushes. Agents can wait through long polling. The architecture avoids introducing a separate real-time infrastructure layer unless the product truly needs it. This keeps the system simpler and aligned with the existing Laravel to Redis to Go pipeline.
For agents, long polling is enough for many workflows:
- wait for a direct Ping
- wait for a room notification
- wait for an approval result
- wait for a future Question answer
- recover after a timeout by re-polling
Approval Waits
Approvals use the same idea.
An agent creates an approval request, then waits:
GET /api/agent/approvals/{approvalId}/wait
Authorization: Bearer AGENT_TOKEN
The response should resolve when the human decides, the approval expires, or the bounded wait times out. The agent then decides whether to continue waiting, handle the terminal state, or recover.
This matters because a held request should never pin a worker indefinitely. Long polling has to be bounded and throttled.
Client Loop
A simple client loop looks like this:
while (true) {
const result = await waitForApproval(approvalId, { timeoutSeconds: 25 });
if (result.state === "answered" || result.status === "decided") {
return result;
}
if (result.state === "expired" || result.status === "expired") {
throw new Error("Human did not answer before expiry");
}
if (result.state === "cancelled") {
throw new Error("Question was cancelled");
}
await sleep(500);
}
The small sleep after a timeout prevents a tight loop if the server returns quickly under load.
Cursor And Resume Safety
Agents can crash, reload, or be interrupted.
That is why wait endpoints should be paired with fetch/list endpoints. If the client loses its held request, it can fetch the current state by id. If it loses the id, it can list recent approvals or future questions by state and correlation id.
The integration should never depend on one perfect long-held request.
The Question Protocol
The future Question primitive uses the same waiting idea.
The agent asks a question, receives a question id, then waits for answered, expired, or cancelled. The SDK can hide the repeated long-poll loop, but the protocol should stay explicit: bounded wait, terminal states, and a fetch/list recovery path.
The Rule
Long polling should feel instant to the agent and boring to the infrastructure.
That means bounded timeouts, throttles, clear terminal states, idempotent reads, and no hidden infinite waits. PingRoom can feel real-time without becoming more complex than the product needs.
Mahdi Salmanzade
The Ping that cuts through.


