CrewAI Tutorial (TypeScript): adding human-in-the-loop for advanced developers
This tutorial shows how to add a human approval checkpoint into a CrewAI workflow in TypeScript, so an agent can pause before taking risky actions like sending emails, updating records, or generating customer-facing output. You need this when the model is good at drafting work but your process still requires a person to review, edit, or approve the final step.
What You'll Need
- •Node.js 20+
- •A TypeScript project with
ts-nodeortsx - •
crewaiinstalled - •An LLM API key configured for your CrewAI runtime
- •A terminal for running the script
- •A human operator who can approve or reject the agent’s proposed action
Step-by-Step
- •Install the dependencies and set up a minimal TypeScript project.
I’m usingtsxhere because it keeps the example simple and runs TypeScript directly.
npm init -y
npm install crewai dotenv readline/promises
npm install -D typescript tsx @types/node
- •Create a
.envfile with your model credentials.
CrewAI will read these at runtime, and you want this outside source control.
OPENAI_API_KEY=your_key_here
OPENAI_MODEL=gpt-4o-mini
- •Build a small human-in-the-loop gate with Node’s built-in prompt input.
The pattern is simple: let the agent draft an action, show it to a human, then continue only if the human approves.
import "dotenv/config";
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
export async function askHumanApproval(prompt: string): Promise<boolean> {
const rl = createInterface({ input, output });
const answer = await rl.question(`${prompt} (yes/no): `);
rl.close();
return ["yes", "y"].includes(answer.trim().toLowerCase());
}
- •Define your CrewAI agents and tasks around a reviewable artifact.
For human-in-the-loop flows, don’t ask the model to “do everything”; ask it to produce a concrete proposal that a person can approve.
import { Agent, Task, Crew } from "crewai";
const analyst = new Agent({
role: "Operations Analyst",
goal: "Draft a safe customer support response",
backstory: "You write concise responses for regulated workflows.",
});
const reviewer = new Agent({
role: "Compliance Reviewer",
goal: "Check whether the draft is safe to send",
backstory: "You look for risky claims, missing context, and policy issues.",
});
const draftTask = new Task({
description:
"Draft a reply to a customer asking why their payment was delayed. Keep it factual and avoid promises.",
expectedOutput: "A short draft reply ready for review.",
agent: analyst,
});
const reviewTask = new Task({
description:
"Review the draft for compliance risk and produce an approval summary.",
expectedOutput: "A clear approve/reject recommendation with reasons.",
agent: reviewer,
});
- •Run the crew, pause for approval, then continue only if approved.
This is the core pattern you’ll use in production: generate output, get human sign-off, then execute the downstream step.
import { askHumanApproval } from "./approval.js";
async function main() {
const crew = new Crew({
agents: [analyst, reviewer],
tasks: [draftTask, reviewTask],
verbose: true,
});
const result = await crew.kickoff();
console.log("\n=== Proposed Output ===\n");
console.log(String(result));
const approved = await askHumanApproval("Approve this response for sending?");
if (!approved) {
console.log("Rejected by human reviewer. Workflow stopped.");
process.exit(0);
}
console.log("Approved. Proceeding with downstream action...");
}
main();
- •If you need stricter control, split “draft” and “execute” into separate phases.
That lets you store the draft in your database, route it to Slack or an internal UI for review, then resume execution later with an explicit approval flag.
type ApprovalRecord = {
requestId: string;
approvedBy?: string;
approvedAt?: string;
};
async function executeAfterApproval(record: ApprovalRecord) {
if (!record.approvedBy || !record.approvedAt) {
throw new Error("Missing approval metadata");
}
console.log(`Executing request ${record.requestId} after approval.`);
}
Testing It
Run the script with npx tsx src/index.ts and verify that the crew produces a draft before any final action happens. Then answer no at the approval prompt and confirm the workflow stops immediately.
Next, run it again and answer yes, then check that your downstream branch executes only after approval. If you’re wiring this into an app instead of a CLI, replace the terminal prompt with a web form or internal admin queue using the same boolean gate.
Next Steps
- •Add structured output validation with Zod before asking for human approval.
- •Persist drafts and approvals in Postgres so every decision is auditable.
- •Move from CLI prompts to an internal review UI with role-based access control.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit