PriorArt Triage Agent
Enables prior-art searches on granted US patent claims. Given an invention description, it returns a grounded memo with citations, verifying each finding against the source and quarantining unverifiable claims.
README
PriorArt Triage Agent
Give it a plain-English invention description. It searches granted US patent claims and returns a prior-art memo with per-finding citations, then verifies every assertion against the raw source material and quarantines anything it cannot ground.
python -m agent "a solar-powered soil moisture sensor that predicts irrigation need six hours ahead"
Runs entirely locally. No API key, no per-token cost, no identity verification.
Why the verification pass exists
A model that searches, drafts, and then grades its own work in one context agrees with
itself. The first three iterations of this project proved it: every supporting_text
came back as the model's own paraphrase, and the model-based verifier passed all of
them because it was assessing relevance rather than provenance. The safety stage was
theater.
The fix was to take the model out of the quoting path entirely.
The model returns a patent number and an explanation of relevance. Nothing else. The citation text is then read out of the corpus by that ID. Two properties fall out for free:
- A hallucinated patent number is not in the database, so the finding is dropped before verification runs.
- Every surviving quote is byte-exact claim text, because no model ever typed it.
That leaves exactly one question worth a model's judgement: does this claim text actually support the assertion made about it? Findings that fail land in a Flagged section, never in the memo body.
The general lesson, and the reason this is the interesting part of the project: don't ask a model to do what a database already does correctly. Retrieval and provenance are code. Judgement is the model.
Architecture
invention description
│
[1] plan_search schema-constrained call → SearchPlan
│ examiner vocabulary, not founder pitch language
[2] draft_memo hand-rolled agent loop over MCP tools → DraftMemo
│ model returns patent IDs + relevance, never quotes
│ code attaches claim text from the corpus by ID
[3] verify_memo per-finding: does this claim text support the assertion?
│
memo — grounded findings, flagged findings, gaps
| Path | What it is |
|---|---|
uspto/ingest.py |
Builds the local corpus from USPTO bulk downloads |
uspto/corpus.py |
SQLite + FTS5 query layer over granted claims |
uspto/server.py |
MCP server exposing three tools over stdio |
agent/llm.py |
Ollama backend: chat, tool calling, schema-constrained output |
agent/pipeline.py |
The three stages and the agent loop |
agent/models.py |
Pydantic schemas |
agent/grounding.py |
Deterministic quote-provenance checking |
agent/__main__.py |
CLI and Markdown renderer |
scripts/smoke_test.py |
Gate: prove the corpus is queryable |
evals/run_eval.py |
Grounding rate, hit rate, term coverage |
Setup
1. Python deps
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
2. Local model
brew install ollama
ollama serve &
ollama pull qwen2.5:7b
Override with OLLAMA_MODEL if you want a different one. It needs tool-calling
support; qwen2.5 and llama3.1 both have it.
3. Corpus
Sign in to data.uspto.gov and download two files into data/:
| File | Dataset | Size |
|---|---|---|
g_claims_2025.tsv.zip |
pvgpattxt |
346 MB |
g_patent.tsv.zip |
pvgpatdis |
232 MB |
Then:
python -m uspto.ingest # ~2 minutes, produces data/corpus.db
python scripts/smoke_test.py
Run it
python -m agent "your invention description"
python -m agent -f invention.txt -o memo.md
python evals/run_eval.py --plan-only # planning stage only, fast
python evals/run_eval.py # full run, writes evals/results.json
Why bulk data instead of the ODP API
USPTO splits access into two tiers:
| Access | Requires |
|---|---|
| Bulk data downloads | USPTO.gov account + MFA |
| ODP API key | USPTO.gov account + verified and linked ID.me |
The API key path requires identity verification through ID.me, which collects an SSN and biometric data. That is a real cost for a side project, and it buys the wrong data: the ODP API serves patent application file-wrapper records (titles, abstracts, CPC codes), not granted claim text.
Bulk downloads need only the account, and they include full granted claims. Claims are what a prior-art search actually reads.
Do not reach for PatentsView as an alternative. It migrated into ODP on
2026-03-20, its search.patentsview.org API is being retired, previously issued keys
do not work against ODP, and there is no announced replacement date.
API routes, if you do go that way
Base URL https://api.uspto.gov/api/v1. Routes confirmed by probing without
credentials: 401 means the route exists and needs a key. There are two distinct
403s, which matters when debugging. Body "Missing Authentication Token" means the
route does not exist. Body "Forbidden" means the route is fine and your key was
rejected.
Real: /patent/applications/search, /patent/applications/{id}, .../meta-data,
.../continuity, .../transactions, .../associated-documents,
/datasets/products/search.
Not real, despite appearing in older docs: /patent/applications/{id}/documents,
/patent/grants/search, /bulk-data/products. The legacy Developer Hub at
developer.uspto.gov was decommissioned 2026-06-05, so most tutorials point at a
dead host.
Results
Ten invention descriptions, qwen2.5:7b throughout, full pipeline per case
(python evals/run_eval.py).
cases run 10/10
findings drafted 42
findings grounded 19
grounding rate 45%
hit rate 90%
term coverage 88%
| Case | Drafted | Grounded | Rate |
|---|---|---|---|
| soil-moisture-pinn | 4 | 4 | 100% |
| rag-retrieval | 5 | 4 | 80% |
| solar-tracker | 5 | 3 | 60% |
| pill-dispenser | 2 | 1 | 50% |
| cold-chain | 5 | 2 | 40% |
| leak-detector | 5 | 2 | 40% |
| handwriting-tablet | 4 | 1 | 25% |
| shift-scheduling | 5 | 1 | 20% |
| hearing-aid | 5 | 1 | 20% |
| bike-lock | 2 | 0 | 0% |
The 45% is a rejection rate, not an error rate. The model drafted 42 findings; 23 did not survive verification and none of those reached a memo. Without that stage all 42 would have shipped as prior art. A number closer to 100% would mean the verifier was rubber-stamping, which is what the first three iterations of this project did.
Hit rate 90% — nine of ten cases returned at least one defensible finding.
The spread tracks the corpus, not the code. The strong cases (soil moisture at 100%, RAG at 80%) are ones where 2025 grants genuinely contain close prior art in standard vocabulary. The weak ones are not: shift-scheduling is a web application rather than a patented device, and bike-lock is a consumer product thinly represented in a single grant year. The verifier declined to manufacture findings that were not there, which is the intended behavior.
What this measurement does not tell you
The rejection rate shows the verifier is filtering, not that it is filtering correctly. A verifier that rejected everything would score 100% here and be worthless. Establishing that the rejections were right needs one of two things, and neither is done yet:
- Known-item retrieval. Take N patents from the corpus, write each invention description from that patent's own claim, and measure whether the agent recovers it. Ground truth is known by construction, so the result is a hard recall number with no labeling and no judgement call.
- Verifier confusion matrix. Hand-label the 42 drafted findings as relevant or not, then report the verifier's precision and false-negative rate. Converts "it rejected 55%" into "it rejected 55%, and was right about X% of them."
The first measures retrieval; the second measures the verification stage. Both are stronger claims than anything in the table above.
Future work: a larger model
Everything here ran on qwen2.5:7b. qwen2.5:14b is also free and open-weight, needs
about 9 GB, and fits in 16 GB of RAM.
The expected benefit is a higher grounding rate through a smaller denominator. The 7b drafts up to five findings per case whether or not five exist, and the surplus is what verification throws away. A larger model should propose fewer speculative findings, so more of what it drafts survives.
Worth stating plainly: that is a hypothesis, not a measured result. It has not been run, and the number is not reported here until it is.
Known false positive
The example memo cites US 12327263, an AI smart-grid patent, as "moderately relevant" to an irrigation invention. It is not relevant, and the verifier passed it. Relevance judgement catches obvious mismatches and misses subtle ones. Provenance is airtight; relevance is not.
Stated limitations
The corpus is 2025 grants only — about 379k patents, 5.5M claims. Real prior-art
search spans decades. This is a scope choice, not an oversight: adding years is one
g_claims_YYYY download each (~350 MB) plus a re-ingest. The agent is required to
call get_corpus_stats before drawing conclusions, and the memo footer states the
range, so an empty result reads as "not in this corpus" rather than "does not exist."
Only exemplary claims are indexed. USPTO flags one representative claim per patent; indexing those rather than all ~14 claims cuts the index by an order of magnitude and keeps the claim that describes the invention. All claims are stored and retrievable once a patent is found.
Retrieval is lexical, not semantic. SQLite FTS5 with Porter stemming. It will miss a patent that describes the same idea in entirely different vocabulary. Mitigated by having the planning stage generate several phrasings, not solved.
A 7B is not a patent attorney. It drafts roughly twice as many findings as survive verification (see Results). A frontier model would draft fewer bad ones to begin with. The architecture is what transfers; the verification stage exists precisely because the drafting model is not trustworthy on its own.
Relevance judgement is the weak link. Provenance is guaranteed by construction. Whether a genuine patent is genuinely relevant is still a model call, and it gets that wrong sometimes in both directions.
Implementation notes
mcp >= 2.0 renamed FastMCP to MCPServer, and Tool.inputSchema to
input_schema. The decorator and run() APIs are otherwise unchanged.
The agent loop is hand-rolled. Ollama has no managed tool runner, so
draft_memo implements the loop directly: call, execute requested tools through MCP,
append results, repeat to a turn cap. MCP is model-agnostic, so switching backends
never touched the tool layer.
Tool results are truncated for the model but kept whole for verification. A 7B degrades badly past a few thousand tokens of context, so the model sees 6k characters per result while the verifier gets the full text. Verification would otherwise fail findings for being unable to see their own evidence.
Unruled findings default to ungrounded. A local model sometimes returns fewer verdicts than findings. Anything the verifier did not rule on is treated as failed, which is the safe direction.
Schema-constrained decoding, then validation anyway. Ollama's format parameter
enforces JSON Schema during decoding, which guarantees parseable output but not
sensible field contents. Pydantic validation and a retry still run.
Scope
Public USPTO bulk data only, downloaded through the documented portal with an account. No scraping.
This is a triage aid that tells an attorney what is worth reading. It is not a patentability opinion and does not attempt to be one.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.