Quick Start
1
It's on by default
Every adapter calls
render_failure_reply at the failure-handling seam — there’s no config knob to enable. A normal agent already benefits:2
Inspect a failure yourself
Call
render_failure_reply on any failed run — an AgentRunOutcome, a PraisonAIError, or a plain exception:3
Record the reason code
Log
reply.reason_code for aggregation. Compare against the REASON_* module constants, not the string literals:Before this primitive, a failed turn delivered a generic string like
Error: 401 Unauthorized — or a silent downgrade. The rich taxonomy in praisonaiagents.errors / praisonaiagents.run_outcome (with retryability and remediation_hint) never reached the user, and every adapter re-invented the mapping.Which primitive?
classify_final (silence) and render_failure_reply (failure) are easy to confuse — one handles empty finals, the other handles failed runs.
FailureReply
render_failure_reply(outcome) is a single, pure, channel-agnostic decision that maps any failed run into a typed FailureReply.
- Import:
from praisonaiagents.bots import FailureReply, render_failure_reply - Signature:
render_failure_reply(outcome: AgentRunOutcome | PraisonAIError | Any) -> FailureReply - Zero deps. Inputs are duck-typed — an
AgentRunOutcome, aPraisonAIErrorsubclass, or any object exposingerror_category/status. Anything else degrades to a generic (but still visible)unknownreply — it never raises.
Reason codes
Compare against theREASON_* module constants, not the string literals.
REASON_RATE_LIMIT, REASON_OVERLOADED, and REASON_TIMEOUT are the always-retryable set. Other reasons still land as retryable=True when the source outcome’s is_retryable() was affirmative (e.g. invalid_output → format_error reason, but retryable).
Mapping tables
_ERROR_KIND_TO_REASON maps a PraisonAIError.error_category to a reason code:
Special case: if the error carries a
config_key and the category is format_error, the reason becomes missing_key — steering the user to onboarding rather than re-auth.
_TERMINATION_TO_REASON maps an AgentRunOutcome.context["termination_reason"]:
When neither map hits, the fallback ladder is
error_category → status (timeout / cancelled / invalid_output → format_error) → unknown.
How It Works
The module never importsPraisonAIError / AgentRunOutcome at runtime — it reads fields by duck-typing, keeping it a zero-dependency leaf.
A concrete remediation_hint on a PraisonAIError (e.g. PraisonAIConfigError) always wins over the static per-class copy. Retryability is preserved from the source outcome — invalid_output maps to format_error but stays retryable=True when outcome.is_retryable() was True.
User Interaction Flow
A user asks the assistant on Slack to summarise a report. The configured OpenAI key has expired. Instead ofError: 401 Unauthorized, the assistant replies with a concrete next step, and the adapter records reason_code="auth_expired" for the operator.
Common Patterns
Offer a retry affordance only when it makes sense — inspectreply.retryable before showing a “Retry” button:
reason_code — because it’s a bounded, machine-readable enum, dashboards bucket failures by class (auth_expired, rate_limit, budget_exhausted, …) without regex on prose.
Custom remediation_hint for domain errors — a PraisonAIConfigError that sets remediation_hint="Run the migration first: alembic upgrade head." delivers that hint verbatim, keyed by missing_key.
Best Practices
Never hand-roll 'Error: …' strings
Never hand-roll 'Error: …' strings
Route every failed turn through
render_failure_reply so downgrade messaging stays consistent across channels.Compare against REASON_* constants, not string literals
Compare against REASON_* constants, not string literals
The reason codes are stable module constants for exactly this reason — import
REASON_RATE_LIMIT rather than typing "rate_limit".A hint is more precise than a category
A hint is more precise than a category
When raising a custom error, set
remediation_hint on the exception so the user sees the concrete next step rather than the generic per-class copy.Trust reply.retryable
Trust reply.retryable
retryable mirrors the source outcome — do not re-derive retry policy from the text.Related
Visible-Outcome Guarantee
The empty-final counterpart — classify_final for blank turns
Intentional Silence
The deliberate no-reply path
Degraded Delivery
The presentation-path counterpart
Agent Run Outcomes
The input taxonomy render_failure_reply reads
Introduced in PraisonAI commit 892b9fb (fixes #3799).

