Upgrade to client-worker mode
About 1156 wordsAbout 4 min
This page is for operators on spora-core ≤ 0.18.x who are upgrading to 0.19.0 or later. The legacy SPORA_SYNC_MODE env var is removed in 0.19.0; the new model has two runtime modes (server, client) plus a per-package default that picks the right one for your host.
What changed
| Pre-0.19 | 0.19+ |
|---|---|
SPORA_SYNC_MODE=true (HTTP request blocks) | Removed. The HTTP endpoint always returns once the task is QUEUED. |
SPORA_SYNC_MODE=false + php bin/spora worker:run | SPORA_WORKER_RUNTIME_MODE=server + php bin/spora worker:run --daemon. Same daemon behaviour, no other changes. |
Cron mode (worker:run --once --include-queue) | Still supported in server mode. |
| (no equivalent) | SPORA_WORKER_RUNTIME_MODE=client + browser SharedWorker. The new zero-config option for shared hosts. |
WorkerMode::Sync enum case | Removed. The enum is single-case (WorkerMode::Worker). |
The migration is forward-only — the 0070_add_lease_columns_to_tasks migration that ships in 0.19.0 adds lease_owner (varchar 100) + lease_expires_at (datetime) to tasks. On MySQL/MariaDB it uses ALGORITHM=INPLACE LOCK=NONE for the index so writes are non-blocking. The migration is fully reversible via down(); see Database schema for the migration conventions.
Before you flip from server to client
If you have an existing install on spora-ai/spora (server-default) and want to flip to client-worker mode, the order matters — and you don’t need to change packages. The same spora install handles both modes via .env:
- Finish or fail any
RUNNINGtasks. The reaper sweeps them based onupdated_at(server mode) orlease_expires_at + updated_at(client mode); on a fresh upgrade the existing server-mode rows have no lease, so the reaper considers them reapableSPORA_WORKER_STALE_MINUTESafter the last write. RunSELECT COUNT(*) FROM tasks WHERE status = 'RUNNING';and either let them finish or set them toFAILEDby hand. - Open the UI once after the upgrade so the reaper can sweep stale tasks. In server mode the daemon drives this every 5 minutes; client mode relies on the first
/housekeepingcall from any browser. - Remove
bin/spora worker:runfrom supervisord / cron / systemd. The command now exits with a docs link in client mode — leaving it scheduled will produce a fresh exit log line every cron tick. - If you have unattended scheduled runs (e.g. nightly at 3 AM, no humans online), do NOT flip. Keep server mode. Client mode only dispatches schedules while a browser is open.
Why two packages now
There’s only one public Composer package: spora-ai/spora. The runtime mode (server vs client) is a per-install .env setting on the same package. A curated spora-ai/spora-shared skeleton (same code, shared-host-specific scaffolding baked in, client-default) is on the roadmap but not yet published — for now, install spora-ai/spora and flip the env var to land in the same place.
For existing installs, the simplest path is to toggle SPORA_WORKER_RUNTIME_MODE in .env. No package change, no lockfile change, no migration. The lockfiles and .env.example defaults are stable across both modes.
What changed for plugin authors
Nothing for the tool interface. The tool.execute() signature is unchanged across server and client modes.
One nuance worth flagging: the orchestrator IS shared, but the lease is not. Server-mode daemon ticks (WorkerQueueProcessor::processQueuedTaskSync, ScheduledRunProcessor::process) skip the lease entirely — lease_owner stays NULL and LeaseGuard::extend() is a no-op. Only client-mode ticks (TaskTickController::tick, ScheduledRunProcessor::dispatchAndTick) write lease_owner + lease_expires_at and extend the lease at every step boundary. The reaper’s predicate (lease_expires_at IS NULL OR <= now()) handles both modes, but the effective reap threshold differs: server-mode reaps purely by updated_at after SPORA_WORKER_STALE_MINUTES; client-mode reaps after the lease expires + the same silence window. Plugin authors don’t need to care about this — the orchestrator interface is unchanged — but custom tools that wrote directly to tasks.lease_owner would need to know which mode they’re running in (none currently do).
What changed for ops
Three new HTTP routes in spora-core, all gated on WorkerRuntimeMode::Client (inline 404 gate at the top of the controller, matching the PluginsController::catalog precedent):
| Verb | Path | Purpose |
|---|---|---|
POST | /api/v1/tasks/{taskId}/tick | Browser-driven claim + tick for a single task the caller ran. |
POST | /api/v1/worker/housekeeping | Browser-driven orphan reap + synchronous scheduled-run dispatch. |
GET | /api/v1/config | (existing public endpoint, extended) returns client_worker.* block. |
In server mode, these endpoints exist in the route table but 404. No change in server-mode behaviour — server-mode operators running php bin/spora worker:run --daemon see no difference.
Server-mode operators staying on spora-ai/spora
Nothing to do. The 0.19.0 upgrade is transparent: SPORA_SYNC_MODE is replaced by SPORA_WORKER_RUNTIME_MODE=server in .env.example, and the daemon picks up the new env var automatically. Existing supervisord / systemd / cron units continue to work — they call php bin/spora worker:run --daemon (or --once), the daemon polls the queue, scheduled runs dispatch, the reaper sweeps. The new lease_owner / lease_expires_at columns exist but are NOT populated by the daemon’s tick path — the daemon never grants a lease and relies on updated_at for orphan detection. The reaper’s new lease_expires_at IS NULL OR <= now() clause handles both cases (NULL for daemon rows, expired for browser rows).
Shared-host operators moving to client mode
To switch an existing spora install from server mode to client mode:
- Back up the database and
.env—storage/database.sqlite(or amysqldumpfor MySQL/MariaDB),storage/secret.key,.env. See Backups. - Set
SPORA_WORKER_RUNTIME_MODE=clientin.env. No file changes, no rebuild, no composer update — the same code paths serve both modes. - Open the UI, log in, click into a chat. The browser’s
SharedWorkerstarts ticking. - Remove any cron entries invoking
worker:runortask:run— they exit with a docs link now.
The data model is unchanged (tasks, agents, principals, etc. all stay where they are). The new lease_owner / lease_expires_at columns on tasks are populated by the first /tick call.
Verify the upgrade
After upgrading, before declaring done:
- Run
composer analyseandcomposer test:parallelinspora-local/ your dev clone. The CI suite must be green. - Open the install in a browser and confirm the worker badge in the chat header shows “Worker online” (client mode) or that
php bin/spora worker:run --daemonexits cleanly (server mode). - Click into a chat and send a message. The task should progress
QUEUED → RUNNING → COMPLETEDwithin a few seconds. - If you have schedules, force one to fire (
UPDATE scheduled_runs_next SET due_at = NOW() WHERE id = ?;) and confirm/housekeepingdispatches + ticks it.
What’s next
- Deployment modes — the canonical three-configuration overview
- Client-worker mode — the zero-config shared-host guide
- Worker deployment — server-mode cron / supervisord / systemd patterns (unchanged)
- Environment variables — full env-var reference