Inspiration

Small and mid-size venues book acts on gut feeling, agent pitches and streaming numbers. Streaming numbers measure global popularity, not whether this room's crowd will buy a ticket on a Thursday. The result is half-empty rooms, safe repetitive lineups, and emerging acts that never get booked where they would actually sell. Opendate, a venue-software vendor, found in its own survey that over 35% of venues are dissatisfied with how they source talent.

The booking tools venues already pay for (Opendate, Gigwell, Prism) answer two questions well: "who is popular?" (Spotify, Apple Music, Bandsintown) and "who sold here before?" (the venue's own ticket history). Neither answers the question a talent buyer actually has: "who will this neighbourhood's crowd love that we have never booked?" That needs taste across domains, tied to a place: the bars, films and brands a crowd over-indexes on, not just its genres. That is Qloo's data, and nobody in this market has it.

Opendate, Gigwell, Prism Outdraw
Question answered "Who is popular?" and "Who sold here before?" "Who will this crowd love that we have never booked?"
Data behind it Streaming charts (Spotify, Apple Music, Bandsintown) and the venue's own ticket history Qloo's taste graph: the music, places, films and brands a crowd over-indexes on, tied to the venue's location
What you get A database to search and a booking workflow (holds, offers, contracts, settlements) A ranked shortlist with the evidence per pick, exported into the booking tool you already use

Opendate tells you who sold here before. Outdraw tells you who your crowd will love next. It is an intelligence layer beside those tools, not a replacement for them.

Ask a plain LLM "who should a jazz bar in New York book?" and it produces plausible, invented names. I wanted an agent whose answer comes from affinity data, with the evidence on screen, so a booker can check every number before trusting it.

The name is booking jargon. An act's draw is how many people it pulls in a market. To outdraw a rival act is to pull the bigger crowd.

What it does

Outdraw is a booking agent for live-music venues and promoters, live at outdraw.live with no login for the hackathon. Three precomputed demo venues (a jazz bar in New Orleans, an indie room in Brooklyn, a comedy club in London) give results in one click; "Set up a venue" runs the full flow live.

  1. Set up the venue. Name, city, capacity and a few past shows that sold well, in the fields talent buyers already use (act, date, tickets sold, capacity, price, gross). Each typed act is resolved to a Qloo entity and the match is confirmed by the user before any taste call is made.
  2. Build the crowd profile. Six Qloo calls turn the confirmed acts and the venue's location into who the room's crowd is: related artists, genre tags, and the places, films and brands that audience skews toward. Sell-through on past shows weights which acts anchor the profile.
  3. Get candidates and rank them. Qloo recommends acts for that crowd in the venue's region. A deterministic scoring function then orders them. The model never ranks.
  4. Soundcheck. Every pick opens an evidence panel: the chain from inputs to Qloo calls to score, each number labelled as Qloo data or Outdraw's interpretation, with partial and degraded envelopes flagged. A With Qloo / LLM only toggle shows, side by side, what the same question looks like without the taste graph.
  5. Refine. "More emerging", "no tribute acts", "Thursday crowd skews younger": the agent parses the request into constraints and re-runs, and the shortlist shows what moved.
  6. Book. A brief per act with a suggested night format (from age skew), a support act chosen by audience overlap, a local partner, a draft outreach email, a pencil hold on a holds calendar, and a CSV or hold summary to paste into the booking tool the venue already uses.

Business model. Free for one room: one venue, its crowd profile, the top five acts on every shortlist with full Soundcheck evidence, three runs a day. Venue Pro, $39 a month per venue group, adds more venues, the whole shortlist, briefs and outreach, exports and thirty runs a day. That sits beside booking workflow tools priced from $99 a month (Gigwell), as an add-on rather than a switch. Every Pro feature is free during the hackathon and nothing is charged today.

How we built it

Stack. A Next.js 16 front end on Vercel, which never touches Qloo. It proxies to a Python agent: FastAPI plus the Strands Agents SDK on Render, in a container that also carries Node 22 and the pinned Qloo harness. Postgres with pgvector holds venues, profiles, runs, cached envelopes and the private call log; Upstash Redis holds rate limits and daily budgets. The model is Claude Haiku 5.5. JSON Schemas in a shared folder generate the TypeScript types and the Pydantic models, and CI fails if either drifts.

Qloo as tools, not HTTP. Every Qloo request goes through the hackathon harness: qloo exec for the scripted profile build and precompute, and the qloo mcp stdio server behind Strands' MCP client for the agent's own tool calls. One gateway module wraps both transports, so the six-status envelope (ok | empty | needs_input | partial | degraded | error) is normalised, cached, retried within bounds, counted against the daily budget and recorded (redacted) on one code path. Eight workflows are wired as tools: find_tags, describe, recommend, rank, where_popular, compare_audiences, audience_demographics and trends.

The agent loop. One run follows a fixed shape: resolve, confirm, ask, then profile, candidates, signals, rank, explain. Words become Qloo IDs through find_tags and describe; an ambiguous match is confirmed with the user, and no taste workflow is called on an unconfirmed entity. Then the profile is built, candidates are recommended, signals are gathered per act, the scoring function ranks, and the model explains the order it was handed. Each run is capped at eight tool-call steps and the Qloo calls per stage are budgeted.

The agent orchestrates; the score decides. The Strands loop holds eight Qloo tools plus seven of Outdraw's own (confirm entity, build profile, generate candidates, gather signals, score and rank, get evidence, draft brief). The model chooses which calls fit the venue, broadens tags when results are thin, stops to ask when Qloo answers needs_input, and explains afterwards. Ranking is a pure function in agent/draw/ranking/score.py with the weights exported to the UI:

$$ \text{score} = 0.35\,a + 0.20\,p + 0.20\,h + 0.10\,n + 0.10\,s + 0.05\,m $$

where \(a\) is crowd affinity (recommend), \(p\) is past fit (compare_audiences tag overlap with the room's best-selling acts), \(h\) is local heat (where_popular in the venue's region), \(n\) is novelty (months since the act last played the room), \(s\) is size fit (popularity band against the requested tier) and \(m\) is momentum (trends). Each sub-score carries a reference to the exact Qloo call that produced it, which is what Soundcheck renders.

Built for what Qloo actually returns. Every envelope status has a code path, not just ok. empty refines the input and broadens tags. needs_input surfaces Qloo's candidates as a confirmation screen. partial and degraded keep the result, read the summary, and label it in Soundcheck instead of shrinking the list. error is retried only when Qloo marks it retryable, and otherwise reaches the booker as a sentence with the recovery hint. An act past the per-run signals budget is scored on the median of what Qloo measured in that run and marked "no Qloo signal", so it is neither rewarded nor punished for not having been looked up. Affinity values are read as ranks, because Qloo rescales them inside each response.

Bounds enforced in code, not in the prompt. Eight tool-call steps per run, max_tokens on every call, a daily token budget checked before each run, per-run Qloo budgets (profile ≤ 6, candidates ≤ 3, signals ≤ 33, briefs ≤ 5), a daily cap on harness spawns with a Telegram alert at 70%, per-IP sliding windows, and a LIVE_MODE=off kill switch that serves precomputed venues at zero cost. A semantic run cache reuses a result when a request is a paraphrase of an earlier one, but only after exact guards on tags, tier and limits pass.

Hygiene. The key is set through the harness or the environment, never a flag. A gitleaks pre-commit hook and CI job scan the full history with a rule for the Qloo key format. Unit tests run offline against synthetic envelopes (every status, real field names, fake IDs) because Qloo's terms keep its response data out of a public repo. A script regenerates a redacted request-to-result walkthrough of a full run from those fixtures.

Challenges we ran into

The ranking was quietly punishing acts for having been measured. The first real shortlist on the deployed agent put seven unmeasured acts in the top eight, with all 33 signal calls returning ok. Three things were wrong at once. Local heat averaged the heatmap's affinity, which Qloo rescales inside each response from 1.0 to 0.0, so every act's mean was exactly 0.5. Past fit, a Jaccard overlap, sat below 0.5 for 14 of 15 measured acts, so the fixed 0.5 fallback for acts past the signals budget beat them. And momentum was flat for everyone. The fix was to read affinity_rank, and to replace the fixed fallback with the median of the values Qloo measured in that run, so an unmeasured act gains and loses nothing for not having been looked up, the weighted sum stays one formula for every act, and the UI still marks it "no Qloo signal".

The model called two tools in one turn and the ranker won the race. Strands runs a turn's tool calls concurrently by default. In the first live run the model asked for gather_signals and score_and_rank together; ranking makes no Qloo calls, finished first, and scored everything on neutral signals. A sequential tool executor made the model's listed order the only order, and a test now guards it.

"Semantically similar" is not "same question". The plan was to reuse a cached run at cosine ≥ 0.92. Measured on the canonical request text, adding one genre to an otherwise identical request scored about 0.98. A threshold alone would have served a jazz shortlist to a request that asked for hip hop, the one mistake this product must never make. The cosine now absorbs wording only; tags, tier, limits and profile freshness are exact guards in SQL.

A young harness. Version 0.1.26 never returns empty (a no-match search is ok with zero results), omits summary on ok, caps limit at 20, and makes one HTTP request per entity inside a trends batch, so the quota counter undercounts unless you know. The MCP server never sees the key unless the whole process environment is passed to the child. Pinning the version, keeping every call behind one module and recording redacted pairs from day one is what made these findable.

Accomplishments that we're proud of

  • A complete booking flow, from venue setup to outreach draft and export, not a chat box. It runs end-to-end on the live site in the vocabulary talent buyers use (holds, draw, sell-through).
  • Soundcheck. Every number on screen traces to a named Qloo workflow and a specific call, and says whether it is Qloo data or Outdraw's interpretation. The LLM-only toggle makes the difference Qloo makes visible rather than claimed.
  • Ranking that cannot be influenced by the model. A reordered list is restored with a note; a run that never ranked is an error.
  • A repository that passes lint, strict type checks, unit, API, database, Playwright and visual-snapshot tests in CI, with every Qloo-spending path bounded in code.
  • Built to launch, not to demo. Outdraw already has a pricing page and plans, a free tier that keeps Soundcheck, exports into the booking tools venues already pay for, spend caps and a kill switch that make a public URL affordable, and a roadmap that starts with live Opendate, Gigwell and Prism integrations. I want this in front of real talent buyers after the hackathon, and the build was made so that nothing has to be thrown away to get there.

What we learned

  • Affinity values are only comparable inside one Qloo response. Build anything cross-entity on ranks, and say so in the evidence.
  • A fallback is a ranking decision. A "neutral" constant is only neutral if the real values centre on it; they rarely do.
  • The provenance rule ("label what is Qloo and what is yours") shapes the data model, not just the UI. Carrying a call reference on every sub-score from the start made the evidence panel almost free.
  • Branching on all six envelope statuses pays off in the odd cases: a needs_input on a venue name becomes a confirmation screen, a partial signals batch degrades a list instead of shrinking it, an error on resolve reaches the booker as a sentence with a retry flag rather than a blank page.
  • Spend protection is a product feature when the demo is public. Build the caps before the URL.

What's next for Outdraw

  • Artist side. The same tools in a different order: where_popular across regions to find where an act would draw, then venues whose crowds match, as a route with outreach drafts. Cross-region ranking needs a second signal because heat is normalised per call, which is why it was cut from this build.
  • Live integrations with Opendate, Gigwell and Prism in place of the CSV export, once a real venue is signed in; accounts with saved history and alerts ("an act your crowd loves is touring near you").
  • An honest third label. Past fit, local heat and momentum are Outdraw arithmetic over Qloo data; a derived label with the method would be more precise than qloo.
  • Sponsor matching for nights on the same audience data, and a mobile app for touring artists.

Outdraw is not affiliated with Qloo. Taste data from the Qloo API; affinity is not ticket sales, and every pick is presented as a likely draw with its evidence, never a guarantee.

Built With

Share this project:

Updates

Submission history