2: The worker pool
The daemon keeps a pool of resident PHP workers per app. Each worker boots the app once and then answers many calls, removing the per-call boot that the one-shot path pays. It is the same idea as FrankenPHP's HTTP worker mode, but for non-HTTP work.
2.1: The worker protocol
A worker runs php <app.php> phlo_serve, boots the app, and then reads newline-delimited JSON on stdin, one request in flight at a time:
in {"id", "target", "args"?, "stream"?}
out {"t":"ready"} // once, after boot
{"id", "t":"line", "data"} // 0..N, only when streaming
{"id", "t":"done", "result"} | {"id", "t":"error", "message"} // exactly one, terminal
Concurrency equals the pool size: a worker handles one call at a time, and the daemon queues the rest until a worker frees up.
2.2: Per-call reset
Between calls a worker resets state (phlo('tech/reset'), session close, GC), exactly like the FrankenPHP worker loop, so one call never leaks into the next. Write worker-safe targets: no request or user state in statics, and commit or roll back DB work. Long-lived connections you explicitly mark with objPers survive; everything else is cleared.
2.3: Dynamic sizing, recycle
The pool sizes itself: it spawns a worker when a call arrives and none is free, up to a global cap of one less than the core count, and reaps a worker once it sits idle. Nothing is configured.
Whether an app runs one-shot (a fresh process per call, which keeps hot-reload working) or on the resident pool (warm workers, no per-call boot) is configured per-host in config/daemon.js (or per-call for CLI dispatch): a build: true dev app is one-shot, a release app is pooled. Two guards keep a resident pool healthy: a per-call timeout (a stuck worker is killed and respawned) and recycling (a worker is replaced after a number of calls, to shed any slow leak). After a deploy, restart the workers so they reload the new build.
2.4: Health
GET /health on the daemon reports the live worker total against the cap, each pool keyed by app path, the connected sockets per host, and the configured hosts:
{
"status": "ok",
"workers": 5,
"cap": 7,
"pools": { "/srv/app/release/www/app.php": { "workers": 4, "busy": 1, "queued": 0 } },
"sockets": { "app.example.com": { "tokens": 12, "sockets": 18 } },
"registered": ["app.example.com", "dev.example.com"]
}
busy is the workers currently running a call and queued the calls waiting for a free worker; workers/cap is the live total against the ceiling. A busy that sits at the cap with a growing queued means the node is at capacity: add nodes or give it more cores.