How to Build a transaction monitoring Agent Using AutoGen in TypeScript for wealth management

By Cyprian AaronsUpdated 2026-04-21
transaction-monitoringautogentypescriptwealth-management

A transaction monitoring agent for wealth management watches client activity, flags suspicious or policy-breaking behavior, and routes only the right cases to compliance. It matters because you’re not just looking for fraud — you’re protecting fiduciary duty, meeting AML/KYC obligations, and keeping an auditable record of every decision the system makes.

Architecture

Build this agent as a small set of explicit components, not one giant prompt:

  • Transaction intake service

    • Receives trades, transfers, deposits, withdrawals, and account profile updates.
    • Normalizes payloads into a single internal schema.
  • Policy/rules layer

    • Encodes wealth-management-specific thresholds and controls.
    • Examples: high-value wires, rapid movement across jurisdictions, unusual beneficiary changes, repeated cash-like activity.
  • AutoGen orchestration layer

    • Uses AssistantAgent to reason over the case.
    • Uses UserProxyAgent or a custom executor to run deterministic checks and call internal tools.
  • Evidence retrieval layer

    • Pulls client profile, historical transactions, KYC risk rating, jurisdiction metadata, and prior alerts.
    • Keeps the model grounded in actual case facts.
  • Case management sink

    • Writes alert decisions, rationale, timestamps, and evidence references to your audit store.
    • Exports cases to compliance review queues.

Implementation

1) Install AutoGen for TypeScript and define your case types

You want a strict schema before you involve any model. Wealth management workflows fail when transaction objects are loosely typed and downstream reviewers cannot reconstruct why a case was flagged.

npm install @autogen/agent @autogen/core zod
import { z } from "zod";

export const TransactionSchema = z.object({
  transactionId: z.string(),
  accountId: z.string(),
  clientId: z.string(),
  type: z.enum(["wire", "transfer", "trade", "deposit", "withdrawal"]),
  amount: z.number().positive(),
  currency: z.string().length(3),
  originCountry: z.string(),
  destinationCountry: z.string().optional(),
  counterpartyName: z.string(),
  timestamp: z.string(),
});

export type Transaction = z.infer<typeof TransactionSchema>;

export const CaseContextSchema = z.object({
  clientRiskRating: z.enum(["low", "medium", "high"]),
  pepFlag: z.boolean(),
  sanctionsHit: z.boolean(),
  priorAlerts30d: z.number().int().nonnegative(),
  kycJurisdiction: z.string(),
});

export type CaseContext = z.infer<typeof CaseContextSchema>;

2) Create an assistant agent that produces a structured monitoring decision

Use AssistantAgent for the analysis step and force it to return a compact decision object. The important part is that the model does not “decide” in free text; it returns something your workflow can store and review.

import { AssistantAgent } from "@autogen/agent";

const monitoringAgent = new AssistantAgent({
  name: "monitoring_agent",
  systemMessage: `
You are a transaction monitoring analyst for wealth management.
Classify each case as clear / review / escalate.
Use only the provided facts.
Always mention compliance drivers such as AML, sanctions, unusual activity, jurisdiction risk,
and whether the case needs human review.
Return concise JSON with fields:
decision, riskScore, reasons, recommendedAction.
`,
});

3) Add deterministic checks before model reasoning

Do not ask the LLM to calculate everything. High-value rules should be deterministic so they are stable under audit. In wealth management, simple controls like threshold breaches and jurisdiction mismatches often matter more than long explanations.

function deterministicFlags(txn: Transaction, ctx: CaseContext) {
  const flags: string[] = [];

  if (txn.amount >= 100000) flags.push("high_value_transaction");
  if (ctx.sanctionsHit) flags.push("sanctions_hit");
  if (ctx.pepFlag && txn.amount >= 25000) flags.push("pep_high_value_activity");
  if (ctx.priorAlerts30d >= 3) flags.push("repeat_alert_pattern");
  if (txn.destinationCountry && txn.destinationCountry !== ctx.kycJurisdiction) {
    flags.push("cross_jurisdiction_transfer");
  }

  return flags;
}

4) Orchestrate the case review with AutoGen and persist the result

The pattern below combines rule output with model reasoning. If your AutoGen version exposes run, use it directly; otherwise adapt this call to your chat/completion wrapper while keeping the same agent boundary.

import { UserProxyAgent } from "@autogen/agent";

const complianceProxy = new UserProxyAgent({
  name: "compliance_proxy",
});

type MonitoringDecision = {
  decision: "clear" | "review" | "escalate";
  riskScore: number;
};

export async function reviewTransaction(txn: Transaction, ctx: CaseContext) {
    const flags = deterministicFlags(txn, ctx);

    const prompt = `
Transaction:
${JSON.stringify(txn)}

Client context:
${JSON.stringify(ctx)}

Deterministic flags:
${JSON.stringify(flags)}

Return JSON only.
`;

    const result = await monitoringAgent.run(prompt);

    const text = typeof result === "string" ? result : JSON.stringify(result);
    const parsed = JSON.parse(text) as MonitoringDecision & {
      reasons?: string[];
      recommendedAction?: string;
    };

    const finalDecision =
      parsed.decision === "escalate" || flags.includes("sanctions_hit")
        ? "escalate"
        : parsed.decision;

    await complianceProxy.send(
      {
        role: "system",
        content: `Persist alert for ${txn.transactionId} with decision=${finalDecision}`,
      },
      undefined
    );

    return {
      transactionId: txn.transactionId,
      finalDecision,
      riskScore: parsed.riskScore,
      flags,
      reasons: parsed.reasons ?? [],
      recommendedAction: parsed.recommendedAction ?? "human_review",
    };
}

Production Considerations

  • Keep data residency explicit

    • Wealth clients often have country-specific storage requirements.
    • Pin logs, embeddings, and alert artifacts to approved regions only.
  • Write every decision to an immutable audit trail

    • Store input payloads, deterministic flags, model output, final override logic, and reviewer actions.
    • Regulators will ask why a case was escalated or cleared months later.
  • Separate model output from enforcement

    • Let AutoGen recommend; let policy code decide when sanctions hits or hard thresholds exist.
    • Never allow free-form text to directly trigger external actions like account freezes.
  • Monitor drift by segment

    • Track false positives by client tier, product type, jurisdiction, and advisor desk.
      • A private banking desk will look very different from mass affluent flows.

Common Pitfalls

  • Treating the LLM as the primary rule engine

    • Fix by putting threshold checks and sanctions logic in code first.
    • Use AutoGen for triage and explanation, not core control enforcement.
  • Skipping evidence normalization

    • If transaction fields vary across custodians or OMS systems, alerts become inconsistent.
    • Fix by enforcing one canonical schema before any agent call.
  • Ignoring reviewer usability

    • Compliance analysts need concise reasons tied to concrete facts.
    • Fix by returning structured outputs with fields like decision, riskScore, reasons, and recommendedAction, then render them in the case queue.

A good wealth-management monitoring agent is boring in the right places. Deterministic rules handle hard controls, AutoGen handles reasoning over messy context, and every output is auditable enough for compliance to defend in front of regulators.


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