How to Build a compliance checking Agent Using LlamaIndex in TypeScript for pension funds

By Cyprian AaronsUpdated 2026-04-21
compliance-checkingllamaindextypescriptpension-funds

A compliance checking agent for pension funds reviews documents, transactions, and policy changes against internal rules and regulatory requirements before they reach an analyst. It matters because pension operations are high-trust, heavily audited, and expensive to fix after the fact; you want violations caught early, with a traceable reason and source evidence attached.

Architecture

  • Document ingestion layer

    • Pulls policy docs, scheme rules, investment mandates, KYC/AML procedures, and regulatory circulars.
    • Normalizes PDFs, DOCX files, and HTML into text chunks with metadata like jurisdiction, effective date, and document owner.
  • Indexing layer

    • Uses VectorStoreIndex for semantic retrieval over compliance content.
    • Stores chunk-level metadata so the agent can filter by fund, region, product type, or effective date.
  • Retrieval + reasoning layer

    • Uses LlamaIndex query engines to retrieve relevant clauses before making a decision.
    • Produces an answer with citations so compliance officers can audit the result.
  • Policy evaluation layer

    • Applies deterministic checks for hard rules like concentration limits, contribution thresholds, or restricted asset classes.
    • Combines rule-based logic with LLM-assisted interpretation for ambiguous policy language.
  • Audit logging layer

    • Records the input request, retrieved sources, decision output, and model version.
    • Keeps an immutable trail for internal audit and regulator review.
  • Data governance layer

    • Enforces tenant isolation, data residency constraints, and redaction of personal data where required.
    • Prevents non-approved documents from entering the index.

Implementation

  1. Load pension compliance documents into a LlamaIndex index

For this pattern, keep your source documents in a controlled folder or object store. Tag each document with metadata that matters in pensions: jurisdiction, scheme name, effective date, and document type.

import {
  Document,
  VectorStoreIndex,
  Settings,
} from "llamaindex";
import fs from "node:fs/promises";

async function loadComplianceDocs() {
  const raw = await fs.readFile("./data/pension-policy.txt", "utf-8");

  const doc = new Document({
    text: raw,
    metadata: {
      scheme: "UK_Staff_Pension",
      jurisdiction: "UK",
      docType: "InvestmentPolicy",
      effectiveDate: "2025-01-01",
    },
  });

  return [doc];
}

async function buildIndex() {
  Settings.chunkSize = 1024;
  Settings.chunkOverlap = 120;

  const documents = await loadComplianceDocs();
  return await VectorStoreIndex.fromDocuments(documents);
}
  1. Create a query engine that returns evidence-backed compliance answers

The key is not “does this look compliant?” The key is “show me the clause and explain why.” Use asQueryEngine() so the agent retrieves relevant chunks before generating a response.

async function runCheck() {
  const index = await buildIndex();

  const queryEngine = index.asQueryEngine({
    similarityTopK: 4,
    systemPrompt:
      "You are a pension fund compliance checker. Answer only using retrieved policy evidence. " +
      "If evidence is insufficient, say so explicitly.",
  });

  const result = await queryEngine.query({
    query: "Can the scheme invest more than 10% in a single corporate bond issuer?",
  });

  console.log(String(result));
}

runCheck().catch(console.error);
  1. Wrap the query engine with deterministic pension rules

LlamaIndex is good at retrieval and explanation. It should not be your only control for hard limits. For things like concentration caps or prohibited instruments, add explicit checks in code before or after the LLM response.

type TradeRequest = {
  issuerExposurePct: number;
  assetClass: string;
};

function hardComplianceChecks(request: TradeRequest) {
  const violations: string[] = [];

  if (request.assetClass === "crypto") {
    violations.push("Prohibited asset class for this pension mandate.");
  }

  if (request.issuerExposurePct > 10) {
    violations.push("Issuer exposure exceeds the policy limit of 10%.");
  }

  return violations;
}

async function evaluateTrade(request: TradeRequest) {
  const hardViolations = hardComplianceChecks(request);

  if (hardViolations.length > 0) {
    return {
      status: "REJECTED",
      reasons: hardViolations,
    };
    }

  const index = await buildIndex();
  const engine = index.asQueryEngine({ similarityTopK: 3 });

  const response = await engine.query({
    query:
      `Assess whether this trade is compliant with the investment policy. ` +
      `Asset class: ${request.assetClass}. Issuer exposure: ${request.issuerExposurePct}%.`,
  });

return {
    status: "REVIEW",
    evidence: String(response),
};
}
  1. Add traceability for audit and regulator review

Pension funds need to show how a decision was made months later. Store the request payload, retrieved snippets, output text, timestamp, and document metadata in your audit store.

FieldWhy it matters
Request IDLinks the check to a trade or member case
Source document IDsProves which policy version was used
Retrieved chunksShows what evidence informed the answer
Model/versionSupports reproducibility
Decision outcomeRequired for audit trails
Reviewer overrideImportant when humans override automated decisions

Production Considerations

  • Deploy in-region

Use storage and inference endpoints that satisfy data residency requirements. Pension data often includes personal information and regulated records that should not leave approved jurisdictions.

  • Keep an immutable audit log

Write every decision to append-only storage with timestamps and versioned policies. If a regulator asks why a trade was blocked six months ago, you need exact evidence.

  • Use guardrails around free-form generation

Never let the model invent policy clauses. Force answers to cite retrieved sources from LlamaIndex output and reject responses without supporting evidence.

  • Separate hard rules from interpretive checks

Contribution limits, prohibited assets, and concentration caps should be deterministic code paths. Use LLM reasoning only for clause interpretation or exception analysis.

Common Pitfalls

  • Using semantic search as the final decision maker

Retrieval is not enforcement. Fix this by adding explicit rule checks in TypeScript before approving any action.

  • Indexing stale policy versions

Pension policies change often across jurisdictions and schemes. Fix this by versioning documents and filtering retrieval by effectiveDate and scheme.

  • Ignoring auditability

If you cannot explain why a decision was made, it is not production-ready for pensions. Fix this by storing source snippets, model version, request payloads, and final output together.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides