OrkaJS
Orka.JS

Human-in-the-Loop Example

Complete example of HITL agent with interactive approval, checkpoints, and resume.

This example demonstrates a financial assistant that requires human approval for sensitive operations like money transfers, creates checkpoints for recovery, and allows resuming from saved states.

ORKA — HUMAN-IN-THE-LOOP WORKFLOW
👤 User Request
HITL Agent
Analyze request
Plan actions
⚠️ Sensitive Action?
Money transfer, deletion, etc.
No
Execute
Auto-run
Yes
🛑 Pause
Wait for approval
👤 Human Review
Approve/Reject/Modify
✅ Execute
If approved
Result + Checkpoint

1. Tool Definitions

tools.ts
import { Tool } from '@orka-js/agent';
 
// Tool pour vérifier le solde d'un compte
const checkBalanceTool: Tool = {
name: 'check_balance',
description: 'Vérifie le solde d'un compte bancaire',
parameters: [
{ name: 'accountId', type: 'string', description: 'ID du compte', required: true },
],
async execute(input) {
const accountId = input.accountId as string;
const balances: Record<string, number> = {
'ACC-001': 5420.50,
'ACC-002': 12890.00,
'ACC-003': 850.25,
};
const balance = balances[accountId] ?? 0;
return { output: `Solde du compte ${accountId}: ${balance}€` };
},
};
 
// Tool pour transférer de l'argent (opération sensible)
const transferMoneyTool: Tool = {
name: 'transfer_money',
description: 'Transfère de l'argent entre deux comptes',
parameters: [
{ name: 'fromAccount', type: 'string', description: 'Compte source', required: true },
{ name: 'toAccount', type: 'string', description: 'Compte destination', required: true },
{ name: 'amount', type: 'number', description: 'Montant à transférer', required: true },
],
async execute(input) {
const { fromAccount, toAccount, amount } = input;
// Simulation du transfert
const transactionId = `TXN-${Date.now()}`;
return {
output: `Transfert de ${amount}€ effectué de ${fromAccount} vers ${toAccount}. Transaction ID: ${transactionId}`
};
},
};
 
// Tool pour consulter l'historique
const getTransactionHistoryTool: Tool = {
name: 'get_transaction_history',
description: 'Récupère l'historique des transactions d'un compte',
parameters: [
{ name: 'accountId', type: 'string', description: 'ID du compte', required: true },
{ name: 'limit', type: 'number', description: 'Nombre de transactions', required: false },
],
async execute(input) {
const accountId = input.accountId as string;
const limit = (input.limit as number) ?? 5;
return {
output: `Dernières ${limit} transactions du compte ${accountId}: [Achat -45€, Virement +200€, Retrait -100€]`
};
},
};

2. Interactive Approval Handler

This handler prompts the user in the terminal for approval decisions. In production, you would integrate with your UI, Slack, email, or webhook system.

approval-handler.ts
import * as readline from 'readline';
import type { InterruptRequest, InterruptResponse } from '@orka-js/agent';
 
// Gestionnaire d'interruption interactif
async function interactiveApprovalHandler(
request: InterruptRequest
): Promise<InterruptResponse> {
console.log(`\n⚠️ INTERRUPTION REQUISE [${request.reason}]`);
console.log(`📋 Message: ${request.message}`);
 
if (request.data.toolName) {
console.log(`🔧 Tool: ${request.data.toolName}`);
console.log(`📥 Input: ${JSON.stringify(request.data.toolInput, null, 2)}`);
}
 
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
 
return new Promise((resolve) => {
rl.question(
'\n👤 Votre décision? (a=approve, r=reject, m=modify): ',
(answer) => {
rl.close();
 
const status = answer.toLowerCase();
 
if (status === 'a') {
resolve({
id: request.id,
status: 'approved',
respondedAt: new Date(),
});
} else if (status === 'r') {
resolve({
id: request.id,
status: 'rejected',
feedback: 'Opération refusée par l\'utilisateur',
respondedAt: new Date(),
});
} else if (status === 'm') {
// Exemple: réduire le montant du transfert
const modifiedInput = { ...request.data.toolInput };
if (modifiedInput.amount) {
modifiedInput.amount = (modifiedInput.amount as number) / 2;
console.log(`✏️ Montant modifié: ${modifiedInput.amount}€`);
}
 
resolve({
id: request.id,
status: 'modified',
modifiedData: { toolInput: modifiedInput },
feedback: 'Montant réduit de moitié',
respondedAt: new Date(),
});
} else {
resolve({
id: request.id,
status: 'rejected',
feedback: 'Réponse invalide',
respondedAt: new Date(),
});
}
}
);
});
}

3. HITL Agent Setup

agent-setup.ts
import { OpenAIAdapter } from '@orka-js/openai';
import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';
 
// Créer le checkpoint store
const checkpointStore = new MemoryCheckpointStore();
 
// Créer l'agent HITL
const financialAgent = new HITLAgent(
{
name: 'financial-assistant',
goal: 'Aider l\'utilisateur avec ses opérations bancaires',
tools: [checkBalanceTool, transferMoneyTool, getTransactionHistoryTool],
maxSteps: 10,
temperature: 0.2,
verbose: true,
hitl: {
// Nécessite approbation pour les transferts d'argent
requireApprovalFor: ['transfer_money'],
 
// Auto-approuve les consultations (non sensibles)
autoApproveTools: ['check_balance', 'get_transaction_history'],
 
// Crée un checkpoint tous les 2 steps
checkpointEvery: 2,
 
// Timeout de 5 minutes pour les approbations
defaultTimeoutMs: 5 * 60 * 1000,
 
// Gestionnaire d'interruption
onInterrupt: interactiveApprovalHandler,
 
// Store de checkpoints
checkpointStore,
},
},
new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! })
);

4. Event Listeners

event-listeners.ts
// Écouter les événements de l'agent
financialAgent.on('step:start', (event) => {
console.log(`\n🔄 Step ${event.stepNumber} démarré`);
});
 
financialAgent.on('step:complete', (event) => {
console.log(`✅ Step ${event.stepNumber} terminé`);
if (event.toolUsed) {
console.log(` Tool utilisé: ${event.toolUsed}`);
}
});
 
financialAgent.on('checkpoint:created', (event) => {
console.log(`💾 Checkpoint créé: ${event.checkpointId}`);
});
 
financialAgent.on('interrupt:requested', (event) => {
console.log(`⏸️ Interruption demandée: ${event.reason}`);
});
 
financialAgent.on('interrupt:resolved', (event) => {
console.log(`▶️ Interruption résolue: ${event.status}`);
});
 
financialAgent.on('complete', (event) => {
console.log(`\n🎉 Agent terminé!`);
console.log(` Steps: ${event.totalSteps}`);
console.log(` Interruptions: ${event.interruptCount}`);
});

5. Running the Agent

main.ts
async function main() {
console.log('🤖 Assistant Financier HITL\n');
 
try {
// Scénario 1: Transfert d'argent (nécessite approbation)
console.log('=== Scénario 1: Transfert d\'argent ===');
const result1 = await financialAgent.run(
'Transfère 500€ de mon compte ACC-001 vers ACC-002'
);
 
console.log('\n📊 Résultat:');
console.log('Réponse:', result1.output);
console.log('Steps:', result1.steps.length);
console.log('Interruptions:', result1.interrupts.length);
console.log('Interrompu?', result1.wasInterrupted);
 
// Afficher les détails des interruptions
if (result1.interrupts.length > 0) {
console.log('\nDétails des interruptions:');
result1.interrupts.forEach((interrupt, i) => {
console.log(` ${i + 1}. Status: ${interrupt.status}`);
if (interrupt.feedback) {
console.log(` Feedback: ${interrupt.feedback}`);
}
});
}
 
// Afficher les checkpoints créés
if (result1.checkpoints.length > 0) {
console.log('\n💾 Checkpoints créés:', result1.checkpoints);
}
 
} catch (error) {
console.error('❌ Erreur:', error);
}
}
 
main().catch(console.error);

6. Resume from Checkpoint

If the agent is interrupted or crashes, you can resume from the last checkpoint:

resume.ts
async function resumeFromCheckpoint() {
// Récupérer tous les checkpoints de l'agent
const checkpoints = await financialAgent.getCheckpoints();
 
if (checkpoints.length === 0) {
console.log('Aucun checkpoint disponible');
return;
}
 
// Afficher les checkpoints disponibles
console.log('\n💾 Checkpoints disponibles:');
checkpoints.forEach((cp, i) => {
console.log(` ${i + 1}. ${cp.id} - Step ${cp.stepNumber} - ${cp.createdAt}`);
});
 
// Reprendre depuis le dernier checkpoint
const latestCheckpoint = checkpoints[checkpoints.length - 1];
console.log(`\n▶️ Reprise depuis: ${latestCheckpoint.id}`);
 
const result = await financialAgent.run(
'Continue l\'opération précédente',
latestCheckpoint.id
);
 
console.log('\n✅ Opération reprise avec succès');
console.log('Repris depuis checkpoint:', result.resumedFromCheckpoint);
console.log('Résultat:', result.output);
}
 
// Utilisation
resumeFromCheckpoint().catch(console.error);

7. Manual Interrupts

You can also request manual confirmations or reviews during agent execution:

manual-interrupts.ts
// Dans votre tool ou logique métier
const complexOperationTool: Tool = {
name: 'complex_operation',
description: 'Effectue une opération complexe',
parameters: [
{ name: 'data', type: 'object', description: 'Données', required: true },
],
async execute(input, context) {
// Demander une confirmation avant de continuer
const confirmation = await context.agent.requestConfirmation(
'Cette opération est irréversible. Continuer?',
{ operation: 'complex_operation', data: input.data }
);
 
if (confirmation.status !== 'approved') {
return { output: 'Opération annulée par l\'utilisateur' };
}
 
// Effectuer l'opération
const result = performComplexOperation(input.data);
 
// Demander une revue du résultat
const review = await context.agent.requestReview(
'Veuillez vérifier le résultat',
context.stepNumber,
'Opération complexe terminée'
);
 
if (review.status === 'rejected') {
return { output: 'Résultat rejeté, opération annulée' };
}
 
return { output: `Opération réussie: ${result}` };
},
};

8. Complete Example Output

output.txt
🤖 Assistant Financier HITL
 
=== Scénario 1: Transfert d'argent ===
 
🔄 Step 1 démarré
Step 1 terminé
Tool utilisé: check_balance
 
💾 Checkpoint créé: cp-1234567890
 
🔄 Step 2 démarré
 
⚠️ INTERRUPTION REQUISE [tool_approval]
📋 Message: Approbation requise pour: transfer_money
🔧 Tool: transfer_money
📥 Input: {
"fromAccount": "ACC-001",
"toAccount": "ACC-002",
"amount": 500
}
 
👤 Votre décision? (a=approve, r=reject, m=modify): a
 
▶️ Interruption résolue: approved
Step 2 terminé
Tool utilisé: transfer_money
 
💾 Checkpoint créé: cp-1234567891
 
🔄 Step 3 démarré
Step 3 terminé
 
🎉 Agent terminé!
Steps: 3
Interruptions: 1
 
📊 Résultat:
Réponse: J'ai effectué le transfert de 500de votre compte ACC-001 vers ACC-002. Transaction ID: TXN-1234567890
Steps: 3
Interruptions: 1
Interrompu? true
 
Détails des interruptions:
1. Status: approved
 
💾 Checkpoints créés: ['cp-1234567890', 'cp-1234567891']

💡 Best Practices

  • Security: Always require approval for sensitive operations (money transfers, data deletion, external API calls)
  • Checkpoints: Create checkpoints frequently for long-running operations to enable recovery
  • Timeouts: Set appropriate timeouts for approvals to avoid blocking indefinitely
  • Production: In production, integrate with your UI, Slack, webhooks, or notification system instead of terminal prompts
  • Persistence: Use RedisCheckpointStore or PostgresCheckpointStore for production instead of MemoryCheckpointStore

🚀 Next Steps

  • Explore the HITL documentation for advanced patterns
  • Implement custom CheckpointStore for your database
  • Integrate approval workflows with your existing systems
  • Add monitoring and observability for HITL events