Operations — AutoScholar.ai on MacLustr
Topology (as deployed)
| Component | Where | How it runs | Health |
|---|---|---|---|
Web + API (apps/web) |
M3U96b ~/apps/autoscholar, port 8470 |
pm2 autoscholar (next start) |
GET /api/public/topics (200), GET / |
Scheduler (services/scheduler) |
M3U96b, same checkout | pm2 autoscholar-scheduler (AUTO_MIGRATE=1) |
log lines tick / scheduler started |
| PostgreSQL 17 | M3U96b (Homebrew), db autoscholar, role autoscholar |
brew service | pg_isready -h 127.0.0.1 |
| Artifact store | M3U96b ~/apps/autoscholar-data/artifacts (private runs/, public public/) |
filesystem | du -sh |
| Worker | M4M64c, user asworker, /var/autoscholar |
LaunchDaemon io.autoscholar.worker |
/var/autoscholar/logs/worker.*.log; admin → MacLustr page |
| Public ingress | BHS64 Caddy autoscholar.ai → 10.67.0.11:8470 |
MacLustr Tunnel (set by mld deploy) |
curl -I https://autoscholar.ai |
Secrets live only in M1M32:~/dispatch/apps/autoscholar.json (pm2 env, mode 600) and /etc/autoscholar/worker.env on the worker (root:asworker 0640). Never in git.
Routine commands
bash
# status
mld status --live | grep autoscholar
ssh M3U96b 'pm2 ls; pm2 logs autoscholar-scheduler --lines 50 --nostream'
ssh M4M64c 'sudo launchctl print system/io.autoscholar.worker | head -20; tail -50 /var/autoscholar/logs/worker.out.log'
# deploy a change (remote-first): edit on the node, then
ssh M3U96b 'cd ~/apps/autoscholar && pnpm install --frozen-lockfile && pnpm build && pm2 restart autoscholar autoscholar-scheduler && git add -A && git commit -m "…" && git push origin main'
# worker update
bash services/worker/install/bundle.sh && rsync -a --delete dist-bundle/worker/app/ M4M64c:/tmp/autoscholar-worker-bundle/ \
&& ssh M4M64c 'sudo rsync -a --delete /tmp/autoscholar-worker-bundle/ /var/autoscholar/bundle/ && sudo chown -R asworker:staff /var/autoscholar/bundle && sudo launchctl kickstart -k system/io.autoscholar.worker'
# worker self-test (CLAUDE.md loading, denials, sandbox, LaTeX)
ssh M4M64c 'sudo -u asworker bash -c "set -a; . /etc/autoscholar/worker.env; set +a; cd /var/autoscholar/bundle && HOME=/var/autoscholar/home node dist/main.js selftest"'Founder controls (admin → Settings)
dispatch.paused— emergency pause of all new dispatches (workers keep heartbeating; queued runs wait).publishing.halted— halts automatic publication globally;publishing.haltedModesper research mode. Runs then stop inawaiting_approval.- Worker drain (finish current run, take no new work) and quarantine (refuse the worker entirely) from the MacLustr page.
- Cancel / retry per run; override of soft gate failures with recorded rationale; manual publish; retract; withdraw artifacts; versioned metadata edits. All audited (
audit_events). - Env switches (require restart):
DISPATCH_ENABLED=0,PUBLISHING_ENABLED=0.
Backups and restore
- Nightly on M3U96b (pm2 cron process
autoscholar-backup, 03:20):pg_dump -Fc autoscholar+rsyncof the artifact store toM3U96a:~/backups/autoscholar/(7 daily copies, 4 weekly). Script:scripts/backup.sh. - Restore test (documented in docs/TEST_REPORT.md):
scripts/restore-test.sh <dump>createsautoscholar_restore, restores, counts papers/runs/artifacts, verifies 20 random artifact digests againstartifacts.sha256, drops the DB. - Search index: PostgreSQL generated
tsvectorcolumn — rebuilt automatically on restore (no separate index).
Monitoring and alerts
- Structured JSON logs (pino) with redaction; pm2 logrotate on M3U96b.
- Admin Overview: queue/running/failed counts, worker heartbeat age, cost; Security events (failed logins, lockouts).
mld heal(every 5 min on M1M32) restarts missing pm2 processes and re-points the tunnel route; worker LaunchDaemon hasKeepAlive.- Degradation modes: control plane down → public site down but no data loss (workers retry, leases expire, runs resume from checkpoint); worker down → queue waits, runs in progress resume on the next worker after lease expiry; Anthropic API failure → stage fails, attempt retried up to
maxAttempts, thenfailedwith the error visible in the run timeline.
Cost ceilings
- Per run:
maxBudgetUsd(default 25, hard max 2000) enforced client-side by the SDK (maxBudgetUsd) per stage and by the runner across stages;provider_usagerecords per stage; the run page shows spend vs cap. - Platform:
DEFAULT_MAX_BUDGET_USDanddispatch.pausedas the brake. Provider spend reported is the SDK's client-side estimate (not the invoice).
Data retention
- Runs, events, artifacts: retained indefinitely for provenance (private). Workspaces on the worker (
/var/autoscholar/runs/<id>) can be deleted afterpublished/failed(scripts/worker-gc.sh, keeps 14 days). - Published artifacts are immutable; retraction keeps the page and notice; withdrawal removes delivery (410) but keeps the private copy.
Onboarding another MacLustr worker
bash services/worker/install/bundle.sh; rsyncdist-bundle/worker/app/to<node>:/tmp/autoscholar-worker-bundle/.ssh <node> 'sudo bash /tmp/autoscholar-worker-bundle/install/install-macos.sh --control-plane http://<M3U96b LAN IP>:8470 --name <alias> --enrollment-secret <secret from the control-plane manifest> --anthropic-key-file /tmp/key --bundle /tmp/autoscholar-worker-bundle'(thenrm -P /tmp/key).- Watch it appear in admin → MacLustr; run the self-test. Rotate the enrollment secret in the control-plane manifest afterwards if desired.