Human-in-the-loop approvals
Some actions should never run unattended — a large payout, deleting production data, emailing your whole customer list. Eda’s approval flow pauses those actions until a human decides, then hands the outcome back to your code.
The two shapes
- Inline — your code waits for the human (
checkAndWait). Best for interactive agents where a short pause is fine. - Deferred — your code returns immediately and resumes later
(
getDecision/waitForApproval) from a webhook, job, or next request. Best for long-running or serverless work.
Inline approval
Add a require_approval policy
In the dashboard → Policies: agent *, action wire_transfer, condition
amount >= 10000, effect require_approval.
Wait for the decision in code
import { Eda, EdaPendingTimeoutError } from "@eda-holding-inc/sdk";
const eda = new Eda({ apiKey: process.env.EDA_API_KEY! });
async function wire(amount: number, to: string) {
try {
const decision = await eda.checkAndWait(
{ agent: "treasury-agent", action: "wire_transfer", params: { amount, to } },
{ timeoutMs: 10 * 60_000, pollMs: 2_000 },
);
if (decision.status !== "approved") {
return { ok: false, reason: decision.reason }; // denied
}
await bank.wire({ amount, to });
return { ok: true };
} catch (e) {
if (e instanceof EdaPendingTimeoutError) {
return { ok: false, reason: "Approval timed out — try again later." };
}
throw e;
}
}Approve in the dashboard
The action shows up in Approvals. A teammate approves or denies (optionally with
a reason); checkAndWait resolves the moment they do.
Deferred approval
When you can’t hold a request open (serverless, a long job), persist the actionId
and resume later.
// 1) kick off the check, store the id, return to the caller
const { actionId, status } = await eda.check({
agent: "treasury-agent",
action: "wire_transfer",
params: { amount, to },
});
if (status === "pending") {
await db.pendingTransfers.insert({ actionId, amount, to });
return { queued: true };
}
// 2) later — a webhook, cron, or the next poll — resolve it
const decision = await eda.getDecision(actionId);
if (decision.status === "approved") {
await bank.wire({ amount, to });
await db.pendingTransfers.markDone(actionId);
}Use waitForApproval(actionId, { timeoutMs }) if you’d rather block in a
background worker than poll manually.
Telling the model what happened
For agentic loops, feed the decision back to the model as the tool result so it can respond gracefully:
if (decision.status === "pending") {
return toolResult("This action needs human approval; I've requested it and will follow up.");
}
if (decision.status === "denied") {
return toolResult(`A human declined this action: ${decision.reason}`);
}Who can approve
Approvers are your team members in the workspace. Invite teammates and set roles in the dashboard; every approval is recorded with the approver’s identity in the audit log.