Skip to content

Revert PR #2190: Enable ACPAgent on RemoteRuntime API#2451

Merged
enyst merged 1 commit intomainfrom
revert-pr-2190
Mar 16, 2026
Merged

Revert PR #2190: Enable ACPAgent on RemoteRuntime API#2451
enyst merged 1 commit intomainfrom
revert-pr-2190

Conversation

@enyst
Copy link
Copy Markdown
Collaborator

@enyst enyst commented Mar 15, 2026

HUMAN Note

I fixed the REST API checks to validate the REST API compatibility policy, and they fail on main from PR 2190, because of the incompatibility explained below. But if the agent is correct that it only breaks hand-written requests, I'm not sure it's an issue significant enough to revert..., WDYT? 🤔

cc: @simonrosenberg @xingyaoww


(OpenHands-GPT-5.4)

Why revert

#2190 added useful ACPAgent remote-runtime support, but it also shipped a breaking change to the public agent-server REST contract without the deprecation runway required by openhands-agent-server/AGENTS.md.

That package policy says the /api/** REST API is public/backward-compatible, and incompatible request/response schema changes need a deprecation notice plus a 5-minor-release runway.

What broke

  1. The public REST/OpenAPI contract changed incompatibly

  2. Real server request validation changed for older REST clients

    • I reproduced this locally.
    • On the reverted code, POST /api/conversations accepts a plain agent object without kind.
    • On Enable ACPAgent on RemoteRuntime API #2190, the same request succeeds only until openhands.agent_server.api is imported; once the real server import path runs, the same payload returns 422 with:
      • Unknown kind '' for openhands.sdk.agent.base.AgentBase; Expected one of: ['ACPAgent', 'Agent']
    • So this was not just a schema-only break; it changed behavior for clients that relied on the previous single-type defaulting behavior.
  3. This is not a confirmed persisted-state restore break for normal SDK Agent conversations

    • I also checked the persistence path.
    • A base_state.json produced on the reverted code for a normal SDK Agent still loads on Enable ACPAgent on RemoteRuntime API #2190, because SDK serialization already includes kind: "Agent".
    • So I do not have evidence that Enable ACPAgent on RemoteRuntime API #2190 broke normal SDK-created conversation restore for standard Agent payloads.
    • The risky behavior change is at AgentBase request/response boundaries for JSON that omitted kind, not normal SDK model_dump() serialization.

Evidence

Who is affected

OpenHands/OpenHands

I inspected OpenHands/OpenHands in openhands/app_server/app_conversation/live_status_app_conversation_service.py.

The app constructs StartConversationRequest(...) using the shared models and sends model_dump(mode='json', context={'expose_secrets': True}), then parses responses with ConversationInfo.model_validate(...).

That means the first-party app likely keeps working on its normal path because it already sends explicit kind and consumes the same typed models.

Other REST clients

The public REST API still broke for other clients:

  • hand-written clients posting the older plain agent shape
  • code-generated or schema-validated clients
  • any external integration relying on the previous OpenAPI contract

Why full revert instead of a targeted patch

#2190 spread ACP remote-runtime support across:

  • REST schema / validation
  • event-service loading behavior
  • ACP runtime behavior
  • Docker image provisioning
  • examples
  • evaluation workflow plumbing

A full revert cleanly restores the pre-2190 public REST contract immediately. ACP remote support can then be reintroduced additively (for example with a versioned/parallel contract or a deprecation runway).

Intentional scope of this revert

This intentionally removes the ACP remote-runtime/eval rollout from #2190:

  • eager ACP registration in the server API
  • remote-runtime ACP example
  • ACP-specific run-eval parameterization
  • Docker image ACP provisioning
  • ACPAgent remote-runtime behavior added as part of that rollout

The EXAMPLE_COST / eval-related example changes were part of that same rollout and are reverted intentionally with it.

Path forward

If ACP remote-runtime support is reintroduced, it should come back in a way that preserves the old REST contract during a migration window. Reasonable options include:

  • keeping the old agent contract working and making ACP support additive
  • introducing a parallel/versioned contract
  • documenting deprecation explicitly and honoring the 5-minor-release runway

If we keep the reverted behavior for any period, a follow-up may also be worthwhile to explicitly document/validate that ACPAgent remote-runtime configs are unsupported again, rather than depending on implicit pre-2190 behavior.

Eval status of #2190

Validation on this revert PR

  • REST API breakage check: ✅ passed
  • Python API breakage check: ✅ passed
  • Targeted local tests used for the revert itself: ✅ passed

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 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:0643a4e-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:0643a4e-golang-amd64
ghcr.io/openhands/agent-server:0643a4e-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:0643a4e-golang-arm64
ghcr.io/openhands/agent-server:0643a4e-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:0643a4e-java-amd64
ghcr.io/openhands/agent-server:0643a4e-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:0643a4e-java-arm64
ghcr.io/openhands/agent-server:0643a4e-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:0643a4e-python-amd64
ghcr.io/openhands/agent-server:0643a4e-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-amd64
ghcr.io/openhands/agent-server:0643a4e-python-arm64
ghcr.io/openhands/agent-server:0643a4e-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-arm64
ghcr.io/openhands/agent-server:0643a4e-golang
ghcr.io/openhands/agent-server:0643a4e-java
ghcr.io/openhands/agent-server:0643a4e-python

About Multi-Architecture Support

  • Each variant tag (e.g., 0643a4e-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., 0643a4e-python-amd64) are also available if needed

This reverts merge commit ca621a4.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   api.py1661292%73, 85, 100, 106, 276, 279, 283–285, 287, 293, 334
   event_service.py3198174%55–56, 74–76, 85–89, 92–95, 115, 219, 236, 290–291, 295, 303, 306, 352–353, 369, 371, 375–377, 381, 390–391, 393, 397, 403, 405, 413–418, 554, 556–557, 561, 575–577, 579, 583–586, 590–593, 601–604, 624, 628–633, 645–646, 648–649, 656–657, 659–660, 664, 670, 687–688
openhands-sdk/openhands/sdk/agent
   acp_agent.py2945581%113–115, 169–172, 174–175, 202, 204, 208, 214, 225–226, 231, 298, 359–360, 397, 407, 412, 423–426, 432–434, 437–439, 441, 443–444, 446, 448, 453, 461–462, 466–467, 471, 478, 481–482, 484, 486–488, 523, 527–528, 652–653
   base.py1912288%200, 257–259, 289, 293–297, 345–347, 357, 367, 375–376, 486, 523–524, 534–535
openhands-sdk/openhands/sdk/conversation/impl
   local_conversation.py3942394%288, 293, 321, 364, 382, 398, 463, 641–642, 645, 797, 805, 807, 818, 820–822, 847, 1040, 1044, 1114, 1121–1122
TOTAL20593514075% 

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

Taste Rating: 🔴 Needs improvement - Missing critical context for revert decision

The Problem: No Justification for Revert

This PR reverts #2190 but the description only says "This reverts #2190" without explaining why. The validation shows tests pass, but not what problem this solves.

A revert of this magnitude (removing remote runtime support, retry logic, authentication, timeout handling, Docker image support, evaluation workflow integration) needs to answer:

  1. What broke? What specific bug/issue in #2190 requires a full revert?
  2. Why full revert? If a specific feature was broken, why not fix just that feature?
  3. Impact analysis: Who is affected? Are there users already using ACPAgent with remote runtime?
  4. Path forward: What needs to happen before remote ACPAgent support can be re-introduced?

Critical Issues

1. Breaking Change Without Deprecation

[openhands-agent-server/openhands/agent_server/event_service.py]

Hardcoding Agent.model_validate instead of using type(self.stored.agent) means:

  • Any stored conversations with ACPAgent will fail to load
  • Remote runtime API silently rejects ACPAgent configs
  • No error message to users about why ACPAgent is unsupported

If ACPAgent can't work in remote mode, that limitation should be validated and documented, not silently broken.

2. Lost Production Features Without Explanation

[openhands-sdk/openhands/sdk/agent/acp_agent.py]

Removing retry logic, timeout handling, authentication, and session mode configuration suggests the entire integration was fundamentally broken. If so:

  • Why was the approach wrong?
  • What would a correct implementation look like?
  • Were these features themselves broken, or was it something else?

3. Example Cost Reporting Removed

[examples/01_standalone_sdk/40_acp_agent_example.py]

Why remove EXAMPLE_COST reporting? The example testing workflow expects this output. This seems unrelated to remote runtime issues.

4. Evaluation Risk Category

This PR affects:

  • Agent behavior (ACPAgent no longer supported remotely)
  • Evaluation workflows (agent_type parameter removed)
  • Benchmark execution (Docker images no longer include ACP servers)

🚨 Flagging for human maintainer review + lightweight eval before merge

Verdict

Needs context before merging: The revert may be technically correct, but without understanding what broke and why a full revert is needed (vs. targeted fix), it's impossible to assess whether this is the right solution.

Requested Actions

  1. Add Evidence section to PR description explaining what broke in #2190
  2. Document why full revert is needed vs. targeted fix
  3. Add validation/error messages for unsupported ACPAgent remote usage (if that's the issue)
  4. Consider whether EXAMPLE_COST removal is intentional or accidental

Key Insight: A revert this large without explanation raises more questions than it answers. Either #2190 had fundamental design flaws (document them), or there's a specific bug that could be fixed without throwing away all the work (fix just that bug).

@enyst enyst requested a review from all-hands-bot March 15, 2026 15:02
all-hands-bot

This comment was marked as outdated.

This comment was marked as resolved.

@enyst enyst requested a review from all-hands-bot March 15, 2026 15:10
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

Revert Analysis: This is a well-justified, cleanly executed revert that fixes a real backward-compatibility break.

Evidence Quality: The PR description comprehensively addresses all previous feedback with concrete evidence (OpenAPI diff, local repro, workflow failures, policy citation). The justification for full revert vs targeted patch is sound given how broadly #2190 spread ACP remote-runtime support.

Technical Execution: The revert appears clean - REST/Python API breakage checks pass, tests updated appropriately, no obvious incomplete cleanup.

Eval-Risk Flag: Per repository code review guidelines, I cannot approve this PR because it affects evaluation behavior (removes ACPAgent remote-runtime support, changes eval workflow parameters). A human maintainer should make the final decision on whether to:

  1. Proceed with this revert (restoring pre-2190 REST contract immediately), or
  2. Implement a targeted compatibility fix (preserving ACP remote support with deprecated contract support)

The engineering justification is solid - this is a pragmatic fix for a documented policy violation. But the eval impact puts the approval decision outside my scope.

@smolpaws
Copy link
Copy Markdown
Contributor

smolpaws here 🐾 I poked this from a downstream client and found a real breakage, not just a schema paper-cut.

While updating enyst/OpenHands-Tab PR #873, its agent-server-e2e check started failing against current OpenHands/software-agent-sdk@main before any WebSocket auth logic ran. The failure was at POST /api/conversations.

I verified it locally with the same request shape OpenHands-Tab sends today (plain agent object, no agent.kind):

{
  "agent": {
    "llm": {"usage_id": "agent", "model": "gpt-4o-mini"},
    "tools": [
      {"name": "terminal"},
      {"name": "file_editor"},
      {"name": "task_tracker"}
    ]
  },
  "workspace": {"kind": "LocalWorkspace", "working_dir": "/tmp"},
  "secrets": {},
  "confirmation_policy": {"kind": "NeverConfirm"},
  "max_iterations": 1
}

Results:

  • current main (with the real server import path via openhands.agent_server.api):

    • POST /api/conversations -> 422

    • detail:

      Unknown kind '' for openhands.sdk.agent.base.AgentBase; Expected one of: ['ACPAgent', 'Agent']
      
  • pre-2190 (ca621a42370f7709efdcb022eb098535d5557b54^1):

    • StartConversationRequest.model_validate(payload) -> VALID Agent
  • this revert branch (#2451):

    • same request -> 201 Created

I also checked the schema mutation directly:

  • on pre-2190 / revert, StartConversationRequest resolves AgentBase to a simple Agent ref
  • on current main, after importing openhands.agent_server.api, AgentBase becomes a discriminated oneOf union over ACPAgent | Agent, keyed by kind

So, yep: this is a real REST compatibility break introduced by #2190, and OpenHands-Tab hit it in the wild. Tiny cat verdict: this is not just OpenAPI fluff; older external REST clients really do start getting 422s here. 🐾

@enyst enyst merged commit ed924a3 into main Mar 16, 2026
45 checks passed
@enyst enyst deleted the revert-pr-2190 branch March 16, 2026 13:54
@simonrosenberg
Copy link
Copy Markdown
Collaborator

@enyst thanks for taking care of this!

@enyst
Copy link
Copy Markdown
Collaborator Author

enyst commented Mar 16, 2026

@OpenHands This PR was merged, we only take it as starting point for a discussion, don't modify it. Read all comments in this PR, and the linked PR and all its comments. Understand the problem. Look on main branch, at recent commits the REST API workflow checking compatibility failed, look at the CI log for one of them.

Then tell me: how many endpoints were affected? List them. Post a comment on this PR so that we know.

@openhands-ai
Copy link
Copy Markdown

openhands-ai bot commented Mar 16, 2026

I'm on it! enyst can track my progress at all-hands.dev

Copy link
Copy Markdown
Collaborator Author

enyst commented Mar 16, 2026

I dug through all comments on this PR and #2190, checked a recent main failure of the REST API compatibility workflow (run 23147005570 on #2430), and then locally regenerated the OpenAPI schema before and after ACPAgent registration to map the oasdiff paths back to concrete routes.

Count: 4 REST operations were affected.

1. POST /api/conversations

Before

  • request model: StartConversationRequest.agentAgentBase-Inputdirect $ref to Agent-Input
  • response model: ConversationInfo.agentAgentBase-Outputdirect $ref to Agent-Output

After #2190

  • request model: StartConversationRequest.agentAgentBase-Inputdiscriminated oneOf(ACPAgent-Input | Agent-Input) keyed by kind
  • response model: ConversationInfo.agentAgentBase-Outputdiscriminated oneOf(ACPAgent-Output | Agent-Output) keyed by kind

What changed in practice

  • This is the only one of the 4 endpoints where the request contract itself changed.
  • Older payloads that omitted agent.kind used to validate as the only known subtype (Agent); after ACP registration they became ambiguous and started returning 422.
  • On the response side, the old plain agent object used to directly expose the Agent-Output fields. After the change, those fields only existed inside the union branches.
  • That is why oasdiff reported parent-level removals like required agent/kind and agent/llm, plus optional properties such as agent/tools, agent/critic, agent/condenser, agent/agent_context, agent/mcp_config, agent/system_prompt_filename, agent/system_prompt_kwargs, etc.

2. GET /api/conversations/{conversation_id}

Before

  • response model: ConversationInfo.agent was a direct Agent-Output object

After #2190

  • response model: ConversationInfo.agent became oneOf(ACPAgent-Output | Agent-Output) via AgentBase-Output

What changed in practice

  • No request-body change here; this one was a response-schema break.
  • Before, a client looking at ConversationInfo.agent saw the plain Agent-Output shape with required kind and llm and the usual optional Agent fields on the object itself.
  • After, the top-level agent property became a polymorphic union, so those same fields were no longer guaranteed at the parent object level.

3. GET /api/conversations

Before

  • response model: list[ConversationInfo | null]
  • for each non-null ConversationInfo, agent was a direct Agent-Output object

After #2190

  • response model still: list[ConversationInfo | null]
  • but nested ConversationInfo.agent became oneOf(ACPAgent-Output | Agent-Output)

What changed in practice

  • This is the same ConversationInfo.agent break as above, just one level deeper in a batch array.
  • In the workflow log this showed up under the nested path:
    • /items/anyOf[subschema #1: ConversationInfo]/agent

4. GET /api/conversations/search

Before

  • response model: ConversationPage
  • inside ConversationPage.items[*], agent was a direct Agent-Output object

After #2190

  • response model still: ConversationPage
  • but ConversationPage.items[*].agent became oneOf(ACPAgent-Output | Agent-Output)

What changed in practice

  • Same underlying response-schema break again, this time inside the paginated search wrapper.
  • In the workflow log this showed up under:
    • items/items/agent

Shared underlying schema change

The 4 endpoint hits all come from the same underlying model mutation:

  • Before:
    • AgentBase-Input = $ref to Agent-Input
    • AgentBase-Output = $ref to Agent-Output
  • After Enable ACPAgent on RemoteRuntime API #2190:
    • AgentBase-Input = discriminated oneOf(ACPAgent-Input | Agent-Input)
    • AgentBase-Output = discriminated oneOf(ACPAgent-Output | Agent-Output)

So the breakage was not “4 unrelated endpoints broke in 4 different ways”; it was one AgentBase contract change that propagated into 4 public conversation API operations.

POST /api/conversations was the most severe because it changed both:

  1. runtime request validation for older clients, and
  2. OpenAPI request/response schema.

The other 3 were response-schema breakages caused by ConversationInfo.agent becoming polymorphic.

@OpenHands OpenHands deleted a comment from openhands-ai bot Mar 16, 2026
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.

4 participants