Quickstart
Use this flow when an agent reaches a decision it should not make alone. The agent sends the question, evidence, choices, and resume target. TryAgent routes the escalation by policy, records the reviewer decision or timeout path, and calls your workflow back with a signed event.
Want to run a complete workflow first? See the examples.
1. Create a policy in TryAgent
In TryAgent, create an escalation policy with the key orders.auth_doc. The policy owns reviewer routing, notification targets, SLA, and the default choice used when an escalation times out.
Keep those operations rules in policy. The agent should send only the decision content and the run handle it needs later.
Create an API key for your workspace. Agent-facing keys are bearer tokens that look like ain_live_.... Keys are issued with full escalation access by default, so the same key can create, read, and act on escalations — see Manage escalations from the SDK.
export TRYAGENT_API_KEY="ain_live_..."
export TRYAGENT_WEBHOOK_SECRET="whsec_..."
Install the SDK:
3. Send the escalation
import { TryAgent } from "@tryagent/sdk";
const tryagent = new TryAgent({
apiKey: process.env.TRYAGENT_API_KEY!,
});
const escalation = await tryagent.escalate("orders.auth_doc", {
agentId,
runId,
subject: {
type: "order",
id: "ord_4821",
label: "Order #4821",
},
question: "The authorization document is missing a signature date. Continue?",
evidence: [
"Candidate name is present.",
"Employer is present.",
"Signature date is blank.",
],
choices: [
{ id: "manual_review", label: "Send to manual review" },
{ id: "continue", label: "Continue anyway" },
],
responseFields: [
{
type: "number",
name: "approvedLimit",
label: "Approved limit",
min: 0,
step: 0.01,
},
{
type: "select",
name: "riskLevel",
label: "Risk level",
options: [
{ value: "low", label: "Low" },
{ value: "high", label: "High" },
],
},
],
resume: {
mode: "webhook",
url: "https://api.example.com/tryagent/resume",
secret: process.env.TRYAGENT_WEBHOOK_SECRET!,
},
});
console.log(escalation.id, escalation.status);
The SDK sends POST /escalations with Authorization: Bearer ain_live_.... Required fields are agentId, runId, subject, question, evidence, and choices; the policy key is the first argument to escalate.
4. Verify the callback
TryAgent returns immediately with an open escalation. When a reviewer decides, or the SLA timeout resolves to the default choice, TryAgent sends a signed POST to resume.url.
The request body includes:
escalationId
runId
agentId
policy
choice
response when the escalation configured structured response fields
resolvedBy
answeredBy
answeredAt
When resume.secret is set, TryAgent signs the exact JSON body with HMAC-SHA256 and sends:
x-tryagent-event: escalation.decided for a reviewer decision, or escalation.breached when the SLA timeout resolves to the default choice. The resolvedBy body field (human or timeout) carries the same distinction.
x-tryagent-delivery: <delivery id>
x-tryagent-signature: v1=<hex hmac>
Verify the signature before applying the decision. The SDK ships
webhooks.constructEvent, which verifies the HMAC (constant-time, via Web Crypto
so it runs on Node, edge, and Workers) and returns the typed event — it throws
WebhookSignatureError when verification fails, so a returned event is safe to
act on.
5. Resume the run
Use runId as the durable handle for the paused workflow. For LangGraph, make it the thread_id; when the callback arrives, resume that thread with the reviewer decision. For the full LangGraph pattern, see Use LangGraph interrupts.
import { TryAgent, WebhookSignatureError } from "@tryagent/sdk";
const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY! });
export async function POST(request: Request) {
// Pass the raw body — re-serializing JSON changes the bytes and breaks the signature.
const body = await request.text();
const signature = request.headers.get("x-tryagent-signature");
let event;
try {
event = await tryagent.webhooks.constructEvent({
payload: body,
signature,
secret: process.env.TRYAGENT_WEBHOOK_SECRET!,
});
} catch (err) {
if (err instanceof WebhookSignatureError) {
return Response.json({ error: "Invalid signature" }, { status: 401 });
}
throw err;
}
await resumeWorkflow(event.runId, {
escalationId: event.escalationId,
choice: event.choice,
answeredBy: event.answeredBy,
answeredAt: event.answeredAt,
});
return Response.json({ ok: true });
}
Callbacks without resume.secret are sent unsigned. Always set a secret for
production resume endpoints.
Manage escalations from the SDK
Beyond escalate, the escalations resource covers the rest of the lifecycle:
await tryagent.escalations.list({ status: "open" });
await tryagent.escalations.get(escalationId);
await tryagent.escalations.acknowledge(escalationId);
await tryagent.escalations.decide(escalationId, { choice: "continue", reason: "Verified" });
await tryagent.escalations.cancel(escalationId, { reason: "Duplicate" });
Each method works with an ain_live_ API key or a workspace user token/getToken.
Keys are issued with all escalation scopes by default, so every method is available
out of the box. Each route maps to a scope, so you can issue a narrower key via
POST /api-keys; a call needing a scope the key lacks returns 403.
| Method | Required scope |
|---|
escalate / create | escalations:write |
list, get | escalations:read |
acknowledge | escalations:acknowledge |
decide | escalations:decide |
cancel | escalations:cancel |