How to Build a loan approval Agent Using AutoGen in TypeScript for pension funds

By Cyprian AaronsUpdated 2026-04-21
loan-approvalautogentypescriptpension-funds

A loan approval agent for pension funds evaluates borrower requests, checks policy constraints, pulls supporting documents, and produces a decision package that a human credit officer can approve or reject. It matters because pension capital has a different risk bar than retail lending: you need strict compliance, auditable decisions, data residency controls, and consistent application of investment policy.

Architecture

  • Orchestrator agent
    • Owns the workflow and decides when to call review, compliance, and decision steps.
  • Credit analysis agent
    • Reads borrower financials, debt service coverage ratio, collateral details, and repayment history.
  • Compliance agent
    • Checks pension fund policy rules, jurisdictional lending limits, KYC/AML flags, and prohibited counterparties.
  • Document retrieval layer
    • Pulls loan applications, trust deeds, financial statements, and board-approved policy documents from approved storage.
  • Decision ledger
    • Persists every prompt, tool call, model output, and final recommendation for audit.
  • Human approval gate
    • Forces a credit committee or delegated officer to sign off before any binding action.

Implementation

1) Install AutoGen for TypeScript and define your agents

For TypeScript, use the AutoGen agentchat package and wire each role as a separate AssistantAgent. Keep the system messages narrow. Pension workflows fail when one model is asked to do underwriting, compliance, and final approval in one shot.

npm install @autogenai/autogen-agentchat @autogenai/autogen-ext
import { AssistantAgent } from "@autogenai/autogen-agentchat";
import { OpenAIChatCompletionClient } from "@autogenai/autogen-ext";

const modelClient = new OpenAIChatCompletionClient({
  model: "gpt-4o-mini",
  apiKey: process.env.OPENAI_API_KEY!,
});

const creditAgent = new AssistantAgent({
  name: "credit_analyst",
  modelClient,
  systemMessage:
    "You analyze loan applications for a pension fund. Focus on DSCR, leverage, collateral quality, tenor fit, and repayment capacity. Output only structured findings.",
});

const complianceAgent = new AssistantAgent({
  name: "compliance_officer",
  modelClient,
  systemMessage:
    "You check pension fund lending policy compliance. Flag breaches related to jurisdiction, concentration limits, KYC/AML gaps, prohibited sectors, and documentation defects.",
});

const orchestrator = new AssistantAgent({
  name: "loan_orchestrator",
  modelClient,
  systemMessage:
    "You coordinate loan approval analysis for a pension fund. Summarize findings from other agents and produce a recommendation for human review.",
});

2) Run the agents in sequence with a single application payload

Use a deterministic workflow. Don’t let the model improvise agent order. Pension funds need repeatable decision paths that can be replayed during audit or regulator review.

const loanApplication = `
Borrower: Northwind Logistics Ltd
Amount: USD 8,000,000
Tenor: 36 months
Purpose: Fleet expansion
Annual EBITDA: USD 3,200,000
Annual debt service existing: USD 1,100,000
Proposed annual debt service: USD 2,000,000
Collateral: Warehouse property valued at USD 6,500,000
Jurisdiction: Kenya
KYC status: Complete
AML status: No adverse hit
Policy notes: Pension fund max single obligor exposure is USD 10M; minimum DSCR is 1.25x; prohibited sectors exclude tobacco and weapons.
`;

async function analyzeLoan() {
  const creditResult = await creditAgent.run(
    `Assess the borrower credit profile:\n${loanApplication}`
  );

  const complianceResult = await complianceAgent.run(
    `Check policy compliance for this application:\n${loanApplication}\n\nCredit findings:\n${creditResult.messages.at(-1)?.content ?? ""}`
  );

  const finalResult = await orchestrator.run(
    `Prepare an approval memo using these inputs.\n\nCredit findings:\n${creditResult.messages.at(-1)?.content ?? ""}\n\nCompliance findings:\n${complianceResult.messages.at(-1)?.content ?? ""}\n\nReturn one of: APPROVE FOR HUMAN REVIEW | REJECT | ESCALATE.\nInclude rationale and required next actions.`
  );

  return {
    credit: creditResult.messages.at(-1)?.content,
    compliance: complianceResult.messages.at(-1)?.content,
    recommendation: finalResult.messages.at(-1)?.content,
  };
}

analyzeLoan().then(console.log);

3) Add an audit trail before you ever think about production

For pension funds, the output is not just the recommendation. You need evidence of what was asked, what was answered, which model version was used, and who approved the case after the agent finished.

type AuditRecord = {
  requestId: string;
  timestamp: string;
  stage: "credit" | "compliance" | "orchestrator";
  inputHash?: string;
  outputText: string;
};

const auditLog: AuditRecord[] = [];

function writeAudit(record: AuditRecord) {
    auditLog.push(record);
}

async function runWithAudit() {
  const requestId = crypto.randomUUID();
  const startedAt = new Date().toISOString();

  const creditResult = await creditAgent.run(`Assess:\n${loanApplication}`);
  writeAudit({
    requestId,
    timestamp: startedAt,
    stage: "credit",
    outputText: creditResult.messages.at(-1)?.content ?? "",
  });

  const complianceResult = await complianceAgent.run(`Check:\n${loanApplication}`);
  writeAudit({
    requestId,
    timestamp: new Date().toISOString(),
    stage: "compliance",
    outputText: complianceResult.messages.at(-1)?.content ?? "",
  });

  const memo = await orchestrator.run(
    `Create memo from prior outputs.\nCredit:\n${creditResult.messages.at(-1)?.content}\nCompliance:\n${complianceResult.messages.at(-1)?.content}`
    );
  
}

Implementation pattern that works in practice

The useful pattern is not “one giant autonomous agent.” It’s a bounded multi-agent pipeline with explicit roles:

StepAgentResponsibility
IntakeOrchestratorValidate required fields are present
Risk reviewCredit analystScore borrower capacity and collateral
Policy reviewCompliance officerCheck against pension fund rules
Memo creationOrchestratorProduce recommendation for human committee

That separation makes it easier to test each stage independently.

Production Considerations

  • Deploy inside your regulated boundary
    • Keep inference in-region if your pension fund has data residency requirements.
  • Log every decision artifact
    • Store prompt inputs, outputs, model version IDs, timestamps, and human overrides in immutable storage.
  • Add hard guardrails before generation
    • Reject missing KYC status, expired financial statements dated outside policy windows, or loans above concentration limits before any LLM call.
  • Monitor drift by policy class
    • Track false approvals on high-value loans separately from low-value cases. A single bad approval on a pension portfolio matters more than dozens of consumer-grade misses.

Common Pitfalls

  • Using one agent for everything

    This creates vague outputs and weak accountability. Split underwriting and compliance into separate agents with separate prompts.

  • Letting the model make binding approvals

    The agent should recommend; humans approve. For pension funds this is non-negotiable because committee oversight is part of control design.

  • Skipping document provenance

    If the agent reads uploaded PDFs without source tracking you cannot defend the decision later. Attach document IDs and storage references to every case.

  • Ignoring jurisdiction-specific rules

    A loan that passes generic underwriting may still violate local pension investment limits or related-party restrictions. Encode those rules outside the model as deterministic checks first.


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