The Control Evidence Agent — Grounded GenAI for NIST 800-171 / CMMC

Stellar Ideas LLC (first-party R&D) · GenAI · Security & Compliance

14/14 Families Hand-Verified
320 Objectives Labeled
4 Model Tiers Compared
175 Tests · $0 · No Network

This is the summary. The full case study — with the interactive per-family chart — lives here: View the full visual case study →. There is also an AI-engineering deep-dive for the systems detail.

A retrieval-augmented agent that drafts NIST SP 800-171 assessment findings — a status (met / not_met / customer / inherited) plus a cited justification for each of the standard’s 320 assessment objectives — and self-checks every sentence against the source it cites before the finding is allowed to stand. Built entirely as internal R&D on my own CMMC Level 2 / NIST 800-171 AWS landing zone, with a stub → local-model → Amazon Bedrock provider ladder behind a single interface.

The project was never “an LLM writes compliance text.” It was to build the machinery that makes an LLM’s compliance text trustworthy and measurable — and, ultimately, an eval-validated map of exactly where a language model can be trusted on this task and where it can’t. That map is the deliverable.

The Challenge

CMMC Level 2 requires a contractor to show, for each of 110 requirements (320 assessment objectives), that a control is implemented — and to keep the SSP narrative honest about what is actually evidenced versus merely claimed. In practice the implementation matrix over-claims: a row says “implemented” while the defining artifact is a TODO, or the mapped Config rule checks something adjacent to what the objective actually asks.

Reconciling claim against evidence, at objective granularity, across a whole standard, is exactly the tedious, high-stakes work a grounded LLM should help with — if it can be trusted not to invent the evidence, and if you can measure where its judgment is sound.

What I Built

Four layers, deterministic-first, each testable at $0 with no model, no network, and no AWS:

Python standard library only (boto3 the single optional dependency, lazily imported for the Bedrock path). temperature=0 and a fixed seed, so runs reproduce exactly. 175 tests, none touching a model, a network, or AWS.

The Headline Result

I brought all 14 families / 320 objectives to hand-verified ground truth, then ran the identical harness with three generators: a $0 lexical stub, a local qwen2.5:14b (Ollama, on a 16 GB laptop), and Claude Haiku 4.5 on Bedrock. The stub is the floor a real model must beat — “has evidence → met” — and it structurally cannot emit customer or inherited.

Sorting the 14 families by the 14B’s status-correctness margin over the stub produces a clean, monotone curve in how much each family’s ground truth requires human judgment:

Familystub $0qwen2.5:14b14B − stub
3.2 Awareness & Training0%100%+100
3.10 Physical Protection0%88%+88
3.6 Incident Response21%86%+65
3.9 Personnel Security0%50%+50
3.7 Maintenance30%80%+50
3.8 Media Protection33%80%+47
3.12 Security Assessment29%57%+28
3.5 Identification & Auth36%44%+8
3.3 Audit & Accountability66%69%+3
3.13 System & Comms Protection61%61%0
3.1 Access Control53%53%0
3.11 Risk Assessment78%67%−11
3.14 System & Info Integrity55%40%−15
3.4 Configuration Management66%48%−18

Where a family’s ground truth is dominated by human judgment the heuristic can’t express — Awareness & Training is customer 9/9, so the stub scores 0 and the reasoning model scores 100 — the model wins by the maximum possible margin. Where the ground truth aligns with an evidence-presence heuristic (Configuration Management is met-heavy because the IaC codebase is the config baseline), the model loses by refusing to rubber-stamp the borderline met calls the stub gets for free.

The takeaway is methodological and load-bearing: aggregate correctness rank is a property of the standard’s shape, not of the models. A single accuracy number, averaged across families, would rank a reasoning model below a one-line heuristic — because half the standard is exactly where the heuristic looks good. That is why the harness scores the per-objective diff, and the curve above is the proof it has to.

Self-Hosted vs. Bedrock, for CUI

For CUI you’d love to keep data on infrastructure you control — so could a self-hosted model do the generator or judge job, or is a managed frontier model required? I ran the identical harness, differing only in the transport (a standard-library HTTP call to Ollama vs. the AWS SDK), so a difference in the numbers is a difference in the model, not the scaffolding. On family 3.3, temperature 0:

Role · metricstub $0llama 8Bqwen 14BHaiku (Bedrock)
judge · fabrications caught40%70%80%90%
generator · status-correct66%38%69%76%

The 8B is a usable judge but an unusable generator — below the trivial stub, failing to emit parseable output for a third of objectives (a structured-output ceiling that recurred, near-identically, on all 14 families). A bigger local model crossed to usable (38% → 69%, zero unassessed), within 7 points of the frontier — so the bottleneck was reasoning, not formatting. The recommendation, with a number behind it: Bedrock in GovCloud (FedRAMP-High) is the default for generation, a 14B-class model is a defensible self-hosted alternative, and an 8B is a $0 judge pre-filter — never the sole gate.

What the Eval Caught (That I Didn’t)

An eval earns its keep by catching your own errors. Across the build it surfaced:

Honest Limitations

Results

An eval that catches your own ground-truth errors is doing its job.

The project produced an eval-validated, per-objective map of where a language model can be trusted on NIST 800-171 assessment — the delta between models, measured on one harness, rather than asserted. Deterministic first, model last. For the full write-up with the interactive per-family chart, read the complete visual case study →.