How to Build a transaction monitoring Agent Using CrewAI in TypeScript for lending
A transaction monitoring agent for lending watches borrower activity, flags suspicious or policy-breaking behavior, and routes cases for review before losses or compliance issues grow. In practice, it helps you detect cash-flow anomalies, rapid drawdowns, circular transfers, and repayment patterns that don’t match the borrower profile or loan covenants.
Architecture
- •
Transaction ingest layer
- •Pulls transactions from core banking, loan servicing, card rails, or event streams.
- •Normalizes them into a consistent schema with borrower ID, timestamp, amount, counterparty, channel, and account type.
- •
Risk rules and policy context
- •Encodes lending-specific thresholds like missed repayment windows, unusual prepayment spikes, velocity changes, and covenant breaches.
- •Keeps jurisdiction-specific rules separate from model prompts so you can audit them.
- •
CrewAI agent layer
- •Uses a
Crewwith specializedAgents for triage, explanation, and escalation. - •Produces structured decisions instead of free-form narratives.
- •Uses a
- •
Tooling layer
- •Gives the agent controlled access to transaction lookup, customer profile fetches, sanctions/PEP checks, and case management APIs.
- •Prevents the model from inventing facts by forcing retrieval through tools.
- •
Case management output
- •Writes alerts into your investigation system with evidence, severity, reason codes, and recommended action.
- •Keeps an immutable audit trail for compliance review.
Implementation
1) Define the transaction schema and tools
For lending workflows, keep the data model narrow. The agent should see only what it needs to decide whether a transaction is anomalous relative to the borrower’s profile.
import { z } from "zod";
import { Tool } from "@crew-ai/crewai";
export const TransactionSchema = z.object({
transactionId: z.string(),
borrowerId: z.string(),
amount: z.number(),
currency: z.string(),
timestamp: z.string(),
merchantCategory: z.string().optional(),
counterparty: z.string().optional(),
channel: z.enum(["ACH", "WIRE", "CARD", "CASH", "OTHER"]),
});
export type Transaction = z.infer<typeof TransactionSchema>;
export const getBorrowerProfileTool = new Tool({
name: "get_borrower_profile",
description: "Fetch borrower risk profile and loan terms for monitoring.",
func: async ({ borrowerId }: { borrowerId: string }) => {
// Replace with real API call
return {
borrowerId,
riskBand: "medium",
productType: "SME_TERM_LOAN",
monthlyPaymentDueDay: 15,
covenantFlags: ["DSCR_MIN_1_25"],
residencyRegion: "EU",
};
},
});
export const getRecentTransactionsTool = new Tool({
name: "get_recent_transactions",
description: "Fetch recent transactions for anomaly comparison.",
func: async ({ borrowerId }: { borrowerId: string }) => {
// Replace with real API call
return [
{ amount: 1200, channel: "ACH", counterparty: "Payroll Co" },
{ amount: 9800, channel: "WIRE", counterparty: "Unknown Vendor" },
];
},
});
2) Create specialized agents
Use one agent to assess risk and another to explain the decision in compliance-friendly language. In lending operations, that separation matters because investigators want a rationale they can defend later.
import { Agent } from "@crew-ai/crewai";
export const riskAnalystAgent = new Agent({
name: "Transaction Risk Analyst",
role: "Assess lending transaction anomalies against policy and borrower profile",
goal:
"Detect suspicious or policy-breaking transaction patterns and assign a severity score.",
backstory:
"You are a senior lending fraud analyst focused on repayment behavior, cash-flow anomalies, and covenant breaches.",
tools: [getBorrowerProfileTool, getRecentTransactionsTool],
});
export const caseWriterAgent = new Agent({
name: "Case Narrative Writer",
role: "Write concise investigation notes for compliance reviewers",
goal:
"Produce an audit-ready summary with facts, reason codes, and recommended next steps.",
});
3) Build the crew and run a monitoring task
This is the core pattern. The first agent gathers context through tools; the second turns that into a case note. Keep outputs structured so downstream systems can parse them.
import { Crew, Task } from "@crew-ai/crewai";
const monitoringTask = new Task({
description:
[
"Review this transaction for lending-related anomalies.",
"Flag unusual repayment behavior, rapid fund movement after disbursement,
or activity inconsistent with the borrower profile.",
"Return JSON with fields:",
"- severity (low|medium|high)",
"- reasonCodes (array)",
"- summary",
"- recommendedAction",
].join(" "),
expectedOutput:
'{"severity":"high","reasonCodes":["..."],"summary":"...","recommendedAction":"..."}',
});
const explanationTask = new Task({
description:
"Convert the risk assessment into an audit-ready case note for compliance reviewers.",
});
const crew = new Crew({
agents: [riskAnalystAgent, caseWriterAgent],
});
export async function monitorTransaction(txnRaw: unknown) {
const txn = TransactionSchema.parse(txnRaw);
const result = await crew.kickoff({
inputs: {
transactionId: txn.transactionId,
borrowerId: txn.borrowerId,
amount: txn.amount,
currency: txn.currency,
timestamp: txn.timestamp,
merchantCategory: txn.merchantCategory ?? "",
counterparty: txn.counterparty ?? "",
channel: txn.channel,
},
tasksToRunFirst:[monitoringTask],
});
return result;
}
4) Wire it into your lending workflow
Trigger this after payment events, disbursement events, or daily batch scoring. For regulated lending operations, persist both the input snapshot and the model output so auditors can reconstruct the decision later.
async function handleIncomingTransaction(eventBody: unknown) {
try {
const alert = await monitorTransaction(eventBody);
if (alert.severity === "high") {
// write to case management system
console.log("CREATE_CASE", alert);
}
return { ok": true };
} catch (err) {
console.error("MONITORING_FAILED", err);
return { ok": false };
}
}
Production Considerations
- •Keep data residency explicit
- •If your lending book sits in the EU or a specific country cluster, run inference and storage in-region.
- •Log every decision path
- •Persist input payload hash, tool outputs used by the agent, final severity score, and reason codes.
- •Add hard guardrails before escalation
- •Use deterministic rules for known red flags like sanctioned counterparties or payments above policy thresholds.
- •Separate monitoring by product type
- •SME term loans, consumer installment loans, revolving credit lines all need different thresholds and explanations.
Common Pitfalls
- •
Letting the model infer missing facts
- •Avoid free-form conclusions without tool-backed evidence. Force all customer/profile lookups through tools.
- •
Using one generic threshold for every loan product
- •A payday-style repayment pattern is not comparable to commercial lending cash flow. Tune rules per portfolio.
- •
Skipping audit-ready outputs
- •If your alert only says “suspicious,” compliance will reject it. Always emit reason codes, timestamps, source data references, and an action recommendation.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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