This runbook covers the detached launcher worker for interactive tasks in OPS Center.
https://ops.solucionesabiertas.net/apiops-dispatch-launcher.service/var/log/ops-dispatch-launcher.log/root/.ops-launcher-logs/*.logX-OPS-Service-Token (key service_api_token in ops_settings)TRUST_PROXY=true when running behind reverse proxyAGENT_CMD_OPENCODE, AGENT_CMD_CLAUDECODE, AGENT_CMD_CODEX, AGENT_CMD_HERMES, SUPPORTED_AGENT_SLUGS, SUPPORTED_MODELSdispatch_stale_minutes (default runtime fallback: 20)POST /api/dispatch/heartbeat, stored in worker_heartbeatsdispatch_worker_stale_seconds (default 90)Related guides:
docs/README.mddocs/ops-daily-mode.mddocs/templates-subagents-playbook.mddocs/ops-efficient-workflow.mddocs/sa-ops-system/README.mddocs/sa-ops-system/operating-modes.mdRun on sa-tools:
systemctl is-active ops-dispatch-launcher.service
systemctl is-active ops-center.service
Expected: both services return active.
Then run runtime doctor from project root:
npm run worker:doctor
Expected: [doctor] PASS all runtimes available.
If doctor fails with MISSING, install the missing CLI before enabling dispatch.
If a new agent uses fallback command resolution, ensure its slug is covered by the launcher env map or set explicit launch_command in OPS.
Current VPS note, 2026-05-05:
claudecode is intentionally inactive in the DB because the VPS has neither ANTHROPIC_API_KEY nor a claude auth login session.npm run worker:doctor passes.opencode, codex and hermes.Health with worker capabilities:
curl -H "X-OPS-Service-Token: $OPS_SERVICE_TOKEN" \
https://ops.solucionesabiertas.net/api/dispatch/health
Expected:
ok=truedispatch_enabled matches the intended operating modeworker_summary.fresh >= 1 when dispatch is enabledqueue.stale_dispatching = 0agent_slugs matching authenticated runtimes on that hostThe launcher only requests tasks for usable agents reported by heartbeat.
From the UI, open Operacion -> Automatizaciones to see the same diagnostics without SSH. Workspace admins can refresh health, toggle dispatch, inspect stale tasks and run the manual watchdog.
For CLI/API operations export the service token once:
export OPS_SERVICE_TOKEN='<token>'
If auth was just migrated, rotate defaults before enabling dispatch:
OPS_ADMIN_USER and OPS_ADMIN_PASSWORD in OPS Center service env.OPS_SERVICE_TOKEN in launcher/doctor env.ops_settings.service_api_token with that same token.For API-level dispatch smoke without launching an AI runtime:
OPS_API_BASE=http://127.0.0.1:3847/api \
OPS_SERVICE_TOKEN=... \
npm run smoke:dispatch
The smoke creates temporary tasks, verifies next -> queued -> dispatching -> review, verifies Human Gate blocking for a sensitive task, and deletes the temporary tasks.
execution=interactive and agent_id assigned.
curl -H "X-OPS-Service-Token: $OPS_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"value":"true"}' \
https://ops.solucionesabiertas.net/api/settings/dispatch_enabled
/tasks/:id/dispatch).queued -> dispatching -> doing.dispatch_output and detached log files.
curl -H "X-OPS-Service-Token: $OPS_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"value":"false"}' \
https://ops.solucionesabiertas.net/api/settings/dispatch_enabled
Common failure signatures:
runtime no disponible en host: <cli>launcher process exited early (code=..., signal=...)command not found in .err.logRequired actions:
dispatch_enabled=false.blocked=true with concrete blocked_reason.kind=blocker) with evidence./api/dispatch/next now reconciles stale dispatching tasks automatically before selecting a new task.
dispatching for more than dispatch_stale_minutes.waiting, sets blocked=true, clears worker claim fields, writes blocker context and opens/reopens a system alert linked to the task.dispatch.reconciled_stale.Manual reconciliation endpoint (admin/service only):
curl -H "X-OPS-Service-Token: $OPS_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
-X POST \
-d '{"minutes":20}' \
https://ops.solucionesabiertas.net/api/dispatch/reconcile-stale
Returns:
stale_minutesreconciledtask_idsControlled smoke:
OPS_API_BASE=http://127.0.0.1:3847/api \
OPS_SERVICE_TOKEN=$OPS_SERVICE_TOKEN \
OPS_WORKSPACE_ID=1 \
npm run smoke:watchdog
The smoke creates temporary dispatch and recurring failures, verifies watchdog alerts/tasks, then archives/resolves its temporary artifacts.
UI path:
Operacion -> Automatizaciones.Dispatching and Watchdog.Reconciliar stale.Recommended default:
20 min
curl -H "X-OPS-Service-Token: $OPS_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"value":"20"}' \
https://ops.solucionesabiertas.net/api/settings/dispatch_stale_minutes
To avoid false positives (dispatch OK without deliverable), AI tasks cannot move to done unless there is visible output evidence:
ai-dispatch log comments)Guard applies to:
PATCH /api/tasks/:id/status when status=donePATCH /api/tasks/:id when status is changed to doneBypass only when strictly needed:
?force=true (admin/service/manual recovery)Operational policy:
done.Worker model filters now match by family, not only exact label. This allows safe aliasing:
minimax* matches minimax-m2, minimax2.7qwen* matches qwen3.6, local-qwen3.6-codergemma* matches gemma4, local-gemma4gpt-* and other families keep existing behaviorThis reduces routing misses during gradual model renaming/migration.
Install logrotate rule:
install -m 0644 scripts/ops-dispatch-logrotate.conf /etc/logrotate.d/ops-dispatch
Manual test:
logrotate -d /etc/logrotate.d/ops-dispatch
systemctl restart ops-dispatch-launcher.servicenpm run worker:doctordispatch_enabled is the intended valuereview or done only after visible deliverable evidence is present.Two scripts are available for this workflow:
OPS_ADMIN_USER='<admin>' \
OPS_ADMIN_PASSWORD='<strong-pass>' \
OPS_SERVICE_TOKEN='<strong-token>' \
scripts/deploy-auth-hardening.sh \
--repo-path /opt/sa-ops-center \
--confirm-prod
OPS_SERVICE_TOKEN='<token>' \
scripts/pilot-dispatch-task.sh \
--task-id 411 \
--confirm-pilot
Both scripts include hard stop flags (--confirm-prod, --confirm-pilot) to prevent accidental production execution.