feat(conversations): unify native session continuation - #939
Open
Neonforge98 wants to merge 3 commits into
Open
Conversation
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 26, 2026 01:01
98d0821 to
f1420a5
Compare
This was referenced Aug 26, 2026
Neonforge98
force-pushed
the
codex/audience-routing
branch
2 times, most recently
from
August 26, 2026 03:58
60fb115 to
2ad6f12
Compare
Neonforge98
marked this pull request as draft
August 26, 2026 08:37
Neonforge98
marked this pull request as ready for review
August 26, 2026 10:08
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 26, 2026 23:50
157e735 to
8e9b3b4
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 26, 2026 23:53
5e9bded to
a91320a
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 27, 2026 16:48
8e9b3b4 to
61ef78b
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 27, 2026 16:48
ec54e67 to
fdc296b
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 27, 2026 23:25
61ef78b to
223ebfa
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
2 times, most recently
from
August 27, 2026 23:42
e801165 to
1c4a772
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 30, 2026 14:09
7f8ec08 to
8f9ad48
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 30, 2026 20:34
6fe12fe to
05f80a6
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 30, 2026 20:34
7361aff to
d601dcf
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 31, 2026 10:58
05f80a6 to
bf692a2
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 31, 2026 10:58
d601dcf to
320dbf1
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 31, 2026 11:50
bf692a2 to
1112c74
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 31, 2026 11:50
320dbf1 to
87e4b35
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 31, 2026 14:12
1112c74 to
2d86124
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 31, 2026 14:13
87e4b35 to
758b2a9
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
August 31, 2026 15:12
2d86124 to
aa7c9df
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
August 31, 2026 15:21
758b2a9 to
36e7649
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
September 1, 2026 16:52
aa7c9df to
c0b0f5a
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
September 1, 2026 16:52
36e7649 to
5c2d481
Compare
Neonforge98
force-pushed
the
feat/pm-expansion-batch
branch
from
September 2, 2026 00:11
c0b0f5a to
2b8c2f2
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
2 times, most recently
from
September 2, 2026 00:20
2d06a5a to
c941f95
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
from
September 2, 2026 00:45
c941f95 to
ce232a9
Compare
Neonforge98
force-pushed
the
codex/audience-routing
branch
2 times, most recently
from
September 2, 2026 01:09
6538c8e to
4e8928a
Compare
Define canonical conversation roots, typed events, turn intents, local continuation bindings, Cloud-plane synchronization, incremental timelines, durable publication outbox, and provider-independent sender metadata.
Convert canonical role/tool events into Codex and Claude Code native histories, bind provider session identities, preserve interrupted tool activity, resume through the selected CLI transport, and recover from provider context exhaustion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Imported provider histories (Codex app, Claude Code) could be read in ORG2 but not continued: there was no canonical owner of message content and delivery state, the window-scoped queue and a separate executions store disagreed after restarts, and nothing could materialize a conversation into a provider's own catalog so the native CLI could resume it. Interrupted tool results, Codex
is_error, and compaction were handled inconsistently across the two existing paths.Solution
Provider-native session continuation stacked on #844 as three reviewable commits:
feat(conversations): add canonical event plane and durable continuationEventStoreas the single owner of message content andpending/sent/failed; single persisteddeliveriesdocument replacing the window-scoped queue + separate executions storefeat(native): materialize and resume provider-native conversationsrefactor(conversations): unify native continuation and delivery ownersPotential risks
EventStoreand the single persisteddeliveriesdocument replace two older owners; any code still reading the window-scoped queue or the separate executions store is now dead by construction and was removed in the third commit.~/.claude/projects/<repo>/), so continued conversations appear in the native app's own history.Defects fixed during verification
ToolCall/ToolResultpair for an unresolved provider call (the provider's own representation of an interrupt), so every latersynchronize_native_conversationfailed its semantic-prefix check. The projection now mirrors the canonical owner;interrupted(not model-visible, no provider can carry it) is no longer compared,is_errorstill is.is_error: a failed or killed tool was replayed to Codex as a successfulfunction_call_output. Failures now use the exec envelope Codex's own reader restores byte-exact (exit_code1; interrupted → 130).Failed. Absence-of-evidence cases warn and returnOk(None); proven divergence still fails closed and blocks the next turn.lastSeqkeeps advancing. Inbound writes stay strictly validated.context_compactedtruncated the native projection to summary + suffix; the documented skip policy is restored.onEditSubmit, submitting the message to the agent as a prompt instead of retrying the comment. Discussion rows now only retry.editCommentno longer RPCs a local-only optimistic id.File::lock/unlock; twoquestion_marklints.@-mention row activates onmousedown, soclickRenderedwas a silent no-op reported as success; the C3 scenario now uses the driver'svisibleClick. Itspending → failedDOM assertion is scoped to what an instantly-refused endpoint can render; the state machine stays covered at the store layer.Acceptance status — stated plainly
The native-continuation acceptance (
E2E_NATIVE_CONTINUATION_LIVE=1) had never been executed before this pass: its anchors readcallId/displayStatus/displayVariant, none of which exist on therawEventscontract. Anchors were re-expressed against the declared contract,assertHistoryfixed (JSON-escaped haystack could never match multi-line anchors), and the ≥40-event bar kept as a hard floor. With real accounts and a real 649-turn Codex session the run opens the session, passes the size bar, and stopped at the runtime-switch step. Root cause: harness, not product. The Claude source option did render (the driver's ownwaitUntilfor it passed), butisSessionPatchedToasserts that the imported session row was mutated — which this PR deliberately never does forexternal_historysessions: the pick is written toconversationTargetOverridesAtomkeyed by the conversation root, and the execution episode created on send is the durable authority. The reportedcount:0was the last retry strategy after the palette had closed;model: gpt-5.5is the imported rollout's own metadata (the configuredgpt-5.3-codexis the return-leg target). The driver's predicate now reads the committed target from the runtime pill's projection for conversation-bound sessions and keeps the row check for ordinary sessions. Two further harness defects surfaced once that passed: the acceptance input must be a session whose last turn contains a completed tool call (imported sessions load only the final turn's body on open, so the tool-result anchor cannot exist otherwise — the spec now preflights this before spending a live turn), and the shared driver clicked the last visible variant of a model group (claude-opus-5-high) while its expectation was clamped to the account'senabled_models(claude-opus-5), rejecting the model it had itself picked; it now prefers the exact requested row and always accepts the clicked model id. In every case the pill projection showed the pick had applied — the product behaved correctly. Final status: not green, and not a product failure. With those harness fixes the run opens the real Codex session, passes the size/anchor preflight, switches runtime and account, creates the execution episode for the conversation root, materializes the full Codex history into Claude's own catalog (515-line transcript) and resumesclaudeon it. The turn then fails at provider auth: the only Claude account available to the run is an auto-detected OAuth account whose access token has expired and which carries no expiry, so the key vault never refreshes it (pre-existing, key-vault untouched by this stack; task filed). Refreshing it underoauth-livewould rotate an OAuth chain cloned from the developer's own CLI keychain, which the E2E config itself warns can revoke the account, so the run was stopped there rather than forced. A working Claude account is the only missing input; the driver and spec are now correct for both directions.Robustness gap found while testing (not fixed here — needs a design call): if the execution episode behind an
accepteddelivery row disappears (the child session is deleted),recoverLocalConversationTurnreturnsnullandconversationTurnRunnertreats that as recovery-pending forever, so the conversation root stays wedged with a retry every minute and every later message queues behind it. A confirmed-missing runner is a permanent state and should turn the row into a failed, retryable delivery. Related pre-existing note: the durable queue store (chat-message-queue.json) lives in the app-support directory, not underORGII_HOME, so E2E runs share it with the developer's own instance.The dual-instance suite's C3 (Team Chat pending/failed/retry) reaches the retry step; retry correctness is proven in-process end to end (
SessionCommentsRetryProjection.test.ts, new coverage for a seam that had none).Verification
All five gates run on the final tree of this PR (stacked on #844, both on
developc1d0e2976):pnpm typecheckpnpm lint(oxlint + eslint--max-warnings 0)vitest --changed <#844 head>cargo clippy --workspace --all-targets -- -D warningscargo test --workspaceWorktreeLockGuardrelease flake fixed in #844)Also run against a fresh build: the rendered dual-instance suite (A/B pass; remaining failures triaged to harness staleness on
develop, one pre-existing product gap, and shared-session cascades — none attributable to this stack) and the stack's own new specs (work-item-features,routine-wizard,session-account-switch), whose failures were root-caused to a spec readingE2E_ORGII_HOMEinstead of the publishedORGII_HOME, a 1680px viewport collapsing the detail pane below its 300px placeholder threshold, and the two Routine regressions fixed in #844.