A patent demand letter takes a professional search and weeks to answer. Nightshift ranks 171,695 patents, reads the 2,000 closest against every claim limitation, and hands your attorney the answer for $34.
Answering one question, was this already invented, means a prior-art search: a specialist, billed by the hour, over days or weeks. Most small companies never ask, because asking costs more than settling. Nightshift asks, autonomously, in the background.
Demo video (4 min): https://www.youtube.com/watch?v=m0k530DiYTg
Live: https://nightshift-1015687974010.us-central1.run.app
Write-up: https://jonathanandrei.com/blog/nightshift-agentic-prior-art-search/
Built for the All Things Agentic Hackathon, track: The Taskmaster.
PRIOR ART EVIDENCE DOSSIER, NOT A LEGAL OPINION. Nightshift produces evidence for review by licensed patent counsel. It reports what a reference discloses. It does not decide whether a claim is invalid.
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). They added 2,236 defendants that year, an 18.7% increase.
Defending is expensive even at the small end: AIPLA puts the median cost of a suit with less than $1M at risk at $600,000, and a case through trial above $3M. The first real question in any of them is whether the asserted patent was already invented by someone else, and answering it properly means a professional prior-art search that is itself priced out of reach of exactly the companies most often targeted.
Every existing tool is a retrieval system: it ranks a corpus and shows a human the top few dozen results.
That design has a ceiling, and it is measurable. Ranking this corpus with
gemini-embedding-001, the strongest embedding available, a top-50 shortlist
still misses 59.7% of the references a USPTO examiner actually applied to
anticipate a claim. Better ranking does not fix it. Reading further down the
list does.
So Nightshift is a judgment system. A vector pass narrows the corpus, then Gemini reads two thousand candidate references, not fifty, deciding for each one whether it discloses each limitation of the asserted claim. The prefilter is not asked to be right. It is only asked not to lose the answer.
| Depth read | Anticipation references found |
|---|---|
| Top 20 | 26.6% |
| Top 50 (what a person is shown) | 40.3% |
| Top 100 | 48.4% |
| Top 500 | 71.0% |
| Top 2,000 (what Nightshift reads) | 83.9% |
All numbers produced by scripts in this repository against public USPTO data on BigQuery. Nothing here is seeded or simulated.
Whether the vector pass keeps the reference an examiner actually applied, out of
171,695 candidates, using gemini-embedding-001 at 768 dimensions:
| Citation category | n | @1k | @2k | @5k | @10k | median rank |
|---|---|---|---|---|---|---|
| X (anticipation, §102) | 124 | 77.4% | 83.9% | 91.1% | 93.5% | 128 |
| Y (obviousness, §103) | 973 | 59.7% | 67.9% | 77.3% | 83.7% | 482 |
This replaced Google Patents' embedding_v1 (64 dimensions, from an unpublished
model with no callable endpoint) after measuring both on the same gold pairs.
Anticipation recall at 2,000 candidates went from 54.0% to 83.9%, and the median
rank of an examiner's reference from 1,230 to 128. Both tables are kept so the
comparison stays reproducible: scripts/gate_recall_compare.sql.
Blinded. The model never saw the reference's patent number, title, assignee or dates, so it could not lean on anything it may have memorized.
| Set | n | Flagged as material |
|---|---|---|
| X, examiner applied as anticipation (§102) | 40 | 97.5% |
| Y, examiner applied as obviousness (§103) | 40 | 92.5% |
| Control, never cited by the examiner | 80 | 18.8% |
The control is what makes the other two mean anything: recall alone is trivially gamed by flagging everything, so the same screener was run over references the examiner did not cite, drawn from the same corpus and passing the same priority-date gate.
Composed with prefilter recall, at the 2,000 candidates the deployed service actually screens:
| Category | Prefilter @2k | x Screening | = End to end |
|---|---|---|---|
| X (anticipation) | 83.9% | 97.5% | 81.8% |
| Y (obviousness) | 67.9% | 92.5% | 62.8% |
On four of every five patents where a USPTO examiner found an anticipating reference, Nightshift independently finds that same reference without ever seeing the file history.
The remaining loss is still in retrieval rather than judgment: at 2,000 candidates the prefilter drops 16.1% of anticipation references before the model sees them, and the model then misses 2.5% of what reaches it. Upgrading the embedding cut that retrieval loss from 46.0% to 16.1%, and what is left sits in the same stage.
Full method, denominators and limits: ACCURACY.md.
Run 10163121-c398c4bc, a real Cloud Run execution.
US 10,163,121 "System and method for targeted marketing and consumer resource management" was prosecuted against US 7,606,730 "System and method for a multiple merchant stored value card", which a USPTO examiner applied as a category-X anticipation rejection.
Blinded, without ever seeing the file history, Nightshift independently surfaced that same reference:
| In CPC G06Q | 171,694 |
| Dropped as not prior art | 126,787 |
| Eligible after the priority-date gate | 44,907 |
| Read by Gemini | 2,000 |
| Closest art | 39 |
| Depth of the examiner's reference | 218 |
| Wall time | ~4 minutes across 10 Cloud Run tasks |
| Cost | $34.57 |
Against a professional search billed by the hour over days or weeks.
Depth 218 is past every shortlist a person is shown. The two patents share almost no vocabulary: one calls itself targeted marketing, the other a stored value card, and both describe accumulating loyalty value and redeeming it at a merchant point of sale. That is why keyword search misses it and why the judgment stage has to read rather than match.
The target also demonstrates the eligibility gate by itself. It was filed in 2017 but claims priority to 2006. Filtering on filing date would have searched eleven years of art that is not prior art at all.
The case was chosen by scripts/pick_demo_target.py, which scores candidates on
depth and on how much of the claim the chart actually carries, rather than by
picking one that looked good. Scores are in eval/demo-candidates.json.
Re-finding what an examiner already found proves the method works. Finding what the examiner missed is the product, and this run did.
| Reference | Depth | Taught outright | In substance | Not taught |
|---|---|---|---|---|
| US 7,606,730, the reference the examiner applied | 218 | 2 | 5 | 0 |
| US 6,564,189 | 1,129 | 6 | 0 | 1 |
US 6,564,189, "Universal methods and apparatus for determining prices and rewards within retail transactions", was filed 1998-12-22, nearly eight years before this patent's priority date. It is unambiguously eligible prior art, it appears nowhere in the examiner's citations, and it teaches six of seven limitations outright where the examiner's own reference teaches two.
That is also the honest reading of every recall number above. They are measured against the examiner, so a reference Nightshift finds that the examiner missed counts here as a miss. This is one of those misses.
"Teaches more of the claim" is a statement about disclosure, not about legal strength. Which reference makes the better invalidity case depends on claim construction and argument, and those are questions for counsel.
The eval denominator is disclosed rather than implied, because a recall figure without its denominator is not a result.
- Gold standard is
citationCategoryCode IN ('X','Y')from the USPTO office action citations dataset: references an examiner applied in a rejection, not merely listed. CategoryA("not prejudicial to novelty") is excluded. - 73% of examiner citations point at pre-grant publications and 6% at non-patent literature. A pre-grant corpus of 413,323 G06Q publications is materialized to close most of that gap; anything still outside the corpus is excluded from numerator and denominator alike.
- Corpus is CPC G06Q (business methods, e-commerce), where non-practicing entities operate. Cross-class prior art is out of scope.
- Issued claim text in
patentsview.claimends in 2019, so targets are drawn from earlier grants. - This is recall against the examiner, not against ground truth. An examiner's own search recall is itself 45 to 85%, so every reference Nightshift finds that the examiner missed scores here as a miss. The number is a floor on performance, not an estimate of it.
Measured cost drove the design. Querying the public patents table per request is not survivable: a single 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 and clustered. A target fetch went from 40.16 GB to 0.20 GB, a factor of about 200.
Every finished run, every claim chart, the accuracy page and the letter intake are open at the URL above with no key and no sign-in. Reviewers should never meet a password box.
Starting a new search is the one action that costs real money: 2,000 patents read by Gemini, about $34. So it takes two independent limits.
- A token.
/runrefuses unless the request carriesPRIOR_ART_RUN_TOKEN. A tester follows/tester?t=<token>, which is the ordinary site with the search button live, and the page says so including the price. - A daily ceiling. At most
PRIOR_ART_DAILY_RUNSnew searches per day, claimed in a Firestore transaction so two concurrent presses cannot both pass, and failing closed if the counter cannot be read. A leaked token costs one run.
Before either is consulted, a patent already searched returns its finished run instead of a new one, which is both free and a faster answer.
This is not a demo restriction bolted on afterwards. The cost of a run is the central design constraint of the whole system, and a public button that spends real money per press has to have a ceiling that does not depend on who finds it.
demand letter
-> target patent + claim limitations
-> priority-date gate (references must predate the target)
-> vector prefilter BigQuery, 171,695 -> candidates
-> Gemini judgment fan-out Cloud Run Jobs, 10 tasks, thousands of candidates
-> claim chart limitation-by-limitation, with pin cites
Needs a Google Cloud project with billing attached. Gemini runs on Vertex AI, not the AI Studio endpoint, so no API key is required: the service account authenticates. (The AI Studio free tier caps some models at 20 requests a day, which is unusable when the unit of work is thousands of candidates.)
gcloud services enable bigquery.googleapis.com aiplatform.googleapis.com \
run.googleapis.com firestore.googleapis.com cloudbuild.googleapis.com \
--project=YOUR_PROJECT
gcloud iam service-accounts create priorart --project=YOUR_PROJECT
for ROLE in bigquery.user bigquery.dataEditor aiplatform.user \
datastore.user run.developer iam.serviceAccountUser; do
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member="serviceAccount:priorart@YOUR_PROJECT.iam.gserviceaccount.com" \
--role="roles/$ROLE" --condition=None
done
gcloud iam service-accounts keys create key.json \
--iam-account=priorart@YOUR_PROJECT.iam.gserviceaccount.com
# Firestore needs a NAMED database. The default database id "(default)" is
# percent-encoded into the resource path inside a container and every call fails
# with "400 Invalid database id %28default%29".
gcloud firestore databases create --database=nightshift --location=nam5 \
--project=YOUR_PROJECTgit clone https://github.com/JonathanSolvesProblems/nightshift.git
cd nightshift
python -m pip install -r requirements.txt
export PRIOR_ART_PROJECT=YOUR_PROJECT
export GOOGLE_APPLICATION_CREDENTIALS=$PWD/key.json
export PYTHONPATH=$PWD/srcOne-time. Every query is dry-run priced first and refuses to run above a configurable scan ceiling, so nothing here can surprise you with a bill.
# ~46 GB scanned. Prints the cost and waits.
python -m priorart.corpus bootstrap --scope G06Q --execute
# Filing and priority dates, needed for the prior-art eligibility gate.
bq query --use_legacy_sql=false --project_id=$PRIOR_ART_PROJECT \
--destination_table=$PRIOR_ART_PROJECT:corpus.dates_g06q --replace \
--clustering_fields=patent_id < scripts/build_dates.sql
# The evaluation gold set: references examiners applied in real rejections.
bq query --use_legacy_sql=false --project_id=$PRIOR_ART_PROJECT \
--destination_table=$PRIOR_ART_PROJECT:corpus.gold_pairs --replace \
< scripts/gold_pairs.sqlpython scripts/smoke_test.py # BigQuery reachable
python -m priorart.corpus stats --scope G06Q # 171,695 patents
python scripts/test_gemini.py # Vertex reachable
python scripts/test_failure_modes.py # 12 checks# Local, single process.
python -m priorart.run 10163121 --candidates 200
# Distributed, after deploying (below).
python -m priorart.orchestrate 10163121 --candidates 2000 --tasks 10gcloud builds submit --tag gcr.io/$PRIOR_ART_PROJECT/nightshift:v1
gcloud run jobs create nightshift-worker \
--image=gcr.io/$PRIOR_ART_PROJECT/nightshift:v1 --region=us-central1 \
--service-account=priorart@$PRIOR_ART_PROJECT.iam.gserviceaccount.com \
--command=python --args="-m,priorart.worker" \
--tasks=1 --max-retries=1 --task-timeout=3600s --memory=1Gi \
--set-env-vars="PRIOR_ART_PROJECT=$PRIOR_ART_PROJECT"
gcloud run deploy nightshift \
--image=gcr.io/$PRIOR_ART_PROJECT/nightshift:v1 --region=us-central1 \
--service-account=priorart@$PRIOR_ART_PROJECT.iam.gserviceaccount.com \
--allow-unauthenticated --port=8080 --memory=1Gi \
--set-env-vars="PRIOR_ART_PROJECT=$PRIOR_ART_PROJECT,PRIOR_ART_TASKS=10,PRIOR_ART_CANDIDATES=2000"python -m priorart.eval --n 40 --cats X,Y # 97.5% / 92.5% / 18.8%
bq query --use_legacy_sql=false < scripts/gate_recall.sql
python scripts/pick_demo_target.py --pairs 16 # how the demo case was chosenpython scripts/test_agent.py <run_id>- Google Patents Public Data, PatentsView, USPTO Office Action Citations, and
USPTO PTAB trials, all via
patents-public-dataon BigQuery. - See
NOTICES.mdfor attribution.
docs/DAY1-FINDINGS.md: the measurements that shaped the designACCURACY.md: end-to-end accuracy
