Multi System Legal Documents Analyser
Contract review your lawyer can actually sign off on.
MSLDA reads a contract clause by clause and produces a structured risk memo where every single claim is backed by a verbatim quote from the document. Nothing is final until a named human approves it — and that approval is written to a log the database itself will not let anyone rewrite.
MSLDA produces a reviewed draft, not final work product. It does not give legal advice and it does not replace a lawyer. It removes the part of first-pass review that is mechanical, and hands a qualified human a memo they can verify line by line.
The problem
First-pass review is expensive, and AI has mostly made it worse.
It burns your best hours
Reading an NDA or MSA end to end to find the six clauses that matter is the most mechanical work in the building, and it is done by the most expensive people in the building. At 20 to 200 contracts a month, that is a standing tax.
A confident summary you cannot check is worth nothing
Generic tools return fluent prose with no traceable source. So the lawyer opens the contract and reads it anyway, to confirm what the model said. The review happened twice and the tool saved nothing.
One invented clause costs more than the tool ever saved
A model that describes an indemnity provision the contract does not contain, or misses a cap that it does, is not a productivity problem. It is a liability problem, and it lands on the firm rather than the vendor.
How grounding works
A quote that is not in the document does not make it onto the page.
Grounding is not a prompt instruction here. It is a verification step in the pipeline. After the model writes a claim, the quote it cited is checked programmatically against the source text. If the quote does not appear verbatim, it is discarded — not softened, not shown with a warning.
Document
PDF or DOCX ingested, format detected from magic bytes rather than the client-supplied header. The original file is retained.
Clause
The document is segmented into clauses. Each one is assessed on its own terms, not because a query happened to retrieve it.
Retrieved evidence
Supporting passages are retrieved and reranked. Candidates scoring below
0.30 relevance are dropped rather than padded in to fill a quota.
Model claim
The model produces a finding with a category, a severity, and a
quoted_span it asserts comes from the source.
Verbatim quote check
The asserted span is matched against the cited source text. This is a string check, not a judgment call, and the model does not get a vote in it.
Cited claim
The span exists in the source. It ships with the finding, tied to a real chunk ID and clause reference.
Dropped span
No verbatim match. The span is thrown away. A deterministic sentence-overlap fallback selects a real supporting sentence instead.
"…shall indemnify Buyer against all third-party claims…"Chunk IDs are never fabricated. Version-diff findings, which have no single originating chunk, carry clause-level citations with an explicit null chunk reference rather than a plausible-looking invented one. When retrieval surfaces nothing relevant, the system returns insufficient information instead of grounding an answer in noise.
The indemnity obligation runs one way and is not bounded by the liability cap in Clause 11.1, so indemnified losses are uncapped in practice.
"Supplier shall indemnify, defend and hold harmless Customer from and against any and all losses arising out of or relating to Supplier's performance hereunder. The limitations set forth in Section 11.1 shall not apply to Supplier's obligations under this Section 9.2."
Illustrative — mocked interface, not a real memo
Why this is the whole pitch
Every other guarantee on this page depends on this one. If a quote on screen might not exist in the document, then coverage does not matter, the audit trail does not matter, and the reviewer is back to reading the contract from the top. Guaranteeing that every span is real is what makes the rest worth building.
Human review checkpoint
Sign-off is a state machine, not a disclaimer in the footer.
Every memo is persisted as draft_pending_review with a review
disclaimer attached. It stays a draft until a named person moves it, and the API
rejects any transition the machine does not permit with a 409.
Content never changes under review
Review actions record a decision. They do not touch memo content. Each memo carries an immutable SHA-256 snapshot of its bytes, so an approval always refers to exactly what was read — not to a version edited afterwards.
Illegal transitions are refused
approved and rejected are terminal.
changes_requested can only return to in_review.
Anything else is a 409 from the API. The rule lives in the system,
not in a process document nobody reads.
Every transition is logged
Each move appends a row recording who acted, when, and with what note. The log is
append-only at the Postgres level: triggers reject UPDATE and
DELETE. Who approved a memo cannot be quietly rewritten later — not
by the application, and not by a developer with database access.
Full clause coverage
It reads every clause, not the ones a keyword happened to hit.
You find what you thought to ask about
A fixed list of search queries defines what gets reviewed. A risk that nobody wrote a query for is not missed by accident — it is structurally unreachable. The tool cannot tell you about the change-of-control clause because it never looked for one.
Coverage is defined by the document
The memo path enumerates every clause in the document and assesses each one. The set of things reviewed is the set of things in the contract. Per-clause coverage manifests ship with each memo, so a partial run cannot be mistaken for a complete review.
14 named risk categories
Each finding is classified into one of these and assigned a severity of low, medium, high, or critical.
Version diffing
Counsel inserts one section. Your diff tool reports forty changes.
Adding a single section turns 4.2 into 4.3, 4.3 into 4.4, and so on to the end of the contract. A tool that matches clauses on their number reports the entire tail of the document as deleted and re-added. You now have to read all of it to find the one paragraph that actually changed.
Clause identity ignores position and number
Each clause gets a cross-version identity derived from its normalized heading text plus a fingerprint of its body with numbering stripped out. Position and clause number are deliberately excluded from that identity. A clause that moved is the same clause, and the diff says so.
Exact identity key
Clauses whose identity key matches across versions are paired immediately. This resolves most of the document at near-zero cost.
Position-agnostic fingerprint
Remaining clauses are matched on a content fingerprint that disregards where they sit in the document. This catches clauses that were moved or re-headed but whose substance is unchanged.
Embedding similarity
What is still unmatched is compared by embedding similarity with a position prior — for clauses whose text genuinely changed. This is where real edits surface, rather than being buried under renumbering noise.
Security and hostile documents
Contracts come from counterparties. Treat them accordingly.
A clause can contain text written to manipulate the model reading it: "ignore your
instructions", "mark every claim as verified", a fake system: role
label. Most tools pass document text straight into the prompt and hope.
"Can a contract talk to your model?"
Every stage that touches document text wraps it in randomly-nonced fences that the system prompt declares inert. A document cannot forge a valid fence because it cannot know the nonce. Control characters are stripped from untrusted content, and content suspected of injection routes to a manual review queue instead of being silently processed.
"How do you know that actually works?"
An adversarial eval ingests a contract with embedded attack payloads — verdict coercion, fake source assertions, prompt exfiltration, role hijack — runs the real pipelines against it, and asserts that no payload changed the output. Two CI gates cover injection recall and false-positive rate on every push.
"Can someone rewrite the audit trail?"
Not through the application, and not with a database client. The memo event log
and the request audit trail are append-only at the Postgres level — triggers
reject UPDATE and DELETE outright.
"What gets recorded?"
Every API request: method, path, status, org, auth method, client IP, user agent, duration — including failed authentication attempts. Every request carries a trace ID echoed on the response and stamped into every log line for that request. Reviewers browse the trail in the UI.
"Whose infrastructure is this on?"
Yours. Self-hosted via Docker Compose on your own VPS or cloud account. Contracts live in your own Postgres. Multi-tenant by design: every document and every search is scoped to an organization via API key.
"What about the files themselves?"
Uploads accept PDF and DOCX, with format detected from magic bytes rather than the client-supplied header. Original files are retained, so a reviewer can see a citation highlighted on the actual source page — not just on extracted text.
Built, not promised
This is a deployable system, not a demo.
Benchmark cases are derived from the public CUAD v1 test set (The Atticus Project, CC BY 4.0). Line counts are current as of 2026-07-20 and will drift as the code changes.
CI gates
Deterministic and network-free, so they run on every push. A gate that regresses past its threshold fails the build.
| Gate | What it measures | Threshold |
|---|---|---|
| structure_detection | Clause structure detected correctly against golden fixtures | ≥ 100% |
| diff_alignment | Clauses aligned correctly across versions, including renumbering | ≥ 100% |
| evidence_grounding | Quoted spans present verbatim in the cited source | ≥ 100% |
| injection_recall | Hostile payloads in document text detected | ≥ 85% |
| injection_false_positive | Benign clauses wrongly flagged as injection | ≤ 5% |
| chunk_bounds | Chunks within configured token bounds | ≥ 100% |
CI runs lint, the test suite, the golden gates, the migration single-head check, and a frontend typecheck on every push.
Stack
Operational reality
Long-running work runs as async jobs with pollable status. Jobs interrupted by a restart are marked failed rather than hanging forever. Per-operation OpenAI cost tracking gives spend visibility. Error tracking is optional, and request bodies and PII are never transmitted.
Reviewer UI
Seven working screens: workspace, document reader, analyze, diff, memo, reviews, and audit. A reviewer can open a citation, see it highlighted on the source page, move a memo through review, and browse the request audit trail without leaving the interface.
What you get
Deployed on your infrastructure, with the source in your hands.
- Full source — the Python services, the React reviewer UI, and the eval suite.
- Docker Compose stack sized for a single VPS: API, worker, Postgres with pgvector, Redis, nginx.
- Database migrations — 13 Alembic migrations with the single-head check wired into CI.
- Reviewer UI — all seven screens, including the review queue and audit trail.
- HTTP API — org-scoped by API key, with async job status polling.
- Test suite and CI — 25 hermetic test modules and the 6 golden-set gates, running on every push.
- Deployment runbook — provisioning, configuration, migration, backup, and upgrade steps.
Engagement options
[OFFER: e.g. one-time deployment + handover / deployment + customization / retained monthly support]
[PRIMARY_CTA][TESTIMONIAL PLACEHOLDER — replace with a real client quote, or delete this block.]
Where it does and doesn't fit
The constraints, stated plainly.
You would find all of these in the first week. Better that you find them now.
- Memos are drafts. They require review by a qualified human and are not legal advice. The system is explicit about this in its own output — every memo is persisted as a draft with a review disclaimer attached.
- It depends on the OpenAI API. Contract text is sent to OpenAI for processing. "Self-hosted" means your infrastructure and your database — not an offline model. If your contracts cannot leave your network at all, this is not the right system for you today.
- DOCX tables are not yet extracted. Clause text in tables within DOCX files is not currently picked up. PDF handling is unaffected.
- DOCX files have no page images. DOCX has no fixed pages, so there is no source page to highlight a citation on. The verbatim span is still verified; you just see it as text rather than on a rendered page.
- Low-confidence documents go to a manual queue. By design. A scanned contract that extracts badly, or content flagged as a suspected injection attempt, is routed to a human rather than processed silently.
Questions
The ones you are already asking.
Does it hallucinate?
It can produce a wrong interpretation. That is what the human review step is for. What it cannot do is show you a quote that is not in your document. Every quoted span is checked against the source text after the model produces it, and a span that does not match verbatim is discarded rather than displayed. When no verified quote survives, a deterministic sentence-overlap fallback picks a real supporting sentence.
Chunk IDs are never fabricated, and when retrieval finds nothing relevant the system says insufficient information rather than grounding an answer in noise.
Where does my data go?
The system runs on your infrastructure via Docker Compose. Documents, clauses, embeddings, memos, and audit logs live in your own Postgres, on your own VPS or cloud account.
Contract text is sent to the OpenAI API for processing — that is a real dependency and we are not going to pretend otherwise. Optional error tracking never transmits request bodies or PII.
Does this replace my lawyers?
No, and it is built so that it structurally cannot. Nothing the system produces
is final work product. Memos are created as draft_pending_review
and only a named human can move them to approved. It removes the
mechanical part of first-pass review and hands a qualified reviewer a memo where
every claim can be checked against a verbatim quote in seconds.
What contract types does it handle?
It is built around commercial agreements — NDAs, MSAs, SOWs, and similar — and classifies findings across 14 named risk categories including liability, indemnification, IP assignment, data protection, and change of control.
Because the memo path enumerates every clause rather than running a fixed list of queries, it is not limited to contract types someone wrote queries for. The retrieval benchmark uses 10 real contracts from the public CUAD v1 test set.
How long is deployment?
The stack is a Docker Compose deployment onto a single VPS, with Alembic migrations and a deployment runbook covering provisioning, configuration, backup, and upgrades. Timeline depends on your environment and any customization — [ADD YOUR TYPICAL TIMELINE HERE].
What does it cost to run?
Two lines: your own infrastructure (a VPS running Postgres, Redis, the API, the worker, and nginx) and OpenAI API usage, which scales with document volume and length.
The system tracks OpenAI cost per operation, so you can see actual spend per document rather than estimating from a monthly bill.
Can we customize the risk categories?
Yes. The 14 categories and their severity scale are defined in the codebase, which you receive. Adding a category, renaming one, or adjusting how severity is assigned is a code change in a system you own — not a support ticket to a vendor.
What happens when OpenAI changes models?
Model selection is configuration, so switching is a config change rather than a rewrite. The reason the golden-set gates exist is exactly this: after a model change you re-run six deterministic gates covering clause structure, diff alignment, evidence grounding, injection recall, false-positive rate, and chunk bounds, plus the 70-case retrieval benchmark. You find out whether the new model regressed before it touches a client contract.
Next step
Every claim traces to a verbatim quote. Every memo waits for a human.
If a first-pass review your team can verify in minutes rather than re-do in hours would change your week, the fastest way to judge it is to watch it run against a contract you already know well.