Skip to main content
Back to projects

Spinneret

Self-healing Lead Intelligence

Scrapers that diagnose and repair themselves. Spinneret is a buying-signal radar that measures every scrape against a contract, writes its own repair prompt from the telemetry when data drifts, and proves the fix with a before and after score.

Spinneret homepage
20 → 97
Health score, healed
57
Unit tests
3
Live collectors
7
Loop stages

Demo video

Demo video coming soon

A full walkthrough of Spinneret will be added here.

A collector breaking, being diagnosed and healing itself.

Overview

Spinneret is two systems that need each other.

The first watches niche public sources for the moment a company commits money to something specific, then ranks those companies by how likely they are to buy right now, with the reasoning attached to every score.

The second keeps that feed honest. Scrapers break quietly, so after every run Spinneret measures what came back, compares it against a contract, and when the data stops matching it writes its own repair prompt from those measurements, sends it through Bright Data Scraper Studio, approves the patch and re-runs to prove the fix worked.

Built for Into the Scrape-Verse, the Bright Data and WeMakeDevs hackathon, in August 2026.

The Problem

Scrapers rarely fail loudly.

A site ships a redesign, a CSS selector stops matching, and the extraction code keeps running perfectly. Same row count, same schedule, same green job. The rows are simply empty.

Nothing in a normal pipeline notices. HTTP returned 200 and no exception was thrown. Three weeks later a sales team reports that leads dried up, and someone starts bisecting a month of data to find when the values turned into nulls.

A lead-signal product that silently goes stale is worse than no product at all, because it converts “I do not know” into “I am confidently wrong”. Freshness is the entire value proposition, so self-healing is not a bonus feature here. It is what makes the core promise true.

Goals of the Project

GoalHow it is met
Score change, not existenceSignals come from what shifted this week, never from a static directory dump.
Make every score arguableEach point traces to a stated rule, shown in the interface.
Detect silent decayEvery run is measured against a contract written independently of the scraper.
Repair without a humanThe prompt sent to Bright Data is generated from telemetry.
Prove the repair workedBefore and after scores are recorded side by side in a durable ledger.
Know when not to actVague evidence and transport failures are explicitly excluded from healing.

System Architecture

Four layers, with dependencies pointing in one direction only. Nothing in the domain core imports a database, a network client or a React component, which is what lets the self-healing decision logic be unit-tested with no I/O at all.

src/
├─ app/, components/     interface: dashboard, docs, HTTP routes, SSE feed
├─ services/             sentinel loop, scheduler, background jobs, read model
├─ adapters/, db/        the only modules that spawn the CLI or write SQL
└─ core/                 health, drift, prompts, signals, cooldown  (imports nothing)
Swapping SQLite for Postgres means rewriting db/repositories.ts and nothing else.
System architecture

The Self-Healing Loop

Seven stages. The first three run on every observation. The last four only run when the diagnosis found evidence worth acting on.

The seven-stage loop
The interactive healing loop diagram from the documentation, showing the seven stages arranged on a ring
The interactive version of the loop in the product docs.

Key Features

Machine-authored repair prompts

This is what separates Spinneret from a monitoring dashboard. The instruction sent to Bright Data is assembled from measurements, not typed by a person.

The scraper for Remote GTM job board (https://weworkremotely.com/categories/
remote-sales-and-marketing-jobs) is returning incomplete data. Fields never
returned as keys in the output: company_name, job_title, location, job_url.
Re-derive selectors for company_name, job_title, location, job_url from the
current live DOM and return the full contracted schema: company_name,
job_title, location, job_url.
Verbatim: 403 characters, generated and dispatched by the sentinel.

Four clauses, each doing a job: name the target, state the evidence with specific fields, say what to do and where to look, and restate the required schema so a partial repair is not mistaken for success. When a collector is only partially broken, a fifth clause appears automatically, fencing off the fields that still work so a rewrite cannot regress them.

The Contract Lab

Everything else on the dashboard is retrospective. It reports that a collector went from 20 to 97 and asks to be believed. The Contract Lab lets you test it. Change what a collector is required to return and the whole diagnosis re-runs in front of you: coverage moves, evidence assembles and the repair prompt types itself out.

Adding a required field to a collector contract, and watching the health score fall, evidence appear, and the repair prompt write itself
Adding a required field: the score falls, evidence appears and the prompt writes itself.

None of it is simulated. The rows are ones those collectors genuinely returned, and the scoring, drift classification and prompt composition are the same functions the sentinel runs in production. It runs entirely in the browser, which is a direct payoff from the architecture: the scoring layer performs no I/O, so it ships to the client and works on the hosted snapshot with zero latency.

Health scoring

Every run produces one number between 0 and 100 from three weighted terms.

ComponentWeightMeaning
Coverage0.50Mean fill rate across contracted fields
Schema conformance0.30Fraction of contracted fields present as keys
Yield0.20Row count against the rolling healthy baseline

A value only counts if it carries information. null, empty strings, whitespace and the literal strings "null", "N/A" and "undefined" all count as missing, as do empty arrays and objects. Broken scrapers emit these constantly, and treating them as real data is exactly how a hollow pipeline passes as healthy.

Drift detection

Each snapshot is classified into a severity that decides whether a heal is allowed.

SeverityRuleHeals?
schema_gapA contracted field is absent as a keyAlways
criticalComposite score below 55Always
degradedScore below 80, or a field regressed against baselineOnly if a specific field was identified
unreachableThe run never completedNever
healthyEverything within toleranceNo action

The unreachable case matters more than it looks. A timeout returns nothing, so every field appears absent, which is indistinguishable from a total site rewrite. Healing in that state spends a cycle and hands the healing AI no live DOM to work from.

Buying signal engine

Each source type gets its own rule table, because a job title, a changelog entry and a directory listing say different things about intent.

The ranked buying-signals board with intent scores, companies, signal descriptions and source tags
The ranked buying-signals board.
SourceRulePointsReasoning
hiringGTM leadership hire34New leaders re-tool their stack within a quarter
hiringRevOps hire32An explicit mandate to buy and integrate tooling
hiringRevenue-carrying role30Quota capacity is being added
changelogEnterprise readiness34SSO, SCIM and audit logs mean larger deals ahead
changelogIntegration surface26Points to an ecosystem play
directoryB2B software24Buys tooling as a matter of course
anyNewly appeared22The change is fresh, not historical
anyConcurrent activityup to 27Coordinated build-out rather than backfill

When no rule fires, no signal is emitted. Spinneret never invents intent it cannot justify, and there is a unit test asserting exactly that.

Unattended supervision

A cron-driven watcher sweeps the fleet every 30 minutes by default. Two safeguards matter for a loop that spends money without a person watching.

  • A sweep can outlast its own interval during a slow heal, so a late tick is skipped rather than queued. Overlapping runs would corrupt the shared baseline.
  • A heal that ran and did not raise the score is never retried on a timer. Re-sending a prompt already shown not to work burns credits and can degrade the scraper further.

Live Collectors

Three collectors, deliberately different in shape so they exercise different parts of the health model: a paginated list, a JavaScript-rendered directory with an array field, and a single-subject changelog with no company column at all.

CollectorTargetSignal
hiring-signalsWeWorkRemotely GTM job boardHiring intent, revenue roles opening
funded-startupsYC directory, Summer 2025New entrants, scored on sector fit
product-velocityLinear changelogProduct velocity, enterprise-readiness work

Every target is public, carries no personal data, sits behind no login or paywall and has no pre-built Bright Data scraper. LinkedIn was deliberately avoided precisely because it already has one, and using it would have meant not really using Scraper Studio.

The self-healing demonstration was not staged

The hiring-signals collector was created from the description “extract every job listing: job title and company name” and came back returning 66 rows containing only product_page_url. Neither contracted field was present. Spinneret found that on its first observation, scored it 20/100, classified it schema_gap and dispatched a heal on its own evidence. The verification run scored 97/100, and the flight recorder holds the full trace.

The heal ledger showing a verified repair, with the machine-authored prompt and a score change from 20 to 97
The heal ledger: the machine-authored prompt and the score moving from 20 to 97.

Tech Stack

LayerChoiceWhy
ScrapingBright Data Scraper StudioAI-built collectors with a heal API and a stable collector ID
FrameworkNext.js 16, App RouterOne repo for dashboard, docs and API, with SSE support
LanguageTypeScript, strictThe domain core is the part that must be provable
StorageSQLite via better-sqlite3Zero-config durable flight recorder, lazily required
UIshadcn/ui on Base UI, Tailwind v4Accessible primitives, tokens remapped onto the palette
MotionFramer Motion + GSAPStaggered reveals, and the web scaffold drawn on load
TypeInstrument Serif, Archivo, JetBrains MonoA deliberate pairing, not a default stack
TestingVitest57 unit tests over pure functions, no mocks needed
Schedulingnode-cronUnattended fleet supervision

Database Design

Entity relationship diagram

Two details are worth noting. expected_fields is a contract written independently of the scraper, which is what lets the sentinel notice a gap nobody told it about. And signals.fingerprint is derived from what was observed rather than how it was worded, so re-observing the same job openings is silent while a genuinely new posting still creates a signal.

Application Flow

One sweep of the fleet

Challenges and Solutions

ChallengeSolution
A broken scraper would define its own idea of “correct”The contract is written by hand, independently of what the scraper currently returns.
One catastrophic run poisoning the baselineBaselines use the median, and are built only from runs that already scored well.
A timeout looking identical to a total site rewriteFailed runs are classified unreachable and excluded from healing.
Vague prompts making a working scraper worseHealing is withheld unless a specific field is identified.
A failed heal being retried forever on a timerA pure cooldown policy blocks retries, and is exhaustively unit-tested.
Re-observing creating duplicate signalsIdentity is a fingerprint of what was observed, never the formatted headline.
Serverless cannot write SQLite or run a 25-minute healThe hosted build serves a committed JSON snapshot and says so in the header.
A failed native build taking down the whole sitebetter-sqlite3 is required lazily, so the snapshot path never loads it.

Best Practices

Architecture and testing

The domain core imports nothing: no database, no network, no filesystem, no React. That constraint is load-bearing. The self-healing decision logic runs in a unit test without a Bright Data account, and the same purity is why the Contract Lab can run in the browser. 57 tests cover the scoring, the drift thresholds, the prompt budget, the signal rules and the retry policy.

Reliability

Every score is measured from a real run and never asserted. A heal is only credited once a verification run records a higher number next to the one it replaced, and the system can report failed, which is what makes it trustworthy when it reports verified.

Accessibility and design

Focus is visible everywhere, icon-only controls carry aria-label, the SVG fleet view exposes an accessible description and every animation stops under prefers-reduced-motion. The vitals ramp darkens in light mode to hold a 4.5:1 contrast ratio, and content is present in the markup so the page stays readable without JavaScript.

Security

The API token is passed through the environment rather than a CLI flag, so it never appears in a process listing. .env.local is gitignored and excluded from deployment uploads, and no scraped personal data enters the system by design.

Deployment and Data Ethics

The hosted build is a showcase, not a control plane, and the header says SNAPSHOT rather than LIVE. A serverless filesystem is read-only, and a heal runs for five to twenty-five minutes shelling out to the Bright Data CLI, far outside any request lifetime. Rather than ship buttons that fail, the deployed build serves a committed snapshot of real runs and disables those actions with tooltips explaining why. The Contract Lab stays fully interactive, because the scoring layer needs no I/O.

Public sources only. No login-walled or paywalled content, and no personal data. Signals are scored at company level and derived from public job postings, directory listings and published changelogs. A job posting contributes its title and its employer, never a named person.

Conclusion

Most scraping projects treat breakage as an operational chore to be handled later. Spinneret treats it as the central design problem, because a signal product whose data quietly goes stale is worse than one that does not exist.

The result is a system that measures itself after every run, writes its own repair instructions from those measurements, and records the before and after so the claim can be audited rather than believed. One collector went from 20 to 97 that way, and the Contract Lab exists so anyone can reproduce the reasoning instead of taking my word for it.