Skip to content

fix: dedupe conversation-persistence session flow into one owner#3170

Merged
Leebana merged 2 commits into
mainfrom
claude/issue-3167-20260718-1615
Jul 20, 2026
Merged

fix: dedupe conversation-persistence session flow into one owner#3170
Leebana merged 2 commits into
mainfrom
claude/issue-3167-20260718-1615

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #3167

Summary

PraisonAIDB (db/adapter.py) and PersistenceOrchestrator (persistence/orchestrator.py) each reimplemented the same create-or-resume session flow over the same persistence.conversation store, across both their sync and async surfaces. This duplication had already begun to drift. This PR gives that store-level flow a single owner.

Changes

  • New internal helper persistence/conversation/_ops.py with resume_or_create_session (sync) and aresume_or_create_session (async): given the caller's get_session result, it either creates the session (via a caller-supplied factory) or returns the existing messages.
  • PraisonAIDB.on_agent_start / aon_agent_start now call the helper, keeping their DbMessage return contract and _dispatch_async discipline.
  • PersistenceOrchestrator.on_agent_start / aon_agent_start now call the helper, keeping their ConversationMessage return contract, session caching, resume flag, and sync/async off-loop dispatch.

Behaviour preserved

  • Distinct return-type contracts (DbMessage vs ConversationMessage) unchanged.
  • Each caller keeps its own session name/metadata/user_id conventions via a build_session factory.
  • Orchestrator caches the exact ConversationSession instance it persists (same timestamps/identity).
  • Init-failure cooldown and sync/async locking in db/adapter.py untouched.
  • No CLI/YAML/Python surface changes.

Testing

  • 62 existing db/persistence unit tests pass (test_db_adapter_*, test_turso_store, test_serverless_postgres).
  • Added a standalone behaviour check covering new-session, resume, and resume=False paths for both classes (sync + async) — all pass.
  • Remaining collection errors in the wider suite are pre-existing missing-dependency issues (e.g. pydantic), unrelated to this change.

Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved conversation session resume and creation across synchronous and asynchronous workflows.
    • Ensured prior messages are restored correctly when resuming an existing session.
    • Ensured new sessions start with an empty message history.
    • Improved asynchronous handling to avoid blocking during session operations.
  • Refactor

    • Standardized session management behavior across persistence components.

#3167)

Deduplicate the create-or-resume session logic that PraisonAIDB and
PersistenceOrchestrator each reimplemented across their sync and async
surfaces. A new internal helper persistence/conversation/_ops.py owns the
store-level flow; both classes call it while keeping their distinct
return-type contracts (DbMessage vs ConversationMessage), caching, and
lock/cooldown machinery. Behaviour-preserving.

Co-authored-by: MervinPraison <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 Jul 18, 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.

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 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: b801471f-eb25-4462-8df1-b0114df8a0a7

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

Session resume-or-create logic is centralized in synchronous and asynchronous conversation helpers. The database adapter and persistence orchestrator now use these helpers while preserving their existing session caching, message conversion, and sync/async store dispatch behavior.

Changes

Conversation persistence

Layer / File(s) Summary
Shared session helpers
src/praisonai/praisonai/persistence/conversation/_ops.py
Adds synchronous and asynchronous helpers that create missing sessions or retrieve messages from existing sessions.
Synchronous session integrations
src/praisonai/praisonai/db/adapter.py, src/praisonai/praisonai/persistence/orchestrator.py
Updates synchronous agent-start hooks to delegate session handling to resume_or_create_session.
Asynchronous session integrations
src/praisonai/praisonai/db/adapter.py, src/praisonai/praisonai/persistence/orchestrator.py
Updates asynchronous agent-start hooks to use aresume_or_create_session with async store calls or thread execution for synchronous stores.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: pipeline/awaiting-merge-gate

🚥 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 clearly matches the refactor to dedupe conversation-persistence session flow.
Linked Issues check ✅ Passed The PR extracts the shared create-or-resume session helper and updates both sync and async call sites as requested.
Out of Scope Changes check ✅ Passed All changes stay within conversation-persistence refactoring and helper extraction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-3167-20260718-1615

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.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR centralizes conversation session creation and resume behavior. The main changes are:

  • Adds shared synchronous and asynchronous session helpers.
  • Uses the helpers in the database adapter and persistence orchestrator.
  • Preserves caller-specific message types, session metadata, caching, and async dispatch.
  • Builds orchestrator sessions lazily so resume paths do not read agent identity.

Confidence Score: 5/5

This looks safe to merge.

  • The lazy factories are called only when a session must be created.
  • Existing sessions no longer trigger agent identity access or discarded session construction.
  • No blocking issues remain in the changed code.

Important Files Changed

Filename Overview
src/praisonai/praisonai/persistence/conversation/_ops.py Adds shared synchronous and asynchronous create-or-resume session operations.
src/praisonai/praisonai/persistence/orchestrator.py Uses lazy session factories while preserving resumed history and session cache identity.
src/praisonai/praisonai/db/adapter.py Adopts the shared operations while preserving message conversion and asynchronous dispatch.

Reviews (2): Last reviewed commit: "fix: build orchestrator session lazily s..." | Re-trigger Greptile

Comment thread src/praisonai/praisonai/persistence/orchestrator.py Outdated
@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
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
  6. Repo routing: agent-callable tools → PraisonAI-Tools; lifecycle plugins → PraisonAI-Plugins; optional sandbox backends → PraisonAI-Plugins (praisonai.sandbox entry point) — request changes if wrongly added to praisonaiagents/

Phase 2: FIX Valid Issues
7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
8. Also independently identify and fix any gaps or issues you find in the changed code — do not rely only on prior reviewer feedback
9. Push all code fixes directly to THIS branch (do NOT create a new PR)
10. Comment a summary of exact files modified and what you skipped

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

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jul 18, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #3170

Todo

  • Read guidelines (AGENTS.md scope: Python packages only)
  • Review changed files (_ops.py, adapter.py, orchestrator.py)
  • Evaluate Greptile "resume path constructs new session" → VALID
  • Fix orchestrator to build session lazily (sync + async)
  • Run tests
  • Push & post verdict

Phase 1 — Architecture (AGENTS.md)

Check Result
Protocol-driven / routing ✅ Heavy impl stays in wrapper (praisonai/persistence, praisonai/db); _ops.py is a thin internal helper, correctly placed. Core SDK untouched.
Backward compatible DbMessage vs ConversationMessage contracts, resume flag, session caching, init-cooldown & sync/async locking all preserved. No CLI/YAML/Python surface change.
Performance ✅ No hot-path regression; async keeps asyncio.to_thread off-loading.
SDK value ✅ Genuine de-duplication — collapses a create-or-resume flow that had begun to drift into a single owner. No new params, no Agent bloat, no new deps.
Scope creep ✅ None. Minimal and focused.

Phase 2 — Valid issue fixed

Greptile (P2) — resume path constructed a discarded ConversationSession. Confirmed valid: orchestrator.on_agent_start (L182-189) and aon_agent_start (L337-344) built the session eagerly on every call, reading agent.name/agent.agent_id even when resuming — avoidable side effects/failures for agents with computed name properties, and wasted allocation.

Fix (persistence/orchestrator.py, both sync + async): moved construction into the build_session factory so it only runs on the create branch; a small created holder captures the exact persisted instance so cache identity/timestamps stay identical.

created: List[ConversationSession] = []
def _build_session() -> ConversationSession:
    agent_id = getattr(agent, "name", None) or getattr(agent, "agent_id", None)
    new_session = ConversationSession(..., agent_id=agent_id, ...)
    created.append(new_session)
    return new_session
messages = resume_or_create_session(..., build_session=_build_session, ...)
if messages is None:
    new_session = created[0]   # exact instance persisted → cache identity preserved

Note: db/adapter.py was already lazy (its build_session=lambda: only fires on the create branch), so no change needed there.

Independent review: _ops.py is clean and minimal; the sync/async split correctly mirrors each caller's dispatch discipline. No further gaps found.

Phase 3 — Tests

  • test_turso_store.py → 12 passed
  • test_db_adapter_tracing_init.py + test_db_adapter_runs_traces.py → 11 passed
  • Standalone behaviour check: verified the resume path never invokes the factory (no agent access) and the create path caches the exact created instance — sync + async. All pass.
  • Wider-suite collection errors are pre-existing missing-dep issues (pydantic etc.), unrelated.

Verdict: Approve

The refactor cleanly de-duplicates the store-level session flow, preserves all contracts, and the one valid reviewer concern (eager resume-path construction) is now fixed. Pushed to claude/issue-3167-20260718-1615 (commit 61ae7aaae).

Files modified this review: src/praisonai/praisonai/persistence/orchestrator.py (lazy build, sync + async).
Skipped: praisonai-rust, praisonai-ts (out of scope); db/adapter.py (already lazy).
· Branch

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/praisonai/praisonai/persistence/conversation/_ops.py`:
- Around line 20-47: The synchronous session-resume helper must use a
caller-provided creation callback so async stores are dispatched through the
orchestrator bridge. In
src/praisonai/praisonai/persistence/conversation/_ops.py#L20-L47, add a
create_session callback parameter and invoke it when session is absent instead
of calling store.create_session directly; in
src/praisonai/praisonai/persistence/orchestrator.py#L191-L203, pass a callback
that routes self.conversation.create_session through self._sync before caching
the session.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4a6ee3e-8a2d-420c-8120-c9ab88e94d9f

📥 Commits

Reviewing files that changed from the base of the PR and between 882143d and 17702d4.

📒 Files selected for processing (3)
  • src/praisonai/praisonai/db/adapter.py
  • src/praisonai/praisonai/persistence/conversation/_ops.py
  • src/praisonai/praisonai/persistence/orchestrator.py

Comment on lines +20 to +47
def resume_or_create_session(
store: Any,
session: Optional[ConversationSession],
session_id: str,
build_session: Callable[[], ConversationSession],
get_messages: Callable[[], List[ConversationMessage]],
) -> Optional[List[ConversationMessage]]:
"""Create the session if missing, else return its messages (sync).

Args:
store: The conversation store.
session: Result of the caller's ``get_session`` lookup (``None`` when
the session does not yet exist).
session_id: The session identifier (unused directly; kept for parity
and readability at call sites).
build_session: Factory returning the ``ConversationSession`` to create
when ``session`` is ``None`` — lets each caller keep its own name/
metadata conventions.
get_messages: Callable returning the existing messages when resuming.

Returns:
``None`` when a new session was created (no history), otherwise the list
of previously persisted messages.
"""
if session is None:
store.create_session(build_session())
return None
return get_messages()

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.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Preserve async-store dispatch in the synchronous creation path.

The sync helper directly calls create_session, bypassing the orchestrator’s coroutine bridge.

  • src/praisonai/praisonai/persistence/conversation/_ops.py#L20-L47: accept and invoke a caller-provided create_session callback.
  • src/praisonai/praisonai/persistence/orchestrator.py#L191-L203: pass lambda s: self._sync(self.conversation.create_session(s)) before caching the session.
📍 Affects 2 files
  • src/praisonai/praisonai/persistence/conversation/_ops.py#L20-L47 (this comment)
  • src/praisonai/praisonai/persistence/orchestrator.py#L191-L203
🤖 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/persistence/conversation/_ops.py` around lines 20 -
47, The synchronous session-resume helper must use a caller-provided creation
callback so async stores are dispatched through the orchestrator bridge. In
src/praisonai/praisonai/persistence/conversation/_ops.py#L20-L47, add a
create_session callback parameter and invoke it when session is absent instead
of calling store.create_session directly; in
src/praisonai/praisonai/persistence/orchestrator.py#L191-L203, pass a callback
that routes self.conversation.create_session through self._sync before caching
the session.

…gent identity

Addresses Greptile review: on_agent_start/aon_agent_start constructed a
ConversationSession eagerly on every call, reading agent.name/agent.agent_id
and building an object discarded on resume. Move construction into the
build_session factory (create-branch only) while capturing the created
instance for identical cache identity/timestamps.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison MervinPraison added pipeline/blocked:stale-final Blocked: FINAL stale after new commits and removed pipeline/blocked:stale-final Blocked: FINAL stale after new commits labels Jul 18, 2026
@praisonai-triage-agent praisonai-triage-agent Bot removed the pipeline/blocked:cooldown Blocked: post-push or @claude cooldown label Jul 18, 2026
@Leebana
Leebana merged commit 005cdc2 into main Jul 20, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:ci Blocked: CI not green on HEAD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate conversation-persistence logic in wrapper: PraisonAIDB and PersistenceOrchestrator reimplement the same session/message flow

2 participants