BGV Agent Implementation
The BGV (Background Verification) Agent is a complete reference implementation showing how to build a multi-state workflow agent in Hexabot.
Overview
The BGV Agent handles the complete background verification workflow:
- ✅ User initiates BGV request
- ✅ Agent requests PAN card upload
- ✅ User uploads via modal
- ✅ Webserver processes OCR
- ✅ Agent generates payment link
- ✅ User completes payment
- ✅ Webserver receives payment webhook
- ✅ Agent starts verification process
- ✅ Webserver calls third-party BGV provider
- ✅ Agent delivers verification report
File Structure
modules/agents/bgv/
├── index.ts # Main BGVAgent class
├── agent.config.ts # Agent configuration & metadata
├── agent.state-machine.ts # State machine logic
├── agent.message-builder.ts # Message templates
├── agent.service.ts # Webserver API client
└── bgv.module.ts # NestJS module definition
Agent Configuration
agent.config.ts:
import { AgentConfig } from '../../agent-orchestrator/types';
export const BGV_AGENT_CONFIG: AgentConfig = {
id: 'bgv',
name: 'Background Verification',
description: 'Handles employee background verification process',
capabilities: [
'pan_verification',
'address_verification',
'employment_verification',
'education_verification'
],
triggerKeywords: [
'bgv',
'background',
'verification',
'verify',
'background check'
],
channelPrefix: 'prophunt_bgv'
};
State Machine
agent.state-machine.ts:
export enum BGVState {
INIT = 'INIT',
PAN_UPLOAD_REQUESTED = 'PAN_UPLOAD_REQUESTED',
PAN_UPLOADED = 'PAN_UPLOADED',
AWAITING_PAYMENT = 'AWAITING_PAYMENT',
PAYMENT_COMPLETED = 'PAYMENT_COMPLETED',
VERIFICATION_IN_PROGRESS = 'VERIFICATION_IN_PROGRESS',
REPORT_READY = 'REPORT_READY',
COMPLETED = 'COMPLETED',
ERROR = 'ERROR'
}
export class BGVStateMachine {
/**
* Get current state from context
*/
getCurrentState(context: ConversationContext): BGVState {
return (context.state as BGVState) || BGVState.INIT;
}
/**
* Transition to new state
*/
transition(
currentState: BGVState,
newState: BGVState
): boolean {
const validTransitions: Record<BGVState, BGVState[]> = {
[BGVState.INIT]: [BGVState.PAN_UPLOAD_REQUESTED],
[BGVState.PAN_UPLOAD_REQUESTED]: [BGVState.PAN_UPLOADED, BGVState.ERROR],
[BGVState.PAN_UPLOADED]: [BGVState.AWAITING_PAYMENT],
[BGVState.AWAITING_PAYMENT]: [BGVState.PAYMENT_COMPLETED, BGVState.ERROR],
[BGVState.PAYMENT_COMPLETED]: [BGVState.VERIFICATION_IN_PROGRESS],
[BGVState.VERIFICATION_IN_PROGRESS]: [BGVState.REPORT_READY, BGVState.ERROR],
[BGVState.REPORT_READY]: [BGVState.COMPLETED],
[BGVState.COMPLETED]: [],
[BGVState.ERROR]: [BGVState.INIT]
};
const allowed = validTransitions[currentState] || [];
return allowed.includes(newState);
}
/**
* Validate if state is valid
*/
isValidState(state: string): boolean {
return Object.values(BGVState).includes(state as BGVState);
}
}
Message Builder
agent.message-builder.ts:
import { Injectable } from '@nestjs/common';
import {
MessageEnvelope,
MessageType,
ButtonType,
CustomPayloadType
} from '../../agent-orchestrator/types';
@Injectable()
export class BGVMessageBuilder {
buildWelcomeMessage(): MessageEnvelope {
return {
type: MessageType.TEXT,
text: '👋 Welcome to Prophunt Background Verification! I\'ll guide you through the process.'
};
}
buildPANUploadRequest(taskId: string): MessageEnvelope {
return {
type: MessageType.CUSTOM,
custom_payload: {
type: CustomPayloadType.OPEN_MODAL,
modal_id: 'pan_upload_modal',
data: { taskId }
}
};
}
buildPANUploadedConfirmation(): MessageEnvelope {
return {
type: MessageType.TEXT,
text: '✅ PAN card uploaded & verified successfully!'
};
}
buildPaymentRequest(paymentUrl: string, amount: string): MessageEnvelope {
return {
type: MessageType.BUTTONS,
text: `To proceed with verification, please complete the payment of ₹${amount}`,
buttons: [
{
type: ButtonType.WEB_URL,
title: 'Pay Now',
url: paymentUrl
}
]
};
}
buildPaymentConfirmation(): MessageEnvelope {
return {
type: MessageType.TEXT,
text: '✅ Payment received! Starting verification process...'
};
}
buildVerificationInProgress(): MessageEnvelope {
return {
type: MessageType.TEXT,
text: '🔄 Your verification is in progress. This typically takes 2-3 business days. We\'ll notify you once complete.'
};
}
buildStatusMessage(context: ConversationContext): MessageEnvelope {
return {
type: MessageType.TEXT,
text: `📊 Current Status: ${context.state}\nTask ID: ${context.taskId}`
};
}
buildHelpMessage(): MessageEnvelope {
return {
type: MessageType.TEXT,
text: `🆘 BGV Help:
• Type "status" to check your verification status
• Type "help" to see this message
• Contact support: support@prophunt.com`
};
}
buildErrorMessage(error: string): MessageEnvelope {
return {
type: MessageType.TEXT,
text: `❌ Error: ${error}`
};
}
}
Service Layer
agent.service.ts:
import { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class BGVService {
private readonly prophuntApiUrl =
process.env.PROPHUNT_API_URL || 'http://localhost:3000/api';
async getBGVStatus(taskId: string): Promise<any> {
const response = await axios.get(
`${this.prophuntApiUrl}/bgv/status/${taskId}`
);
return response.data;
}
async notifyPANUpload(taskId: string, fileUrl: string): Promise<any> {
const response = await axios.post(
`${this.prophuntApiUrl}/bgv/pan-upload`,
{ taskId, fileUrl }
);
return response.data;
}
async getPaymentLink(taskId: string): Promise<string> {
const response = await axios.post(
`${this.prophuntApiUrl}/bgv/payment-link`,
{ taskId }
);
return response.data.paymentUrl;
}
async verifyPayment(taskId: string): Promise<boolean> {
const response = await axios.get(
`${this.prophuntApiUrl}/bgv/payment-status/${taskId}`
);
return response.data.paid === true;
}
async startVerification(taskId: string): Promise<any> {
const response = await axios.post(
`${this.prophuntApiUrl}/bgv/start-verification`,
{ taskId }
);
return response.data;
}
async getVerificationReport(taskId: string): Promise<any> {
const response = await axios.get(
`${this.prophuntApiUrl}/bgv/report/${taskId}`
);
return response.data;
}
}
Main Agent Implementation
index.ts:
import { Injectable } from '@nestjs/common';
import {
Agent,
AgentEvent,
AgentEventType,
ChannelMessage,
ConversationContext,
MessageEnvelope
} from '../../agent-orchestrator/types';
import { BGV_AGENT_CONFIG } from './agent.config';
import { BGVStateMachine, BGVState } from './agent.state-machine';
import { BGVService } from './agent.service';
import { BGVMessageBuilder } from './agent.message-builder';
@Injectable()
export class BGVAgent implements Agent {
config = BGV_AGENT_CONFIG;
private stateMachine = new BGVStateMachine();
constructor(
private readonly service: BGVService,
private readonly messageBuilder: BGVMessageBuilder
) {}
/**
* Process incoming message
*/
async processMessage(
message: ChannelMessage,
context: ConversationContext
): Promise<MessageEnvelope[]> {
const currentState = this.stateMachine.getCurrentState(context);
const text = message.text?.toLowerCase() || '';
// Handle commands
if (text.includes('help')) {
return [this.messageBuilder.buildHelpMessage()];
}
if (text.includes('status') || text.includes('check')) {
return [this.messageBuilder.buildStatusMessage(context)];
}
// State-based responses
switch (currentState) {
case BGVState.INIT:
return this.handleInit(message, context);
case BGVState.PAN_UPLOADED:
return this.handlePANUploaded(context);
case BGVState.PAYMENT_COMPLETED:
return this.handlePaymentCompleted(context);
default:
return [this.messageBuilder.buildHelpMessage()];
}
}
/**
* Handle initialization state
*/
private async handleInit(
message: ChannelMessage,
context: ConversationContext
): Promise<MessageEnvelope[]> {
const messages: MessageEnvelope[] = [
this.messageBuilder.buildWelcomeMessage()
];
const taskId = context.taskId || `bgv_${Date.now()}`;
messages.push(this.messageBuilder.buildPANUploadRequest(taskId));
return messages;
}
/**
* Handle PAN uploaded state
*/
private async handlePANUploaded(
context: ConversationContext
): Promise<MessageEnvelope[]> {
const messages: MessageEnvelope[] = [
this.messageBuilder.buildPANUploadedConfirmation()
];
try {
const paymentUrl = await this.service.getPaymentLink(
context.taskId || ''
);
messages.push(this.messageBuilder.buildPaymentRequest(paymentUrl, '499'));
} catch (error) {
messages.push(
this.messageBuilder.buildErrorMessage('Failed to generate payment link')
);
}
return messages;
}
/**
* Handle payment completed state
*/
private async handlePaymentCompleted(
context: ConversationContext
): Promise<MessageEnvelope[]> {
const messages: MessageEnvelope[] = [
this.messageBuilder.buildPaymentConfirmation()
];
try {
await this.service.startVerification(context.taskId || '');
messages.push(this.messageBuilder.buildVerificationInProgress());
} catch (error) {
messages.push(
this.messageBuilder.buildErrorMessage('Failed to start verification')
);
}
return messages;
}
/**
* Handle events (webhooks, file uploads, etc.)
*/
async handleEvent(
event: AgentEvent,
context: ConversationContext
): Promise<void> {
const currentState = this.stateMachine.getCurrentState(context);
switch (event.type) {
case AgentEventType.FILE_UPLOADED:
await this.handleFileUploadEvent(event, context, currentState);
break;
case AgentEventType.PAYMENT_COMPLETED:
await this.handlePaymentCompletedEvent(event, context, currentState);
break;
case AgentEventType.WEBHOOK_RECEIVED:
await this.handleWebhookEvent(event, context, currentState);
break;
}
}
/**
* Handle file upload event
*/
private async handleFileUploadEvent(
event: AgentEvent,
context: ConversationContext,
currentState: BGVState
): Promise<void> {
if (currentState === BGVState.PAN_UPLOAD_REQUESTED) {
await this.service.notifyPANUpload(
context.taskId || '',
event.data?.url || event.data?.fileUrl
);
}
}
/**
* Handle payment completed event
*/
private async handlePaymentCompletedEvent(
event: AgentEvent,
context: ConversationContext,
currentState: BGVState
): Promise<void> {
if (currentState === BGVState.AWAITING_PAYMENT) {
const isPaid = await this.service.verifyPayment(context.taskId || '');
if (isPaid) {
await this.service.startVerification(context.taskId || '');
}
}
}
/**
* Handle webhook event
*/
private async handleWebhookEvent(
event: AgentEvent,
context: ConversationContext,
currentState: BGVState
): Promise<void> {
const webhookType = event.data?.eventType;
switch (webhookType) {
case 'VERIFICATION_COMPLETED':
// Handled by webserver calling /agent-api/send-message
break;
case 'PAYMENT_RECEIVED':
// Handled by webserver calling /agent-api/send-message
break;
}
}
/**
* Get current state
*/
getState(context: ConversationContext): string {
return this.stateMachine.getCurrentState(context);
}
/**
* Transition to new state
*/
async transition(
from: string,
to: string,
context: ConversationContext
): Promise<ConversationContext> {
const fromState = from as BGVState;
const toState = to as BGVState;
if (!this.stateMachine.isValidState(to)) {
throw new Error(`Invalid state: ${to}`);
}
return {
...context,
state: toState
};
}
/**
* Check if agent can handle message
*/
canHandle(message: ChannelMessage): boolean {
const text = message.text?.toLowerCase() || '';
return this.config.triggerKeywords.some(keyword =>
text.includes(keyword.toLowerCase())
);
}
}
Complete BGV Flow
┌─────────────────────────────────────────────────────────────────────┐
│ 1. User: "I want BGV" │
│ → BGVAgent.processMessage() │
│ → State: INIT → PAN_UPLOAD_REQUESTED │
│ → Response: "Upload PAN" + OPEN_MODAL │
└──────────────────────────────────────────┬──────────────────────────┘
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 2. User uploads PAN via modal │
│ → Frontend: socket.emit('event', { type: 'FILE_UPLOADED' }) │
│ → ChatGateway → EventHandler → EventBus.emit('FILE_UPLOADED') │
│ → BGVAgent.handleFileUploadEvent() │
│ → BGVService.notifyPANUpload() → Webserver HTTP POST │
│ → State: PAN_UPLOAD_REQUESTED → PAN_UPLOADED │
└──────────────────────────────────────────┬──────────────────────────┘
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 3. Webserver processes OCR, stores PAN data │
│ → Webserver: POST /agent-api/send-message │
│ { messageType: 'doc_uploaded' } │
│ → ConversationManager.sendMessage() │
│ → User receives: "✅ PAN uploaded" │
│ → BGVAgent.handlePANUploaded() │
│ → BGVService.getPaymentLink() → Webserver │
│ → Response: Payment button with URL │
│ → State: PAN_UPLOADED → AWAITING_PAYMENT │
└──────────────────────────────────────────┬──────────────────────────┘
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 4. User clicks "Pay Now", completes payment │
│ → Payment Gateway → Webhook → Webserver │
│ → Webserver: POST /agent-api/send-message │
│ { messageType: 'payment_success', data: { amount: 499 } } │
│ → User receives: "✅ Payment received" │
│ → State: AWAITING_PAYMENT → PAYMENT_COMPLETED │
└──────────────────────────────────────────┬──────────────────────────┘
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 5. BGVAgent.handlePaymentCompleted() │
│ → BGVService.startVerification() → Webserver │
│ → Webserver calls third-party BGV service │
│ → Response: "🔄 Verification in progress" │
│ → State: PAYMENT_COMPLETED → VERIFICATION_IN_PROGRESS │
└──────────────────────────────────────────┬──────────────────────────┘
│
(Wait 2-3 days)
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 6. BGV Report ready (external service → webserver) │
│ → Webserver: POST /agent-api/send-message │
│ { messageType: 'verification_completed', │
│ data: { reportUrl: 'https://...' } } │
│ → User receives button: "View Report" │
│ → State: VERIFICATION_IN_PROGRESS → REPORT_READY │
└──────────────────────────────────────────┬──────────────────────────┘
│
┌───────────────────────────────────────────▼──────────────────────────┐
│ 7. User clicks "View Report" │
│ → Opens report URL │
│ → State: REPORT_READY → COMPLETED │
└───────────────────────────────────────────────────────────────────────┘
Key Takeaways
✅ State machine drives workflow - Clear progression through states
✅ Message builder encapsulates UI - Consistent formatting
✅ Service layer isolates API calls - Clean separation of concerns
✅ Event handling for async operations - File uploads, payments
✅ External system integration - Webserver as business logic layer
Testing
See Testing Agents for unit and integration test examples.
Next Steps
- Read How to Add a New Agent to create your own
- Understand Webserver Integration for API details
- Learn Frontend Integration for UI implementation