Skip to content

fix(sdk): reject switch_llm/switch_profile on ACP conversations - #4333

Draft
VascoSch92 wants to merge 1 commit into
mainfrom
openhands/fix-4158-switch-profile-acp-guard
Draft

fix(sdk): reject switch_llm/switch_profile on ACP conversations#4333
VascoSch92 wants to merge 1 commit into
mainfrom
openhands/fix-4158-switch-profile-acp-guard

Conversation

@VascoSch92

@VascoSch92 VascoSch92 commented Aug 3, 2026

Copy link
Copy Markdown
Member

HUMAN:


AGENT:

Why

Fixes #4158. On an ACP conversation, switch_profile (and the underlying switch_llm) half-applied: base_state.json was rewritten to the new OpenHands LLM while the live ACP session kept running the old ACP agent/model. The caller (agent-canvas UI / REST endpoint / switch_llm tool) was told the switch succeeded, so persisted state and the running agent disagreed.

The root cause is that an ACP conversation runs its model inside the ACP subprocess, which owns its own model. switch_llm only swaps OpenHands' own LLM object, which has no effect on an ACP subprocess — but it still persisted the swap, producing the inconsistency.

Summary

  • Guard LocalConversation.switch_llm to raise ValueError when the conversation's agent is an ACPAgent, pointing callers at switch_acp_model instead. switch_profile delegates to switch_llm, so it is covered too.
  • Both existing callers already surface ValueError as a 4xx (POST /switch_profile → 400) or an error observation (SwitchLLM tool), so the half-applied path is now rejected loudly rather than silently succeeding.
  • Added regression tests for both switch_llm and switch_profile against an ACP conversation.

This matches the issue's suggested fix: "cross-agent-kind switching is out of scope, so switch_profile should validate agent_kind compatibility and return 4xx."

Issue Number

Fixes #4158

How to Test

Run the targeted tests:

uv run pytest tests/sdk/conversation/test_switch_model.py -q
uv run pytest tests/agent_server/test_conversation_router.py -q -k switch

Both suites pass (26 and 15 tests respectively). New tests:

  • test_switch_llm_rejects_acp_agentswitch_llm on an ACP conversation raises ValueError and leaves the agent/state untouched.
  • test_switch_profile_rejects_acp_agentswitch_profile on an ACP conversation raises ValueError; persisted state is not rewritten.

Pre-commit (ruff, pyright, import rules) passes on the changed files.

Video/Screenshots

N/A — SDK-level behavior change covered by unit tests.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

This is intentionally a "reject loudly" fix rather than "tear down and re-provision the ACP agent from updated state," matching the out-of-scope guidance in the issue. Cross-agent-kind live switching can be a follow-up if desired. Downstream UIs should also avoid offering incompatible profiles mid-conversation, but that is outside this repo.

This PR was created by an AI agent (OpenHands) on behalf of the user.

@VascoSch92 can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:e77cd7f-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-e77cd7f-python \
  ghcr.io/openhands/agent-server:e77cd7f-python

All tags pushed for this build

ghcr.io/openhands/agent-server:e77cd7f-golang-amd64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-golang-amd64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-golang-amd64
ghcr.io/openhands/agent-server:e77cd7f-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:e77cd7f-golang-arm64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-golang-arm64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-golang-arm64
ghcr.io/openhands/agent-server:e77cd7f-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:e77cd7f-java-amd64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-java-amd64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-java-amd64
ghcr.io/openhands/agent-server:e77cd7f-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:e77cd7f-java-arm64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-java-arm64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-java-arm64
ghcr.io/openhands/agent-server:e77cd7f-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:e77cd7f-python-amd64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-python-amd64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-python-amd64
ghcr.io/openhands/agent-server:e77cd7f-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:e77cd7f-python-arm64
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-python-arm64
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-python-arm64
ghcr.io/openhands/agent-server:e77cd7f-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:e77cd7f-golang
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-golang
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-golang
ghcr.io/openhands/agent-server:e77cd7f-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:e77cd7f-java
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-java
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-java
ghcr.io/openhands/agent-server:e77cd7f-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:e77cd7f-python
ghcr.io/openhands/agent-server:e77cd7f2d6876c4d9860a74677ac9a063bc5f960-python
ghcr.io/openhands/agent-server:openhands-fix-4158-switch-profile-acp-guard-python
ghcr.io/openhands/agent-server:e77cd7f-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., e77cd7f-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., e77cd7f-python-amd64) are also available if needed

An ACP conversation runs its model inside the ACP subprocess, which owns
its own model. switch_llm/switch_profile only swaps OpenHands' own LLM
object, so calling it on an ACP conversation half-applied: base_state.json
was rewritten to the new LLM while the live session kept the old ACP agent,
and callers were told the switch succeeded.

Guard switch_llm so it raises ValueError for ACP conversations, pointing
callers at switch_acp_model. Both existing callers (the REST switch_profile
endpoint and the SwitchLLM tool) already surface ValueError as a 4xx / error
observation, so the half-applied path is now rejected loudly.

Fixes #4158

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/conversation/impl
   local_conversation.py10539191%150, 603–604, 637, 654, 982, 1006–1007, 1012, 1027, 1029, 1133, 1149, 1197, 1223, 1287, 1291–1297, 1356, 1376–1378, 1427, 1446–1448, 1786–1787, 1802, 1890–1891, 1894, 2027, 2030–2031, 2056, 2089–2090, 2093, 2099, 2180, 2187, 2190, 2193, 2197–2198, 2202–2203, 2206, 2213, 2238, 2242, 2245, 2264, 2316, 2319, 2358, 2365–2366, 2374, 2378–2380, 2387, 2424–2430, 2433, 2436, 2443, 2506, 2511, 2620–2621, 2639–2640, 2669, 2869, 2873, 2943, 2950–2951
TOTAL387601108171% 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] switch_profile on an ACP conversation half-applies: state file updated but live session keeps old agent

2 participants