
Implementing Human-in-the-Loop AI Agents using Eve.dev and TypeScript
Ensure safety and accuracy in your AI workflows. Learn how to build human-in-the-loop authorization gates using Eve.dev's durable execution framework.
Summary
TL;DR: Never let an AI execute dangerous actions unchecked. This tutorial covers how to use Eve.dev to build "Human-in-the-Loop" (HITL) workflows, allowing agents to pause and request human approval before proceeding.
As AI agents move from answering questions to taking actions (like modifying databases, sending emails to clients, or deploying code), safety becomes the primary concern. Fully autonomous execution is a liability.
You need a Human-in-the-Loop (HITL) architecture.
In this guide, we will use Eve.dev to build a durable workflow where an agent prepares a financial transaction, suspends its own execution, asks a human administrator for approval, and then securely resumes.
Why Eve.dev for HITL?
Implementing HITL from scratch is incredibly difficult. If an LLM decides to execute an action, how do you pause the script, store the entire execution state (including chat history and memory), wait hours for a human to click a button on a web dashboard, and then resume the exact same script?
Eve.dev solves this inherently because every step of an Eve workflow is durable and checkpointed to a database.
Step 1: Designing the Agent
Let's build a "Financial Analyst Agent" that can review invoices and issue payments.
In our Eve workspace (/eve/agents/finance), we create our core instructions.md:
# Financial Analyst Agent
Your job is to review incoming invoices and process payments.
## Rules
1. You may extract data from invoices autonomously.
2. **CRITICAL:** You MUST NEVER execute a payment without getting human approval first using the `request_approval` tool.
Step 2: Building the Suspension Tool
The magic happens in our tools. We will create a tool that intentionally suspends the agent.
Create /eve/agents/finance/tools/request_approval.ts:
import { tool, suspend } from '@eve/core';
import { z } from 'zod';
export default tool({
name: 'request_approval',
description: 'Pauses execution to ask a human manager for approval before taking a critical action.',
schema: z.object({
amount: z.number(),
payee: z.string(),
reason: z.string()
}),
execute: async (payload) => {
// The suspend() function throws a special interrupt that Eve.dev catches.
// It serializes the agent's state to the database and halts execution.
return suspend({
type: 'human_approval_required',
data: payload,
message: `Waiting for human approval to pay $${payload.amount} to ${payload.payee}.`
});
}
});
Step 3: Building the Execution Tool
We also need the actual tool that performs the payment, which should only run after approval.
Create /eve/agents/finance/tools/execute_payment.ts:
import { tool } from '@eve/core';
import { z } from 'zod';
export default tool({
name: 'execute_payment',
description: 'Executes the payment transaction.',
schema: z.object({
amount: z.number(),
payee: z.string(),
}),
execute: async ({ amount, payee }) => {
// In production, this would call Stripe or a banking API
console.log(`[BANK] Processing payment of $${amount} to ${payee}`);
return { success: true, transactionId: "tx_12345" };
}
});
Step 4: The Application Backend (Next.js or Express)
Now, how does our UI interact with this agent? We need two endpoints: one to start the process, and one for the human to submit their approval.
import { Eve } from '@eve/core';
const eve = new Eve({ workspace: './eve', storage: 'postgres://...' });
// 1. Endpoint to process an invoice
async function processInvoice(invoiceId: string, invoiceText: string) {
const agent = await eve.getAgent('finance');
// We use the invoiceId as our durable workflow ID
const result = await agent.run({
id: invoiceId,
input: `Please review and pay this invoice: ${invoiceText}`
});
if (result.status === 'suspended') {
// The agent hit our request_approval tool.
// We notify the frontend to show an "Approve/Reject" UI.
console.log("ALERT: Human approval required:", result.suspensionMessage);
// e.g., trigger email or Slack notification to manager
}
}
// 2. Endpoint for the manager to click "Approve"
async function handleHumanDecision(invoiceId: string, isApproved: boolean) {
const agent = await eve.getAgent('finance');
// We resume the exact same workflow by passing the ID
// and we inject the human's decision back into the agent's context!
const result = await agent.resume({
id: invoiceId,
injectedMessage: isApproved
? "The human manager has APPROVED this transaction. Proceed with execute_payment."
: "The human manager has REJECTED this transaction. Do not pay. Explain why."
});
console.log("Agent finished:", result.finalOutput);
}
How the Execution Flows
- The user asks the agent to pay an invoice.
- The agent reads it, extracts the amount, and realizes it must follow Rule #2.
- The agent calls
request_approval(amount: 5000, payee: "Acme Corp"). - Eve.dev catches this, saves the entire memory array to PostgreSQL, and stops the process. The server can safely shut down.
- Two days later, a manager clicks "Approve" in a web dashboard.
- The
handleHumanDecisionfunction wakes up the agent, passing in the approval message. - The LLM reads the new message, realizes it has permission, and calls
execute_payment().
Conclusion
By leveraging Eve.dev's durable execution model, building Human-in-the-Loop workflows becomes trivial. You no longer have to build custom state machines or hack together Redis caches to pause AI execution. You can securely gate any dangerous action, ensuring your AI applications are safe for enterprise deployment.
Frequently Asked Questions (FAQ)
What is a Human-in-the-Loop (HITL) system? HITL is an architecture where an automated process (like an AI agent) must pause and request authorization or input from a human operator before proceeding with a high-risk action, such as executing a financial transaction or deleting data.
How does Eve.dev handle pausing an agent?
Eve.dev uses a suspend() function within tool definitions. When called, the framework halts the agent's execution loop, serializes the current state (memory, chat history, active variables), and saves it to a durable database until a resume() signal is received.
Can Eve.dev workflows survive a server crash? Yes. Because Eve.dev is built on durable execution principles, every state transition is checkpointed. If the server crashes while the agent is waiting for human approval, the state is safely preserved in the database and can be resumed on any other server instance.
Related reading
Slopsquatting is when an attacker registers the package name an AI coding tool keeps hallucinating, then waits for someone to install it. Here's how it works and how to defend against it.
The 2026-07-28 MCP revision removes sessions, the initialize handshake, and server-initiated requests. Here's what actually breaks in your server, the new wire format, the requestState and MRTR patterns that replace sessions, and the SDK v2 migration path.
agent-for-insurance is an open-source drafting aid that will not state a coverage conclusion without citing your policy's own text. Here's how it works, and the two parsing bugs that taught us why that rule has to be enforced in code, not prose.
Let's build something great.
Have a project in mind? We are an elite software and AI development studio ready to bring your ideas to production. Let's talk about your roadmap.