5.3 Error Handling in Agent Flows
Key Takeaways
- Design explicit error-handling patterns in agent flows—scopes with run-after (try/catch style), retries for transient faults, and controlled terminate paths—so the agent always receives a usable response contract.
- Respond to the agent with structured success/failure outputs and user-facing messages; never leak raw stack traces or secrets into chat.
- Know major flow-related agent error codes: FlowActionBadRequest, FlowActionException, FlowActionTimedOut, BindingKeyNotFoundError, and capacity/auth related failures.
- Use Configure run after for Failed/Timed out/Skipped paths; apply retry policies (fixed or exponential) only where retries are safe and idempotent.
- Think dead-letter style for agents: capture failed context (correlation id, inputs summary, error code), notify owners or queues, and offer user fallback or human handoff instead of silent failure.
5.3 Error Handling in Agent Flows
Quick Answer: Wrap risky connector work in a Try scope; add a Catch scope that runs after the Try scope fails (and often after timed out). Use retry policies for transient HTTP/connector faults. Always Respond to the agent with structured outputs (
Succeeded,UserMessage, optionalErrorCode). Log failures for operators (dead-letter thinking). Map agent error codes to fixes: bad request/schema, missing outputs, 100-second timeout, capacity, and auth.
Microsoft’s integration guidance is blunt: the developer must design an error-handling pattern so the agent knows how to handle exceptions. Agent flows will not magically convert every connector failure into a graceful chat reply. AB-620 rewards designs that fail loudly to operators and politely to users.
Why agent flows need deliberate resilience
Agent flows call external systems under conversational time pressure. Failures include:
- Transient network blips and HTTP 429/5xx throttling
- Business validation errors (HTTP 400/422)
- Auth failures (401/403, expired connections, maker connection blocked)
- Timeouts when logic exceeds the agent’s ~100-second wait
- Schema/binding mistakes at the agent boundary
- DLP or capacity enforcement
Without handling, users see generic errors, makers lack correlation data, and retries may double-submit orders. Resilience design prevents all three.
Error-handling building blocks
1) Scopes and try/catch (run after)
Group related actions into a Scope (commonly named Try). Create a second Scope (Catch) and set Configure run after so Catch runs when Try has failed (and, if appropriate, has timed out). Optionally add a Finally-style scope that runs after both success and failure for cleanup logging.
| Scope | Run after | Responsibility |
|---|---|---|
| Try | Default (success path of previous) | Happy-path connectors and transforms |
| Catch | Try Failed / Timed out | Build user-safe message, set Succeeded=false, notify ops, write failure row |
| Success respond | Try Succeeded | Map results; Respond to the agent with success outputs |
| Failure respond | Catch completed | Respond to the agent with failure outputs (if not already responded) |
Critical rule: On both success and failure paths that the agent is waiting on, ensure Respond to the agent still runs with a complete output schema. An uncaught failure that skips Respond produces FlowActionException (no output / missing output).
2) Configure run after on individual actions
For finer control, set run-after on a single action:
- Is successful — default chain
- Has failed — compensation or alternate connector
- Is skipped — branch when a condition skipped upstream
- Has timed out — slow system fallback
Example: if “Create ServiceNow incident” fails, run-after Failed sends a Teams message to the service desk queue and Responds to the agent with a handoff message.
3) Retry policies
On connector/HTTP actions, configure Retry policy in settings:
| Policy idea | Use when | Avoid when |
|---|---|---|
| None | Business errors that will not self-heal | — |
| Fixed interval | Simple transient blips | Long agent wait budget already tight |
| Exponential | Throttling (429) and intermittent 5xx | Non-idempotent create without dedupe keys |
Retries help transient faults. They hurt when the action is a non-idempotent “create payment” without an idempotency key—you may create duplicates. Prefer retries on read and safe update operations; for creates, use connector upsert patterns or store a client-generated idempotency key.
Respect overall agent timeout: aggressive retries inside the pre-respond path can cause FlowActionTimedOut even if the eventual call would succeed.
4) Terminate and controlled stop
Use Terminate (or equivalent control) in deep automation branches that should not continue after a fatal configuration error—but if the agent is still waiting, Respond first with failure outputs, then terminate remaining work. Terminating without responding leaves the agent hanging until timeout.
5) Conditions as guard rails
Before expensive calls:
- If
OrderIdis empty → Respond (Succeeded=false, clearUserMessage) - Else → continue into the Try scope with the connector call
Guard rails are cheaper than catch blocks after a failed API call.
Agent-facing error contract (user messages)
Separate three channels of information:
| Channel | Audience | Content |
|---|---|---|
| UserMessage output | End user | Short, polite, actionable (“I couldn’t create the ticket. Try again or ask for an agent.”) |
| ErrorCode output | Topic logic / support | Stable code (CRM_TIMEOUT, VALIDATION) |
| Ops payload | Makers/admins | Correlation id, action name, raw connector message in logs—not in chat |
Never return secrets, tokens, connection strings, or full exception stacks to the user channel. Pair flow failures with topic On Error / fallback messaging so channel UX stays professional even when orchestration surfaces platform codes.
Sample structured outputs
| Output | Success example | Failure example |
|---|---|---|
Succeeded | true | false |
UserMessage | “Incident INC012345 was created.” | “I couldn’t reach ServiceNow. A human can help if you retry or ask for support.” |
RecordId | INC012345 | null or empty |
ErrorCode | OK | SNOW_HTTP503 |
CorrelationId | guid | same guid used in ops log |
Topics can branch on Succeeded to show Adaptive Cards vs escalate.
Dead-letter style thinking for agents
Classic integration uses a dead-letter queue for messages that cannot be processed. Agent solutions need the same idea even when the transport is a conversation:
- Capture — write failed run metadata to Dataverse, SharePoint list, or Log Analytics (who, when, inputs summary, error, correlation id).
- Notify — Teams/email to the owning queue when failure rate spikes or high-severity actions fail.
- Preserve user progress — tell the user what was not done; avoid implying a ticket exists when create failed.
- Offer recovery — retry guidance, alternate topic, or human-in-the-loop / handoff.
- Do not poison the user — one failure should not loop infinitely (
InfiniteLoopInBotContentrisk if topics re-call a failing flow without backoff).
This is “dead-letter thinking”: failed work is recorded and owned, not dropped on the floor when the chat session ends.
Platform error codes makers must map
| Error / signal | Typical cause | First fix |
|---|---|---|
| FlowActionBadRequest | Malformed call; type mismatch; missing input; unsupported parameter type | Align types; supply required inputs; refresh bindings |
| FlowActionException | Flow error or missing outputs vs agent schema | Flow checker + run history; ensure Respond outputs |
| FlowActionTimedOut | No response within ~100 seconds | Optimize queries; respond earlier; move work after Respond |
| BindingKeyNotFoundError | Inputs/outputs changed; stale bindings | Refresh/re-add flow on agent |
| FlowMakerConnectionBlocked | Maker connection not allowed for run | Run-only user connections / share flow with run-only permissions |
| ExecutionTimeout / HTTP 408/504 | Dependency too slow | Narrow queries; retries with care; async post-respond work |
| HTTP 429 / QuotaExceeded | Throttling or capacity | Backoff; reduce calls; capacity planning |
| DataLossPreventionViolation | DLP / auth policy | Align connectors and authentication with admin policy |
| EnforcementMessageC2 | Usage limit | Prepaid capacity or pay-as-you-go |
When the agent surfaces these codes in test chat, pair them with Activity map + flow run history as taught in 5.1.
End-to-end resilience pattern (reference architecture)
- Validate inputs (conditions) → early Respond on validation failure.
- Try scope: connector actions with retry on idempotent calls; timeouts tuned.
- On success: map fields → Respond to the agent (
Succeeded=true, cleanUserMessage). - Catch scope (run after failed/timed out): compose
UserMessage+ErrorCode→ write dead-letter row → notify ops → Respond to the agent (Succeeded=false). - After respond (optional): non-blocking analytics, secondary notifications.
- Agent topic: if
Succeededis false, offer retry once, then escalate to human. - Monitor: Activity failed filter + weekly review of dead-letter table.
Scenario — Contoso refund flow
Contoso’s agent can request refunds under $50 automatically and larger amounts with approval. Error handling:
- Validation catch: amount missing → user message, no ERP call.
- ERP create with exponential retry for 429 only; idempotency key = conversation id + refund request id.
- If ERP fails after retries: Catch responds
Succeeded=false, logs row, posts to Finance Teams channel with correlation id. - If amount > 50: human-in-the-loop approval; if approver rejects, Respond with clear denial message—not an exception.
- Timeout risk: ERP status poll moved after Respond once the refund id exists, so the agent is not blocked.
Scenario — Schema mismatch in production
A maker adds required output ApprovalStatus but forgets to refresh the agent. Users see FlowActionException. Fix is not “more retries”—retries cannot invent a missing schema binding. Refresh tool bindings and republish; add a CI checklist item for schema changes.
Testing error paths (non-negotiable)
Happy-path designer tests are insufficient. Before production:
- Force connector failure (invalid id) and confirm Catch Respond outputs.
- Force timeout (mock delay) and confirm user message + ops log.
- Call with missing required input from the agent tool.
- Verify retries do not create duplicate records.
- Confirm Activity shows Failed vs Complete appropriately and that transcripts remain understandable.
- Retest after each parameter rename.
Exam decision table
| Symptom | Best design response |
|---|---|
| Occasional 503 from LOB API | Retry policy + Catch fallback message |
| Invalid user input | Guard condition + Respond without calling LOB |
| Flow exceeds 100s | Respond earlier; continue work after Respond |
| Duplicate orders on retry | Idempotency key / disable blind retries on create |
| Users see raw errors | UserMessage contract + topic fallback |
| Failures invisible to ops | Dead-letter log + notification |
| Errors after parameter rename | Refresh agent bindings, not only flow retries |
Resilience checklist for AB-620
- Try/Catch scopes with correct run after settings.
- Retry only where safe; document idempotency.
- Respond on every agent-waited path with full output schema.
- User-safe messages; operator-rich logs.
- Time budget awareness for the 100-second rule.
- Dead-letter or equivalent failure capture.
- Mapped understanding of FlowAction* and binding errors.
- Channel retests after publishing fixes.
Error handling is how agent flows earn trust. On AB-620, choose patterns that keep the agent informed, the user calm, and the operator informed—using scopes, run-after, retries, terminate discipline, and structured Respond outputs as your standard resilience toolkit.
You want a Catch path to run when any action inside a Try scope fails. Which configuration implements that try/catch pattern in an agent flow?
An agent flow must call a non-idempotent “create order” API. Which error-handling choice is safest?
Users receive FlowActionTimedOut when an agent calls a flow that eventually succeeds after three minutes. What is the best remediation?