Louis is an open-source legal AI workbench for solo practitioners, in-house teams, and boutique law firms. It pairs a chat assistant with a document workspace, a redlining/comparison view, a clause/citation library, a risk scanner, a tabular reviewer for due-diligence-style batches, and ~990 authored "skills" that bias the model toward the way actual lawyers draft, review, and reason.
The whole stack runs on commodity infrastructure (Supabase + an S3-compatible bucket + a model provider of your choice) and BYO API keys mean you can run a private deployment without exposing client data to an intermediary.
- Frontend — Next.js 15 (App Router), Tailwind, shadcn/ui.
- Backend — Express + TypeScript, Supabase admin client, AWS-S3-SDK.
- Auth + DB — Supabase.
- Object storage — Cloudflare R2 (or any S3-compatible bucket — MinIO, Wasabi, B2).
- Model providers — Anthropic Claude, Google Gemini, OpenAI. Pick per-message; per-user BYO keys override the server's defaults.
- MCP server — exposes Louis's calculators, clause/citation lookups, risk scanner, and skill router to any MCP-aware AI client.
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Next.js 15 │ ←→ │ Express + │ ←→ │ Supabase │
│ (frontend) │SSE │ TypeScript │ │ (Auth + PG) │
└───────────────┘ └───────┬───────┘ └───────────────┘
│
▼
┌───────────────┐ ┌───────────────┐
│ R2 / S3 │ │ Claude/Gemini │
│ bucket │ │ OpenAI APIs │
└───────────────┘ └───────────────┘
.
├── frontend/ Next.js 15 app
│ └── src/app/(pages)/ One folder per top-level UI surface
├── backend/ Express API + skills + schema
│ ├── src/routes/ One file per HTTP namespace (chat, billing, …)
│ ├── src/skills/ ~990 authored .md skills + registry
│ ├── src/lib/llm/ Provider adapters (claude/gemini/openai)
│ └── schema.sql Idempotent schema for fresh Supabase databases
├── deploy/
│ └── cloudflared/ Cloudflare Tunnel config for legal.dashable.dev
└── docs/ Storage setup + local testing guides
- Node.js 20+
- npm
- A Supabase project (free tier works)
- An S3-compatible bucket (Cloudflare R2, MinIO, etc.)
- At least one model provider API key: Anthropic, Gemini, or OpenAI
- LibreOffice (only if you need DOC/DOCX → PDF conversion)
Assumes you have Node 20+, npm, and Homebrew (or your platform's equivalent for installing LibreOffice / cloudflared). You'll need free-tier accounts for Supabase and Cloudflare R2 (or any S3-compatible bucket: MinIO, Wasabi, Backblaze B2, Supabase Storage…).
# 1. Clone
git clone https://github.com/sboghossian/louis-legal.git
cd louis-legal
# 2. Install deps for both apps
npm install --prefix backend
npm install --prefix frontend
# 3. Create env files
touch backend/.env frontend/.env.local
# Now open both and paste the templates from the "Environment"
# section below; fill in the Supabase / R2 values you just minted.
# 4. Apply the schema to a fresh Supabase database
# Open the Supabase SQL editor for your project and paste:
# backend/schema.sql
# Hit Run. The file is idempotent — safe to re-run.
# 5. Start both servers (two terminals, or `npm run dev` from the root
# if you have `concurrently` installed)
npm run dev --prefix backend # → http://localhost:3001
npm run dev --prefix frontend # → http://localhost:3000Open http://localhost:3000, sign up, complete onboarding (you'll see
it exactly once per account), then add an API key for at least one
model provider — either in backend/.env for the whole instance, or
per-user in Account → Models & API Keys.
A live build sits at https://legal.dashable.dev (Cloudflare-tunneled from a workstation; uptime is best-effort). Use it for a tour, not for real data.
- Supabase confirmation email never arrives. Disable email confirmation in Supabase → Authentication → Providers → Email for local dev, or wire your own SMTP — the built-in mailer is heavily rate-limited.
- Model picker shows "missing key". Set
ANTHROPIC_API_KEY,GEMINI_API_KEY, orOPENAI_API_KEYinbackend/.envand restart the backend, or just paste a key in Account → Models & API Keys (user-set keys take precedence over env keys). - DOC / DOCX conversion fails.
brew install libreofficeand restart the backend sosofficeis onPATH. - Multiple lockfiles warning. Cosmetic; ignore.
frontend/has its ownpackage-lock.json.
PORT=3001
FRONTEND_URL=http://localhost:3000 # CORS allow-list
DOWNLOAD_SIGNING_SECRET=<random 32-byte hex>
# Supabase
SUPABASE_URL=https://<project>.supabase.co
SUPABASE_SECRET_KEY=<service-role key>
# Object storage (R2 or any S3-compatible)
R2_ENDPOINT_URL=https://<account>.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=…
R2_SECRET_ACCESS_KEY=…
R2_BUCKET_NAME=louis
# Model providers (any subset; users can also BYO at runtime)
ANTHROPIC_API_KEY=…
GEMINI_API_KEY=…
OPENAI_API_KEY=…
# AES-GCM secret used to encrypt per-user BYO keys at rest.
# Cannot be rotated without re-encrypting; generate once and keep it stable.
USER_API_KEYS_ENCRYPTION_SECRET=<long random string>
# Optional
RESEND_API_KEY=… # for transactional emailNEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=<anon key>
SUPABASE_SECRET_KEY=<service-role key>
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001BYO key precedence. A user's stored API key takes precedence over the env key. Set
ANTHROPIC_API_KEYetc. as fallbacks for users who haven't configured their own — never as overrides. Status reporting at/api/api-keys/statusalways reflects which key will actually be used.
For a fresh project, paste backend/schema.sql into the Supabase SQL editor
and run. It is idempotent (uses create table if not exists and drop policy if exists/create policy) so re-running is safe.
For an existing production database, do not rerun the full schema;
apply only the relevant incremental migrations under backend/migrations/.
The easiest way to expose Louis with a public TLS hostname is a Cloudflare
Tunnel. No port-forwarding, no inbound firewall change — the
cloudflared daemon holds an outbound HTTP/2 connection that Cloudflare
routes traffic over.
See deploy/cloudflared/README.md for the
full walkthrough. tl;dr:
brew install cloudflared
cloudflared tunnel login
cloudflared tunnel create louis-legal
cloudflared tunnel route dns louis-legal legal.dashable.dev
cloudflared tunnel route dns louis-legal legal-api.dashable.dev
# fill in tunnel UUID + credentials path in deploy/cloudflared/config.yml
npm run tunnelThis routes:
| Host | → | Local port |
|---|---|---|
legal.dashable.dev |
→ | localhost:3000 (Next) |
legal-api.dashable.dev |
→ | localhost:3001 (API) |
Don't forget to set NEXT_PUBLIC_API_BASE_URL=https://legal-api.dashable.dev
in frontend/.env.local and FRONTEND_URL=https://legal.dashable.dev in
backend/.env once you've pointed the tunnel.
- Frontend can also run as a static export on Cloudflare Pages or
Vercel; the backend stays elsewhere (Fly.io, Railway, a small VM). The
frontend/open-next.config.tsis set up for the OpenNext Cloudflare adapter inherited from the upstream Mike fork. - Backend packs a
Dockerfile-freenixpacks.toml; Railway / Coolify pick it up automatically.
- Assistant chat with streaming responses, citations, doc reads, workflow turn-key actions, and per-message thumbs up/down feedback.
- Reasoning is surfaced by default in a collapsible block whenever the
model exposes thinking (Claude
thinking: adaptive, GeminiincludeThoughts, OpenAIreasoning.summary). - Document workspace with versions, accept/reject tracked-change edits, cross-doc diffs, and a redline export.
- Prompt Library (
/prompt-library) — ~150 expert prompts ported from haqq.ai, filterable by use case and practice area; one click sends the template into the assistant composer. - Skill router — Louis routes each user message through ~990 authored skills (drafting, review, research, jurisdiction, persona, …) and composes only the relevant ones into the system prompt.
- Processor v2 (
docs/PROCESSOR_V2.md) — three credit/trust/memory wins layered on the router:- Adaptive cost governor — a cheap classifier picks an intensity tier
(
quick/standard/thorough) that scales the skill count, model tier, Claudeeffort, and a per-turn budget cap; low-confidence/high-risk turns escalate one tier. Easy questions get cheap, fast answers. Live: an over-ceiling turn emits abudgetSSE alert (decision #87 — alert, not block). - Zero-LLM grounding — every quoted span and section reference in an
answer is mechanically checked against the cited document (pure string
matching, no extra model call). Live: surfaced on
/chatas a trailinggroundingSSE event{ score, matched, unmatched }. - Four-tier memory — Session / Matter / Institutional / Precedent store
with tag-filtered retrieval and effectiveness/recency weighting, per-user
isolated (every read scoped to the querying user, all tiers). Live:
the earned slice is injected into each turn's system prompt and the turn is
captured back. Persistent: Supabase-backed when
SUPABASE_URL+SUPABASE_SECRET_KEYare set (tablememory_entries, per-user RLS), else an in-process fallback for dev/self-host.
- Adaptive cost governor — a cheap classifier picks an intensity tier
(
- Quality & onboarding suite (
docs/LAVERN_FEATURES.md) — Lavern-inspired:- Legal-Design quality audit — deterministic ethics/dark-pattern scan,
readability scoring, and meaning-preservation risk flags.
POST /api/quality/audit. - Agent-builder onboarding — scrape a firm's site (SSRF-hardened) → propose
AI agent profiles, each with a
seenOnSitecitation.POST /api/agent-builder/analyze. - Auto-brief intake — short message + attached docs → synthesized task brief.
- Output-quality eval harness — score legal outputs against expectations + an optional LLM judge.
- Legal-Design quality audit — deterministic ethics/dark-pattern scan,
readability scoring, and meaning-preservation risk flags.
- Workflow orchestration (
docs/WORKFLOWS.md) — typed, multi-step legal workflows (Contract Review, Due Diligence, Research Memo) that run as BullMQ jobs outside the chat turn, reuse the Processor v2 primitives, and emit severity-tagged findings. RED findings get a bounded adversarial Full-Bench (Challenger → Defender → Evaluator); the deliverable is assembled, validated (no skeleton/placeholder/process-dump), and fidelity-checked. Mandatory human gate on any side-effect step (POST /api/workflows/:runId/approve) — no autonomous multi-step action./api/workflows; Supabase-persisted (workflow_runs, per-user RLS) or in-process. Derivatives (client-letter / summary / redline / memo) generate from a completed run. - Agent-native surface (
docs/WAVE3.md,docs/WAVE4.md) —GET /.well-known/agent.jsonadvertises Louis's capabilities andPOST /.well-known/agent/taskruns an A2A task via the MCP tools. Hybrid local(Ollama)+frontier triage (opt-incompleteRouted), Cohere rerank, durable session checkpointing, and an opt-in/chatworkflow dispatch (workflowTemplateId) are wired additively — the default chat path is unchanged. - Tabular review — apply the same prompt(s) across many documents, one column = one question, like a due-diligence checklist.
- MCP server at
/api/mcpexposes calculators, clause lookups, citation lookups, the risk scanner, and the skill router to any MCP-aware client. - Billing, team & permissions, inbox, routines (scheduled agents), GitHub skills sync, and integrations (Drive/Slack/etc.) are all wired against real backend stores.
npm run typecheck # both apps
npm run build --prefix backend # compile TS + copy skills
npm run build --prefix frontend
npm run lint --prefix frontend- Sign-up confirmation email never arrives. Supabase Auth sends these, not Louis. Easiest fix locally: disable email confirmation in Supabase → Authentication → Providers → Email. In production, configure custom SMTP (Supabase's built-in mailer is heavily rate-limited).
- Model picker shows a missing-key warning. Either add a key in
Account → Models & API Keys, or set the provider key in
backend/.envand restart the backend. - DOC / DOCX conversion fails. Install LibreOffice locally and
restart the backend so
sofficeis onPATH. - SSE chat hangs over the tunnel. Don't put a buffering reverse
proxy in front of
cloudflared— it'll silently break streaming.NEXT_PUBLIC_API_BASE_URLshould point atlegal-api.dashable.devdirectly. - CORS errors after deploying.
FRONTEND_URLinbackend/.envmust match the public origin exactly.
Forked from willchen96/mike and
rebranded as Louis for the HAQQ Legal AI vision. See docs/ and the
session-level commit messages for the running narrative.
MIT — see LICENSE.