Inspiration

An outdoor ad is seen by the people who live and spend time around it, so the wall should be chosen, and the line written, for what those people love. Planners buy billboards by traffic counts and neighbourhood reputation. Qloo has taste data aggregated by place, which lets us ask a question a language model cannot answer on its own: where in this city does this brand's audience live, block by block, and what does each block over-index on compared with the whole city?

Out-of-home advertising (billboards, bus shelters, street screens) is a market where taste by place is the whole decision. We built Wallspace to plan it by taste only: never by identity, and never from anyone's personal or device location.

What it does

You describe your audience by what it loves ("stand-up comedy specials, 90s sitcoms, southern hip-hop"), set a budget and flight dates, and an agent returns a ranked, budgeted plan of billboards, bus shelters and digital screens in Atlanta. Every face comes with:

  • its Qloo evidence;
  • three copy lines written for its block;
  • events nearby during the flight;
  • a site-photo check.

No neighbourhood takes more than 30% of spend.

Media owners get the other side: an inventory and a public audience card for each face. The card shows what the people around the face love, ranked against Atlanta, and which brands' fans over-index there, so the owner knows whom to pitch. Pitch sheets go out from the app.

The sample plan, for the sample brand Porchlight Comedy Tour, was built from a live run. The agent:

  • scored 86 faces;
  • picked 8 of them for $33,189 of a $40,000 budget;
  • dropped ATL-0101, because its site photo shows a distant board, partly hidden by trees, poles and wires, that fills 4% of the frame.

Plans go out by Gmail, as the plan e-mail and the media-owner pitch sheet. Replies come back sorted as confirmed, counter-offer, unavailable or question. Slack approval, Outlook, HubSpot, Google Sheets and Google and Outlook calendars are built in and connectable from the Connections page. An OpenOOH deal-request file is ready for screen networks.

First pilot live in Atlanta: 86 faces, 46 of them at real OpenStreetMap billboard positions and the rest on real streets; Porchlight Comedy Tour is the sample brand.

How Qloo is used

Qloo decides where the plan goes and what each face says. Every call is made server-side, live, through one client (src/server/qloo/client.ts).

# Qloo call What it decides Code
1 GET /v2/tags with filter.tag.types=urn:tag:genre:media, then urn:tag:genre:music, take=100 What the brief means. The tag names are embedded once with Qwen3 Embedding 8B, and the brief's phrases are matched to the closest Qloo tag IDs by cosine similarity. In the sample run the brief became Comedy, Hip-Hop, Rhythm & Blues and three more Qloo tags. agent/qloo-adapter.ts, agent/vocab.ts
2 GET /search with types=urn:entity:tv_show,urn:entity:movie,urn:entity:artist, take=3 Which named loves become entity seeds. This is the agent's qloo_search tool. A hit counts only if its popularity is at least 0.5 and its name matches the phrase. In the sample run "90s sitcoms" resolved to That '90s Show. agent/qloo-adapter.ts, agent/planner.ts
3 GET /v2/insights with filter.type=urn:heatmap, filter.location=POLYGON(...) (Atlanta), signal.interests.entities and signal.interests.tags set to the seeds Where the audience lives. Qloo returns a taste affinity for each geohash cell. Every face is scored by inverse-distance weighting of the cells within 1,500 m of it, times a visibility weight. A face with no cell in reach is dropped. This is the agent's qloo_heatmap tool. Sample run: 5,070 cells from 7 seeds. qloo/endpoints.ts, scoring/affinity.ts
4 GET /v2/insights with filter.type=urn:tag, signal.location=POINT(lng lat) of the face, take=50 What each block over-indexes on. These tags are the evidence on the face sheet and the only material the copy model writes from. Sample run: 13 blocks, 65 tags. qloo/endpoints.ts, agent/pipeline.ts, agent/copy.ts
5 GET /v2/insights with filter.type=urn:tag, signal.location.query=Atlanta, take=50 The city baseline. Lift is the tag's rank across Atlanta divided by its rank at the block, so "Southern Hip Hop 18x" means it ranks 18 times higher at ATL-0046 than across the city. Identity-style namespaces are removed before ranking. qloo/endpoints.ts, qloo/lift.ts
6 GET /v2/insights with filter.type=urn:entity:brand, signal.location=POINT(lng lat), take=10 Brands to pitch. The media owner's card lists the top 6 brands whose fans over-index around the face. For ATL-0020 in Midtown: High Museum of Art, Atlanta United FC, Atlanta Falcons, Atlanta Braves, NPR and Atlanta Hawks. qloo/card.ts
7 Call 5 with bias.trends=high, compared with the plain call Taste shift. A nightly re-scan marks a tag as rising when the trend-biased Atlanta list ranks it higher than the plain list. Nemotron 3.5 Lightning then flags the faces whose top tags are rising. agent/qloo-adapter.ts, workflows/rescan-core.ts

Every response is validated with zod against the shapes we observed. Real responses are cached in Cloudflare KV. If Qloo cannot answer, the screen shows an error with a Try again button. Wallspace never invents Qloo data.

Qloo receives taste tags, entity IDs and map positions only: the Atlanta area and the point of each billboard face. It never receives a person's name, e-mail, device or location history.

What makes it Qloo-powered

Without Qloo's taste-by-place data Wallspace cannot exist. Asked where comedy fans live in Atlanta, a language model can only repeat neighbourhood stereotypes. It has no data on how taste spreads block by block, and no way to tell what one block over-indexes on against the city.

In Wallspace, Qloo makes the decisions:

  • Which faces are bought. Every face is scored by the Qloo heatmap affinity of the cells around it.
  • What each face says. The copy model is given only the block's Qloo tags, ranked by lift against Atlanta.
  • What the brief means. The free-text brief is mapped onto Qloo's own tag vocabulary and resolved to Qloo entities before anything is scored.
  • Whom a media owner pitches. The card's brand list comes from Qloo brand insights at the face's point.
  • When a plan needs another look. The nightly re-scan reads Qloo's trend-biased tags for Atlanta.

The agent's two tools are Qloo tools. Take Qloo out and there is no map, no ranking, no evidence and nothing for the copy to say.

How we built it

  • Agentic tool use. Each plan is a Cloudflare Workflow. NVIDIA Nemotron 3.5 Super VL is the planning agent. It gets two Qloo tools by function calling, qloo_search and qloo_heatmap, and decides which loves to resolve and when to map. In the sample run it chose three searches, then the heatmap. A fixed spine runs the same calls if tool calling fails twice. The same model then chooses which site photos to inspect and reads them for sightline, clutter and share of frame.
  • Copy from Qloo tags. Nemotron 3.5 Lightning writes three lines of at most 7 words per face from that block's Qloo tags. A line over 7 words is rejected and redrafted, never trimmed. Every line passes a taste-only guard.
  • Review. Nemotron 3 Ultra 550B reviews the whole plan. In the sample plan it flagged four faces for clashing copy on nearby boards: ATL-0021 and ATL-0014, 1,106 m apart, and ATL-0046 and ATL-0019, 235 m apart. All four were redrafted.
  • Sign-off. NVIDIA NeMo Guardrails signs off every line before print, as a Nebius Serverless Job. For the sample plan, job wallspace-nemo-sign-off-202610302317 passed 24 of 24 lines.
  • Supporting stack.
    • All four models run on Nebius Token Factory: three Nemotron models, plus Qwen3 Embedding 8B for matching briefs to Qloo tags.
    • Tavily finds events near each face during the flight.
    • The app is Next.js on Cloudflare Workers, with D1, KV, R2, Durable Objects for live run streaming, and cron triggers.
    • The map is MapLibre.
  • Run trace. Every step is logged with its model or tool, time, tokens, cost and request ID. The sample run has 36 steps and 21 model calls. It took about 67 seconds and cost $0.0099 in model calls.

Challenges we ran into

  • Response shapes. Qloo's endpoints do not share one shape. /search and /entities return a bare results array with types[]. Insights tag rows use tag_id and subtype, while /v2/tags rows use id and type. Insights lists reject take above 50. We probed each endpoint, wrote down the shapes we saw, and validate every response against them.
  • "New York" resolves to the state. signal.location.query=New York returned New York State, which would make a city baseline state-wide. We check the resolved locality for every city we use; "Atlanta" resolves to the city.
  • The heatmap ignores take. One brief returned 5,070 cells. We stopped sending take, score faces against every cell, and draw the strongest 400 on the map.
  • Affinities are popularity-like. Tag affinities around a point sit between 0.98 and 1.0, and the scale shifts between queries, so raw numbers cannot tell two blocks apart. We rank each block's tags against the same query for the whole city and show the lift. Brand affinities crowd just under 1, so the card shows a "count of nines" score.
  • Explainability without names. feature.explainability returns anchor IDs and scores, with no names, and every score is 1 when there is a single anchor. Our first "matched because..." line came out empty. Lift against the city baseline became the evidence instead.
  • Search needs a type filter. Without one, "90s sitcoms" returned a comedy club in another country and "southern hip-hop" returned a locality. We restrict search to TV shows, films and artists and require a name match.
  • A careful credit budget. The hackathon key has a monthly pool. A KV cache of real responses, keyed by endpoint and parameters, means a repeated question never costs a second call.

Accomplishments that we're proud of

  • Every face in the sample plan traces back to Qloo: its heatmap cell, its block tags with lift against Atlanta, and the copy written from those tags. The face sheet links each number to the run step that produced it.
  • The agent really uses tools. Nemotron 3.5 Super VL decides the Qloo calls by function calling, and the trace shows every choice.
  • Taste only, measured:
    • In our latest evaluation the live guard blocked 15 identity-targeting briefs and wrongly blocked no taste briefs.
    • The NeMo Guardrails red-team job caught every attack, with no false positives.
  • A complete product for both sides of the market, planners and media owners, with sign-up, workspaces and a public audience card a media owner can send.

What we learned

  • Taste by place only means something against a baseline. "This block likes live music" says nothing until you know it ranks 29 times higher there than across the city.
  • Agents need narrow, well-described tools. Two Qloo tools with clear rules gave reliable function calling, and a fixed fallback keeps every run safe.
  • Probe the API before building on it. Most of our Qloo bugs were assumptions about response shapes, not logic.

What's next

  • More cities, each with its own checked city baseline.
  • Live inventory from media owners, alongside the Atlanta pilot faces.
  • Brand-led briefs: start from a brand's own Qloo entity, so a planner can ask for "fans of this brand".
  • Campaign measurement, and hand-off to screen networks from the OpenOOH file Wallspace already prepares.

Try it (testing instructions)

Wallspace is free to use. No test credentials are needed.

No account (about 3 minutes)

  1. Open https://wallspace.pages.dev/sample: the plan for Porchlight Comedy Tour, a sample brand, built from a live run.
  2. The Qloo heatmap. The map shows Qloo's taste affinity for "stand-up comedy specials, 90s sitcoms, southern hip-hop" across Atlanta, with the 8 picked faces numbered.
  3. Block tags with lift. In the picks table, open ATL-0046 (Home Park). Its face sheet shows:
    • the block's Qloo tags ranked against Atlanta (Southern Hip Hop 18x, Dirty South 7x);
    • the heatmap affinity of its cell;
    • three copy lines written from those tags. Each evidence row links to its run step.
  4. The agent's steps. Open https://wallspace.pages.dev/sample/run. Nemotron 3.5 Super VL chooses qloo_search three times, then qloo_heatmap. Then come:
    • the heatmap (5,070 cells);
    • scoring of 86 faces;
    • the photo read that drops ATL-0101;
    • block tags for 13 blocks;
    • copy, Ultra's review and the redrafts.
  5. Brands to pitch. Open https://wallspace.pages.dev/faces/ATL-0020, the public audience card a media owner sends. It shows what the people around the face love against Atlanta (Live music venue 29x) and the brands whose fans over-index there.

Your own plan

  1. Sign up at https://wallspace.pages.dev/signin?mode=sign-up with any e-mail and a password. Sign-up is free and instant, with no e-mail verification. Each account gets its own workspace and a short onboarding.
  2. Open New brief. Describe an audience by what it loves, set a budget and flight dates, and run it. The run streams in step by step: Qloo tag matching, the agent's Qloo searches, the heatmap, block tags with lift, photo reads, copy and review.
  3. Try a brief that targets people by identity rather than taste. Wallspace refuses it and suggests a taste-based version.

Team

  • Marsela: Design and engineering: built the product.
  • Cipher Kuma: Product ideation, planning and brainstorming.

Qloo submission requirements

  1. Functional demo: https://wallspace.pages.dev, and the no-account sample plan at https://wallspace.pages.dev/sample.
  2. Public code repository: https://github.com/RealMarsela/wallspace-qloo
  3. What it does and what makes it Qloo-powered: see "What it does", "How Qloo is used" and "What makes it Qloo-powered" above.
  4. Externally hosted: live on Cloudflare Workers and Cloudflare Pages.
  5. Open-source licence: MIT, shown in the repository's About section.

Built With

  • cloudflare-d1
  • cloudflare-durable-objects
  • cloudflare-kv
  • cloudflare-pages
  • cloudflare-r2
  • cloudflare-workers
  • cloudflare-workflows
  • composio
  • gmail-api
  • maplibre
  • nebius-serverless-jobs
  • nebius-token-factory
  • nemo-guardrails
  • next.js
  • nvidia-nemotron
  • opennext
  • openstreetmap
  • python
  • qloo
  • qloo-insights-api
  • qwen3-embedding
  • slack-api
  • tavily
  • typescript
  • zod
Share this project:

Updates

Submission history