Mukalaf.ai — Legal AI Platform
A bilingual tax-litigation platform for Saudi Zakat, tax and customs law. An agent drafts legal documents grounded in retrieved regulation, stopping for a human whenever it needs facts only the lawyer has.

The problem
Zakat, tax and customs litigation is regulation-dense and high-stakes, and it runs in Arabic. Two things break naive AI here: legal Arabic defeats plain text extraction and embedding-only retrieval, and a document that reads fluently but cites the wrong article is worse than no draft at all. A generated filing has to be traceable to the regulation it rests on.
Architecture
Agent workflow — a LangGraph state machine: fetch case → extract documents → analyze → search legal references → generate document → generate PDF → review. Two of those edges are loops rather than steps: a request-info loop when the draft needs facts only the lawyer holds, and a feedback loop after review.
Human-in-the-loop durability — PostgreSQL-backed checkpointing means an interrupt is a real pause. The run resumes at the node it stopped on instead of replaying the workflow from the start, so a lawyer answering a question tomorrow does not re-trigger extraction and retrieval.
Retrieval — legal references embedded with Cohere embed-v4.0 (1536 dims) into Qdrant, then re-scored by a BAAI/bge-reranker-v2-m3 cross-encoder before anything reaches the model.
Document extraction — PyMuPDF for clean text, with a Gemini 2.0 Flash vision model as the fallback path for scanned filings and complex tabular layouts that defeat text extraction.
Provider factory — LLM and embedding backends behind one interface (OCI Cohere primary, OpenAI, Cohere, Ollama), so model choice is configuration rather than a code change.
Platform — FastAPI with async SQLAlchemy 2.0 over ~29 tables, two-tier RBAC (9 firm roles, 5 client roles, ~45 permissions), JWT with OTP/2FA, and Arabic-aware PDF generation via ReportLab with arabic-reshaper and python-bidi.

Key decisions
A LangGraph state machine with database-backed checkpoints
A prompt chain that runs start to finish
Legal drafting cannot complete in one pass — it stops to ask the lawyer for facts, and stops again for review. Checkpointing makes an interrupt durable: the run resumes at the node that paused instead of replaying extraction and retrieval, which would waste tokens and could produce a different draft the second time.
A cross-encoder reranker between retrieval and generation
Feeding top-k vector hits straight to the model
Embedding similarity in Arabic legal text surfaces passages that are topically close but legally wrong — a neighbouring article, a superseded provision. The reranker scores each candidate against the actual question, and being right about which article applies is the whole product.
A vision model as the fallback extraction path
Text extraction alone
Real filings arrive scanned, stamped, and laid out in tables. PyMuPDF handles the clean majority cheaply; routing only the documents it fails on to a VLM keeps cost proportional to difficulty instead of paying vision prices for every page.
Two-tier RBAC with permissions as data
Role checks scattered through route handlers
The firm and its clients see different slices of the same case, and ~45 permissions across 14 roles is past the point where inline checks stay correct. Centralising it means a new role is a data change, not an audit of every endpoint.
The hard part
Arabic legal retrieval. Legal Arabic is morphologically rich and heavily templated, so embeddings cluster documents that share phrasing rather than meaning — and the passages that share phrasing in this domain are precisely the ones that are easy to confuse: adjacent articles, amended provisions, and near-identical clauses from different regulations. Vector search alone returned plausible-looking references that a lawyer would reject. The fix was a two-stage retrieval path — recall from Qdrant, then precision from a cross-encoder that scores each candidate against the question rather than against the corpus — plus grounding every generated claim in a cited source so a reviewing lawyer can check the draft against the regulation rather than trusting it.
Results
Stack
Related field notes

MAJID
“Innovation distinguishes between a leader and a follower.”
Need an AI system that holds up in production? Tell me what breaks today and I'll tell you what I'd build.

