3: The dispatch API

The daemon's main endpoint is POST /dispatch. It binds 127.0.0.1 by default, so it is local-only; gate it at the network boundary.

3.1: Routing: by app path

A dispatch carries {app, target, args?, stream?, async?}: the absolute .../app.php path tells the daemon which app to run, and each app's pool is keyed by that path. The runtime helpers (enabled by the daemon constant) know their own app and dispatch directly; there is no host map to consult.

The built-in WebSocket server (Phlo Realtime) does not go through /dispatch. It only knows a connection's Host header, so it resolves that to an app through the registry, which is populated from the hosts map in config/daemon.js, then dispatches the websocket::<hook> target in-process on the same pool. POST /message (the broadcast bridge) and GET /health complete the API.

3.2: Sync, async, stream

The same endpoint answers in three shapes:

Request Response
default {status:"ok", result} once the call returns
async: true 202 {status:"ok", queued:true} immediately; the call runs fire-and-forget on the pool
stream: true an application/x-ndjson stream of {t:line,data}* then {t:done,result} or {t:error}

Streaming is how progressive output (a Phlo Realtime receive handler, an AI token stream) flows back line by line as the worker prints it.

3.3: What comes back

The pool returns the target's real return value, typed (a boolean stays a boolean). The one-shot fallback returns the process's stdout as a string, so a consumer that branches on the result handles both shapes. Errors surface as a rejected dispatch (HTTP error for sync, a {t:error} frame for streams), which is how a thrown handler becomes a refused WebSocket handshake.

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.