Skip to content

fix: restore _delivery.py removed while still imported by durable delivery#2129

Draft
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/critical-bug-investigation-3971
Draft

fix: restore _delivery.py removed while still imported by durable delivery#2129
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/critical-bug-investigation-3971

Conversation

@cursor

@cursor cursor Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Bug and impact

PR #2054 (fix: remove three orphaned wrapper modules) deleted bots/_delivery.py, believing it was superseded by bots/delivery.py. Those modules are unrelated:

  • delivery.py β€” DeliveryRouter / ChannelDirectory (proactive routing)
  • _delivery.py β€” DurableDelivery, deliver_with_retry, deliver_chunked (crash-safe outbound delivery)

_durable_adapter.py and bots/__init__.py still import from ._delivery, so any code path that enables durable outbound delivery crashes with ModuleNotFoundError. This breaks crash-safe channel reply delivery added in PR #2036.

Concrete trigger: from praisonai.bots import DurableDelivery or enabling DurableAdapterMixin.setup_durable_delivery(outbox_path=...).

Root cause

Dead-code removal grep only checked for UnifiedDelivery / create_delivery call sites and missed active imports of DurableDelivery and related exports.

Fix

  • Restore bots/_delivery.py from the pre-deletion revision
  • Add tests/unit/bots/test_delivery_imports.py regression tests for the public exports

Validation

  • python3 -c "from praisonai.bots import DurableDelivery" succeeds
  • pytest tests/unit/bots/test_delivery_imports.py β€” 5 passed
Open in WebΒ View AutomationΒ 

Summary by CodeRabbit

  • New Features

    • Added adaptive message delivery with support for typing indicators and automatic message chunking for long content.
    • Implemented retry mechanism with exponential backoff for failed message deliveries.
    • Added durable delivery layer with message persistence and queue processing for reliable message handling.
  • Tests

    • Added unit tests for delivery system exports and functionality.

…ivery

PR #2054 deleted bots/_delivery.py believing it was superseded by
bots/delivery.py, but delivery.py only provides DeliveryRouter and
ChannelDirectory. DurableDelivery, deliver_with_retry, and deliver_chunked
remain referenced by _durable_adapter.py and bots/__init__.py, causing
ModuleNotFoundError on any durable outbound delivery usage.

Restore the module and add regression tests for the public exports.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more β†’

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account β†’

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us β†’

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor
βœ… Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d05d1d0c-4134-486a-8aea-238687e08fa3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
πŸ“ Walkthrough

Walkthrough

Adds a new _delivery.py module implementing a complete async message delivery layer for PraisonAI bots: a MessageSender protocol, UnifiedDelivery for platform-adaptive chunking/streaming, deliver_with_retry with exponential backoff, deliver_chunked, DurableDelivery with outbox persistence and drain, and a regression test for public exports.

Changes

Bot Delivery Layer

Layer / File(s) Summary
MessageSender protocol, module setup, and factory
src/praisonai/praisonai/bots/_delivery.py
Defines module-level imports, logging, the public MessageSender async protocol, the create_delivery factory, and the __all__ export list.
UnifiedDelivery: platform-adaptive send and streaming
src/praisonai/praisonai/bots/_delivery.py
Implements UnifiedDelivery.send_message dispatching typing indicators, progressive streaming/editing via _send_streamed for long messages on capable platforms, and chunked fallback with per-chunk rate limiting. Adds _show_typing and _acquire_rate_limit helpers.
deliver_with_retry and deliver_chunked
src/praisonai/praisonai/bots/_delivery.py
Adds deliver_with_retry with bounded exponential backoff, permanent-vs-recoverable error classification, abort signal cancellation, and per-attempt logging returning (success, error_message). Adds deliver_chunked scoping reply_to to the first chunk only.
DurableDelivery: outbox persistence and drain
src/praisonai/praisonai/bots/_delivery.py
Implements DurableDelivery.send enqueuing payloads with idempotency keys and marking outbox entries as sent or permanently/transiently failed. Implements drain_pending to process queued items with single-attempt delivery and permanent-failure escalation via raise.
Export and wiring regression tests
src/praisonai/tests/unit/bots/test_delivery_imports.py
Parameterized test checks praisonai.bots exports for durable-delivery symbols; wiring test confirms DurableAdapterMixin.setup_durable_delivery(outbox_path=None) sets durable_delivery to None.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • MervinPraison/PraisonAI#2036: Introduces the same durable, idempotent outbound delivery mechanics (deliver_with_retry, DurableDelivery, DurableAdapterMixin, outbox drain) that this PR implements.
  • MervinPraison/PraisonAI#2054: Removed _delivery.py as dead code; this PR reintroduces and fully implements that same module.

Suggested reviewers

  • MervinPraison

Poem

πŸ‡ A message hops from bot to chat,
With chunking, streaming β€” fancy that!
Retry with backoff, typed and queued,
The outbox drained, no message skewed.
Idempotent keys keep things neat,
This rabbit's delivery is complete! πŸ“¨

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately and specifically describes the main change: restoring a previously deleted module (_delivery.py) that is still being imported, fixing a critical module dependency bug.
Docstring Coverage βœ… Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/critical-bug-investigation-3971

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MervinPraison

Copy link
Copy Markdown
Owner

@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first β€” incorporate their findings.

Review areas:

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/praisonai/praisonai/bots/_delivery.py (2)

89-89: πŸ’€ Low value

Store the task reference to prevent premature garbage collection.

The task created for typing indicator is not stored, which could lead to the task being garbage collected before completion if no other references exist. While _show_typing handles exceptions internally, storing the reference is the recommended pattern.

♻️ Suggested fix
         # Show typing indicator if supported
         if typing and caps.supports_typing:
-            asyncio.create_task(self._show_typing(channel_id))
+            self._typing_task = asyncio.create_task(self._show_typing(channel_id))

Add the instance variable in __init__:

self._typing_task: Optional[asyncio.Task] = None
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/bots/_delivery.py` at line 89, The
asyncio.create_task call for self._show_typing(channel_id) at line 89 does not
store the returned task reference, which can cause premature garbage collection.
Add an instance variable self._typing_task of type Optional[asyncio.Task]
initialized to None in the __init__ method, and then assign the result of
asyncio.create_task(self._show_typing(channel_id)) to self._typing_task instead
of discarding it. This ensures the task reference is maintained and prevents it
from being garbage collected before completion.

Source: Linters/SAST tools


503-504: ⚑ Quick win

Fragile string-based permanent error detection.

The permanent error check relies on matching the exact prefix "Permanent error:" from deliver_with_retry (line 323). This coupling is fragileβ€”if the error message format changes, this detection silently fails and permanent errors get retried indefinitely.

Consider using a structured return value or a sentinel constant for error classification instead of string matching.

♻️ Suggested approach

Define a constant for the prefix and use it in both places:

# At module level
PERMANENT_ERROR_PREFIX = "Permanent error:"

# In deliver_with_retry (line 323)
return False, f"{PERMANENT_ERROR_PREFIX} {last_error}"

# In DurableDelivery.send (line 503)
permanent = error and error.startswith(PERMANENT_ERROR_PREFIX)

Or use a structured return like DeliveryResult(success: bool, error: Optional[str], permanent: bool).

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/bots/_delivery.py` around lines 503 - 504, The
permanent error detection at the mark_failed call relies on substring matching
"Permanent error:" which is fragile and tightly coupled to the error message
format in deliver_with_retry. Extract the error prefix into a module-level
constant (define it near the top of the file), then update the permanent error
check to use startswith() with this constant instead of the in operator, and
ensure the deliver_with_retry function also uses this same constant when
constructing its error message so that both locations reference the same value.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/praisonai/praisonai/bots/_delivery.py`:
- Line 89: The asyncio.create_task call for self._show_typing(channel_id) at
line 89 does not store the returned task reference, which can cause premature
garbage collection. Add an instance variable self._typing_task of type
Optional[asyncio.Task] initialized to None in the __init__ method, and then
assign the result of asyncio.create_task(self._show_typing(channel_id)) to
self._typing_task instead of discarding it. This ensures the task reference is
maintained and prevents it from being garbage collected before completion.
- Around line 503-504: The permanent error detection at the mark_failed call
relies on substring matching "Permanent error:" which is fragile and tightly
coupled to the error message format in deliver_with_retry. Extract the error
prefix into a module-level constant (define it near the top of the file), then
update the permanent error check to use startswith() with this constant instead
of the in operator, and ensure the deliver_with_retry function also uses this
same constant when constructing its error message so that both locations
reference the same value.

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf9e037a-c755-4390-8d14-9242947062ae

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5f39d66 and 34e4c63.

πŸ“’ Files selected for processing (2)
  • src/praisonai/praisonai/bots/_delivery.py
  • src/praisonai/tests/unit/bots/test_delivery_imports.py

@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions

Phase 2: FIX Valid Issues
4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
5. Push all code fixes directly to THIS branch (do NOT create a new PR)
6. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
7. If all issues are resolved, approve the PR / close the Issue
8. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task β€”β€” View job


Architecture Review Complete βœ…

Phase 1: Architecture Review

βœ… Protocol-driven design: The module properly imports protocols and capabilities from TYPE_CHECKING. Heavy implementations like UnifiedDelivery and DurableDelivery are in the wrapper layer (praisonai/), not in core SDK.

βœ… Backward compatibility: The PR correctly restores a module that was still being imported. No API changes, just restoring existing functionality. All imports verified working.

βœ… Performance: Uses lazy imports with TYPE_CHECKING, no hot-path regressions identified. No heavy module-level imports.

Phase 2: Fixed Issues

Implemented both fixes identified by CodeRabbit:

  1. Line 89: Added self._typing_task instance variable to store asyncio task reference, preventing premature garbage collection
  2. Lines 503-504, 323, 552: Extracted PERMANENT_ERROR_PREFIX constant and replaced fragile string matching with startswith() for robust error detection

Files Modified

  • src/praisonai/praisonai/bots/_delivery.py: Added typing task storage and error constant

Phase 3: Final Verdict βœ…

APPROVED - This PR correctly fixes the critical bug where _delivery.py was incorrectly deleted while still being imported. The restoration:

  • Maintains backward compatibility (no API changes)
  • Follows protocol-driven architecture (heavy implementations in wrapper)
  • Includes regression tests
  • Addresses all valid review feedback

The changes are minimal, focused, and necessary to fix the ModuleNotFoundError that breaks durable delivery functionality.


…t for error detection

- Store asyncio task reference to prevent premature garbage collection
- Extract PERMANENT_ERROR_PREFIX constant for robust error detection
- Replace fragile string matching with startswith() for permanent errors

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants