Photo: Unsplash
I Made My Mac Read Every Contract Before I Sign It
Two years ago I signed a gym membership that auto-renewed annually with a 60-day cancellation notice buried in clause 9.4. It cost me an extra year of fees for a gym I’d stopped attending. Last year I almost signed a freelance contract with a non-compete covering “software development in the European Union” — for twelve months, uncompensated.
I caught the second one because by then I’d built the habit this post describes: no contract gets my signature until a local LLM on my Mac has read it first. Not ChatGPT. Not Claude’s web interface. A model running entirely on my own hardware, because the documents in question are my lease, my employment terms, my NDAs — the exact category of document that should never be uploaded to anyone’s cloud, ever.
Let me be blunt before we start, and I’ll repeat it at the end because it matters: this is issue-spotting assistance, not legal advice. The workflow finds the clauses worth asking a lawyer about. It does not replace the lawyer.
Step 1: Get the PDF into text
Contracts arrive as PDFs. LLMs eat text. The bridge is pdftotext from the poppler package:
brew install poppler
pdftotext -layout lease.pdf lease.txt
The -layout flag preserves columns and tables, which matters for fee schedules. For scanned contracts (image-only PDFs), pdftotext returns nothing — open the PDF in Preview instead, select-all, copy: macOS runs Live Text OCR automatically and it’s surprisingly good, including with Czech diacritics. For batch OCR, ocrmypdf (brew install ocrmypdf) adds a text layer first.
Always skim the .txt output before feeding it to the model. If the extraction mangled the numbering, the model will mangle the analysis.
Step 2: The review prompt that does the work
This is the prompt I’ve refined over dozens of contracts. Copy it verbatim; the structure is the point — vague prompts get vague reviews.
You are reviewing a contract for the party identified as [MY ROLE,
e.g. "the Tenant"]. Read the full text below, then produce:
1. OBLIGATIONS — every obligation placed on me, as a numbered list,
each with the clause number it comes from.
2. DEADLINES & NOTICE PERIODS — every date, deadline, notice period,
and time limit, with clause numbers. Flag any notice period
longer than 30 days.
3. AUTO-RENEWAL — does this contract renew automatically? Quote the
exact renewal clause and state the last safe day to cancel.
4. TERMINATION — under what conditions can each party terminate?
Are the conditions symmetric? Flag any asymmetry.
5. MONEY AT RISK — deposits, penalties, contractual fines, liability
caps (or their absence), and who holds money when.
6. UNUSUAL OR ONE-SIDED TERMS — anything that deviates from what is
standard for this contract type, anything that exists only to
benefit the other party, and anything I should ask a lawyer about.
Quote exact contract language for every flag. If a section is
ambiguous, say so explicitly rather than guessing. Do not provide
legal advice; identify issues only.
CONTRACT TEXT:
[paste lease.txt here]
Sections 3 and 6 pay the rent. Section 3 is what would have saved me the gym fees — the model quotes the renewal clause and computes the cancellation deadline. Section 6 is what flagged the EU-wide non-compete: the model noted that the geographic scope was “unusually broad relative to the work described in Schedule A” and that no compensation was attached to the restriction. That one sentence sent me to an actual lawyer, who got the clause narrowed to direct clients only.
Step 3: Pick a model that can hold the whole contract
Contracts are long, and this is where most people’s local setup silently fails. Ollama defaults to a small context window, and when the input exceeds it, Ollama truncates from the beginning without a loud warning — your model reviews a contract whose first five pages it never saw.
Rules of thumb: a page of contract text is roughly 500–700 tokens. A 20-page lease is ~12,000 tokens, plus the prompt and the response. So set the context explicitly:
ollama run qwen2.5:14b
>>> /set parameter num_ctx 24576
Or permanently, via a Modelfile (FROM qwen2.5:14b + PARAMETER num_ctx 24576). Watch your RAM: bigger context means a bigger KV cache. On my 36 GB MacBook Pro M3 Pro, Qwen 2.5 14B at 24k context fits comfortably; 32B at the same context is a squeeze. On the Mac Studio with 128 GB I run Qwen 2.5 32B at 32k context, and the quality jump on clause 6 (“unusual terms”) is noticeable — the 32B model catches subtler asymmetries. If you have 16 GB, use Llama 3.1 8B at 16k context and split very long contracts in half, reviewing each half with the full prompt.
For Czech contracts — and as a Czech, half of what I sign is a nájemní smlouva or dodatek in Czech — model choice matters more than size. Qwen 2.5 and Gemma 2 handle Czech legal text remarkably well; some otherwise-strong English models stumble on Czech declension and produce confused clause summaries. My trick: I have the model output the review in English even for Czech contracts (“Respond in English, but quote the contract in the original Czech”). Quoting in the original prevents translation drift on the exact language that matters, and I verify every quoted clause against the PDF anyway.
Why local is non-negotiable here
For blog drafts I’ll use any cloud model. For contracts, never, and the reasoning is concrete, not paranoid:
- The documents identify you completely. A lease contains your full name, address, birth date or ID number, salary information sometimes, and your landlord’s identity. An employment contract contains your compensation. An NDA contains the counterparty’s confidential matters — which you may be contractually forbidden from disclosing to third parties. Uploading an NDA to a cloud AI service is plausibly itself a breach of that NDA.
- Retention is not your call. Cloud providers have varying retention policies, training opt-outs, and breach histories. With
ollama run, the contract goes from your SSD to your unified memory and back. Pull the Wi-Fi cable mid-review if you want; nothing changes. - The habit needs zero friction exceptions. The moment “this one’s not sensitive, I’ll just use the web app” enters the workflow, the discipline dies. Local-always is easier to maintain than local-sometimes.
What it catches — and the hard limits
Real flags from my last twelve months: a rental agreement where the deposit (kauce) clause allowed the landlord to deduct “costs of restoring the apartment to original condition” with no normal-wear-and-tear carve-out — the model flagged the missing carve-out explicitly. A SaaS agreement for my business with a 90-day cancellation notice on an annual term, meaning the real decision deadline was month nine. Two auto-renewals. One indemnification clause with no liability cap flowing my way while the vendor’s liability was capped at three months of fees — a textbook asymmetry the model caught under section 4.
Now the limits, in bold honesty. The model does not know Czech or any other jurisdiction’s law. It cannot tell you that a clause is unenforceable under the Czech Civil Code — some of the scariest clauses it flags are actually void, and some innocuous-looking ones are dangerous precisely because they’re enforceable. It can misread cross-references (“subject to clause 12.3”) and occasionally attributes an obligation to the wrong party in dense legalese — which is why the prompt demands clause numbers and exact quotes, so every flag takes 20 seconds to verify against the source. It will never appear in court with you.
What it actually does: turns a 20-page contract from “skimmed and signed in hope” into a one-page list of questions, in about three minutes of compute. For routine documents that list is often reassuringly empty. For the ones where it isn’t, the model has paid for the Mac twice over — once on a gym membership, once on a non-compete. The lawyer still gets the final word. The model just makes sure I know which questions to bring.
Set this up before the next contract lands in your inbox, not after. The whole stack — poppler, Ollama, one model pull, the prompt saved as a text snippet — takes fifteen minutes to install and the rest of your signing life to pay off.
