Skip to main content

Grounding Policy

The grounding policy category enforces factual accuracy standards — it requires agent outputs to be grounded in cited sources, with measurable confidence scores and limits on unsupported claims.

Use it for RAG (retrieval-augmented generation) agents, research assistants, medical or legal Q&A, and any agent where hallucinated or unsupported output carries real risk.

Rules​

RuleTypeDefaultDescription
require_source_groundingbooleanfalseIf true, block (or warn) when zero citations are provided
min_grounding_scorenumber0.7Minimum acceptable score per grounding claim (0.0-1.0). Checked at mid_execution
min_citationsinteger1Minimum number of source citations required in the output
max_unsupported_claimsintegernullMaximum number of claims without source support. null means no limit
factual_consistency_checkbooleanfalseReserved for future cross-claim consistency validation
abstention_thresholdnumbernullIf output_confidence falls below this threshold, block (the agent should abstain rather than guess)
abstention_responsestringnullCustom message to return when abstention threshold is triggered
action_on_violationstring"warn""block" raises PolicyViolationError; "warn" logs and continues
score_relevance_floornumbernullIgnore retrieval scores below this floor. Only scores at or above this value are checked against min_grounding_score
score_eval_modestring"all"How to evaluate filtered scores: "all" (each must pass), "average" (mean must pass), "top_n" (only top N checked)
score_top_ninteger3When score_eval_mode="top_n", how many top scores to evaluate
Default action is "warn"

The grounding handler defaults to action_on_violation: "warn" unlike most other handlers that default to "block". This is intentional — grounding quality degrades gradually and initial deployments often need to calibrate thresholds. Change to "block" once you have tuned your score thresholds against real traffic.

How It Works​

The grounding handler runs at two enforcement phases:

before_workflow​

Stores the grounding rules into context._grounding_rules. Always returns ALLOW — no blocking happens at before_workflow.

mid_execution​

Checks per-claim grounding scores as they are reported:

  1. Reads context.grounding_scores (a list of float values 0.0-1.0)
  2. If the list is empty, returns ALLOW immediately (nothing to check yet)
  3. For each score, checks against min_grounding_score
  4. The first score below the threshold produces a BLOCK or WARN

This phase fires every time ctx.record_grounding() is called with new scores.

after_workflow​

Audits the final output state against citation and confidence requirements:

  1. Citation count: len(context.citations) < min_citations → violation
  2. Require grounding: require_source_grounding=true and len(context.citations) == 0 → violation
  3. Unsupported claims: context.unsupported_claims > max_unsupported_claims → violation
  4. Abstention threshold: context.output_confidence < abstention_threshold → violation

All violations are collected and returned together in the warnings list.

Filtering for RAG Pipelines​

Real RAG pipelines with top_k=5 always return tail results with low similarity scores. Without filtering, min_grounding_score would block on irrelevant tail results, making the policy unusable.

The Relevance Floor​

Set score_relevance_floor to ignore retrieval results below a relevance threshold. Only scores at or above the floor are evaluated against min_grounding_score.

Example: A query about "2008 financial crisis" returns 5 results:

  • 0.92, 0.87, 0.85 (relevant finance docs)
  • 0.35, 0.22 (irrelevant tail results)

With score_relevance_floor: 0.5, only the three relevant scores are checked. All three pass min_grounding_score: 0.7, so the policy allows the response.

Without the floor, the policy would block on the 0.35 score — a false positive from an irrelevant retrieval result.

Edge Case: Nothing Relevant​

If NO scores pass the floor, the policy triggers a violation: "No grounding scores above relevance floor — all retrieved results appear irrelevant." This correctly catches the case where retrieval returned nothing useful.

Evaluation Modes​

ModeBehavior
"all" (default)Every filtered score must pass min_grounding_score
"average"The mean of filtered scores must pass the threshold
"top_n"Only the top N filtered scores are checked (set N with score_top_n)

Example: RAG Pipeline Policy​

{
"min_grounding_score": 0.7,
"score_relevance_floor": 0.5,
"score_eval_mode": "all",
"min_citations": 1,
"action_on_violation": "block"
}

Example: Lenient Average-Based Policy​

{
"min_grounding_score": 0.7,
"score_relevance_floor": 0.4,
"score_eval_mode": "average",
"action_on_violation": "warn"
}
When to use filtering

Set score_relevance_floor whenever your grounding scores come from retrieval similarity (e.g., Pinecone, FAISS, Weaviate). Without filtering, top_k > 3 retrieval will almost always produce tail results below any useful threshold.

If your scores come from LLM self-evaluation or NLI classifiers (where every score is meaningful), leave score_relevance_floor unset to use the legacy per-score checking.

SDK Integration​

Recording Grounding Data​

import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError

async with waxell.WaxellContext(
agent_name="research-agent",
enforce_policy=True,
) as ctx:
# Retrieve sources and generate answer
sources = await retrieve_sources(query)
answer, scores = await synthesize_with_grounding(query, sources)

# Report grounding data — triggers mid_execution check on scores
ctx.record_grounding(
grounding_scores=scores, # per-claim scores [0.9, 0.85, 0.92]
citations=sources, # ["Source A", "Source B", "Source C"]
unsupported_claims=[], # any claims without source support
output_confidence=0.88, # overall confidence in the answer
)

ctx.set_result({"answer": answer, "citations": sources})

Grounding Data Fields​

FieldTypeWhat to put here
grounding_scoreslist[float]One score per claim or sentence in the output (0.0 = unsupported, 1.0 = fully grounded)
citationslist[str]Source identifiers — URLs, document names, DOIs, database record IDs
unsupported_claimslist[str]The actual text of claims you could not ground in any source
output_confidencefloatYour model's or pipeline's overall confidence that the full answer is accurate

Handling Violations​

try:
async with waxell.WaxellContext(
agent_name="research-agent",
enforce_policy=True,
) as ctx:
sources = await retrieve_sources(query)
answer, scores = await synthesize_with_grounding(query, sources)

ctx.record_grounding(
grounding_scores=scores,
citations=sources,
unsupported_claims=[],
output_confidence=0.88,
)
ctx.set_result({"answer": answer})

except PolicyViolationError as e:
# e.g. "Grounding score (0.42) below threshold (0.7)"
# e.g. "Citations (0) below minimum (1); Unsupported claims (2) exceeds max (0)"
print(f"Grounding policy blocked: {e}")
return abstention_message

Example Policies​

Strict Research Assistant​

Block any output with low grounding scores, missing citations, or low confidence:

{
"require_source_grounding": true,
"min_grounding_score": 0.8,
"min_citations": 2,
"max_unsupported_claims": 0,
"abstention_threshold": 0.5,
"abstention_response": "I don't have sufficient grounded evidence to answer this accurately.",
"action_on_violation": "block"
}

Medical Q&A​

High accuracy standards — abstain rather than guess:

{
"require_source_grounding": true,
"min_grounding_score": 0.9,
"min_citations": 3,
"max_unsupported_claims": 0,
"abstention_threshold": 0.7,
"abstention_response": "I cannot provide a reliable answer based on available evidence. Please consult a qualified professional.",
"action_on_violation": "block"
}

Lenient RAG Agent​

Warn on grounding issues but do not block execution during development:

{
"require_source_grounding": false,
"min_grounding_score": 0.6,
"min_citations": 1,
"max_unsupported_claims": 3,
"abstention_threshold": null,
"action_on_violation": "warn"
}

Citation-Only Enforcement​

Require citations but do not enforce per-claim scores (useful when scores are not available):

{
"require_source_grounding": true,
"min_citations": 1,
"max_unsupported_claims": 2,
"action_on_violation": "block"
}

Enforcement Flow​

Agent starts (WaxellContext.__aenter__)
│
└── before_workflow governance runs
└── Stores grounding rules → ALLOW

Agent runs — calls ctx.record_grounding(grounding_scores=[...], ...)
│
└── mid_execution governance runs
├── grounding_scores empty? → ALLOW (nothing to check)
├── For each score:
│ └── score < min_grounding_score? → BLOCK/WARN (first violation only)
└── All scores pass → ALLOW

Agent completes (WaxellContext.__aexit__)
│
└── after_workflow governance runs
├── len(citations) < min_citations? → violation
├── require_source_grounding AND len(citations) == 0? → violation
├── unsupported_claims > max_unsupported_claims? → violation
├── output_confidence < abstention_threshold? → violation
├── Any violations?
│ ├── action=block → BLOCK (PolicyViolationError)
│ └── action=warn → WARN (agent completed, warning recorded)
└── No violations → ALLOW with citation_count metadata

Creating via Dashboard​

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Grounding
  4. Set your grounding thresholds
  5. Set action_on_violation to warn initially to calibrate, then block for production
  6. Set scope to target specific agents (e.g., research-agent)
  7. Enable

Creating via API​

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Strict Research Grounding",
"category": "grounding",
"rules": {
"require_source_grounding": true,
"min_grounding_score": 0.8,
"min_citations": 2,
"max_unsupported_claims": 0,
"abstention_threshold": 0.5,
"action_on_violation": "block"
},
"scope": {
"agents": ["research-agent"]
},
"enabled": true
}'

Observability​

Governance Tab​

Grounding evaluations appear at each enforcement phase:

mid_execution (per-score check):

FieldExample
Policy nameStrict Research Grounding
Actionblock
Phasemid_execution
Reason"Grounding score (0.42) below threshold (0.7)"
Metadata{"score": 0.42, "threshold": 0.7}

after_workflow (citation and confidence audit):

FieldExample
Actionblock
Phaseafter_workflow
Reason"Citations (0) below minimum (1); No source citations provided (grounding required)"
Metadata{"warnings": [...], "citation_count": 0}

For successful runs:

FieldExample
Actionallow
Reason"Grounding audit passed (3 citations)"
Metadata{"citation_count": 3}

Trace Tab​

The source_retrieval tool span (if recorded with ctx.record_tool_call) shows the number of sources found. The grounding data (scores, citations, confidence) appears in the steps data alongside any LLM call records.

LLM Grounding Judge​

In addition to checking self-reported scores and citation counts, the grounding handler supports LLM-based grounding evaluation — an independent AI judge that reads the output and its cited sources, then scores whether the output is genuinely grounded.

This is an admin-controlled feature: developers record grounding data as usual, and the platform automatically runs the LLM judge when the policy enables it. No developer code changes are needed.

LLM Judge Rules​

RuleTypeDefaultDescription
llm_grounding_checkbooleanfalseEnable LLM-based grounding evaluation
llm_grounding_modelstring"gpt-4o-mini"Model to use for the judge (gpt-*, claude-*, llama-*, mixtral-*)
llm_grounding_thresholdnumber0.7Minimum LLM judge score (0.0-1.0)
llm_grounding_criteriastring""Custom evaluation criteria appended to the judge prompt
llm_grounding_phasestring"mid_execution"When to run the judge: "mid_execution", "after_workflow", or "both"

How It Works​

The LLM judge evaluates three dimensions:

  1. Source support — Is every factual claim in the output supported by at least one cited source?
  2. Citation correctness — Are the citations used correctly (not fabricated or misattributed)?
  3. Scope adherence — Does the output avoid making claims beyond what the sources support?

The judge returns a single score from 0.0 to 1.0:

ScoreMeaning
0.0Completely ungrounded (fabricated facts)
0.3Poorly grounded (many unsupported claims)
0.5Partially grounded (some claims supported)
0.7Mostly grounded (minor gaps)
1.0Fully grounded (every claim traceable)

Phase Behavior​

  • mid_execution: After each record_grounding() call, the judge evaluates the latest LLM response against the citations just recorded. A score below threshold produces an immediate BLOCK or WARN.
  • after_workflow: After the agent completes, the judge evaluates the final output against all citations collected across all record_grounding() calls. A score below threshold adds to the violation warnings.
  • both: Runs at both phases.

Supported Providers​

The judge model uses your organization's API keys stored in Tenant Secrets:

Model prefixProviderKey needed
gpt-*OpenAIOPENAI_API_KEY
claude-*AnthropicANTHROPIC_API_KEY
llama-*, mixtral-*GroqGROQ_API_KEY

Example: Policy with LLM Judge​

{
"require_source_grounding": true,
"min_grounding_score": 0.7,
"min_citations": 2,
"max_unsupported_claims": 0,
"abstention_threshold": 0.5,
"action_on_violation": "block",
"llm_grounding_check": true,
"llm_grounding_model": "gpt-4o-mini",
"llm_grounding_threshold": 0.7,
"llm_grounding_phase": "after_workflow"
}

Graceful Degradation​

The LLM judge never blocks your agents due to infrastructure failures:

  • No API key configured — judge is skipped, threshold checks still run
  • LLM API error — warning logged, judge skipped
  • Unparseable response — defaults to 0.5 (neutral score)
  • llm_grounding_check: false (default) — judge completely skipped, backward compatible

Cost​

The judge uses minimal tokens (~500 input + 10 output per evaluation):

ModelApproximate cost per evaluation
gpt-4o-mini~$0.0001
llama-3.1-8b-instant (Groq)Near-zero
claude-sonnet-4-20250514~$0.002

Accumulating Grounding Data​

The SDK's record_grounding() method accumulates entries — each call appends to a list rather than overwriting. This supports multi-step workflows where different steps produce different grounding data:

async with waxell.WaxellContext(...) as ctx:
# Step 1: Retrieve financial sources
ctx.record_grounding(
grounding_scores=[0.92, 0.87],
citations=["Federal Reserve Report", "IMF Analysis"],
output_confidence=0.89,
)
# → mid_execution checks these scores

# Step 2: Retrieve legal sources
ctx.record_grounding(
grounding_scores=[0.85, 0.91],
citations=["SEC Filing 2023", "Legal Review"],
output_confidence=0.88,
)
# → mid_execution checks these scores too

# → after_workflow evaluates ALL citations from both steps

Calibrating Thresholds​

Grounding scores are computed by your pipeline — the grounding policy evaluates them but does not compute them. Common approaches:

ApproachHow to compute scores
Embedding similarityCosine similarity between each output sentence and its retrieved source chunk
LLM self-evaluationAsk the LLM to score its own claim grounding (0-10 scale, normalize to 0.0-1.0)
Cross-encoder rerankerUse a cross-encoder model to score claim-source relevance
NLI classifierNatural Language Inference model classifies each claim as entailed/neutral/contradicted

Start with action_on_violation: "warn" and observe the distribution of scores across real executions. Set min_grounding_score at the 5th-percentile score from good runs — this blocks genuinely weak outputs without over-triggering on edge cases.

Common Gotchas​

  1. mid_execution fires per record_grounding() call, not per-claim. If you report all scores in a single call and one is below threshold, the entire call is rejected. Split into separate calls only if you want per-claim mid-execution enforcement.

  2. unsupported_claims takes a list of strings. Pass the actual claim text in the list. The handler compares len(unsupported_claims) > max_unsupported_claims. An empty list [] means zero unsupported claims.

  3. max_unsupported_claims: null (the default) disables the check. Set an explicit integer (including 0) to enable it. 0 means any unsupported claim blocks the output.

  4. abstention_threshold requires output_confidence to be set. If you record grounding but omit output_confidence, the abstention threshold is skipped even if configured.

  5. require_source_grounding and min_citations can double-fire. With require_source_grounding=true and min_citations=1, an empty citation list triggers both warnings. The result reason string concatenates them with ;.

  6. Default action_on_violation is "warn". Unlike most handlers, grounding defaults to warn mode. Explicitly set "block" for production enforcement.

  7. factual_consistency_check is a no-op. The rule is schema-valid and accepted, but the handler does not implement cross-claim consistency checking yet. It is reserved for a future release.

Combining with Other Policies​

  • retrieval: Use retrieval to govern the quality of source documents retrieved; use grounding to govern whether the output is actually grounded in those sources. They complement each other in RAG pipelines.
  • content: Use content to detect PII or prohibited content in outputs; use grounding to ensure outputs are factually accurate. Both protect the quality of agent outputs from different angles.
  • quality: Use quality for general output quality metrics (length, format, tone); use grounding specifically for factual accuracy and citation requirements.
  • compliance: In regulated industries (medical, legal, financial), combine grounding with compliance to ensure both factual accuracy and regulatory adherence.

Next Steps​