You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a native fork/bifurcation menu beneath completed Codex, Claude Code, OpenCode, and Pi replies. The menu offers Fork in side chat and Fork as main conversation. Either choice creates an independent native provider session containing the conversation through the selected reply and opens an empty composer. Side chats open in the right pane; main conversations appear in the left sidebar and open in the main chat area. Creating the fork does not send a prompt or start inference.
For U1 → A1 → U2 → A2 → U3 → A3, forking A1 produces U1 → A1 → fork marker. The first new question resumes the child session without inheriting the later turns. The original chat retains its identity, history, and execution.
User-visible behavior
Keep the existing metadata strip in the order timestamp → fork → Copy, with its reserved height, hover behavior, keyboard access, and Copy feedback. Group the fork and Copy controls with a compact 4 px gap.
Clicking the fork icon opens the existing contextual-menu component to the right of the button. Use compact text-only rows without an empty icon gutter. Support mouse selection, arrow-key navigation, Enter, Escape, and outside-click dismissal. Opening or dismissing the menu does not create a session.
Render one action on the last visible row of each complete logical assistant reply, including replies split across Markdown blocks or storage continuations and replies containing only tools.
Hide the fork action when a reply has no recorded native fork point or its execution host does not advertise native message fork support. It appears automatically when those prerequisites become available. Other availability failures, such as an unsupported provider contract, retain a disabled action with an explanation. Streaming and aborted replies, user messages, fork markers, and subagent transcripts do not expose an executable fork action.
Fork in side chat opens the saved child in the originating right pane; a fork from a side chat follows the sibling-under-root policy. Fork as main conversation creates a top-level chat with no visual parent, immediately adds it to the left sidebar, and selects it in the main chat area. Both destinations start with an empty composer. Navigation during creation does not redirect the result into a different conversation or steal its focus.
Show the simple provenance marker Forked from conversation, including for previously created native forks whose old display title contained an internal reply ID. Keep the exact cutoff and canonical native provenance in durable metadata.
Keep the same execution device, provider, and current checkout. The child's provider picker is locked. There is no file rewind, worktree creation, or historical prompt replay.
Preserve the existing empty-side-chat and textual context-copy flows as separate actions.
Provider implementations
Provider
Native operation and boundary
Verification
Codex
App-server thread/fork with explicit threadId, inclusive lastTurnId, and ephemeral: false.
Probe the executable's read-only schema for the required contract; require a completed source turn and verify the returned child with thread/read. No turn/start during creation. Live-tested with 0.158.0.
Claude Code
Official forkSession(sourceSessionId, { dir, upToMessageId }) through a small managed Node helper.
Pin @anthropic-ai/claude-agent-sdk@0.3.284; verify source and child using getSessionMessages, allowing the SDK's UUID remapping. Preserve CLAUDE_CONFIG_DIR and project cwd. No query() during creation. Live-tested with CLI 2.1.284 and Node 22.23.1.
OpenCode
Existing HTTP client and protocol detection: v1 /session/{id}/fork; v2 /api/session/{id}/fork. Convert the inclusive product boundary into the next native message's exclusive boundary.
Serialize prompt admission with boundary preparation; allow a stable historical boundary during a later run; require an idle, stable tail for whole-session copying. Read back and verify the child's exact native prefix. Live-tested with 1.18.33 and 2.0.11.
Pi
Official SessionManager.createBranchedSession(entryId) through a managed storage-only Node helper. Copy the inclusive root-to-assistant path into an independent persistent session.
Pin @earendil-works/pi-coding-agent@0.85.1; verify source UUID, canonical cwd and format, then the child entry sequence, ancestry and reconstructed context. Record the exact native entry at authoritative completion. No prompt or active-parent session switch during creation. Verified with Pi 0.85.1 and Node 22.23.1 using a local mock provider.
OpenCode 2.0.11's executable exposes the exclusive boundary as before in /openapi.json, while the website describes messageID. Sending the website's field to that executable over-copied history; child verification detected the mismatch. The validated v2 implementation uses before, v1 uses messageID, and unknown OpenCode versions stay unavailable. V2 history reading also handles envelopes and cursor pagination, sending ordering only on the first page.
Codex boundaries represent completed native turns. A visible segment closed during steering is not automatically a valid cut point. None of the adapters infer native identities by matching text, timestamps, or bubble indexes.
Pi now supports both destinations
Pi was initially excluded while Zeron used an ACP adapter. After the migration to native RPC, this PR now gives Pi the same Fork in side chat and Fork as main conversation actions as the other supported providers.
Capture the exact persisted assistant entry through an embedded Pi extension at turn_end. Require the same message object in the authoritative session manager and stopReason: stop; do not match text, timestamps or visual message indexes. Consume the metadata notification internally and associate the point with the explicit Zeron assistant ID before Steered or terminal Done.
Create the independent child through the pinned official storage SDK in an isolated helper. Do not use RPC fork, which changes the active session and cuts at a user message. Historical points can be forked while a later parent turn stays active. Check the inclusive prefix and effective model context, including labels, compaction and custom entries.
Sync the child file and UUID-to-file lookup before returning. Preserve canonical source-session provenance on inherited replies; new child replies record their own session's points.
Require the saved child on every cold resume. Missing history, incompatible format/project or an extension-driven change to an unrelated session fails explicitly, without creating a fresh conversation or replaying a textual history wrapper. The existing engine routes MCP tools to the child chat.
Embed both helper and extension in the Rust binary. Prepare @earendil-works/pi-coding-agent@0.85.1 through the existing managed package installer. Node >=22.19.0 is required; npm is needed for first preparation. Only the verified Pi 0.85.1 fork contract is enabled; ordinary Pi RPC conversations retain their existing version range.
Persistence, routing, and continuity
Add a versioned NativeForkPoint with typed provider boundaries, native session provenance, execution device, and cwd. Persist it on the exact logical reply through Loro writes, snapshots, continuation joins, and journal replay, before terminal Done processing completes.
Add native-only ForkMessageSideChat and batched GetNativeForkAvailability RPCs, advertised as native-message-fork-v1. The creation request includes a typed visual destination; omitted destinations retain the existing side-chat behavior. Advertise main-conversation support separately as native-message-fork-main-v1, and disable that menu option on older hosts while preserving their supported side-chat option. Public requests reference Zeron message IDs; the host resolves and validates native identities. Remote requests route to the execution device and use the existing provider-update execution lease.
Freeze the visual prefix before provider creation. Record Prepared, ProviderCreated, Published, Failed, or Indeterminate locally. Persist the child document and registry before acknowledging publication.
Deduplicate an in-flight operation and its retries by the full request identity, including the destination. The UI tracks retries separately for each source reply and destination; confirmed success clears that identity so the same reply can produce independent subsequent forks. Reusing a request ID with a different destination is rejected. Recovery from ProviderCreated publishes the known child without creating another provider session. An ambiguous provider result is not automatically retried; there is no exactly-once guarantee across the provider and local storage.
Persist NativeFork lineage and derive RequireExisting from trusted document metadata. Cold starts, warm sends, steers, queues, startup recovery, and initial native commands do not add a textual <conversation> wrapper or silently create a fresh session after a rejected resume.
Configure Zeron MCP for the child chat. Resolve historical approvals and remove inherited patch attribution while retaining source ownership for historical tool/subagent details.
Keep canonical native provenance on copied replies. Forking an inherited reply uses the session that actually contains its boundary; newly generated replies belong to the child's own native session. Side-chat forks follow the existing sibling-under-root relationship. Main-conversation forks keep the same native lineage and strict resume policy while storing no visual parent.
The Claude helper is embedded and installed through the existing pinned-package mechanism. Node >=18 is required, npm prepares the package, and cancellation/timeouts reap preparation and helper processes before releasing the execution lease.
Regression fixes included
Independent repeated forks: clear the UI's operation identity after confirmed success. Another click on the same reply creates a new child; pending double clicks and unconfirmed retries retain deduplication.
Registry write failures: make WorkspaceHost::flush() return persistence errors. Both initial publication and the existing-in-memory-row retry path remain ProviderCreated until the registry is saved. Tests inject a real SQLite write failure and recover through both retry and restart without another provider creation.
Remote availability recovery: invalidate availability when the execution host's presence or the selected chat's delivery path changes, even when the viewer's local engine stays connected. Retry failed batches with exponential backoff from 1 to 30 seconds, retain definitive availability answers, and cancel stale requests/timers on navigation or replacement. Heartbeat timestamps, pending update counts, and unrelated chats do not trigger repeated queries.
Why the remaining providers are out of scope
This feature requires an exact native historical boundary, independent persistent child identity, creation without inference, verified prefix contents, and strict resume behavior. General resume support alone does not establish those guarantees.
Cursor: Zeron uses @cursor/sdk@1.0.32. Inspection of the published SDK declarations and documentation did not establish an equivalent public fork operation with a selected-message cutoff. The CLI documents /fork, but that does not establish the required historical-cutoff contract for Zeron's SDK integration. Native checkpoints and the public storage interfaces suggest a possible future implementation; that route has not been validated as a fork/resume workflow with authenticated Cursor access. This is a validation gap, not a claim that Cursor cannot support it.
Devin, Grok, Hermes, and Antigravity: explicitly excluded from this implementation's provider scope. No exact-boundary native fork contract or end-to-end fork validation was established for them in this work. They retain their existing runtime behavior and do not receive the new action; shared struct initializers receive additive defaults where compilation requires them.
Mock harness: implements the contract only for deterministic tests.
There is no automatic textual-history fallback for any unavailable provider or message.
Validation
Directed validation was performed on Linux during implementation and the subsequent fixes. Provider live probes ran separately from the ignored opt-in integration tests.
Claude Node helper tests, including actual pinned SDK storage
4 passed in prior validation
Pi Node helper tests, including actual pinned 0.85.1 SDK storage
7 passed
Pi harness pi:: unit filter
13 passed
Pi RPC fixture suite (--features native-fixture --test pi_rpc)
16 passed
Pi real-process tests (--test pi_live -- --ignored --nocapture)
3 passed with isolated local mock provider; no model API requests
cargo check -p zeron-ui
Passed
Changed-file formatting and git diff --check
Passed, excluding the two mobile initializer-only files' pre-existing formatting
Latest Pi extension validation used --locked. The suites above cover both fork destinations and strict child resume after engine restart. Pi's three real-process tests additionally check historical and final cuts, a parent held in an active later turn, unchanged parent bytes, first child send after process restart, missing-child rejection, tool-loop completion, exact native-point/assistant-ID associations during 43 steers, and extension-driven session-switch rejection. The SDK storage tests cover labels, compaction, custom entries and two generations of forks. Administrative helper tests cancel/time out after simulated provider creation, require an Indeterminate result, and verify process reaping before the update lease is released.
A requested Astra high audit found a Windows persistence issue: flushing a read-only index handle would fail after the provider had created the child. The implementation now opens the handle with write access and adds a cross-platform durability/reopen test. A follow-up audit found no additional actionable regressions. Native process tests were run on Linux; macOS/Windows execution remains unverified.
CI now runs Pi's offline unit/RPC/helper contracts and the host native-fork suites. The actual SDK storage tests require an explicit module path, and the real Pi tests stay opt-in; CI does not invoke a live provider or install a model-facing Pi runtime for these tests. The existing Windows harness unit job includes the new index durability test.
cargo fmt --all -- --check is not green: the same files already have formatting differences in the original 8f62632a baseline. Unrelated formatting changes are excluded from this feature.
Live smoke probes passed for Codex 0.158.0, Claude Code 2.1.284, OpenCode v1 1.18.33 with opencode/muse-spark-1.3-contributor-free, and OpenCode v2 2.0.11 with opencode/mimo-v2.6-flash-free. They verify a historical fork, exclusion of later context, first child send, a child-owned native point, and strict resume after reconstructing the driver. Engine tests separately cover engine restart. The OpenCode probe requires an explicit model containing free.
The GPUI fixture renders the real shell/transcript/composer and exercises the fork RPC with a synthetic provider, without inference. Physical two-device execution and macOS/Windows distribution were not exercised. Remote recovery is covered by simulated RPC/GPUI tests.
Old replies without an exact mapping remain unavailable. Deleting a canonical provider session can make inherited replies unavailable. Forks share the current checkout; files are not restored. No iOS UI is added. No release version is changed and no final application is installed by this work.
The original implementation was based on 8f62632a (v0.2.98), followed by focused regression fixes, upstream updates, the destination-menu extension, and native Pi support described above.
Screenshots
Captured from the native GPUI mockup with an isolated engine and synthetic provider; no model inference. These existing captures predate the destination menu, tighter action spacing, and simplified provenance label; they illustrate the original native side-chat workflow and were not refreshed for this update.
Timestamp → fork → Copy, dark theme:
The same action in the light theme:
A completed reply without a recorded native point shows only its timestamp and Copy:
Historical fork with the selected prefix and an empty child composer:
Compact window with the sidebar collapsed:
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
jsgrrchg
changed the title
Add native conversation forks from completed agent replies
Add native conversation fork/bifurcation from agent replies into side chats
Sep 29, 2026
Updated in commit 8d3945b0: the fork icon is now hidden when a reply has no recorded native fork point (including older conversations) or the execution host does not advertise native-message-fork-v1. These cases no longer show a disabled button or tooltip.
The action reappears automatically when the point or host capability becomes available. Stale availability responses cannot restore a hidden action, and no availability requests are sent for these ineligible replies. Other availability failures continue to show a disabled action with an explanation.
Updated the side-chat documentation and PR description to match. Validation: all 7 native_fork GPUI tests and the metadata-strip test passed, including coverage for both hidden states, stale responses, and recovery.
jsgrrchg
changed the title
Add native conversation fork/bifurcation from agent replies into side chats
Add native conversation fork/bifurcation into side chats or main threads
Sep 29, 2026
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
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.
Summary
Add a native fork/bifurcation menu beneath completed Codex, Claude Code, OpenCode, and Pi replies. The menu offers Fork in side chat and Fork as main conversation. Either choice creates an independent native provider session containing the conversation through the selected reply and opens an empty composer. Side chats open in the right pane; main conversations appear in the left sidebar and open in the main chat area. Creating the fork does not send a prompt or start inference.
For
U1 → A1 → U2 → A2 → U3 → A3, forkingA1producesU1 → A1 → fork marker. The first new question resumes the child session without inheriting the later turns. The original chat retains its identity, history, and execution.User-visible behavior
Provider implementations
thread/forkwith explicitthreadId, inclusivelastTurnId, andephemeral: false.thread/read. Noturn/startduring creation. Live-tested with 0.158.0.forkSession(sourceSessionId, { dir, upToMessageId })through a small managed Node helper.@anthropic-ai/claude-agent-sdk@0.3.284; verify source and child usinggetSessionMessages, allowing the SDK's UUID remapping. PreserveCLAUDE_CONFIG_DIRand project cwd. Noquery()during creation. Live-tested with CLI 2.1.284 and Node 22.23.1./session/{id}/fork; v2/api/session/{id}/fork. Convert the inclusive product boundary into the next native message's exclusive boundary.SessionManager.createBranchedSession(entryId)through a managed storage-only Node helper. Copy the inclusive root-to-assistant path into an independent persistent session.@earendil-works/pi-coding-agent@0.85.1; verify source UUID, canonical cwd and format, then the child entry sequence, ancestry and reconstructed context. Record the exact native entry at authoritative completion. No prompt or active-parent session switch during creation. Verified with Pi 0.85.1 and Node 22.23.1 using a local mock provider.OpenCode 2.0.11's executable exposes the exclusive boundary as
beforein/openapi.json, while the website describesmessageID. Sending the website's field to that executable over-copied history; child verification detected the mismatch. The validated v2 implementation usesbefore, v1 usesmessageID, and unknown OpenCode versions stay unavailable. V2 history reading also handles envelopes and cursor pagination, sending ordering only on the first page.Codex boundaries represent completed native turns. A visible segment closed during steering is not automatically a valid cut point. None of the adapters infer native identities by matching text, timestamps, or bubble indexes.
Pi now supports both destinations
Pi was initially excluded while Zeron used an ACP adapter. After the migration to native RPC, this PR now gives Pi the same Fork in side chat and Fork as main conversation actions as the other supported providers.
turn_end. Require the same message object in the authoritative session manager andstopReason: stop; do not match text, timestamps or visual message indexes. Consume the metadata notification internally and associate the point with the explicit Zeron assistant ID beforeSteeredor terminalDone.fork, which changes the active session and cuts at a user message. Historical points can be forked while a later parent turn stays active. Check the inclusive prefix and effective model context, including labels, compaction and custom entries.@earendil-works/pi-coding-agent@0.85.1through the existing managed package installer. Node >=22.19.0 is required; npm is needed for first preparation. Only the verified Pi 0.85.1 fork contract is enabled; ordinary Pi RPC conversations retain their existing version range.Persistence, routing, and continuity
NativeForkPointwith typed provider boundaries, native session provenance, execution device, and cwd. Persist it on the exact logical reply through Loro writes, snapshots, continuation joins, and journal replay, before terminalDoneprocessing completes.ForkMessageSideChatand batchedGetNativeForkAvailabilityRPCs, advertised asnative-message-fork-v1. The creation request includes a typed visual destination; omitted destinations retain the existing side-chat behavior. Advertise main-conversation support separately asnative-message-fork-main-v1, and disable that menu option on older hosts while preserving their supported side-chat option. Public requests reference Zeron message IDs; the host resolves and validates native identities. Remote requests route to the execution device and use the existing provider-update execution lease.Prepared,ProviderCreated,Published,Failed, orIndeterminatelocally. Persist the child document and registry before acknowledging publication.ProviderCreatedpublishes the known child without creating another provider session. An ambiguous provider result is not automatically retried; there is no exactly-once guarantee across the provider and local storage.NativeForklineage and deriveRequireExistingfrom trusted document metadata. Cold starts, warm sends, steers, queues, startup recovery, and initial native commands do not add a textual<conversation>wrapper or silently create a fresh session after a rejected resume.The Claude helper is embedded and installed through the existing pinned-package mechanism. Node >=18 is required, npm prepares the package, and cancellation/timeouts reap preparation and helper processes before releasing the execution lease.
Regression fixes included
WorkspaceHost::flush()return persistence errors. Both initial publication and the existing-in-memory-row retry path remainProviderCreateduntil the registry is saved. Tests inject a real SQLite write failure and recover through both retry and restart without another provider creation.Why the remaining providers are out of scope
This feature requires an exact native historical boundary, independent persistent child identity, creation without inference, verified prefix contents, and strict resume behavior. General resume support alone does not establish those guarantees.
@cursor/sdk@1.0.32. Inspection of the published SDK declarations and documentation did not establish an equivalent public fork operation with a selected-message cutoff. The CLI documents/fork, but that does not establish the required historical-cutoff contract for Zeron's SDK integration. Native checkpoints and the public storage interfaces suggest a possible future implementation; that route has not been validated as a fork/resume workflow with authenticated Cursor access. This is a validation gap, not a claim that Cursor cannot support it.There is no automatic textual-history fallback for any unavailable provider or message.
Validation
Directed validation was performed on Linux during implementation and the subsequent fixes. Provider live probes ran separately from the ignored opt-in integration tests.
cargo test -p zeron-protocargo test -p zeron-doccargo test -p zeron-harness --test codexcargo test -p zeron-harness --test claudecargo test -p zeron-harness --lib opencode::native_fork/adapter_installfiltersnative_side_chats/native_points/side_chatspi_resumenative_forkunit filternative_fork/side_chatfilterspi::unit filter--features native-fixture --test pi_rpc)--test pi_live -- --ignored --nocapture)cargo check -p zeron-uigit diff --checkLatest Pi extension validation used
--locked. The suites above cover both fork destinations and strict child resume after engine restart. Pi's three real-process tests additionally check historical and final cuts, a parent held in an active later turn, unchanged parent bytes, first child send after process restart, missing-child rejection, tool-loop completion, exact native-point/assistant-ID associations during 43 steers, and extension-driven session-switch rejection. The SDK storage tests cover labels, compaction, custom entries and two generations of forks. Administrative helper tests cancel/time out after simulated provider creation, require anIndeterminateresult, and verify process reaping before the update lease is released.A requested Astra high audit found a Windows persistence issue: flushing a read-only index handle would fail after the provider had created the child. The implementation now opens the handle with write access and adds a cross-platform durability/reopen test. A follow-up audit found no additional actionable regressions. Native process tests were run on Linux; macOS/Windows execution remains unverified.
CI now runs Pi's offline unit/RPC/helper contracts and the host native-fork suites. The actual SDK storage tests require an explicit module path, and the real Pi tests stay opt-in; CI does not invoke a live provider or install a model-facing Pi runtime for these tests. The existing Windows harness unit job includes the new index durability test.
cargo fmt --all -- --checkis not green: the same files already have formatting differences in the original8f62632abaseline. Unrelated formatting changes are excluded from this feature.Live smoke probes passed for Codex 0.158.0, Claude Code 2.1.284, OpenCode v1 1.18.33 with
opencode/muse-spark-1.3-contributor-free, and OpenCode v2 2.0.11 withopencode/mimo-v2.6-flash-free. They verify a historical fork, exclusion of later context, first child send, a child-owned native point, and strict resume after reconstructing the driver. Engine tests separately cover engine restart. The OpenCode probe requires an explicit model containingfree.The GPUI fixture renders the real shell/transcript/composer and exercises the fork RPC with a synthetic provider, without inference. Physical two-device execution and macOS/Windows distribution were not exercised. Remote recovery is covered by simulated RPC/GPUI tests.
Evidence and limitations
Old replies without an exact mapping remain unavailable. Deleting a canonical provider session can make inherited replies unavailable. Forks share the current checkout; files are not restored. No iOS UI is added. No release version is changed and no final application is installed by this work.
The original implementation was based on
8f62632a(v0.2.98), followed by focused regression fixes, upstream updates, the destination-menu extension, and native Pi support described above.Screenshots
Captured from the native GPUI mockup with an isolated engine and synthetic provider; no model inference. These existing captures predate the destination menu, tighter action spacing, and simplified provenance label; they illustrate the original native side-chat workflow and were not refreshed for this update.
Timestamp → fork → Copy, dark theme:
The same action in the light theme:
A completed reply without a recorded native point shows only its timestamp and Copy:
Historical fork with the selected prefix and an empty child composer:
Compact window with the sidebar collapsed:
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.