wiki/sources/legacy/Design-2026-05-04-phase4-prism-integration.md
Scout Phase 4 — PRISM intel-hiring Integration
What shipped
Scout Phase 4 wired Scout as the Tier 2 stealth browser in PRISM's BrowserClient and built a 2-track pipeline for intel-hiring.
Component 1: prism_platform/browser/tier2_stealth.py
Replaced the Playwright stub with Scout. ScoutCrawler.scrape() is called with use_js=True. Maps ScrapeResponse → FetchResult:
- resp.metadata.url → FetchResult.url (redirect tracking)
- resp.markdown → FetchResult.text
- resp.raw_html → FetchResult.html
- FetchTier.PLAYWRIGHT always
- Content shorter than options.min_content_length → is_bot_blocked=True
interactive_session remains a stub (search audit Phase 5+).
Component 2: prism_platform/v2/modules/intel_hiring/fetcher.py
fetch_careers_page(domain, timeout=20.0) -> HiringFetchResult with 3 phases:
1. httpx probe on /careers and /jobs (fast, cheap — exits if 200 OK with content)
2. Scout Playwright on 9 career path patterns
3. Scout Playwright on career subdomains (careers.{domain}, jobs.{domain})
4. LinkedIn redirect detection: resp.metadata.url contains linkedin.com → redirected_to_linkedin=True
5. Empty HiringFetchResult if all fail (non-fatal)
HiringFetchResult is a frozen Pydantic model.
Component 3: prism_platform/v2/modules/intel_hiring/playbook.md
Added ## Live Career Page Content section at the top with {upstream_careers_page} placeholder. Perplexity is instructed to treat the injected content as authoritative ground truth and fall back to web search if empty.
Component 4: prism_platform/orchestrator/activities.py
Added _run_intel_hiring_pipeline():
1. Track 1: fetch_careers_page(input.domain) → inject careers_page into v2_context.upstream_results
2. LinkedIn redirect: log warning, continue (Perplexity still runs)
3. Track 2: ModuleExecutor.execute() — Perplexity research with injected content
4. No Track 3 (no synthesis — ground truth injected directly into Perplexity prompt)
Routing: added elif input.module_name == "intel-hiring" before the generic fallback.
fetch_careers_page is imported at module level (not locally) so unittest.mock.patch can intercept.
Bugs fixed in Scout during Phase 4
crawl.py / map.py — BFSDeepCrawlStrategy broken in 0.7.7
BFSDeepCrawlStrategy + arun_many() returns a list/generator of CrawlResultContainer objects where .dispatch_result.result is None — the actual crawl content is lost. This is a 0.7.7 internal API incompatibility.
Fix: Removed BFSDeepCrawlStrategy entirely. Implemented manual BFS:
- arun() per URL (returns CrawlResultContainer with full content via attribute proxying)
- Link discovery via result.links["internal"] and result.links["external"]
- deque queue with (url, depth) tuples
- Respects max_depth, max_pages, url_pattern, include_external
extract.py — LLM extraction returns list not dict
LLMExtractionStrategy wraps extracted items in a JSON array. result.extracted_content = '[{"field": "value"}]'. The ExtractResponse.data field is typed as dict, so parsing without unwrapping → Pydantic validation error.
Fix: After json.loads, check if result is list → use raw[0] if raw else {}.
core/init.py — ScoutCrawler not exported
from scout.core import ScoutCrawler raised ImportError — __init__.py was empty.
Fix: Added from scout.core.crawler import ScoutCrawler; __all__ = ["ScoutCrawler"].
Tests
19 new tests in tests/v2/test_intel_hiring_phase4.py covering AC-1 through AC-12:
- Layer 1 (unit): TestTier2Stealth (4 tests), TestHiringFetcher (7 tests)
- Layer 2 (contract): TestHiringFetchResult (3 tests), TestPlaybookCareer (2 tests)
- Layer 3 (integration): TestHiringPipeline (3 tests)
Full suite: 213/213 passing. Ruff: 0 errors in new files. Mypy: 0 errors.
Open items (deferred)
- Dockerfile:
playwright install chromiumneeded in PRISM production container - Scout production packaging: editable install (
uv add --editable) works dev only - Temporal timeout: intel-hiring activity timeout may need 120s → 180s
- Fast-follows: cap Scout at 3 paths (not 9), asyncio.gather for concurrent probing
- Scout Claude Code skill not yet installed in settings
- intel-hiring frontend not yet built (module not "complete" per definition)