[Bug]: ACP session load can lose controls when replay idle wins before the final response#21739

opened23d agobyNnicodoggie

Summary

After loading an existing ACP session, T3 can lose the model, reasoning and other session controls. The agent can send a valid session/load response later, but T3 does not adopt its configuration.

The conversation may still work. A later config_option_update can restore the displayed options while model switching remains disabled.

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included a controlled reproduction and source references.

Area

apps/server — shared ACP runtime and adapter in packages/provider-acp.

Steps to reproduce

Use a stdio ACP test agent that supports session/load and returns model and reasoning options in configOptions.

  1. Start a fresh T3 ACP runtime with an existing session ID to load. This matters: loading into an already-started runtime can behave differently.
  2. Have the agent send a replay message during session/load.
  3. Leave a quiet gap longer than the default 2-second replay-idle interval.
  4. Send the final session/load response about 3.2 seconds after the replay message. Include a model option with category: "model" and a reasoning option.
  5. Check the runtime options and supportsModelSwitchInSession after the final response arrives.

Result: T3 becomes provisionally ready after about 2 seconds. Its options remain empty after the final response, and model switching remains disabled.

For a second case, send config_option_update after the final response. The options can reappear, but the captured model-switch capability stays false.

Expected behavior

Replay idle may allow the session to become usable before the final response.

When that response arrives, T3 should adopt its configuration for the current session and update the catalog and switching capability together. It should not require a window restart.

A late response must not overwrite a newer user selection or update a superseded session.

Actual behavior and cause

There are two parts to the same failure:

  1. The final load response loses its consumer. runLoadSessionWithReplayIdle uses Effect.raceFirst between the load RPC and replay idle. When idle wins, it interrupts the RPC waiter. The agent can still finish the request, but T3 ignores the later response.
  2. Switching capability stays stale. The adapter derives supportsModelSwitchInSession from the initial setup. A later config notification can update the options without refreshing that capability.

Source on checked main at 5307c233b1f0ae72b8894dd39d94019aaf9e7cb8:

Evidence

The controlled reproduction used the real installed T3 runtime and stdio transport, with a fake ACP agent. It did not replace the race with a simulation.

  • Final response before idle: controls and switching capability remain available.
  • Idle before final response: both remain missing, even though the transport receives the final response.
  • Config notification during cold startup: options can be present while switching capability is false.
  • Config notification after idle: options return, but switching capability stays false.
  • Continued replay until the final response: controls remain available. A long load alone is not enough to trigger the defect; the quiet gap matters.

That run passed 28 assertions across seven cases. These results prove the runtime failure, not complete app acceptance or the timing of every real-agent incident.

Impact

Minor bug or occasional failure. Users can lose access to model, reasoning and Fast controls after a session load, even while the conversation continues.

Version or commit

Reproduced against 0.1.1-nightly.20261016.3190.

The affected source is unchanged between the local repair base, b72740852ffb5ab727e1ae7c042d9e89533bc341, and checked upstream main, 5307c233b1f0ae72b8894dd39d94019aaf9e7cb8. This is a source comparison, not a full unpatched-app test at the newer commit.

Environment

Linux x86_64. The controlled runtime reproduction used Node.js 24.16.0, the installed Effect 4.0.5 runtime and a fake ACP stdio agent using the v1 compatibility path. Real-world observations involved Hermes ACP.

Workaround

Restarting the T3 window restored the controls in a reported real-agent incident. This is a workaround, not a reliable fix for the race.

Proposed repair scope

Keep the authoritative load RPC alive after provisional readiness, with a bounded timeout and ownership tied to the current runtime/session/load. Adopt its effective metadata and refresh the live catalog and switching capability together.

Preserve newer config writes and notifications. Cancel pending work on supersession or shutdown. Do not replay history or activate the session twice.

A local candidate has focused runtime/adapter regressions and a controlled web-client check: delayed controls appear without a restart, and model/reasoning/Fast changes reach the same ACP process. A local desktop build is also in use. A brief empty-controls state that recovered by itself was reported during that trial; its cause is not established, and it is not evidence that all control-state transients are fixed.

I am requesting triage of this failure and repair scope before opening a PR, as required by the contribution guide.

#15177 concerns one model's option list being copied to other models. This report concerns losing the authoritative load response and retaining a stale switching capability, not per-model catalog separation.

Investigation and local implementation assistance: GPT-6.1-Sol through Hermes Agent in T3 Code.

Note

Grok responding on behalf of Julius.

Thanks for the controlled repro. I confirmed both parts on current main.

  1. runLoadSessionWithReplayIdle races the session/load request against replay idle with Effect.raceFirst (packages/provider-acp/src/server/AcpSessionRuntime.ts:1968-1975). When idle wins, the request waiter is interrupted. The session is then adopted from a placeholder response that keeps whatever options already existed (AcpSessionRuntime.ts:1914-1923), and nothing reads the real response when it arrives.
  2. The adapter computes supportsModelSwitchInSession once, from the setup response (packages/provider-acp/src/server/adapter.ts:509-517, :5795). A later config_option_update can change the options without updating it.

This isn't a duplicate of #17260 or #15177. Those are about which models show up in the picker, not about losing the load response. None of the open ACP pull requests change this race or recompute the capability after setup.

Accepting this as a bug.