Repository navigation
fix(live): a post-close job result replays quietly instead of opening a reopened voice - #1312
Merged
Merged
Conversation
… a reopened voice
Both S104 attempts in Turbo S run 36599321227 failed "the assistant greeted
on its own after the reopen with summary" and "no append lane may be used
before the first user turn, but 3 thinking appends were sent". A probe
reproduced it locally on main 1 of 1: the reopen's pre-open summary missed
the 2.5 s bound (Late), its boundary was canonical row 7, and the DurableFork
job's post-close merge (queued work on the source member) committed rows 8
and 9 ("Result of the voice request ..." and the member's "Done, coffee.md
reads ...") while channel 2 opened. The mirror queued both as rows the
channel will voice, `EnqueueLiveContextRow` recorded `SpokenCanonicalRow`
(5afbac0, in every release since v0.8.46), and the late summary and the
result went out before the user spoke; the model read the result aloud.
When the merge committed before the boundary the result sat in the summary
and the call stayed silent, which is why this raced.
- The post-close merge is runtime-authored: it is queued as injected
execution context (`MobHandle::start_injected_context_work_for_identity_bounded`,
exact runtime-input custody), never as a conversational user row.
- The live mirror marks the member's reply to injected execution context as
`LiveContextRowSource::RuntimeWork` (new `EnqueueLiveContextRow` input
`row_source`). Runtime work output becomes a quiet reassertion and never
starts the conversation; typed and peer rows are voiced as before
(lukacf/meerkat-meta#474 still releases the summary).
- A quiet reassertion waits for the conversation to start
(`quiet_history_waits_for_the_conversation`, typed deferral
`AuthorizeLiveContextAppendDeferredByConversation`), and a started user
turn requests a drain.
- Quiet reassertions are no longer deferred by an active provider turn: the
thinking lane does not take the turn, and holding the replayed result
until the user's first turn ended let the model answer "what happened while
I was gone" without it (it delegated instead in 1 of 6 runs of an earlier
version of this fix). Voiced rows still wait for the turn boundary.
Kernels, TLA models, cfgs, mapping and contract are regenerated through
xtask machine-codegen. S104 live after the fix: 5 of 5 passed, every run on
the Late race path with the reply queued as runtime work.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The first commit let every quiet reassertion through during an active
provider turn. A local S106 run then stalled on its second reopen: the late
summary and two reassertions of the user's own in-flight utterance ("What's
the", "project codename again") were appended on the thinking lane while the
model was starting its answer; it emitted nine characters of transcript, no
audio, and never answered.
Runtime work output now has its own runtime-minted disposition,
`LiveContextRowDisposition::ReplayRuntimeWork`. Only it bypasses
`safe_provider_turn_boundary` (the merged job result still reaches the model
during the user's first utterance) and only it waits for the conversation
(`quiet_history_waits_for_the_conversation`, the typed deferral
`AuthorizeLiveContextAppendDeferredByConversation`). Reassertions of live
speech are deferred by the provider turn exactly as before this branch.
The meerkat machine poster is regenerated for the new guards.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 29, 2026
…der turn `safe_provider_turn_boundary` admitted ReplayRuntimeWork whenever a provider turn was active. It now admits it only while no assistant turn is attributed to the turn's interaction (`live_assistant_interaction_by_turn` against `live_active_interaction_by_channel`), and the typed turn deferral covers the complement, so a quiet replay can never be appended into an in-flight response. The machine test builds the responding state and asserts the same row is deferred there and admitted in the user phase. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… transcript position Review of #1312: the live mirror inferred RuntimeWork from transcript position (an assistant row after an injected-context user row in the same boundary). A durable mid-turn steer after a typed row flipped the typed turn's reply to RuntimeWork, and a merge reply committed in a later boundary than its input fell back to Conversation and started the conversation. - `TranscriptMessageIdentity` gains `turn_input: Option<TranscriptTurnInput>` (`RuntimeAuthored`), stamped at admission: the mob's work-attribution lowering sets it on the turn metadata's transcript identity when the work is `WorkContentAttribution::InjectedExecutionContext`, so every message the turn commits carries it. A batched turn keeps it only when every input carries it (`TurnInputConsensus`); absence is conversational input. - The mirror reads `row_source` from the committed assistant row's identity alone. Tests: the mid-turn steer after a typed row stays Conversation, a lone merge reply stays RuntimeWork, and the mob-mcp post-close merge test asserts the committed reply carries `RuntimeAuthored`. - A runtime test proves the user's turn start itself requests the drain that replays held runtime work output on the quiet lane (and fails without it). - `LiveContextRowSource` is `#[non_exhaustive]`; wire schemas and generated SDK artifacts are re-emitted; the CHANGELOG declares the identity field and drops a stray blank line in ### Added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lukacf
added a commit
that referenced
this pull request
Sep 30, 2026
Review of #1304 found the supersession guard fired on ANY later ReassertCausalTail row. That disposition also covered the assistant's own observed speech (and, before #1312's ReplayRuntimeWork, runtime work output), so the assistant talking could demote a typed question, and the demoted row carried the causal-replay framing ("already heard and answered, do not respond"), false for a row the provider never received. The live-context row model now keeps the origin. EnqueueLiveContextRow gains row_author (LiveContextRowAuthor: User, Assistant, Runtime), derived from the committed message: a live transcript row is AlreadyPresentInLiveChannel whoever spoke it, so neither the input disposition nor row_source (what drove the turn) identifies the author. The runtime mints ReassertCausalTail only for heard user speech and the new ReassertAssistantOutput for the assistant's observed or live speech; both wait for the provider turn boundary, DeferredByTurn covers both, and later ReassertCausalTail supersedes a typed row. Newer is canonical commit order, documented on the guard: a typed row has no live observation ordinal to compare with heard order. Framing, one per quiet kind, none claiming what is false: - LiveContextAppendKind::SupersededTypedRow with LIVE_SUPERSEDED_TYPED_PREFIX: a text-chat row delivered late, ordered before the newer speech that takes precedence where they conflict; a user request that speech did not replace still needs a response. The machine cannot tell a correction from an unrelated remark; the model can. - LiveContextAppendKind::RuntimeWorkReplay with LIVE_RUNTIME_WORK_PREFIX for ReplayRuntimeWork: background work that finished while the model was away, context the model has not seen (S104 needs it used when asked). Tests: runtime_control covers typed then assistant-only speech (voiced), typed then unrelated user speech (delivered as SupersededTypedRow, text intact), and typed then a spoken correction (S99 shape). gpt_live_generated_authority covers the edge boundaries: later user speech supersedes (flag true, complement false); later assistant output, a later runtime work reply, a later voiced row and earlier user speech do not; an equal cursor cannot occur; replayed heard speech is deferred during a provider turn. A facade test pins the framing contract. S99's oracle refuses an exchange whose question onset overlaps a response still streaming. Changelog nits fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 30, 2026
lukacf
added a commit
that referenced
this pull request
Sep 30, 2026
…ne invariant) #1312 exempted ReplayRuntimeWork from the provider-turn deferral only while no assistant turn was attributed to the active interaction, with the negation on AuthorizeLiveContextAppendDeferredByTurn. That condition can never be false: an assistant turn is attributed either through the awaiting interaction, which a provider turn start clears, or as provider initiated, which requires no active provider turn, and every provider turn mints a fresh interaction. The generated MeerkatMachine now states it as the invariant live_active_provider_turn_has_no_assistant_turn. The exemption reads "no provider turn, or the row is ReplayRuntimeWork", and the dead ReplayRuntimeWork branch leaves the DeferredByTurn edge. The test that built the unreachable state through recover_from_state now asserts that recovery rejects it, and still checks that the runtime work row is admitted in the user phase. Kernels, TLA models, contract, mapping and poster regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Why
Release Turbo S run 36599321227 failed S104 on both attempts with "the assistant greeted on its own after the reopen with summary" and "no append lane may be used before the first user turn ... but 0 instructions and 3 thinking appends were sent".
A probe reproduced it locally on main (1 of 1):
merge_result_into_source, queued work on the source member) committed rows 8 and 9 while channel 2 opened: "Result of the voice request ..." and the member's "Done, coffee.md reads ...".EnqueueLiveContextRowrecordedSpokenCanonicalRow. That cause came in with 5afbac0 and has been in every release since v0.8.46.When the merge commits before the boundary, the result sits in the summary and the call stays silent. That is why it raced.
What
MobHandle::start_injected_context_work_for_identity_bounded(exact runtime-input custody), never as a conversational user row.TranscriptMessageIdentitygainsturn_input: Option<TranscriptTurnInput>(RuntimeAuthored).WorkContentAttribution::InjectedExecutionContext, so every message the turn commits carries it. A batched turn keeps it only when every input carries it (TurnInputConsensus).LiveContextRowSource::RuntimeWork(newEnqueueLiveContextRowinputrow_source,#[non_exhaustive]) from the committed assistant row alone. Transcript position is never used: a mid-turn steer can follow a typed row, and a reply can commit in a later boundary than its input.LiveContextRowDisposition::ReplayRuntimeWork, is replayed on the quiet thinking lane, and never starts the conversation. Typed and peer rows are voiced as before; the typed turn of Voice reopen: canonical typed answer completes but the new voice channel stays silent meerkat-meta#474 still releases the summary.quiet_history_waits_for_the_conversationholds it (typed deferralAuthorizeLiveContextAppendDeferredByConversation), and the user's turn start itself requests the drain.ObserveLiveAssistantTurnStartedattributes an assistant turn to the interaction,safe_provider_turn_boundaryrefuses the replay and it waits for the turn boundary like every other row. The machine testreplayed_runtime_work_after_the_assistant_turn_started_stays_deferredbuilds that responding state and asserts the deferral.Kernels, TLA models, cfgs, mapping, contract and the meerkat machine poster are regenerated (
xtask machine-codegen,generate-machine-posters.mjs), andmachine-check-driftand the poster--checkare clean. The Breaking and Added entries are declared in the CHANGELOG.Evidence
Live S104, local:
Tests:
meerkat-runtime --test gpt_live_generated_authority: 65 passed, including the user-phase deferral test, runtime work waiting for the conversation, and live-speech reassertions still turn-deferred.a_started_user_turn_drains_replayed_runtime_work_held_for_the_conversation: the productionobserve_live_provider_turn_startedrequests a fresh drain that replays the held row on the quiet lane. The test fails when that drain request is removed.meerkat-mob-mcp live_delegation(50 passed): the post-close merge test asserts the committed reply carriesRuntimeAuthored.meerkat-core --lib: 1896 passed.meerkat-runtime --features live --lib: 1742/1745; the 3 failures (hard-cancel/interrupt tests) pass individually and are an in-process concurrency flake unrelated to this change.meerkat --lib experimental_gpt_live: 83 passed.Lint and checks: clippy
-D warnings --all-targets --all-featuresis clean on meerkat-core, meerkat-contracts, meerkat-machine-schema, meerkat-runtime, meerkat-mob, meerkat-mob-mcp and meerkat-session.make wasm-check(check + clippy) passes. The emitted schemas and the SDK codegen are refreshed.Combined live validation (2026-09-30, local, VM load < 120 at each run start)
One build combining #1307 (9837acb), #1308 (74e627a), #1309, #1310 (df32876), #1311 (f9a2f50), #1312 (2962983) and origin/main:
🤖 Generated with Claude Code