All work

Signal-to-Content Intelligence Platform

A production SaaS that turns market signals into brand-checked content drafts. A 62-rule guardrail engine reviews every draft before a human sees it.

Role
Architect & sole engineer
Timeline
2026 to present
Status
In production
Context
Built for FullFunnel, a Boston-based GTM agency serving 50+ B2B clients
Next.jsPostgreSQLpgvectorInngestClerkOpenRouterVercel

// stage.problem

Content teams drown in noise and ship generic AI slop

The agency’s clients hit two failures at once. Content calendars were built from last quarter’s keyword research, so by publish day the conversation had moved. And when teams used AI to close the speed gap, they got fluent, generic text: off-brand, unverifiable, and often wrong in the details.

The brief I set myself: make the research continuous instead of manual, and constrain AI drafting hard enough that the output survives a brand editor.

// stage.system

Signals in, governed drafts out

System architecture · signal to governed draft
CompetitorsignalsIndustry news& PR feedsCommunitychatterSearch ConsolequeriesIngestionInngest jobsretries + limitspgvectorclusteringOpportunityranker (ICP fit)LLM router8 modelscost trackedweekly queueGuardrail gate62 rules +brand voicesimilarityfail → regenerate with reasonsHuman reviewqueue with fullprovenancepassMulti-tenant: Clerk auth · per-tenant scoping in middleware · 45+ tables

Ingestion runs on scheduled Inngest jobs: per-client listening configs pull competitor updates, industry news, and community signals through scraping and search APIs, normalize them, and embed them into pgvector. Clustering groups related signals into topics. A ranking pass scores each topic against the client’s ICP, Search Console data, and past content performance, surfacing the gap between what the market is asking and what the client ranks for. The result is a weekly opportunity queue.

Generation runs through two gates. A router picks among 8 models via OpenRouter based on task type and cost ceiling, with undated model aliases so provider deprecations don’t break production. Every draft then passes the guardrail engine (62 codified rules against AI-tell patterns, vague claims, and structural slop) and a brand-voice comparison against embeddings of the client’s own published writing. Drafts that fail either gate are regenerated with the failure reasons injected into the prompt. Drafts that pass reach a human review queue with full provenance: which signals, which model, what cost.

The whole system is multi-tenant: Clerk auth, per-tenant scoping enforced in middleware, 45+ tables. It runs across the agency’s entire client portfolio.

// stage.product.view

A review queue instead of a blank page

content-intelligence / this weekOpportunity queueRANKED AGAINST ICP + SEARCH DATA · REFRESHED WEEKLYCompetitor launched usage-based pricing14 SIGNALS · COMPETITOR + COMMUNITY + SEARCH92Buyers asking about AI governance11 SIGNALS · COMPETITOR + COMMUNITY + SEARCH87Category keyword losing rank vs rivals9 SIGNALS · COMPETITOR + COMMUNITY + SEARCH81Community debating build vs buy7 SIGNALS · COMPETITOR + COMMUNITY + SEARCH74Review queueNOTHING REACHES A HUMAN UNCHECKEDGUARDRAIL ENGINE · 62 RULESAI-tell patterns: 0 matchesVague claims: 0 flaggedStructure rules: passedBrand-voice similarity: highPROVENANCE14 source signals · routed across 8 models by task and cost · price per draft on the recordaudit trail
INTERFACE RECREATED WITH REPRESENTATIVE TOPICS · CLIENT NAMES REMOVED

// stage.key.decisions

Why it’s built this way

Decision 01

Durable queues over cron scripts

Chose
Inngest for every pipeline step
Over
Vercel cron + fire-and-forget API routes

Signal ingestion is long-running, failure-prone work against third-party APIs. Inngest gave step-level retries, concurrency limits per tenant, and replayable runs. A failed scrape retries itself; a failed cron script silently loses a week of signals.

Decision 02

One vector store instead of two databases

Chose
pgvector (halfvec) inside the main Postgres
Over
Dedicated vector DB (Pinecone/Qdrant)

Signals need joins against tenants, topics, and drafts constantly. Keeping vectors in Postgres meant one source of truth, transactional writes, and no sync layer. halfvec cut the index footprint roughly in half; accuracy loss was unmeasurable for this workload.

Decision 03

Codified rules over tone prompts

Chose
A codified 62-rule guardrail engine
Over
"Write in a human tone" prompt instructions

Prompt-level tone instructions decay as models change and prompts grow. Explicit rules, each testable with its own failure message, made quality enforceable and debuggable. When a draft fails, the log names the rule.

Decision 04

Model routing as a first-class system

Chose
Multi-model router with per-call cost tracking
Over
One flagship model for everything

Clustering summaries don’t need the same model as final drafts. Routing by task cut generation costs materially, and cost-per-piece became a reportable metric instead of a monthly surprise.

// stage.production.notes

What broke, and what it taught the system

Silent fallbacks are worse than loud failures

A scraping provider started returning degraded results instead of errors. The pipeline kept running while signal quality collapsed. Fix: validation on ingest with explicit quality thresholds, and alerts when a source’s yield drops below baseline. Every external dependency now fails loudly.

Model deprecations happen on someone else’s schedule

A dated model identifier got deprecated mid-operation. The router now uses undated aliases resolved at call time, with fallback chains per task type. A provider retiring a model no longer takes the pipeline down.

Multi-tenancy is a middleware problem, not a query problem

Tenant scoping enforced once, in middleware, rather than trusting every query to remember a WHERE clause. An early auth-bypass bug found in testing turned this from a preference into a rule.

OUTCOMES

What the numbers say

62guardrail rules enforced on every draft
8LLM models routed by task and cost
45+Postgres tables, multi-tenant schema
50end-to-end tests in CI

The platform runs the agency’s content operation across its client portfolio: weekly ranked topics per client, drafts that arrive pre-checked against the brand’s own voice, and a review queue instead of a blank page. Content research went from a recurring manual task to a background process.

Every draft carries provenance: source signals, model, cost, guardrail results. Editors can check any claim against its source. That audit trail is why they trust the queue.

WHAT I’D DO DIFFERENTLY

The honest retro

  • I over-invested in scraping breadth early. Half the sources produced signals nobody acted on; the ranking layer mattered more than source count. Today I’d ship with 5 high-yield sources and expand by demand.
  • Guardrail rules should have been versioned from day one. Editing rules in place made "why did this draft pass last month?" harder to answer than it should be.

Your ability to quickly understand what we’re trying to do, interpret the concept, and then play tennis with me to get it from theory to a product — that is your most meaningful contribution. We came up with a story. You helped me make it real.

CEO & Founder, GTM Agency

// stage.common.questions

What is a content intelligence platform?

A system that continuously ingests market signals (competitor moves, industry news, community discussions), clusters them into topics, ranks them against a client’s ICP and search data, and drafts content under brand guardrails. This one runs as a multi-tenant SaaS across an agency’s client portfolio.

How does the guardrail engine prevent AI-sounding content?

62 codified rules check every draft for AI-tell patterns, vague claims, and structural slop, alongside a brand-voice comparison against embeddings of the client’s own published writing. Drafts that fail either gate are regenerated with the failure reasons injected into the prompt.

Why route across 8 LLM models instead of using one?

Different pipeline steps need different quality-to-cost ratios: clustering summaries tolerate cheaper models, final drafts do not. Routing by task type with per-call cost tracking made cost-per-piece a reportable metric instead of a monthly surprise.