Decision 084: one lifecycle surface (start|stop|status|restart) driven by per-service enabled flags
- Status: proposed
- Date: 2026-08-14
- Work item: issue-228
- Deciders: maintainer (via ticket); harness (proposal)
Context
the-loop grew from a poller into four services — poller, webhook receiver, REST control-plane service, MCP layer — but the way an operator brings it up stayed poller-shaped: the-loop poll start, with the receiver and service on sibling commands and the service also auto-started implicitly by any CLI invocation. The ticket asks for a dedicated the-loop start that reads the CLI config and starts what is enabled, removal of the poll commands, and the-loop restart [--with-upgrade] as both a command and an API.
Decision
- Four additive booleans decide composition:
service.enabled(default true),service.mcp.enabled(default true),webhooks.ghWebhook.enabled(default false),polling.enabled(default false). Network-listening ingresses are explicit opt-ins; the service and MCP keep their long-standing on-by-default behaviour. Enablement is never inferred from a block's presence or a non-emptysourceslist (the one-question-two-answers trap). the-loop start|stop|status|restartcompose existing runtimes through a newcore.lifecyclefacade; they are bootstrap commands (the decision-058 exception, likeservice).stopignoresenabled(a disabled-after-start service must still stop);status's exit code means "everything enabled is running".- The
pollcommand is removed; its run loop moves topoller/daemon.py, driven bydaemon_entry poller [--once]. The issue-191 double-fork (daemonize()) goes with it — every remaining detached start is one idiom,Popen(start_new_session=True)with a logfile;startproves liveness by waiting for the daemon's pidfile lock instead of the ready-handshake. POST /api/v1/restartschedules a detached, fixed-argvpython -m the_loop restart [--with-upgrade]and answers immediately — a service cannot stop itself synchronously and still respond. It is deliberately not an MCP tool: it tears down the MCP transport mid-call, and--with-upgradereaches the installer an agent must not drive.--with-upgradereuses the issue-152 planner, CLI component only; an upgrade failure is reported but never leaves the system down (the start half still runs).- Fail closed on disablement:
service.enabled: falsealso refuses implicit auto-start (client.ensure_service). - Amended on owner review (PR #229):
— "Why is there a need for this? It should all fold intogh-webhookandservicecommands are keptthe-loop start". Both are removed withpoll; the receiver's run loop relocates towebhook/daemon.py(daemon_entry gh-webhookis its foreground form) and the lifecycle surface is the only operator surface. The same review ordered the dashboard restart wired now, not as follow-up: the Settings tab gained a Service card callingPOST /api/v1/restart, and a config save reportingrestartRequiredkeys offers "Restart now". - Amended on owner review round 2 (PR #229, issue-231): single-process mode is the default.
service.hostIngresses(default true) makesstartboot one process: with the service enabled, the enabled ingresses run as threads inside its lifespan instead of as spawned daemons. Each hosted ingress still acquires its own pidfile flock — under the service's pid — so the issue-159 lock discipline,status/stopand the daemons API are unchanged; "hosted" is detected, never recorded: an ingress lock whose holder equals the service's pid is hosted, anything else is standalone. Contention is a skip-with-warning (a standalone daemon is never fought over), an enabled poller with no sources refuses to host (the service keeps serving), andstopstops the one process, reporting the hosted rows only once their locks are released.falserestores one process per service (fault isolation);service.enabled: falsealways means standalone ingresses.
Consequences
- Breaking CLI change (
poll,gh-webhookandservicegone) — declared in the commits; cron/systemd users move topython -m the_loop.daemon_entry <poller|gh-webhook> [--once], and everything else isthe-loop start|stop|status|restart. - No config migration: keys are added, none removed or moved.
- The dashboard restarts the deployment (Settings → Service, and the config editor's "Restart now") — wired in this work item on the owner's instruction.
Spec: docs/specs/issue-228/