How to Build a transaction monitoring Agent Using CrewAI in TypeScript for wealth management
A transaction monitoring agent for wealth management watches client activity, flags suspicious or policy-breaking patterns, and routes the right cases to compliance or relationship teams. It matters because wealth platforms deal with high-value transfers, complex ownership structures, and strict regulatory expectations around AML, suitability, and auditability.
Architecture
- •
Transaction ingestion layer
- •Pulls trades, wires, journal entries, and account movements from your OMS, custodial feeds, or data warehouse.
- •Normalizes records into a consistent schema before the agent sees them.
- •
Risk scoring toolset
- •Encapsulates deterministic checks like velocity rules, threshold breaches, unusual counterparty patterns, and sanctioned geography exposure.
- •Keeps the agent from guessing when a rule already exists.
- •
CrewAI agents
- •One agent evaluates transaction risk.
- •Another agent validates compliance context like KYC status, account type, and jurisdiction.
- •A final agent writes a case summary for investigators.
- •
Case management output
- •Produces structured alerts with severity, rationale, evidence, and next actions.
- •Pushes results into your workflow system or SIEM.
- •
Audit log store
- •Persists every input, tool call, model output, and final decision.
- •This is non-negotiable in wealth management.
Implementation
1) Install CrewAI and set up TypeScript project
CrewAI’s TypeScript package is designed for agent orchestration. You want strict typing around your transaction payloads so compliance rules stay explicit.
npm install @crewai/crewai zod dotenv
npm install -D typescript tsx @types/node
Create a minimal tsconfig.json with strict mode on:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "dist"
}
}
2) Define the transaction schema and tools
In wealth management, unstructured transaction text is not enough. You need a typed payload that includes account metadata, jurisdiction, client risk profile, and source-of-funds context.
import { z } from "zod";
import { Agent, Task, Crew } from "@crewai/crewai";
const TransactionSchema = z.object({
transactionId: z.string(),
accountId: z.string(),
clientId: z.string(),
amount: z.number().positive(),
currency: z.string().length(3),
type: z.enum(["wire", "trade", "journal", "cash_movement"]),
counterpartyCountry: z.string(),
clientCountry: z.string(),
kycStatus: z.enum(["verified", "pending", "expired"]),
sourceOfFundsKnown: z.boolean(),
timestamp: z.string()
});
type Transaction = z.infer<typeof TransactionSchema>;
const riskRulesTool = async (tx: Transaction) => {
const flags: string[] = [];
if (tx.amount >= 250000) flags.push("high_value_transfer");
if (tx.counterpartyCountry !== tx.clientCountry) flags.push("cross_border");
if (tx.kycStatus !== "verified") flags.push("kyc_not_verified");
if (!tx.sourceOfFundsKnown) flags.push("source_of_funds_unknown");
return {
score: Math.min(flags.length * 25, 100),
flags
};
};
This is where you keep deterministic controls out of the model. The agent can explain and prioritize; the tool decides known policy checks.
3) Build the agents and tasks
Use separate agents for risk analysis and compliance review. That separation helps with audit trails because each step has a clear responsibility.
const riskAnalyst = new Agent({
role: "Transaction Risk Analyst",
goal: "Assess transaction activity for suspicious patterns in wealth management accounts",
backstory:
"You review transactions under AML and suitability controls for high-net-worth clients.",
});
const complianceReviewer = new Agent({
role: "Compliance Reviewer",
goal: "Validate whether flagged transactions require escalation or can be closed",
});
const summaryWriter = new Agent({
role: "Case Summary Writer",
goal: "Write concise investigation notes for auditors and case managers",
});
const assessTask = (tx: Transaction) =>
new Task({
description: `Assess this transaction for monitoring alerts:\n${JSON.stringify(tx)}`,
expectedOutput:
"A structured assessment including risk level, key flags, and escalation recommendation.",
agent: riskAnalyst,
tools: [riskRulesTool],
});
const reviewTask = new Task({
description:
"Review the initial assessment against wealth management compliance requirements.",
expectedOutput:
"Decision on whether to escalate to AML/compliance with rationale.",
agent: complianceReviewer,
});
const summarizeTask = new Task({
description:
"Write an audit-ready case summary with evidence and next steps.",
expectedOutput:
"A short investigator summary suitable for case management systems.",
agent: summaryWriter,
});
4) Execute the crew and persist an audit record
This pattern gives you one place to validate inputs before orchestration starts. In production, send the final result to your case system and store the raw outputs in immutable storage.
async function runMonitoring(txInput: unknown) {
const tx = TransactionSchema.parse(txInput);
const crew = new Crew({
agents: [riskAnalyst, complianceReviewer, summaryWriter],
tasks: [assessTask(tx), reviewTask, summarizeTask],
process: "sequential"
});
const result = await crew.kickoff();
return {
transactionId: tx.transactionId,
alertGenerated: true,
result
};
}
const sampleTx = {
transactionId: "txn_10001",
accountId: "acct_7788",
clientId: "client_5521",
amount:500000,
currency:"USD",
type:"wire",
counterpartyCountry:"AE",
clientCountry:"US",
kycStatus:"verified",
sourceOfFundsKnown:false,
timestamp:new Date().toISOString()
};
runMonitoring(sampleTx).then(console.log);
The important part is not just getting an answer back. It is producing a repeatable chain of reasoning that compliance can inspect later.
Production Considerations
- •
Deploy close to your data boundary
- •Wealth data often has residency constraints by region.
- •Keep EU client data in EU regions and avoid routing sensitive fields through unauthorized endpoints.
- •
Log every decision path
- •Store input payload hashes, tool outputs, task outputs, timestamps, model version, and operator overrides.
- •This supports audits under AML programs and internal model governance.
- •
Add hard guardrails before the LLM
- •Block unsupported currencies, malformed timestamps, missing KYC fields, or incomplete beneficiary data at ingestion time.
- •Do not let the agent infer missing compliance facts.
- •
Separate alerting from adjudication
| Stage | Responsibility | Output |
|---|---|---|
| Detection | Rules + agent analysis | Alert candidate |
| Review | Compliance validation | Escalate / close |
| Case write-up | Summary generation | Audit note |
Common Pitfalls
- •Using the model as the primary rule engine
Treat policy checks as code first. If you rely on prompts alone for threshold logic or jurisdiction rules, you will get inconsistent outcomes under load.
- •Skipping KYC and account context
A $300k wire means something different on a private banking account than on an institutional custody account. Always include KYC status, source-of-funds history, client segment, and jurisdiction in the task input.
- •No immutable audit trail
If you cannot reconstruct why an alert was created six months later, your implementation will fail operational review. Persist raw inputs, tool outputs, prompt versions, model versions, and final decisions in append-only storage.
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