| name | llm-data-assistant |
| description | Architecture playbook for LLM-powered data assistants over secured SQL (distilled from secure reference patterns). Use when designing, building, reviewing, or debugging any feature where an LLM authors SQL, reads firm data, or streams rich answers — tool loops, row-level security, sensitive-data masking, data-reference hydration, output guarding, audit — and for Blazor Server app design around streaming LLM UIs (service scoping, circuits, SignalR, JS interop). Also applies when porting these patterns to a new app. |
LLM Data Assistant — design playbook
Created by Jimmy Park — distilled from secure reference patterns.
Proven architecture from secure Blazor Server reference implementations (.NET Blazor Server, standard dependency injection, secure scopes). File paths in this skill's reference docs are relative to the target repository when you're working in it; the rules themselves generalize to any LLM-over-secured-SQL feature in any codebase.
The one invariant that governs everything
The LLM authors query structure and presentation; it never authors security, never sees unmasked sensitive data it shouldn't, and never writes raw data values into output. Security predicates are hand-written SQL (TVFs/stored procs) that the runtime executes; validators enforce their presence; masking happens before the LLM sees results; real values are hydrated server-side after generation. If a design lets the LLM do any of those four things directly, it is wrong.
Layered architecture (each layer assumes the one above it failed)
- Tool design — tiered SQL tools with per-tier row caps and enforcement levels (see references/sql-security.md).
- Static validation — AST-based check (ScriptDom) that every protected table is INNER JOINed to its required
#visible* security temp before execution. Reject with a copy-paste-ready patch hint so the LLM can self-correct.
- Runtime gates — read-only statement whitelist, auto-injected
TOP, sensitive-column masking, hidden-table/column stripping.
- Output guarding — fence-aware regex backstop (
Clean / NeedsRewrite / HardBlock) + a second-LLM sanitizer pass with placeholder-count rollback.
- Hydration last —
{{$qN.Col.Selector}} placeholders resolved against a per-turn query registry after sanitization, so no guard LLM ever sees real values.
- Audit always — persist executed SQL, tool calls, and responses immediately; compute confidence/memory in the background and patch in later.
Pipeline stage order (do not reorder)
intent expansion (bypass detection — refuse early)
→ LLM tool loop (capped iterations, prompt-cache breakpoints)
→ parse to typed content blocks
→ sanitize (second LLM pass; placeholders still unreal)
→ regex guard recheck (HardBlock ⇒ canonical refusal)
→ hydrate (placeholders → real values; unmask LLM-echoed masked text)
→ return to user immediately
→ background: audit, confidence scoring, memory extraction
Sanitize-before-hydrate is load-bearing: it was originally the other way around in early implementations and was reordered (2026-05-27) so the sanitizer LLM never sees real names/bytes.
Reference files
- references/sql-security.md — tool tiers, CTE/visibility validation, read-only whitelist, masking levels, business-rules tool, T-SQL house style.
- references/pipeline.md — orchestrator stages, placeholder grammar, query registry, mask/reveal map, output guard patterns, audit model.
- references/blazor-server.md — Scoped vs Singleton rules, Channel-based streaming, circuit lifecycle/reattach, conversation locks, JS-interop boundaries.
Quick checklist for any new LLM-data feature