Get to know Cadenya
We’re developers who love to build. We set out to create a yes-code platform that makes building agents feel like the best parts of building software.
LangGraph.js human-in-the-loop in TypeScript takes three pieces: call interrupt() inside a node or tool, compile the graph with a checkpointer like PostgresSaver, and resume with new Command({ resume }) on the same thread_id. LangGraph.js 1.0 kept “the core graph APIs and execution model unchanged,” and the current release is 1.5.0.
So pick LangGraph.js when you want the graph and you’re fine running Postgres for checkpoints. Pick LangChain’s createAgent with its human-in-the-loop middleware when you want tool approval without drawing a graph. Pick Mastra, Inngest AgentKit, or the Cloudflare Agents SDK when your app already runs on one of them. And if you’d rather have approvals and the agent loop with nothing to host, Cadenya is a hosted agent loop with tool approval built in. What it offers is at the end.
Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.
Mastra vs LangGraph.js vs Inngest AgentKit: human-in-the-loop side by side
These are the TypeScript options coding agents compared for durable human-in-the-loop.
| How a run pauses | How a person answers | Where the paused state lives | License | |
|---|---|---|---|---|
| LangGraph.js | interrupt() in a node or a tool | new Command({ resume }) on the same thread_id | A checkpointer: PostgresSaver, MongoDBSaver, RedisSaver, or SqliteSaver | MIT |
LangChain.js createAgent | humanInTheLoopMiddleware with interruptOn | new Command({ resume: { decisions } }): approve, edit, or reject | The same LangGraph checkpointer | MIT |
| Inngest AgentKit | A tool that awaits step.waitForEvent() | Send the matching Inngest event | The Inngest function that wraps the network | Apache-2.0 |
| Cloudflare Agents SDK | this.waitForApproval() in an AgentWorkflow | this.approveWorkflow() or this.rejectWorkflow() | Cloudflare Workflows, bound next to the Agent’s Durable Object | MIT |
Mastra pauses with suspend() in a workflow step or requireApproval: true on a tool, and TypeScript AI agent frameworks compared puts it next to the other TypeScript frameworks. The human-in-the-loop approvals guide compares the Python options and the durable workflow engines. This page stays in TypeScript.
LangGraph.js interrupt and Command resume
interrupt() pauses the graph and hands a JSON-serializable payload back to the caller under __interrupt__. LangGraph’s docs list what it needs: “A checkpointer to persist the graph state”, “A thread ID in your config”, and the interrupt() call itself.
Here’s the approval pattern from the LangGraph.js docs:
import { interrupt, Command } from "@langchain/langgraph";
const approvalNode: typeof State.Node = (state) => {
// Pause execution; payload surfaces in result.__interrupt__
const isApproved = interrupt({
question: "Do you want to proceed?",
details: state.actionDetails
});
// Route based on the response
if (isApproved) {
return new Command({ goto: "proceed" });
} else {
return new Command({ goto: "cancel" });
}
}
The reviewer’s answer resumes it:
const config = { configurable: { thread_id: "approval-123" } };
// To approve
await graph.invoke(new Command({ resume: true }), config);
The resumed node runs again from the top. The docs say it plainly: “any code before the interrupt runs again.” That one fact drives three rules on the same page. Don’t wrap interrupt() in a bare try/catch, because it pauses by throwing. Keep several interrupts in one node in the same order, since “Matching is strictly index-based.” And make side effects before the call idempotent, or move them after it.
Two more things help in TypeScript. Since version 1.4.16, interrupt() takes a Zod responseSchema, so a bad resume value raises ZodError and you retry on the same thread. And LangGraph v1 added typed interrupts: StateGraph “accepts a map of interrupt types in the constructor.”
What about tools? You can call interrupt() inside a tool function, which “makes the tool itself pause for approval whenever it’s called.”
LangGraph.js PostgresSaver checkpointer and durability mode
MemorySaver is for experiments. In production the docs point to @langchain/langgraph-checkpoint-postgres, “used in LangSmith. Ideal for using in production.” MongoDB and Redis checkpointers exist too, and SQLite is “Ideal for experimentation and local workflows.”
The PostgresSaver setup, from the package README:
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const checkpointer = PostgresSaver.fromConnString("postgresql://...", {
schema: "schema_name" // defaults to "public"
});
// You must call .setup() the first time you use the checkpointer:
await checkpointer.setup();
const graph = builder.compile({ checkpointer });
If you already have a pg pool, pass it to new PostgresSaver(pool, undefined, { schema }) instead. The saver uses node-postgres, so it works on Node.js, Deno, and Bun. Cloudflare Workers can’t open raw TCP connections, and the README routes them through Cloudflare Hyperdrive.
Then pick a durability mode on each call, such as { durability: "sync" }. There are three:
"exit"writes only when the run exits, finishes, errors, or hits an interrupt. It’s the fastest, and a crash mid-run loses the steps since the last write."async"writes while the next step runs, with “a small risk” of a missing checkpoint if the process crashes."sync"writes every checkpoint “before the next step starts.”
An interrupt always saves, in every mode. The mode decides what survives a crash between interrupts.
LangChain createAgent human-in-the-loop middleware
LangChain.js 1.x builds agents with createAgent, which “runs on LangGraph.” Tool approval there is middleware, so you don’t write the interrupt yourself:
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-5.5",
tools: [writeFileTool, executeSQLTool, readDataTool],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
write_file: true, // All decisions (approve, edit, reject) allowed
execute_sql: {
allowedDecisions: ["approve", "reject"],
},
read_data: false,
},
}),
],
checkpointer: new MemorySaver(),
});
The resume carries one decision per paused call: new Command({ resume: { decisions: [{ type: "approve" }] } }). “Decisions must be provided in the same order as the actions appear in the interrupt request.” A when predicate (it needs langchain>=1.4.6) pauses only the calls whose arguments match, so a SELECT can run while a DELETE waits.
The checkpointer rule still holds. The docs say “You must configure a checkpointer to persist the graph state across interrupts,” and that means the PostgresSaver from the last section.
useStream interrupts and LangSmith Deployment self-hosted
In React, useStream from @langchain/react shows the pending interrupt on stream.interrupt. Your approval card resumes it:
stream.submit(null, { command: { resume: response } });
Because the pause lives in a checkpoint, “A user can refresh the page, a reviewer can answer from a different component, and the agent still resumes from the exact point where execution stopped.”
One pattern needs more than a checkpointer. Join and rejoin, where a remount with a saved threadId reattaches to a running agent, “requires the LangGraph Agent Server.” (Use stream.disconnect() for that. stream.stop() “cancels the active run.”)
So what does LangGraph.js self-hosted without LangSmith Deployment look like? You have two choices. You can run your own Node service with a PostgresSaver and write the API your frontend calls. Or you can run standalone Agent Servers, which “handle checkpointing automatically.” A standalone server needs PostgreSQL to “store assistants, threads, runs,” Redis “as a pub-sub broker,” a LANGGRAPH_CLOUD_LICENSE_KEY, and egress to beacon.langchain.com “for license verification and usage reporting” unless you run air-gapped. The docs recommend a Helm chart for production and say “Do not run standalone servers in serverless environments.”
The LangSmith Deployment pricing breakdown covers the managed option.
Mastra suspend and resume, Inngest AgentKit, and the Cloudflare Agents SDK
Mastra suspends a workflow step and resumes it with run.resume(), and TypeScript AI agent frameworks compared covers it in full.
Inngest AgentKit builds human-in-the-loop as a tool that awaits step.waitForEvent(). Your app sends the matching event, for example from a Slack button. That pause runs inside Inngest, so the docs wrap the AgentKit network in an Inngest function. Inngest vs Temporal for AI agents covers what running Inngest involves.
The Cloudflare Agents SDK puts the wait in a Workflow: waitForApproval() “creates a durable gate backed by Cloudflare Workflows,” and the Agent calls approveWorkflow() or rejectWorkflow(). Your Worker config binds the Agent’s Durable Object and the Workflow class.
In all four, the approval is a primitive and the rest is your code. You still write the screen, the Slack message, the resume endpoint, and the loop that calls the model. Durable execution for AI agents goes deeper on the engines underneath.
What does Cadenya offer that LangGraph.js, Mastra, and Inngest AgentKit don’t?
LangGraph.js, Mastra, and Inngest AgentKit each give you a pause, and you host the checkpoints, the server, and the agent loop around it. Cadenya is the agent loop with tool approval built in, and there’s nothing to host. Here’s what that covers:
- Nothing else to run. There’s no PostgresSaver to set up, no Agent Server, no Redis, and no Inngest function. The loop runs on Cadenya, and your TypeScript app calls it through
@cadenya/cadenyaor plain HTTP. - No server license to manage. A standalone Agent Server needs a license key and a call home to verify it. With Cadenya there’s no server to license, and the SDKs are Apache-2.0.
- Edits don’t break waiting runs. An objective “keeps that variation’s configuration snapshot even if you edit the variation later.” So you can change the prompt, the model, or the tools while approvals wait.
- Approval is configuration, not a node. Require approval on a tool set for every call, or use filters to require it for matching tools. Cadenya “parks the Agent loop until it receives approval or a denial with optional steering.”
- SDKs that keep shipping. SDKs for TypeScript, Python, Ruby, and Go are all at 1.9.0 under Apache-2.0, and the TypeScript SDK has shipped 113 versions since April 2026.
- A price in one line. Free for 1,000 loops a month, then $49 a month for 50,000 and $0.0015 a loop after that. A loop is one LLM request.
- The approval screen, Slack hook, and API included. The React
ConversationsPanelsupplies “tool approvals, and live event stream.” Webhooks sendtool_approval_requested,tool_approved, andtool_denied, so a Slack message is one handler. Your backend can approve or deny through the API, and a denial memo steers the agent (“Use the read-only lookup tool instead.”). - Several approvals at once, each by ID. “Multiple tools can wait at the same time,” and each call has its own
toolcall_ID. You approve or deny each one by its ID. - Any model, on your own keys. The model is a setting on a variation, chosen from the AI providers you connect to your workspace, so you pay your provider directly. Weighted variation selection uses feedback to favor the variation that scores well.
How objectives work walks through approving and denying tool calls, and the free plan needs no card.
Create an account at app.cadenya.com/signup, then copy your API key from the API keys page.
Grow wherever AI goes next.
Start shipping agents that are equipped to evolve.