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

By Cyprian AaronsUpdated 2026-04-21
underwritingcrewaitypescriptpension-funds

A underwriting agent for pension funds takes member, employer, and plan data, then helps assess whether an application, contribution change, transfer request, or exception should be approved, escalated, or rejected. It matters because pension operations are high-volume, policy-heavy, and audit-sensitive: you need consistent decisions, traceable reasoning, and strict handling of compliance and residency constraints.

Architecture

  • Input normalizer

    • Converts raw pension case data into a structured underwriting request.
    • Validates required fields like plan type, jurisdiction, contribution history, employer status, and member eligibility.
  • Policy retrieval layer

    • Pulls the current pension scheme rules, regulator guidance, and internal underwriting policy.
    • Keeps the agent grounded in approved documents instead of model memory.
  • Underwriting analyst agent

    • Reviews the normalized case against policy.
    • Produces a decision draft with rationale, risk flags, and required follow-up questions.
  • Compliance reviewer agent

    • Checks the draft for regulatory issues: disclosure gaps, unfair treatment risk, missing evidence, and breach of approval thresholds.
    • Forces escalation when the case touches protected classes or ambiguous benefit rules.
  • Audit logger

    • Stores every input, retrieved policy snippet, decision output, and human override.
    • This is non-negotiable for pension funds.
  • Decision service

    • Exposes the final decision through an API to your workflow system.
    • Routes low-risk cases straight through and sends edge cases to a human underwriter.

Implementation

  1. Install CrewAI and define your domain types

    Use CrewAI’s TypeScript package and keep your case model explicit. Pension underwriting breaks when teams pass around loosely typed JSON.

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

export const PensionUnderwritingCaseSchema = z.object({
  caseId: z.string(),
  fundId: z.string(),
  jurisdiction: z.string(),
  memberAge: z.number().int().min(0),
  employerStatus: z.enum(["active", "suspended", "insolvent"]),
  requestType: z.enum(["contribution_change", "transfer_in", "benefit_exception"]),
  annualContribution: z.number().nonnegative(),
  requestedChangePct: z.number(),
  hasKycComplete: z.boolean(),
  hasConsentOnFile: z.boolean(),
});

export type PensionUnderwritingCase = z.infer<typeof PensionUnderwritingCaseSchema>;
  1. Create agents with hard roles and narrow scope

    Use Agent for the underwriting analyst and compliance reviewer. Keep the prompts specific to pension rules so the model does not wander into generic insurance logic.

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

export const underwritingAgent = new Agent({
  name: "Pension Underwriting Analyst",
  role: "Assess pension fund underwriting requests against scheme rules",
  goal:
    "Approve only cases that meet policy; flag exceptions with concise rationale and escalation triggers",
  backstory:
    "You review pension fund cases under strict compliance constraints. You must cite policy-driven reasons only.",
});

export const complianceAgent = new Agent({
  name: "Pension Compliance Reviewer",
  role: "Validate decisions for regulatory and audit completeness",
  goal:
    "Detect missing evidence, jurisdictional conflicts, consent issues, residency concerns, and escalation requirements",
});
  1. Define tasks and run them in sequence with a crew

    The pattern here is straightforward: analyze first, review second. In production you want deterministic handoff between tasks so every decision can be audited.

import { Crew } from "@crewai/crewai";
import { Task } from "@crewai/crewai";

const analyzeTask = new Task({
  description: `
Review this pension underwriting case:
{caseJson}

Decide approve / reject / escalate.
Return:
- decision
- rationale
- risk_flags
- missing_information
- escalation_required
`,
  expectedOutput:
    "Structured underwriting recommendation with decision and audit-friendly rationale.",
  agent: underwritingAgent,
});

const complianceTask = new Task({
  description: `
Review the underwriting recommendation for:
- pension regulation issues
- missing consent or KYC
- unfair treatment risk
- data residency or cross-border processing concerns
- audit completeness

Return final compliance verdict.
`,
  expectedOutput:
    "Compliance review with any required escalations or corrections.",
  agent: complianceAgent,
});

export async function runUnderwriting(caseJson: string) {
  const crew = new Crew({
    agents: [underwritingAgent, complianceAgent],
    tasks: [analyzeTask, complianceTask],
    verbose: true,
    memory: false,
  });

  const result = await crew.kickoff({
    inputs: {
      caseJson,
    },
  });

  return result;
}
  1. Wrap it in an API endpoint with validation

    Validate before calling the crew. If KYC or consent is missing, short-circuit early instead of spending tokens on a case that must be escalated anyway.

import express from "express";
import { PensionUnderwritingCaseSchema } from "./schema";
import { runUnderwriting } from "./crew";

const app = express();
app.use(express.json());

app.post("/underwrite", async (req, res) => {
  const parsed = PensionUnderwritingCaseSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json({ error: parsed.error.flatten() });
    }

  const input = parsed.data;

  if (!input.hasKycComplete || !input.hasConsentOnFile) {
    return res.status(422).json({
      decision: "escalate",
      reason: "Missing KYC or consent on file",
    });
  }

  const result = await runUnderwriting(JSON.stringify(input));
  return res.json({ caseId: input.caseId, result });
});

app.listen(3000);

Production Considerations

  • Data residency

    • Keep member PII inside the region required by your fund’s legal entity.
    • If your model provider processes data outside that region, do not send raw identifiers; tokenize or redact first.
  • Auditability

    • Persist the exact prompt inputs, retrieved policy version, model output, final human decision, and timestamp.
    • Pension regulators will care more about traceability than clever prompts.
  • Guardrails

    • Hard-block decisions when KYC is incomplete, consent is missing, or jurisdiction is unknown.
    • Add deterministic rules before the agent runs so obvious rejects never depend on model judgment.
  • Monitoring

SignalWhy it mattersAction
Escalation rateSpikes can indicate policy driftReview prompts and policy retrieval
Override rateHigh override means weak recommendationsTighten task scope
Missing-field frequencyShows upstream data quality issuesFix intake validation
Cross-border casesData residency riskRoute to restricted workflow

Common Pitfalls

  • Using one generic agent for everything

    A single agent handling eligibility, compliance, exceptions, and audit writing will drift. Split underwriting and compliance into separate roles with separate outputs.

  • Skipping policy versioning

    If you don’t attach a policy version to each decision, you cannot explain why two identical cases were treated differently six months apart. Store the exact rule set used at runtime.

  • Letting raw PII flow into prompts

    Member names, national IDs, bank details, and medical-adjacent notes should not go straight into the model. Redact first; only pass what the task actually needs.


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