Compliance Monitoring Agents for Regulated Document Streams
Build agents that watch regulated document streams for violations using LlamaIndex workflows, LlamaParse extraction, and typed events with human review.
Published on • October 18, 2026
AI Assistant

Compliance teams drown in documents. Contracts, disclosure filings, vendor invoices, patient correspondence — the volume grows faster than headcount, and the cost of missing one regulated clause is not linear. A compliance monitoring agent watches a stream of documents as it arrives, classifies each one, extracts the fields your policy engine actually checks, and routes only the ambiguous cases to humans. The LlamaIndex framework gives you the pieces to build this: LlamaParse for hard documents, agents and tools for reasoning, and workflows for the deterministic orchestration layer that auditors will ask you about.
Why Workflows, Not a Free-Running Agent
The instinct is to give a model a document pile, a compliance manual, and a report_violation tool. That fails review for a simple reason: compliance is a pipeline with mandatory stages, not an open-ended conversation. Every document must pass classification, extraction, rule evaluation, and disposition — in that order, with an audit trail.
LlamaIndex workflows are event-driven and step-based: “a step receives an event, does some work, and returns another event. That returned event triggers the next step whose type annotation accepts it.” Branches are ordinary if statements returning different event types; loops are steps that return an event handled by an earlier step. The framework validates the event graph before running — checking that start and stop events exist, that produced events have consumers, and that there are no dead ends — which is exactly the pre-flight guarantee you want before wiring a compliance system into a document feed.
The Pipeline Shape
A minimal monitoring workflow has five steps:
- Ingest — accept a document event (a parsed page batch, an email attachment, a webhook payload).
- Classify — assign a document type and risk tier.
- Extract — pull typed fields relevant to that tier.
- Evaluate — run deterministic rule checks against the extracted fields.
- Disposition — auto-clear low-risk documents, route violations and ambiguities to human review.
The typed events wire it together:
from workflows import Workflow, step
from workflows.events import Event, StartEvent, StopEvent
from pydantic import BaseModel, Field
class IngestedDoc(Event):
doc_id: str
text: str
source: str
class ClassifiedDoc(IngestedDoc):
doc_type: str # e.g. "vendor_contract", "disclosure_filing"
risk_tier: str # "low" | "standard" | "high"
class ExtractedFields(IngestedDoc):
doc_type: str
risk_tier: str
fields: dict
class Finding(Event):
doc_id: str
rule_id: str
severity: str
detail: str
class ComplianceFlow(Workflow):
@step
async def classify(self, ev: StartEvent) -> IngestedDoc | Finding:
# Route straight to a violation when the document itself
# declares a category your policy forbids (e.g. unlabeled PII export).
if looks_like_restricted_transfer(ev.text):
return Finding(
doc_id=ev.doc_id,
rule_id="DOC-001",
severity="high",
detail="Document matches restricted-data transfer pattern",
)
return IngestedDoc(doc_id=ev.doc_id, text=ev.text, source=ev.source)
Returning Finding from the classification step is a real branch: the workflow graph validates it because some other step consumes Finding. That is the pattern the docs recommend over encoding control flow into graph edges.
Extraction Is Where Compliance Succeeds or Fails
Rule engines are only as good as the fields they receive. For clean digital text the framework’s built-in readers are fine, but the LlamaIndex docs are blunt about the rest: “For scanned PDFs, forms, spreadsheets and slide decks, the quality of what goes into your index decides the quality of every answer.” That is LlamaParse’s job — agentic OCR via Parse, schema-typed extraction via Extract, plus Classify and Split for ingestion. Parsed pages drop straight into a framework index or, in this case, into your extraction step:
from llama_cloud import LlamaCloud
client = LlamaCloud() # reads LLAMA_CLOUD_API_KEY
file = client.files.create(file=doc_path, purpose="parse")
result = client.parsing.parse(
file_id=file.id, tier="agentic", version="latest", expand=["markdown"]
)
pages = [p.markdown for p in result.markdown.pages if p.success]
Then let a structured output model fill a Pydantic model per document type. Keep the schema tight — compliance fields should be enums, dates, monetary amounts, and counterparty identifiers, not free text. Free text belongs in the evidence snippet you attach to the finding, not in the field the rule engine branches on.
Evaluation: Deterministic Rules, Not Model Judgement
Let the model do perception; let code do policy. A rule evaluator over extracted fields is testable, versionable, and explainable to an auditor — three properties “the LLM decided it was a violation” does not have:
class RuleEngine:
def __init__(self):
self.rules = {
"VEN-014": lambda f: f.get("auto_renewal") and not f.get("termination_days", 999) >= 30,
"FIN-007": lambda f: f.get("payment_terms_days", 0) > 60,
"DIS-002": lambda f: f.get("material_change") and not f.get("filing_deadline"),
}
def evaluate(self, doc_type: str, fields: dict) -> list[Finding]:
return [
Finding(doc_id=..., rule_id=rid, severity="medium",
detail=f"Rule {rid} failed on extracted fields")
for rid, check in self.rules.items()
if check(fields)
]
Keep the model in the loop for one job: resolving ambiguity. When extraction returns "termination_days": "thirty (30?)", a second pass with a narrower prompt can normalize it. When the model disagrees with a rule, the finding still stands — it just gets flagged for human adjudication instead of auto-clearing.
Human Review and Durable State
High-risk findings and low-confidence extractions need a human. Workflows support this natively — the docs list “human-in-the-loop review” as a first-class capability, and there are dedicated examples for pausing a run for input. More importantly for regulated streams, runs can be made durable: you can checkpoint workflow context and resume a run after a restart, so a document mid-review survives a deploy. A compliance pipeline that silently drops in-flight documents during maintenance is itself a compliance incident.
Design the review step to capture what an auditor needs: the document id, the rule id, the extracted fields, the evidence snippet, the reviewer identity, and the decision with reasoning. Write it back into your case store, not just into logs.
Operational Checklist
- Fail closed. If parsing fails or extraction confidence is below threshold, the document goes to the review queue, not the cleared pile.
- Version everything. Rule versions, prompt versions, and schema versions travel with each finding so a re-run six months later reproduces the same outcome.
- Separate perception from policy. Extraction prompts change weekly; rule identifiers should not.
- Validate the graph. Call
workflow.validate()in CI — a consumed-but-never-produced event is a document class that will hang forever. - Respect the stream’s shape. A step that returns
list[Event]can fan out pages for parallel extraction, then a collecting step joins them before evaluation.
Conclusion
A compliance monitoring agent is not a chatbot with a policy PDF. It is a validated, durable, event-driven pipeline where the model handles the fuzzy parts — OCR, classification, field extraction — and deterministic code handles policy, routing, and evidence. LlamaIndex workflows give you the orchestration contract, LlamaParse turns hard documents into clean text and typed fields, and human review catches what the rules and the model both miss. Start with one document class and three rules, wire the review queue on day one, and expand only after the audit trail proves itself.