Skip to main content
All work

Air-gapped bilingual AI assistant for a government client

A bilingual English/Arabic generative AI assistant, taken from discovery to deployment on a government client’s air-gapped servers. Working in a team of three, I took on most of the technical delivery.

  1. Discovery
  2. Requirements
  3. Design
  4. Build
  5. Air-gap
  6. Deploy
  7. UAT handover

Context and constraints

A government client wanted a generative AI assistant its staff could use in English and Arabic. It had to handle chat, questions about their own documents, summaries, side-by-side comparison, translation, a searchable knowledge base, and voice in and out. All of it had to run on the client’s own servers, inside a fully offline network.

Those conditions drove most of the technical choices:

  • The environment is fully offline. Every model, package and container image had to be available inside the network, at a version fixed in advance.
  • The client set the baseline. Versions, ports and limits came from the client’s own specification, and the client’s infrastructure team owned the operating system and GPU driver layer.
  • All the models had to fit a fixed GPU budget on NVIDIA H200 hardware: chat, Arabic generation, safety checks, speech and embeddings.
  • Arabic had to work end to end, from reading PDFs through retrieval and prompts to speech.
  • Safety checks run on every message and every reply, so every answer the assistant gives has passed them.

My role

We were a team of three, and I took on most of the technical work. I was on site with the client in September and October 2026. Requirements and design work started in June 2026. From September the work moved to hardening, deployment and rollout, which ended with the handover at the end of October.

The parts I owned:

  • running the discovery sessions and drafting answers to the client’s open items
  • co-writing the requirements and design documents
  • converting the application to run fully offline, and the offline release tooling
  • a security self-review before deployment, and fixing what it found
  • the GPU benchmarks on the client’s server on 4 October 2026, and the Arabic model re-tune that followed
  • the October release deployments
  • the on-site code changes: the bilingual hybrid retrieval (dense and keyword search with reranking, Arabic stemming, cross-language lookup), guardrail upgrades, a scope gate that declines out-of-scope questions, and loading the knowledge base
  • troubleshooting the rollout, from fixing reversed Arabic text in PDFs to resyncing a server clock

The application itself started from an earlier internal product, which the team adapted for this client. The client’s infrastructure team prepared the servers and owned the operating system and GPU driver layer. My work sat above that layer, in model serving, the application and its releases. The handover documents were a team effort.

Discovery to requirements

I ran the discovery sessions with the client. The client then raised a long list of open items from discovery, and later a further set of comments on the business requirements. I drafted the answers to both. Where a limit applied, the answer set it out in writing, so expectations were clear early. One example: replies could not be guaranteed to match every Arabic dialect.

I co-wrote the business requirements (BRD), solution design (SDD) and low-level design (LLD) documents and revised them through client and partner review rounds. Other teams wrote the infrastructure and DevOps sections.

Architecture

Architecture of the bilingual assistant Simplified architecture. Inside an air-gapped client network, staff use a React web app and sign in through the client's directory with Keycloak. Each question goes to a FastAPI orchestrator, which checks approved answers first, then a scope gate, then routes the rest to retrieval against a Qdrant knowledge base with hybrid dense and keyword search and reranking. Guardrails check every message on the way in and every reply block on the way out, and refuse the request if a check cannot run. Self-hosted model serving on the client's GPUs, vLLM plus dedicated model servers, runs the chat, Arabic, safety, speech and embedding models. Prometheus, Grafana, central logs and Langfuse traces cover every service. Air-gapped client network Staff Browser, English or Arabic Sign-in Client’s directory, through Keycloak Web app React interface for chat, documents and voice Orchestrator FastAPI Approved answers Scope gate Routing Guardrails Check every message in and every reply block out, and fail closed Model serving Self-hosted on the client’s GPUs: vLLM and dedicated model servers Chat Arabic Safety Speech Embeddings Knowledge base Qdrant hybrid search: dense and keyword, then reranking Observability Prometheus and Grafana, central logs, and a Langfuse trace for every chat turn Architecture of the bilingual assistant Simplified architecture. Inside an air-gapped client network, staff use a React web app and sign in through the client's directory with Keycloak. Each question goes to a FastAPI orchestrator, which checks approved answers first, then a scope gate, then routes the rest to retrieval against a Qdrant knowledge base with hybrid dense and keyword search and reranking. Guardrails check every message on the way in and every reply block on the way out, and refuse the request if a check cannot run. Self-hosted model serving on the client's GPUs, vLLM plus dedicated model servers, runs the chat, Arabic, safety, speech and embedding models. Prometheus, Grafana, central logs and Langfuse traces cover every service. Air-gapped client network Staff Browser, English or Arabic Sign-in Client’s directory, through Keycloak Web app React interface for chat, documents and voice Orchestrator FastAPI Approved answers Scope gate Routing Guardrails Check every message in and every reply block out, and fail closed Model serving Self-hosted on the client’s GPUs: vLLM and dedicated model servers Chat Arabic Safety Speech Embeddings Knowledge base Qdrant hybrid search: dense and keyword, then reranking Observability Prometheus and Grafana, central logs, and a Langfuse trace for every chat turn

Select a part of the diagram to see what it does and which part was mine, or trace one question through the system. Use the arrow keys to move between parts.

Simplified view of the system.

Sign-in goes through Keycloak, which connects read-only to the client’s directory, and staff use a React web app. Every question goes to the orchestrator, a FastAPI service, which works through three steps. It first checks a short list of approved answers: questions the organisation answers in its own fixed words. Next, a scope gate declines questions outside the assistant’s remit. Everything else goes to retrieval and generation.

Guardrails sit on both sides of the model. A fast CPU filter and a self-hosted safety model check every incoming message, and every block of a reply is checked as it streams out. If a check cannot run, the request is refused.

The models run on the client’s own GPUs. vLLM serves the chat, Arabic, safety and speech-to-text models, and dedicated servers handle embeddings and text-to-speech. The knowledge base is a Qdrant collection with hybrid dense and keyword search and a reranker. Prometheus and Grafana cover metrics and dashboards, logs are collected centrally, and Langfuse keeps a trace of every chat turn with the message content redacted.

Key decisions

Offline builds from pinned inputs

Decision
Build every in-house container image with networking switched off, from inputs pinned to the client’s version baseline and checked by hash.
The other option
Build on a connected machine and let dependencies resolve at build time.
Why
On site there is no internet to fall back on. A build that passes with networking off proves nothing is missing, and pinned versions mean the image that was tested is the image that ships. Releases carried a checksum manifest so every image could be verified before it ran.

Approved answers before the scope gate

Decision
Answer a short list of questions with the organisation’s own fixed wording, before the scope gate or any model sees them.
The other option
Put the approved wording in the system prompt or the knowledge base and rely on the model to repeat it.
Why
A model paraphrases, and conflicting documents can pull it away from the approved text. For these questions the organisation needs its exact words every time, so a check that runs first returns the same sentence however the question is phrased.

Guardrails that fail closed

Decision
If an input or output check cannot run, refuse the request.
The other option
Fail open, so the assistant stays available while a safety service is down.
Why
For a government client, an unchecked answer costs more than a short outage. Output checks also run on each block of a streamed reply, not only on the finished answer.

Self-hosted models only

Decision
Remove the inherited hosted-model path from the code and serve every model on the client’s own GPUs.
The other option
Keep the hosted path in the code, switched off in configuration.
Why
A switched-off path is one setting away from being switched on, and in an air-gapped network it can only fail. With it gone, every model the assistant can call runs inside the network at a pinned version.

Deployment and rollout

The first job was getting the application to run fully offline. I removed the hosted model path, pinned every version to the client’s baseline and built every in-house container image offline. Before deployment I ran a security self-review of the code and fixed every verified high-severity finding, adding regression tests for the backend and frontend fixes.

I ran multiple release cycles on the client’s servers in a short timeline in October 2026. Most release records include checksums and rollback steps. Findings from the client’s acceptance testing went into releases the same week, including fixes to retrieval depth and to how the assistant handles a question built on a wrong premise.

Rollout needed two fixes, and the benchmarks led to one tuning change.

Making Arabic PDF text searchable

Arabic questions were finding only English sources. I traced it to the PDF reader used when loading the knowledge base, which stored Arabic text with the letters reversed, so even common Arabic words never matched. I switched to a different reader and re-indexed the knowledge base.

Bringing a server clock back in sync

A read-only check found one server’s clock running far past the few seconds of drift that sign-in tokens tolerate, before any sign-in failed. I pointed time sync at the client’s time servers, which brought the clock back within tolerance.

Re-tuning the Arabic model

On 4 October 2026 I benchmarked the deployed models with vLLM’s serving benchmark. After the first round I changed the Arabic model’s KV-cache precision and raised its limit on concurrent sequences. At the same load, its output throughput rose by more than a quarter. Every benchmark run that day for the chat, Arabic and safety models finished with no failed requests.

Handover

On 28 October 2026 the final release was deployed and handed over for user acceptance testing, together with the team’s handover documents: architecture, API, security, deployment, administration and troubleshooting guides, and user guides in English and Arabic.

Results

Each result sits next to the conditions it was measured or delivered under, and each figure comes from a single run. Load tests used simulated users.

Results, with the conditions of each
Metric Value Conditions Type
Release cadence Multiple release cycles in a short timeline Offline bundles to the client’s air-gapped servers, acceptance-test findings fixed the same week Delivered
Burst-load benchmark of the deployed models Every request succeeded vLLM serving benchmark at an unbounded request rate, chat, Arabic and safety models, on site Measured
Arabic model re-tune Over a quarter more output throughput Same load, after changing KV-cache precision and the concurrent-sequence limit Measured
Pre-deployment validation 500 concurrent simulated users, every request succeeded On test hardware (NVIDIA H200) before deployment, mixed speech-to-text, text-to-speech and embedding load Measured
Text-to-speech micro-batching Median latency from over a minute to under three seconds Measured on a standalone validation service on test hardware Measured
Security self-review Every verified high-severity finding fixed Before rollout, with regression tests for the backend and frontend fixes Delivered

What I’d carry into the next engagement

  • Build the evaluation question set before the first release, so every change, the Arabic re-tune included, is measured against the same yardstick from day one.
  • Agree who owns each day-two task (monitoring, upgrades, data changes) in the first release plan.
  • Use a release-record template with rollback steps built in.

How I worked

During development I used Claude Code as my main coding agent, under a written security plan for the engagement. A managed policy enforced the plan, and neither a user nor a repository could loosen it:

  • Claude Code could read code and propose edits. Shell, web, MCP and plugin tools were removed, so I ran every command myself.
  • Every edit came to me as a diff to approve, and the modes that skip approval were switched off.
  • Everything in the project except source code was classed as restricted. Deny rules blocked credential files and common data, document, log and media file types, so none of it could enter a session.
  • A firewall allowlist let it reach only Anthropic’s API and sign-in. Telemetry, feedback uploads and auto-updates were off, and the version was pinned.
  • Session transcripts stayed on the machine as the audit record.

A project rulebook (a CLAUDE.md file) set the invariants, data-handling rules and release gates, and a scripted gate of type checks, linting and automated tests ran before every release build.

Search and jump

Home Start page
Work All projects
Air-gapped bilingual AI assistant for a government client Case study
About How I work
Blog All posts
Context and constraints Case study section
My role Case study section
Discovery to requirements Case study section
Architecture Case study section
Key decisions Case study section
Deployment and rollout Case study section
Making Arabic PDF text searchable Case study section
Bringing a server clock back in sync Case study section
Re-tuning the Arabic model Case study section
Handover Case study section
Results Case study section
What I’d carry into the next engagement Case study section
How I worked Case study section
How I load-test self-hosted model serving before an air-gapped rollout November 3, 2026
I Stopped Using Basic RAG in 2026. Here's What Replaced It. March 16, 2026
Building a Multi-Agent Log Analytics Platform From Scratch March 14, 2026
The Secret Life of a Data Detective January 24, 2026
Why You Don't Use a Sledgehammer to Crack a Nut January 21, 2026
Building an Intelligent Document Extraction System with Vision-Language Models January 2, 2026
Building Enterprise Agentic AI Systems: Lessons from Production Voice Agents January 1, 2026
Open resume PDF, new tab
Send an email arnolps172@gmail.com
GitHub github.com/Arnol-P-S
LinkedIn linkedin.com/in/arnol-ps
Switch theme Light or dark