Corroba — an ad agency that only says what it can prove Corroba is an autonomous, evidence-grounded growth engine: it mines real customer proof, turns the strongest proof into grounded ad campaigns, launches and measures them, then feeds the outcomes back in as new proof. The loop closes.
Corroba Autopilot — the closing proof loop
Inspiration Generative AI writes fluent ad copy — and will happily invent claims it can't back up. I wanted the opposite: a system that can only say something if it can point to real, scored proof and show the receipt. That single rule — every claim traceable to validated evidence — became the whole product. The second spark was autonomy: I wanted the entire loop (research → create → check → launch → learn) to run itself, without ever cutting the honesty corner.
How I built it
Front end & loop: Next.js + TypeScript + Tailwind, with an animated "autopilot" view that streams the agent loop live.
LLM: Google Gemini through the Vercel AI SDK (bring-your-own-key via the AI Gateway), with a fully offline mock provider behind it.
Grounding (the moat): a deliberately lightweight RAG — no vector DB. For each product I embed its validated proof points and the campaign query with Gemini embeddings, rank by cosine similarity, and inject the top proof into the prompt.
Agents: a multi-step pipeline — Briefing → Audience → Creative → Safety → Simulation → Deploy → Feedback — where each agent is one step with retry → deterministic fallback → skip, so a single failure never stops the loop.
Demo-first, degrade gracefully: every external service is optional and env-gated. Key present → real call with a timeout; missing or erroring → deterministic fallback; never throw.
// One contract, applied to every integration:
// real call when keyed, deterministic fallback otherwise — never throw.
export async function embed(text: string): Promise<number[]> {
if (!embeddingsEnabled()) return hashEmbed(text); // zero-key offline path
try {
const { embedding } = await sdkEmbed({ model: gemini(), value: text });
return embedding; // real Gemini vector
} catch {
return hashEmbed(text); // graceful degrade
}
}
Challenges I faced
Designing for failure first. Making ~10 integrations each optional, mockable, and loop-safe was more work than the happy path — but it's what makes the demo bulletproof.
Models get retired mid-build. My pinned gemini-2.0-flash and text-embedding-004 started returning 404 — and a model can still appear in the "list models" response yet fail on use. I learned to verify every model with a real call before trusting it, then moved to gemini-3.5-flash and gemini-embedding-001.
Thinking tokens. Gemini 3.5 spent its output budget thinking and returned empty text on small limits. Fix: set a minimal thinking level and raise the token ceiling.
Serverless reality. No writable disk, so state lives in memory with a best-effort DB mirror, and long planner calls get skipped under serverless time limits.
What I learned
RAG doesn't need heavy infra — embeddings + cosine over a small, curated proof set gives real grounding with almost no moving parts.
Fallbacks are a feature. Designing the degraded path first made the system both demo-able and genuinely robust.
LLM plumbing is a moving target — model names, token accounting, and provider quirks shift under you.
Agents are mostly orchestration — retries, veto/consensus, and provenance matter more than any single prompt.
Built With
- clickhouse
- elevenlabs
- gemini
- google-cloud
- javascript
- next.js
- paypal
- prometheux
- tavily
- typescript
Log in or sign up for Devpost to join the conversation.