EverLink logoEverLink

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.

Open source · MITSelf-hosted · not a SaaSAWS Strands SDK + Bedrock
Illustration: a robotic arm re-welding a broken chain link between web pages

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.

A · Generic read-only crawl — the default, no source DB
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.
B · First-party DB snapshot — optional deep path
<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
  1. 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

  2. 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

  3. 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

  4. 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 →
  5. 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

  6. 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 ago

Nobody 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.

Steer it on /settings — rotation cycle, run hour, kill-switch, run-now, heartbeat liveness and the cron ledger →

python scripts/nightly.py

The 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 none

Safe 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.

Your site's databaseoptional
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.
EverLink's own databaserequired
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.
A sitemap or page URLrequired
scan --site <url>
  • · The zero-config default (path A): patrol any public site.
An LLMoptional
AWS Bedrock credentials
  • · The Judge's fix proposals. Scan + L1/L2 detection need none.
A schedulerrequired
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.

This deployment is an example. The AethelGem / HotDeals / FlashDeals sites in this board are the maintainer's own — the first user dogfooding EverLink. Their internal site keys (e.g. 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.