"In God We Trust. In Land We Verify."
The Problem Is Personal
In Kenya, land is not just an asset. It is identity. Families save for decades to buy a small shamba. It is the thing you show your children. The thing that says you made it.
And it is being stolen — systematically, at scale, with forged documents and complicit officials — from ordinary people who have no way to fight back.
The most brutal variant is called "air supply": a fraudster markets a plot of land that does not legally exist. They have a polished brochure, a site visit to a real-looking location, and a convincing sale agreement. The buyer pays. The title deed never comes. By the time they discover the fraud — sometimes years later — the money is gone and the legal system offers little recourse.
The Lesedi Developers scandal is the most notorious recent example: thousands of prospective homeowners lost over Sh1 billion to phantom estates in Nairobi's satellite towns. Juja. Ruiru. Thika. Real places. Fake plots.
This is the problem I set out to solve here using this Code
The Hackathon That Pushed Me to Build It
I submitted TitleTrust to the 2026 Google Gemini 3 Hackathon. The timing was right: Gemini 3 Pro's native multimodality, two-million-token context window, and "Thinking Mode" made it the first model powerful enough to reason about the chain of title in the way a forensic auditor would — not just retrieve facts, but detect what is missing, what is temporally impossible, and what is legally void.
This is not a chatbot wrapper. This is an autonomous investigation agent.
What TitleTrust Actually Does
Let me describe the product as a user experiences it, then I'll explain the engineering underneath each step.
1. The Investigator Opens a Session
A field investigator — a community advocate, a local official, a buyer's agent — opens TitleTrust on their phone and starts a new investigation. They upload the "deal pack": a Title Deed, a Green Card (the official registry history), a Mutation Form (subdivision records), and a Sale Agreement.
The session starts. A live timeline appears on screen. The agents go to work.
2. The Forensic Agent Reads the Documents
The ForensicEngine (backend/forensic_engine.py) takes the uploaded images and PDFs and runs them through Gemini 3 Pro with structured prompts that force chain-of-thought reasoning.
It does not just OCR the documents. It reasons about them.
It extracts every date, every transaction, every party name — and then checks:
- Can you transfer land before you legally own it? (No.)
- Can a Discharge of Charge predate the Charge itself? (No — but forged Green Cards try.)
- Is the Surveyor on the Mutation Form licensed? (Cross-referenced against gazette records.)
- Does the sum of the subdivided plots exceed the Mother Title's area? (If yes: oversubscription fraud.)
Why Gemini Thinking Mode matters here: standard LLMs hallucinate compromises when laws conflict. A County Zoning map might say "Residential" while the National Land Act says "Riparian Reserve." Gemini 3's include_thoughts=True forces the model to reason through the hierarchy — National Law overrides County Law — and produce a defensible verdict with a visible reasoning trace. That trace is not a nice-to-have. In this context, the reasoning trace is the product. An investigator, a lawyer, or a judge needs to see why the AI flagged something, not just that it did.
3. The Location Agent Checks Physical Reality
The GeospatialEngine (backend/geospatial_engine.py) answers a different question: does the land exist where they say it does?
The most insidious fraud variant is the bait-and-switch: the buyer is taken to a beautiful, flat, accessible plot. The title deed they receive is for a swamp 5km away.
The investigator stands on the land, captures GPS and photos. The GeospatialEngine:
- Validates GPS traces against the parcel geometry from the deed
- Runs plausibility checks: distance-to-boundary, location confidence heuristics
- Detects riparian vegetation patterns that suggest the plot is in a protected river reserve
- Emits a
geospatial_verificationevent: either "Location verified" or "Location mismatch — please re-scan beacons"
If the Solar API data shows the claimed plot is in a flood zone. If the 30-metre riparian buffer overlay shows 60% of the plot is legally unbuildable. The investigator knows before they pay.
4. The Orchestrator Keeps the Investigation Moving
The MarathonLoop (backend/agent/marathon_loop.py) is the job state machine that coordinates everything. It:
- Starts when a session is created
- Advances through investigation stages
- Decides what to check next (without the investigator having to think about it)
- Retries failed API calls with exponential backoff
- Escalates to the user when human input is genuinely needed: "Please provide a clearer photo of the beacon in the northeast corner"
It is designed to behave like an assistant-led investigation. The field investigator should not need to understand conveyancing law. They follow prompts. The system reasons.
5. The Mobile Client Shows Everything, Live
The Flutter client maintains a live, deduplicated, sequence-aware timeline of all agent activity. Every finding. Every evidence registration. Every verification step.
This is not just UX polish. For an investigation tool, the live timeline is the audit trail that an investigator presents to authorities. It needs to be complete, ordered, and reproducible — even if the phone lost signal for ten minutes in the middle of a rural field check.
The Engineering Architecture — Every Decision Tied to the Product
Here is where most case studies go wrong: they describe the architecture and then separately describe the product. In TitleTrust, every engineering decision was made because of a product constraint. Let me walk through them.
Why SSE Instead of WebSockets
The field investigator is standing in Juja, on cheap mobile data, with an intermittent signal. WebSockets require a persistent bidirectional connection. When it drops, you rebuild state from scratch.
SSE (Server-Sent Events) with Last-Event-ID gives you something better: the browser (and Flutter client) automatically reconnects and sends the last event ID it received. The server replays from exactly that point. The investigator's timeline heals itself without them noticing.
For a progress-update use case — which is all we need for server→client communication — SSE is simpler, more resilient on mobile networks, and natively supports replay semantics.
Why Redis Streams (Not Just a Message Queue)
A standard message queue delivers messages and forgets them. That is fine for background jobs. It is not fine for an investigation audit trail.
Redis Streams is an append-only, ordered log. Every event is stored with a stable offset. When the mobile client reconnects after a signal drop, the server can replay the exact sequence of agent actions from the last confirmed point.
More importantly: after an investigation closes, a lawyer needs to reconstruct exactly what the agent found, in what order, with what evidence. Redis Streams is that record. It is not just infrastructure — it is the chain of custody.
Why Two Cursors (event_id + stream_offset)
Every event carries two identifiers:
-
event_id: a stable application-level UUID the client tracks across reconnections -
stream_offset: the Redis Streams position for efficient server-side seeks
The client knows event_id. The server knows stream_offset. The resume logic maps between them.
Why both? Because event_id survives Redis restarts and re-ingestions — it is stable application identity. stream_offset gives the server an efficient seek into the durable log. Without both, you either replay too much (wasteful) or risk replaying from the wrong point (incorrect).
For an investigation where every event is a piece of evidence, incorrect replay is a correctness bug, not just a performance bug.
Why In-Process Broadcaster + Redis Streams (The Hybrid)
The Broadcaster (backend/realtime/broadcaster.py) maintains both an in-memory local fanout queue and a durable Redis Streams append.
The in-memory queue delivers events to the SSE client in milliseconds — even when Redis has a hiccup. The Redis Streams append persists the event for replay and audit.
In degraded mode (Redis unavailable), the system flips a flag, continues local delivery, and the client can recover authoritative state from Firestore when connectivity returns. The investigator's timeline keeps updating. They never see a spinner.
The product constraint driving this: an investigator doing a site visit in a low-connectivity area should not have their session stall because a Redis instance is momentarily unreachable. Availability for the user is non-negotiable. But so is the audit trail. The hybrid gives you both.
Why Evidence Gets SHA256 Checksums and Trace IDs
Every piece of evidence the ForensicEngine registers — every photo, every document analysis result — gets:
- A SHA256 checksum of the content
- A
trace_idlinking it across services - A
sequence_idfor ordering within the session
This is not engineering over-engineering. This is chain of custody. If an investigator presents TitleTrust findings to a Land Control Board or a court, the evidence must be verifiable as unmodified and correctly attributed. The checksum proves content integrity. The trace ID proves provenance. The sequence ID proves ordering.
Why Deterministic Chaos Tests
Before any public deployment of a system that people will rely on to protect their life savings, I needed to be able to prove the system behaves correctly under failure — not just hope it does.
The test harness (tests/test_realtime_chaos.py, tests/support/fake_redis.py) includes:
- A fake Redis that can be programmatically failed, truncated, or made to reject writes
- A failure injector that simulates xadd failures, publish delays, and partial persistence
- Tests that validate: sequence monotonicity, correct replay after Redis restart, fallback to local buffer, client convergence to authoritative state after gap detection
Real bugs surfaced in this harness that would never have appeared in happy-path testing:
- Prometheus collector collisions when test instances reused metric names
- Replay mismatches when clients passed a non-Redis ID as the resume token
- Accidental use of non-durable items as the authoritative replay source
The product argument for this: stochastic integration tests find bugs sometimes. Deterministic failure injection finds bugs reproducibly. For a correctness-critical system, reproducible is the only acceptable standard.
Architecture Diagram
┌─────────────────────────────────────────────────────────┐
│ Flutter Mobile Client │
│ RealtimeController: dedupe, sequence, gap detection │
│ RecoveryCoordinator: authoritative Firestore recovery │
└────────────────────┬────────────────────────────────────┘
│ SSE (Last-Event-ID)
▼
┌─────────────────────────────────────────────────────────┐
│ FastAPI Backend │
│ /realtime/sse · /realtime/last-state/{session_id} │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Broadcaster │
│ In-process bounded queues (low-latency local fanout) │
│ Redis Pub/Sub (cross-instance fanout) │
│ Redis Streams (durable ordered log + replay) │
└──────┬──────────────────────┬───────────────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────────────────┐
│ Redis │ │ Agent Workers │
│ Streams │ │ MarathonLoop (orchestrator) │
│ (durable │ │ ForensicEngine (vision/docs) │
│ log) │ │ GeospatialEngine (GPS/maps) │
└─────────────┘ └──────────────┬───────────────────┘
│
▼
┌──────────────────┐
│ Firestore │
│ (canonical │
│ session state) │
└──────────────────┘
A Day in the Life: One Investigation
To make this concrete, here is the complete flow for a single field check:
- Investigator uploads a deal pack (Title Deed, Green Card, Mutation Form) via the Flutter app.
-
Session created in Firestore. MarathonLoop starts. A
session_startedevent is emitted, broadcast locally, appended to Redis Streams. -
ForensicEngine runs: Gemini 3 Pro reads the Green Card. It finds
Entry #4: Charge to Equity Bank, 12/01/2018andEntry #6: Discharge of Charge, 10/01/2018. The Discharge predates the Charge. Temporal anomaly flagged. Evidence registered with SHA256 checksum and trace ID.evidence_registeredevent emitted. - The timeline on the investigator's phone updates instantly via SSE. They see: "⚠️ Temporal anomaly detected — Discharge of Charge predates Charge. Likely forgery."
-
Investigator walks the plot boundary and captures GPS + photos. GeospatialEngine validates location. Finds: plot coordinates overlap 60% with the Athi River 30m riparian buffer. Critical risk flagged.
geospatial_verificationevent emitted. -
Phone signal drops for 4 minutes. Client reconnects with
Last-Event-ID. Server maps it to Redis stream offset. Replays the 3 events the client missed. Timeline is complete. -
Investigation complete. MarathonLoop emits
investigation_completewith a risk score of 91 (CRITICAL). The full event log — ordered, checksummed, traceable — is the audit trail the investigator presents to the Land Control Board.
What I Used Gemini 3 For Specifically
This is important because I did not use Gemini 3 as a chatbot. I used it as a reasoning engine embedded in a structured investigation workflow.
Thinking Mode (include_thoughts=True) — for legal conflict resolution. When a County Zoning map conflicts with the National Land Act, I cannot use a black-box verdict. The reasoning trace showing why one law overrides another is what makes the output usable in a legal context.
Native multimodality — for reading handwritten Green Cards. Kenya's registry history is often handwritten, sometimes in cursive, on physical cards. Gemini 3 Pro reads these without a separate OCR step, preserving spatial context (stamps over signatures, marginal notes) that traditional OCR loses.
2M token context window — for tracing chain of title through decades of subdivisions. A Mother Title in Kiambu might have 50+ years of subdivision history. Fitting the complete legal history in a single context window enables the kind of deep chronological reasoning that was previously only possible for senior conveyancing lawyers charging Ksh 10,000+ per review.
Structured outputs — for emitting machine-readable findings. Every ForensicEngine result is a typed JSON finding, not free text. This is what allows the mobile timeline to render findings as UI components rather than just paragraphs.
The Numbers
| What | Why it matters |
|---|---|
| ~Ksh 500 per basic forensic check | vs Ksh 5,000–10,000 for a lawyer |
| ~3 minutes for a full audit | vs 30 days for traditional due diligence |
| SHA256 checksums on all evidence | Chain of custody for legal proceedings |
| Deterministic chaos tests | Correctness provable, not assumed |
| Degraded mode (Redis down) | Investigators never lose session continuity |
Key Engineering Takeaways
1. Tie every infrastructure decision to a user outcome.
Redis Streams exists because investigators need an audit trail, not because append-only logs are cool. SSE exists because mobile reconnection in rural Kenya needs to be seamless. When you can answer "why does this exist?" with a user story, your architecture stays honest.
2. The reasoning trace is the product.
For AI systems used in high-stakes decisions — land fraud, medical diagnosis, legal analysis — the output is not the verdict. The output is the evidence behind the verdict. Design your AI integration accordingly.
3. Deterministic failure injection is the only way to prove correctness.
Stochastic tests find bugs sometimes. Deterministic failure injection finds them reproducibly. For a system that people will rely on to protect their life savings, "probably works" is not good enough.
4. Hybrid realtime (local fanout + durable stream) is the right model for mobile-first AI systems.
Local in-memory fanout keeps UX responsive under partial failures. Redis Streams provides the durable, replayable log for recovery and audit. You do not have to choose between fast and correct.
What's Next
- Shadow Registry: a crowdsourced, hashed double-allocation detector. When two independent investigators verify the same plot, the system flags a potential double sale — without exposing either party's identity until a match is confirmed.
- Case Law RAG: grounding forensic reasoning in Kenya Law Reports (eKLR) for land dispute precedents.
- Blockchain title anchoring: publishing investigation hashes to an immutable ledger so findings cannot be retroactively modified.
- Hardware-backed device attestation: for high-assurance deployments where the physical device must be cryptographically verified.
The Repo
The codebase is organized around clear responsibility boundaries:
| File | Purpose |
|---|---|
backend/realtime/broadcaster.py |
In-process fanout, Redis Pub/Sub, degraded mode |
backend/realtime/store.py |
Redis Streams event store, two-cursor resume logic |
backend/agent/marathon_loop.py |
Orchestration, job lifecycle, event emission |
backend/forensic_engine.py |
Vision analysis, evidence registration, checksumming |
backend/geospatial_engine.py |
GPS/parcel validation, spatial event emission |
frontend/titletrust/lib/realtime/realtime_controller.dart |
Dedupe, sequence tracking, authoritative recovery |
tests/test_realtime_chaos.py |
Deterministic chaos suite |
tests/support/fake_redis.py |
Programmatic Redis failure injection |
Final Thought
Land fraud in Kenya is not a technology problem. It is a power problem: the people who commit it have access to systems, officials, and legal processes that ordinary buyers do not. TitleTrust does not fix that power imbalance by itself.
But it gives ordinary people — investigators, buyers, community advocates — the same forensic tools that a senior lawyer and a licensed surveyor would use. For Ksh 500 and three minutes instead of Ksh 10,000 and thirty days.
That is the point. The engineering exists to serve that point.
If you are building AI systems for high-stakes decisions in emerging markets, I would love to talk. The problems are real, the constraints are severe, and the standard tooling assumptions often do not hold.
Built with Gemini 3 Pro, FastAPI, Flutter, Redis Streams, and Firebase. Submitted to the Google Gemini 3 Hackathon, 2026.
Top comments (0)