OrkaJS
Orka.JS

Human-in-the-Loop (HITL)

Agents Human-in-the-Loop capable de pause pour approbation, de création de checkpoints et de modification en temps réel. Essentiel pour lesworkflows critiques.

Approbation d'Outils

Security Gate

Impose une validation manuelle stricte pour les opérations sensibles comme l'écriture en base.

Checkpoints d'État

Resilience

Persistance atomique de la mémoire de l'agent. Revenez ou reprenez depuis n'importe quelle étape.

Modification d'Actions

Supervision

Permet aux utilisateurs de modifier les paramètres d'outils en plein vol avant l'exécution.

# Utilisation de Base

Le HITLAgent étend l'agent de base avec des capacités human-in-the-loop. Configurez quels outils nécessitent une approbation et fournissez un gestionnaire d'interruption pour traiter les décisions humaines.

import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';
import { OpenAIAdapter } from '@orka-js/openai';
import type { Tool, InterruptRequest, InterruptResponse } from '@orka-js/agent';
 
const llm = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! });
 
// Define tools - some will require approval
const searchTool: Tool = {
name: 'web_search',
description: 'Search the web for information',
parameters: [{ name: 'query', type: 'string', description: 'Search query', required: true }],
execute: async (input) => ({ output: `Results for: ${input.query}` })
};
 
const sendEmailTool: Tool = {
name: 'send_email',
description: 'Send an email',
parameters: [
{ name: 'to', type: 'string', description: 'Recipient', required: true },
{ name: 'subject', type: 'string', description: 'Subject', required: true },
{ name: 'body', type: 'string', description: 'Email body', required: true }
],
execute: async (input) => ({ output: `Email sent to ${input.to}` })
};
 
const deleteFileTool: Tool = {
name: 'delete_file',
description: 'Delete a file',
parameters: [{ name: 'path', type: 'string', description: 'File path', required: true }],
execute: async (input) => ({ output: `Deleted: ${input.path}` })
};
 
// Create HITL agent
const agent = new HITLAgent(
{
goal: 'Help users with tasks while requiring approval for sensitive operations',
tools: [searchTool, sendEmailTool, deleteFileTool],
verbose: true,
hitl: {
// Tools that require human approval
requireApprovalFor: ['send_email', 'delete_file'],
// Tools that are always auto-approved
autoApproveTools: ['web_search'],
// Create checkpoint every N steps
checkpointEvery: 3,
// Timeout for human response
defaultTimeoutMs: 60000,
// Handler for interrupt requests
onInterrupt: async (request) => {
// Your approval logic here (UI, CLI, API, etc.)
console.log(`Approval needed: ${request.message}`);
return { id: request.id, status: 'approved', respondedAt: new Date() };
}
}
},
llm
);
 
const result = await agent.run('Search for AI news and email a summary to team@example.com');
 
console.log(result.output);
console.log(`Was interrupted: ${result.wasInterrupted}`);
console.log(`Interrupts: ${result.interrupts.length}`);
console.log(`Checkpoints: ${result.checkpoints.length}`);

# Gestionnaire d'Interruption

Le gestionnaire d'interruption est appelé chaque fois que l'agent a besoin d'une entrée humaine. Vous pouvez implémenter n'importe quel workflow d'approbation : prompts CLI, interface web, intégration Slack, etc.

import type { InterruptRequest, InterruptResponse } from '@orka-js/agent';
 
// Interactive CLI handler
async function cliInterruptHandler(request: InterruptRequest): Promise<InterruptResponse> {
console.log('\n' + '='.repeat(50));
console.log('🔔 HUMAN APPROVAL REQUIRED');
console.log('='.repeat(50));
console.log(`Reason: ${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)}`);
}
 
if (request.data.thought) {
console.log(`Agent reasoning: ${request.data.thought}`);
}
 
// Get user input (use readline, inquirer, etc.)
const answer = await promptUser('Approve? (y/n/m for modify): ');
 
if (answer === 'y') {
return { id: request.id, status: 'approved', respondedAt: new Date() };
} else if (answer === 'm') {
const newInput = await promptUser('Enter modified input (JSON): ');
return {
id: request.id,
status: 'modified',
modifiedData: {
toolName: request.data.toolName,
toolInput: JSON.parse(newInput)
},
respondedAt: new Date()
};
} else {
const feedback = await promptUser('Reason for rejection: ');
return {
id: request.id,
status: 'rejected',
feedback,
respondedAt: new Date()
};
}
}
 
// Web API handler (for async approval via webhooks)
async function webhookInterruptHandler(request: InterruptRequest): Promise<InterruptResponse> {
// Store pending request in database
await db.pendingApprovals.create({
id: request.id,
agentId: request.agentId,
data: request.data,
createdAt: request.createdAt
});
 
// Send notification (Slack, email, etc.)
await slack.send({
channel: '#agent-approvals',
text: `🔔 Approval needed: ${request.message}`,
actions: [
{ type: 'button', text: 'Approve', value: 'approve' },
{ type: 'button', text: 'Reject', value: 'reject' }
]
});
 
// Wait for response (with timeout)
const response = await waitForApproval(request.id, request.timeoutMs);
return response;
}

📋 Structure de la Requête d'Interruption

interface InterruptRequest {
id: string; // Unique request ID
agentId: string; // Agent that triggered the interrupt
reason: InterruptReason; // 'tool_approval' | 'checkpoint' | 'review' | 'confirmation' | 'custom'
message: string; // Human-readable message
data: {
toolName?: string; // Tool being called
toolInput?: Record<string, unknown>; // Tool parameters
stepNumber?: number; // Current step
thought?: string; // Agent's reasoning
context?: Record<string, unknown>; // Additional context
};
createdAt: Date;
timeoutMs?: number; // How long to wait for response
}

Types de Réponse

approved
Autorisation Totale

Exécution immédiate. L'agent poursuit son intention initiale.

{ status: 'approved' }
rejected
Veto Manuel

Exécution stoppée. L'agent reçoit un feedback pour replanifier.

{ status: 'rejected', feedback: '...' }
modified
Correction Guidée

Interception et pivot. Exécute l'outil avec des entrées corrigées.

{ status: 'modified', modifiedData: { ... } }
timeout
Repli Sécurisé

Suspension automatique. Évite les tâches fantômes sans révision.

{ status: 'timeout' }

# Checkpoints & Récupération

Les checkpoints sauvegardent l'état de l'agent à des intervalles spécifiques, vous permettant de reprendre l'exécution depuis n'importe quel point sauvegardé. C'est crucial pour les tâches longues et la récupération d'erreurs.

import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';
 
// Use the built-in memory store (or implement your own)
const checkpointStore = new MemoryCheckpointStore();
 
const agent = new HITLAgent(
{
goal: 'Process a large dataset',
tools: [/* ... */],
hitl: {
checkpointEvery: 5, // Save state every 5 steps
checkpointStore,
onInterrupt: myHandler
}
},
llm
);
 
// Run the agent
const result = await agent.run('Analyze all customer data');
 
// List all checkpoints
const checkpoints = await agent.getCheckpoints();
console.log('Saved checkpoints:', checkpoints.map(cp => cp.id));
 
// Resume from a specific checkpoint
const resumedResult = await agent.run('Continue analysis', checkpoints[2].id);
console.log(`Resumed from: ${resumedResult.resumedFromCheckpoint}`);

Store de Checkpoints Personnalisé

Implémentez l'interface CheckpointStore pour persister les checkpoints vers Redis, PostgreSQL, ou tout backend de stockage :

import type { Checkpoint, CheckpointStore } from '@orka-js/agent';
 
class RedisCheckpointStore implements CheckpointStore {
constructor(private redis: Redis) {}
 
async save(checkpoint: Checkpoint): Promise<void> {
await this.redis.hset(
`checkpoints:${checkpoint.agentId}`,
checkpoint.id,
JSON.stringify(checkpoint)
);
}
 
async load(checkpointId: string): Promise<Checkpoint | null> {
// Scan all agents for this checkpoint
const keys = await this.redis.keys('checkpoints:*');
for (const key of keys) {
const data = await this.redis.hget(key, checkpointId);
if (data) return JSON.parse(data);
}
return null;
}
 
async loadLatest(agentId: string): Promise<Checkpoint | null> {
const all = await this.redis.hgetall(`checkpoints:${agentId}`);
const checkpoints = Object.values(all).map(v => JSON.parse(v) as Checkpoint);
return checkpoints.sort((a, b) =>
b.createdAt.getTime() - a.createdAt.getTime()
)[0] ?? null;
}
 
async list(agentId: string): Promise<Checkpoint[]> {
const all = await this.redis.hgetall(`checkpoints:${agentId}`);
return Object.values(all).map(v => JSON.parse(v));
}
 
async delete(checkpointId: string): Promise<void> {
const keys = await this.redis.keys('checkpoints:*');
for (const key of keys) {
await this.redis.hdel(key, checkpointId);
}
}
}

# Patterns d'Approbation

Exiger l'Approbation pour Tous les Outils

hitl: {
requireApprovalFor: ['*'], // Wildcard: all tools need approval
autoApproveTools: ['web_search'], // Except these
}

Approbation pour des Outils Spécifiques Seulement

hitl: {
requireApprovalFor: ['send_email', 'delete_file', 'execute_code'],
// All other tools run without approval
}

Interruptions Manuelles

Vous pouvez également déclencher des interruptions programmatiquement pour des flux de confirmation personnalisés :

// Request confirmation before a critical action
const response = await agent.requestConfirmation(
'About to process 10,000 records. Continue?',
{ recordCount: 10000, estimatedTime: '5 minutes' }
);
 
if (response.status === 'approved') {
await processRecords();
}
 
// Request review of agent's reasoning
const reviewResponse = await agent.requestReview(
'Please review my analysis before I proceed',
currentStep,
'I believe the data shows a 15% increase...'
);

# Structure du Résultat

interface HITLAgentResult extends AgentResult {
// Standard agent result fields
input: string;
output: string;
steps: AgentStepResult[];
totalLatencyMs: number;
totalTokens: number;
toolsUsed: string[];
metadata: Record<string, unknown>;
 
// HITL-specific fields
interrupts: InterruptResponse[]; // All interrupt responses
checkpoints: string[]; // IDs of created checkpoints
wasInterrupted: boolean; // True if any interrupts occurred
resumedFromCheckpoint?: string; // Checkpoint ID if resumed
}
 
// Usage
const result = await agent.run('...');
 
if (result.wasInterrupted) {
console.log(`Agent was interrupted ${result.interrupts.length} times`);
 
for (const interrupt of result.interrupts) {
console.log(`- ${interrupt.status}: ${interrupt.feedback ?? 'No feedback'}`);
}
}
 
if (result.resumedFromCheckpoint) {
console.log(`Resumed from checkpoint: ${result.resumedFromCheckpoint}`);
}

Bonnes Pratiques

1. Classifiez les Outils par Niveau de Risque

Auto-approuvez les outils en lecture seule (recherche, récupération). Exigez une approbation pour les opérations d'écriture (envoi, suppression, modification). Exigez toujours une approbation pour les actions irréversibles.

2. Définissez des Timeouts Appropriés

Utilisez des timeouts courts pour le CLI interactif (30-60s). Utilisez des timeouts plus longs pour l'approbation asynchrone (heures/jours). Gérez le timeout gracieusement — soit réessayez, soit ignorez l'action.

3. Persistez les Checkpoints en Production

MemoryCheckpointStore est uniquement pour le développement. Utilisez Redis, PostgreSQL ou S3 pour la production. Incluez le nettoyage des checkpoints dans vos routines de maintenance.

4. Fournissez du Contexte dans les Messages d'Interruption

Incluez le raisonnement de l'agent (thought) pour que les humains comprennent pourquoi l'action est entreprise. Montrez l'entrée complète de l'outil pour que les humains puissent prendre des décisions éclairées.

Imports Tree-shakeable

// ✅ Import only HITL components
import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';
import type {
InterruptRequest,
InterruptResponse,
Checkpoint,
CheckpointStore,
HITLConfig
} from '@orka-js/agent';
 
// ✅ Or import from agent index
import {
type InterruptRequest,
type InterruptResponse,
type Checkpoint,
type CheckpointStore,
type HITLConfig,
HITLAgent,
MemoryCheckpointStore,
} from '@orka-js/agent';