How to Build a transaction monitoring Agent Using AutoGen in TypeScript for investment banking
A transaction monitoring agent flags suspicious activity across payments, trades, and internal transfers before it becomes a regulatory problem. In investment banking, that matters because the same system has to catch AML patterns, market abuse signals, and policy breaches while producing an audit trail that compliance can defend in front of regulators.
Architecture
Build this agent around a small set of hard boundaries:
- •
Ingestion layer
- •Pulls transactions from Kafka, S3, a database, or a streaming bus.
- •Normalizes fields like
accountId,counterparty,amount,currency,jurisdiction, andtimestamp.
- •
Risk rules service
- •Applies deterministic checks first: threshold breaches, structuring patterns, rapid movement of funds, watchlist hits.
- •Keeps obvious cases out of the LLM path.
- •
AutoGen orchestrator
- •Uses
AssistantAgentandUserProxyAgentto reason over enriched transaction batches. - •Produces a risk classification plus a short explanation for analysts.
- •Uses
- •
Case management adapter
- •Opens alerts in your case system with evidence, rule hits, and model reasoning.
- •Stores immutable audit logs for every decision.
- •
Compliance guardrail layer
- •Redacts PII where possible.
- •Enforces residency and retention constraints before anything reaches the model.
Implementation
1) Install AutoGen for TypeScript and define the transaction shape
For TypeScript, use the AutoGen package published by Microsoft. Keep your transaction schema explicit; investment banking workflows fail when downstream code has to guess what a field means.
npm install @autogenai/autogen
export type Transaction = {
id: string;
accountId: string;
counterparty: string;
amount: number;
currency: string;
jurisdiction: string;
timestamp: string;
channel: "wire" | "swift" | "internal_transfer" | "trade_settlement";
flags?: string[];
};
export type MonitoringResult = {
transactionId: string;
riskScore: number;
decision: "clear" | "review" | "escalate";
rationale: string;
};
2) Create the agents and wire them to an OpenAI-compatible model
Use an assistant agent for analysis and a user proxy agent to execute tool calls or terminate the conversation. The pattern below keeps the LLM on analysis only; deterministic logic stays outside.
import { AssistantAgent, UserProxyAgent } from "@autogenai/autogen";
const llmConfig = {
model: "gpt-4o-mini",
apiKey: process.env.OPENAI_API_KEY!,
};
export const monitoringAnalyst = new AssistantAgent({
name: "monitoring_analyst",
llmConfig,
systemMessage:
"You are a transaction monitoring analyst for investment banking. Classify risk using AML, sanctions, market abuse, and policy breach signals. Be concise. Always cite the rule hits provided.",
});
export const opsProxy = new UserProxyAgent({
name: "ops_proxy",
humanInputMode: "NEVER",
});
3) Enrich transactions with deterministic rules before calling AutoGen
This is where most production systems should start. If you already know a transfer is over threshold or matches structuring logic, pass that context into the agent instead of asking it to rediscover it.
function enrichTransaction(tx: Transaction) {
const flags: string[] = [];
if (tx.amount >= 1000000) flags.push("high_value");
if (tx.jurisdiction === "high_risk_country") flags.push("jurisdiction_risk");
if (tx.channel === "swift" && tx.counterparty.includes("shell")) flags.push("counterparty_risk");
return {
...tx,
flags,
riskContext: {
amlThresholdBreached: tx.amount >= 1000000,
residencySensitive: true,
auditRequired: true,
},
};
}
4) Run a short AutoGen conversation and produce an alert record
The actual pattern is simple: send structured JSON to the assistant, get back a classification, then persist both input and output for audit. Do not let free-form text become your system of record.
async function reviewTransaction(tx: Transaction): Promise<MonitoringResult> {
const enriched = enrichTransaction(tx);
const prompt = `
Review this transaction for investment banking monitoring.
Return JSON only with fields:
transactionId, riskScore (0-100), decision ("clear"|"review"|"escalate"), rationale
Transaction:
${JSON.stringify(enriched, null, 2)}
`;
const response = await opsProxy.initiateChat(monitoringAnalyst, prompt);
const content = response.chatHistory?.at(-1)?.content ?? "";
const parsed = JSON.parse(content as string);
return {
transactionId: parsed.transactionId,
riskScore: parsed.riskScore,
decision: parsed.decision,
rationale: parsed.rationale,
};
}
If you want stronger control over tool execution or multi-step workflows, keep the same structure but add tools through AutoGen’s agent configuration rather than embedding business logic in prompts. For transaction monitoring, I’d keep tool usage narrow:
- •fetch customer KYC profile
- •fetch recent transaction history
- •write case record
- •emit audit event
That keeps model behavior observable and easier to certify internally.
Production Considerations
- •
Deploy close to regulated data
Keep inference in-region. Investment banking data residency rules often block cross-border processing of client data, especially for EU and APAC entities.
- •
Log everything needed for audit
Store input payload hash, rule hits, model version, prompt version, output JSON, reviewer override, and final case disposition. If you cannot reproduce why an alert was raised six months later, you do not have a defensible control.
- •
Put deterministic gates before the model
Use hard rules for sanctions screening, threshold breaches, duplicate detection, and known typologies. The LLM should rank ambiguous cases, not replace controls that compliance already trusts.
- •
Add human review thresholds
Anything above your escalation threshold should go to an analyst queue with evidence attached. Never auto-close high-risk alerts solely on LLM output.
Common Pitfalls
- •
Letting the model see raw sensitive data
Don’t send full account statements or unnecessary PII into prompts. Redact names where possible and pass only the minimum fields required for classification.
- •
Using free-form responses as system output
If your parser accepts arbitrary prose, you will get broken downstream automation. Force JSON-only responses and validate them before creating alerts.
- •
Skipping reproducibility controls
Model drift is real. Pin model versions where possible, version your prompts, store rule engine outputs separately from LLM outputs, and keep every decision tied to an immutable transaction snapshot.
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