Inspiration
Seven hundred and eighty-seven and a half million dollars. That is what Fox News paid Dominion Voting Systems to settle one defamation case. Newsmax paid another sixty-seven million over the same claims. Netflix was sued for a hundred and seventy million over a single line of on-screen text in Baby Reindeer. Pepsi spent months and a global media buy on one Kendall Jenner ad and pulled it in twenty-four hours.
Not one of those was caught in a writers' room. Every one was found after release, by a plaintiff, a journalist, or the internet. And in almost every case, the precedent already existed in public reporting before the script did. Somebody just had to know to go and look for it.
That check is real work that studios genuinely do, but it is manual, slow, and depends on whichever reviewer happens to personally remember the relevant case. A 120-page script crossed with five different risk domains is not a review one person can hold in their head, so in practice it is partial and inconsistent.
The obvious fix, "ask an LLM," fails for a specific and fatal reason: a model asked whether a scene resembles a past controversy will confidently produce a case name, a plaintiff, and a year, and some of it will not exist. In a compliance workflow a fabricated precedent is worse than no answer at all, because somebody will act on it. That failure mode is what CineRisk is actually designed around.
What it does
CineRisk audits a screenplay for real-world release risk before anything is shot, and every published finding is backed by a real, retrieved, citable source.
Upload a script (.pdf, .docx, .txt, .fountain) or paste raw text, and:
- Intake segments the document into auditable units using script conventions (INT./EXT. slug lines, character cues), keeping a pointer back to each scene's location so every finding stays anchored.
- Supervisor routes each unit only to the specialists it actually trips. A scene can go to three specialists, or to none. It is a routing decision, not a broadcast.
- Five specialists run concurrently: Cultural Sensitivity, Defamation and Real-Person Resemblance, IP and Plot Similarity, Trademark and Brand Risk, and Historical and Factual Misrepresentation.
- Every suspicion is grounded through Parallel with a two-stage identify-then-verify pipeline. This is not optional enrichment; a flag cannot reach the report without a real search having run for it, and the schema enforces it.
- Aggregator compiles a scene-anchored report. Units reviewed and found clear are marked clear rather than silently omitted.
The report is browsable (scene on the left, retrieved sources on the right) and exports to PDF server-side, including the full grounding audit trail.
The core differentiator is that CineRisk will tell you it could not confirm something. A suspicion no retrieved source substantiates is published as an "unconfirmed suspicion," visually and semantically distinct from a grounded finding, and it does not get to lead with a severity verdict. An audit tool that cannot say "I looked and I did not find it" is one that will eventually invent a lawsuit.
Two optional escalation tiers sit on top of the fast path, per run: deep verification (Parallel Task API, core processor, Basis framework) and deep research (pro processor). Both only re-check flags the fast search failed to ground, and both are off by default because they cost real wall-clock time.
How we built it
Google Cloud: A LangGraph StateGraph running on Gemini 2.5 Flash via Vertex AI (ChatVertexAI from langchain-google-vertexai), with thinking_budget=0. The graph is START -> intake -> supervisor, then a conditional edge that fans out to whichever specialists were routed, with the aggregator acting as a barrier join. The compiled graph is deployed to Vertex AI Agent Engine via agent_engines.LanggraphAgent (resource projects/35260314620/locations/us-central1/reasoningEngines/1012621072123559936), and also runs in-process behind FastAPI for the interactive app.
Parallel: The official parallel-web SDK (AsyncParallel). The Search API (POST /v1/search) is the grounding path on every flag on every run, with no toggle to skip it. The Task API (task_run.execute) powers both escalation tiers. The search tool is additionally exposed as an MCP tool server via FastMCP, so any MCP-speaking client can call the same grounding tool the agents use.
Backend: FastAPI on Cloud Run, SQLModel over Neon serverless Postgres, Upstash Redis (REST SDK) for live per-agent status and search caching, reportlab for PDF export, pypdf and python-docx for extraction. 27 tests, all passing.
Frontend: Next.js 14 with Tailwind and Framer Motion, deployed on Vercel.
Infrastructure: All of it is Terraform with remote state in GCS: APIs, Artifact Registry, a dedicated Cloud Run runtime service account, three Secret Manager secrets mounted as secret env vars with per-secret accessor grants, and the Agent Engine staging bucket. The container is built with Cloud Build.
Challenges we ran into
Vertex 429s that were actually a config bug. Specialists were dying with RESOURCE_EXHAUSTED. It looked like a hard rate limit. It was two things: the project pointed at a regional Vertex endpoint, which carries far tighter per-region Gemini quota than the global endpoint, and location was never actually being passed into the ChatVertexAI constructor, so the setting was inert anyway. Fixing both took a representative run from four 429s and two dead specialists to zero of each.
A bug that made me report the opposite of the truth. Deep research looked like it had never fired. I told the user zero escalations ran. The database showed four. The real defect was that Uvicorn configures only its own loggers, so every app.* INFO line, including the only runtime evidence an escalation had happened, was invisible. An escalation that had run looked exactly like one that never did.
deploy.py could never have worked. LanggraphAgent.__init__ takes no graph argument, so the original call would have raised TypeError before ever contacting GCP. It needed runnable_builder, the current vertexai.agent_engines surface, real pip specifiers instead of -r requirements.txt, extra_packages, and a service account.
Agent Engine reserves the env vars you most want to set. Deployment failed with Environment variable name 'GOOGLE_CLOUD_PROJECT' is reserved. Dropping it and GOOGLE_CLOUD_LOCATION would have been a silent regression, because the runtime injects its own region, which would have dragged every Gemini call back onto the exact per-region quota we had just finished fixing. The fix was a non-reserved CINERISK_VERTEX_LOCATION override.
Cloud Run's defaults would have broken the product silently. Audits run as FastAPI background tasks after the 202 is returned, and Cloud Run throttles CPU to zero once a response is sent. With scale-to-zero, every audit would have frozen the instant it was accepted, working perfectly in local dev and never finishing in production.
A near-miss on secrets. Without a .dockerignore, gcloud builds submit would have uploaded backend/.env, with the real Parallel key, Neon URL, and Upstash token, into Cloud Build's staging bucket. It never reaches the image, so nothing would have looked wrong.
Accomplishments that we're proud of
The grounding actually refuses. The most instructive runs were the ones where a specialist raised a sensible concern, a real search executed, five real results came back, and the verifier still returned grounded=false because none were on point. A naive pipeline would have cited the closest result and sounded authoritative doing it.
Fabrication is structurally impossible, not just discouraged. The verifier cites sources by their index in the real result list. The system looks up the actual title and URL by that index. There is no field in the contract through which an invented citation could travel. It is enforced at the schema level too: a flag marked grounded with no sources raises, and so does a flag with no recorded search query.
It found precedents nobody told it about. Given a fictional cable news anchor defaming a fictional voting machine company, it returned the real Dominion v. Fox News settlement, citing the New York Times and AP. Given a scene about a shark terrorising a beach town, it returned Universal City Studios, Inc. v. Film Ventures International, the actual Jaws copyright case.
It is genuinely deployed and verified end to end, not a localhost demo: a live Vercel frontend calling a live Cloud Run backend, exercising Vertex AI, Parallel, Neon, and Upstash from the deployed environment, with a five-scene audit completing in 67.5 seconds and a single scene in 16.1.
What we learned
"Unproven" and "cleared" are different, and conflating them is the failure mode nobody warns you about. The first version of the report implied an ungrounded flag was fine. It is not fine, it is unknown. Every ungrounded flag now exposes its query and result count, because "results returned but none on point" and "zero results" mean very different things, and only a human can tell whether the search or the risk was the weak link.
The two Parallel paths are a real trade, not an upgrade. On the same cultural sensitivity concern, the Search API took about 3 seconds and grounded 0 of 5 results. The Task API core processor took about 230 seconds and grounded the same concern with five real cited incidents from outlets including the NYT and LA Times. Roughly a hundred times the latency for a genuinely different answer. That measurement is why escalation is opt-in and per-run rather than always-on.
A feature that looks broken can be a gate doing its job. Deep research appeared to do nothing. The wiring was fine; the gate was scoped to high-severity flags and that run had no ungrounded high-severity flags, so nothing was eligible.
Failure isolation belongs in the graph, not the client. A failing specialist now returns an empty flag list and the run degrades. Only an aggregator failure is terminal.
What's next for CineRisk, Pre-Release Content Risk Audit
- Automatic escalation of every ungrounded high-severity flag through the Task API, once the latency is amortised with a parallelised or background second pass that updates the report in place.
- A jurisdiction filter. Precedent is not globally portable, and a US defamation case is weak guidance for a UK or Indian release.
- Cross-scene aggregation. A portrayal that is defensible in one scene can become a pattern across eight, and the current per-unit view cannot see that.
- Serving traffic from the deployed Agent Engine rather than the in-process graph, so the managed runtime handles scaling.
- Multi-language support. Segmentation and grounding searches are currently English only.
- Reviewer feedback as calibration data. Letting a compliance reviewer confirm or reject each flag would produce exactly the labelled set needed to tune the confidence thresholds.
Built With
- gcp
- parallel
- python
- vertex-ai
Log in or sign up for Devpost to join the conversation.