fix(orchestrator): Skip blank subagent opening messages - #5456
Open
mwolson wants to merge 208 commits into
Open
Conversation
Co-authored-by: codex <codex@users.noreply.github.com>
- Initialize provider as unchecked in a pending state - Update initial probe message to reflect session-local status
- Type the runtime effect with `Scope` - Build the ACP session runtime without wrapping it in `Effect.scoped`
- Use strict TurnId and ProviderItemId parsing in Codex session routing - Decode in-memory stdio chunks in streaming mode to avoid split UTF-8 corruption
- Transfer session-owned scopes into adapter state - Ensure runtime scopes close on stop and startup failure - Add regression coverage for scoped lifecycle cleanup
- Close the managed native event logger when the adapter layer tears down - Make session runtime close idempotent with an atomic closed flag - Add coverage for flushing thread native logs on shutdown
- Use codex app-server snapshots for auth, models, and skills - Remove legacy CLI/config discovery paths and related helpers - Update tests for the new provider status flow
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model - Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types - Add replay runtime, fixtures, and integration coverage - Update shared contracts and probe transcripts Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring - Introduce in-memory orchestration projections and provider registry - Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections - Support cheap fork creation and Codex native fork rollback - Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work - Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions - Add shell snapshot projection support plus coverage tests - Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server` - Add Claude Agent SDK replay fixtures and test harness - Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module - Share the SDK query runner between live and replay paths - Add replay driver error wrapping for unexpected failures
- persist the selected model on run records - surface run model selection in the debug UI - update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes - Add approval and tool-call replay coverage for new orchestration fixtures - Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy - Add read-only replay fixture and policy mapping tests - Reuse shared approval-policy fixtures across orchestrator tests Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures - update Claude adapter/orchestrator turn handling for steering - refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex - log Claude Agent SDK protocol frames to native event traces - project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles - Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Follow-ups from the main merge: - Migration numbering: main's 035_ProjectionThreadTitleRegeneration is released and keeps its id; this branch's unreleased v2 migrations shift to 036-044 instead (a released migration can never be renumbered). - thread.metadata.update accepts regenerateTitle: true arms an in-flight titleRegeneration marker on the thread payload (requestId + startedAt), a landing title or explicit false clears it. The marker projects onto thread shells and through the client-runtime shell model, so the sidebar's Regenerating state works unchanged. - New ThreadTitleRegenerationService worker reacts to armed markers on the live domain-event stream (so ws, MCP, and mobile dispatches all behave the same): builds a newest-first conversation digest from the v2 projection (8k-char budget, retained attachments — ported from the v1 reactor), generates via TextGeneration, and lands the title with a follow-up metadata update. Failures clear the marker and log. - The server advertises threadTitleRegeneration again, and the client updateThreadMetadata dispatches regenerateTitle-only updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The turn item materialized when a queued run is promoted was emitted with inputIntent "turn_start", but the deterministic replay driver (and the recorded queued_turn fixtures) expect "queued_turn" — the mismatch stalled replay before the queued run could start, leaving run 1 waiting and run 2 queued forever in all five queued_turn fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Effect service conventions: ThreadManagementThreadNotSendableError carried an Archived/NoSteerableRun reason union and switched on reason._tag inside the message getter. Modelled as two error classes (ThreadManagementThreadArchivedError, ThreadManagementNoSteerableRunError); both still map to the MCP thread_not_sendable failure code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Prevent the title row from shifting when output is revealed - Preserve panel and compact button sizing
…ingdotgg#5309) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Schema.UnknownFromJsonString -> Schema.fromJsonString(Schema.Unknown) - SchemaIssue.InvalidValue single-argument form in checkpointDiff - McpServerClient requires protocolVersion Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Required for follow-up requests by the 2025-06-18 MCP HTTP transport that effect beta.103 enforces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The thread-panel mapping change replaced the compact className on the Run and Add controls with the isPanel conditional and dropped the non-panel icon-compact classes that the responsive test asserts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the deleted v1 ProviderCommandReactor title coverage onto the v2 service: marker arming/clearing via thread.metadata.update, superseded requestId no-ops, digest-driven regeneration, the "New thread" and unchanged-title fallbacks, generation failure, and missing initial messages, plus unit tests for formatThreadTitleContext. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoist inline Schema compiles to module scope, drop unused imports/vars, stabilize react-markdown component identities via a module-scope factory, remove useless spreads, and use data-derived keys for release-note bullets. No behavior changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The codex resume test was the only replay scenario without a runtimePolicyOverride, so its checkpoint scope cwd fell back to process.cwd() and baseline capture ran real git over the entire checkout. Locally the capture short-circuits on checkpoint refs left behind by earlier runs; on a fresh CI checkout it is a cold multi-second capture that outlives the scenario wait budget, failing await_thread_idle while the run is still mid-checkpoint. Point the fixture's turn/start frames at the <workspace> placeholder and checkpoint a throwaway git workspace like every other replay test. Scenario waits are also wall-clock-bounded now: the iteration budget counts event-loop turns, which burn at full speed while git/fixture IO is in flight, so exhaustion additionally requires a 60s real-time deadline to pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r's scope (pingdotgg#5406) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Keep the git action control disabled when the branch is up to date - Omit open PR menu entries and remove their link-opening behavior - Update logic tests for the new states
…sPinned Main owns migration numbering: 036_ProjectionThreadsPinned landed on main, so the v2 migrations shift from 036-044 to 037-045. Release path runs all of main's migrations first, then the v2 stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Port thread pinning (pingdotgg#5312) into the orchestration-v2 command pipeline: thread.pin/unpin commands, thread.pinned/unpinned events, pinnedAt on the v2 thread state and projected shells, promotion semantics (pin clears settle/snooze, settle clears pin) matching the v1 decider, and client pin/unpin operations in the v2 dispatch style. - Port the regenerated-title context anchoring (pingdotgg#5365) into ThreadTitleRegenerationService: pin the first user message ahead of the retained tail when the digest is truncated. - Re-apply the right-panel controls positioning from pingdotgg#5260 to the v2 ChatView title bar controls. - Repair merge artifacts: committed conflict markers in BranchToolbar, duplicate capability keys, duplicate CommandPalette import, v1 turn naming in DiffPanel's focus-refresh effect, onSend signature merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A provider-native subagent could open its child thread with an empty or whitespace-only user message, which renders as an empty bubble under the "Sent by another agent" label. The Claude adapter emitted the opening prompt whenever the subagent was new, and a task_progress frame can register a subagent before any frame carries its prompt, so the message was emitted with the "" default and never rewritten when the real prompt arrived on a later task_started. A shared hasSubagentPromptText guard now gates every subagent opening message. The Claude adapter emits it the first time the task prompt actually has text rather than on first registration, so a late prompt still opens the child thread exactly once. Codex's existing length check becomes trim-aware, and Cursor and ACP pick up the same guard.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
juliusmarminge
force-pushed
the
t3code/codex-turn-mapping
branch
from
August 7, 2026 12:10
25de21d to
0af2a6e
Compare
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.
Carried from another PR
fix(orchestration): Preserve Claude subagent attribution after settle—carried for #5388 claude-postsettle-attribution.
Review it there, not here.
fix(orchestrator): Skip blank subagent opening messages— this PR.Summary
whitespace-only user message under the "Sent by another agent" label.
Claude's opening message the first time the task prompt has real text rather
than on first registration.
t3code/codex-turn-mapping.Problem and Fix
promptis whitespace-only still produced a child user message. The Agent tool accepts it,task_startedcarries it, and the emission site had no emptiness guard, so the web timeline rendered an empty bubble.hasSubagentPromptTexttrims before deciding. EverymakeSubagentConversationArtifactscaller withrole: "user"goes through it (Claude, Codex, Cursor, ACP).task_progressframe can register a Claude subagent before any prompt-bearing frame. Registration fell back toprompt: input.prompt ?? "", and the opening message was tied to first registration, so it was emitted as""and never rewritten when a latertask_startedsupplied the real prompt.hasSubagentPromptText(task.prompt) && !hasSubagentPromptText(priorTask?.prompt), so a late prompt still opens the child exactly once and a prompt change from one non-empty value to another does not re-emit.input.prompt.length > 0, which still admits whitespace. Cursor and ACP had no guard.Validation
claude_subagent_empty_promptcovers both routes (whitespace Agent prompt andtask_progressbeforetask_started). Confirmed fail before the emission fix and pass after.OrchestratorReplayFixtures.integration/.contract,ClaudeReplayFixtures.integration,CodexReplayFixtures.integration): 89 tests, 1 skipped.SubagentProjection): 274 tests.vp check: passed.vp run typecheck: passed all 15 packages.prompt: " "and a child thread with no user message; a sibling ordinary-prompt child held exactly one:promptmessage. Zero trim-empty user-role projection messages in the isolated DB.Checklist
Note
Low Risk
Server-side projection-only change with a narrow guard; low risk to auth, data, or core runtime paths.
Overview
Subagent child threads no longer get an opening user message when the task prompt is empty or whitespace-only, avoiding empty “Sent by another agent” bubbles in the timeline.
A shared
hasSubagentPromptText(trim-aware) gatesmakeSubagentConversationArtifactsfor user-role opening messages across provider adapters. In this diff, ACP only emitsmessage.updated/turn_item.updatedfor the:promptitem whenupdate.prompthas real text after trim.Related adapter behavior (same PR): Claude defers the opening message until the first frame with non-empty prompt (not first registration); Codex drops a raw length check in favor of the shared helper; Cursor and ACP gain the same guard.
Reviewed by Cursor Bugbot for commit 0e1b4fe. Bugbot is set up for automated code reviews on this repo. Configure here.