EverLink
The autonomous link-rot steward
EverLink patrols your sites’ outbound links on a schedule, detects rot — dead pages, changed offers, ended affiliate programs — and repairs what it safely can. It stops at the decision inbox only when a fix is risky enough to need a human judgment; everything else heals on its own and leaves an audit trail. It is open-source and self-hosted: you clone the code, run it on your own infrastructure, and fill in your own config — no signup, no billing, no multi-tenancy.

Two ways in: crawl any public sitemap, or snapshot your own DB.
Both strictly read-only · a human approves every write
Two ways to feed it · no database required to start
EverLink does not need access to your database to start. Point it at any public sitemap or page URL and it crawls read-only over HTTP, extracting outbound links on the fly. Connecting your own DB is an optional deep path for block-level slots.
scan --site https://your-site.com/sitemap.xml- · Works with ANY website — no source database, no code change.
- · Read-only HTTP: crawls public pages and extracts outbound links live (defaults 25 pages / 500 slots, --max-pages).
- · Politeness: descriptive UA, 1 req/s rate limit, robots.txt honoured (fail-open).
- · L1/L2 detection needs zero AWS credentials; only the Judge's fix proposals need an LLM.
<SITE>_DATABASE_URL + scripts/export_slots.py- · For your OWN site, when you want block-level slots (article / block / role) instead of a crawl.
- · export_slots.py connects STRICTLY READ-ONLY and writes data/slots_<site>.csv; the scan reads that snapshot.
- · <SITE>_PUBLIC_ORIGIN turns a relative /products/x into an absolute URL to probe — no domain is hardcoded.
- · Write-back is gated twice (approved cards only + a per-site whitelist) and lands in EverLink's own mirror; a production CMS adapter is out of scope.
How it works · one autonomous loop
Audit trail- Scan
Inventories outbound link slots from a first-party CSV snapshot or a read-only crawl of any sitemap/page (capped at 25 pages / 500 slots). Nothing is written back to the scanned site; findings mirror into EverLink's own store.
needs: a URL · no creds · the mirror + rotation need the DB
- Detect
L1 probes HTTP status + redirect chain. L2 parses the page when L1 says healthy (soft-404 check) or unsure (blocked/timeout); it is skipped when L1 is already conclusive.
needs: no LLM · 1–2 requests per link
- Judge
A Strands agent on AWS Bedrock proposes the safest fix and scores its risk. Three steering hooks ride along (scope / editorial / recheck) and cancel or re-steer an unsafe proposal before it can become a card.
needs: an LLM (optional) · hooks on by default
- You decide
Each distinct dead link becomes ONE card (duplicates merged). Medium- and high-risk cards are pushed immediately, one message each; low-risk rolls into a single weekly batch list. Reject with a reason and it stays on the card.
needs: a human · the only mandatory human step
Open Inbox → - Apply
The worker snapshots the slot, applies the approved fix, re-probes to verify it took. On failure it rolls back, dead-letters the card, and files a rollback card for the human.
gated: an approved decision_id, or the write is refused · writes only EverLink's own store
- Report
Every step lands in the audit trail; the weekly digest reports the same numbers /report shows, over Resend email or Telegram (skipped honestly when keys are absent).
needs: nothing extra
Open Report →
Automation · the nightly loop
chain runs 03:00 UTC · full pass every 30dlast run 22h ago · degraded · scheduled hour (3:00 UTC) · railwaylast activity 22h agoNobody opens a terminal in production. The cron fires as a heartbeat and the gate decides: only the fire matching the run hour runs the whole chain, and every other fire logs an honest heartbeat skip. (An hourly cron gives run-now a within-the-hour pickup; a daily cron set to the run hour works too.) Each run rotates: it takes the least-recently-checked slots, budget = ceil(active ÷ cycle) per site (floor 25), so one cycle covers the WHOLE mirror instead of re-probing the same head forever.
python scripts/nightly.pyThe production entrypoint: schedule gate → rotation budget → scan every site → notify → drain approved fixes. On a cron fire it decides for itself whether to run or log a heartbeat.
python scripts/nightly.py --dry-run --judge noneSafe offline smoke — proves the chain is wired; writes nothing, sends nothing, skips the worker.
Useful flags
- --force
- Bypass the schedule gate and run the chain now (manual triggers).
- --select due|head
- due = the least-recently-checked mirror slots (rotation; the default when a DB is configured). head = the snapshot's first N, the legacy behaviour.
- --judge none|stub|bedrock|mantle
- Judge backend. mantle = the real LLM via Bedrock (production); none = detection only, no creds.
- --dry-run
- Scan writes nothing and notify sends nothing; the worker step is skipped (it writes).
- --sites a,b,c
- Restrict the scan targets (default: the three first-party sites).
- --weekly
- Force the weekly digest step now (otherwise it folds in automatically on Monday).
Self-host it · what you provide, where it writes
EverLink hardcodes no domain, no database address, no secret — you supply all of it. The minimal deploy is a sitemap URL + EverLink's own database; the LLM and your site's database are optional. Full runbook in DEPLOYMENT.md.
env <SITE>_DATABASE_URL- · Only for deep path B, when you want block-level slots instead of a crawl.
- · Opened STRICTLY READ-ONLY: SET default_transaction_read_only = on.
- · db._assert_writable refuses any write aimed at a source host — this is the boundary that makes the whole product safe to point at production.
env EVERLINK_DATABASE_URL- · The ONLY place EverLink writes: link_slots (its mirror), slot_checks, decisions, audit_log, settings, nightly_runs.
- · Also what powers this board — without it the pages render, but empty.
scan --site <url>- · The zero-config default (path A): patrol any public site.
AWS Bedrock credentials- · The Judge's fix proposals. Scan + L1/L2 detection need none.
Railway cron 0 * * * *- · Ticks hourly as a heartbeat; the chain itself runs once a day at the hour you set.
Run hour, rotation cycle, kill-switch and a run-now request live on /settings — no redeploy needed to change the schedule.
sandcart = FlashDeals) stand in for your site keys. When you deploy EverLink you point it at your own databases and origins; nothing about these example sites is baked into the code.