# Escalation primitive Source: https://tryagentai.mintlify.app/concepts/escalation-primitive The one decision object an agent sends to TryAgent. # Escalation Primitive An escalation is a structured pause in an agent run. It says: this run is blocked, this is the case, this is the question, this is the evidence, and these are the choices a human can make. The agent supplies decision content: * `agentId`: the workflow or agent that paused. * `runId`: the durable run handle you will use to resume. * `subject`: the real-world object under review. * `question`: the exact decision needed from a reviewer. * `evidence`: an array of strings. * `choices`: selectable outcomes for the reviewer. * `resume`: optional callback instructions. The policy supplies operations: queue, reviewers, notifications, SLA, timeout behavior, and audit context. See the [quickstart](/quickstart) for the complete request and callback flow. # Policies and routing Source: https://tryagentai.mintlify.app/concepts/policies-and-routing How policy keeps routing out of agent code. # Policies And Routing Policies let product code name the decision point while operations owns how it is handled. Your agent passes a stable policy key such as `orders.auth_doc`. TryAgent resolves that key inside the workspace attached to your `ain_live_...` bearer token. The agent does not send a tenant ID or reviewer list. A policy controls: * Review queue. * Eligible reviewers. * Slack and email notifications. * SLA and timeout choice. * Policy version recorded on each escalation. Changing a policy affects new escalations. Existing escalations keep the version they were created with. See the [quickstart](/quickstart) for the complete SDK call that uses a policy key. # Examples Source: https://tryagentai.mintlify.app/examples Runnable TryAgent examples on GitHub. # Examples Use the public examples repo when you want a complete runnable workflow instead of isolated snippets: [TryAgent examples on GitHub](https://github.com/hiredbyreact/tryagent-examples) The repo is generated from the private TryAgent monorepo, so examples stay aligned with the SDK and product code. Manual edits in the public repo may be overwritten by the sync workflow. ## Stock research review The stock research example shows a portfolio workflow that escalates a high-risk equity memo to TryAgent before it reaches a client. High-risk research pauses twice using LangGraph interrupts: first for investment committee review, then again for compliance review of the client-facing memo. The CLI publishes each interrupt payload to TryAgent, and the graph is resumed with `new Command({ resume: ... })` when the reviewer decision comes back. See [Use LangGraph interrupts](/guides/langgraph-interrupts) for the TryAgent integration pattern. ```bash theme={null} git clone https://github.com/hiredbyreact/tryagent-examples.git cd tryagent-examples corepack enable corepack pnpm install corepack pnpm --filter @tryagent/langgraph-stock-research-example start -- NVDA ``` `NVDA` follows the high-risk path and creates the first TryAgent escalation from a LangGraph interrupt. Use `MSFT` to see the lower-risk path complete without human review. Before running against TryAgent, create escalation policies and an API key, then export: ```bash theme={null} export TRYAGENT_API_KEY="ain_live_..." export TRYAGENT_INVESTMENT_POLICY_KEY="research.investment_committee" export TRYAGENT_COMPLIANCE_POLICY_KEY="research.compliance_review" ``` The example defaults to the production TryAgent API. Set `TRYAGENT_BASE_URL` only when you need to point it at a local or non-production environment. ## Customer refund approval The refund approval example shows a customer support workflow that escalates a risky refund request to TryAgent for human review. High-risk requests pause twice using LangGraph interrupts: first for refund approval, then again for customer message review. ```bash theme={null} corepack pnpm --filter @tryagent/customer-refund-approval-example start -- refund_high_value_late ``` `refund_high_value_late` follows the high-risk path and creates the first TryAgent escalation from a LangGraph interrupt. Use `refund_low_value_duplicate_shipping` to see the low-risk path complete without human review. For the refund example, create an escalation policy and export: ```bash theme={null} export TRYAGENT_POLICY_KEY="support.refund_approval" ``` ## Test locally ```bash theme={null} corepack pnpm test ``` See the [quickstart](/quickstart) for the underlying SDK call and signed resume callback flow, or [Use LangGraph interrupts](/guides/langgraph-interrupts) for the concrete LangGraph pause/resume pattern. # Connect Slack Source: https://tryagentai.mintlify.app/guides/connect-slack Install the TryAgent Slack bot for DMs and channel notifications. # Connect Slack TryAgent reaches people and channels in Slack through a bot installed once per workspace. The bot can DM a user by Slack email and post to any channel it has been invited to. ## Create the Slack app In TryAgent, open **Destinations -> Slack connection** and choose **Create the Slack app**. This opens Slack's create-from-manifest flow with the bot name and scopes pre-filled. The bot token scopes are: | Scope | Purpose | | ------------------ | --------------------------------------------- | | `chat:write` | Post messages to people and channels. | | `channels:read` | Resolve public channel names to IDs. | | `groups:read` | Resolve private channel names to IDs. | | `users:read` | Required parent scope for reading user email. | | `users:read.email` | Find a person by Slack email for DMs. | | `im:write` | Open a direct message with a person. | To create it manually, go to [Slack apps](https://api.slack.com/apps), create an app from scratch, and add the same bot token scopes under **OAuth & Permissions**. ## Install and connect 1. In Slack **OAuth & Permissions**, choose **Install to Workspace**. 2. Copy the **Bot User OAuth Token**. It starts with `xoxb-`. 3. In TryAgent **Destinations -> Slack connection**, paste the token and choose **Connect**. TryAgent verifies the token with Slack and stores it encrypted for the workspace. Only owners and admins can connect Slack. ## Use Slack destinations * For DMs, add a **Slack DM** contact method on the People page using the person's Slack email. * For channels, invite the bot in Slack with `/invite @TryAgent`, then add a Slack destination with the channel name or ID. ## Troubleshooting * `Slack is not connected for this workspace`: finish the connection step. * `No Slack user found`: confirm the email belongs to the Slack workspace and `users:read.email` is installed. * `not_in_channel`: invite the bot to the channel. * `invalid_auth`: reconnect with a valid `xoxb-` bot token. # Create an escalation policy Source: https://tryagentai.mintlify.app/guides/create-escalation-policy Define routing for one agent decision point. # Create An Escalation Policy Create one policy for each repeatable decision point in an agent workflow. Use a stable key that reads like product code, for example `orders.auth_doc`. ## Recommended shape * Key: `orders.auth_doc` * Name: `Order authorization document review` * Queue: the team that owns the decision. * Reviewers: the users or roles allowed to decide. * Notifications: Slack channel, Slack DM, or email targets. * SLA: how long the reviewer has before timeout. * Default choice: the choice applied when the SLA expires. Once the policy exists, agents can create escalations with an API key: See the [quickstart](/quickstart) for the complete SDK call and signed resume callback. Keep reviewer and notification rules in the policy instead of hard-coding them in the agent. # Use LangGraph interrupts Source: https://tryagentai.mintlify.app/guides/langgraph-interrupts Pause a LangGraph workflow and resume it from a TryAgent decision. # Use LangGraph Interrupts Use LangGraph interrupts when a graph reaches a decision that needs a human before it can continue. The graph pauses with `interrupt()`, TryAgent routes the decision to the right reviewer, and your callback resumes the same graph thread with `new Command({ resume })`. This guide follows LangGraph's official [interrupts](https://docs.langchain.com/oss/javascript/langgraph/interrupts) pattern: * Compile the graph with a checkpointer. * Invoke and resume with the same `{ configurable: { thread_id } }`. * Call `interrupt()` inside the node that needs external input. * Read the interrupt payload from `__interrupt__`. * Resume with `new Command({ resume: ... })`. ## 1. Interrupt with a TryAgent payload Keep the graph node deterministic up to the interrupt. Build the TryAgent escalation payload inside the node, then let the caller publish it to TryAgent after LangGraph has paused. ```typescript theme={null} import { Annotation, END, MemorySaver, START, StateGraph, interrupt, } from "@langchain/langgraph"; import type { EscalateInput } from "@tryagent/sdk"; type ApprovalResume = { escalationId: string; choice: "approve" | "reject"; answeredBy?: string; }; type TryAgentInterruptPayload = { kind: "tryagent_approval"; policy: string; tryagentInput: EscalateInput; }; const State = Annotation.Root({ runId: Annotation(), orderId: Annotation(), status: Annotation<"approved" | "rejected" | undefined>(), escalationId: Annotation(), }); const graph = new StateGraph(State) .addNode("approval", (state) => { const decision = interrupt({ kind: "tryagent_approval", policy: "orders.auth_doc", tryagentInput: { agentId: "order-review-agent", runId: state.runId, subject: { type: "order", id: state.orderId, label: `Order #${state.orderId}`, }, question: "Approve this order authorization document?", evidence: ["Signature date is missing.", "Customer identity matched."], choices: [ { id: "approve", label: "Approve" }, { id: "reject", label: "Reject" }, ], resume: { mode: "webhook", url: "https://api.example.com/tryagent/resume", }, }, }); return { status: decision.choice === "approve" ? "approved" : "rejected", escalationId: decision.escalationId, }; }) .addEdge(START, "approval") .addEdge("approval", END) .compile({ checkpointer: new MemorySaver() }); ``` `MemorySaver` is useful for local examples. Use a durable checkpointer in production so paused graph state survives process restarts. ## 2. Publish the interrupt to TryAgent Use one durable value for both systems: * TryAgent `runId` * LangGraph `thread_id` When the graph pauses, the payload you passed to `interrupt()` is returned under `__interrupt__`. Publish that payload to TryAgent. ```typescript theme={null} import { INTERRUPT, isInterrupted } from "@langchain/langgraph"; import { TryAgent } from "@tryagent/sdk"; const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY!, }); const runId = "order-review-ord_4821"; const config = { configurable: { thread_id: runId } }; const result = await graph.invoke( { runId, orderId: "ord_4821" }, config, ); if (isInterrupted(result)) { const activeInterrupt = result[INTERRUPT][0]; const payload = activeInterrupt.value as TryAgentInterruptPayload; const escalation = await tryagent.escalate( payload.policy, payload.tryagentInput, ); console.log({ interruptId: activeInterrupt.id, escalationId: escalation.id, runId, }); } ``` ## 3. Resume from the signed callback When TryAgent calls your `resume.url`, verify the callback signature first with [`webhooks.constructEvent`](/sdk/webhooks). Then resume the same LangGraph thread with `new Command({ resume })`. ```typescript theme={null} import { Command } from "@langchain/langgraph"; export async function POST(request: Request) { const event = await tryagent.webhooks.constructEvent({ payload: await request.text(), signature: request.headers.get("x-tryagent-signature"), secret: process.env.TRYAGENT_WEBHOOK_SECRET!, }); await graph.invoke( new Command({ resume: { escalationId: event.escalationId, choice: event.choice, answeredBy: event.answeredBy, }, }), { configurable: { thread_id: event.runId, }, }, ); return Response.json({ ok: true }); } ``` The object passed as `resume` becomes the return value of the original `interrupt()` call. In the first example, that means `decision.choice` and `decision.escalationId` are available inside the `approval` node after resume. ## Rules for TryAgent integrations * Use `runId` as the LangGraph `thread_id`. * Keep interrupt payloads JSON-serializable. * Do not perform non-idempotent side effects before `interrupt()` in the same node. If the node is re-entered on resume, that code can run again. * Prefer publishing the TryAgent escalation from the caller after LangGraph has returned an interrupt. * For sequential approvals, resume each interrupt as it appears. For parallel interrupts, pair resume values with the interrupt IDs returned by LangGraph. See the runnable [examples](/examples) for complete stock research and refund approval workflows using this pattern. # TryAgent docs Source: https://tryagentai.mintlify.app/index Human decision boundaries for AI agents. # TryAgent TryAgent gives AI agents one safe way to pause for a human decision. Your agent sends the blocked question, evidence, choices, and resume target. TryAgent routes it to the right reviewers, records the decision, and calls your workflow back when it can continue. Use TryAgent when an agent should not guess: missing documents, conflicting policy, risky account actions, approvals, or any point where an auditable human choice should unblock the run. Create policies, issue API keys, and review escalations in the TryAgent app. 100 escalations free each month. Start with the [quickstart](/quickstart) for the full create-escalation, signed-callback, and resume flow. For complete runnable code, use the [examples](/examples). # Quickstart Source: https://tryagentai.mintlify.app/quickstart Create an escalation and resume your workflow from a signed callback. # Quickstart ## Generate contracts from an existing agent From the repository containing your LLM calls, run the CLI front door: ```bash theme={null} npx tryagent ``` The guided flow signs you in (signup included), sets up the repo, then walks your agents' decision points one at a time: TryAgent discovers each LLM call locally, sends only relevant redacted source context for remote semantic analysis (with your consent), explains the four autonomy modes — `required`, `reviewed`, `sampled`, `autonomous` — with a deterministic recommendation from the call's decision surface, lets you accept, edit in `$VISUAL` or `$EDITOR`, regenerate, or skip each draft, wraps exact LLM call expressions with concrete TryAgent guards, and applies the complete validated change set after one confirmation. Each step is also its own command: ```bash theme={null} npx tryagent status # where you stand, and the next step npx tryagent agents list # decision points with coverage and autonomy npx tryagent agents walk # the walkthrough on its own npx tryagent rubrics push # publish reviewed rubric files ``` Plain `npx tryagent scan` remains read-only for CI. The runtime integration below shows the contract the generated wiring targets. 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](/examples). ## 1. Create a policy in TryAgent In [TryAgent](https://tryagent.ai), 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. ## 2. Configure your runtime 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](#manage-escalations-from-the-sdk). ```bash theme={null} export TRYAGENT_API_KEY="ain_live_..." export TRYAGENT_WEBHOOK_SECRET="whsec_..." ``` Install the SDK: ```bash theme={null} pnpm add @tryagent/sdk ``` ## 3. Send the escalation ```typescript theme={null} 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: ` * `x-tryagent-signature: v1=` 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](/guides/langgraph-interrupts). ```typescript theme={null} 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: ```typescript theme={null} 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` | # Client Source: https://tryagentai.mintlify.app/sdk/client Create and configure the TryAgent SDK client. # Client Install the SDK and create a client. The client is the entry point for every SDK call. ```bash theme={null} pnpm add @tryagent/sdk ``` ```typescript theme={null} import { TryAgent } from "@tryagent/sdk"; const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY!, }); ``` ## Authentication Provide exactly one of `apiKey`, `token`, or `getToken`. Passing zero or more than one throws at construction. | Option | Type | Use it for | | ---------- | --------------------------------- | --------------------------------------------------------------------------------------------- | | `apiKey` | `string` | Agent runtimes creating escalations. A workspace key that looks like `ain_live_...`. | | `token` | `string` | A static user bearer token. An alternative to a scoped API key for reviewer and read methods. | | `getToken` | `() => string \| Promise` | A user token fetched lazily per request — refresh it without recreating the client. | ```typescript theme={null} // Agent runtime — create escalations with a workspace API key. const agent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY! }); // Reviewer / dashboard — read and decide on behalf of a user. const reviewer = new TryAgent({ getToken: () => session.getAccessToken(), }); ``` Read and reviewer methods (`list`, `get`, `acknowledge`, `decide`, `cancel`) work with an `ain_live_` API key or a workspace user `token`/`getToken`. Keys are issued with all escalation scopes (`escalations:write`, `:read`, `:acknowledge`, `:decide`, `:cancel`) by default; pass a narrower `scopes` array to `POST /api-keys` to restrict one. A call missing the scope returns `403`. ## Options | Option | Default | Description | | --------- | ------------------------- | ----------------------------------------------------------------------------------------- | | `baseUrl` | `https://api.tryagent.ai` | Override only for local development or tests. | | `fetch` | global `fetch` | Supply a custom `fetch` implementation (proxies, instrumentation, non-standard runtimes). | ```typescript theme={null} const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY!, baseUrl: "http://localhost:4000", }); ``` ## Resources * [Escalations](/sdk/escalations) — create, read, and decide on escalations. * [Webhooks](/sdk/webhooks) — verify signed resume callbacks. * [Errors](/sdk/errors) — handle API, network, and signature failures. # Errors Source: https://tryagentai.mintlify.app/sdk/errors Handle API and network errors from the SDK. # Errors The SDK throws `ApiError` for non-2xx API responses and network failures. ```typescript theme={null} import { ApiError, TryAgent } from "@tryagent/sdk"; const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY!, }); try { await tryagent.escalate("orders.auth_doc", input); } catch (error) { if (error instanceof ApiError) { console.error(error.status, error.requestId, error.body); throw error; } throw error; } ``` `ApiError.status` is `0` for network failures. For API responses, `status` is the HTTP status code, `body` contains the parsed response when available, and `requestId` is populated from the response headers when present. ## WebhookSignatureError `webhooks.constructEvent` throws `WebhookSignatureError` when the `x-tryagent-signature` header fails HMAC verification. Treat it as a `401` — a verified event is safe to act on, an unverified one is not. ```typescript theme={null} import { WebhookSignatureError } from "@tryagent/sdk"; try { const event = await tryagent.webhooks.constructEvent({ payload: body, signature: request.headers.get("x-tryagent-signature"), secret: process.env.TRYAGENT_WEBHOOK_SECRET!, }); } catch (error) { if (error instanceof WebhookSignatureError) { return Response.json({ error: "Invalid signature" }, { status: 401 }); } throw error; } ``` # Escalations Source: https://tryagentai.mintlify.app/sdk/escalations Create, read, and decide on escalations from the SDK. # Escalations The `escalations` resource covers the full lifecycle. Creating escalations uses a workspace `apiKey`; the read and reviewer methods act on behalf of a user, so use a client built with `token` or `getToken` (see [Client](/sdk/client)). ## escalate Convenience wrapper that creates an escalation under a policy. The policy key is the first argument; everything else is the decision content. ```typescript theme={null} const escalation = await tryagent.escalate("orders.auth_doc", { agentId: "order-agent", runId: "run_4821", 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" }, ], resume: { mode: "webhook", url: "https://api.example.com/tryagent/resume", secret: process.env.TRYAGENT_WEBHOOK_SECRET!, }, }); console.log(escalation.id, escalation.status); // esc_..., "open" ``` `escalate` returns as soon as TryAgent creates the escalation — it does not wait for a reviewer. Required fields are `agentId`, `runId`, `subject`, `question`, `evidence`, and `choices`. Optional fields include `responseFields`, `severity`, `resume`, and `metadata`. ## escalations.create The same call as `escalate`, with `policy` included in the input object. Use it when you already have a fully assembled `CreateEscalationInput`. ```typescript theme={null} const escalation = await tryagent.escalations.create({ policy: "orders.auth_doc", agentId: "order-agent", runId: "run_4821", // ...the remaining decision content }); ``` ## escalations.list Returns escalations for the workspace, optionally filtered by status. Resolves to an `Escalation[]` (the SDK unwraps the API envelope). ```typescript theme={null} const open = await tryagent.escalations.list({ status: "open" }); ``` `status` accepts `open`, `acknowledged`, `decided`, `resolved`, `breached`, or `canceled`. Omit it to list all. ## escalations.get Fetch a single escalation by id, including its current `status`, `decision`, and learned `recommendation` when present. ```typescript theme={null} const escalation = await tryagent.escalations.get("esc_123"); ``` ## escalations.acknowledge Mark an open escalation as being worked on. Only `open` escalations can be acknowledged. ```typescript theme={null} await tryagent.escalations.acknowledge("esc_123"); ``` ## escalations.decide Record a reviewer decision and resolve the escalation. `choice` must match one of the escalation's choice ids. Include `response` when the escalation defined `responseFields`. ```typescript theme={null} await tryagent.escalations.decide("esc_123", { choice: "continue", reason: "Signature verified out of band.", response: { approvedLimit: 5000, riskLevel: "low" }, }); ``` Deciding triggers the signed resume callback to your `resume.url`. Verify it with [`webhooks.constructEvent`](/sdk/webhooks). ## escalations.cancel Cancel an escalation without recording a choice — for example, when the underlying run is abandoned. ```typescript theme={null} await tryagent.escalations.cancel("esc_123", { reason: "Order withdrawn." }); ``` Both `decide` and `cancel` reject escalations already in a terminal state (`decided`, `resolved`, `breached`, `canceled`). # Python Source: https://tryagentai.mintlify.app/sdk/python Create and manage TryAgent escalations from Python agents and workflows. # Python Use the Python SDK from agents, workflow workers, and reviewer tools that need to create or manage TryAgent escalations. ## Install The Python SDK is available in this repository and is not published to PyPI yet. From a checkout, install it locally: ```bash theme={null} python3 -m pip install -e packages/python-sdk ``` After the first PyPI release, install it with: ```bash theme={null} python3 -m pip install tryagent ``` ## Create a client ```python theme={null} import os from tryagent import TryAgent tryagent = TryAgent(api_key=os.environ["TRYAGENT_API_KEY"]) ``` Client options are Pythonic snake\_case: ```python theme={null} tryagent = TryAgent( api_key=os.environ["TRYAGENT_API_KEY"], base_url="http://localhost:4000", ) ``` Provide exactly one of `api_key`, `token`, or `get_token`. ## Send an escalation Payload fields are the TryAgent API fields, so keep them camelCase inside the request body. ```python theme={null} import os from tryagent import TryAgent tryagent = TryAgent(api_key=os.environ["TRYAGENT_API_KEY"]) escalation = tryagent.escalate( "orders.auth_doc", { "agentId": "order-agent", "runId": "run_4821", "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"}, ], "resume": { "mode": "webhook", "url": "https://api.example.com/tryagent/resume", "secret": os.environ["TRYAGENT_WEBHOOK_SECRET"], }, }, ) print(escalation["id"], escalation["status"]) ``` Use `tryagent.escalations.create({...})` when you already have a complete input object that includes `policy`. ## Manage escalations ```python theme={null} open_escalations = tryagent.escalations.list(status="open") escalation = tryagent.escalations.get("esc_123") tryagent.escalations.acknowledge("esc_123") tryagent.escalations.decide( "esc_123", { "choice": "continue", "reason": "Signature verified out of band.", "response": {"approvedLimit": 5000, "riskLevel": "low"}, }, ) tryagent.escalations.cancel("esc_456", {"reason": "Order withdrawn."}) ``` ## Verify resume callbacks When a reviewer decides, TryAgent sends a signed callback to your `resume.url`. Pass the raw body bytes or string to `construct_event`; do not parse JSON before verification. ```python theme={null} import os from tryagent import TryAgent, WebhookSignatureError tryagent = TryAgent(api_key=os.environ["TRYAGENT_API_KEY"]) def handle_resume(raw_body: bytes, signature: str | None) -> tuple[dict[str, bool], int]: try: event = tryagent.webhooks.construct_event( payload=raw_body, signature=signature, secret=os.environ["TRYAGENT_WEBHOOK_SECRET"], ) except WebhookSignatureError: return {"ok": False}, 401 resume_workflow( event["runId"], { "escalationId": event["escalationId"], "choice": event.get("choice"), "answeredBy": event.get("answeredBy"), "answeredAt": event.get("answeredAt"), }, ) return {"ok": True}, 200 ``` You can also use the standalone helpers: ```python theme={null} from tryagent import construct_webhook_event, verify_webhook_signature ``` ## ApiError The SDK raises `ApiError` for non-2xx API responses and network failures. ```python theme={null} from tryagent import ApiError try: tryagent.escalate("orders.auth_doc", input) except ApiError as error: print(error.status, error.request_id, error.body) raise ``` `error.status` is `0` for network failures. For API responses, `error.body` contains the parsed response when available and `error.request_id` comes from the response headers when present. # Webhooks Source: https://tryagentai.mintlify.app/sdk/webhooks Verify signed resume callbacks from TryAgent. # Webhooks When a reviewer decides — or a policy timeout path applies — TryAgent sends a signed `POST` to your escalation's `resume.url`. The SDK verifies the signature for you so you never hand-roll HMAC. ## The signature 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 applies. The `resolvedBy` field (`human` or `timeout`) carries the same distinction. * `x-tryagent-delivery: ` * `x-tryagent-signature: v1=` Verification runs on Web Crypto, so it works unchanged on Node 20+, edge runtimes, Deno, and Workers. Always pass the raw, unparsed request body. Re-serializing JSON changes the bytes and breaks the signature. Callbacks without `resume.secret` are unsigned — always set a secret in production. ## webhooks.constructEvent Verifies the signature and returns the typed event. Throws [`WebhookSignatureError`](/sdk/errors) when verification fails, so a returned event is always safe to act on. ```typescript theme={null} import { TryAgent, WebhookSignatureError } from "@tryagent/sdk"; const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY! }); export async function POST(request: Request) { const body = await request.text(); try { const event = await tryagent.webhooks.constructEvent({ payload: body, signature: request.headers.get("x-tryagent-signature"), secret: process.env.TRYAGENT_WEBHOOK_SECRET!, }); await resumeWorkflow(event.runId, { escalationId: event.escalationId, choice: event.choice, answeredBy: event.answeredBy, answeredAt: event.answeredAt, }); return Response.json({ ok: true }); } catch (error) { if (error instanceof WebhookSignatureError) { return Response.json({ error: "Invalid signature" }, { status: 401 }); } throw error; } } ``` ## webhooks.verify Returns a `boolean` instead of throwing or parsing — use it when you want to branch on validity yourself. ```typescript theme={null} const valid = await tryagent.webhooks.verify({ payload: body, signature: request.headers.get("x-tryagent-signature"), secret: process.env.TRYAGENT_WEBHOOK_SECRET!, }); ``` Both methods are also exported as standalone functions, `constructWebhookEvent` and `verifyWebhookSignature`, for use without a client instance. ## Event shape `constructEvent` resolves to a `WebhookResumeEvent`: | Field | Type | Description | | -------------- | ---------------------- | --------------------------------------------------------------- | | `escalationId` | `string` | The escalation that was resolved. | | `runId` | `string` | The durable run handle to resume. | | `agentId` | `string` | The agent that paused. | | `policy` | `string` | The policy key the escalation ran under. | | `choice` | `string \| null` | The selected choice id. `null` when canceled. | | `response` | `object` | Structured values when the escalation defined `responseFields`. | | `resolvedBy` | `"human" \| "timeout"` | Whether a reviewer decided or the SLA timeout applied. | | `answeredBy` | `string \| null` | The reviewer name, or `null` for a timeout. | | `answeredAt` | `string` | ISO timestamp of the decision. |