Son of Anton Docs
Self-host

Run on a Node Box

Supervise the Node webhook server and one-shot worker with durable shared state and explicit access boundaries.

This is the production layout for son-of-anton-review on feat/cloudflare-native. Start with Requirements and the Node quickstart. They cover private source access, missing license terms, Node 25, App configuration, separate Git clone authentication and an approved sandbox.

Do not substitute son-of-anton-operator-parity for this checkout: it is a separate Git worktree on feat/greptile-operator-parity, with a different Cloudflare queue/operator API. The UI trees are separate again.

Process model

EntrypointLifecycleResponsibility
server.mjsLong-runningGitHub webhook ingress, health and optional read-only operator routes
worker.mjsOne-shotReconcile stored work, drain a bounded batch, then exit

Run both as the same dedicated service user, with matching absolute state/queue paths and deliberate environment loading. Neither file automatically reads .env. With App credentials present they use a real GitHub publisher; GILF_PUBLISH_MODE is not a Node publication gate.

Worker variableRuntime default
GILF_WORKER_MAX_JOBS1
GILF_WORKER_CONCURRENCY1, effective concurrency capped by the job limit
GILF_WORKER_IDHostname, with :<pid> appended to the lease owner
GILF_REVIEWER_MODEcodex, the real runner path, even when its selected provider is OpenRouter

Do not change GILF_REVIEWER_MODE to the provider name: a value other than codex selects the entrypoint's empty-result fallback rather than the real reviewer. For this Node entrypoint use GILF_CODEX_PROVIDER and GILF_CODEX_MODEL. worker.mjs does not wire resolveModelConfig(), so the modern GILF_MODEL_PROVIDER / GILF_MODEL pair and role overrides do not select its runner. Native deployment explicitly wires the modern pair.

A worker invocation can exit 0 after individual jobs fail because the drainer collects failures. Monitor stored run state and receipts, not just the exit code or drained=N. Lease renewal and publication bookkeeping reduce races; they are not a blanket exactly-once guarantee across independent deployments.

Queue and state

Queue selection is:

  1. Both GILF_QUEUE_URL and GILF_QUEUE_TOKEN: HTTP Durable Object queue.
  2. Otherwise GILF_QUEUE_DB_PATH: SQLite queue at that path.
  3. Otherwise GILF_DB_PATH: queue in the state database.
  4. Otherwise a process-local in-memory queue.

The last choice cannot join separate server/worker processes. Setting only one remote-queue variable falls through, rather than reporting a complete remote configuration.

For a single box, use one absolute GILF_DB_PATH. SQLiteStore enables WAL and a 5000 ms busy timeout. Back up state and queue consistently using SQLite-aware backup tooling or a fully stopped deployment. Preserve policies, memory, publication receipts and pending work, not just review text. Copying only the live main DB file can miss WAL data.

A remote queue does not share the run store. Pointing two independent Node hosts at the same queue while each has its own SQLite state is not a complete multi-host deployment. Do not assume the parity API adds replication to these entrypoints.

Optional remote queue from the engine tree

son-of-anton-review/cloudflare/ has a queue/context-graph Worker. Its wrangler.toml names GILF_QUEUE, a context-graph D1 database and an R2 bucket. Replace account/resource IDs and provision those bindings in your account before deploying.

Unlike the parity Worker, this engine-tree Worker uses one GILF_QUEUE_TOKEN for its queue and context-graph routes. The existing cloudflare/deploy.sh expects that token in the environment; it does not accept the parity split-token setup. A human may instead configure it explicitly from the engine root:

wrangler secret put GILF_QUEUE_TOKEN --config cloudflare/wrangler.toml
wrangler deploy --config cloudflare/wrangler.toml

Give both Node processes that URL/token through approved secret tooling. This deploys a queue Worker, not the native review runtime or expanded operator API. Do not overwrite an existing queue deployment from a different branch.

The Node context-graph client uses GILF_CONTEXT_GRAPH_URL / GILF_CONTEXT_GRAPH_TOKEN, falling back to the queue URL/token. If neither pair is usable, no remote graph client is constructed. Local graph analysis is separate from remote graph persistence.

GILF_QUEUE_LEASE_SECONDS overrides lease duration. Otherwise the worker calculates model timeout plus the longest configured validation timeout plus five minutes; its E2B and Crabbox lease-calculation fallbacks are 45 minutes each. The server's explicit queue lease default is 300 seconds. Set timeout/lease policy deliberately, especially if you copied the sample env's fixed 300-second lease.

Supervision example: Linux systemd

The following is an example, not a claim about installed units or a measured production timer interval. Adapt paths and the absolute Node binary. It assumes the service user's checkout is %h/son-of-anton-review and the private environment file is /etc/son-of-anton/engine.env.

Create ~/.config/systemd/user/son-of-anton-pr-review-webhook.service:

[Unit]
Description=Son of Anton webhook server

[Service]
Type=simple
WorkingDirectory=%h/son-of-anton-review
EnvironmentFile=/etc/son-of-anton/engine.env
ExecStart=/usr/local/bin/node server.mjs
Restart=on-failure

[Install]
WantedBy=default.target

Create ~/.config/systemd/user/son-of-anton-pr-review-worker.service:

[Unit]
Description=Son of Anton review batch

[Service]
Type=oneshot
WorkingDirectory=%h/son-of-anton-review
EnvironmentFile=/etc/son-of-anton/engine.env
ExecStart=/usr/local/bin/node worker.mjs

Create ~/.config/systemd/user/son-of-anton-pr-review-worker.timer:

[Unit]
Description=Schedule Son of Anton review batches

[Timer]
OnBootSec=1min
OnUnitInactiveSec=1min
Unit=son-of-anton-pr-review-worker.service

[Install]
WantedBy=timers.target

After the service user has the required filesystem and GitHub access:

systemctl --user daemon-reload
systemctl --user enable --now son-of-anton-pr-review-webhook.service son-of-anton-pr-review-worker.timer
systemctl --user list-timers 'son-of-anton-*'

A user manager's boot/logout behavior is host policy; arrange persistence with the administrator. Ensure the supervised environment's PATH includes Git, gh and the selected executor. On non-systemd hosts use an equivalent long-running server supervisor and scheduled worker invocation. Never infer that a timer succeeded merely because it is enabled.

Webhook and operator access

Expose POST /github/webhooks through an approved HTTPS proxy/tunnel and set the same secret in GitHub and the server environment. GET /health verifies the listener, not end-to-end readiness. Default port is 8787.

The optional Node dashboard is off by default. Enable it with GILF_OPERATOR_UI_ENABLED=1 and a separately provisioned GILF_OPERATOR_UI_TOKEN. Requests need the exact Authorization: Bearer token; missing token configuration fails closed. Its views are /operator/pull-requests, /operator/analytics, /operator/memory and /operator/settings, with a smaller read-only snapshot/trace API. Keep the route private even when token-protected.

This is not the WorkOS session UI. anton-ui has a bearer gate defaulting off; anton-ui-trace-parity implements the authenticated session BFF. Neither is installed by starting server.mjs.

Operator CLI

From the engine root, pass the actual database path:

node bin/gilf-review.mjs runs --db /var/lib/son-of-anton/anton.db --limit 20
node bin/gilf-review.mjs usage --db /var/lib/son-of-anton/anton.db --limit 100
node bin/gilf-review.mjs show 'OWNER/REPO#12@HEAD_SHA' --db /var/lib/son-of-anton/anton.db

Replace the quoted key with a real stored key. show includes the run payload; keep sensitive repository content out of public logs. harness is not purely read-only: it saves a deployed harness record. For strict read-only inspection use the SQLite CLI with -readonly; constructing the application store can initialize missing schema.

After proving a prior attempt is safe to retry, an operator may run:

node bin/gilf-review.mjs requeue 'OWNER/REPO#12@HEAD_SHA' --db /var/lib/son-of-anton/anton.db --variant retry-1

If queue and state are split, pass --queue-db for the real queue file, or load the configured remote queue environment. --json selects JSON output. Do not requeue to work around an unresolved publication receipt.

Cold fallback and upgrades

Retain a fallback's code, state, environment and service definitions, but keep its mutation timers stopped while Cloudflare owns publication. Rollback requires draining the old authority, reconciling accepted/uncertain work, switching the webhook and only then resuming the replacement worker. Independent SQLite/D1 databases do not deduplicate each other automatically.

Follow Upgrading and the native rollback procedure. Verify one actual review/check receipt from the restored lane before calling rollback complete.

On this page