Multi-turn chat with persistent history
api/app/api/chats.pyCatalog of what LQ.AI ships today, what is deferred, and how to verify each. Maintained per release. Current as of the fiduciary-grade agentic legal work milestone close (ADRs 0018–0021); migration head
0064. (Prior baselines: the legal-research + connectors (MCP) milestone close, #158–#193, head0055; before that, the M4 close plus the post-v0.4.0 “Donna” run, #115–#139, head0047.)
This document catalogs what LQ.AI ships today, what is deferred, and how an operator can verify each. We publish it in source because the verification path for an open-source project terminates in code, not in a vendor’s marketing claims. If you find a discrepancy between this doc and the codebase, the codebase is canonical; please open an issue.
Each table has three columns:
M1/M2/M3/M4 (shipped today, naming the milestone it landed in; a sub-tag like M4 or M3 may name a specific phase for context), partial (shipped with explicit caveats, named inline), scaffold (plumbing only, feature work deferred), or deferred-Mx / deferred (community-friendly) for roadmap or PRD §9 items not yet wired in source.Status markers reference the roadmap milestones (M1 → M4) documented in README.md and PRD §8.
On-demand model reads are default-on for enabled, trusted filesystem skills,
with an independent host-disable policy and retained authorized human supporting-
file inspection. The initial profile supports admitted references/ UTF-8 text:
64 files, 64 KiB per explicit read, safe ASCII paths, no hidden names or symlinks.
Large files stay discoverable and expose size/range/continuation metadata. Streamed
validation and hashing retain only one bounded range in memory. Legacy
reference/ prompt loading is unchanged. Assets, broader paths, Unicode names,
complete-list discovery remain compatibility follow-up work. This
entry claims neither a released capability nor complete Agent Skills support.
Verification targets include api/tests/skills/test_reference_read.py, skill
API/chat and autonomous policy regressions, and supporting-file UI tests.
The October 17 PR review records agent-run U-1–U-7 workflow checks and final
maintainer/security review findings, including the durable-result boundary
correction. These are implementation evidence, not a claim that the PR is
merged or released. See the operator notes.
The D9/D5 policy clarifications record the maintainer’s adopted direction as
of October 17; no further design-ratification round is required.
M1 — Foundation (shipped). Self-hostable conversational legal AI on the starter skills, with the engineering surfaces (audit, tier enforcement, projects/matters, knowledge bases + ingestion, saved prompts, receipts, the skill-creator pipeline) that later milestones build on. Provider adapters: Anthropic, OpenAI, Ollama (local Tier 1).
M2 — Citation Engine + Anonymization Layer (shipped). The four-stage citation verification cascade (exact → tolerant → paraphrase judge → ensemble) and the gateway anonymization middleware (Presidio + custom legal recognizers, streaming-aware rehydration, privileged + retrieval skips) both ship operational. The Azure OpenAI provider adapter rounds out the provider set.
M3 — Playbooks · Tabular Review · Word add-in · Slack/Teams intake bridge (shipped, with two honest caveats).
/lq slash-command surface is inert (DE-288).M4 — Autonomous Layer (shipped). The background autonomous executor runs real in-loop work: a five-phase LangGraph state machine (intake → analysis → drafting → ethics_review → delivery) where every external action routes through a single guarded_tool_call chokepoint enforcing three brakes — R4 (per-session/per-trigger cost cap), R5 (external halt + idle watchdog), R6 (phase-gated tool grants). It ships the four primitives (watches, schedules, per-user memory, precedent board), honest per-session receipts, per-user opt-in, and a full web dashboard. The Contract Repository auto-relationship graph (a separate M4-roadmap capability) is not built.
Legal research + connectors (MCP) — gateway-brokered (shipped after M4; #158–#193). The assistant can now reach external tools — case-law lookup (CourtListener) and operator-approved MCP connectors — but only through the Inference Gateway, which is extended from sole inference egress to sole tool egress (ADR 0014/0015). A governed chat tool-loop routes every tool call through a single governed_tool_invocation chokepoint; destructive/connector tools pause for an in-chat persist-and-resume confirmation gate; case-law results carry external-source retrieval provenance (“Sources consulted”, kept architecturally distinct from the Citation Engine’s character-verified quotes); per-user connector tokens are Fernet-encrypted at rest and never logged. Detailed in §5.5.
Fiduciary-grade agentic legal work — gateway-brokered (shipped after the legal-research + MCP milestone; ADRs 0018–0021). Building on the governed tool-loop above, the assistant now keeps a per-turn Citation Ledger of every source and passage it actually read, computes a pass/fail fiduciary gate over every tool-retrieved citation, can run governed plain-language matter sessions through the Autonomous Layer’s plan → act → observe → replan loop (the analysis phase’s implementation, replacing the single scripted call M4 shipped with), reaches three more free authority sources (GovInfo, SEC EDGAR, EUR-Lex) via a content-source registry, and derives a “derived, not editorial” case-law treatment signal. Migration head 0064. Detailed in §5.6.
The honest reading: an operator can deploy LQ.AI today for everyday in-house work on the starter skills, with character-verified citation grounding, operator-configurable pseudonymization, codified-position playbooks, multi-document tabular review, an opt-in autonomous background layer with hard economic/temporal/contextual brakes, gateway-brokered legal-research + connector (MCP) access under a governed, human-gated tool-loop, and a fiduciary-grade layer (citation ledger, pass/fail gate, governed matter sessions, free authority sources, treatment signal) on top of it. The deferred edges are: in-Word feature surfaces (scaffold today), live-verified chat-platform intake (plumbing today), the contract relationship graph, a dedicated matter-intake UI (the governed-matter-session backend is shipped; it reuses the autonomous session UI today), chat/autonomous fiduciary-gate verdict-tier parity (DE-371; the chat-side attributed-authority FAIL tier DE-370 is shipped), and EUR-Lex full-text search / treaty coverage (DE-374, DE-375).
The surface in-house counsel touches every day. Every row is wired end-to-end in M1.
Multi-turn chat with persistent history
api/app/api/chats.pyMatter (project) workspace with attached files / skills / KBs
api/app/api/projects.py; web/src/routes/lq-ai/matters/[id]/+page.svelteSlash-invoked skills with provenance pill
web/cypress/e2e/wave-d2-skill-creator.cy.ts Test 4Built-in starter skills
skills/*/SKILL.md (read every prompt — no hidden instructions)Community skill catalog via LegalQuants/lq-skills submodule
skills/community/ is a git submodule, empty until initialized — run git submodule update --init --remote skills/community to populate it. The loader walks built-in + community paths with built-in winning on slug collision (api/app/skills/loader.py); on a fresh clone with no submodule checkout there are no community skills.Skill capture / wizard authoring / fork / versions tab
web/cypress/e2e/wave-d2-skill-creator.cy.ts Tests 1–6Optional skill persistence and installed bundled Python helpers
Bounded orchestration chat
Governed runs in chat: “New harness” (ADR 0038, proposed)
propose_plan tool call. The owner approves that exact plan. A sub-agent then works on each task with a small set of tools (read a selected document, search the matter’s knowledge bases by keyword, keep notes), and the orchestrator combines the results. Quotations written "words" (Source: [N]) are checked against the file they name, by the Citation Engine’s exact and tolerant stages; the application reports which matched and nothing more. A chat need not be in a matter. Both roles are instructed by versioned prompt files, not skills, and the owner can read how each call was set up. The run message is one column of lines. The 0.8.0 fictional demonstration is no longer offered in chat; it can still be started through its stand-alone route (POST /api/v1/autonomous/orchestration/chat-runs) while LQ_AI_ORCHESTRATION_CHAT_ENABLED is on, and its retained runs stay readable. Code: api/app/api/chat_runs.py; api/app/autonomous/orchestration/ (new_harness.py, profiles.py, assembly.py, executor.py, loop.py, tools.py); migrations 0074 to 0077; gateway/app/providers/ollama.py; web/src/lib/lq-ai/components/ModelPicker.svelte and components/run/. Evidence: controlled-provider tests (cd api && pytest tests/autonomous/orchestration) and single runs by agents on local models. ADR 0038, build plan and implementation status. The UAT runbook was rewritten for New harness and no person has followed it. No walkthrough of the present build is recorded; the first build’s was removed from the branch.Saved Prompts library with one-click “Use in chat”
api/app/api/saved_prompts.py; web/cypress/e2e/wave-m1-final-surfaces.cy.ts Test 1Knowledge bases — create, attach documents, ingest to ready (hybrid BM25 + vector retrieval)
api/app/api/knowledge_bases.py; api/app/workers/document_pipeline.pyIngest formats — PDF (PyMuPDF) and plain text / Markdown (parse_text, verbatim canonical text → exact-match citations; non-UTF-8 → decode_error). DOCX is roadmap. A Docling dependency shipped alongside PyMuPDF but has never produced output (broken since C5); ingestion is PyMuPDF-only and Docling is being removed per ADR 0026.
api/app/pipeline/parsers.py; api/app/pipeline/ingest.pyReceipts drawer with per-event provenance
api/app/api/chat_receipts.py; web/cypress/e2e/wave-m1-final-surfaces.cy.ts Test 3Enhance Prompt (⌘E)
api/app/api/enhance_prompt.pyAudit log of all sensitive actions
api/app/audit.py; admin reads at /lq-ai/admin/audit-logAdmin account provisioning and password reset; immediate access/MFA token invalidation on password change or reset, including CLI reset
api/app/api/admin.py; api/app/security/jwt.py; web/src/lib/lq-ai/components/DevRoleManagementCard.svelte; api/tests/test_admin_user_provisioning.py (including concurrent refresh/reset); token lifecycleFTS over chat history
api/app/api/chats.py (search route); migration 0016_chat_messages_fts.pyGDPR-aligned export and account deletion
api/app/workers/user_export.py; api/app/workers/user_deletion.py; api/app/api/users.pyPer-message ephemeral file attach (MessageCreate.file_ids → applied_file_ids echo; separate channel from skill_inputs, injected as document-context per Decision M2-1)
api/app/schemas/chats.py (file_ids, applied_file_ids); api/app/schemas/gateway.py (lq_ai_file_ids)Self-service profile edit (PATCH /api/v1/users/me, display_name only; email is DE-329)
api/app/api/users.pyPending-deletion visibility (deletion_scheduled_at on GET /users/me; /users/me/delete/cancel clears it)
api/app/api/users.pyCaveats (honest), governed runs in chat (ADR 0038, proposed):
LQ_AI_ORCHESTRATION_CHAT_ENABLED and LQ_AI_NEW_HARNESS_ENABLED both default to off. The direct answer, a run in a chat that is in no matter, and the selection of a file a knowledge-base search finds amend ADR 0035 D1, D2 and D3, which are Accepted. ADR 0038 D1 gives the words, and they are ratified with it.gateway/**. Five are on a security path: the gateway changes (a reasoning-effort field, two Ollama tool-call fixes, and a caller’s opt-in that restores placeholders in the arguments of the tool calls a reply makes and puts that caller’s tool results and earlier call arguments under the request pseudonymization); the rule for a run outside a matter, including a matter’s file read on that matter’s terms; the roles on runs and the cross-owner read with its audit row; pseudonymization following the operator’s setting; and the knowledge-base search. Under ADR 0034, accepted on 2026-10-04, security review means maintainer review with the security-path checklist applied; the project has no separate security team. The DE-294 handoff rules are implemented as tests, not reviewed.propose_plan fails the plan. Any other complete reply from it is delivered as the orchestrator’s own answer, unverified.The Inference Gateway is the security boundary — the only component holding privileged provider API keys and the only component making outbound calls. As of the legal-research + MCP milestone this extends from inference egress to tool egress: case-law (CourtListener) and MCP-connector calls also leave only through the gateway, SSRF-guarded and audited (see §5.5). The backend (api/app) holds exactly one outbound HTTP client, pointed at the gateway. Four provider adapters ship: Anthropic, OpenAI, Azure OpenAI, Ollama (local Tier 1). Google Vertex AI remains deferred. The Bedrock compatible-API implementation now covers explicit Messages, Chat Completions and stateless Responses transports, with live AWS verification deferred beyond 0.9.1 by maintainer direction; see release acceptance and the deferred verification plan.
Inference gateway with provider routing
gateway/app/router.pyAPI/worker gateway HTTP timeout (ADR 0027): default 900s, operator override LQ_AI_GATEWAY_TIMEOUT_SECONDS, connect capped at the shorter of 10s and the configured value; API budget must exceed adapter budgets
api/app/config.py; api/app/clients/gateway.py; .env.example / .env.release.example; both Compose recipes; cd api && pytest tests/test_gateway_timeout_setting.pyAnthropic / OpenAI / Ollama provider adapters
gateway/app/providers/{anthropic,openai,ollama}.py; Ollama via docker compose --profile localAzure OpenAI provider adapter
gateway/app/providers/azure_openai.py (DE-267, closed in M2)Google Vertex AI provider adapter
AWS Bedrock compatible-API provider
gateway/app/providers/bedrock.py; verification gates; credit @dropthejase, #265Tier enforcement (Tiers 1–5) + privileged-matter tier floor
gateway/app/tier_floor.pyAnonymization pre/post middleware
gateway/app/anonymization/middleware.py (wired on the request path at gateway/app/api/inference.py); see §3.2. Recognizer accuracy on a legal corpus is empirically unmeasured — DE-282.Routing log (per-inference)
gateway/app/routing_log.pyUntrusted-content isolation — documents, skill inputs, retrieved chunks kept out of the model’s instruction channel
gateway/app/skills/assembler.py; cd gateway && pytest tests/test_skill_assembler.py; relocation tracked as DE-388Provider-key encryption at rest (Fernet master-key)
gateway/app/secrets.py; docs/security/encrypted-keys.mdHot-reload of gateway config via SIGHUP
gateway/app/config_holder.py; docs/adr/0010-gateway-config-hot-reload.mdRuntime provider keys / BYOK (gateway /admin/v1/provider-keys + backend is_admin proxy /api/v1/admin/provider-keys; GET masked last4/configured/source env|runtime; POST Fernet-encrypts into gateway.yaml and hot-applies the adapter in-place with no restart; PATCH rotates, DELETE revokes; requires LQ_AI_GATEWAY_MASTER_KEY else 400 failed_precondition; env keys still work and show source env)
gateway/app/provider_keys.py, gateway/app/api/admin.py; api/app/api/admin.py (proxy); api/app/clients/gateway.pyCharacter-level verification of every model-emitted citation against source documents; failed citations surface as “unverified” rather than confident wrong text.
api/app/citation/verification.py): Stage 1 verify_exact_match → Stage 2 verify_tolerant_match (rapidfuzz ≥95 + normalization) → Stage 3 verify_paraphrase (LLM judge via gateway) → Stage 4 verify_ensemble (N-model parallel, strict/majority, cost-budget fallback to Stage 3).GET /api/v1/chats/{chat_id}/messages/{message_id}/citations; rows persist in message_citations (migrations 0025–0027). Candidates that miss every stage are not persisted — the UI reads the absence as “unverified” (red).cd api && pytest tests/citation/ tests/test_chat_citations.py; full reference in docs/citation-engine.md.api/tests/citation/test_edge_cases.py).Pseudonymizes named entities before requests leave for the provider; rehydrates originals on the response path (streaming-aware). The middleware is wired and running — pre_anonymize_request is called on the request path in gateway/app/api/inference.py after tier derivation, before provider dispatch.
gateway/app/anonymization/engine.py): Presidio defaults (PERSON, ORGANIZATION, EMAIL_ADDRESS, PHONE_NUMBER, LOCATION via spaCy en_core_web_lg) + custom CaseNumberRecognizer + MatterNumberRecognizer.cd gateway && pytest tests/anonymization/ tests/test_inference_anonymization.py; full reference in docs/security/anonymization.md. Open surfaces: pseudonymize-source-docs (DE-269), per-request salt (DE-274).Codified legal positions with detection + redline strategy, plus an “easy” auto-generation pipeline from a document corpus.
Playbook CRUD + execute against a document
api/app/api/playbooks.py (GET/POST/PATCH/DELETE /api/v1/playbooks, POST /playbooks/{id}/execute, GET /playbook-executions/{id}); executor api/app/playbooks/Built-in seeded playbooks (NDA mutual/unilateral, MSA, DPA)
0032/0033; cd api && pytest tests/test_builtin_nda_playbooks.pyEasy playbook auto-generation (3-stage)
POST /api/v1/playbooks/easy + GET /playbooks/easy/{id}; migration 0035Tables: playbooks, playbook_positions, playbook_executions, easy_playbook_generations
0031, 0035Learn viz
web/static/learn/playgrounds/playbook-cascade.htmlCaveats (honest): execution runs in-process via FastAPI BackgroundTasks (not arq) per the M3 architecture decision; soft-delete only; built-ins are immutable (admins fork to edit); tracked-changes rendering into Word is deferred (DE-287).
Run a skill (or ad-hoc column spec) across a document corpus into a document × column grid with per-cell confidence + citations; export XLSX/CSV.
Cost preview · execute · list · get · cancel · soft-delete
api/app/api/tabular.py; arq job api/app/workers/tabular_worker.py; table tabular_executions (migration 0036)Export to XLSX (cell comments) / CSV (citation links)
GET /api/v1/tabular/executions/{id}/exportColumn-spec snapshot at execution start (auditable invariant)
api/app/models/tabular.py (columns JSONB snapshot)Per-cell provenance (DE-309): deterministic verbatim matches mint real ids/offsets in tabular_cell_citations (migration 0079); GET/cancel select minted rows by persisted m3-c2-v2 even with zero matches. Legacy-v1 and exports keep bridge ids; read-side source enrichment remains batched. Untyped in gen:api — DE-330
api/app/tabular/nodes.py; api/app/api/tabular.py; api/tests/tabular/test_cell_citations.py; api/tests/test_tabular_endpoints.pyapi/app/tabular/nodes.py (_verify_cell_ensemble), api/app/tabular/cost.py; api/app/schemas/tabular.pyLearn viz
web/static/learn/playgrounds/tabular-review.htmlCoverage boundary: tabular has unit/component coverage (api/tests/tabular/) plus live-DB GET/cancel provenance regressions (api/tests/test_tabular_endpoints.py) and bulk-operation endpoint tests. These do not substitute for a full worker-to-browser acceptance run. The legacy parent_execution_id sibling field remains separate from the retained tabular_bulk_ops implementation.
Installable Office.js add-in (manifest, task pane, React shell)
word-addin/manifest.xml, word-addin/src/taskpane/Admin manifest download + version handshake
api/app/api/word_addin.py (GET /api/v1/admin/word-addin/manifest, GET /api/v1/word-addin/version)OAuth sign-in (reuses /auth/login + refresh)
word-addin/src/taskpane/auth.ts; web/src/routes/lq-ai/word-addin/oauth-start/Learn viz
web/static/learn/playgrounds/word-addin-flow.htmlIn-Word chat / skills (tracked changes + comments) / playbook execution
Honest assessment: the add-in is installable, authenticatable, and version-safe, but every substantive feature surface is a placeholder pointing to the web app. Do not market it as feature-shipped.
Slack bridge service + OAuth install + encrypted persistence
slack-bridge/ (compose --profile slack, port 8002); table slack_workspaces (migration 0037, Fernet-encrypted bot token under a distinct master key); api/app/api/integrations_slack.pyTeams bridge service + multi-tenant admin-consent OAuth
teams-bridge/ (compose --profile teams, port 8003); table teams_tenants (migration 0038); api/app/api/integrations_teams.pyAdmin intake-bridges management (list + soft-delete)
api/app/api/admin_intake_bridges.py; web/src/routes/lq-ai/admin/intake-bridges/+page.svelte/lq slash-command intake surface
End-to-end OAuth against live Slack/Microsoft
docs/intake-bridges.md “Honest state up front”An opt-in background executor that does real in-loop agentic work under hard brakes. Not a skeleton — each phase calls real tools through the chokepoint. Full reference: docs/autonomous-layer.md. The analysis phase’s implementation described below (a single scripted call) was later replaced by a governed plan → act → observe → replan loop under the same brakes — see §5.6.
Five-phase executor (intake → analysis → drafting → ethics_review → delivery)
api/app/autonomous/executor.py, nodes.py; arq job autonomous_session_jobReal in-loop work: run_skill/run_playbook inference, emit_finding, propose_memory, propose_precedent, notify
api/app/autonomous/guard.py (_dispatch); cd api && pytest tests/autonomous/test_executor_real_work.pySingle chokepoint guarded_tool_call enforcing R5 → R6 → R4
api/app/autonomous/guard.py; tests/autonomous/test_executor_skeleton.py::test_no_tool_call_bypasses_chokepointR4 per-session and per-trigger cost cap (max_cost_usd)
api/app/autonomous/cost.py; migration 0045; tests/autonomous/test_r4_per_trigger_cap.pyR5 external halt (POST /autonomous/sessions/{id}/halt) + idle watchdog
api/app/workers/autonomous_worker.py (idle cron); tests/autonomous/test_idle_watchdog.pyR6 phase-gated tool grants (PHASE_GRANTS)
api/app/autonomous/enums.py; tests/autonomous/test_brakes.pyWatches (KB-attach-triggered sessions)
api/app/autonomous/watch_trigger.py; GET/POST/PATCH/DELETE /autonomous/watches; table autonomous_watches (migration 0039, max_cost_usd in 0045)Schedules (in-repo cron dispatcher)
api/app/autonomous/cron.py; /autonomous/schedules; table autonomous_schedulesPer-user memory (proposed → kept/dismissed)
/autonomous/memory/*; table autonomous_memoryPrecedent board (race-safe upsert, observed_count) + promote-to-Project proposals
/autonomous/precedents/*, /autonomous/project-context-proposals/*; tables precedent_entries (migration 0039), project_context_proposals (migration 0041)Honest per-session receipt (terminal_reason: completed / cost_cap_reached / external_halt / idle_timeout)
api/app/autonomous/receipt.py (build_receipt / build_receipt_safe); stored in autonomous_sessions.resultIn-app notifications (durable; best-effort email transport)
/autonomous/notifications/*; table autonomous_notifications (migration 0040)Per-user opt-in (off by default)
User.autonomous_enabled (migration 0044); spawn paths + mutate endpoints gatedFindings persistence (autonomous_findings written at the emit_finding chokepoint; GET /autonomous/sessions/{id}/findings; ?source_session_id= filter on GET /autonomous/memory, precedents excluded — recurrence-aggregated)
api/app/api/autonomous.py; table autonomous_findings (migration 0046)Document-grade artifacts (opt-in emit_artifacts, default OFF; drafting emit_artifact chokepoint direct-writes a real KB document — object-storage upload-first, File ready + Document + chunks, direct KB attach bypassing watch-fire so no loop; markdown/plain only; GET /autonomous/sessions/{id}/artifacts, owner-gated; notification payload artifact_count)
api/app/autonomous/guard.py (_handle_emit_artifact), api/app/api/autonomous.py; table autonomous_artifacts (migration 0047, session CASCADE / file SET NULL — the document outlives the session)Matter binding on schedules/watches (project_id accepted on create + PATCH incl. clear-to-null; ownership validated at all five assignment sites — create_schedule/create_watch/run-now/two PATCHes — closing a pre-existing IDOR)
api/app/api/autonomous.pyRetrieval ownership scoping (retrieve_chunks verifies the session owner owns the model-supplied kb_id/file_id before retrieval; mirrors the HTTP visibility predicates (owner scope + archived_at/deleted_at IS NULL); foreign or unknown ids fail closed with a 404-shaped error, closing a cross-user read reachable via prompt-injected planner args)
api/app/autonomous/guard.pyapi/app/skills/bootstrap.py; api/app/workers/arq_setup.py; docker-compose.yml (arq-worker volume)Web dashboard (sessions/receipt/halt, memory, precedents, watches, schedules, notifications, proposals)
web/src/routes/lq-ai/autonomous/; opt-in toggle at settings/autonomous/Learn viz
web/static/learn/playgrounds/autonomous-flow.html (phase walk + the four brake scenarios; the four primitives are not yet visualized — see §11)Honesty notes: the ethics-review phase is a light v1 (emits a privilege/scope-concerns finding from the structured output; a dedicated ethics LLM gate is a future enhancement). A gateway error mid-analysis produces an honest “analysis failed at the gateway” finding and a completed (not fabricated) receipt. Audit rows carry counts/types/IDs/enums only — never raw entity values or document text.
After M4, LQ.AI gained the ability to reach external tools — case-law lookup and operator-approved MCP connectors — without weakening the gateway boundary. The gateway is extended from sole inference egress to sole tool egress; a governed chat tool-loop routes every call through one chokepoint; destructive/connector calls pause for explicit human approval; and case-law results carry retrieval provenance kept architecturally distinct from character-verified citations. Shipped across #158–#193 (migrations 0048–0055); design in ADR 0014 / ADR 0015 and docs/proposals/legal-research-and-mcp.md (delivered).
Gateway tool-provider egress boundary — sole tool egress, SSRF-guarded, audited (tool_egress_log, counts/types only)
gateway/app/providers/tool/ (validate_egress_target); migration 0048_tool_egress_log.py; ADR 0014; api/app holds exactly one outbound client → the gateway (api/app/clients/gateway.py)CourtListener case-law provider (search / clusters / citation-lookup against REST v4)
gateway/app/providers/tool/courtlistener.py; cd gateway && pytest tests/test_courtlistener_adapter.pyResearch API subsystem (/api/v1/research/*: capabilities, search + cursor pagination, verify-citations, clusters/opinions/find-in-case)
api/app/api/research.py, api/app/research/service.py; migration 0049_research_metadata.py; cd api && pytest tests/test_research_endpoints.py tests/test_research_service.pyMCP client subsystem — gateway adapter (streamable_http, per-call OAuth token injection, tool discovery)
gateway/app/providers/tool/mcp.py; cd gateway && pytest tests/test_mcp_adapter.pyMCP registry + discovery-cache + /api/v1/admin/mcp surface
api/app/api/admin_mcp.py, api/app/mcp/service.py; migration 0050_mcp_tools.py; cd api && pytest tests/test_admin_mcp.py tests/test_mcp_service.pyPer-user MCP OAuth (api flow, tokens Fernet-encrypted at rest, gateway passthrough, return_url + connections list)
api/app/api/mcp_oauth.py, api/app/models/mcp_oauth.py, api/app/security/encryption.py; migrations 0051/0052; cd api && pytest tests/test_mcp_oauth_service.py tests/test_mcp_encryption.pyapi/app/tools/governance.py; migration 0053_tool_call_log.py; ADR 0015; cd api && pytest tests/test_tool_governance.pyGoverned chat tool-loop + persist-and-resume confirmation gate (in-chat approve/deny resumes the turn; connect-on-demand)
api/app/chat/tool_loop.py, api/app/api/chats.py (resume route); migration 0054_chat_pending_tool_call.py; UI web/src/lib/lq-ai/components/ToolGatePrompt.svelte; cd api && pytest tests/integration/test_chat_tool_call_resume.pyExternal-source citations — case-law retrieval provenance (“Sources consulted”), distinct from Citation-Engine verified quotes
api/app/models/message_tool_source.py, api/app/chat/tool_loop.py (extract_tool_sources); migration 0055_message_tool_sources.py; UI web/src/lib/lq-ai/components/ToolSourcesPanel.svelte + ProvenancePill.svelte; cd api && pytest tests/test_message_tool_sources.pycase-law-research skill + C5 tool-usage (declared via lq_ai.tool_usage, surfaced as “Uses: …” in the skill UI; surface-not-enforce)
skills/case-law-research/SKILL.md; api/app/skills/schema.py; web/src/lib/lq-ai/skills/toolUsageNote.ts; cd api && pytest tests/test_skills_tool_usage.pyOpenWebUI MCP stub retired (DE-341)
web/backend/open_webui/utils/mcp/ removed; grep -r "utils.mcp.client" web/backend is empty; PRD §DE-341 (Resolved in PR6e)Learn viz — governed-tool-boundary explorer
web/static/learn/playgrounds/governed-tool-flow.html (Learn → How it works)Caveats (honest):
tool_providers entry (external egress is operator-controlled — ADR 0014). Case-law requires setting COURTLISTENER_API_TOKEN in .env (forwarded into the gateway container by docker-compose.yml) and uncommenting the courtlistener-prod block in gateway.yaml (template in gateway.yaml.example), then restarting the gateway; /api/v1/research/capabilities reports enabled: true once on. MCP connectors register via /api/v1/admin/mcp + per-user OAuth. See the README “Enabling legal-research connectors” section. Verified live on a fresh clone (build → migrate 0055 → enable → real CourtListener retrieval → message_tool_sources → tool_egress_log).message_tool_sources) from the Citation Engine’s verified quotes (message_citations). The two are deliberately never conflated.tool_call_log — counts/types/args_digest only, never raw arguments) but do not yet emit a dedicated OTel chat.tool_call span (deferred).LQ_AI_GATEWAY_MASTER_KEY; env-configured keys work without it.Building on the governed tool-loop in §5.5, a fiduciary-grade milestone shipped next: a per-turn ledger of every source the assistant actually read, a pass/fail gate over every tool-retrieved citation, governed plain-language matter sessions on the Autonomous Layer, three more free authority sources, and a derived case-law treatment signal. Migration head 0064 (ledger/gate 0058/0059; treatment 0061/0062; authority citations + text cache 0064). Design in ADR 0018 (citation ledger + fiduciary gate), ADR 0019 (treatment layer), ADR 0020 (matter sessions), and ADR 0021 (content-source registry) — all Status: Accepted, all merged.
Citation Ledger — per-turn record of every source/passage actually read (chat + autonomous sessions), one-click trace from a claim to its ledger entry
api/app/citation/ledger.py (assemble_ledger_entries/resolve_ledger_entries); GET /api/v1/chats/{chat_id}/ledger (api/app/api/chats.py), GET /api/v1/autonomous/sessions/{session_id}/ledger (api/app/api/autonomous.py); migration 0058_citation_ledger_entry.pyFiduciary-grade gate — derive-don’t-assert PASS/FAIL verdict per assistant message, computed from the ledger’s own verification statuses
api/app/citation/gate.py (compute_and_record_gate); migration 0059_work_product_fiduciary_gate.py; ADR 0018 D3Governed agentic matter sessions — plain-language “describe your matter” query drives a governed plan → act → observe → replan loop confined to the analysis phase, under the same R4/R5/R6 brakes
api/app/autonomous/planner.py; PRs #239/#240; ADR 0020Content-source registry + free authority sources — GovInfo (US Code/CFR statutes and regulations), SEC EDGAR (public company filings), EUR-Lex (EU legislation + CJEU case law by CELEX), alongside CourtListener
api/app/research/registry.py (SOURCE_REGISTRY); ADR 0021Validity / treatment layer — judge-backed, derived case-law treatment signal (e.g. followed, distinguished, criticized) from citing opinions, with one-click trace back to each citing case read
api/app/citation/treatment.py (derive_treatment_for_message); migrations 0061_citation_treatment.py/0062_citation_treatment_signal.py; ADR 0019Caveats (honest):
web/src/routes/lq-ai/autonomous/) rather than a purpose-built intake flow.SOURCE_REGISTRY["eurlex"].ops == ("get_authority",) — no search_authority. Full-text search via Cellar SPARQL (DE-374) and treaty/corrigendum CELEX support (DE-375) are both open.tool_providers entry in gateway.yaml (ADR 0021 D1/D5); an unconfigured registry entry is reported unavailable, never fabricated.Honest milestone deferrals — the subsystem does not yet exist (or only as plumbing). Verifiable by absence.
In-Word feature surfaces (chat/skills/playbooks in the add-in)
word-addin/ tabs are deep-link cards; DE-287/lq Slack/Teams slash-command intake
Contract Repository auto-relationship detection (PRD §3.16)
contract_relationships table in api/alembic/versions/The Compliance Alignment Pack at docs/compliance/ is a documented commitment; the per-framework alignment docs land incrementally. The pack is the project’s contribution to the operator’s certification work (pre-mapped control responses citing source), not a certification of the project itself.
Threat model (STRIDE) / Architecture / Cryptography / Audit-logging / Encrypted-keys / Dependencies
docs/security/*.md, docs/architecture.mdSecurity policy + coordinated disclosure
SOC2 / ISO 27001 / ISO 42001 / GDPR / HIPAA / FedRAMP alignment
docs/compliance/README.md describes the format; per-framework docs land incrementallyOWASP LLM Top 10 / NIST AI RMF profiles
docs/contribute/mini-prds/Procurement Pack (SIG Lite + CAIQ)
docs/procurement/sig-lite.md; full pack DE-086Engineering rigor is measurable, not asserted. Test file counts below are verifiable without standing up the stack (find … | wc -l); pass counts run in CI (.github/workflows/ci.yml).
Documented test strategy + per-surface E2E coverage matrix
docs/test-strategy.md — inventory, surface × depth matrix with real spec paths, CI-reality statement, flake + gap registersBackend tests (pytest, live Postgres)
test_*.py files in api/tests/ (incl. tests/autonomous/, tests/citation/, tests/tabular/; pass count refreshed in CI per the .github/workflows/ci.yml API gate); cd api && DATABASE_URL=… pytestGateway tests (pytest)
test_*.py files in gateway/tests/; cd gateway && pytestFrontend unit tests (Vitest)
*.test.ts files in web/src/; cd web && npx vitest runCypress E2E (LQ.AI shell)
web/cypress/e2e/ (all LQ.AI-authored; not run in CI, see docs/test-strategy.md §2)Ruff lint + format (Python)
.github/workflows/ci.yml: ruff check api scripts + ruff format --checkmypy (api standard, gateway strict)
mypy app per subsystemsvelte-check (LQ.AI-owned code)
cd web && npm run check:lq-ai (0 errors on src/{lib,routes}/lq-ai/**); inherited OpenWebUI debt tracked as DE-262 (§8.1)Coverage gate (target 80% api / 90% gateway)
--cov-fail-under (.github/workflows/ci.yml). Measured 2026-07-25: api 81.49% (12242/15023 stmts) → gated at the 80% target; gateway 88.94% (4464/5019 stmts) → gated at the measured floor of 88%, not the 90% targetMutation / property-based testing, eval harness, Cypress-in-CI
OpenSSF Scorecard / Best Practices Badge
docs/contribute/mini-prds/Sigstore-signed images / SBOM / SLSA build provenance per release
docs/security/releases/README.md; the v0.7.1 release run logs an attestation per imageSLSA Build Level 3 (provenance from an isolated reusable workflow)
.github/workflows/build-image.yml; verify with --signer-workflow LegalQuants/lq-ai/.github/workflows/build-image.yml (see the releases README)Annual third-party pen test + adversarial red-team
The dedicated accessibility.yml check uses a production frontend with synthetic API responses, separate from the full-stack nightly Cypress workflow introduced by #432 and the broader per-PR deterministic-track work in #706. It covers seven route states × two viewport widths × two explicit theme preferences. Exact pre-existing noncritical fingerprints are tolerated; every new axe A/AA violation fails. Local measurement: 28 browser variants passed with 230 serious fingerprints recorded from main at 3ecf284177b97ff6acdcaff5eda57bee0bf86a98; manual/incomplete checks and broader route coverage remain open. The first two GitHub runs passed their production builds but stopped at baseline provenance validation after main changed; the latest local refresh measures main after #710. Passing GitHub scanner evidence and required-check activation remain pending. See the credited implementation plan and audit scope. Source credit: @SaifAlYounan, #437/#386. This supersedes any older “no browser checks” wording only for this new first slice; DE-232 is not complete.
The web frontend is a fork of OpenWebUI (ADR 0001). npm run check (full scope) surfaces ~9,359 TypeScript strict-mode signals, all in upstream files inherited at fork time; none in LQ.AI-owned code, none in the (separate Python) gateway. CI scopes the check to LQ.AI code (npm run check:lq-ai). Migration tracked as DE-262.
Docker Compose reference deployment
docker-compose.yml — always-on: postgres, redis, rustfs, gateway, api, ingest-worker, arq-worker, web; the opt-in ops profile hosts the journaled migration CLILocal-only profile (Ollama)
docker compose --profile local up — adds the Ollama sidecar. Scanned-PDF OCR / PaddleOCR is not implemented (DE-320); the prior placeholder sidecar was removed.Slack / Teams bridge profiles
docker compose --profile slack up / --profile teams upWorker skill-registry bootstrap (operator-visible change, #139)
./skills:/skills:ro and installs the same registry. api/app/skills/bootstrap.py; docker-compose.ymlHelm chart for Kubernetes
deploy/helm/lq-ai/ (worker-migration parity with the compose single-migrator fix is a community item — DE-327)OpenTelemetry instrumentation (traces + metrics + domain spans)
Reverse-proxy/TLS recipes · backup tooling · complete runbook set · SLOs · status page · postmortem · DR cadence
docs/runbooks/minio-to-rustfs.md; mini-PRDs / deferreddocker compose up -d --build — the api runs migrations 0001→0077 on boot).If a claim does not check out, the codebase is canonical — please open an issue. The point of publishing this in source is that verification runs through readable code, not a vendor’s representation of it.
autonomous-flow.html covers phases + brakes only); projects/matters + org-profile + privilege tiers; KB hybrid retrieval (BM25 + vector); the §5.6 fiduciary-grade capabilities (Citation Ledger trace, fiduciary gate, governed matter sessions, content-source registry, treatment layer — none has a Learn playground yet). Tracked in the M4-D2 doc/Learn alignment plan.build_receipt_safe hardening), DE-326 (fresh-install worker alembic-migration race). DE-327 (Helm worker-migration parity) is open as a community-suitable item.Maintained per release. Last rewritten at the M4 close (Autonomous Layer shipped end-to-end; fresh-install acceptance passed), reconciled against the post-v0.4.0 “Donna” run (#115–#139; head 0047), then reconciled against the legal-research + connectors (MCP) milestone (#158–#193; migration head 0055; the release-readiness verification pass for v0.5.0), then reconciled a third time against the fiduciary-grade agentic legal work milestone (ADRs 0018–0021; migration head 0064) as part of the DE-365 docs-honesty audit (docs/audits/2026-07-01-claims-vs-reality.md), which added §5.6 and refreshed the header, the M4 cross-reference, and the §8 test-file counts. Substantive content drivers: PRD §3 (capabilities), PRD §8 (roadmap), PRD §9 (deferrals), and the per-feature docs (docs/citation-engine.md, docs/playbooks.md, docs/tabular-review.md, docs/word-addin.md, docs/intake-bridges.md, docs/autonomous-layer.md, the ADRs docs/adr/0014/0015 + docs/proposals/legal-research-and-mcp.md for the tool-egress/MCP milestone, and ADRs docs/adr/0018–0021 for the fiduciary-grade milestone).