Human-in-the-Loop (HITL) Security Guide for NeuroLink
Build production-ready AI with Human-in-the-Loop (HITL) tool confirmation workflows using NeuroLink's event-based safety system.
You will add Human-in-the-Loop (HITL) safety to your AI applications so dangerous operations like file deletions, database writes, and email sends require human approval before executing. By the end of this guide, you will have event-based confirmation workflows, configurable triggers, argument modification support, and audit logging – all configured in minutes.
flowchart TB
START(["AI Request"]) --> TOOL
subgraph TOOL["Tool Execution Check"]
CHECK{"Requires Confirmation?"}
KEYWORDS["Check Dangerous Keywords"]
RULES["Check Custom Rules"]
end
subgraph HITL["HITL Confirmation Flow"]
EMIT["Emit confirmation-request Event"]
WAIT["Wait for User Response"]
TIMEOUT{"Timeout?"}
RESPONSE{"User Decision"}
end
subgraph OUTCOME["Execution Outcome"]
EXECUTE["Execute Tool"]
REJECT["Reject Execution"]
AUTO["Auto-approve/Reject"]
AUDIT["Audit Log"]
end
START --> CHECK
CHECK -->|"No"| EXECUTE
CHECK -->|"Yes"| KEYWORDS
KEYWORDS --> RULES
RULES --> EMIT
EMIT --> WAIT
WAIT --> TIMEOUT
TIMEOUT -->|"Yes"| AUTO
TIMEOUT -->|"No"| RESPONSE
RESPONSE -->|"Approved"| EXECUTE
RESPONSE -->|"Rejected"| REJECT
EXECUTE --> AUDIT
REJECT --> AUDIT
AUTO --> AUDIT
style START fill:#3b82f6,stroke:#2563eb,color:#fff
style CHECK fill:#f59e0b,stroke:#d97706,color:#fff
style EMIT fill:#8b5cf6,stroke:#7c3aed,color:#fff
style WAIT fill:#8b5cf6,stroke:#7c3aed,color:#fff
style EXECUTE fill:#22c55e,stroke:#16a34a,color:#fff
style REJECT fill:#ef4444,stroke:#dc2626,color:#fff
style AUDIT fill:#64748b,stroke:#475569,color:#fff
Why enterprise AI needs HITL
When your AI can delete files, execute code, or send communications, human oversight prevents costly mistakes. Here is why you need HITL and when to use it.
Risk mitigation
Without HITL safeguards, enterprises face:
- Data Loss: Accidental file or database deletions
- Security Breaches: Unauthorized code execution
- Compliance Violations: Unreviewed actions in regulated domains
- Reputational Damage: Automated communications sent without review
When to use HITL
Require confirmation for:
- File deletion or modification operations
- Database write/delete operations
- Code execution in any environment
- Sending emails or messages
- Making purchases or payments
- Modifying production systems
Skip confirmation for:
- Read-only operations (fetching data, searching)
- Content generation without external effects
- Development/testing environments with isolated data
Quick start: your first HITL configuration
You will start with a basic HITL configuration and build up from there:
1
2
3
4
5
6
7
8
9
10
11
12
13
import { NeuroLink } from "@juspay/neurolink";
// Configure HITL in the constructor
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete", "remove", "drop", "truncate", "kill"],
timeout: 30000, // 30 seconds for user to respond
allowArgumentModification: false, // Let users edit tool arguments (default: false)
autoApproveOnTimeout: false, // Reject if user doesn't respond
auditLogging: true, // Enable audit trail for compliance
},
});
Configuration Summary:
| Setting | Value | Description |
|---|---|---|
enabled | true | Master switch for HITL functionality |
dangerousActions | ["delete", ...] | Keywords that trigger confirmation |
timeout | 30000 | Milliseconds to wait for user response |
allowArgumentModification | false | Users can modify tool arguments during approval (default: false) |
autoApproveOnTimeout | false | Reject operations when timeout occurs |
auditLogging | true | Log all HITL events for compliance |
Event-based confirmation workflow
Next, you will set up event listeners to handle confirmation requests. When a dangerous tool is about to execute, the SDK emits a confirmation request that your app handles.
Setting up event listeners
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { NeuroLink } from "@juspay/neurolink";
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete", "remove", "drop"],
timeout: 30000,
},
});
// Listen for confirmation requests
neurolink.getEventEmitter().on("hitl:confirmation-request", async (event) => {
const {
confirmationId,
toolName,
arguments: args,
timeoutMs,
allowModification,
} = event.payload;
console.log(`HITL: AI wants to execute ${toolName}`);
console.log(`Arguments:`, JSON.stringify(args, null, 2));
// Show your application's confirmation UI
const userDecision = await showConfirmationDialog({
action: toolName,
details: args,
message: `AI wants to ${toolName}. Allow this action?`,
timeoutMs,
allowModification,
});
// Send response back to NeuroLink
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId, // Must match the request
approved: userDecision.approved,
reason: userDecision.approved ? undefined : userDecision.reason,
modifiedArguments: userDecision.modifiedArgs,
metadata: {
timestamp: new Date().toISOString(),
responseTime: Date.now(),
userId: "current-user-id",
},
},
});
});
// Handle timeouts
neurolink.getEventEmitter().on("hitl:timeout", (event) => {
console.warn(`HITL timeout for ${event.payload.toolName}`);
// Notify user that the operation was not completed
});
Complete flow diagram
sequenceDiagram
participant App as Your Application
participant NL as NeuroLink SDK
participant AI as AI Provider
participant User as Human Reviewer
App->>NL: neurolink.generate({ input: { text: "..." } })
NL->>AI: Send prompt with tools
AI->>NL: Tool call: deleteFile({ path: "/data/old.csv" })
Note over NL: Check if tool requires confirmation
NL->>NL: "delete" keyword detected
NL-->>App: emit("hitl:confirmation-request", event)
App->>User: Show confirmation dialog
User->>App: Approve/Reject decision
App->>NL: emit("hitl:confirmation-response", response)
alt Approved
NL->>NL: Execute tool
NL->>AI: Return tool result
AI->>NL: Generate final response
NL->>App: Return result
else Rejected
NL->>AI: Tool execution denied
AI->>NL: Handle rejection gracefully
NL->>App: Return with rejection info
end
Confirmation request event structure
Here is the full structure of the event your listeners receive:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Event payload structure
interface ConfirmationRequestEvent {
type: "hitl:confirmation-request";
payload: {
// Unique ID to match with response
confirmationId: string;
// Tool information
toolName: string;
serverId?: string; // MCP server ID if external tool
actionType: string; // Human-readable description
// Tool parameters for review
arguments: unknown;
// Context metadata
metadata: {
timestamp: string; // ISO timestamp
sessionId?: string;
userId?: string;
dangerousKeywords: string[]; // Keywords that triggered HITL
};
// Confirmation settings
timeoutMs: number;
allowModification: boolean;
};
}
Example request event
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"type": "hitl:confirmation-request",
"payload": {
"confirmationId": "hitl-1705312800000-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"toolName": "deleteFile",
"actionType": "Delete Operation",
"arguments": {
"path": "/data/customer-records.csv"
},
"metadata": {
"timestamp": "2026-01-15T10:00:00.000Z",
"sessionId": "sess_abc123",
"userId": "user_456",
"dangerousKeywords": ["delete"]
},
"timeoutMs": 30000,
"allowModification": false
}
}
Confirmation response structure
Your application responds with approval or rejection:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Response payload structure
interface ConfirmationResponseEvent {
type: "hitl:confirmation-response";
payload: {
// Must match the request
confirmationId: string;
// User decision
approved: boolean;
reason?: string; // Required if rejected
// Modified arguments (if user edited them)
modifiedArguments?: unknown;
// Response metadata
metadata: {
timestamp: string;
responseTime: number; // Milliseconds
userId?: string;
};
};
}
Example response events
Approval:
1
2
3
4
5
6
7
8
9
10
11
12
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId: "hitl-1705312800000-a1b2c3d4...",
approved: true,
metadata: {
timestamp: new Date().toISOString(),
responseTime: 5200, // User took 5.2 seconds
userId: "[email protected]",
},
},
});
Rejection with reason:
1
2
3
4
5
6
7
8
9
10
11
12
13
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId: "hitl-1705312800000-a1b2c3d4...",
approved: false,
reason: "File contains active customer data - cannot delete",
metadata: {
timestamp: new Date().toISOString(),
responseTime: 12000,
userId: "[email protected]",
},
},
});
Approval with modified arguments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId: "hitl-1705312800000-a1b2c3d4...",
approved: true,
modifiedArguments: {
path: "/data/archived/customer-records-2024.csv", // Changed path
},
metadata: {
timestamp: new Date().toISOString(),
responseTime: 8500,
userId: "[email protected]",
},
},
});
Custom rules for advanced scenarios
For more granular control, you will define custom rules that go beyond keyword matching:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { NeuroLink } from "@juspay/neurolink";
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete", "remove"],
timeout: 60000,
auditLogging: true,
customRules: [
{
name: "large-batch-operations",
requiresConfirmation: true,
condition: (toolName, args) => {
// Require confirmation for batch operations over 100 items
if (typeof args === "object" && args !== null) {
const typedArgs = args as Record<string, unknown>;
if (Array.isArray(typedArgs.items) && typedArgs.items.length > 100) {
return true;
}
}
return false;
},
customMessage: "Large Batch Operation (100+ items)",
},
{
name: "production-environment",
requiresConfirmation: true,
condition: (toolName, args) => {
// Always confirm operations targeting production
if (typeof args === "object" && args !== null) {
const typedArgs = args as Record<string, unknown>;
return typedArgs.environment === "production";
}
return false;
},
customMessage: "Production Environment Operation",
},
{
name: "financial-transactions",
requiresConfirmation: true,
condition: (toolName, args) => {
// Require confirmation for transactions over $1000
if (typeof args === "object" && args !== null) {
const typedArgs = args as Record<string, unknown>;
if (typeof typedArgs.amount === "number" && typedArgs.amount > 1000) {
return true;
}
}
return false;
},
customMessage: "High-Value Transaction ($1000+)",
},
],
},
});
Complete example: file management with HITL
Now you will put it all together with a production-ready file management example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { NeuroLink } from "@juspay/neurolink";
import * as fs from "fs/promises";
import * as readline from "readline";
// Initialize NeuroLink with HITL
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete", "remove", "write", "modify"],
timeout: 60000, // 1 minute
allowArgumentModification: false,
autoApproveOnTimeout: false,
auditLogging: true,
},
});
// Set up console-based confirmation UI
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function askQuestion(question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, resolve);
});
}
// Handle HITL confirmation requests
neurolink.getEventEmitter().on("hitl:confirmation-request", async (event) => {
const { confirmationId, toolName, arguments: args, timeoutMs } = event.payload;
console.log("\n========================================");
console.log("HITL CONFIRMATION REQUIRED");
console.log("========================================");
console.log(`Tool: ${toolName}`);
console.log(`Arguments: ${JSON.stringify(args, null, 2)}`);
console.log(`Timeout: ${timeoutMs / 1000} seconds`);
console.log("========================================");
const answer = await askQuestion("Approve this action? (yes/no): ");
const approved = answer.toLowerCase() === "yes" || answer.toLowerCase() === "y";
let reason: string | undefined;
if (!approved) {
reason = await askQuestion("Reason for rejection: ");
}
// Send response
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId,
approved,
reason,
metadata: {
timestamp: new Date().toISOString(),
responseTime: Date.now(),
},
},
});
});
// Handle timeouts
neurolink.getEventEmitter().on("hitl:timeout", (event) => {
console.log(`\nTimeout: Operation "${event.payload.toolName}" was not approved in time.`);
});
// Define tools - HITL triggers based on dangerousActions keywords and customRules
const tools = [
{
name: "deleteFile",
description: "Permanently deletes a file from the filesystem",
// HITL triggers automatically because "delete" is in dangerousActions
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Path to the file to delete" },
},
required: ["path"],
},
execute: async (args: { path: string }) => {
await fs.unlink(args.path);
return { success: true, message: `Deleted ${args.path}` };
},
},
{
name: "readFile",
description: "Reads contents of a file",
// No HITL trigger - "read" is not in dangerousActions
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Path to the file to read" },
},
required: ["path"],
},
execute: async (args: { path: string }) => {
const content = await fs.readFile(args.path, "utf-8");
return { success: true, content };
},
},
{
name: "writeFile",
description: "Writes content to a file (creates or overwrites)",
// HITL triggers automatically because "write" is in dangerousActions
parameters: {
type: "object",
properties: {
path: { type: "string", description: "Path to write to" },
content: { type: "string", description: "Content to write" },
},
required: ["path", "content"],
},
execute: async (args: { path: string; content: string }) => {
await fs.writeFile(args.path, args.content);
return { success: true, message: `Wrote to ${args.path}` };
},
},
];
// Use the AI with HITL-protected tools
async function main() {
try {
const result = await neurolink.generate({
input: {
text: "Please delete the file at /tmp/test-data.csv",
},
provider: "anthropic",
model: "claude-sonnet-4-5-20250929",
tools,
});
console.log("\nAI Response:", result.content);
} catch (error) {
console.error("Error:", error);
} finally {
rl.close();
}
}
main();
Audit logging for compliance
When auditLogging is enabled, every HITL event is logged for compliance and debugging:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Audit log entry structure
interface HITLAuditLog {
timestamp: string; // ISO timestamp
eventType:
| "confirmation-requested"
| "confirmation-approved"
| "confirmation-rejected"
| "confirmation-timeout"
| "confirmation-auto-approved";
toolName: string;
userId?: string;
sessionId?: string;
arguments: unknown;
reason?: string;
responseTime?: number;
}
Listening to audit events
1
2
3
4
5
6
7
8
9
10
11
// Subscribe to audit events for external logging
neurolink.getEventEmitter().on("hitl:audit", (auditEntry) => {
// Send to your logging system
console.log("[HITL Audit]", JSON.stringify(auditEntry));
// Example: Send to external logging service
// await loggingService.log(auditEntry);
// Example: Store in database for compliance
// await database.hitlAuditLogs.insert(auditEntry);
});
Example audit log entries
Confirmation Requested:
1
2
3
4
5
6
7
8
{
"timestamp": "2026-01-15T10:00:00.000Z",
"eventType": "confirmation-requested",
"toolName": "deleteFile",
"userId": "user_456",
"sessionId": "sess_abc123",
"arguments": { "path": "/data/old-records.csv" }
}
Confirmation Approved:
1
2
3
4
5
6
7
8
{
"timestamp": "2026-01-15T10:00:05.200Z",
"eventType": "confirmation-approved",
"toolName": "deleteFile",
"userId": "[email protected]",
"arguments": { "path": "/data/old-records.csv" },
"responseTime": 5200
}
Confirmation Rejected:
1
2
3
4
5
6
7
8
9
{
"timestamp": "2026-01-15T10:00:12.000Z",
"eventType": "confirmation-rejected",
"toolName": "deleteFile",
"userId": "[email protected]",
"arguments": { "path": "/data/active-customers.csv" },
"reason": "File contains active customer data",
"responseTime": 12000
}
Framework comparison
How does NeuroLink HITL compare to other AI frameworks?
Feature comparison
| Feature | NeuroLink | LangChain | Vercel AI SDK |
|---|---|---|---|
| Built-in HITL | Yes | No | No |
| Event-based Workflow | Yes | Manual | Manual |
| Keyword Triggers | Built-in | Manual | Manual |
| Custom Rules | Built-in | Manual | Manual |
| Argument Modification | Built-in | Manual | Manual |
| Audit Logging | Built-in | Manual | Manual |
| Timeout Handling | Built-in | Manual | Manual |
Implementation comparison
NeuroLink - Configuration only, minutes to deploy:
1
2
3
4
5
6
7
8
9
10
11
12
13
import { NeuroLink } from "@juspay/neurolink";
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete", "remove"],
timeout: 30000,
},
});
neurolink.getEventEmitter().on("hitl:confirmation-request", async (event) => {
// Handle confirmation UI
});
Other Frameworks - Full custom implementation required:
1
2
3
4
5
6
7
// You must build your own HITL layer:
// - Tool interception middleware
// - Confirmation state management
// - Timeout handling
// - Audit logging
// - Event coordination
// This typically requires 200-500 lines of code
Best practices
For developers
- Use keyword triggers - Add dangerous action keywords to
dangerousActionsarray - Use customRules for specific tools - Target tools by name with condition functions
- Clear prompts - Ensure users understand exactly what will happen
- Set appropriate timeouts - Balance security with usability
- Log everything - Enable audit logging for compliance
- Handle denials gracefully - The AI should recover from rejected operations
What to confirm
Always require confirmation:
- File deletions
- Database write/delete operations
- Sending emails or messages
- Code execution
- Production environment changes
- Financial transactions
Skip confirmation:
- Read-only operations
- Search and fetch operations
- Content generation
- Non-destructive previews
Timeout configuration
| Use Case | Recommended Timeout | Rationale |
|---|---|---|
| Interactive CLI | 30-60 seconds | User is present |
| Web Application | 60-120 seconds | User may switch tabs |
| Batch Processing | 300+ seconds | May require expert review |
| Critical Operations | 600+ seconds | Multi-level approval |
Troubleshooting
Problem: Tool executes without asking for permission
Cause: Tool name does not contain any keywords from dangerousActions and no customRules match
Solution: Use dangerousActions for keyword matching or customRules for specific tool targeting:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const neurolink = new NeuroLink({
hitl: {
enabled: true,
// Option 1: Add keywords that match tool names
dangerousActions: ["delete", "remove", "drop", "transfer"],
// Option 2: Use customRules to target specific tools by name
customRules: [
{
name: "specific-tool-rule",
condition: (toolName, args) => toolName === "myDangerousTool",
customMessage: "Confirm dangerous operation",
},
{
name: "amount-threshold",
condition: (toolName, args) => {
if (typeof args === "object" && args !== null) {
const typedArgs = args as Record<string, unknown>;
return typeof typedArgs.amount === "number" && typedArgs.amount > 1000;
}
return false;
},
customMessage: "High-value transaction requires approval",
},
],
},
});
Note: The SDK does not support a
requiresConfirmationproperty on individual tool definitions. HITL triggers are controlled entirely through thedangerousActionskeywords andcustomRulesconfiguration.Tip: For simpler use cases where you want to require confirmation for specific tools without complex conditions, you can use a straightforward
customRulesentry:
1 2 3 4 5 6 7 customRules: [ { name: "confirm-send-email", condition: (toolName) => toolName === "sendEmail", customMessage: "Confirm email send", }, ]This effectively acts as a per-tool
requiresConfirmation: trueoption.
Problem: Confirmation dialog never shows
Cause: Not listening to hitl:confirmation-request event
Solution:
1
2
3
4
5
6
7
8
9
// Set up event listener BEFORE making AI requests
neurolink.getEventEmitter().on("hitl:confirmation-request", async (event) => {
// Your confirmation UI logic
});
// Then make AI requests
const result = await neurolink.generate({
input: { text: "Delete the file" },
});
Problem: AI keeps asking for confirmation repeatedly
Cause: Confirmation response sent with wrong confirmationId
Solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
neurolink.getEventEmitter().on("hitl:confirmation-request", async (event) => {
const { confirmationId } = event.payload; // Extract exact ID
// ... get user decision ...
neurolink.getEventEmitter().emit("hitl:confirmation-response", {
type: "hitl:confirmation-response",
payload: {
confirmationId, // Must match exactly
approved: true,
metadata: { timestamp: new Date().toISOString(), responseTime: Date.now() },
},
});
});
Problem: Operations timeout before user can respond
Cause: Timeout too short for your use case
Solution:
1
2
3
4
5
6
7
8
const neurolink = new NeuroLink({
hitl: {
enabled: true,
dangerousActions: ["delete"],
timeout: 120000, // Increase to 2 minutes
autoApproveOnTimeout: false, // Keep rejecting on timeout for safety
},
});
Next steps
Ready to deploy enterprise-grade HITL safety? Here are your next steps:
Install NeuroLink:
npm install @juspay/neurolinkConfigure HITL: Start with basic keyword triggers and tune based on your needs
Set up event handlers: Implement confirmation UI appropriate for your application
Enable audit logging: Ensure compliance from day one
Monitor and tune: Track approval rates and response times
For detailed API documentation, see the NeuroLink HITL Documentation.
Related Resources:
- Custom Tools Guide - Build tools with HITL support
- NeuroLink vs LangChain - Compare NeuroLink to other SDKs
What you built
You now have a complete HITL safety system with event-based confirmation workflows, keyword triggers, custom rules, argument modification, and audit logging. Your AI applications will require human approval for dangerous operations, log every decision for compliance, and handle timeouts safely.
For the complete API reference, see the NeuroLink HITL Documentation. For source code and contributions, visit github.com/juspay/neurolink.
Related posts:
