Skip to content

[codex] Flush trailing realtime transcript tail - #29918

Merged
guinness-oai merged 1 commit into
mainfrom
guinness/realtime-transcript-tail
Jul 6, 2026
Merged

[codex] Flush trailing realtime transcript tail#29918
guinness-oai merged 1 commit into
mainfrom
guinness/realtime-transcript-tail

Conversation

@guinness-oai

@guinness-oai guinness-oai commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Why

A realtime session can end after more transcript has accumulated than was included in its last handoff. That tail already lives in core's active transcript state, but the stop path aborted the realtime input/fanout tasks before routing it, so the final bit of the conversation could disappear before thread/realtime/closed.

This behavior is still being evaluated, so clients must opt in per realtime session. Omitted or false leaves shutdown behavior unchanged.

What changed

  • Add optional flushTranscriptTailOnSessionEnd to thread/realtime/start, defaulting to false in app-server.
  • Expose an idempotent take_transcript_tail() from the existing active transcript state using last_handoff_entry_count.
  • When enabled, let shutdown cancel the input owner cleanly and publish at most one final existing <realtime_delegation> with the remaining text in <transcript_delta>.
  • Have the existing fanout drain already-parsed events before routing that final delegation, so a queued handoff wins first and is not duplicated in the tail.
  • Flush realtime shutdown before ordinary session task abort during core cleanup.

Validation

  • just test -p codex-app-server-protocol
  • just test -p codex-app-server realtime_conversation_stop_emits_closed_notification
  • just test -p codex-core conversation_transport_close_
  • just test -p codex-core conversation_close_routes_only_remaining_transcript_tail_once
  • scoped just fix for codex-app-server-protocol, codex-app-server, codex-protocol, and codex-core
  • just fmt

@guinness-oai
guinness-oai force-pushed the guinness/realtime-transcript-tail branch 6 times, most recently from ac63f06 to 31cdb43 Compare July 6, 2026 19:49
@guinness-oai
guinness-oai marked this pull request as ready for review July 6, 2026 19:59
@guinness-oai
guinness-oai requested a review from a team as a code owner July 6, 2026 19:59

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31cdb43e0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1420 to +1422
if flush_transcript_tail_on_session_end
&& let Some(transcript_delta) =
realtime_transcript_delta(&events.take_transcript_tail().await)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 Badge Cap transcript tail before routing

When an opted-in realtime session accumulates a long tail after the last handoff, this converts the entire take_transcript_tail() result into a synthetic user turn with no token or byte cap before it enters model context. A multi-minute call can easily exceed 1k tokens or grow unbounded, causing context/cache blowups on session close; please truncate or otherwise hard-cap the tail before routing it.

AGENTS.md reference: AGENTS.md:L91-L100

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we should not truncate here. It's crucial that the raw transcripts are not lost -- thats the whole point of the PR

Comment on lines +1054 to 1055
// Drain already-parsed events so a queued handoff is routed before the final tail.
while let Ok(event) = events_rx.recv().await {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Avoid routing handoffs after realtime stop

When flushTranscriptTailOnSessionEnd is omitted or false, thread/realtime/stop still now waits for this drain and routes any already-queued HandoffRequested via route_realtime_text_input, so a client that stops a realtime session can unexpectedly start a new Codex turn from an old handoff that previously would have been aborted. Please gate this post-stop handoff routing to the tail-flush opt-in path or skip it once shutdown was requested.

AGENTS.md reference: AGENTS.md:L102-L110

Useful? React with 👍 / 👎.

Comment on lines 72 to +75
pub client_managed_handoffs: Option<bool>,
/// Routes any transcript tail remaining at session end through Codex. Defaults to false.
#[ts(optional = nullable)]
pub flush_transcript_tail_on_session_end: Option<bool>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3 Badge Document the new realtime start option

This adds a client-visible thread/realtime/start parameter, but the app-server API docs for that method still do not mention flushTranscriptTailOnSessionEnd, so experimental clients cannot discover the opt-in behavior or its default from the documented API surface. Please update app-server/README.md alongside this protocol change.

AGENTS.md reference: AGENTS.md:L298-L303

Useful? React with 👍 / 👎.

Comment on lines 72 to +75
pub client_managed_handoffs: Option<bool>,
/// Routes any transcript tail remaining at session end through Codex. Defaults to false.
#[ts(optional = nullable)]
pub flush_transcript_tail_on_session_end: Option<bool>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3 Badge Use the false-default bool payload shape

This new client-to-server v2 boolean is interpreted with unwrap_or(false), so omission means false; modeling it as Option<bool> instead publishes a nullable field and serializes None as null in generated request shapes. Please use the defaulted bool shape for false-default request flags so the wire/API contract matches the v2 convention.

AGENTS.md reference: AGENTS.md:L289-L294

Useful? React with 👍 / 👎.

@guinness-oai
guinness-oai force-pushed the guinness/realtime-transcript-tail branch from 31cdb43 to 3c67d5f Compare July 6, 2026 20:40
@guinness-oai
guinness-oai merged commit c976741 into main Jul 6, 2026
35 checks passed
@guinness-oai
guinness-oai deleted the guinness/realtime-transcript-tail branch July 6, 2026 21:12
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants