refactor(session): let the host own the running turn and its requests - #556
Merged
Merged
Conversation
A client folds a byte-bounded window of a session's log and used to decide liveness itself: mark_idle unless the status said the turn ran. That lost the pending question and the working indicator whenever a stale cached status settled the fold, and #554 patched it with a refold on every running flip plus back-to-back history pages until the running turn's start arrived, downloading the whole running turn (often several MB). The host already holds the complete fold and knows whether its provider is live, so SessionStatus now carries the running turn (its index in the whole log and start time), the pending approvals and the pending question, gated on an in-flight turn. The client folds held records purely and settles which held turn runs from the status, in place and without discarding anything, whenever either topic changes. An unready plan is hidden while its turn is not running instead of being discarded. PROTOCOL_VERSION 7.
yermakoffivan
pushed a commit
to yermakoffivan/tcode
that referenced
this pull request
Sep 30, 2026
The host now owns a running turn's requests (Tryanks#556), so a request pushed straight into the timeline no longer reaches the status the composer reads.
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.
Behaviour
Cause
A client folds a byte-bounded window of the log, and it decided liveness itself: it called
mark_idleunless the status said the turn ran. A fold settled by a stale cached status discarded the running turn and its pending question and approvals. #554 revived them with a refold on every running flip, and timed a window cut inside the turn by paging back to its start.A window fold can place live state but cannot decide it. The window may start after the turn or its requests opened, and a provider can stop without a closing record (user shutdown, orchestrator cancel, host restart). The host already holds the complete fold and knows whether its provider is live.
Change
SessionStatuscarriesrunning_turn(RunningTurn { turn, started_at }, the turn's index among every turn in the whole log), pluspending_approvalsandpending_user_inputwith their content. These replace thepending_approvalandpending_user_inputflags.session_status_snapshotderives these from the resident timeline and approval registry.open_requestsholds the one rule that requests count only while a turn is in flight, for both the status and the index activity summary. The rule matches what the client did before: fold requests ANDturn_running.has_approvallost its last production caller and is removed.Timeline::running_turnderives the value on the host.Timeline::settle_running_turnmarks which turn runs and fills its start. It discards nothing, so a later settle can revive the turn.Timeline::shown_proposed_planhides a streamed, unready plan while its turn is not running, instead ofmark_idlediscarding it.fold_held_recordsis a pure fold.session_turn_offset.reconcile_replica_livenessandrunning_turn_opening_unheldare removed.Tests
the_status_settles_the_running_turn_whichever_topic_arrives_first(replaces fix(chat): keep the working indicator on a thread opened mid-turn #554's status test). For both arrival orders, the turn runs from the host's start and the question shows. A status whose running turn is not yet held marks no held turn. An idle status hides the question that the fold still holds.a_window_cut_inside_the_running_turn_is_timed_from_the_host(replaces fix(chat): keep the working indicator on a thread opened mid-turn #554's paging test). A cut window runs from the status start, and no history page is requested.status_carries_the_running_turn_and_its_question_only_while_in_flight(runtime). Checks the turn index and start across two turns. After a shutdown without a record, the timeline still runs but the status reports neither the turn nor the question.proposed_plan_lifecycle…(core, extended). A settled-idle fold hides the streamed plan and a settle to running revives it. A final plan stays shown.provider_event_for_testwrapper next torecord_event_for_replica_test.Mutation checks: reading the question from the fold, or skipping the in-place settle on a status change, each fails the client test.
Checks run
cargo fmt --all --checkcargo clippy --workspace --all-targets --locked -- -D warningscargo nextest run --workspace --locked: 955 passedcargo machetecargo check -p tcode-web --target wasm32-unknown-unknownand-p tcode-ios --target aarch64-apple-ios-sim, both withRUSTFLAGS=-D warningsThe Android check is left to CI (no NDK locally). Not exercised on a real phone. Clients and hosts must both be on protocol 7.