Skip to main content

Media: the agent that checks before it publishes

Track: Media · In the room: pre-assigned teams · Seed use case: rights and metadata research agent using Browser Rendering.

Who this is for​

Teams from a streaming media platform, with effectively no Cloudflare developer-platform footprint today, on a business built on one fact: money follows metadata. Get a composer credit, rights territory, ISRC or licence expiry wrong and the wrong party gets paid or the right one doesn't — expensive to unwind months later. The metadata isn't hard to find: rights holders, labels and registries publish most of it, just on web pages, not a clean API. The pain is that checking it by hand at catalogue scale is a job nobody has hours for, so it only surfaces after something's already gone wrong.

Synthetic data only

Invented tracks, rights holders, and catalogue entries. Nothing resembling a real artist, label relationship, or 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 entry is flagged because a licence term nears expiry or a rights claim looks stale.
  • Decides. Whether the catalogue's stated rights holder, territory and expiry match the rights holder's public page or a registry, and which looks more current if they disagree.
  • Tools. research_rights loads a source page, captures a screenshot as evidence, and extracts the claims it finds; flag_discrepancy compares them against the catalogue entry and drafts what should change and why.
  • Human in the loop? Always, before anything publishes. The agent never writes to the catalogue — it produces a diff and supporting screenshot for a rights operations person to accept, edit or reject. Wrong metadata that misdirects payment is a 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.
  • Decides. Whether each release has complete, internally consistent metadata — matching credits across formats, a territory list not contradicting the licence type, artwork meeting spec — before go-live.
  • Tool. check_release runs completeness/consistency checks and returns a pass, or a specific list of what's missing or contradictory.
  • Human in the loop? Only on failure. A clean pass proceeds on schedule, no human at all — the point is only escalating releases that need a look before go-live.

3. Take-down and dispute triage​

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

The 60-minute cut​

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

In scope:

  1. 5 synthetic catalogue entries (title, rights holder, territory, licence expiry) — make two look outdated against your synthetic "public source."
  2. A tool using Browser Rendering against a public page (https://example.com works fine) that captures a screenshot and extracts a claim from it.
  3. A comparison step turning the catalogue entry plus 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, current state, proposed diff, screenshot, approval state.

Out of scope, and say so: real distributor integrations, real registry APIs, territory-by-territory licence logic, authentication, anything resembling a royalty calculation.

The demo that wins. Run the check on a stale-expiry entry and show the screenshot next to the catalogue's current value — evidence, not verdict, earns a rights person's trust. Approve the diff live and show the catalogue update, 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 a clean API, and plain fetch gets you a JS shell with no rights claim in it. A screenshot alongside the extracted text gives a reviewer something to check in one glance, not just a model's assertion.
  2. Workflows. A metadata correction must be exactly-once — a retry mustn't apply the same diff twice — and must wait for a rights operator on a different shift than the sweep. 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. An Agent instance addressed by catalogue id is the natural home for that history: strongly consistent, hibernating when nothing is happening to that track.

Skip schedules as the trigger — a new release or a flag is an event this agent reacts to, not a clock it watches (a good production feature, not hour-one). Skip AI Search — you're checking external public sources against your own catalogue record, not 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 an entry against every source that could disagree, and surfacing the disagreement itself as a signal rather than picking one answer.
  2. Every approval carries a named identity; keep the screenshot forever. The screenshot is evidence of what the public source said at the moment of decision, since sites change — retaining it next to the approver's identity makes a correction defensible a year later.
  3. Turn the sweep from reactive to scheduled. Once the logic is trustworthy, running it on a clock across the catalogue catches rights that go stale quietly — the failure mode nobody notices until an invoice is wrong.

Next: Level Up explains each primitive, the cheat sheet has the snippets.