Skip to content

persist provider threads per conversation - #222

Merged
pmbstyle merged 3 commits into
mainfrom
feature/persistent-conversation-sessions
Aug 12, 2026
Merged

persist provider threads per conversation#222
pmbstyle merged 3 commits into
mainfrom
feature/persistent-conversation-sessions

Conversation

@pmbstyle

@pmbstyle pmbstyle commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • keep one durable provider thread for each active Alice conversation, keyed by channel, conversation scope, and chat identity
  • resume saved threads across provider processes and application restarts, while resetting safely when model, runtime, or tool-catalog configuration changes
  • serialize turns within a conversation and store only redacted session references in logs

Root cause and fix

The main conversation router computed the scoped session key, but its planner call used the raw provider. When the planner selected mode=reply, the early return bypassed the keyed tool-loop path, so ordinary chat replies remained ephemeral.

The planner now receives the same scoped conversation key through a narrow provider delegate. System and internal routes keep distinct scoped identities, while worker and helper calls without a conversation key remain isolated and ephemeral.

Validation

  • focused provider-session and routing suites: 98 tests passed
  • full pytest suite passed with one expected Windows-only skip
  • repository-wide Ruff, Black, MyPy, and diff checks passed
  • post-restart ordinary Telegram conversation created a distinct durable user-chat session and completed through the planner reply route

Summary by CodeRabbit

  • New Features

    • Codex-powered conversations can now resume across requests, preserving context for up to 30 days.
    • Conversation sessions are isolated by chat and scope, with incremental updates sent when possible.
    • Invalid, expired, or incompatible sessions are automatically recreated.
    • Streaming, tool-enabled, retry, and fallback responses consistently support session continuity.
  • Bug Fixes

    • Improved recovery from failed or unreadable session state.
    • Prevented concurrent turns within the same conversation from interfering with one another.

@pmbstyle
pmbstyle marked this pull request as ready for review August 12, 2026 23:26
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pmbstyle, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8db2d1c9-161a-4ca3-9556-9417ab4e73bd

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0b2d3 and 5a2c2db.

📒 Files selected for processing (2)
  • src/octopal/infrastructure/providers/codex_provider.py
  • tests/test_codex_provider_sessions.py
📝 Walkthrough

Walkthrough

Codex completion now supports persistent, keyed sessions with 30-day expiration, session validation, incremental input, atomic state writes, and reset handling. The router derives and propagates scoped session keys across planning, tool, retry, fallback, and plain completion paths.

Changes

Codex resumable sessions

Layer / File(s) Summary
Persistent Codex session execution
src/octopal/infrastructure/providers/codex_provider.py
The provider stores session metadata, resumes compatible threads, sends incremental input, resets invalid sessions, serializes keyed turns, and preserves ephemeral behavior for unkeyed calls.
Scoped session-key routing
src/octopal/runtime/octo/route_completion.py, src/octopal/runtime/octo/router.py
The router derives session keys from channel, conversation scope, and chat ID. Provider keyword arguments pass through planning, tool, retry, fallback, and plain completion calls.
Session and routing validation
tests/test_codex_provider_sessions.py
Tests cover persistence, resumption, incremental input, reset behavior, serialization, isolation, ephemeral cleanup, and router propagation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🔵 Low · up to 7a0b2

The change makes provider threads durable across conversation turns and restarts. A fallback path can omit tool results in some context-mismatch cases, and concurrent processes sharing state can occasionally reset persisted sessions; these are bounded follow-up risks, so the PR is mergeable with explicit owner awareness.

Sequence Diagram(s)

sequenceDiagram
  participant Router
  participant CodexProvider
  participant CodexSessionStore
  participant CodexAppServer
  Router->>CodexProvider: Send completion with codex_session_key
  CodexProvider->>CodexSessionStore: Load session metadata
  CodexProvider->>CodexAppServer: Resume thread with incremental input
  CodexAppServer-->>CodexProvider: Return turn result
  CodexProvider->>CodexSessionStore: Persist updated metadata
Loading
🚥 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 and concisely describes the main change: persisting provider threads for each conversation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/persistent-conversation-sessions

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/octopal/infrastructure/providers/codex_provider.py (2)

396-406: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

The turn lock is instance-scoped.

self._session_locks lives on the provider instance. Two CodexProvider instances in one process that share a state_dir can run concurrent turns against the same persisted thread_id. If the runtime creates one provider per process, no action is needed. If several instances can exist, move the lock registry to module scope keyed by (state_dir, session_ref).

🤖 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/octopal/infrastructure/providers/codex_provider.py` around lines 396 -
406, Make session-turn locking shared across CodexProvider instances by moving
the _session_locks registry to module scope and keying entries by both state_dir
and session_ref. Update the turn path around _run_session_turn and provider
initialization/access accordingly, while preserving the existing per-session
asyncio.Lock behavior.

77-97: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider cross-process safety for the registry read-modify-write.

put and delete read the full JSON file, mutate it, then rewrite it. Inside one event loop this cycle is atomic because no await occurs. Across processes it is not. If two Octopal processes share the same state_dir, one write can drop entries written by the other. The visible effect is an unnecessary session reset, so this is not urgent. If you expect several processes on one state directory, store one file per session_ref or add an advisory file lock around the read-modify-write.

🤖 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/octopal/infrastructure/providers/codex_provider.py` around lines 77 - 97,
Make the session registry update cross-process safe by changing the
read-modify-write flow in _CodexSessionRegistry.put and delete: either persist
each session_ref in its own file or protect the existing JSON mutations with an
advisory file lock spanning _read, mutation, and _write. Preserve current
session data and deletion behavior while preventing concurrent processes from
overwriting each other’s entries.
tests/test_codex_provider_sessions.py (1)

242-270: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the TTL and corrupt-state reset paths.

_CodexSessionStore.get returns "expired" after CODEX_SESSION_TTL_DAYS and "invalid_state" for a malformed record. Neither path is covered. Both delete persisted state and force a fresh thread/start. Add two cases: write a registry entry with an old updated_at, and write a registry entry that misses thread_id. Assert that the next call performs thread/start and not thread/resume.

🤖 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 `@tests/test_codex_provider_sessions.py` around lines 242 - 270, Add separate
tests alongside
test_resume_failure_falls_back_to_full_context_and_replaces_mapping for expired
and malformed session records. Seed codex_sessions.json with an entry whose
updated_at exceeds CODEX_SESSION_TTL_DAYS, and another entry missing thread_id;
invoke complete_with_tools with the matching codex_session_key and assert the
client calls thread/start without thread/resume, preserving the expected
fresh-session behavior.
🤖 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/octopal/infrastructure/providers/codex_provider.py`:
- Around line 736-747: Update the mismatch fallback in the fingerprint suffix
selection to preserve the trailing tool-result messages along with the latest
user message. When scanning reversed messages, collect the contiguous terminal
tool messages and include them with the most recent user message, while
retaining the existing behavior for ordinary user-ended requests.

---

Nitpick comments:
In `@src/octopal/infrastructure/providers/codex_provider.py`:
- Around line 396-406: Make session-turn locking shared across CodexProvider
instances by moving the _session_locks registry to module scope and keying
entries by both state_dir and session_ref. Update the turn path around
_run_session_turn and provider initialization/access accordingly, while
preserving the existing per-session asyncio.Lock behavior.
- Around line 77-97: Make the session registry update cross-process safe by
changing the read-modify-write flow in _CodexSessionRegistry.put and delete:
either persist each session_ref in its own file or protect the existing JSON
mutations with an advisory file lock spanning _read, mutation, and _write.
Preserve current session data and deletion behavior while preventing concurrent
processes from overwriting each other’s entries.

In `@tests/test_codex_provider_sessions.py`:
- Around line 242-270: Add separate tests alongside
test_resume_failure_falls_back_to_full_context_and_replaces_mapping for expired
and malformed session records. Seed codex_sessions.json with an entry whose
updated_at exceeds CODEX_SESSION_TTL_DAYS, and another entry missing thread_id;
invoke complete_with_tools with the matching codex_session_key and assert the
client calls thread/start without thread/resume, preserving the expected
fresh-session behavior.
🪄 Autofix

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 Plus

Run ID: 9d13d282-76c5-43be-a9a1-adad0f35cb0d

📥 Commits

Reviewing files that changed from the base of the PR and between 3e01594 and 7a0b2d3.

📒 Files selected for processing (4)
  • src/octopal/infrastructure/providers/codex_provider.py
  • src/octopal/runtime/octo/route_completion.py
  • src/octopal/runtime/octo/router.py
  • tests/test_codex_provider_sessions.py

Comment thread src/octopal/infrastructure/providers/codex_provider.py
@pmbstyle
pmbstyle merged commit 2e0ac6d into main Aug 12, 2026
7 checks passed
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.

1 participant