I Built a Legal OS for a Zimbabwean Law Firm — Here's What AI-Assisted Legal Research Actually Looks Like A developer built MutemoOS, a legal operating system for a Zimbabwean law firm, to address the limitations of generic legal AI tools in specific jurisdictions. The system uses a two-stage synthesis pipeline to prevent hallucinated citations and silent failures, and handles Zimbabwe-specific legal terminology and dual legal systems. It integrates FastAPI, ChromaDB, Laws.Africa KB API, and Anthropic Claude, deployed on Railway. My wife is a lawyer and partner at a law firm in Harare, Zimbabwe. She has been practising for over two decades, juggles her caseload with speaking engagements, and runs legal education programs on two radio stations three days a week. She was tracking 118 active matters in a diary and a notepad. That's not a criticism — she's been practising for over a decade and the system worked. But when I started building with AI tools, I kept thinking: what happens when she's sick? What happens when a junior associate needs to know the deadline on a matter she's handling? What happens when someone needs to research a legal question at 10pm before a morning hearing? So I built MutemoOS. Here's what I learned. Most legal research tools are built for BigLaw in London or New York. They're expensive, bandwidth-heavy, and tuned for English common law or US federal jurisdiction. They don't know what a dies induciae is. They've never heard of the Labour Court of Zimbabwe. The interesting engineering problems only show up when you're building for a specific jurisdiction: If you're building a legal AI for Zimbabwe, you can't just use a generic RAG pipeline and call it done. MutemoOS is a FastAPI backend with a React-style HTML frontend no framework, just vanilla JS , deployed on Railway. The core components: PostgreSQL — matters, documents, chunks, calendar events ChromaDB — vector store for semantic search firm collection Laws.Africa KB API — live Zimbabwe legislation + judgments sentence-transformers — all-MiniLM-L6-v2 embeddings Anthropic Claude — synthesis, grounding, document drafting Cloudflare R2 — document storage The Legal Intelligence Feed is a separate FastAPI service that scrapes ZimLII, Veritas, ZLHR, and NewsDay daily and pushes content to MutemoOS via a multi-instance pusher — built for multiple law firm clients. The first version of search was straightforward RAG: embed query → retrieve chunks → synthesise answer. It worked well when the right content was indexed. But it failed in two important ways: 1. Hallucinated citations. The model would confidently cite "section 12 3 of the Labour Act" when the retrieved chunks didn't actually contain that section. Lawyers can't use hallucinated citations. 2. Silent failures. When no relevant content was indexed, the model would fall back to general knowledge and present it as if it came from the sources. A lawyer reading the output had no way to know which claims were grounded and which weren't. The fix was a two-stage synthesis pipeline: Stage 1 — Ground check Claude Haiku, fast and cheap grounding = ground check sync query, context Returns: sources sufficient, source gap, grounding note Stage 2 — Constrained synthesis Claude Sonnet If sources insufficient, Sonnet is explicitly told what's missing and instructed to prefix unsupported claims with: " General principle — verify in source " answer = synthesise answer sync query, context, grounding The frontend renders a ✓ green badge when sources are sufficient, and a ⚠ yellow badge with a specific gap description when they're not: ⚠ Partial sources — Missing: Insolvency Act, Companies Act, Labour Act provisions on employee rights during liquidation This single change transformed how lawyers interact with the system. They now know exactly which parts of an answer to verify before relying on it. Zimbabwe legal terminology has a mismatch problem. A lawyer asks about "small houses" — a colloquial term for informal second relationships. The legislation calls them "civil partnerships" under section 41 of the Marriages Act Chapter 5:15 . The embedding model all-MiniLM-L6-v2 doesn't know this mapping. The problem runs deeper with customary law. Zimbabwe operates a dual legal system — general Roman-Dutch law and customary law — and a significant portion of legal practice involves both simultaneously. A client walks in and says "we paid lobola but never registered the marriage." That's not just a cultural statement — it maps to a specific legal regime under the Customary Marriages Act Chapter 5:07 , with distinct inheritance rules, maintenance claims, and estate administration procedures under the Administration of Estates Act. The system needs to understand that "lobola" and "roora" and "unregistered customary union" are all entries into the same legal framework, and that the applicable law differs materially from a civil marriage registered under the Marriages Act. The solution is a two-layer fallback: Layer 1 — Similarity threshold filtering. ChromaDB returns results below a similarity threshold 0.35 which are likely noise. Filter them out. When ChromaDB returns nothing above threshold, the FTS fallback fires: FTS fallback — word overlap + exact phrase bonus query words = set query lower.split - STOPWORDS for chunk in chunks: text lower = chunk "text" .lower word score = len query words & set text lower.split / max len query words , 1 phrase bonus = 0.5 if any w in text lower for w in query words if len w 4 else 0 score = word score + phrase bonus Layer 2 — Zimbabwe-specific query expansion. When both semantic and FTS fail, Claude Haiku expands the query with Zimbabwe legal synonyms: QUERY EXPANSION PROMPT = """You are a Zimbabwean legal research assistant. Key Zimbabwe-specific mappings: - "civil partner" / "unmarried couple" → "section 41 Marriages Act Chapter 5:15 unregistered union" - "Islamic marriage" → "qualified civil marriage section 44 Marriages Act polygamous union" - "small houses" → "section 41 civil partnership unregistered union Marriages Act" - "lobola" / "roora" / "customary marriage" → "Customary Marriages Act Chapter 5:07 unregistered union" - "unfair dismissal" → "Labour Act Chapter 28:01 due inquiry section 12" - "estate" / "deceased estate" → "Administration of Estates Act Chapter 6:01 Master of High Court" ... Expand this query with Zimbabwe legal synonyms. Return ONE line only. Query: {query}""" The result: searching "small houses" returns section 41 of the Marriages Act with a ✓ green badge and a complete answer about civil partnership protections under Zimbabwe law. This is the feature that matters most to practitioners. Every Zimbabwe procedural step has a deadline in working days, excluding weekends, public holidays, and — for Heads of Argument only — High Court recess periods. The High Court recess rule is specific: recess suspends the dies only for Heads of Argument. All other deadlines NITDs, plea, discovery run straight through recess. Building this as a hardcoded rule set: class CourtProcedureEngine: FIXED HOLIDAYS = { 1,1 , 2,21 , 4,18 , 5,1 , 5,25 , 8,11 , 8,12 , 12,22 , 12,25 , 12,26 } @classmethod def add working days cls, start, days, recess periods=None, suspend in recess=False : count = 0 recess hit = 0 d = start + timedelta days=1 while count < days: if cls. is working day d : in recess = suspend in recess and any r 'start' <= d <= r 'end' for r in recess periods or if in recess: recess hit += 1 else: count += 1 if count < days: d += timedelta days=1 return d, recess hit The recess periods are user-supplied the Registrar publishes the court calendar annually rather than hardcoded — a law firm inputs the recess dates once at the start of the court year and they apply to all matters automatically. When a lawyer selects "High Court Application" and enters a service date, the system calculates all deadlines, creates calendar events automatically, and flags critical deadlines in red: The most impactful single change was integrating the Laws.Africa Knowledge Base API. Instead of scraping Zimbabwe legislation and uploading PDFs manually, we now query their vector index directly: php async def search laws africa query: str, top k: int = 3 - list: payload = { "text": query, "top k": top k, "filters": {"commenced": True, "repealed": False, "principal": True} } for kb code in "legislation-zw", "judgments-zw" : resp = await http.post f"{LAWS AFRICA API}/{kb code}/retrieve", json=payload, headers={"Authorization": f"Bearer {token}"} Returns section-level chunks with source URL, Act title, section name Every search now queries three sources simultaneously: local ChromaDB firm precedents + uploaded documents , Laws.Africa legislation-zw all Zimbabwe Acts , and Laws.Africa judgments-zw all Zimbabwe judgments . The grounding check runs across all three. Colloquial language works. The query expansion approach means a lawyer can type exactly how they'd describe a problem to a colleague. "My client is a small house" gets the right legal framework. This was the biggest UX win — it turns out the barrier to using legal AI isn't the AI, it's the expectation that you need to speak like a statute. Grounding is more valuable than accuracy. A system that gives you a 90% accurate answer with no indication of what it doesn't know is more dangerous than a system that gives you a 75% accurate answer with a clear ⚠ badge saying "I'm missing the Insolvency Act." Lawyers can work with uncertainty. They can't work with invisible uncertainty. all-MiniLM-L6-v2 is a general-purpose model. It doesn't know that "qualified civil marriage" and "Islamic marriage" are the same thing in Zimbabwe law. The query expansion workaround helps, but the right long-term answer is a legal-domain embedding model. This is the next migration. text-embedding-3-small or legal-BERT The product is live. My wife uses it daily. A second firm is onboarding next month. If you're building legal tech for Africa or any underserved legal market, I'd genuinely like to compare notes. The problems are interesting and the competition is thin. Lenard Francis is the founder of Tofamba Technology, building MutemoOS legal practice management and AlertEngine human-authorized incident recovery for FastAPI . Based in Harare, Zimbabwe. @leoofharare