cd /news/artificial-intelligence/i-built-a-legal-os-for-a-zimbabwean-… · home topics artificial-intelligence article
[ARTICLE · art-49299] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=↑ positive

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.

read8 min views3 publishedJul 7, 2026

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:

grounding = ground_check_sync(query, context)

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:

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 up PDFs manually, we now query their vector index directly:

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}"}
        )

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

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @mutemoos 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/i-built-a-legal-os-f…] indexed:0 read:8min 2026-07-07 ·