How to Build a loan approval Agent Using CrewAI in TypeScript for retail banking

By Cyprian AaronsUpdated 2026-04-21
loan-approvalcrewaitypescriptretail-banking

A loan approval agent takes an incoming retail loan application, gathers the needed facts, checks policy and risk rules, and produces a decision recommendation with an audit trail. In retail banking, that matters because you need faster turnaround without losing control over compliance, explainability, and human oversight.

Architecture

  • Application intake

    • Normalizes borrower data from web forms, CRM records, or core banking systems.
    • Validates required fields before any AI reasoning starts.
  • Policy retrieval

    • Pulls product rules, lending thresholds, KYC requirements, and jurisdiction-specific constraints.
    • Keeps the agent grounded in bank-approved documents instead of free-form reasoning.
  • Decisioning agent

    • Uses CrewAI Agent to assess eligibility, identify missing information, and draft a recommendation.
    • Must not make final credit decisions without policy checks and human review where required.
  • Task orchestration

    • Uses CrewAI Task objects to split work into intake validation, policy analysis, risk summarization, and recommendation drafting.
    • Makes the workflow auditable.
  • Human review gate

    • Routes borderline cases to a credit officer or underwriter.
    • Required for adverse action reasons, exceptions, and low-confidence cases.
  • Audit logging

    • Stores prompts, retrieved policy snippets, outputs, timestamps, model version, and reviewer actions.
    • Essential for regulator requests and internal model governance.

Implementation

1) Install dependencies and define the workflow shape

CrewAI’s TypeScript package gives you Agent, Task, Crew, and Process. For banking use cases, keep the agent narrow: one agent for analysis, one optional tool layer for policy lookup or internal systems.

npm install crewai zod
import { Agent, Task, Crew, Process } from "crewai";

type LoanApplication = {
  applicantId: string;
  amount: number;
  incomeMonthly: number;
  existingDebtMonthly: number;
  employmentStatus: "employed" | "self-employed" | "unemployed";
  country: string;
  requestedTermMonths: number;
};

const application: LoanApplication = {
  applicantId: "APP-10291",
  amount: 25000,
  incomeMonthly: 6200,
  existingDebtMonthly: 1450,
  employmentStatus: "employed",
  country: "ZA",
  requestedTermMonths: 48,
};

2) Create a loan underwriting agent with strict instructions

Use a single agent that only recommends. The model should not invent policy; it should evaluate against supplied facts and return a structured result. In retail banking, this is where you constrain behavior around fairness, explainability, and regulatory language.

const underwriterAgent = new Agent({
  role: "Retail Loan Underwriting Analyst",
  goal:
    "Assess retail loan applications against bank policy and produce a compliant recommendation.",
  backstory:
    "You are a bank underwriting analyst. You only use provided application data and retrieved policy text. You never approve loans autonomously.",
});

3) Define tasks for validation, decision analysis, and explanation

Break the work into tasks so each step is visible in logs. This also helps when compliance asks why an application was flagged or referred.

const validateTask = new Task({
  description:
    `Validate the loan application fields for completeness and obvious issues.
     Application JSON:
     ${JSON.stringify(application)}`,
  expectedOutput:
    "A short validation summary listing missing fields or confirming the application is complete.",
});

const assessTask = new Task({
  description:
    `Assess this application using standard retail lending logic:
     - Debt-to-income ratio
     - Affordability
     - Employment stability
     - Jurisdictional constraints
     Return one of: APPROVE_RECOMMENDATION, REFER_FOR_REVIEW, DECLINE_RECOMMENDATION.
     Include reasons tied to the data.`,
  expectedOutput:
    "A structured underwriting recommendation with reasons and any manual review triggers.",
});

const explainTask = new Task({
  description:
    `Write a customer-safe explanation of the recommendation.
     Avoid prohibited content like protected-class references or hidden scoring logic.
     Keep it suitable for internal case notes.`,
  expectedOutput:
    "A concise explanation that can be stored in the audit trail.",
});

4) Run the crew and persist an audit record

This is the production pattern: execute the crew synchronously in your service layer, then store both input and output. If your deployment requires regional hosting or local model endpoints for data residency, point CrewAI at your approved provider configuration outside this code path.

async function runLoanAssessment() {
	const crew = new Crew({
		agents: [underwriterAgent],
		tasks: [validateTask, assessTask, explainTask],
		process: Process.sequential,
	});

	const result = await crew.kickoff();

	const auditRecord = {
		applicantId: application.applicantId,
		input: application,
		output: String(result),
		modelProvider: "approved-bank-provider",
		purpose: "retail-loan-underwriting",
		timestamp: new Date().toISOString(),
	};

	console.log(JSON.stringify(auditRecord, null, 2));
	return auditRecord;
}

runLoanAssessment();

Production Considerations

  • Keep final approval outside the agent

    • The agent should recommend approve/refer/decline.
    • Final credit decisions should flow through your LOS or underwriting system with human sign-off where policy requires it.
  • Log everything needed for audit

    • Persist prompt inputs, retrieved policy versions, outputs, reviewer actions, and model metadata.
    • Regulators care about traceability more than clever prompts.
  • Enforce data residency

    • Route PII through approved regional infrastructure only.
    • If your bank cannot send customer data خارج jurisdiction boundaries, use local deployments or redaction before inference.
  • Add guardrails for adverse action handling

    • Block outputs that mention protected characteristics or unsupported reasons.
    • Map decline recommendations to approved reason codes generated by deterministic rules.

Common Pitfalls

  • Letting the model invent policy

    • Avoid this by retrieving bank-approved lending rules first and including them in task context.
    • Never ask the agent to “decide based on best judgment” without explicit policy text.
  • Skipping deterministic affordability checks

const dti = application.existingDebtMonthly / application.incomeMonthly;
if (dti > 0.45) {
	console.log("REFER_FOR_REVIEW");
}
  • Use hard rules like DTI thresholds before any LLM recommendation. The agent should explain the result, not compute critical eligibility from scratch.

  • No audit trail

// bad
await crew.kickoff();
  • Don’t stop at the model output. Store inputs, outputs, timestamps, policy version IDs, reviewer decisions, and exception reasons in an immutable log table.

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