● Setup and training guide · DIFC-regulated wealth management firm · updated 7 October 2026

AI for a DIFC wealth firm, running on a server in your own office

The full system: the machine in the cabinet, the code that runs on it, the DIFC and DFSA rules it meets, the twelve jobs it does with an adviser signing each one, and the training that lets your own team run it. No client name, document or number ever reaches the internet.

Setup or training: pick a track Download the server kit (.zip) Full guide PDF Run the 15-minute demo Try the private desk
New Checklist PDF Checklist HTML (tick online)
0
client records on the internet
12
workflows, adviser signs each one
5
services on one office server
6 yrs
encrypted records, two drives
4
training tracks, one per role
● 1 · Two ways to start

Setup, training, or both

Same system, same rules. The only difference is who builds it.

Track A · Setup

I build it in your office

  • Server installed, encrypted and cut off from the internet
  • Twelve workflows tested on sample clients, then on yours
  • AI policy, AI register and impact assessment written with your compliance officer
  • Every login handed back at go-live, runbook and recorded walkthrough
Best for: firms with no in-house IT. Four phases, first pilot in week 3.
Track B · Training

Your team builds and runs it

  • Leadership: 90 minutes on what to approve and what to refuse
  • Advisers: half a day on the desk, drafts and checking sources
  • Compliance: half a day on the register and audit-log sampling
  • IT: a full day on the server, backups and restore tests, using the kit below
Best for: firms with an IT person or managed provider. Taught at your DIFC office. See the four tracks
Most firms do both: Track A for the build, then Track B so the people who use it every day know how it works and where the limits sit. The DFSA expects the firm, not the supplier, to understand and own the system.
● 2 · What it looks like in the office

One cabinet, two networks

A five to ten person DIFC office needs one locked cabinet, one safe and one desk that stays on the normal internet. Nothing else changes for visitors or clients.

Office floor plan: adviser desks, compliance office and server cabinet on the offline World 1 network, and a separate internet desk on World 2 Meeting room no screens with client data Compliance office PC · World 1 sees all clients Locked cabinet AI safe Internet desk PC · World 2 no client names router Advisers · open plan Adviser 1 Adviser 2 Adviser 3 Para-planner Analyst Ops / admin W1 switch Reception guest Wi-Fi on the router, never on W1 World 1 · offline, client data World 2 · internet, no client data W1 cable no cable crosses this line
Floor plan, not to scale. Green cables run only between World 1 machines. The router serves the internet desk and guest Wi-Fi and has no port into the World 1 switch.
Inside the locked cabinet: patch panel, World 1 switch, AI server, UPS, with backup drive A in the safe and drive B off site Patch panel World 1 switch AI server Ubuntu 24.04 · LUKS 1 or 2 GPUs · 2 TB NVMe ollama · model postgres · app · caddy /srv/wealth (encrypted) Backup drive slot UPS 15 min on battery, clean shutdown no WANport used safe: drive A off site: drive B Drives swapevery Monday
Small office build. A Mac Studio with 192 GB+ memory can take the place of the GPU server. Ollama then runs natively on macOS and the other services stay in Docker.

Hardware for World 1

SizeMachineFitsModel that runs wellPlanning range
SmallMac Studio, 192 GB+ unified memory, or a workstation with one 48 GB professional GPU5 to 10 staff, documents and draftsgpt-oss:120b or qwen3:32bAED 20k to 50k
MediumRack server with one or two 80 GB GPUs, 256 GB RAM10 to 40 staff, many documents a daygpt-oss:120b with vLLMAED 150k and up
BothUPS, managed switch, two 4 TB encrypted USB drives, small safeEvery buildn/aAED 4k to 8k
Model licences (Apache 2.0 for gpt-oss and Qwen3) reviewed once by counsel. Sizing guide: Iternal · models: Context Studios · prices are planning ranges, Dubai retail, October 2026.
● 3 · Technical map

How a request moves through the server

Staff open desk.office.lan in a browser. Only the HTTPS gateway faces the office network. The model and the database sit on an internal Docker network with no route out, not even to the LAN.

Network map: staff PCs reach the Caddy HTTPS gateway, which passes to the workflow app and private chat; these reach the model and database on an internal network; files and backups stay on encrypted disks; World 2 connects only by an approved encrypted transfer drive World 1 · office LAN 192.168.50.0/24 · no internet Advisers Compliance Ops / IT browser only caddy HTTPS :443 staff login adds X-User app · wealth desk FastAPI :8000 12 jobs · queue who sees which client chat · Open WebUI web search off network "core" · internal · no route out ollama gpt-oss:120b on the GPU postgres clients · jobs · tasks audit log, append only temp 0.1 · drafts only · every call logged /srv/wealth clients/C-0042/… LUKS encrypted auditd on every read backup drive restic, 22:00 nightly AIR GAP · WAN CABLE REMOVED World 2 · internet model downloadscontainer imagesplatform exportsmarket news, researchregulator updates never: client names,documents, numbers encrypted transfer drive logged, ops only one way in: models,images, exports.Approved emails goout as printed text.
All five services are defined in one file, docker-compose.yml, in the kit. The names in the boxes match the service names in that file.
● 4 · Safeguarding the data

Two worlds, no link between them

The one rule everything hangs on: client data never touches the internet. Anything with a client in it lives in World 1. Public work lives in World 2. Nothing crosses except on an approved encrypted drive.

World 1 · offline, inside the DIFC office

Client data and the AI that reads it

  • File server: encrypted at rest, one folder per client (KYC, fact-find, suitability, portfolio, correspondence, reviews)
  • Database: client register, pipeline stages, follow-up tracker, AI audit log
  • AI model: open-weight, on the server in the cabinet. Downloaded once in World 2 and carried in
  • Backups: two encrypted drives, one in the office safe, one off site, swapped weekly. Six-year retention
  • Access: advisers see their own clients only, compliance sees all, every open and edit logged
  • Rules: no cloud sync, no USB without approval, laptops encrypted, nothing to personal email
NO LINK
World 2 · the normal internet desk

Public work, no client names

  • Prospect research, market news, regulator updates
  • The prompts and skills on azizsaif.com/ai-wealth
  • Platform exports downloaded here, carried to World 1 on the transfer drive
  • Approved client emails typed or pasted on the mail desk from the signed draft
  • Written rule in the AI policy: no client name, document or account number, ever

What the firm gives up offline, and the answer

  • No web research in World 1. Done in World 2 with no client names.
  • No email or drive connectors in World 1. Approved emails are copied to the normal mail desk for sending.
  • A slightly weaker model than the best cloud models. For documents, drafts and summaries at a 5 to 10 person firm, it is enough.
  • Someone owns the machine. Updates, backups and the drive swap: the firm's IT support, trained in Track B, or a monthly visit.
● 5 · The rules this meets

DIFC and DFSA, line by line

RuleWhat it asksWhat this design doesWhere in the buildRead more
DIFC Data Protection Law No. 5 of 2020The firm is the controller: lawful basis, security measures, data subject rights, breach reportingData never leaves the firm. Access logged. Retention set per client01-setup-server.sh, retain_until columnDIFC Commissioner
DIFC DP Regulation 10 (autonomous and semi-autonomous systems)Tell clients an AI system is used and name it. Keep a human in control. Keep records. High-risk uses may need an Autonomous Systems Officer and Commissioner approvalAI drafts, adviser signs. A named line in the client agreement. The audit log is the record. Onboarding assessed before go-livejobs table status, ai_audit_logRegulation text · Clyde & Co
DFSA GEN 5.3 systems and controls, outsourcing 5.3.21 and 5.3.22Written agreement with any supplier, risk assessment, contingency and exit plan, firm stays responsibleWritten scope and NDA, all logins handed back, runbook and Track B training so the firm runs it aloneREADME.md runbookDFSA GEN module
DFSA record keepingClient files, suitability reports and logs kept and retrievable. Six years is the period cited for governance recordsServer plus two offline backups kept 72 months, restore tested quarterlyops/backup.sh, ops/restore-test.shDFSA GEN module
DFSA AI Survey 2025 expectationsWritten AI policy, clear accountability, an AI register. 26% of firms using AI in critical areas had no governancePolicy, register and named owner written in phase 1, before any AI runsclaude/CLAUDE.md, register templateDFSA survey
Read this first: these are summaries, not the rulebook. The firm's compliance officer confirms each line against the current DFSA Rulebook and DIFC Commissioner guidance before sign-off. That review is built into phase 1.
Document 1

AI-use policy

What AI may do, what it may never do, who signs off, how clients are told, what happens on a breach.
Document 2

AI register

One line per use: purpose, data touched, human owner, risk level, date reviewed. Each line maps to one key in app/jobs.py.
Document 3

Impact assessment

For onboarding and KYC first, since that is where personal data is heaviest. Repeated for each new workflow.
● 6 · Build the local server

Nine steps, with the code

Every file below is in the server kit. Tap a file name to open the code. It is sample code: your IT support and compliance officer review it before live use. Track progress on the development checklist (PDF).

Day 1 · World 1 server

Install an encrypted server, then roles and folders

Ubuntu Server 24.04 LTS, full-disk encryption ticked in the installer. The script creates three roles, the folder tree, an idle lock and an audit watch on every client file.

01-setup-server.sh bash
#!/usr/bin/env bash
# World 1 server setup. Ubuntu Server 24.04 LTS, run once as root.
# Disk encryption (LUKS) is chosen in the Ubuntu installer, before this script.
set -euo pipefail

# 1. Roles: advisers see their own clients, compliance sees all, ops runs the box
groupadd -f advisers; groupadd -f compliance; groupadd -f ops

# 2. Folder tree. One folder per client is created later by ops/new-client.sh
install -d -m 0750 -g compliance /srv/wealth/{clients,models,inbox,audit,exports}
install -d -m 0770 -g advisers   /srv/wealth/inbox
chmod 1770 /srv/wealth/inbox            # sticky: staff cannot delete each other's drops

# 3. Packages that work offline once installed
apt-get update
apt-get install -y docker.io docker-compose-v2 acl restic cryptsetup jq ufw postgresql-client
systemctl enable --now docker

# 4. Auto-lock idle sessions and log every login
echo 'TMOUT=900; readonly TMOUT; export TMOUT' > /etc/profile.d/idle-lock.sh
systemctl enable --now auditd 2>/dev/null || apt-get install -y auditd
auditctl -w /srv/wealth/clients -p rwa -k client_files   # every open and edit, logged

# 5. Firewall: run 02-firewall.sh next, then unplug the WAN cable for good
echo "Done. Next: ./02-firewall.sh"
Day 1 · World 1 server

Block the internet, prove it, unplug it

Deny all outgoing traffic, then block containers in the DOCKER-USER chain, since Docker skips ufw. The last line must print OK: no internet. Screenshot it for the compliance file, then pull the WAN cable.

02-firewall.sh bash
#!/usr/bin/env bash
# Second layer behind the physical air gap. The office LAN is 192.168.50.0/24.
set -euo pipefail
LAN=192.168.50.0/24

ufw --force reset
ufw default deny incoming
ufw default deny outgoing
ufw allow from $LAN to any port 443 proto tcp   # the dashboard, over HTTPS
ufw allow from $LAN to any port 22  proto tcp   # ops only, key login
ufw allow out to $LAN
ufw --force enable

# Docker writes its own rules and skips ufw. Block container traffic leaving the LAN.
iptables -I DOCKER-USER -d $LAN -j RETURN
iptables -I DOCKER-USER 2 -d 172.16.0.0/12 -j RETURN
iptables -A DOCKER-USER -j DROP
apt-get install -y iptables-persistent && netfilter-persistent save

# Proof for the compliance file: this must FAIL
curl -s --max-time 5 https://example.com && echo "WARNING: internet reachable" || echo "OK: no internet"
Day 2 · World 2 desk, then the transfer drive

Carry the model and images in

On the internet desk: ollama pull gpt-oss:120b, docker compose build, then docker save every image to the encrypted transfer drive. On the server: docker load and copy the model folder to /srv/wealth/models. Ops logs the drive in and out.

.env.example env
# Copy to .env and change every value. Never commit .env.
POSTGRES_PASSWORD=change-me-long-random
MODEL=gpt-oss:120b          # small office: gpt-oss:20b or qwen3:32b
OLLAMA_URL=http://ollama:11434
# Mac Studio: run Ollama natively, then use
# OLLAMA_URL=http://host.docker.internal:11434
Day 2 · World 1 server

Start five services with one file

docker compose up -d. The model and database live on the core network marked internal: true. Only Caddy publishes a port, and only to the office LAN.

docker-compose.yml yaml
# World 1 stack. Start with: docker compose up -d
name: wealth-desk

networks:
  core:              # model and database. No route out, not even to the LAN
    internal: true
  lan:               # only Caddy publishes a port to the office network
    driver: bridge

services:
  ollama:            # the AI model, on the firm's own GPU
    image: ollama/ollama:latest
    networks: [core]
    volumes: ["/srv/wealth/models:/root/.ollama"]
    environment: ["OLLAMA_KEEP_ALIVE=24h"]
    deploy:
      resources:
        reservations:
          devices: [{driver: nvidia, count: all, capabilities: ["gpu"]}]
    restart: unless-stopped

  db:                # client register, review queue, audit log
    image: postgres:16
    networks: [core]
    environment:
      POSTGRES_DB: wealth
      POSTGRES_USER: wealth
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - /srv/wealth/db:/var/lib/postgresql/data
      - ./db/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
    restart: unless-stopped

  app:               # the workflow dashboard (drop, draft, review, approve, file)
    build: ./app
    networks: [core, lan]
    environment:
      DATABASE_URL: postgresql://wealth:${POSTGRES_PASSWORD}@db:5432/wealth
      OLLAMA_URL: ${OLLAMA_URL}
      MODEL: ${MODEL}
      CLIENT_ROOT: /srv/wealth/clients
    volumes:
      - /srv/wealth/clients:/srv/wealth/clients
      - /srv/wealth/inbox:/srv/wealth/inbox
    depends_on: [db, ollama]
    restart: unless-stopped

  chat:              # Open WebUI: a private ChatGPT-style screen for staff
    image: ghcr.io/open-webui/open-webui:main
    networks: [core, lan]
    environment:
      OLLAMA_BASE_URL: ${OLLAMA_URL}
      ENABLE_SIGNUP: "false"
      ENABLE_WEB_SEARCH: "false"
      OFFLINE_MODE: "true"
    volumes: ["/srv/wealth/webui:/app/backend/data"]
    restart: unless-stopped

  caddy:             # HTTPS on the office LAN, staff login, passes the user name on
    image: caddy:2
    networks: [lan]
    ports: ["443:443"]
    volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "/srv/wealth/caddy:/data"]
    restart: unless-stopped
Caddyfile caddy
# Internal certificate. Install Caddy's root cert on office PCs once.
{
  local_certs
}

desk.office.lan {
  basic_auth {
    # caddy hash-password --plaintext 'their-password'
    sara   $2a$14$REPLACE_WITH_HASH
    omar   $2a$14$REPLACE_WITH_HASH
    comply $2a$14$REPLACE_WITH_HASH
  }
  reverse_proxy app:8000 {
    header_up X-User {http.auth.user.id}
  }
}

chat.office.lan {
  reverse_proxy chat:8080
}
Day 3 · database

The register, the queue and the audit log

Loaded on first start. The audit log refuses updates and deletes, so nobody can tidy it later. Each client gets a retain_until date six years from opening.

db/schema.sql sql
-- Who may see what
CREATE TABLE staff (
  username   text PRIMARY KEY,
  full_name  text NOT NULL,
  role       text NOT NULL CHECK (role IN ('adviser','compliance','ops'))
);

CREATE TABLE clients (
  client_id  text PRIMARY KEY,               -- e.g. C-0042, never the name
  adviser    text NOT NULL REFERENCES staff,
  stage      text NOT NULL DEFAULT 'enquiry', -- enquiry, kyc, suitability, active, review
  risk_level text,
  opened_on  date NOT NULL DEFAULT current_date,
  retain_until date GENERATED ALWAYS AS (opened_on + interval '6 years') STORED
);

-- Every AI job waits here until a person approves it
CREATE TABLE jobs (
  id          bigserial PRIMARY KEY,
  job_type    text NOT NULL,
  client_id   text REFERENCES clients,
  source_file text NOT NULL,
  draft       text,
  status      text NOT NULL DEFAULT 'drafted' CHECK (status IN ('drafted','approved','rejected')),
  created_by  text NOT NULL REFERENCES staff,
  approved_by text REFERENCES staff,
  created_at  timestamptz NOT NULL DEFAULT now(),
  decided_at  timestamptz
);

CREATE TABLE tasks (                         -- staff follow-up tracker
  id        bigserial PRIMARY KEY,
  client_id text REFERENCES clients,
  owner     text NOT NULL REFERENCES staff,
  action    text NOT NULL,
  due_on    date NOT NULL,
  done      boolean NOT NULL DEFAULT false
);

-- The record the regulator reads. Append only.
CREATE TABLE ai_audit_log (
  id          bigserial PRIMARY KEY,
  at          timestamptz NOT NULL DEFAULT now(),
  username    text NOT NULL,
  action      text NOT NULL,                  -- draft, approve, reject, view
  job_id      bigint,
  model       text,
  prompt_sha  text,
  output_sha  text,
  note        text
);
REVOKE UPDATE, DELETE ON ai_audit_log FROM PUBLIC;
CREATE RULE no_update AS ON UPDATE TO ai_audit_log DO INSTEAD NOTHING;
CREATE RULE no_delete AS ON DELETE TO ai_audit_log DO INSTEAD NOTHING;
Days 3 to 5 · the workflow app

Drop, draft, review, approve, file

About a hundred lines of Python. An adviser drops a file, the model drafts with page references, the draft waits in the queue, and only an approve call writes it into the client folder. Every step is hashed into the audit log. Advisers see their own clients. Compliance sees all.

app/main.py python
"""Wealth desk: drop a file -> AI drafts -> review queue -> adviser approves -> filed + logged."""
import hashlib, os, pathlib, httpx, psycopg
from fastapi import FastAPI, Header, HTTPException, UploadFile, Form
from pypdf import PdfReader
from jobs import HOUSE_RULES, JOBS

DB = os.environ["DATABASE_URL"]
OLLAMA = os.environ["OLLAMA_URL"]
MODEL = os.environ["MODEL"]
ROOT = pathlib.Path(os.environ["CLIENT_ROOT"])
app = FastAPI(title="Wealth desk (World 1)")

def sha(t: str) -> str: return hashlib.sha256(t.encode()).hexdigest()

def log(cur, user, action, job_id=None, prompt="", output="", note=""):
    cur.execute("INSERT INTO ai_audit_log(username,action,job_id,model,prompt_sha,output_sha,note)"
                " VALUES (%s,%s,%s,%s,%s,%s,%s)",
                (user, action, job_id, MODEL, sha(prompt), sha(output), note))

def role_of(cur, user):
    row = cur.execute("SELECT role FROM staff WHERE username=%s", (user,)).fetchone()
    if not row: raise HTTPException(403, "unknown user")
    return row[0]

def may_see(cur, user, client_id):
    if role_of(cur, user) == "compliance": return True
    row = cur.execute("SELECT adviser FROM clients WHERE client_id=%s", (client_id,)).fetchone()
    return bool(row) and row[0] == user

def read_text(f: UploadFile) -> str:
    if f.filename.lower().endswith(".pdf"):
        pages = PdfReader(f.file).pages
        return "\n".join(f"[p.{i+1}]\n{p.extract_text() or ''}" for i, p in enumerate(pages))
    return "[p.1]\n" + f.file.read().decode("utf-8", "replace")

@app.post("/jobs")
def new_job(job_type: str = Form(...), client_id: str = Form(...), file: UploadFile = None,
            x_user: str = Header(...)):
    if job_type not in JOBS: raise HTTPException(400, "unknown job")
    with psycopg.connect(DB) as con, con.cursor() as cur:
        if not may_see(cur, x_user, client_id): raise HTTPException(403, "not your client")
        prompt = f"{HOUSE_RULES}\n\nTASK: {JOBS[job_type]}\n\nDOCUMENT:\n{read_text(file)}"
        r = httpx.post(f"{OLLAMA}/api/generate", timeout=600,
                       json={"model": MODEL, "prompt": prompt, "stream": False,
                             "options": {"temperature": 0.1}})
        draft = r.json()["response"]
        job_id = cur.execute(
            "INSERT INTO jobs(job_type,client_id,source_file,draft,created_by)"
            " VALUES (%s,%s,%s,%s,%s) RETURNING id",
            (job_type, client_id, file.filename, draft, x_user)).fetchone()[0]
        log(cur, x_user, "draft", job_id, prompt, draft)
        return {"job": job_id, "status": "drafted", "draft": draft}

@app.get("/queue")
def queue(x_user: str = Header(...)):
    with psycopg.connect(DB) as con, con.cursor() as cur:
        sql = ("SELECT j.id,j.job_type,j.client_id,j.created_by,j.created_at FROM jobs j "
               "JOIN clients c USING(client_id) WHERE j.status='drafted'")
        if role_of(cur, x_user) != "compliance":
            sql += " AND c.adviser=%s"
            rows = cur.execute(sql, (x_user,)).fetchall()
        else:
            rows = cur.execute(sql).fetchall()
        log(cur, x_user, "view", note="queue")
        return [dict(zip(["id", "type", "client", "by", "at"], r)) for r in rows]

@app.post("/jobs/{job_id}/{decision}")
def decide(job_id: int, decision: str, final_text: str = Form(""), x_user: str = Header(...)):
    if decision not in ("approve", "reject"): raise HTTPException(400)
    with psycopg.connect(DB) as con, con.cursor() as cur:
        job = cur.execute("SELECT client_id,job_type,draft FROM jobs WHERE id=%s AND status='drafted'",
                          (job_id,)).fetchone()
        if not job or not may_see(cur, x_user, job[0]): raise HTTPException(404)
        text = final_text or job[2]
        cur.execute("UPDATE jobs SET status=%s, approved_by=%s, decided_at=now() WHERE id=%s",
                    (decision + "d", x_user, job_id))
        if decision == "approve":                 # filed into the client's own folder
            out = ROOT / job[0] / job[1] / f"{job_id}-approved-by-{x_user}.md"
            out.parent.mkdir(parents=True, exist_ok=True)
            out.write_text(text)
        log(cur, x_user, decision, job_id, output=text)
        return {"job": job_id, "status": decision + "d"}
app/jobs.py python
# One instruction per workflow. The firm's compliance officer owns this file.
HOUSE_RULES = """You work for a DIFC-regulated wealth firm. Rules:
- Draft only. A named adviser approves everything you write.
- Never recommend a product, fund or security.
- Every figure carries its source as [p.N] from the page markers.
- If a field is not in the document, write MISSING. Never guess.
- Plain English. No promises about returns."""

JOBS = {
  "kyc": "Extract: full name, date of birth, nationality, passport number and expiry, "
         "residential address, source of wealth, source of funds, employer, annual income. "
         "Return one line per field as FIELD: value [p.N]. List every MISSING field at the end.",
  "meeting_prep": "Write a one-page meeting brief: who, goals on file, holdings vs model, "
                  "open actions, three questions to ask. Max 300 words.",
  "file_note": "Turn these meeting notes into a file note: attendees, what was discussed, "
               "what the client asked for, decisions, actions with owner and due date.",
  "suitability": "Draft a suitability report from the fact-find and risk profile: client "
                 "objectives, risk level and why, how the model portfolio fits, risks, costs. "
                 "Leave the recommendation section as [ADVISER TO WRITE].",
  "drift": "Compare holdings to the model weights. List every line outside +/-5 points "
           "with current %, target %, and the gap. No trade suggestions.",
  "research": "Summarise this report in one page. Every figure with [p.N]. End with "
              "'What this does not say' in three bullets.",
  "client_update": "Draft a short personal update email for this client from their numbers "
                   "and open actions. Warm, factual, under 180 words. No forecasts.",
  "screening": "Apply the house screening rules below to each security. For each: PASS or "
               "FAIL and the rule that decided it.",
}
app/Dockerfile docker
FROM python:3.12-slim
WORKDIR /app
# Build this image on the World 2 desk, then: docker save | load on the server
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
app/requirements.txt text
fastapi==0.115.*
uvicorn==0.32.*
psycopg[binary]==3.2.*
httpx==0.27.*
pypdf==5.*
python-multipart==0.0.*
Day 5 · ops

Open a client folder the right way

Six sub-folders, access for the named adviser and compliance only, and a matching row in the database. Client IDs only, never names, in folder names.

ops/new-client.sh bash
#!/usr/bin/env bash
# Usage: sudo ./new-client.sh C-0042 sara
# Creates the client folder. Only the named adviser and compliance get in.
set -euo pipefail
ID=$1; ADVISER=$2; D=/srv/wealth/clients/$ID
install -d -m 0700 "$D"/{kyc,fact-find,suitability,portfolio,correspondence,reviews}
setfacl -R -m u:$ADVISER:rwX -m g:compliance:rX "$D"
setfacl -R -d -m u:$ADVISER:rwX -d -m g:compliance:rX "$D"
psql "$DATABASE_URL" -c "INSERT INTO clients(client_id,adviser) VALUES ('$ID','$ADVISER')"
echo "Client $ID opened for $ADVISER"
Day 6 · backups

Nightly encrypted backup, quarterly restore test

Two drives swap every Monday: one in the safe, one off site. 72 monthly snapshots cover six years. Once a quarter compliance picks a random client and ops restores it while both watch.

ops/backup.sh bash
#!/usr/bin/env bash
# Nightly at 22:00 via cron. Two encrypted drives (A and B) swap every Monday:
# one in the office safe, one off-site.
set -euo pipefail
DRIVE=$(ls -d /media/backup-[AB] | head -1)          # whichever drive is plugged in
export RESTIC_REPOSITORY=$DRIVE/restic
export RESTIC_PASSWORD_FILE=/root/.restic-pass        # also sealed in the safe on paper

docker compose -f /opt/wealth-desk/docker-compose.yml exec -T db \
  pg_dump -U wealth wealth > /srv/wealth/exports/db-$(date +%F).sql

restic backup /srv/wealth --exclude /srv/wealth/models --tag nightly
restic forget --keep-daily 14 --keep-weekly 52 --keep-monthly 72 --prune   # 72 months = 6 years
restic check --read-data-subset=5%
echo "$(date -Is) backup ok to $DRIVE" >> /srv/wealth/audit/backup.log
ops/restore-test.sh bash
#!/usr/bin/env bash
# Quarterly. Compliance picks a random client, ops restores it, both sign the log.
set -euo pipefail
CLIENT=${1:?client id}
T=/tmp/restore-test-$(date +%F); rm -rf "$T"
restic restore latest --target "$T" --include "/srv/wealth/clients/$CLIENT"
find "$T" -type f | wc -l
echo "$(date -Is) restore test $CLIENT ok, files: $(find "$T" -type f | wc -l)" >> /srv/wealth/audit/restore.log
rm -rf "$T"
Day 7 · build machine

Lock the build tool

Claude Code is used to build and test the workflows on sample data. Managed settings deny web access and client folders, and the hook logs every tool call. Users cannot switch these off. Details in the next section.

claude/managed-settings.json json
{
  "permissions": {
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable",
    "deny": [
      "WebFetch",
      "WebSearch",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Bash(scp:*)",
      "Bash(ssh:*)",
      "Read(//srv/wealth/clients/**)",
      "Edit(//srv/wealth/clients/**)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      { "matcher": "*",
        "hooks": [ { "type": "command", "command": "/opt/wealth-desk/claude/audit-hook.sh" } ] }
    ]
  }
}
claude/audit-hook.sh bash
#!/usr/bin/env bash
# Runs before every Claude Code tool call. Logs it, and blocks client paths.
# Exit code 2 = blocked, and the reason goes back to Claude.
IN=$(cat)
TOOL=$(jq -r '.tool_name' <<<"$IN")
ARGS=$(jq -c '.tool_input' <<<"$IN")
echo "$(date -Is) $USER $TOOL $ARGS" >> /srv/wealth/audit/claude-code.log
if grep -q '/srv/wealth/clients' <<<"$ARGS"; then
  echo "Blocked: client folders are off limits to the build tool. Use sample data." >&2
  exit 2
fi
exit 0
claude/CLAUDE.md markdown
# Firm rules for Claude Code (build machine)

- You build and test workflows. You never see live client data.
- Test files live in ./samples and use made-up clients only (C-9001 to C-9099).
- Never write code that sends data outside 192.168.50.0/24.
- Every new workflow needs: an entry in app/jobs.py, a line in the AI register,
  and a test on three sample files before the compliance officer sees it.
- Drafts only. Nothing in this system sends an email or places a trade.
● 7 · The safeguards, enforced by settings

What Claude Code locks down

Claude Code builds and tests the workflows. Its controls are plain settings files the compliance officer can read, not promises in a sales deck.

Ask before actingEvery file write, command or email is shown first and needs a person's approval. Bypass mode is disabled in managed settings, so no user can turn approvals off.
Deny listsSettings block tools outright: WebFetch, WebSearch, curl, wget, scp, ssh, and any read or edit under /srv/wealth/clients.
Hooks that log and blockA hook runs before every tool call, writes who, what and when to claude-code.log, and stops any call that names a client path.
Managed settingsThe file lives in /etc/claude-code/ on Linux or /Library/Application Support/ClaudeCode/ on macOS, owned by root. User settings cannot override it.
Rules in plain EnglishCLAUDE.md carries the firm's rules: sample clients only, no outbound code, every workflow tested on three files and added to the register.
No training on your dataUnder Anthropic's commercial terms, API and enterprise data is not used to train models. Zero-data-retention is available for enterprise accounts.
Honest limit: Claude Code needs a connection to a model, so it runs on a World 2 build machine with sample data. Live client documents are read only by the open-weight model inside World 1. A firm that accepts an enterprise cloud account with the controls above can choose to run more in the cloud. That is the firm's decision, made in the impact assessment.
● 8 · Twelve workflows, one loop

How each job flows

Every job runs the same five steps. Only the input and the person who signs change.

The universal loop: drop a file, AI drafts, review queue, adviser approves, filed and logged, then back to the client file 1Dropfile into the desk 2AI draftsevery figure with [p.N] 3Queuewaits for a person 4Adviser signsedits, approves or rejects 5Filed + loggedclient folder, audit log rejected → back with a reason next job for the same client starts from the approved file
Development checklist: all twelve jobs as a tick matrix (prompt written, tested on 3 files, on the AI register, live with a named owner), plus 71 build and governance checks. Tick it online or download the PDF.
inputAI doesperson checksoutput and where it is filed
1

Prospect brief

World 2
InProspect name, public website, press
AI doesReads public pages, matches needs to services, drafts first email
Person checksAdviser opens each source link and edits the email
FiledCRM note, no client file yet
job key World 2 only
2

Onboarding pipeline

World 1
InNew client record, stage list
AI doesTracks each stage, lists missing items, drafts the chaser
Person checksAdviser confirms, compliance signs the file
Filedclients.stage + tasks table
job key taskstoday 3 to 4 h per client
3

Fact-find and KYC extraction

World 1
InPassport, bank statements, payslips (PDF)
AI doesFills nine fields, each with [p.N], marks MISSING
Person checksCompliance officer confirms every field
Filed/clients/C-0042/kyc/
job key kyctoday 3 to 4 h per client
4

Meeting prep and file note

World 1
InClient file, last review, meeting notes
AI doesOne-page brief before, file note and actions after
Person checksAdviser reviews both
Filed/clients/C-0042/reviews/
job key meeting_prep, file_notetoday 30 to 45 min before, 30 after
5

Suitability report

World 1
InFact-find, risk profile, model portfolio
AI doesFirst draft, recommendation left blank
Person checksAdviser writes the recommendation and signs
Filed/clients/C-0042/suitability/
job key suitability
6

Portfolio drift check

World 1
InHoldings export, model weights
AI doesLists every line outside +/-5 points
Person checksAdviser decides any rebalance
Filed/clients/C-0042/portfolio/
job key drifttoday 45 min per quarterly check
7

Equity and bond screening

World 1
InSecurity list, house screening rules
AI doesPASS or FAIL per security with the rule
Person checksAnalyst signs the shortlist
Filed/research/screens/
job key screening
8

Research notes

World 1
In40-page research report (PDF)
AI doesOne page, every figure with [p.N]
Person checksAnalyst clicks references, adds house view
Filed/research/notes/
job key researchtoday An afternoon per report
9

News watch

World 2
InPublic news, regulator updates
AI doesMatches stories to client segments, rates impact
Person checksAdviser reads the primary source
FiledSegment list, no client names
job key World 2 only
10

Client update emails

World 1
InClient numbers, open actions
AI doesPersonal draft under 180 words, no forecasts
Person checksAdviser approves, sends from the mail desk
Filed/clients/C-0042/correspondence/
job key client_updatetoday 90 to 120 min per note
11

Staff follow-up tracker

World 1
InApproved file notes
AI doesTurns actions into tasks with owner and date
Person checksManager reviews overdue every Monday
Filedtasks table
job key file_note → tasks
12

Audit log and verification

World 1
InEvery job, every approval
AI doesHashes prompt and output, records who and when
Person checksCompliance samples 10 jobs a month
Filedai_audit_log (append only)
job key automatic
World 1 = offline office network. World 2 = internet desk, no client data. Job keys match app/jobs.py. "Today" times are industry ranges reported by Automaton (May 2026), a vendor, and are replaced by the firm's own pilot figures. Interactive version: azizsaif.com/wealth-demo
● 8b · Instant jobs, no server at all

The private desk: drop a file, results in a second

Four of the twelve jobs need no AI model at the moment of use. Their rules were written in advance with Claude and frozen as code that runs inside the staff member's own browser. Nothing is uploaded, nothing is stored, and the page carries a browser-enforced block on every outgoing connection.

What runs in the browser

  • Client file check: drop every file for one client, see what is missing, expired or too old
  • Portfolio drift: holdings export against the model weights, with the tolerance line
  • Bank statement scan: money in and out by month, top payers, cash, round and cross-border flags
  • Research figures: every sentence with a number, tagged with its page

Why a DIFC compliance officer can accept it

  • Content-Security-Policy connect-src 'none': the browser itself refuses any upload, even from a bad script
  • Libraries served from azizsaif.com with integrity hashes, so a changed copy is refused
  • No Google Fonts, no analytics, no cookies on the desk page. Check the Network tab: it stays empty
  • No AI service is called, so no client data can reach a model or be used for training
  • Excel and print downloads are made on the user's computer too. Nothing about the file is sent anywhere
Where the server still matters: the other eight jobs draft text (KYC fields from a scanned passport, file notes, suitability reports, client emails). Those need a model, and that model lives on the office server in World 1. The desk and the server share the same rule: the adviser signs.
● 9 · Clean guidelines for staff

The rules on one wall

Print this section and pin it next to every World 1 desk. It is the short version of the AI-use policy.

Always

  • Use client IDs (C-0042) in file names, never names
  • Click every [p.N] reference before you approve
  • Fix MISSING fields with the client, never with a guess
  • Write your own recommendation section in suitability reports
  • Reject a weak draft with a reason so the prompt improves
  • Lock your screen when you stand up (Windows key + L)

Never

  • Paste client data into any website, app or phone chat
  • Use a personal USB stick or personal email for client files
  • Send an AI draft to a client before an adviser approves it
  • Let AI pick a product, fund or security
  • Plug a World 1 machine into the router or guest Wi-Fi
  • Share your desk login, even with the IT support person

Operations calendar

Daily

Advisers

  • Clear your review queue
  • Overdue tasks first
Weekly · Monday

Ops

  • Swap backup drives A and B
  • Check backup.log shows 7 nights
Monthly

Compliance

  • Sample 10 approved jobs
  • Read the audit-log summary
  • Update the AI register
Quarterly

Ops + compliance

  • Restore test on a random client
  • Model and patch update via transfer drive
Yearly

Partners

  • Review policy and impact assessments
  • Delete files past retain_until

If something goes wrong

What happenedFirst 10 minutesWho
Client data pasted into a websiteScreenshot, close the tab, tell compliance. Do not delete anything. Compliance assesses a breach notice to the DIFC CommissionerStaff member, then compliance
A draft went to a client unapprovedCall the client, correct in writing, log it in the audit table as a noteAdviser, then compliance
Backup drive lostDrives are encrypted, so record it. Rotate the restic password, take a fresh backup on a new driveOps
Server will not startWork from paper files. Ops follows the runbook. Restore to spare hardware from the safe drive if neededOps
● 10 · Training · Track B

Four tracks, one per role

Taught at your DIFC office on the firm's own server, using made-up sample clients. Each person leaves able to do their part without calling anyone.

90 minutes

Partners and leadership

  • What the twelve jobs do and do not do
  • Regulation 10 duties in plain words
  • Reading the monthly audit summary
  • Approving the policy and register
Half day · 4 hours

Advisers and para-planners

  • Dropping files and choosing the job
  • Checking [p.N] and MISSING fields
  • Editing, approving and rejecting well
  • Private chat for drafting, the never-list
Half day · 4 hours

Compliance officer

  • Writing the AI register and impact assessment
  • Sampling the audit log with three queries
  • Client disclosure wording
  • Breach steps and the restore test
Full day · 7 hours

IT and ops

  • Server build from the kit, step 1 to 9
  • Firewall proof and the transfer drive
  • Adding staff, clients and a new job
  • Backup, drive swap and restore

What compliance runs each month

monthly-sample.sql sql
-- 1. Who did what with AI in the last 30 days
SELECT username, action, count(*) FROM ai_audit_log
WHERE at > now() - interval '30 days' GROUP BY 1, 2 ORDER BY 1;

-- 2. Ten random approved jobs to read in full
SELECT id, job_type, client_id, created_by, approved_by, decided_at
FROM jobs WHERE status = 'approved'
ORDER BY random() LIMIT 10;

-- 3. Anything approved by the same person who created it
SELECT id, job_type, client_id, created_by FROM jobs
WHERE status = 'approved' AND created_by = approved_by;

How a training day runs

TimeBlock
09:00The two worlds, with the cabinet open in front of the group
09:45Hands-on: each person runs one job on a sample client
11:00Find the planted mistake: drafts with one wrong figure each
12:00The never-list, and what to do in the first 10 minutes
13:00Role track: advisers, compliance or IT
15:30Each person signs the AI-use policy
Groups of 4 to 12. Fees and dates: AI training in Dubai · DIFC firms: AI automation for DIFC
Training path: leadership approves, advisers use, compliance checks, IT runs, then each role signs the policy Leadershipapproves Advisersuse daily Compliancechecks monthly IT / opsruns weekly Policy signedby every user 90 min · 4 h · 4 h · 7 h · one office, sample clients only
● 11 · Implementation plan

Four phases, first pilot in week 3

1
Weeks 1 to 2 · Foundations
  • Build steps 1 to 9, on sample clients
  • AI-use policy, AI register, impact assessment
  • Compliance officer reviews the rules table
  • Outsourcing agreement and NDA signed
2
Weeks 3 to 4 · Pilot
  • One job: meeting prep, one adviser
  • Runs beside the old way
  • Hours logged daily
  • Go or no-go on real numbers
3
Month 2 · Onboarding
  • KYC extraction and onboarding pipeline
  • Follow-up tracker
  • Adviser and compliance training
  • First audit-log sample by compliance
4
Months 3 to 4 · Full desk
  • Drift, screening, research, client updates, suitability
  • IT training and first restore test
  • Runbook and recorded walkthrough handed over
  • Compliance review, then decide what's next

Handed over at the end

Working desk on the firm's own server, with every login and password returned
AI-use policy, AI register, impact assessment, audit-log procedure
Runbook, the server kit, the signed development checklist, a recorded walkthrough and four training tracks delivered
● 12 · Questions DIFC firms ask

Straight answers

Can a DIFC wealth firm use AI without sending client data to the cloud?
Yes. An open-weight model such as gpt-oss-120b runs on a server inside the office with the internet cable removed. Client documents are read only on that server. The model, software and updates are downloaded on a separate internet desk and carried in on an encrypted drive.
What hardware does a small DIFC wealth firm need for offline AI?
For five to ten staff, one Mac Studio with 192 GB or more of memory, or a workstation with one 48 GB professional GPU, plus a UPS, a switch, two encrypted backup drives and a small safe. Planning range is AED 20,000 to 50,000 for the machine. Ten to forty staff need a rack server with one or two 80 GB GPUs, from AED 150,000.
Which DIFC and DFSA rules apply to AI at a wealth firm?
The DIFC Data Protection Law No. 5 of 2020, Regulation 10 on autonomous and semi-autonomous systems, DFSA GEN 5.3 on systems and controls and outsourcing, DFSA record keeping, and the governance expectations in the DFSA AI Survey 2025. The firm's compliance officer confirms each against the current rulebook before sign-off.
Do we have to tell clients we use AI?
Under DIFC Data Protection Regulation 10, clients are told an AI system is used and what it does. This build adds a named line to the client agreement. The AI drafts and an adviser approves, so a person stays in control of every output.
How long does the setup take?
Four phases. Server, policy and register in weeks 1 and 2, a one-adviser pilot in weeks 3 and 4, onboarding and KYC in month 2, and the full twelve-job desk in months 3 and 4.
Can our own staff run the system after setup?
Yes. Training comes in four tracks taught at the firm's DIFC office: 90 minutes for partners, half a day for advisers, half a day for the compliance officer, and a full day for IT on the server, backups and restore tests. The server kit and runbook are handed over.
Where does Claude Code fit if client data stays offline?
Claude Code builds and tests the workflows on a separate build machine using made-up sample clients. Managed settings deny web access and any read of client folders, disable bypass mode, and a hook logs every tool call. Live client documents are read only by the model inside the office.
Can some jobs run without any server?
Yes. Four fixed-rule jobs (client file check, portfolio drift, bank statement scan, research figures) run as code inside the browser at azizsaif.com/DIFC-ai-wealth-management/desk/. The page blocks every outgoing connection with a Content-Security-Policy, stores nothing, and calls no AI service, so no client data reaches any model. The rules were written in advance with Claude and frozen as code.
Is there a checklist to track the build?
Yes. A development checklist with 119 tick boxes in nine modules: office and hardware, server build, data and backups, DIFC and DFSA governance, the build machine, the shared review loop, all twelve workflows as a tick matrix, training, and go-live sign-off. Tick it online or download it as PDF or HTML from azizsaif.com/DIFC-ai-wealth-management/checklist.html.
How are records kept for six years?
Nightly encrypted backups with restic to two drives that swap every Monday, one in the office safe and one off site. 72 monthly snapshots are kept. Every quarter compliance picks a random client and ops restores it to prove the backup works.
● Next step

A 30-minute call, then the 15-minute demo with your team

Say setup, training or both. NDA before any document is shared. Phase 1 can start the week after.
Book a 30-min call Email
Prepared by Aziz Saif, AI automation consultant, Dubai. Related: AI automation for DIFC firms · five wealth jobs and prompts · 15-minute demo · case study · AI training in Dubai · one-page proposal PDF · development checklist (PDF). Time savings are planning estimates from the sources linked and are replaced by the firm's own pilot figures. The code is sample code, reviewed by the firm's IT and compliance before live use. Nothing here is legal advice or investment advice. Sources checked 4 to 7 October 2026.