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

By Cyprian AaronsUpdated 2026-04-21
loan-approvalautogentypescriptpayments

A loan approval agent for payments decides whether a payment-linked loan request should move forward, needs more review, or should be rejected. In practice, it sits between your payment flow and your credit/risk systems, so approvals are fast, auditable, and consistent without putting regulated decisions fully in the hands of a human queue.

Architecture

  • Request intake layer

    • Receives the loan application payload from your payments backend.
    • Normalizes customer, transaction, and repayment data before the agent sees it.
  • Policy/risk context service

    • Pulls KYC status, fraud signals, repayment history, exposure limits, and jurisdiction rules.
    • Keeps sensitive data out of prompts unless it is required for the decision.
  • AutoGen agent group

    • Uses AssistantAgent instances for risk analysis and compliance review.
    • Uses a UserProxyAgent to execute deterministic tool calls and enforce termination.
  • Decision engine

    • Converts the agent discussion into a structured outcome: approve, reject, or manual review.
    • Applies hard business rules after the model output.
  • Audit and evidence store

    • Persists prompts, tool outputs, model responses, and final decision metadata.
    • Needed for disputes, compliance review, and internal controls.
  • Payments integration layer

    • Posts the decision back to your checkout or disbursement workflow.
    • Triggers downstream actions like loan creation, payment authorization hold, or case creation.

Implementation

1. Install AutoGen and define the decision contract

For TypeScript, use AutoGen’s autogen-core package and keep the output shape strict. Loan decisions need structured results because you cannot safely parse free-form text in a payments flow.

npm install autogen-core zod
import { z } from "zod";

export const LoanDecisionSchema = z.object({
  decision: z.enum(["approve", "reject", "manual_review"]),
  reason: z.string(),
  riskScore: z.number().min(0).max(100),
  complianceFlags: z.array(z.string()),
});

export type LoanDecision = z.infer<typeof LoanDecisionSchema>;

export interface LoanApplication {
  applicationId: string;
  customerId: string;
  amount: number;
  currency: string;
  country: string;
  kycStatus: "passed" | "pending" | "failed";
  fraudScore: number;
  repaymentHistoryMonths: number;
}

2. Create agents with explicit roles

Use one agent for risk analysis and one for compliance checks. The UserProxyAgent handles orchestration and can call tools that fetch bank-side facts instead of asking the model to infer them.

import { AssistantAgent, UserProxyAgent } from "autogen-core";

const riskAgent = new AssistantAgent({
  name: "risk_agent",
  systemMessage:
    "You assess loan eligibility for payments. Return concise JSON only. Focus on repayment ability, fraud indicators, and exposure.",
});

const complianceAgent = new AssistantAgent({
  name: "compliance_agent",
  systemMessage:
    "You assess regulatory and policy constraints for payment-linked lending. Return concise JSON only. Flag KYC gaps, jurisdiction issues, and audit concerns.",
});

const orchestrator = new UserProxyAgent({
  name: "loan_orchestrator",
});

3. Run the agents against real application data

The pattern below sends the same application context to both agents, then applies deterministic rules before returning a final result. That matters because model output alone should never be your final control in a regulated payments workflow.

type AgentResult = {
  content: string;
};

async function analyzeLoanApplication(app: LoanApplication): Promise<LoanDecision> {
  const prompt = `
Loan application:
${JSON.stringify(app)}

Return JSON with:
- decision: approve | reject | manual_review
- reason
- riskScore (0-100)
- complianceFlags (string[])
`;

  const riskTask = orchestrator.initiateChat(riskAgent, prompt);
  const complianceTask = orchestrator.initiateChat(complianceAgent, prompt);

  const [riskResponse, complianceResponse] = await Promise.all([
    riskTask,
    complianceTask,
  ]);

  
import { LoanDecisionSchema } from "./schema";

function parseJson(content: string) {
  return JSON.parse(content);
}

async function finalizeDecision(
  app: LoanApplication,
  riskContent: string,
  complianceContent: string
): Promise<LoanDecision> {
  const risk = parseJson(riskContent);
  const compliance = parseJson(complianceContent);

  
function applyPolicy(app: LoanApplication): LoanDecision {
    if (app.kycStatus !== "passed") {
      return {
        decision: "manual_review",
        reason: "KYC not passed",
        riskScore: Math.max(app.fraudScore, 70),
        complianceFlags: ["KYC_INCOMPLETE"],
      };
    }

    if (app.fraudScore >= 85) {
      return {
        decision: "reject",
        reason: "Fraud score above threshold",
        riskScore: app.fraudScore,
        complianceFlags: ["HIGH_FRAUD_RISK"],
      };
    }

    return {
      decision:
        app.amount > 5000 || app.repaymentHistoryMonths < 6
          ? "manual_review"
          : "approve",
      reason:
        app.amount > 5000
          ? "Amount exceeds straight-through threshold"
          : "Meets automated approval policy",
      riskScore: app.fraudScore,
      complianceFlags:
        app.country === "US" ? [] : ["CHECK_DATA_RESIDENCY"],
    };
}
export async function approveLoanForPayment(app: LoanApplication) {
    const policyDecision = applyPolicy(app);

    if (policyDecision.decision !== "approve") {
      return policyDecision;
    }

    const result = await analyzeLoanApplication(app);
    return LoanDecisionSchema.parse(result);
}

What this pattern does

  • The model helps summarize risk and compliance context.
  • Your code still owns the final approval rules.
  • The schema forces a structured response that downstream payment systems can trust.

Production Considerations

  • Keep hard controls outside the model

    • KYC failure, sanctions hits, exposure limits, and jurisdiction blocks should be enforced in code before any approval is emitted.
    • Treat the agent as advisory unless policy explicitly allows automated approval.
  • Log every decision with evidence


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