How to Build a transaction monitoring Agent Using CrewAI in TypeScript for pension funds

By Cyprian AaronsUpdated 2026-04-21
transaction-monitoringcrewaitypescriptpension-funds

A transaction monitoring agent for pension funds watches contributions, transfers, withdrawals, and settlement activity for patterns that look wrong: duplicate payments, rapid movement between accounts, suspiciously large redemptions, and rule breaches tied to member eligibility. It matters because pension operations sit under heavy compliance pressure, and the cost of missing a bad transaction is not just financial loss — it is audit findings, regulatory escalation, and member trust damage.

Architecture

  • Transaction ingestion layer

    • Pulls transactions from core pension admin systems, payment rails, or a warehouse table.
    • Normalizes records into a single schema: member ID, plan ID, amount, currency, timestamp, channel, counterparty.
  • Rules and policy context

    • Encodes pension-specific checks:
      • contribution caps
      • early withdrawal restrictions
      • duplicate disbursements
      • unusual beneficiary changes before payout
    • Stores rules outside the agent so compliance can update them without redeploying code.
  • CrewAI analysis crew

    • Uses Agent, Task, and Crew to orchestrate specialized roles:
      • triage agent
      • compliance analyst agent
      • case summarizer agent
    • Produces structured outputs instead of free-form chat.
  • Evidence store

    • Persists the raw transaction payload, rule hits, model output, and final disposition.
    • Required for audit trails and post-review by compliance teams.
  • Case management integration

    • Opens a case in ServiceNow, Jira, or an internal queue when risk exceeds threshold.
    • Sends only the minimum necessary data to keep residency and privacy constraints intact.
  • Human review gate

    • Keeps high-risk alerts out of auto-action paths.
    • Lets operations staff approve holds, reversals, or escalation.

Implementation

  1. Install dependencies and define the transaction schema

Use the TypeScript CrewAI package plus a validator like Zod for clean inputs. Keep the schema tight; pension data is not the place for loosely typed objects.

npm install @crewai/crewai zod dotenv
import 'dotenv/config';
import { z } from 'zod';

export const TransactionSchema = z.object({
  transactionId: z.string(),
  memberId: z.string(),
  planId: z.string(),
  type: z.enum(['contribution', 'withdrawal', 'transfer', 'benefit_payment']),
  amount: z.number().positive(),
  currency: z.string().length(3),
  timestamp: z.string(),
  channel: z.enum(['bank_transfer', 'card', 'cheque', 'internal']),
  destinationCountry: z.string().optional(),
});

export type Transaction = z.infer<typeof TransactionSchema>;
  1. Create agents with narrow responsibilities

Do not build one giant “monitoring agent.” Split triage from compliance reasoning. That gives you better prompts, better traces, and easier audit review.

import { Agent } from '@crewai/crewai';

export const triageAgent = new Agent({
  role: 'Transaction Triage Analyst',
  goal: 'Identify whether a pension transaction should be flagged for review',
  backstory:
    'You review pension transactions against policy rules and highlight anomalies with concise evidence.',
});

export const complianceAgent = new Agent({
  role: 'Pension Compliance Analyst',
  goal: 'Assess flagged transactions against pension fund rules and regulatory constraints',
  backstory:
    'You understand contribution limits, withdrawal restrictions, audit requirements, and escalation criteria.',
});
  1. Define tasks that force structured output

The key pattern is to ask for JSON-like output that downstream systems can parse. In production you want deterministic fields such as riskScore, decision, reasons, and recommendedAction.

import { Task } from '@crewai/crewai';
import { triageAgent, complianceAgent } from './agents';
import type { Transaction } from './schema';

export function buildTasks(txn: Transaction) {
  const triageTask = new Task({
    description: `
Review this pension transaction for suspicious patterns:
${JSON.stringify(txn)}

Return:
- riskScore (0-100)
- decision (clear | flag | escalate)
- reasons (array)
`,
    expectedOutput: 'Structured assessment with risk score and reasons',
    agent: triageAgent,
  });

  const complianceTask = new Task({
    description: `
Validate the flagged transaction against pension fund controls.
Focus on compliance impact, auditability, data residency concerns,
and whether a human reviewer must approve action.

Transaction:
${JSON.stringify(txn)}
`,
    expectedOutput:
      'Compliance assessment with disposition and required follow-up',
    agent: complianceAgent,
    context: [triageTask],
  });

  return [triageTask, complianceTask];
}
  1. Run the crew and persist the result

This is where Crew ties everything together. The result should be written to your case store before any operational action is taken.

import { Crew } from '@crewai/crewai';
import { TransactionSchema } from './schema';
import { buildTasks } from './tasks';

async function main() {
  const rawTxn = {
    transactionId: 'txn_1001',
    memberId: 'mem_4421',
    planId: 'pension_gold_01',
    type: 'withdrawal',
    amount: 95000,
    currency: 'USD',
    timestamp: new Date().toISOString(),
    channel: 'bank_transfer',
    destinationCountry: 'US',
  };

  const txn = TransactionSchema.parse(rawTxn);
  const tasks = buildTasks(txn);

  const crew = new Crew({
    agents: tasks.map((t) => t.agent!),
    tasks,
    verbose: true,
  });

  const result = await crew.kickoff();
  console.log(JSON.stringify(result));
}

main().catch(console.error);

Production Considerations

  • Keep data residency explicit

    • Run model inference in-region if your fund operates under local residency rules.
    • Do not send full member profiles to external tools unless legal has approved the processing boundary.
  • Store every decision path

    • Persist input payloads, rule hits, prompt version hashes, task outputs, and reviewer actions.
    • Auditors will ask why a withdrawal was flagged; “the model said so” is not acceptable.
  • Add hard guardrails before any action

    • The agent should never auto-reverse or freeze funds.
    • Use it to recommend actions only; an internal policy engine or human approver executes them.
  • Monitor false positives by transaction type

    • Pension contributions often have different risk patterns than benefit payments.
    • Track alert rates separately for each class so you do not drown operations in noise.

Common Pitfalls

  • Using one generic prompt for every transaction

    This produces vague alerts and poor explanations. Split logic by transaction type so withdrawals are judged against different controls than employer contributions.

  • Skipping immutable audit logging

    If you only log the final score, you lose the evidence trail. Save raw input, task output, model version, timestamp, and reviewer disposition in append-only storage.

  • Letting the agent make execution decisions

    A monitoring agent should detect and explain risk, not move money or block accounts on its own. Put a deterministic approval layer between alert generation and any operational change.


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