Modules/Intel-Techstack.md
Module: intel-techstack
WAVE 1 SPOKE. Detects the current search vendor, ecommerce platform, and supporting tech stack via BuiltWith API. Fans out to every competitor domain from intel-company output. The
golden_angle_competitorsfield -- competitors who use Algolia -- is the most commercially critical output in this module.
Identity
| Field | Value |
|---|---|
| Name | intel-techstack |
| Version | 0.1.0 |
| Layer | Intelligence (Wave 1) |
| Wave | 1 (runs alongside other spoke modules after intel-company completes) |
| Description | Technology stack detection via BuiltWith v22 API. Identifies search vendor, ecommerce platform, CMS, CDN, analytics, personalization, and bot detection for the prospect and all competitor domains. Produces a comparative summary and flags Golden Angle competitors (competitors using Algolia). |
| LLM Tier | None (deterministic classification, no LLM enrichment) |
| Timeout | 120 seconds |
| Max Retries | 2 (module level) |
| Gate Module | NO. Failure returns partial result; does not abort audit. |
Dependencies
| Dependency | Field Read | Why |
|---|---|---|
| intel-company | Account.competitors (via ExecutionContext.intelligence.competitor_domains) |
Fan-out: runs BuiltWith on each competitor domain in parallel |
If intel-company has not populated competitors, the module falls back to prospect-only detection. This is non-fatal.
Hub-and-Spoke: What This Spoke Provides
intel-techstack writes to module_executions. Downstream consumers read its output as:
| Consumer | Field Read | Why |
|---|---|---|
| intel-competitors | search_vendor, golden_angle_competitors, competitor_tech_stacks |
Competitor tech intelligence for displacement analysis |
| audit-report | search_vendor, algolia_detected, golden_angle_competitors |
Determines audit framing: displacement vs. greenfield vs. existing customer |
| synth-business-case | ecommerce_platform, search_vendor |
Platform context for ROI model calibration |
| algolia-audit-skill | tech_stack_summary, comparative_summary |
Pre-built narrative for skill output file 02-tech-stack.md |
Input Schema
| Field | Type | Required | Description |
|---|---|---|---|
| domain | str | yes | Website domain to analyze, e.g. "dell.com" |
Competitor domains are NOT in the input schema. They are read from ExecutionContext.intelligence.competitor_domains at runtime, populated by intel-company.
Output Schema: TechStackOutput
Prospect Tech Stack Fields
| Field | Type | Required | Description |
|---|---|---|---|
| search_vendor | SearchVendor or None | no | Detected search vendor. None if no search tech detected. |
| ecommerce_platform | str or None | no | Detected ecommerce platform (e.g. "Salesforce Commerce Cloud") |
| cms | str or None | no | Detected CMS (e.g. "Adobe Experience Manager") |
| cdn | str or None | no | Detected CDN/WAF (e.g. "Akamai", "Cloudflare") |
| analytics | list[str] | no | All detected analytics tools (e.g. ["Google Analytics 4", "Adobe Analytics"]) |
| personalization | list[str] | no | Detected personalization/A-B testing tools |
| bot_detection | str or None | no | Detected bot detection / CAPTCHA solution |
| all_technologies | list[dict] | no | Full raw technology list from BuiltWith (Name, Tag, Categories per entry) |
| removed_technologies | list[dict] | no | Technologies BuiltWith has flagged as removed (not currently live) |
| tech_stack_summary | str | yes | Human-readable summary, e.g. "dell.com: Search: Elasticsearch (ACTIVE); Ecommerce: Salesforce Commerce Cloud" |
| algolia_detected | bool | yes | True if Algolia tag detected on the prospect's own domain |
Competitive Intelligence Fields
| Field | Type | Required | Description |
|---|---|---|---|
| competitor_tech_stacks | list[CompetitorTechStack] | no | Tech stack profiles for each competitor domain |
| comparative_summary | str | no | Narrative comparison of search vendors across prospect + competitors |
| golden_angle_competitors | list[str] | no | Company names of competitors confirmed using Algolia. The primary sales signal. |
Sub-Schema: SearchVendor
| Field | Type | Required | Description |
|---|---|---|---|
| name | str | yes | Canonical vendor name (e.g. "Algolia", "Elasticsearch", "Coveo") |
| status | Literal["ACTIVE", "TAG_ONLY", "REMOVED", "UNDETECTED"] | yes | ACTIVE = name-matched vendor; TAG_ONLY = search-category match but not in known vendors list; REMOVED = BuiltWith flagged as historical |
| detection_source | str | yes | How it was detected (e.g. "BuiltWith v22 API", "BuiltWith v22 API (category match)") |
| evidence_tier | EvidenceTier | yes | VERIFIED for name-matched vendors; WEBFETCH for TAG_ONLY category matches |
Sub-Schema: CompetitorTechStack
| Field | Type | Required | Description |
|---|---|---|---|
| company_name | str | yes | Competitor company name |
| domain | str | yes | Competitor domain (e.g. "hp.com") |
| search_vendor | SearchVendor or None | no | Detected search vendor for this competitor |
| ecommerce_platform | str or None | no | Detected ecommerce platform for this competitor |
| all_technologies | list[dict] | no | Full raw tech list for this competitor |
| tech_count | int | yes | Number of technologies detected |
| is_algolia_customer | bool | yes | True if Algolia tag confirmed on this competitor domain |
Collection Strategy
Source 1: BuiltWith v22 API (Prospect Domain)
| Aspect | Detail |
|---|---|
| Adapter | Direct httpx (TechStackCollector._fetch_builtwith) |
| Endpoint | GET https://api.builtwith.com/v22/api.json |
| Params | KEY={api_key}, LOOKUP={domain}, LIVEONLY=yes |
| Output | Nested JSON: Results -> Result -> Paths -> Technologies[{Name, Tag, Categories}] |
| Retry | httpx raises_for_status (no built-in retry at collector level) |
| Evidence Tier | VERIFIED (direct API, live-only filter applied) |
| Timeout | 30 seconds |
LIVEONLY=yes is critical: it filters out historical technologies. Without it, BuiltWith returns both current and removed stacks, muddying the search vendor detection.
Source 2: BuiltWith v22 API (Competitor Fan-out)
Same endpoint and params, called once per competitor domain via asyncio.gather. Each competitor is an independent BuiltWith call. Failures are isolated -- one competitor timing out does not abort others.
Execution strategy: Prospect + all competitors run in parallel via asyncio.gather. This is the module's primary performance optimization: N+1 competitor calls complete in the time of 1 sequential call chain.
Enrichment Strategy
| Aspect | Detail |
|---|---|
| Method | deterministic_classify (NO LLM) |
| LLM Model | None |
| Pipeline | raw JSON -> _classify_technologies() -> category regex matching -> TechStackOutput |
Technology Classification Logic
The _classify_technologies() method iterates every technology from the BuiltWith response and applies the following rules:
Search Vendor Detection (priority order):
1. Name match against KNOWN_SEARCH_VENDORS list: Algolia, Elasticsearch, Solr, Coveo, Bloomreach, Searchspring, Klevu, Constructor, Lucidworks, Swiftype, Yext, SearchStax. If matched, status = ACTIVE, evidence_tier = VERIFIED.
2. Category contains "search" (case-insensitive) and no name match: status = TAG_ONLY, evidence_tier = WEBFETCH.
3. First match wins. Multiple search vendor tags do not create multiple SearchVendor records.
Other Category Matching (case-insensitive substring on Categories list): - ecommerce_platform: category contains "ecommerce" or "shopping-cart" - cms: category contains "cms" - cdn: category contains "cdn" - analytics: category contains "analytics" (all matches, not first-only) - personalization: category contains "personalization" or "a-b-testing" - bot_detection: category contains "bot" or "captcha"
Golden Angle Detection
For each competitor in competitor_tech_stacks:
- If is_algolia_customer = True on the CompetitorTechStack model, add company_name to golden_angle_competitors.
- is_algolia_customer is set by checking if any raw technology name matches "Algolia" (via _match_search_vendor).
This is a direct API confirmation -- not inference. If Algolia is live-detected on a competitor's domain, that competitor is confirmed as an Algolia customer.
Validation Rules (8 Checks)
| # | Rule | Severity | Threshold |
|---|---|---|---|
| 1 | search_vendor is populated | warning | Not None |
| 2 | Minimum technology count | required | >= 3 technologies in all_technologies |
| 3 | At least 1 source recorded | required | sources list not empty |
| 4 | tech_stack_summary not empty | required | Non-empty string |
| 5 | comparative_summary present when competitors exist | required | Not empty if competitor_tech_stacks populated |
| 6 | Each competitor has at least 1 technology | warning | No empty competitor stacks |
| 7 | golden_angle_competitors subset of competitor names | required | All golden_angle entries present in competitor_tech_stacks |
| 8 | All competitor domains are valid | required | Each domain contains a "." |
Required failures -> status "partial". Warning failures -> logged but passed.
Note: Check 1 (search_vendor) is a warning, not a required failure. It is valid for a prospect to have no detectable search vendor (custom-built search, no search bar, etc.).
Evidence Requirements
| Field | Minimum Tier | Rationale |
|---|---|---|
| search_vendor (name-matched) | VERIFIED | Direct BuiltWith API detection with LIVEONLY filter |
| search_vendor (TAG_ONLY) | WEBFETCH | Category-inferred, not name-confirmed |
| ecommerce_platform | VERIFIED | Direct BuiltWith API detection |
| all_technologies | VERIFIED | Direct BuiltWith API, live-only |
| golden_angle_competitors | VERIFIED | Direct API confirmation per competitor domain |
| comparative_summary | WEBSEARCH | Derived synthesis, no direct citation |
Persistence
intel-techstack writes to module_executions only. It does NOT write to the accounts table directly.
module_executions Fields Written
module_name="intel-techstack",
module_version="0.1.0",
status="success" | "partial" | "failed",
output={...TechStackOutput fields...},
sources=[...Source records...],
duration_ms=...,
llm_calls=0,
llm_cost_usd=0.0
Status logic:
- success: len(all_technologies) >= 3
- partial: len(all_technologies) < 3 but module did not throw
- failed: unhandled exception in execute()
Cost Profile
| Metric | Expected Value |
|---|---|
| LLM Calls | 0 |
| Estimated Cost | $0.00 (BuiltWith subscription, per-domain lookup) |
| Expected Duration | 5-30 seconds (scales with competitor count; parallel) |
| External API Calls | 1 (prospect) + N (competitors, parallel) |
BuiltWith is typically licensed as a subscription with a per-lookup quota rather than per-call billing. Competitor fan-out consumes N additional lookups per audit.
Retry Architecture
Module Level (execute wrapper)
| Trigger | Behavior |
|---|---|
| Exception in execute() | Caught, logged, returns ModuleExecutorResult(status="failed") |
| Max Retries | 2 (via ModuleConfig.max_retries, handled by ModuleExecutor) |
Collector Level
| Trigger | Behavior |
|---|---|
| BuiltWith API failure (prospect) | Returns TechStackOutput with tech_stack_summary="BuiltWith API call failed" |
| BuiltWith timeout (competitor) | Returns CompetitorTechStack(company_name, domain) with no tech data |
| BuiltWith HTTP error (competitor) | Returns CompetitorTechStack(company_name, domain) with no tech data |
| asyncio.gather exception | Logged, that competitor result skipped; others unaffected |
The isolation boundary is the competitor: one competitor API failure does not contaminate other competitor results or the prospect result.
Runtime Notes
SaaS Runtime (Temporal/Python)
- Module:
prism_platform/v2/modules/intel_techstack/module.py - Collector:
prism_platform/v2/modules/intel_techstack/collector.py - Validator:
prism_platform/v2/modules/intel_techstack/validator.py - Schemas:
prism_platform/v2/modules/intel_techstack/schemas.py - Config key:
settings.builtwith_api_key(BUILTWITH_API_KEY env var)
Skill Runtime (Claude Code)
- Skill file:
~/.claude/skills/algolia-intel-techstack/SKILL.md - Reads from:
01-company-context.json(competitor domains) - Writes to:
02-tech-stack.md,02-tech-stack.json - MCP tools used: BuiltWith MCP (all 7 endpoints for prospect), BuiltWith MCP per competitor
What Makes This Production-Grade (vs a Simple Prompt)
| Aspect | Simple Prompt | PRISM Module |
|---|---|---|
| Detection method | "What search vendor does X use?" | Direct BuiltWith API with LIVEONLY filter -- no hallucination risk |
| Competitor coverage | Ask about each competitor manually | asyncio.gather fan-out: all competitors in parallel, one timeout budget |
| Golden Angle | Manual check | Deterministic: Algolia tag on competitor domain = confirmed customer |
| Status classification | Hope for the best | ACTIVE / TAG_ONLY / REMOVED / UNDETECTED with explicit evidence tier |
| Failure isolation | One error aborts everything | Per-competitor try/catch -- 1 failure doesn't lose 5 others |
| Evidence | "BuiltWith says..." | Typed Source records with evidence tier, source URL, method |
| Validation | Trust the output | 8-point validation; partial status returned if data quality is low |
Changelog
| Date | Change | Reason |
|---|---|---|
| 2026-04-14 | Module spec written to vault | Knowledge extraction for v2 planning |
Evolution: v1 -> v2
What Changes in v2
| Dimension | v1 (current) | v2 (planned) |
|---|---|---|
| Module structure | module.py + collector.py + validator.py | config.py + playbook.md + schemas.py + generic executor |
| Competitor input | Read from ExecutionContext.intelligence (fragile, JSONB path) | Read from typed Account.competitors column via FindingQuery contract |
| Execution strategy | Hardcoded asyncio.gather fan-out | Declared execution_strategy: per_company in config.py -- executor handles fan-out generically |
| Finding model | Raw dict in module_executions.output | Typed Finding with FindingCategory (TECH_STACK_DETECTION, GOLDEN_ANGLE_SIGNAL) |
| Claim registry | Not generated | ClaimRegistryEntry auto-generated per Finding for citation chain |
| Evidence tier | Set per-field in collector | Declared in playbook.md; enforced by executor |
| Search vendor taxonomy | 12 hardcoded strings in KNOWN_SEARCH_VENDORS | Loaded from config.py KNOWN_SEARCH_VENDORS list (overridable per deployment) |
| LLM fallback | None | Optional LLM classification step for ambiguous TAG_ONLY cases (configurable, off by default) |
v2 FindingCategory Taxonomy
TECH_STACK_DETECTION -- prospect tech stack confirmed
GOLDEN_ANGLE_SIGNAL -- competitor confirmed using Algolia (sales-critical)
SEARCH_VENDOR_CONFIRMED -- prospect search vendor name + status
COMPETITIVE_TECH_MAP -- competitor technology landscape summary
Key v2 Design Decisions
-
execution_strategy: per_company replaces the hardcoded
collect_all_with_competitorsmethod. The generic executor reads competitor domains from the account and runs the playbook once per domain, then merges results. This makes the fan-out pattern reusable across all modules that need it (intel-traffic, intel-hiring, etc.). -
Golden Angle as a first-class Finding (not a field in a larger output). In v2, each confirmed Algolia competitor generates its own
Finding(category=GOLDEN_ANGLE_SIGNAL)with the competitor domain, evidence URL, and detection timestamp. This makes Golden Angle signals queryable and surfaceable independently from the full tech stack dump.
Related
- Module-Catalog -- all 20 modules overview
- Intel-Company -- provides competitor list this module fans out on
- Evidence-Tier-Spec -- evidence tier system
- Adapter-Interfaces -- BuiltWith API adapter