A deterministic, auditable, plug-in platform for all things rules-based document review. One engine. Many domains. Same input β same verdict, every time. Built to withstand legal, regulatory and audit scrutiny.
Lambda-RAG turns any policy / regulation / contract template into an executable rule set, then projects those rules over a target document (contract, architecture design, MOU, permit application, ITSM runbook, etc.) and produces:
- π A structured verdict report β score, per-rule pass / fail / gap / N/A, remediation text, full audit trail
- π A redlined Word document β tracked-changes + comments anchored to the offending clause, with a top-of-document GAP ANALYSIS summary
- π Or both β emitted from the same deterministic pipeline
Generative LLMs are non-deterministic. For contract review, regulatory compliance, audit, or permitting you cannot defend a verdict that changes between runs. Lambda-RAG enforces a strict separation:
| Phase | When | LLM allowed? | Determinism guarantee |
|---|---|---|---|
| Authoring | Offline, once per rule | β Yes (temp=0, JSON-schema-validated, human-reviewed) | Output is signed, fingerprinted, version-locked |
| Projection | Runtime, per document | Same bytes β same projection | |
| Selection | Runtime, per rule Γ section | β Never | Pure-code JSONPath / regex / topic-map match |
| Evaluation | Runtime, per matched section | β Never | Microsoft RulesEngine lambda |
| Markup | Runtime, per verdict | β Never | OpenXml tracked changes, fixed timestamp, pinned IDs |
At runtime no LLM is in the decision loop. Re-running the same
review against the same ruleset produces byte-identical OOXML parts
inside reviewed.docx and a byte-identical report.json.
π Before evaluating lambda-rag, please read
docs/what-lambda-rag-is-not.md. It is the explicit non-claims sheet β what we deliberately do not guarantee β and is the most useful single page for anyone deciding whether this tool fits a regulator-facing use case.
π§ The engineering discipline is codified in
docs/FOUR-PILLARS.mdβ Determinism, Idempotency, Accuracy, and Flexibility (in-domain paraphrase robustness). Every feature must pass a falsifiable test for each pillar before it ships. Cross-domain review is blocked at the entry point via--domain(defaults to the ruleset's authored domain) β seeDomainScopeValidator.
πΌοΈ One picture:
docs/diagrams/authoring-vs-runtime.mdis the canonical authoring-vs-runtime architecture diagram. Use it in slides, papers, and onboarding.
π One page of prose:
docs/manifesto.mdβ Rule Projection: Deterministic Reasoning over Documents. The pattern, the bet, and the honest limits. Read this before deciding whether lambda-rag fits your problem.
Out of the box, Lambda-RAG ships with topic ontologies for several high-review-burden industries. Each maps free-form section headings and keywords onto canonical topic IDs that rules can be authored against:
| Topic map | Use cases |
|---|---|
contract.v1 |
Commercial contract review (payment terms, governing law, warranty, IP, liability, β¦) |
architecture-review.v1 |
Cloud architecture / ASD review (security, network, compliance, performance, β¦) |
fsi.v1 |
Financial services (basel, AML, KYC, capital adequacy, model risk, β¦) |
oil-gas.v1 |
Upstream / downstream policies (HSE, well integrity, asset integrity, environmental, β¦) |
business-review.v1 |
MOUs, SOWs, business cases, vendor reviews |
gov-architecture.v1 |
Government cloud architecture review |
permitting.v1 |
Government permit / planning application review |
arb-psa.v1 |
Architecture Review Board β Project Solution Architecture review (12 dims) |
healthcare.v1 |
Healthcare / HIPAA (PHI safeguards, BAAs, breach notification, telehealth, β¦) |
privacy-gdpr.v1 |
EU GDPR (lawful basis, DPIA, data subject rights, cross-border transfers, β¦) |
privacy-law25.v1 |
QuΓ©bec Law 25 (consent, PIA, automated decisions, data residency, β¦) |
List them at any time:
dotnet run --project src/LambdaRag.Cli -- topic-map listdotnet build
dotnet test # 266 unit + 35 idempotency / golden-master proofs
# Review the bundled sample contract β JSON report
dotnet run --project src/LambdaRag.Cli -- review `
--document samples/contracts/contoso-sample-contract.docx `
--ruleset rulesets/contracts/contoso-demo-ruleset.json `
--out out/sample `
--mode report
# Same review β redlined Word document with tracked changes
# (Markup mode requires a .docx source β uses the bundled sample contract)
dotnet run --project src/LambdaRag.Cli -- review `
--document samples/contracts/contoso-sample-contract.docx `
--ruleset rulesets/contracts/contoso-demo-ruleset.json `
--out out/sample `
--mode markup
# Add positive-confirmation β comments for Pass verdicts (full coverage proof)
dotnet run --project src/LambdaRag.Cli -- review `
--document samples/contracts/contoso-sample-contract.docx `
--ruleset rulesets/contracts/contoso-demo-ruleset.json `
--out out/sample `
--mode markup `
--annotate-pass
# Both at once, with --rewrite to emit tracked-change replacements
# (ComplianceEditor renders a concrete rewrite for each Fail verdict
# and the markup stage applies it as a w:del / w:ins pair anchored
# to the offending clause)
dotnet run --project src/LambdaRag.Cli -- review `
--document samples/contracts/contoso-sample-contract.docx `
--ruleset rulesets/contracts/contoso-demo-ruleset.json `
--out out/sample `
--mode both `
--rewriteOutputs land in out/sample/:
report.jsonβ verdict, score, per-rule outcome, remediation, full provenancereviewed.docxβ original document with tracked changes + comments + gap-analysis summary
The platform is designed so you can drop in any set of policy documents (PDF, Word, Markdown, JSON) for any industry / customer and have an executable ruleset out the other end.
Best when you have customer / regulator policy PDFs or Word docs.
The --domain flag tells the extractor which topic ontology to use
when mapping policy sections to canonical topic IDs. Available domains:
| Domain | Description |
|---|---|
contract |
Commercial contract review (default) |
architecture-review |
Cloud architecture / ASD review |
fsi |
Financial services (Basel, AML, KYC, capital adequacy) |
oil-gas |
Upstream / downstream (HSE, well integrity, environmental) |
business-review |
MOUs, SOWs, business cases, vendor reviews |
gov-architecture |
Government cloud architecture review |
permitting |
Government permit / planning application review |
# 1. Drop your policy files into a folder
mkdir policies\acme-corp
# copy ACME-Procurement-Policy.pdf, ACME-DataProtection.docx, etc. into it
# 2. Run the deterministic extractor
dotnet run --project src/LambdaRag.Cli -- extract-rules `
--policy-dir policies/acme-corp `
--domain contract `
--id rs_acme_procurement `
--out rulesets/contracts/acme-procurement.json `
--prefix ACME `
--min-chars 200Output: rulesets/contracts/acme-procurement.json β every rule includes:
- A natural-language statement
- A typed predicate (lambda) the engine evaluates
- A pointer to the source span in the originating policy document
- An applicability tag (Mandatory / Conditional / Optional, inferred at authoring time)
- A content-addressed fingerprint
Review it, edit it, commit it, version it β it's plain JSON.
When you have one policy clause and want a single rule:
dotnet run --project src/LambdaRag.Cli -- author `
--chunk policies/acme-corp/clause-7.txt `
--domain contract `
--prefix ACME `
--out rulesets/contracts/clause-7-rule.jsonLook at rulesets/contracts/ruleset.json. The schema is small and
documented in docs/. Anything you can express as a typed predicate
over a projected document graph can be a rule.
# Sanity-check coverage of your ruleset against a target document
dotnet run --project src/LambdaRag.Cli -- coverage `
--document my-customer-doc.docx `
--ruleset rulesets/contracts/acme-procurement.json `
--out out/acme/coverage.json
# Run the full review
dotnet run --project src/LambdaRag.Cli -- review `
--document my-customer-doc.docx `
--ruleset rulesets/contracts/acme-procurement.json `
--out out/acme `
--mode bothOnce a new ruleset + sample document pair works end-to-end, freeze it
under tests/Goldens/corpus/<vertical>/ so every future change is
checked against it on CI:
tests/Goldens/corpus/<vertical>/
ruleset.json β the ruleset you authored above
<doc-id>/
source.md β the test document (markdown)
expected-verdict.json β frozen runtime output (byte-identical replay)
expected-llm.json β LLM ground-truth verdicts per rule
Then both gates run on it automatically:
# Byte-identical replay (CorpusRegression + RuntimeDeterminismGuardrails)
dotnet test tests/LambdaRag.IdempotencyTests --filter "FullyQualifiedName~CorpusRegression"
# LLM-level accuracy gate (AccuracyHarness β recall β₯0.85, FP=0, F1 β₯0.85)
dotnet test tests/LambdaRag.IdempotencyTests --filter "FullyQualifiedName~AccuracyHarness"Generating expected-llm.json for a new scenario: run the
tools/LlmGroundTruth helper (Azure OpenAI) against your source.md
ruleset.json; it emits the per-rule TRUE/FALSE/NA labels the AccuracyHarness compares the runtime against. Each AccuracyHarness run also appends a row tobench-results/cross-industry-ledger.csvso you can sweep the Pillar-9 dials and pick the best operating point.
If the ontology you need isn't in the table above, copy
src/LambdaRag.Projection/TopicMaps/contract.v1.json to
my-industry.v1.json, add your headings/aliases per topic, rebuild,
and pass --topic-map my-industry.v1 to the extractor.
Every projected section now carries a text_features block with
generic numeric facts extracted from the section's prose:
| Field | What it captures | Example match |
|---|---|---|
day_counts / day_count_min / day_count_max |
day quantities | 45 days, 120-day cure, 90 calendar days |
month_counts / _min / _max |
month quantities | 12 months, 36-month term |
year_counts / _min / _max |
year quantities | 5 years, 2-year warranty |
percent_values / percent_min / percent_max |
percentages | 1.5%, 30 percent |
dollar_amounts / dollar_min / dollar_max |
dollar values | $5,000,000, $1.5M, USD 10,000,000, CAD$ 2.5 million |
Rule lambdas reference these fields directly β no per-domain code:
{
"predicate": "input1.topics.Contains(\"insurance\") && input1.text_features.dollar_amounts.Count > 0",
"lambda": "input1.text_features.dollar_max >= 5000000"
}This is a generic extractor: it works on any domain (vendor bonds, ESG recycled-content thresholds, permit response windows, pipeline pressure-test durationsβ¦). The same rule shape is used for contracts, public-sector permitting, oil-and-gas, FSI policies, and governance frameworks.
Rules can declare semanticAnchors[] β named natural-language phrases
the engine cosine-binds against the tokens of each candidate section at
evaluation time. The lambda then accesses bindings via
LambdaPrimitives.SemanticBindings("anchor_name") to express things
like "the section mentions RPO or any phrasing that semantically
means RPO" without the lambda having to enumerate every synonym.
{
"id": "ARB-PSA-DR-001",
"semanticAnchors": [
{ "name": "rpo", "anchorText": "recovery point objective rpo data loss", "threshold": 0.78 },
{ "name": "rto", "anchorText": "recovery time objective rto downtime", "threshold": 0.78 },
{ "name": "failover", "anchorText": "failover design warm standby hot standby", "threshold": 0.78 }
],
"lambda": "LambdaPrimitives.SemanticBindings(\"rpo\").Count > 0 && LambdaPrimitives.SemanticBindings(\"rto\").Count > 0"
}Determinism: tokens are produced by the signed SemanticTokenizer
(TokenizerVersion = "semantic-tokenizer-v1", stopword list SHA-256
exposed via SemanticTokenizer.StopwordHash), and embeddings are
served from the file-backed embedding cache β no LLM call at runtime.
Every binding is emitted on Verdict.SemanticBindings with
(anchor, matched, cosine, charStart, charLength) so an auditor can
re-derive the verdict from the projection + ruleset + embedder bytes.
Rules without semanticAnchors[] skip the binding pass entirely;
adding Pillar 6 to the engine cannot flip a single pre-Pillar-6
verdict (asserted by tests/LambdaRag.IdempotencyTests/AdditiveGuaranteeTests.cs).
lambda-rag review --document <path> --ruleset <path> --out <dir> [--mode report|markup|both] [--overlay <path>] [--annotate-pass] [--rewrite] [--doc-kind <id>] [--topic-map <id-or-path>] [--domain <name>]
lambda-rag extract-rules --policy-dir <dir> --domain <name> --id <ruleset-id> --out <path>
lambda-rag author --chunk <path> --domain <name> --prefix <id-prefix> --out <path>
lambda-rag coverage --document <path> --ruleset <path> --out <path>
lambda-rag project --document <path> --out <path>
lambda-rag parse --document <path> --out <path>
lambda-rag index --ruleset <path> [--out <path>] # authoring-side only β see wrong-path-search-index.md
lambda-rag topic-map <list|show|coverage> [args]
# Governance β never edits the ruleset; works through diffs and overlays
lambda-rag rules diff <old.json> <new.json> [--out diff.json]
lambda-rag rules show --ruleset <path> --rule <id>
lambda-rag rules disable --ruleset <path> --overlay <path> --rule <id> --reason "..." [--by <name>]
lambda-rag rules enable --ruleset <path> --overlay <path> --rule <id>
lambda-rag rules annotate --ruleset <path> --overlay <path> --rule <id> --note "..." [--by <name>]
A web UI is on the roadmap. For now everything runs from the CLI and produces files you can diff, hash, sign, and ship.
Lambda-RAG deliberately ships without an in-place rule editor. The legal-defensibility chain is:
Signed policy PDF β extract-rules β RuleSet.json (in git) β review β Verdict
Editing a rule directly in the index would break the cited source span, silently invalidate idempotency, and create two competing sources of truth. So the platform is opinionated:
The policy document is law. The RuleSet is its compiled form. Both are versioned. Neither is edited in production.
When a rule legitimately needs to change, edit the policy doc and
re-run extract-rules. To see what changed:
lambda-rag rules diff old-ruleset.json new-ruleset.json --out delta.jsonYou'll get added / removed / changed rules, and for each changed rule
the exact list of fields that drifted (predicate, lambda,
severity, applicability, schema, naturalLanguage, version).
Exit code is 2 when there are deltas β wire it into CI to gate
ruleset promotions.
There are exactly two such cases, and both are handled via a RuleOverlay sidecar β never by mutating the ruleset:
-
Suppress a rule β e.g. "rule X is superseded by a side-letter"
lambda-rag rules disable ` --ruleset rulesets/contracts/acme.json ` --overlay rulesets/contracts/acme.overlay.json ` --rule ACME-PAY-003 ` --reason "superseded by 2026-Q2 side-letter clause 4.2" ` --by legal@acme.com
-
Annotate a rule β reviewer commentary that does not change the verdict
lambda-rag rules annotate ` --ruleset rulesets/contracts/acme.json ` --overlay rulesets/contracts/acme.overlay.json ` --rule ACME-LIAB-001 ` --note "see clause 7.2 in MSA β capped at fees paid in prior 12 months" ` --by legal@acme.com
Then run a review with the overlay applied:
lambda-rag review `
--document customer-doc.docx `
--ruleset rulesets/contracts/acme.json `
--overlay rulesets/contracts/acme.overlay.json `
--out out/customerProperties of overlays that make them safe:
- π Bound to a specific RuleSet id + version β refuse to apply to a different ruleset
- π§Ύ Every disable carries a
reasonand anattimestamp (and optionallyby) β--reasonis required - π Recorded on the report β
report.jsonhas anoverlayAppliedblock with the overlay's SHA-256 fingerprint, the disabled list, and the annotations, so any reviewer can see exactly which governance decisions were active for that run - π Sidecar JSON, not a database β store next to the ruleset in git; review via PR; revert via
rules enable - β Never edits a rule's predicate, lambda, severity, or applicability β those changes have to flow through the policy β extract pipeline
This is the pattern used by signed-binary release management, applied to rules. You get all the practical value of an "editor" (turn a rule off, attach a note) with none of the chain-of-custody risk.
src/
LambdaRag.Core/ Domain, hashing, selectors, abstractions
LambdaRag.Parsing/ PDF/DOCX/MD parsers β ParsedDocument
LambdaRag.Projection/ ParsedDocument β ProjectedDocument + topic maps
LambdaRag.Selectors/ JSONPath-subset matcher
LambdaRag.Evaluation/ Microsoft RulesEngine wrapper, verdict aggregator
LambdaRag.Markup/ OpenXml tracked-changes annotator + IClauseRewriter
LambdaRag.Authoring/ MAF agents: extract rules + ComplianceEditor (rewrites)
LambdaRag.Authoring.ExtractFunction/ Azure Function β Web API custom skill for indexer-driven extraction
LambdaRag.Indexing/ Authoring-side Azure Search adapters (rule semantic / signature indexes)
LambdaRag.Persistence/ SQLite stores: rules, projections, evaluations
LambdaRag.Api/ ASP.NET Core minimal API β POST /review
LambdaRag.Cli/ `lambda-rag` command-line tool
tests/
LambdaRag.UnitTests/ 266 unit tests
LambdaRag.IdempotencyTests/ 35 run-twice + golden-master byte-equality proofs
Goldens/corpus/ 5-vertical regression corpus (frozen expected-verdict.json)
rulesets/ generated and hand-authored rulesets, organized by domain
arb/ ARB rulesets (arb-ruleset.json, arb-ruleset-with-examples.json)
cloud/ cloud architecture rulesets (cloud_architecture.json, β¦)
contracts/ contract review rulesets (contoso-demo-ruleset.json, loi-25-ruleset.json, β¦)
samples/ sample input documents for review (no rulesets here)
architecture/ sample_asd.docx, Cloud-service-Architecture.docx
contracts/ contoso-sample-contract.docx, contract.md
policies/ source policy documents (input to extract-rules / author)
arb/ Architecture Review Board markdown policies
cloud/ Azure cloud policy documents
docs/ ARCHITECTURE.md, DETERMINISM.md, PIPELINE.md, SELECTORS.md, manifesto.md, diagrams/, findings/, regulatory/
wrong-path-search-index.md Postmortem: why the runtime never reads rules from Azure Search
π§ Anti-pattern note. A previous direction wired the runtime to fetch rules from an Azure AI Search index. It is rolled back on
main. The reasoning, signals we missed, and rules for next time live inwrong-path-search-index.md. TheLambdaRag.Indexingproject remains for authoring-side discovery only β no runtime evaluation reads from a search index.
Phase 0 (credibility close-out) β β complete. Phase 0 gap analysis, reviewed.docx golden-master idempotency, defensible accuracy framing,
what-lambda-rag-is-not.md, and a Roslyn-scripting contingency for the RulesEngine dependency are all shipped. SeeCHANGELOG.mdand the phase-0 backlog filter.
P1.8 (golden test corpus) β β shipped (8 verticals). A public-source-grounded regression corpus lives under
tests/Goldens/corpus/with eight verticals: gov-architecture (Government of Canada Cloud Guardrails v2.0), fsi (OSFI Guideline B-10), contract (TBS SACC + PIPEDA), permitting (Ontario Building Code O.Reg.332/12 + IASR/AODA O.Reg.191/11
- Impact Assessment Act S.C.2019 c.28 + Constitution Act 1982 s.35), oil-gas (CER Onshore Pipeline Regulations SOR/99-294 + Methane Regulations SOR/2018-66 + AER Directive 071 + s.35), healthcare (HIPAA Security & Privacy Rules + HITECH breach notification), privacy-gdpr (EU GDPR Arts. 5/6/13/30/35/44), and privacy-law25 (QuΓ©bec Law 25 / Act respecting the protection of personal information in the private sector). 17 candidate documents, frozen
expected-verdict.jsonsnapshots, paired LLM ground truth (expected-llm.json), and acorpus-regressionGitHub Actions job that fails the build on any drift.
P1 pattern-definition batch β β shipped (5 docs). The canonical documentation set for the rule-projection pattern is now in repo: the manifesto (P1.1 #11), the authoring-vs-runtime diagram (P1.6 #16), and three regulatory clause-by-clause mappings: OSFI E-23 (P1.2 #12), TBS Directive on ADM (P1.5 #15), and Bill C-27 / AIDA (P1.3 #13) with ~80 candidate rules sketched and worked JSON examples for each.
Phases 1β5 (canonical pattern, Canadian regulatory wedges, distribution, governance + tooling, ecosystem) live as labelled GitHub issues. Near-term:
- π₯οΈ Lightweight web UI (drag-drop document + ruleset β verdict + redlined .docx download)
- π Live Word task-pane add-in for in-place review (currently offline
.docxmarkup only) - π REST API surface in
LambdaRag.Apiexposing the same pipeline - β Positive-confirmation comments in markup mode (currently only Fail / Gap / Error are surfaced)
Apache License 2.0 β see LICENSE for the full text.