Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contractix

Term-sheet & employment-offer diligence agent — upload an employment offer, VSOP/ESOP agreement, or startup term sheet (German or English) and get every material term extracted, benchmarked against market standards, and flagged — with every claim citing the exact page and clause.

⚠️ Contractix provides informational analysis, not legal or tax advice. Statutory references are pointers, not determinations.

Who it's for

You've been handed a dense, adversarially-drafted document and a short deadline. Contractix reads it the way a careful advisor would — extracting every material term, checking it against market and German/EU statutory norms, and surfacing what to push back on, with a citation to the exact clause behind every claim.

  • 🧑‍💻 Senior engineer / exec weighing an offerIs this VSOP grant actually worth anything? Is the non-compete enforceable? Where's the bad-leaver trap?
  • 🚀 Founder handed a term sheetWhich terms deviate from standard, and what do I negotiate? — liquidation preference, anti-dilution, ESOP dilution math, board control.
  • 👼 Angel / operator-advisorGive me a red-flag summary of this whole document set in five minutes.

German and English documents, including mixed sets — with DE/EU statutory awareness (HGB non-compete rules, BGB notice periods, Probezeit ceilings) that generic "chat with your PDF" tools don't have.

What it catches

A sample of the 31 deterministic rules. Each fires only on the extracted terms, is severity-ranked so the worst traps surface first, and cites the clause(s) that triggered it plus its statutory or market source:

document trigger flag
Employment post-contractual non-compete without ≥ 50% Karenzentschädigung 🔴 unenforceable under §74 HGB, yet still deters
Employment Probezeit longer than 6 months 🔴 exceeds the §622 BGB ceiling
Employment IP assignment with no side-project carve-out 🟠 captures your open-source / side work
VSOP/ESOP bad-leaver forfeiture of vested options 🔴 voids already-earned value
VSOP/ESOP payout only on a narrow, board-defined exit 🟠 vested options may never pay out
Term sheet liquidation preference > 1× or participating 🔴 / 🟠 investors paid first — sometimes twice
Term sheet full-ratchet anti-dilution 🔴 maximal founder dilution on a down round
Term sheet ESOP pool created pre-money ℹ️ founder-dilutive — the math is shown

🔴 red · 🟠 amber · ℹ️ info

Status

Phases 0–2 complete; Phase 3 in progress — the ingestion + retrieval spine, the extraction + red-flag engine, the agentic Q&A path, the API surface, and the web app's report, chat, citation and trace views are built and tested. The staging deploy is what remains of Phase 3; comparison mode is Phase 4. See PRD.md for the full specification and roadmap.

  • Ingestion & retrieval — layout-aware PDF/DOCX parse → clause segmentation → chunking → pgvector + full-text + trigram hybrid search with cross-encoder rerank.

  • Extraction — schema-first, per-field-cited extraction (employment offers/contracts, VSOP/ESOP, term sheets); every field carries a structural citation to the exact clause span, and not_found is a first-class value, never inferred.

  • Red-flag engine — 31 deterministic, versioned rules over the extracted schema (sampled above), each citing the clause(s) that triggered it and its source. Deterministic and auditable — not LLM-judged, so the same document always yields the same flags.

  • Agentic Q&APOST /cases/{id}/ask streams a cited answer over SSE. A hand-rolled tool loop (search, clause lookup, extraction, red flags, deterministic arithmetic) answers the question, then a validator checks the result: every factual sentence must carry a [[clause_id]] marker naming a clause a tool actually returned, resolved structurally to a frozen span — never quote-matched. A failure buys one corrective regeneration; anything still unsupported is returned under "could not verify" rather than dropped. Each turn persists its trace, tokens, cost and latency.

  • A conversation, not a series of one-shot questions — a follow-up like "und ist das gesetzlich zulässig?" is answered with the exchange above it in context. History is read back from the database rather than accepted from the client, because a request that can assert what the assistant previously said is a free hand at steering the model through text it never wrote; and prior answers have their [[clause_id]] markers stripped before replay, so the citable set is still built only from the tools this request actually called.

  • Show your work — the chat panel streams the answer and, while it works, what it is reaching for. Every answer opens a trace: each tool call with its arguments and duration, chips for the exact clauses that call surfaced (click one, the document opens at it), the token count, the cost in euros, and — when the validator rejected a draft — the specific sentences that carried no citation and the markers naming a clause no tool returned. That last section is the grounding contract visibly biting, and it is the reason the trace is a published schema rather than a debug blob (ADR-0013). A keyless run reports "no model key" instead of pricing itself at €0.0000.

  • Web app — upload or one-click demo → streamed per-document progress → report → click any citation and the clause is highlighted on the page it came from. The highlight is computed in the browser from the same frozen character offsets the citation carries, intersected with the block geometry the parser retained — so it is exact wherever a citation covers whole blocks (every red flag, since clauses are segmented on block boundaries), and visibly approximate rather than confidently wrong where it covers part of one. Beside it sits the clause text with the cited span marked, which is a plain slice of frozen text at frozen offsets and therefore exact by construction — which is also the fallback for DOCX, which has no page geometry at all. Searching the text layer for the quoted anchor would have been pixel-exact and is exactly what ADR-0006/0007 forbid: its failure mode is highlighting a different occurrence of the same phrase (ADR-0012).

  • Anonymous sessions — no accounts. Creating a case, or one-click adopting the demo corpus, mints a signed HttpOnly cookie carrying nothing but an opaque tenant id; every query then carries a single tenant_id equality guard, so one session 404s another's case. The session and everything under it are purged 24 h later, and because the tenant row is the authority, deleting the data is revoking the cookie. Redis-backed per-IP and per-tenant rate limits return an exact Retry-After, and fail open — a limiter that takes the demo down when Redis hiccups has done more damage than the abuse it prevents (ADR-0011).

  • Try without upload — the seeded corpus is copied into your session rather than shared read-only, so you can question it, re-analyze it, upload your own contract beside it and delete the lot. Sharing it read-only would have turned the tenant guard into set membership at every query site, forever; the clone keeps that invariant literally true, and the embeddings never leave Postgres.

  • Evals — golden-corpus gates in CI, with pinned real numbers on the demo corpus:

    gate key metrics
    Extraction (Haiku 4.5) field accuracy 0.95 · not_found precision 1.00 · hallucination 0.00 · citation recall 0.91
    Rules / red-flag (deterministic, keyless) precision 1.00 · recall 1.00 · F1 1.00 (24 flags / 5 docs, per severity)
    Retrieval (Jina v4) recall@8 1.00 · MRR@8 1.00

    The rules gate is keyless and runs on every PR; the extraction and retrieval gates run live behind ANTHROPIC_API_KEY / JINA_API_KEY (both baselines pinned from real runs).

    The Q&A path is not eval-gated yet. Its citation integrity is enforced structurally at runtime rather than measured — a marker resolves only against a clause a tool actually returned — but faithfulness and answer relevance need an LLM-as-judge suite with a pinned judge model and versioned prompts (PRD E-2/E-4), which is Phase 4. The 30 gold Q&A pairs that currently score retrieval become its answer-level set then. Same for the adversarial injection suite.

    Running things live has repeatedly earned its keep: the first live extraction run caught a citation-validation defect that keyless fakes had masked (ADR-0007), and the first live Q&A run caught a grounding validator strict enough to reject its own correct answers (ADR-0010).

Runs fully offline in keyless mode (deterministic fake providers) — pnpm test, the rules/red-flag eval, and their CI gates need no API keys. Real extraction/retrieval and their eval baselines require provider keys; see .env.example.

One engine, many document types

The spine — typed extraction → a deterministic, versioned rules engine → a report where every claim resolves to an exact source span — is document-agnostic. The same approach benchmarks NDAs, DPAs, or SaaS/commercial contracts, or runs EU-regulation gap analysis (GDPR, the AI Act) — anywhere an auditable, cited answer matters more than a fluent one. Employment offers and venture-finance are the first vertical, not the ceiling.

Documentation

  • PRD.md — product requirements (authoritative spec)
  • docs/adr/ — architecture decision records
  • PRIVACY.md — data handling & model-provider posture

License

MIT for this codebase — see LICENSE.

Note: the PDF parsing path currently uses mupdf (AGPL-3.0). Parsing sits behind a Parser interface precisely so this dependency is swappable; the licensing reasoning is documented in the ADRs.

About

Diligence agent for founders, candidates & angels — reads employment offers, VSOP/ESOP grants & startup term sheets (DE/EN), extracts every material term, benchmarks it against market & German statutory norms, and flags red flags with clause-level citations. Not legal advice.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages