`homeboy daemon`
Run and inspect the local-only Homeboy HTTP API daemon.
Synopsis
homeboy daemon <COMMAND>Subcommands
start— start the local daemon in the backgroundserve— run the daemon in the foregroundstop— gracefully stop the background daemon recorded in the state filestatus— show bounded daemon health, active jobs, blockers, and recovery actions; use--fullfor complete evidencerecover— resolve and run the right recovery from the current status reportbroker-config— render a deployable reverse-runner broker service recipe
Daemon lifetime
Automatically started daemons (ensure-running, including managed WorkJob
launches) stop after 300 seconds with no active durable jobs. Their
supervisors use the existing lease-scoped stop gate: pending admissions and
active work prevent shutdown, and every signal is tied to the exact lease and
startup token. A subsequent command starts a new daemon using the retained
durable store. Disposable clients do not need to leave a resident process behind.
homeboy daemon start and foreground serve are explicitly resident launches,
appropriate for continuous schedules and services. Set
HOMEBOY_DAEMON_IDLE_TIMEOUT_SECS to a positive number to select another idle
window for a supervised launch, or 0 for a resident automatic launch. Foreground
serve lifetime is owned by its calling service manager. Superseded resident generations
also become bounded once their own durable work drains.
Only the admission-owning generation runs global scheduling, notification, and orchestration passes. Older generations maintain their own jobs while draining. One blocked generation retirement does not prevent independent idle generations from being retired; failed entries retain their evidence and are retried.
Controller completion custody
Controller drivers execute once; result persistence is a separate operation. A terminal winner is immutable: a delayed success, failure, or cancellation writer observes that winner without rewriting the queue or appending events.
Before publishing a terminal queue snapshot, the store durably records the public completion and exact execution claim in its existing SQLite index. If the snapshot write fails, this completion ledger owns the result and the finished worker exits. Startup and periodic maintenance retry bounded batches of terminal projections rather than resuming completed driver side effects. Blocked entries rotate behind other completions. The retained-owner resource report includes the ledger’s pending count and payload bytes.
Cancellation recorded before completion retains priority. Once completion custody is durable, a new cancellation request reports that terminal persistence is pending instead of reopening finished execution.
If storage cannot record either the terminal projection or completion custody, the one existing worker remains fail-closed with backoff capped at 30 seconds. Permanent transition/ownership rejections exit immediately. This bounds retry rate without pretending that unavailable storage durably recorded an outcome.
Recovery dispatch
homeboy daemon status is a bounded operator projection by default. It reports
daemon liveness, reachability, build freshness, active-job summaries,
replacement blockers, and exact authorized recovery commands. It summarizes
unrelated process candidates by count and includes only ownership-relevant
coordinates. Use homeboy daemon status --full to inspect the complete state,
candidate command lines, runtime-path, termination, and job-recovery evidence.
homeboy daemon status already computes the repair its own evidence
authorizes and reports it as freshness.repair_plan. homeboy daemon recover
is the dispatcher for that plan: the status resolves the selected generation,
store, lease, and protected jobs. Before mutation, recovery re-reads that same
authority identity and refuses with stale_daemon_recovery_plan and a dry-run
handoff if the authority or protected job set changed. Restartable stale
generations use daemon ensure-running so replacement is rotated through the
admission registry; recovery does not stop a registered-generation store and
then start in the caller’s legacy state directory. After --yes applies every
step, a final authoritative status read must prove the selected daemon fresh
before the command succeeds. Nothing is transcribed by hand between commands.
homeboy daemon recover # resolve and print the plan (default)
homeboy daemon recover --yes # resolve and run itDry run is the default because recovery mutates the daemon that owns the
caller’s durable jobs. The output carries the resolved plan (each step with
its code, its rendered command, and its argv), the stale_reason_code it
matched, the store_path and lease_id that own that plan, and next_command.
A preview, blocked recovery, stale plan, stale postcondition, or unavailable
postcondition read returns a nonzero structured outcome while preserving the
plan and any applied-step evidence.
A report the evidence authorizes nothing for does not produce an empty plan. It
produces the read-only daemon_diagnose step and blocked_on, stating in the
evidence’s own words why no mutation was resolved.
The explicit subcommands below remain available as escape hatches for cases the
dispatcher cannot resolve — notably recover-missing-lease-state, whose
required values (a recorded lease ID, PID, and endpoint) are precisely the ones
that no longer exist in any report by the time they are needed.
--confirm-workload-processes-absent is never supplied automatically. When the
resolved plan is a dead-lease reconciliation, recover reports it under
blocked_on and refuses to execute until the operator passes it. See the
reasoning in the dead-lease recovery section below: the attestation is
unverifiable in process by construction, so a dispatcher supplying it would be
fabricating evidence.
Local HTTP API
The daemon binds to loopback only. homeboy daemon start writes the selected
address and PID to the daemon state file so headless clients can discover it via
homeboy daemon status.
Always treat the API as a local UI contract. It is not a hosted or remote multi-user service.
Dead-Lease Recovery
When a recorded daemon is stale or unreachable but its PID is still live, an
operator can use a lease-bound local force stop. It never uses the daemon HTTP
endpoint, revalidates the exact persisted lease and zero-job state before every
signal, and refuses while durable jobs are active. It uses Linux /proc token
evidence on Linux and the explicit startup-token command argument on other Unix
controllers, with bounded SIGTERM-to-SIGKILL escalation for the exact supervised
daemon pair:
homeboy daemon stop --force --lease-id <exact-live-lease>When status reports a dead lease, active_job_recovery_evidence lists each
active job’s exact ID, lease, timestamps, terminal evidence, child identity,
and linked_durable_run_id plus linked_durable_run_state (terminal,
active, or unresolved). Active and unresolved linked runs are reported as
blocking evidence. Status is read-only: it never reconciles or changes durable
jobs.
For a proven unexpected daemon exit where exact active jobs have no persisted child identity, use the explicit all-active-job-set recovery command. It requires the dead lease, every active job ID, and an operator attestation that workload processes were inspected and absent. It refuses a live or reused daemon PID, missing or mismatched unexpected-exit evidence, a held daemon owner lock, conflicting daemon-process evidence, child identities, or an omitted/extra/ non-active job ID. Each named job receives durable typed daemon-loss failure evidence before the replacement daemon starts:
homeboy daemon reconcile-dead-lease-orphans
--lease-id <exact-dead-lease>
--job-id <active-job-id>
--confirm-workload-processes-absent--job-id is not a transcription of status output. The store recomputes the
active durable-job set and refuses any mismatch, so the repeated flag is a
compare-and-swap over the exact destructive scope — omit a job, name an extra
one, or race a change and the command aborts instead of terminalizing work you
never saw. The named set is persisted with the reconciliation as
exact_active_job_set.
--confirm-workload-processes-absent stays required. This command exists
precisely because the daemon died before persisting any child identity, so the
store holds no PID for the named jobs and homeboy cannot observe whether their
workloads are still running. The check that refuses jobs carrying recorded child
evidence proves only that no such record exists — which is what makes the
operator’s inspection the sole source of truth. The attestation is written into
every affected job’s durable event data as
operator_confirmed_workload_processes_absent.
VPS Reverse Runner Broker
homeboy daemon broker-config renders the code-backed deployment shape for a
VPS-hosted reverse runner broker. The safe default is a durable systemd
service that keeps the daemon on a stable loopback port:
homeboy daemon broker-config --listen-addr 127.0.0.1:7421The JSON output includes:
systemd_unitfor ahomeboy-brokerservice runninghomeboy daemon serveprivate_tunnel_examplesfor SSH, Cloudflare, or tailnet-only access- optional
nginx_siteandcaddy_sitesnippets when--domainis supplied daemon_state_pathanddaemon_jobs_pathservice-owned operational state locations- status and log commands for day-two operations
- restart, retention, and claim caveats
The service config intentionally requires a stable loopback address. Broker routes are currently suitable for private loopback or private tunnel access only. Public Internet exposure through Nginx or Caddy is blocked until broker auth/pairing from #2990 lands. The rendered proxy snippets include that warning and should stay disabled or protected by private network controls until the auth model is available.
Extra Chill-compatible private setup:
- Install Homeboy on the VPS at the binary path used in
broker-config. - Create the service user/group named in the generated output.
- Install the rendered
systemd_unitas/etc/systemd/system/homeboy-broker.service. - Run
systemctl daemon-reload && systemctl enable --now homeboy-broker. - Verify with
systemctl status homeboy-broker,homeboy daemon status, andcurl -fsS http://127.0.0.1:7421/healthon the VPS. - Reach the broker from the runner machine through a private SSH tunnel or private network URL, then use reverse runner connection commands against that private broker URL.
Operational caveats:
- The systemd service sets
HOME=/var/lib/homeboy, so daemon state lives under/var/lib/homeboy/.config/homeboy/daemon/instead of the service user’s login home. - Queued reverse-runner jobs survive daemon restart.
- Broker-owned running jobs are marked failed as stale when the durable store is reopened after restart.
- Active reverse-runner claims are lease-scoped; runners should retry claim after the lease expires.
- The job store has bounded per-job event retention and is not a long-term audit archive. Persist important evidence through Homeboy observations/artifacts.
Built-in Endpoints
GET /health— daemon health and Homeboy versionGET /version— Homeboy versionGET /config/paths— local Homeboy config paths
Completed Read-Only Contract Endpoints
These endpoints dispatch through Homeboy’s transport-free read-only HTTP API contract and return the same JSON envelope shape as other daemon responses.
GET /componentsGET /components/:idGET /components/:id/statusGET /components/:id/changesGET /rigsGET /rigs/:idPOST /rigs/:id/checkGET /stacksGET /stacks/:idPOST /stacks/:id/statusGET /runs?kind=bench|audit&component=<id>&rig=<id>&status=<status>&limit=<n>GET /runs/:idGET /runs/:id/artifactsGET /runs/:id/artifacts/syncGET /runs/:id/artifacts/:artifact_idGET /runs/:id/artifacts/:artifact_id/contentGET /runs/:id/findings?tool=<tool>&file=<path>&fingerprint=<id>&limit=<n>GET /audit/runs?component=<id>&rig=<id>&status=<status>&limit=<n>GET /bench/runs?component=<id>&rig=<id>&status=<status>&limit=<n>GET /jobsGET /jobs/:idGET /jobs/:id/eventsPOST /jobs/:id/cancelGET /toolsGET /tools/:idPOST /tools/:id/runPOST /runner/sessionsPOST /runner/jobsPOST /runner/jobs/claimPOST /runner/jobs/:id/eventsPOST /runner/jobs/:id/finish
The run readers expose persisted observation-store evidence from previous
analysis runs. They do not start audit, lint, test, bench, rig, or stack work.
Run summaries include status_note when a running record appears stale or
cannot be verified with owner metadata, matching the CLI run-history output.
Artifact list/sync responses include a byte-retrieval contract for each record:
content_available, content_url, fetch_command, and retrieval.mode.
retrieval.mode: direct_download means the daemon route can serve bytes and the
CLI command can fetch them. retrieval.mode: metadata_only means orchestrators
must treat the record as evidence metadata only; no byte endpoint is expected to
work for that artifact. Daemon artifact byte routes stream the file response;
the transport-free API handler reports inline byte payloads as
retrieval.mode: inline_base64 with content_field: content_base64.
homeboy runs compare --format=json remains CLI-only for now. A daemon compare
endpoint should reuse that implementation rather than duplicating comparison
logic in the HTTP API contract.
The analysis entry points POST /audit, POST /lint, POST /test, and
POST /bench enqueue daemon jobs. Clients inspect those jobs through
GET /jobs/:id and GET /jobs/:id/events instead of parsing terminal output.
Sandbox agents should prefer the typed tool surface over command-shaped routes:
GET /toolsreturns the bounded Homeboy tool allowlist.- Each tool declares its required capability, risk category, job behavior, and accepted JSON request fields.
POST /tools/homeboy.audit/run,POST /tools/homeboy.lint/run,POST /tools/homeboy.test/run,POST /tools/homeboy.bench/run,POST /tools/homeboy.build/run, andPOST /tools/homeboy.review/runenqueue jobs through the same job/event/result contract.- Tool IDs that are not in the allowlist, including deploy, release, SSH, auth, keychain, and DB operations, are rejected before execution.
Mutating operations such as deploy, release, rig up/down, stack apply, git writes, and SSH execution are not exposed by this daemon slice.
See Headless Daemon API Contract for the headless client contract, job/event shape, mutating capability model, and preview/apply rules for future write endpoints.
Related
Scheduled runs
A running daemon fires due schedules without an external timer. It polls every 30 seconds by default; override with HOMEBOY_DAEMON_SCHEDULE_TICK_SECS, or set it to 0 to disable daemon-driven scheduling and drive homeboy schedule tick yourself.
Each due schedule runs on its own thread, so a slow scheduled command delays neither the poll loop nor daemon shutdown. Markers left by a run the daemon did not finish are reclaimed at start.