How to Build a fraud detection Agent Using CrewAI in TypeScript for investment banking
A fraud detection agent in investment banking triages suspicious trades, payments, and account activity, then routes high-risk cases to the right human team with evidence attached. It matters because false negatives become regulatory incidents, while false positives burn analyst time and slow down client operations.
Architecture
- •Ingestion layer
- •Pulls alerts from trade surveillance, payment rails, KYC/AML systems, and case management queues.
- •Normalization layer
- •Converts raw events into a consistent schema: counterparty, instrument, timestamp, venue, amount, jurisdiction, and risk flags.
- •Analysis agent
- •Uses a
Crewwith one or moreAgentinstances to score patterns against fraud typologies like layering, wash trading, spoofing, account takeover, and mule behavior.
- •Uses a
- •Evidence retrieval
- •Queries internal documents: client profiles, historical cases, sanctions hits, order logs, and communication metadata.
- •Decision router
- •Produces one of three outputs: close as benign, escalate for review, or freeze/report under policy.
- •Audit trail store
- •Persists prompt inputs, tool calls, model outputs, and final decisions for compliance review and model governance.
Implementation
1) Install dependencies and define the case schema
Use the TypeScript package for CrewAI plus a validation library. For investment banking work you want strict schemas; don’t pass loose JSON around and hope the model behaves.
npm install @crewai/typescript zod dotenv
// src/types.ts
import { z } from "zod";
export const FraudCaseSchema = z.object({
caseId: z.string(),
clientId: z.string(),
accountId: z.string(),
instrumentType: z.string(),
jurisdiction: z.string(),
amountUsd: z.number(),
timestamp: z.string(),
alertType: z.enum(["trade", "payments", "kyc", "communications"]),
notes: z.string().optional(),
});
export type FraudCase = z.infer<typeof FraudCaseSchema>;
2) Create agents with explicit responsibilities
Keep the roles narrow. One agent should investigate; another should decide whether the evidence meets escalation thresholds. That separation helps with auditability and makes the outputs easier to defend.
// src/crew.ts
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/typescript";
import { FraudCaseSchema } from "./types";
const investigator = new Agent({
role: "Fraud Investigation Analyst",
goal:
"Analyze suspicious investment banking activity and summarize evidence for escalation decisions.",
backstory:
"You are a surveillance analyst focused on trade fraud, payment fraud, AML typologies, and client risk.",
verbose: true,
});
const decisionMaker = new Agent({
role: "Fraud Triage Lead",
goal:
"Decide whether a case should be closed, escalated to compliance, or frozen pending review.",
backstory:
"You apply bank policy conservatively and always preserve an audit-friendly rationale.",
verbose: true,
});
export function buildFraudCrew(input: unknown) {
const caseData = FraudCaseSchema.parse(input);
const investigationTask = new Task({
description: `
Investigate this investment banking fraud alert:
${JSON.stringify(caseData, null, 2)}
Look for indicators of spoofing, wash trading, layering,
account takeover, mule activity, unusual jurisdiction routing,
or inconsistent client behavior.
Return concise evidence bullets only.
`,
expected_output:
"A structured summary of suspicious indicators with severity assessment.",
agent: investigator,
});
const triageTask = new Task({
description:
"Using the investigation summary above, determine disposition: CLOSE | ESCALATE | FREEZE.",
expected_output:
"A final decision with rationale aligned to bank policy.",
agent: decisionMaker,
context: [investigationTask],
});
return new Crew({
agents: [investigator, decisionMaker],
tasks: [investigationTask, triageTask],
verbose: true,
});
}
3) Run the crew and persist an audit record
In production you need immutable traces. The output below is intentionally simple; wire it to your SIEM or case-management platform.
// src/index.ts
import { buildFraudCrew } from "./crew";
import fs from "node:fs/promises";
async function main() {
const crew = buildFraudCrew({
caseId: "CASE-10492",
clientId: "C-88312",
accountId: "A-22901",
instrumentType: "Equity Swaps",
jurisdiction: "GB",
amountUsd: 12500000,
timestamp: new Date().toISOString(),
alertType: "trade",
notes:
"Multiple orders entered then cancelled within seconds; counterparty changed twice.",
});
const result = await crew.kickoff();
await fs.appendFile(
"./audit-log.jsonl",
JSON.stringify({
ts: new Date().toISOString(),
caseId: "CASE-10492",
result,
}) + "\n"
);
console.log(result);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
4) Add a deterministic guardrail before escalation
Do not let the model make the final call alone on freeze actions. Use policy rules first; let CrewAI explain or enrich the decision.
// src/policy.ts
import { FraudCase } from "./types";
export function requiresImmediateFreeze(caze: FraudCase): boolean {
return (
caze.amountUsd >= 10000000 &&
["trade", "payments"].includes(caze.alertType) &&
["IR", "RU", "KP"].includes(caze.jurisdiction)
);
}
If requiresImmediateFreeze() returns true, route directly to compliance workflow and use the agent only to draft the rationale. That keeps your control plane deterministic.
Production Considerations
- •Deployment
- •Run the agent in a private VPC or on-prem cluster if your bank has residency constraints.
- •Keep prompts and outputs in-region; do not send client data across borders without legal approval.
- •Monitoring
- •Track precision/recall by alert type.
- •Log every tool call and every final disposition into your governance store.
- •Guardrails
- •Hard-code policy thresholds for freeze/escalate actions.
- •Require human approval for adverse actions on high-value clients or politically exposed persons.
- •Compliance
- •Retain full traces for model risk management reviews.
- •Map each output to an internal control ID so auditors can trace why a case was escalated.
Common Pitfalls
- •
Using one generic agent for everything
- •This creates vague outputs and weak audits.
- •Split investigation and triage into separate agents with narrow goals.
- •
Letting the model override policy rules
- •A language model should not be the source of truth for freeze decisions.
- •Apply deterministic checks first; use CrewAI to explain findings.
- •
Ignoring data residency and retention
- •Sending transaction data to an external region can break legal obligations fast.
- •Pin storage and inference to approved regions and redact unnecessary PII before prompting.
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