Inspiration

We run a small studio and use AI agents to market our own products. They kept picking cultural references our audience didn't care about: the famous artist instead of the one our listeners actually stream, a podcast whose audience is the opposite of ours, sometimes a name that isn't in any catalogue at all. LLM marketing agents hallucinate culture, and they sound confident doing it.

What it does

Tastemaker plans a launch campaign for a creator or small brand (audience, launch city, collaborators, brand partners, hook references, podcast pitches, a venue, a presenter, hooks, a 30-second script and per-platform captions) and uses Qloo as the critic inside the agent loop:

  1. The model drafts the plan from Qloo's audience data.
  2. Every pick it lists, and any other name in its copy that Qloo knows by that exact name, is resolved in Qloo, with a typed search and then an untyped one. A name Qloo can't find is flagged "not found in Qloo"; a real entity filed under the wrong type is flagged as a role mismatch.
  3. Every real pick is scored against the target audience with filter.results.entities and feature.explainability (the launch city is ranked on the audience's heatmap instead). Venues must be in the launch city; hook references must not be fading on /v2/trending.
  4. For each failure, the model either picks one of Qloo's above-bar options or proposes its own idea, which Qloo scores first and code keeps only if it clears the same bar. The model then revises the copy, and the critic checks the whole plan again.
  5. Every surviving pick carries a receipt: affinity, pass bar, which seed drove it, trend, and the exact Qloo call.
  6. Without Qloo: one toggle shows the plan the same model writes with no Qloo access, with its misses struck through.
  7. Ship: the verified pack goes to Sigmora's publishing engine as a schedule (a labeled sandbox on the public demo).
  8. Audit any AI's plan: paste a campaign written by ChatGPT, Claude or anyone, and the same Qloo critic checks every cultural name in it against your audience, with a receipt and a Qloo-preferred replacement for each failure. One click runs it on a sample chatbot plan.

Why it can't work without Qloo

  • The critic step is a Qloo call. Remove Qloo and the agent has no way to reject a culturally wrong pick; the Without Qloo view shows the result on every run.
  • City choice, demographic over-indexing, rising vs. fading references and audience-vs-audience gaps are measured data an LLM cannot produce from pretraining.

How we built it

Next.js + TypeScript, a zod-typed Qloo client, and a model-agnostic LLM layer (any OpenAI-compatible or Anthropic model from env). Qloo surfaces used, each visible in the live agent trace:

  • /search and /entities (Spotify/IMDb external IDs) to resolve seeds and catch names that aren't in Qloo
  • /v2/insights with urn:demographics (who over-indexes) and urn:tag (taste vocabulary for the copy)
  • /v2/insights across artist, brand, tv_show, movie, podcast and person: the cross-domain culture map
  • urn:heatmap over the US, the audience's strongest cells folded into candidate metros on one scale: where to launch
  • urn:entity:place with filter.location.query and filter.location.radius=0: venues, and the venue-in-city check on the city's own boundary
  • filter.results.entities + feature.explainability: the critic
  • /v2/trending: rising vs. fading references
  • /v2/analysis/compare: the positioning gap vs. a comparable's audience
  • signal.demographics.* on every call the docs allow it on (people calls pending a live check)

Every request is validated against per-filter.type parameter allow-lists (Qloo silently drops unknown params), throttled, retried with backoff, budgeted, cached by normalized request, recordable as fixtures, and labeled live / replayed in the UI.

Responsible taste

Aggregate-only: inputs are public catalog entities, tags and demographic buckets, never a person's data. Results are audience-level affinity. Every receipt names the Qloo calls behind its number, every Taste Map panel names its endpoint, and the trace shows every call's parameters.

New-work disclosure

Sigmora (content production and publishing) pre-existed and is reached only through a narrow /v1 API. All Qloo integration, the agent, the critic loop, the UI and the benchmark were built for this hackathon (planning from 2026-10-06, code from 2026-10-24). Details: docs/new-work-disclosure.md.

Challenges we ran into

  • Qloo drops parameters it doesn't support, silently. A misspelled or unsupported filter returns an empty or unfiltered result instead of an error. We built per-filter.type allow-lists from the docs (src/lib/qloo/params.ts) that reject an invalid request in development and tests (in production they log it), wire tests that pin each request's parameters (tests/integration/wire-params.test.ts), and contract tests that parse the response examples the docs publish (tests/unit/qloo-contract.test.ts).
  • Search always finds something. A fuzzy /search hit for an invented name looks like a real entity. The critic only accepts a match whose normalized name agrees with the one the model wrote, so a hallucinated name is reported as "not found in Qloo" instead of being quietly swapped for a lookalike.
  • A circular pass bar. The bar is drawn from the same Qloo pool the plan is grounded in, so we say so in the UI and keep a fixed floor under it.
  • A free public demo with a paid quota. Every visitor runs a real agent, so Qloo calls are budgeted per run, per network and per day, presets and custom briefs draw from separate pools, and a refused run shows the last recorded run for that preset instead of a blank screen.

Accomplishments that we're proud of

  • Qloo isn't a lookup step bolted onto a chatbot. It decides what the agent may ship: Ship stops on any pick Qloo can't find, scores below the audience's bar or sees fading, and goes ahead only after an explicit yes; a pick Qloo couldn't score is labelled unscored, never passed.
  • The Without Qloo toggle makes the value visible on every run, not only in a benchmark.
  • The whole product runs from a fresh clone with zero credentials (an offline Qloo simulator and a scripted model, each labelled on screen), so judges and contributors can try every path.
  • Honest numbers: the demo's screens never show a number computed from simulated data, and no benchmark figure is published until a live run produces it.

What we learned

  • The hard part isn't spotting invented names, it's knowing whether a real name fits this audience. A model can't know that from pretraining; audience-level affinity can measure it.
  • Explainability (feature.explainability) turns a score into something a marketer can check: knowing which seed drove an affinity is what makes a receipt useful.
  • Read the API docs line by line. Qloo drops unsupported parameters silently, defaults matter (bias.trends is medium unless you say otherwise, so plain affinity has to ask for off), and locality insights accept only locality signals, so we rank cities for an audience with heatmaps instead.

What's next for Tastemaker

  • Run the first real campaign planned by Tastemaker through Sigmora's publishing engine, and publish its reach next to the plan.
  • Re-check live campaigns against /v2/trending and swap references that start fading.
  • Offer the critic as a tool any agent can call (the repo already exposes it over MCP as audit_plan).

Built With

  • fast-check
  • maplibre-gl
  • model-context-protocol
  • next.js
  • playwright
  • qloo
  • react
  • tailwindcss
  • typescript
  • vitest
  • zod
Share this project:

Updates

Submission history