SendErrorKind. Permanent failures — a blocked user, a deleted chat, a revoked token — short-circuit instead of exhausting the retry budget against a dead target.
Quick Start
1
Benefit automatically
Any adapter that inherits
BasePlatformAdapter gains status-code-aware short-circuiting with no code change. A permanent failure returns immediately instead of retrying max_retries times.2
Inspect the result
After a delivery, read
error_kind and retryable to react — rotate credentials on auth_fatal, mark a target dead on forbidden.How It Works
Failures are classified once at the boundary viaclassify_error(exc). Only retryable kinds are retried; provably-permanent kinds short-circuit immediately, and the router records the dead target by kind.
The retry loop (BasePlatformAdapter._send_with_retry) stamps chat_id onto the classified SendResult, so the caller can act on result.error_kind without re-parsing the error string.
Which Kind Gets Returned?
The classifier keys off the HTTP/platform status code first, then falls back to a small set of cross-platform substrings when no status is present.asyncio.TimeoutError, TimeoutError, ConnectionError, and OSError are treated as transient before the text fallback runs.
SendErrorKind Reference
Seven members, spelled exactly as inpraisonaiagents/bots/base.py.
The enum is a
str-Enum: SendErrorKind.FORBIDDEN.value == "forbidden".
Writing a Custom Adapter
Overrideclassify_error to map your platform SDK’s native exceptions into the taxonomy with full fidelity. Fall through to classify_send_error for the shared status/text-aware default.
classify_error delegates to classify_send_error, so an adapter that skips this override still short-circuits standard HTTP failures.
Serialization & Observability
SendResult.to_dict() exposes error_kind as the enum value (a plain string) plus the derived retryable flag — ready for log pipelines and dashboards.
error_kind is None on success. Record dead targets by kind so dashboards read forbidden: bot was kicked instead of an opaque str(exc).
Best Practices
Override classify_error in your adapter
Override classify_error in your adapter
The default’s text fallback is deliberately narrow so it stays cross-platform. Your SDK’s typed exceptions give perfect fidelity — map them directly and fall through to
classify_send_error for the rest.Never downgrade a permanent failure to retryable
Never downgrade a permanent failure to retryable
A blocked user or deleted chat cannot be recovered by retry. Short-circuiting is a feature, not a bug — keep
retryable=False for forbidden, target_not_found, auth_fatal, and invalid_request.Persist dead targets by error_kind, not str(exc)
Persist dead targets by error_kind, not str(exc)
Record the failure by kind (e.g.
reason="forbidden: bot was kicked"), matching what DeliveryRouter now does. Machine-readable kinds make dashboards and alerts readable.Treat UNKNOWN as retryable-by-default
Treat UNKNOWN as retryable-by-default
unknown preserves the historical retry-everything behaviour and prevents silent drops of failures the classifier can’t name. Do not special-case it into a permanent failure.This is a fully backward-compatible change. Existing YAML, existing Python
Agent(...) code, and existing custom adapters that don’t override classify_error all keep working — they simply gain automatic status-code-aware short-circuiting via the default implementation.Related
Durable Outbound Delivery
Retry-with-backoff and dead-letter queue mechanics
Gateway Channel Supervision
Dead-target parking and degraded credential states

