Skip to content

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

Open
praisonai-triage-agent[bot] wants to merge 2 commits into
mainfrom
claude/issue-3167-20260718-1615
Open

fix: dedupe conversation-persistence session flow into one owner#3170
praisonai-triage-agent[bot] wants to merge 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
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

1 participant