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

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

A KYC verification agent for pension funds collects identity evidence, checks it against policy rules, flags missing or inconsistent documents, and produces an auditable decision trail for compliance teams. For pension administrators, this matters because onboarding delays, weak evidence handling, or poor auditability create regulatory risk, operational backlog, and member frustration.

Architecture

  • Document intake layer

    • Accepts passports, national IDs, proof of address, tax forms, and beneficiary documents.
    • Normalizes file metadata and stores hashes for tamper detection.
  • Extraction and validation layer

    • Uses OCR or document parsers to extract names, DOB, ID numbers, addresses, and issue/expiry dates.
    • Validates field presence and format before any agent reasoning starts.
  • CrewAI decision layer

    • Uses a Crew with specialized Agent roles:
      • document reviewer
      • sanctions/PEP checker
      • policy compliance reviewer
      • final adjudicator
    • Produces a structured KYC outcome: approved, rejected, or manual review.
  • Audit trail layer

    • Persists every input, tool call, intermediate result, and final decision.
    • Required for pension fund audits and dispute resolution.
  • Policy engine

    • Encodes pension-fund-specific rules: residency restrictions, minimum document set, source-of-funds checks where required, retention periods.
  • Case management integration

    • Pushes exceptions into a human review queue when confidence is low or policy conflicts exist.

Implementation

  1. Install the CrewAI TypeScript package and define your data model

Use a strict schema first. Pension workflows need deterministic outputs because compliance teams will ask why a case was approved or escalated.

npm install @crewai/crewai zod
import { z } from "zod";

export const KycInputSchema = z.object({
  customerId: z.string(),
  fullName: z.string(),
  dateOfBirth: z.string(),
  countryOfResidence: z.string(),
  documents: z.array(
    z.object({
      type: z.enum(["passport", "national_id", "utility_bill", "tax_form"]),
      fileName: z.string(),
      sha256: z.string(),
      extractedText: z.string().optional(),
    })
  ),
});

export const KycDecisionSchema = z.object({
  status: z.enum(["approved", "rejected", "manual_review"]),
  reasons: z.array(z.string()),
  missingDocuments: z.array(z.string()),
  riskFlags: z.array(z.string()),
});
  1. Create agents with narrow responsibilities

Keep each agent focused. Don’t make one giant “KYC agent” that does extraction, policy interpretation, and approval; that’s hard to audit and harder to test.

import { Agent } from "@crewai/crewai";

export const documentReviewer = new Agent({
  role: "KYC Document Reviewer",
  goal: "Verify submitted identity documents and identify missing or inconsistent fields.",
  backstory:
    "You review pension fund onboarding documents under strict compliance controls.",
});

export const complianceReviewer = new Agent({
  role: "KYC Compliance Reviewer",
  goal: "Check the case against pension fund KYC policy and regulatory constraints.",
  backstory:
    "You enforce audit-ready compliance decisions for retirement account onboarding.",
});

export const adjudicator = new Agent({
  role: "KYC Adjudicator",
  goal: "Produce the final KYC outcome using only verified evidence and policy findings.",
  backstory:
    "You make conservative decisions when evidence is incomplete or ambiguous.",
});
  1. Wire the crew with tasks and structured output

This is the pattern you want in production: one task extracts facts, one task applies policy, one task returns a final JSON decision. Use Task, Crew, Process.sequential, then parse the result into your schema.

import { Crew, Process, Task } from "@crewai/crewai";
import { KycInputSchema } from "./schemas";
import { documentReviewer, complianceReviewer, adjudicator } from "./agents";

type KycInput = typeof KycInputSchema._type;

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

  const documentTask = new Task({
    description:
      `Review the following KYC case for customer ${input.customerId}. ` +
      `Check whether identity documents are present and consistent with the declared profile.`,
    expectedOutput:
      "A concise list of verified facts, missing items, inconsistencies, and risk flags.",
    agent: documentReviewer,
  });

  const complianceTask = new Task({
    description:
      `Apply pension fund KYC policy to this case. Country of residence is ${input.countryOfResidence}. ` +
      `Identify any residency concerns, document gaps, or escalation triggers.`,
    expectedOutput:
      "A compliance assessment with reasons suitable for audit logging.",
    agent: complianceReviewer,
    context: [documentTask],
  });

  const finalTask = new Task({
    description:
      "Decide whether the case is approved, rejected, or requires manual review. Return strict JSON only.",
    expectedOutput:
      '{"status":"approved|rejected|manual_review","reasons":["..."],"missingDocuments":["..."],"riskFlags":["..."]}',
    agent: adjudicator,
    context: [documentTask, complianceTask],
  });

  const crew = new Crew({
    agents: [documentReviewer, complianceReviewer, adjudicator],
    tasks: [documentTask, complianceTask, finalTask],
    process: Process.sequential,
    verbose: true,
  });

  const result = await crew.kickoff();

  return result;
}
  1. Validate the output before persisting or acting on it

Never let an LLM response go straight into your case management system. Parse it into a schema first so you can reject malformed output and force manual review when needed.

import { KycDecisionSchema } from "./schemas";

export async function verifyAndPersist(inputRaw: unknown) {
  const rawResult = await runKycVerification(inputRaw);

  // Depending on your CrewAI runtime/version this may be stringified output.
  const parsed =
    typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;

  const decision = KycDecisionSchema.parse(parsed);

   if (decision.status === "manual_review") {
    // send to human queue
   }

   return decision;
}

Production Considerations

  • Data residency

    • Keep document storage and model inference in the same jurisdiction as your pension fund’s regulatory boundary.
    • If member data cannot leave region-specific infrastructure, don’t route it through external APIs without explicit legal approval.
  • Audit logging

    • Store:
      • input hashes
      • extracted fields
      • task outputs
      • final decision
      • model version
      • timestamp
    • Pension auditors care about reproducibility more than clever prompts.
  • Guardrails

    • Force strict JSON output for decisions.
    • Escalate automatically when documents conflict on name/DOB/address.
    • Reject cases with missing mandatory evidence instead of guessing.
  • Monitoring

    • Track approval rate by jurisdiction.
    • Alert on spikes in manual review volume.
    • Measure false rejects separately from false approvals; in pensions both are expensive but false approvals carry higher regulatory risk.

Common Pitfalls

  1. Using one agent for everything

    • This creates brittle behavior and poor traceability.
    • Split extraction, policy checking, and adjudication into separate agents with clear handoffs.
  2. Skipping deterministic validation

    • If you don’t validate outputs with Zod or similar schemas before persistence, malformed responses will leak into downstream systems.
    • Treat every model response as untrusted until parsed.
  3. Ignoring pension-specific policy constraints

    • Generic KYC logic misses residency restrictions, retirement-account documentation rules, retention requirements, and escalation thresholds.
    • Encode these as explicit policies outside the prompt so they can be reviewed by compliance teams.
  4. Weak audit trails

    • If you only store the final answer without intermediate reasoning artifacts and source hashes, you won’t survive a real compliance review.
    • Log every step as if someone will reconstruct the case 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