back to all work

Current: Content Intelligence Platform

Current watches a brand’s market, finds the gap between what buyers search and what the brand ranks for, drafts in the brand’s own voice, and publishes nothing a human has not approved. FullFunnel runs their own content operation on it.

Role
Architect & sole engineer
Timeline
2026 to present
Status
In production
Context
Built for FullFunnel, a Boston-based GTM agency. Shipped as Current (fullfunnel.co/current); results stated publicly on video by FullFunnel
Next.jsPostgreSQLpgvectorInngestClerkOpenRouterVercelApifyDataForSEOGoogle Search ConsoleSlackNano BananaSeedanceKlingChatGPT Image

// 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 routermodel per taskcost trackedweekly queueGuardrail gaterule checksbrand voicesimilarityfail → regenerate with reasonsHuman reviewqueue with fullprovenancepassMulti-tenant: Clerk auth · per-tenant scoping in middleware

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 the model 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 (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 with per-tenant scoping enforced in middleware. 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 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 rule set plus a brand-voice comparison
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.

// stage.outcomes

What the numbers say

+166%content output at FullFunnel, stated on videoclient-reported
Page 2 → 1target keyword movement inside 90 daysclient-reported
Cheapercost per piece, the win FullFunnel ranks firstclient-reported
Human gateapproval required before anything ships, by product rulecounted in the codebase

The system replaced a manual research cycle with a continuous one. Competitors and thought leaders stream in as signals, cluster into patterns once enough of them share a topic, and get scored against the brand brief and the keyword universe. Keywords carry their provenance: from the brief, from a competitor, from the brand’s own Search Console data, or from a signal. A topic surfaces because something in the market moved, not because a quarterly spreadsheet said so.

This one is checkable. Current is public at fullfunnel.co/current and FullFunnel walks through the product and states the results on video, so the outcomes do not rest on my description of them. I built the system; FullFunnel’s team produced the content those numbers measure.

// stage.retro

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 Agencysource: client feedback

// stage.common.questions

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?

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

Why route across several 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.