Knowledge/AlgoliaCrawler/06-l1-implementation-plan.md
06 — L1 Data Fetch: Algolia Crawler Implementation Plan
Status: BUILT + DEPLOYED — crawler running as of 2026-04-30 08:10 UTC Layer: L1 Data Fetch (RC3 Phoenix engineering spec)
Mandatory standards (CodingSOPs §7 + TestingSOPs Layer 3): - Pydantic v2 model for every data structure crossing the L1→L2 boundary — no raw dicts - pyright --strict on all L1 modules - Contract test validates
CrawlerRecordoutput satisfiesEnrichmentInputschema - See07-schema-contracts.mdfor the full contract design
Inputs received and analysed
| Input | Status | Key findings |
|---|---|---|
| n8n Firehose v2 | ✓ Read | URL normalization logic, source_type routing, classification taxonomy |
| n8n Refinery | ✓ Read | Chunking strategy (technical 600/150, narrative 1500/300), GPT summary, status flag |
| Seeder list PDF | ✓ Read | 18 sections, ~67 unique URLs, single-vendor (all algolia.com properties) |
| Live Algolia index | ✓ Queried | 10,615 records, exact field schema, source_type distribution, tag taxonomies |
Seeder list analysis
Critical insight: this is NOT a broad web crawl. All 67 seeds are algolia.com properties.
The 6 domains
| Domain | Seeds | Live records | Notes |
|---|---|---|---|
www.algolia.com |
47 seeds | ~3,200 | Main site — blog, customers, resources, about, policies |
algolia.com (no www) |
7 seeds | ~5,475 | Docs — the deepest section |
support.algolia.com |
4 seeds | 1,732 | Zendesk HC — standard HTML |
academy.algolia.com |
4 seeds | 139 | LMS — likely SPA, needs renderJavaScript |
stories.algolia.com |
1 seed | 149 | Medium-like — SSR |
changelog.algolia.com |
1 seed | 11 | Static page |
Section priorities
| Section | Priority | Why |
|---|---|---|
| §1 Core product + §2 Docs sub-areas | MUST-HAVE | Bread-and-butter knowledge — 5,475 doc records in live index |
| §6 Support KB | MUST-HAVE | 1,732 support records — agent's primary Q&A source |
| §3 Customer stories | MUST-HAVE | 149 customer_story records — proof/social |
| §4 Blog | MUST-HAVE | 1,161 blog records — thought leadership |
| §8 Academy | MUST-HAVE | Learning content |
| §10 Changelog | MUST-HAVE | Release notes |
| §11–17 GOOD-TO-HAVE | GOOD | Pricing, use-cases, industries, partners, policies |
| §18 Fragment quick-links | SKIP | Hash fragments collapse to customer-hub — 4 duplicates |
Fragment URL issue (§18)
These 4 seed URLs contain # fragments:
https://www.algolia.com/customer-hub#documentation
https://www.algolia.com/customer-hub#knowledge-base
https://www.algolia.com/customer-hub#algolia-academy
https://www.algolia.com/customer-hub#changelog
The Algolia Crawler strips # fragments — all 4 normalize to https://www.algolia.com/customer-hub. Only one record produced. Remove these 4 from startUrls and keep just https://www.algolia.com/customer-hub.
Resource center ?type= params
These MUST be kept — they represent distinct page content:
?type=webinar, ?type=report, ?type=ebook, ?type=video, ?type=customer-stories
Do NOT include type in ignoreQueryParams.
All decisions locked
D1: Crawl scope — FULL DOMAIN per seed domain
Since all seeds are algolia.com properties, use full domain crawl for each of the 6 domains. The seeds act as entry points + content anchors; the crawler discovers the rest via links.
This is correct because:
- We already have ~9,600 records from RC1/RC2 — full domain worked before
- The seeds cover all meaningful subsections — discovery within those domains is safe
- discoveryPatterns + exclusionPatterns control depth, not seed count
D2: objectID scheme — SHA256 of normalized URL
objectID = require("crypto").createHash("sha256").update(normalizedUrl).digest("hex").substring(0, 36)
SHA256 over base64: cleaner, collision-proof, stable. Normalized = fragment stripped, tracking params removed.
D3: Field schema — LOCKED to live RC2 index
See 03-record-extraction.md — full schema verified against live index 2026-04-30.
L1 sets: objectID, record_type, url, title, description, content, source_type, language_code, is_chunk=false, status="pending", created_at, updated_at
L2 fills: summary, product_tag, feature_tag, solution_tag, customer_tag, industry_tag, status→"indexed"
D4: Index name — algolia-central_enterprise_ledger
App ID 0EXRPAXB56. Use indexPrefix: "dev_" for local testing.
D5: renderJavaScript — academy only
Only academy.algolia.com/** likely needs it (SPA/LMS). All other domains are SSR/static.
D6: rateLimit — 10
Single vendor, well-resourced CDN. Start at 10; raise if needed.
D7: maxUrls — 30,000
Current index has ~9,600 ledger records. 30,000 gives room for growth with a safety cap.
D8: schedule — manual initially, then weekly
Run manually during RC3 build phase. Add schedule: "every 7 days" once config is validated.
D9: source_type routing — FROM n8n Firehose (ported to JS)
Exact same logic as the Normalize fields node in Firehose v2. Do not deviate.
D10: content field — markdown preferred
The Refinery chunked on markdown field. The Algolia Crawler can extract markdown-like text. Use the $ Cheerio instance to extract clean text from main content areas, not the full page HTML.
Complete crawler config
See 08-crawler-config-complete.md for the production-ready JavaScript config.
Type safety and testing gates
Per CodingSOPs §7 and TestingSOPs, the following are non-negotiable before L1 ships:
| Gate | Tool | When |
|---|---|---|
CrawlerRecord Pydantic model defined |
Pydantic v2 | Before writing any extractor |
EnrichmentInput Pydantic model defined |
Pydantic v2 | In sync with L2 team |
| pyright --strict clean on L1 module | pyright | Pre-commit |
| Contract test: L1 output satisfies L2 input | pytest Layer 3 | On every commit |
| Unit tests on extractor logic | pytest Layer 1 | Before any crawl |
| Integration test against live crawler API | pytest Layer 2 | Before full crawl run |
See 07-schema-contracts.md for the full schema + contract test design.
Build → test → execute sequence
Phase 0: Config authoring (NOW — unblocked)
- Write full crawler config (
08-crawler-config-complete.md→scripts/crawler/config.js) - Write Pydantic models (CrawlerRecord, EnrichmentInput —
07-schema-contracts.md) - Write contract tests
Phase 1: Smoke test (URL Tester, no full crawl)
- Use Crawler dashboard URL Tester on 10 sample URLs across all 6 domains
- Verify: title extracted, content clean, source_type correct, objectID stable
- Specific URLs to test (one from each domain):
https://algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/https://support.algolia.com/hc/en-us/articles/4406981910289https://www.algolia.com/blog/product/algolia-ai-search-2024/https://www.algolia.com/customers/lacoste/https://academy.algolia.com/courses/https://changelog.algolia.com/
Phase 2: Limited crawl (maxUrls: 500)
- Run with hard cap — validate success rate, inspect 20 records
- Compare source_type distribution vs live index
- Check Data Analysis tab for missing fields
Phase 3: Schema audit
- Export 100 sample records from Algolia
- Validate against
CrawlerRecordPydantic model (model_validate()on all 100) - All must parse clean — any failure = extractor bug
Phase 4: Full crawl
- Remove
maxUrlscap - Monitor: Monitoring tab, success rate > 85%
- Watch
safetyChecks— setmaxLostRecordsPercentage: 15for first run
Phase 5: Deduplication audit
- Query index: facet on
url, check for any URL with count > 1 - Verify total record count is in expected range (within 20% of ~9,600)
Phase 6: L2 handoff validation
- Query index for
status: pending— should be ~all new records - Run L2 Enrichment on 50 pending records manually
- Verify tags populated, status flips to
indexed
Open questions — RESOLVED
| Question | Answer |
|---|---|
| How many domains? | 6 (all algolia.com properties) |
| Seeder URL count? | ~67 unique (after dedupping §18 fragments) |
| Any SPA/JS-heavy? | academy.algolia.com only |
| Any auth required? | No — all public pages |
What ?type= params to keep? |
All — they represent distinct content |
| Estimated record count? | ~9,600 parent docs + chunks (matches RC2 live) |
| maxUrls safe cap? | 30,000 |