How to Build a KYC verification Agent Using AutoGen in TypeScript for pension funds

By Cyprian AaronsUpdated 2026-04-21
kyc-verificationautogentypescriptpension-funds

A KYC verification agent for pension funds automates the intake, validation, and escalation of member identity checks. It matters because pension administrators deal with long-lived accounts, regulatory scrutiny, and high penalties for weak onboarding or stale records.

Architecture

For a pension-fund KYC agent, keep the design narrow and auditable:

  • User-facing intake layer
    • Accepts member-submitted documents and structured fields like name, DOB, address, tax ID, and retirement account number.
  • Document extraction service
    • Pulls text from passports, national IDs, proof-of-address letters, and benefit statements.
  • AutoGen orchestration layer
    • Uses AssistantAgent to reason over extracted data and UserProxyAgent to trigger deterministic checks and human escalation.
  • Policy/rules engine
    • Enforces pension-specific rules: jurisdiction checks, sanctions screening flags, age eligibility, residency constraints, and document freshness.
  • Audit store
    • Persists every decision, tool call, prompt version, model response, and reviewer override for compliance review.
  • Human review queue
    • Handles edge cases: mismatched names after marriage, expired proof of address, foreign addresses, or politically exposed persons.

Implementation

1) Install AutoGen and define your domain types

Use the TypeScript AutoGen package that exposes AssistantAgent and UserProxyAgent. Keep the KYC payload explicit so you can validate it before any model call.

npm install @microsoft/autogen openai zod
import { z } from "zod";

export const KycInputSchema = z.object({
  memberId: z.string(),
  fullName: z.string(),
  dateOfBirth: z.string(),
  countryOfResidence: z.string(),
  taxId: z.string().optional(),
  documentText: z.string(),
  proofOfAddressDate: z.string().optional(),
});

export type KycInput = z.infer<typeof KycInputSchema>;

export type KycDecision = {
  status: "approved" | "needs_review" | "rejected";
  reasons: string[];
  missingFields: string[];
};

2) Create the AutoGen agents

The assistant does the policy reasoning. The user proxy executes deterministic functions like date checks or residency lookups.

import { AssistantAgent, UserProxyAgent } from "@microsoft/autogen";

const assistant = new AssistantAgent({
  name: "kyc_assistant",
  systemMessage: `
You are a KYC verification agent for a pension fund.
Return only valid JSON with:
status: approved | needs_review | rejected,
reasons: string[],
missingFields: string[].

Rules:
- Flag expired proof of address older than 90 days.
- Flag missing tax ID where required by jurisdiction.
- Escalate if identity evidence is inconsistent.
- Be strict on auditability; do not invent facts.
`,
});

const userProxy = new UserProxyAgent({
  name: "kyc_executor",
});

3) Add deterministic checks before the model decides

Don’t let the model infer obvious compliance rules. Run hard checks in code first, then pass structured results into AutoGen.

function checkProofOfAddress(dateStr?: string): boolean {
  if (!dateStr) return false;
  const date = new Date(dateStr);
  const now = new Date();
  const diffDays = (now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24);
  return diffDays <= 90;
}

function buildKycPrompt(input: KycInput): string {
  const poaValid = checkProofOfAddress(input.proofOfAddressDate);

  return JSON.stringify({
    memberId: input.memberId,
    fullName: input.fullName,
    dateOfBirth: input.dateOfBirth,
    countryOfResidence: input.countryOfResidence,
    taxIdPresent: Boolean(input.taxId),
    proofOfAddressValid: poaValid,
    documentText: input.documentText,
    instructions:
      "Assess KYC status for a pension fund. Use only provided data. Return JSON only.",
  });
}

4) Run the conversation and persist an audit record

This pattern keeps the model output constrained and gives you a clean handoff to your compliance pipeline.

import OpenAI from "openai";
import fs from "node:fs/promises";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function verifyKyc(inputRaw: unknown) {
  const input = KycInputSchema.parse(inputRaw);
  const prompt = buildKycPrompt(input);

  const result = await assistant.generateReply(
    [{ role: "user", content: prompt }],
    { llmConfig: { configList: [{ model: "gpt-4o-mini", apiKey: process.env.OPENAI_API_KEY }] } }
  );

  const content = typeof result === "string" ? result : result.content;
  const decision = JSON.parse(content as string) as KycDecision;

  await fs.appendFile(
    "./kyc-audit.log",
    JSON.stringify({
      timestamp: new Date().toISOString(),
      memberId: input.memberId,
      prompt,
      decision,
    }) + "\n"
  );

  return decision;
}

If you want a stricter production shape, wrap the output parsing in a Zod schema and reject anything that is not valid JSON with the expected keys.

Production Considerations

  • Deployment
    • Keep this service inside your regulated network boundary. For pension funds with residency requirements, pin inference to approved regions and avoid sending raw PII to non-compliant endpoints.
  • Monitoring
    • Track approval rate, manual-review rate, false rejects, average time-to-decision, and prompt/version drift. Sudden changes usually mean upstream document parsing broke or policy text changed.
  • Guardrails
    • Hard-code jurisdiction rules outside the model. The agent should recommend outcomes; your rules engine should enforce them.
  • Auditability
    • Store raw inputs, extracted fields, model outputs, reviewer overrides, and timestamps. Pension auditors will ask why a record was approved years later.

Common Pitfalls

  1. Letting the LLM make final compliance decisions

    • Fix this by separating policy enforcement from reasoning. The agent can classify risk; your code must approve or reject based on explicit rules.
  2. Passing unstructured documents straight into the agent

    • Extract fields first. A scanned utility bill plus free-form OCR text is not enough unless you normalize names, dates, addresses, and document types before prompting.
  3. Ignoring pension-specific retention and residency rules

    • Some jurisdictions require local storage of identity data and long retention windows. Design storage per region and log every cross-border transfer explicitly.

A good pension-fund KYC agent is boring in the right ways. It is deterministic where it must be, explainable where it can be, and easy to audit when regulators show up six months later.


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