Most data pipeline blog posts are written by people who ran a script once and got 100 records. This isn't one of those. We've operated the Kairos Signal data pipeline for over 36,000 aggregate hours — scraping, classifying, enriching, and serving structured intelligence to autonomous AI agents. Along the way we've learned exactly what breaks, what doesn't, and what nobody tells you about running AI infrastructure in production.
Here's the architecture, the war stories, and the lessons.
The Pipeline, Step by Step
Every signal in our corpus follows the same path. Understanding this flow is the key to understanding why some pipelines survive contact with reality and others don't.
Layer 1: Collection
Our scraper (propintel-scraper) runs 30 workers in parallel. Each worker is assigned a metro area. Within that metro, the worker queries 12 distinct distress sources:
- County deed records
- Tax assessor databases
- Building permit registries
- Facebook distress groups
- Reddit local forums
- X/Twitter keyword searches
- Nextdoor neighborhood posts
- Craigslist housing sections
- Weather/disaster feeds (for water damage, fire, flood)
- Earthquake and wildfire monitors
- Federal contract databases
- Municipal meeting minutes
Layer 2: Ingestion
Raw signals land in ingest.db, a SQLite database running in WAL mode (Write-Ahead Logging). We chose SQLite over PostgreSQL for this layer because:
- No network overhead for local writes
- WAL mode handles concurrent reads during writes
- Backup is a file copy — no
pg_dumpneeded - 193,000+ raw sales records, 69,000+ social signals, and the database is still under 100MB
Layer 3: Enrichment
This is where the magic happens — and where most pipelines fail.
Our enrichment engine reads raw signals from the ingest database and runs them through a local LLM for classification. We use granite4.1:3b via Ollama, running on an NVIDIA RTX 3070. The model receives a prompt like:
> "You are a distress signal classifier. Analyze this social media post and extract: intent category, location, urgency level, and estimated property value."
The response is parsed into structured JSON. If the intent is real estate distress, the signal is enriched with:
- Property data (from tax rolls and county records)
- Contact vectors (public phone numbers, email addresses)
- Psychographic profile (urgency, motivation, likely sale timeline)
- Confidence score (0-100%)
- Tier assignment (GOLD > 80%, SILVER > 60%, BRONZE < 60%)
enriched_leads.jsonl and immediately become available in the MCP marketplace. As of today, that file contains 1,007,725 records.
Lesson 3: Classification breaks everything. We went through 2,953 enrichment crashes before finding the root cause: the fallback model was qwen3.5:4b, which wasn't loaded in Ollama. Every time our primary model timed out, the enricher tried qwen3.5, got a connection refused, and crashed. The fix was simple — point the fallback at granite4.1:3b — but discovering it took weeks of log analysis.
Lesson 4: Local inference is not just a cost play. At 70,000 signals per day, sending each one to a cloud API would cost approximately $420/day at current rates. Local inference costs approximately $0.07/day in electricity. But the real advantage isn't cost — it's latency and privacy. Cloud APIs add 200-800ms per request. Local inference is 50-100ms. And your data never leaves your machine.
Layer 4: Distribution
Enriched leads are served through two channels:
/api/v3/): A traditional REST API with Stripe integration (pending live key configuration)/mcp/): Our MCP-native terminal that AI agents use to discover and purchase data autonomouslyBoth run behind nginx, which routes traffic through a Cloudflare tunnel. The tunnel provides SSL termination, DDoS protection, and hides our origin server.
Lesson 5: Serve AI agents first, humans second. Human interfaces require dashboards, forms, email confirmations, and support. AI interfaces require structured JSON, clear pricing, and reliable uptime. When you build for machines first, the human experience improves automatically — because the data is clean, the pricing is transparent, and the delivery is instant.What Broke (And How We Fixed It)
No production pipeline runs without incidents. Here are the ones that taught us the most:
The qwen3.5 crash loop. We mentioned this above, but it deserves emphasis: 2,953 restarts over a model that wasn't even loaded. The fix was a one-line configuration change. The lesson is that model fallback logic needs to validate model availability before attempting inference. We now run a pre-flight check on startup. The disk space spiral. Ourenriched_leads.jsonl file hit 554MB. With only 15GB free on the root partition, the guard process started warning. The fix was implementing log rotation and archiving historical signals to the 1TB data drive. But the real lesson is that JSONL files grow faster than you expect — plan for 5-10x your initial volume estimate.
The recon broker dead-end. For months, our recon service was submitting leads to localhost:5002, a mock endpoint that didn't exist. Every single ping-post returned HTTP 400. We fixed it by redirecting recon to our own MCP terminal — now recon-enriched leads feed directly into the marketplace. The lesson: mock endpoints in production configs are landmines. Audit them regularly.
The port collision. Fast Checkout and Data Hound both tried to bind port 5007. Fast Checkout crashed on startup. The fix was giving each service its own port (6007 and 5007 respectively) and documenting the port map.
The Stack: What We Use
If you're building your own pipeline, here's our stack in full:
| Component | Technology | Why | |---|---|---| | Scraper | Python, 30 multiprocessing workers | Python's ecosystem for HTTP and parsing is unmatched | | Ingestion DB | SQLite (WAL mode) | Zero-config, single-file, fast enough for 100 writes/second | | Enrichment | Python + Ollama + granite4.1:3b | Local inference, fast classification, no API costs | | Vault | PostgreSQL (hermes_vault) | 48 tables, relational integrity, complex queries | | Web servers | Nginx + Cloudflare Tunnel | SSL termination, caching, DDoS protection | | Process management | PM2 | Auto-restart, log management, zero-config clustering | | Model serving | Ollama (12 models) | Local LLMs without Docker or Kubernetes | | MCP terminal | Custom Python HTTP server | Lightweight, single-file, MCP protocol compliant | | Blog | Custom Python server | Static-ish markdown blog with RSS and sitemap |
Total cost: approximately $140/month (server + Cloudflare + domain). Compare that to any managed data pipeline service, which starts at $2,000/month and doesn't include the data itself.
Building Yours
If you're starting a data pipeline, here's our advice:
localhost, mock, and placeholder and verify each one.We've open-sourced components of our pipeline and documented the architecture in depth. If you're building something similar, we'd love to hear about it.
And if you'd rather not build it yourself — we sell the output.



