Inspiration

A company gets a letter saying it infringes a patent. The first real question is whether somebody had already invented the thing. Most of them never find out.

Finding out means a prior-art search: a specialist, billed by the hour, over days or weeks. Defending is expensive even at the small end, and AIPLA puts the median cost of a patent suit with less than $1 million at risk at $600,000, a case through trial above $3 million. So the companies most often targeted are exactly the ones for whom looking costs more than folding. They settle without ever learning whether the patent would have survived.

There are more of them every year. Non-practicing entities filed 55.4% of US patent cases in 2025, up from 51.8% the year before, and drove 90.3% of high-tech patent litigation (Unified Patents, Patent Dispute Report 2025 in Review). They added 2,236 defendants that year, an 18.7% increase.

I wanted to know what happens to that if the search runs itself while everyone is asleep.

What it does

A demand letter arrives. You hand Nightshift the letter itself, a PDF or a photo of the page, and Gemini reads the document: who is asserting, which patent, and whether it is an assertion at all. Nothing is typed in.

That one event starts the job, and nothing after it is guided. Nightshift splits claim 1 into its limitations, works out the priority date and drops every patent that cannot legally be prior art against it, ranks the survivors by embedding distance, spreads the closest 2,000 across ten Cloud Run tasks, and has Gemini 3.5 Flash read every one of them against every limitation. One call per candidate. Not a shortlist of fifty for a human to skim. Thousands, read.

It coordinates BigQuery, Vertex AI, Cloud Run Jobs and Firestore to do it, deciding at each step what the next one should be, and it runs unattended. Close the tab and the work carries on without you.

What comes back is a claim chart: every limitation of claim 1 mapped to the prior art that teaches it, with the supporting passage quoted verbatim from the reference. It states what each reference discloses. It never states that a claim is invalid, because that is a question for licensed counsel and ultimately for a court or the PTAB.

How I built it

Measured cost decided the architecture before a line of product code was written. Querying the public patent tables per request is not survivable: one description lookup scans 1,052 GB and one target fetch joining claims scans 40 GB, because those tables are neither partitioned nor clustered on patent id. So the corpus is materialized once into a clustered table, which took a target fetch from 40.16 GB to 0.20 GB, about 200x. All of it measured by dry run rather than estimated.

The orchestrator and the workers are separate processes in one image. A Cloud Run service splits claim 1 with Gemini, applies the priority-date gate, ranks the eligible corpus by cosine distance on gemini-embedding-001, and materializes the top 2,000 into a per-run BigQuery table. It then launches a Cloud Run Job with ten tasks. Each task claims its own slice with MOD(rank, CLOUD_RUN_TASK_COUNT) = CLOUD_RUN_TASK_INDEX and calls Gemini once per candidate.

Tasks never talk to each other. All state lives in Firestore: a run document, a shard document per task, and a finding document per hit. Whichever task finishes last observes that every shard is done and closes the run. No coordinator, no leader election, no shared memory.

Credentials never appear in code or in the image. The service and the job run as a service account and reach Vertex AI, BigQuery and Firestore through Application Default Credentials.

An ADK agent sits on top for questions about a finished run, with four tools. It is instructed to refuse validity questions and point at the claim chart instead, and there is a test that fails if it ever answers one directly.

Challenges I ran into

Screening the wrong question cost me a day. An examiner's rejection applies to the claims as they stood at that office action, and the applicant then amends to overcome it, so the issued claim is by construction the version that survived. Asking whether a reference anticipates the issued claim returned zero hits on a pair the examiner had actually applied. Screening now judges materiality; limitation-by-limitation mapping belongs in the chart.

Firestore refused every write from inside the container while working locally, failing with an invalid database id of %28default%29. Version pinning did not fix it. Using a named database did.

The retrieval upgrade broke my favourite demo. Re-embedding with gemini-embedding-001 took anticipation recall at 2,000 candidates from 54.0% to 83.9% and the median rank of an examiner's reference from 1,230 to 128, which meant the case I had been showing moved from depth 548 to rank 16 and stopped demonstrating anything. I replaced the anecdote with the population result instead.

And the system was wrong about its own cost by a factor of four. It computed spend from usage_metadata.candidates_token_count, which is only the visible JSON. Gemini 3.5 Flash thinks by default, reports those tokens separately, and Vertex bills them at the output rate. Every test passed and the arithmetic was right; the input to the arithmetic measured the wrong half of what was billed. I found it in a billing alert, not in a test.

Accomplishments that I'm proud of

The accuracy number is graded by USPTO examiners, not by me. The USPTO publishes which references an examiner applied in a rejection and against which claims. So the test is: hide the file history, run the agent, and see whether it independently re-finds the reference the examiner used.

Blinded, with the reference's number, title, assignee and dates stripped from the prompt: on references an examiner applied to anticipate, it finds them 97.5% of the time (n=40). On references applied for obviousness, 92.5% (n=40). On references the examiner never cited, drawn from the same corpus and passing the same date gate, it stays quiet 81.2% of the time (n=80). That control is what makes the first two numbers mean anything.

And the number that explains why the architecture looks like this: ranking with the strongest embedding available, a top-50 shortlist still misses 59.7% of the references examiners actually applied. Every commercial patent tool ranks a corpus and shows a person the top few dozen. No amount of ranking quality rescues that design. Reading 2,000 finds 83.9%.

But the result I am most pleased with is one the scoring counts against me. On the demo patent the examiner applied US 7,606,730 at depth 218. At depth 1,129 Nightshift found US 6,564,189, filed eight years before the priority date, absent from the examiner's citations entirely, teaching more of the claim than the examiner's own reference. Because accuracy here is measured against the examiner, that reference scores as a MISS. The published numbers understate the tool by exactly the amount an examiner's search understates the art, and I would rather report it that way than move the goalposts.

The agent also knows what it costs and refuses to spend without saying so. An agent that fans out across a corpus can spend real money on your behalf, and the hosted one is public. Reading is open to anyone. Starting a new search is not, because it costs about $34, so it takes a token and is capped per day, claimed in a Firestore transaction so two concurrent presses cannot both pass. A patent already searched returns its finished run instead, which is both free and a faster answer.

What I learned

That measuring a pipeline in stages is what tells you where to spend. Splitting recall into "did the prefilter keep the reference" and "did the model then flag it" showed the loss was almost entirely in retrieval: the model was missing 2.5% of what reached it while the prefilter dropped 46%. That pointed at re-embedding the corpus, which is where the 54.0% to 83.9% came from.

And that a system can be wrong about itself in a direction no test catches. The cost bug was not a broken calculation, it was a correct calculation over an input that measured the wrong thing. The general form is that a number a system computes about itself is a claim rather than evidence, and it needs a source outside the system to check it against. Which is the same reason the accuracy figures here are measured against USPTO examiner citations rather than against my own judgement of what looks relevant.

What's next for Nightshift

Pre-grant publications are 73% of what examiners cite, and the corpus already holds 413,323 of them but the pipeline does not screen them yet. That is the largest available gain in recall, and it is a decision about money rather than engineering: the code does not care which table a candidate came from, but reading another 413,323 documents costs what it costs, and I would rather ship a measured number over 171,695 than an unmeasured one over half a million.

Depth is the other axis. Every figure here is quoted at 2,000 candidates because that is what I could afford to measure repeatedly. Recall rises with depth by construction, so 5,000 and 10,000 are straightforward and simply cost more. Where it stops paying is measurable rather than arguable.

Then the remaining CPC classes, and non-patent literature, which is the largest blind spot in the current design: a journal paper or a product manual can invalidate a claim and this corpus contains neither.

Further out, the thing this shape is actually for. IPR petition grounds under 35 U.S.C. 311(b) restrict inter partes review to patents and printed publications, which is exactly what a patents-first corpus holds, and a petition is built from claim charts. The chart Nightshift already produces is the same artifact in a rougher form.

Built With

  • artifact-registry
  • bigquery
  • cloud-build
  • cloud-run
  • cloud-run-jobs
  • docker
  • fastapi
  • firestore
  • gemini
  • gemini-3.5-flash
  • gemini-embedding-001
  • google-adk
  • google-cloud
  • google-genai
  • patentsview
  • playwright
  • pytest
  • python
  • uspto
  • uvicorn
  • vertex-ai
Share this project:

Updates