Skip to main content

Media: the agent that checks before it publishes

Track: Media · In the room: Spotify · Seed use case: rights and metadata research agent using Browser Rendering.

Who this is for

Spotify. Effectively no Cloudflare developer-platform footprint today, on a platform whose entire business runs on one unglamorous fact: money follows metadata. A track's composer credits, its rights territory, its ISRC, its licence expiry — get any one of those wrong and either the wrong party gets paid or the right party doesn't, and both are expensive to unwind months later.

The pain is not that this metadata is hard to find. Rights holders, labels and public registries publish most of it — on web pages, not through a clean API you have access to. The pain is that checking it by hand, page by page, at catalogue scale, is a job nobody has enough hours for, so it only happens after something has already gone wrong.

Synthetic data only

Invented tracks, invented rights holders, invented catalogue entries. Nothing that resembles a real artist, a real label relationship, or a real royalty figure goes into a shared account today.

Three agent use cases

1. Rights and metadata research agent — the seed use case

  • Trigger. A new release enters the catalogue, or an existing entry is flagged because a licence term is approaching expiry or a rights claim looks stale.
  • What the agent decides. Whether the catalogue's stated rights holder, territory and expiry match what the rights holder's own public page or a public registry currently claims, and — if they disagree — which one looks more current.
  • What it does — the tools. research_rights loads a public source page, captures a screenshot as evidence, and extracts the claims it can find. flag_discrepancy compares those claims against the catalogue entry and drafts exactly what should change and why.
  • Human in the loop? Always, before anything publishes. This agent never writes to the catalogue directly — it produces a diff and the screenshot that supports it, and a rights operations person accepts, edits or rejects it. Wrong metadata that pays the wrong party is the kind of mistake you want a name attached to, on purpose.

2. New-release compliance sweep

  • Trigger. A batch of new releases lands from a distributor ahead of a scheduled go-live.
  • What the agent decides. Whether each release has complete, internally consistent metadata — matching credits across formats, a territory list that doesn't contradict the licence type, artwork that meets the published spec — before it is allowed to go live.
  • What it does — the tool. check_release runs the completeness and consistency checks and returns a pass, or a specific list of what is missing or contradictory.
  • Human in the loop? Only on failure. A clean pass can proceed on schedule with no human in the loop at all — the whole point is to stop bothering people about releases that are fine, and only escalate the ones that genuinely need a look before the go-live time.

3. Take-down and dispute triage

  • Trigger. A rights dispute or take-down request arrives referencing a specific track or release.
  • What the agent decides. Whether the claim is plausible against the catalogue's own rights record and any public source it can check, and how urgent it is — a claim from the stated rights holder is a different priority than an anonymous one.
  • What it does — the tool. assess_dispute produces a plausibility assessment with the evidence it checked, and a recommended priority band.
  • Human in the loop? Yes, entirely — this agent triages, it never resolves. Taking a track down is a legal act with real consequences either way, and the agent's job stops at making sure the right claim reaches a person quickly with the context already assembled.

The 60-minute cut

Build use case 1. Cut it to: research a public source, produce evidence, propose a diff, a human decides.

In scope:

  1. A hardcoded array of 5 synthetic catalogue entries, each with a track title, a stated rights holder, a stated territory, and a licence expiry date. Deliberately make two of them look outdated against your synthetic "public source."
  2. A tool that uses Browser Rendering against a public page — https://example.com works fine for the hour — captures a screenshot, and extracts a claim from the page content.
  3. A comparison step that turns the catalogue entry plus the extracted claim into a plain-language diff.
  4. A Workflow that stores the proposed diff and pauses on step.waitForEvent() for a rights operator's decision.
  5. A one-page queue: every entry, its current state, its proposed diff, the screenshot, and its approval state.

Out of scope, and say so: real distributor integrations, real registry APIs, territory-by-territory licence logic, authentication, and anything resembling a royalty calculation. If you are modelling actual rights contracts, you have lost the hour.

The demo that wins. Run the check on an entry with a stale expiry date and show the screenshot of the public page next to the catalogue's current value — the evidence, not the verdict, is what makes a rights person trust it. Approve the diff live and show the catalogue entry update, with the screenshot still attached as the reason.

Primitives — exactly three

  1. Browser Rendering. The honest media primitive: the rights holder's page and the public registry exist, but neither offers you a clean API for this, and a plain fetch against a modern page gets you a JavaScript shell with no rights claim in it. Rendering the page and capturing a screenshot alongside the extracted text gives a human reviewer something they can check in one glance, not just a model's assertion.
  2. Workflows. A metadata correction has to be exactly-once — a retry must not apply the same diff twice — and it has to be able to wait for a rights operator who works a different shift than the sweep that found the issue. step.waitForEvent() holds that open for as long as it takes.
  3. Persistent Durable Object state. A catalogue entry's rights record accumulates over its life — label changes, territory renewals, prior disputes — and an Agent instance addressed by catalogue id is the natural place for that history to live, strongly consistent, hibernating when nothing is happening to that track.

Skip schedules as the trigger here — a new release entering the catalogue, or a flag being raised, is an event this agent reacts to, not a clock it watches; a sweep is a reasonable production feature but not the hour-one story. Skip AI Search — you are checking external public sources against your own catalogue record, not retrieving from an internal document corpus.

Paste-ready starter prompt

Scaffold the starter first:

npm create cloudflare@latest -- team-media-rights-research --template cloudflare/agents-starter
cd team-media-rights-research

Then paste this into your coding agent:

I'm working in a fresh clone of Cloudflare's agents-starter template. The agent code is in
src/server.ts and the React client is in src/client.tsx. I have 60 minutes. Keep the
existing chat agent working — extend it, don't replace it.

Build a rights and metadata research agent that never publishes a change without approval.

1. Add a hardcoded array of 5 synthetic catalogue entries to src/server.ts. Each has:
trackId, title, statedRightsHolder, statedTerritory, and licenceExpiry (a date string).
Make two of them have a licenceExpiry in the past. Invented data only.

2. Add a tool called research_rights. Given a trackId, it uses Cloudflare Browser Rendering
to load https://example.com, captures a screenshot, and extracts whatever text content is
present. Store the screenshot key and the extracted text against that trackId. If Browser
Rendering fails for any reason, catch it, carry on, and mark the entry
"research unavailable" — this must never crash the check.

3. Add a tool called flag_discrepancy. Given a trackId and the research_rights output, use a
Workers AI model to compare the catalogue's stated fields against the extracted text and
return JSON: hasDiscrepancy (boolean), proposedRightsHolder, proposedTerritory,
proposedExpiry, and a two-sentence explanation of what changed and why. If the extracted
text has nothing usable, hasDiscrepancy must be false — never invent a discrepancy.

4. Add a Cloudflare Workflow called RightsCorrection in a new file, with steps as step.do():
a) research — call research_rights and flag_discrepancy, store both results
b) await_approval — ONLY if hasDiscrepancy is true, use step.waitForEvent() with a
generous timeout, waiting for an event of type "correction-decision". If
hasDiscrepancy is false, skip straight to done with no change proposed.
c) apply — record whether the correction was applied, declined, or not needed, and by
whom
Import WorkflowEntrypoint, WorkflowStep and WorkflowEvent from "cloudflare:workers", and
add the workflow binding to wrangler.jsonc.

5. Store the current state of every catalogue entry in the Agent's SQLite storage using
this.sql, including the screenshot key and the last research result, keyed by trackId.

6. Add routes:
POST /research/:trackId starts a RightsCorrection workflow for that track
GET /catalogue HTML page: every track, its stated fields, proposed diff (if
any), screenshot link, and approval state
POST /decide/:trackId sends the "correction-decision" event with approved true or
false and an operator name from the body

Requirements: TypeScript. Route every Workers AI call through AI Gateway by passing
{ gateway: { id: "agenthack" } } as the options argument. Never write a proposed correction
into the "current" fields without an approved decision. Run `npx wrangler types` after
changing wrangler.jsonc. Then deploy with `npx wrangler deploy` and give me curl commands
that walk one track through research, flag, approve, and apply.

The Worker name team-media-rights-research follows the shared-account rule <team_prefix>-<slug>. Eight teams deploy into one account today, so if the Scoping agent issued you a different prefix, use that one instead.

What "production" looks like

  1. Point it at your real rights-holder and registry sources, plural. One source is a demo; production means checking a catalogue entry against every source that could disagree with it, and surfacing the disagreement itself as a signal, not just picking one answer.
  2. Make every approval carry a named identity and keep the screenshot forever. The screenshot is your evidence of what the public source said at the moment of the decision — sites change. Retaining it next to the approver's identity is what makes a metadata correction defensible a year later, not just plausible today.
  3. Turn the sweep from reactive to scheduled. Once the research and diff logic is trustworthy, running it on a clock across the whole catalogue — not just on new releases — catches rights that go stale quietly, which is the failure mode nobody notices until an invoice is wrong.

Next: the Level Up page explains each primitive, and the cheat sheet has the snippets. Both are in the sidebar.