Skip to content

Prepare AgentSettings removal helpers#3208

Merged
neubig merged 1 commit into
mainfrom
remove-legacy-agent-settings
May 14, 2026
Merged

Prepare AgentSettings removal helpers#3208
neubig merged 1 commit into
mainfrom
remove-legacy-agent-settings

Conversation

@neubig
Copy link
Copy Markdown
Contributor

@neubig neubig commented May 11, 2026

Summary

  • keep the deprecated AgentSettings public export while main remains at v1.22.x, so the API breakage policy stays satisfied until the scheduled removal release
  • move persisted settings loading through validate_agent_settings() and canonical helpers in agent-server persistence paths
  • make validate_agent_settings() apply persisted settings migrations directly while preserving validation context for encrypted secrets
  • keep compatibility guardrails for the legacy alias while focusing migration tests on the canonical validate_agent_settings() entry point

The actual AgentSettings public export removal must wait for a version bump to v1.23.0 or later, because its scheduled removal is v1.23.0.

Testing

  • uv run pytest tests/sdk/test_settings.py -k 'agent_settings or llm_agent_settings or validate_agent_settings or acp_agent_settings_acp_env_encrypts_with_cipher' -q
  • uv run pre-commit run --files openhands-agent-server/openhands/agent_server/persistence/models.py openhands-sdk/openhands/sdk/settings/model.py tests/sdk/test_settings.py

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


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:455fdd6-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:455fdd6-golang-amd64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-golang-amd64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-golang-amd64
ghcr.io/openhands/agent-server:455fdd6-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:455fdd6-golang-arm64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-golang-arm64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-golang-arm64
ghcr.io/openhands/agent-server:455fdd6-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:455fdd6-java-amd64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-java-amd64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-java-amd64
ghcr.io/openhands/agent-server:455fdd6-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:455fdd6-java-arm64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-java-arm64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-java-arm64
ghcr.io/openhands/agent-server:455fdd6-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:455fdd6-python-amd64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-python-amd64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-python-amd64
ghcr.io/openhands/agent-server:455fdd6-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:455fdd6-python-arm64
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-python-arm64
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-python-arm64
ghcr.io/openhands/agent-server:455fdd6-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:455fdd6-golang
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-golang
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-golang
ghcr.io/openhands/agent-server:455fdd6-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:455fdd6-java
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-java
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-java
ghcr.io/openhands/agent-server:455fdd6-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:455fdd6-python
ghcr.io/openhands/agent-server:455fdd6541b28053f2d483e01de68490dbf42c66-python
ghcr.io/openhands/agent-server:remove-legacy-agent-settings-python
ghcr.io/openhands/agent-server:455fdd6-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server/persistence
   models.py1612385%185, 190, 227, 275, 310–316, 318, 320, 323, 327, 353, 370–371, 402–405, 408
openhands-sdk/openhands/sdk/settings
   model.py5695091%83, 108, 113, 352, 362–365, 368, 381, 385, 391, 401, 407, 412, 602, 615, 626, 636, 640, 642, 644, 646, 648, 650, 652, 924, 926, 1039, 1220, 1288, 1326, 1411, 1447–1450, 1476, 1600, 1645, 1677, 1687, 1689, 1694, 1712, 1725, 1727, 1729, 1731, 1738
TOTAL27052623776% 

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.

[CRITICAL ISSUES]

  • [openhands-sdk/openhands/sdk/settings/model.py] Deprecation Policy: LLMAgentSettings removal appears to violate the 5-release deprecation policy (only 4 releases since 1.19.0). See inline comment for details.

[RISK ASSESSMENT]
⚠️ Risk Level: 🟡 MEDIUM (would be HIGH if policy violation confirmed)

This is a breaking API change removing long-deprecated aliases. Risk factors:

  • ✅ Migration logic properly preserved in validate_agent_settings()
  • ✅ Tests comprehensively updated
  • AgentSettings (deprecated 1.17.0) meets 5-release requirement
  • ⚠️ LLMAgentSettings timeline needs verification (appears 1 release short)
  • The PR stacks on rel-1.22.0 and will be retargeted, which mitigates timing concerns

Recommendation: Verify deprecation timeline before merge. If policy violation confirmed, either remove only AgentSettings in this PR or update removal target to v1.24.0.

VERDICT:
⏸️ Needs clarification: Core implementation is solid, but deprecation policy compliance must be confirmed by maintainer.

KEY INSIGHT:
The migration strategy correctly preserves backward compatibility for persisted data (old "llm" discriminators still work) while preventing new code from using deprecated variants—a textbook deprecation removal, contingent on timeline verification.

Comment thread openhands-sdk/openhands/sdk/settings/model.py Outdated
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.

✅ QA Report: PASS

All changes verified successfully through functional testing.

Does this PR achieve its stated goal?

Yes. The PR successfully removes the deprecated AgentSettings and LLMAgentSettings aliases and makes validate_agent_settings() the canonical migration-aware loader for persisted agent settings. The agent-server persistence layer correctly uses the new API, and legacy payloads (including agent_kind: "llm") are properly migrated to the current schema.

Phase Result
Environment Setup ✅ Dependencies installed, project builds successfully
CI Status ✅ All checks passing (sdk-tests, agent-server-tests, cross-tests, pre-commit, deprecation check)
Functional Verification ✅ 9 functional tests + 3 integration tests all passing
Functional Verification

Test 1: Verify deprecated classes are removed

Baseline: Before this PR, AgentSettings and LLMAgentSettings could be imported from openhands.sdk and openhands.sdk.settings (though they emitted deprecation warnings).

After PR changes: Attempted to import both classes:

from openhands.sdk import AgentSettings
from openhands.sdk import LLMAgentSettings
from openhands.sdk.settings import AgentSettings
from openhands.sdk.settings import LLMAgentSettings

All import attempts correctly raise ImportError with message cannot import name 'AgentSettings' / cannot import name 'LLMAgentSettings'. The deprecated aliases have been successfully removed from the public API.

Test 2: Current agent settings payloads work

Test: Validated current agent_kind: "openhands" and agent_kind: "acp" payloads with validate_agent_settings():

# OpenHands agent
result = validate_agent_settings({
    "agent_kind": "openhands",
    "llm": {"model": "anthropic/claude-sonnet-4-5-20250929"}
})
assert result.agent_kind == "openhands"

# ACP agent
result = validate_agent_settings({
    "agent_kind": "acp",
    "acp_server": "custom",
    "acp_command": ["npx", "-y", "claude-agent-acp"]
})
assert result.agent_kind == "acp"

Both payloads validate correctly and return the expected settings instances.

Test 3: Legacy 'llm' kind migration

Test: Verified that legacy agent_kind: "llm" payloads are migrated to "openhands":

legacy_payload = {
    "schema_version": 1,
    "agent_kind": "llm",
    "llm": {"model": "legacy-model"}
}
result = validate_agent_settings(legacy_payload)

Result: result.agent_kind == "openhands" (migrated from "llm")

The v1→v2 migration (_migrate_agent_settings_v1_to_v2) correctly rewrites the discriminator from "llm" to "openhands", allowing legacy persisted payloads to load into the canonical OpenHandsAgentSettings class.

Test 4: v0 payload handling

Test: Verified that v0 payloads (no schema_version, no agent_kind) default correctly:

v0_payload = {"llm": {"model": "v0-model"}}
result = validate_agent_settings(v0_payload)

Result: Validates successfully with agent_kind == "openhands" (default discriminator).

Test 5: Agent-server persistence uses validate_agent_settings()

Verification: Inspected openhands.agent_server.persistence.models.PersistedSettings.update() source code:

  • ✅ Imports validate_agent_settings from openhands.sdk.settings
  • ✅ Line 139 calls validate_agent_settings(agent_merged) for agent settings updates
  • ✅ No references to deprecated AgentSettings.from_persisted() remain in the code

The persistence layer has been successfully updated to use the new canonical loader.

Test 6: validate_agent_settings() accepts model instances

Test: Verified that validate_agent_settings() is idempotent—passing an already-validated instance returns it unchanged:

instance = OpenHandsAgentSettings(llm=LLM(model="test-model"))
result = validate_agent_settings(instance)
assert result is instance

This allows the function to be used safely in code paths that may receive either raw payloads or already-validated instances.

Integration Test 1: Create agent from legacy settings

Test: End-to-end scenario simulating loading persisted legacy settings and creating an agent:

legacy_payload = {
    "schema_version": 1,
    "agent_kind": "llm",
    "llm": {"model": "anthropic/claude-sonnet-4-5-20250929"},
    "tools": []
}
settings = validate_agent_settings(legacy_payload)
agent = settings.create_agent()

Result:

  • ✅ Settings loaded with agent_kind="openhands" (migrated)
  • ✅ Agent created successfully (type: Agent)
  • ✅ Agent configured with specified model

Integration Test 2: default_agent_settings() helper

Test: Verified the replacement for the old AgentSettings() constructor:

settings = default_agent_settings()

Result: Returns OpenHandsAgentSettings instance with agent_kind="openhands" and default model.

Integration Test 3: AgentSettingsConfig union

Test: Verified the discriminated union behavior:

  • ✅ Accepts "openhands" discriminator → OpenHandsAgentSettings
  • ✅ Accepts "acp" discriminator → ACPAgentSettings
  • ✅ Rejects "llm" discriminator with ValidationError (expected—migration only happens via validate_agent_settings())

Automated Test Suite

Command: uv run pytest tests/sdk/test_settings.py -k "validate_agent_settings or from_persisted"

Result: All 9 test cases passing:

  • test_validate_agent_settings_defaults_to_openhands_when_discriminator_missing
  • test_validate_agent_settings_dispatches_on_agent_kind
  • test_validate_agent_settings_migrates_v0_llm_payload
  • test_validate_agent_settings_dispatches_current_acp_payload
  • test_validate_agent_settings_canonicalizes_legacy_llm_kind
  • test_validate_agent_settings_drops_legacy_verification_fields
  • test_validate_agent_settings_rejects_newer_schema_version
  • test_conversation_settings_from_persisted_migrates_v0_payload
  • test_conversation_settings_from_persisted_rejects_newer_schema_version

Deprecation Check

Command: uv run --with packaging python .github/scripts/check_deprecations.py

Result: ✅ Checked 9 deprecation metadata entries across 4 packages—all valid.

Issues Found

None.

@neubig neubig force-pushed the remove-legacy-agent-settings branch from 687bd10 to b977b29 Compare May 11, 2026 16:31
@neubig neubig changed the title Remove legacy agent settings aliases Remove legacy AgentSettings alias May 11, 2026
Copy link
Copy Markdown
Contributor Author

neubig commented May 11, 2026

Addressed the policy feedback by splitting the removal and keeping LLMAgentSettings until v1.24.0; also fixed the compatibility test import in 7f04cd7. Ready for another look. This comment was created by an AI agent (OpenHands) on behalf of the user.

@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 11, 2026 — with OpenHands AI
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: 🟢 Good taste

Clean deprecation removal that follows the repo's 5-release policy (1.17.0 → 1.22.0). Migration logic properly preserved in validate_agent_settings(), tests comprehensive, agent-server updated correctly.

[RISK ASSESSMENT]
⚠️ Risk Level: 🟡 MEDIUM

Breaking API change removing a long-deprecated public class. Risk factors:

  • ✅ Migration path preserved via validate_agent_settings()
  • ✅ 5-release deprecation runway satisfied
  • ✅ Tests cover legacy payload migration (including agent_kind: "llm" canonicalization)
  • ✅ Agent-server persistence correctly updated
  • ⚠️ External code that ignored deprecation warnings will break

VERDICT: ✅ Worth merging

KEY INSIGHT: The refactor elevates validate_agent_settings() from a wrapper to the canonical migration-aware loader, simplifying the API surface while maintaining full backward compatibility through migrations.

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.

✅ QA Report: PASS

All PR goals achieved. The deprecated AgentSettings alias has been successfully removed while maintaining backward compatibility for persisted settings and keeping LLMAgentSettings available through v1.24.0.

Does this PR achieve its stated goal?

Yes. The PR set out to remove the deprecated AgentSettings public alias while preserving migration support for persisted settings. All four stated goals were verified:

  1. AgentSettings is no longer importable from openhands.sdk
  2. LLMAgentSettings remains available with proper deprecation warnings (scheduled for v1.24.0 removal)
  3. validate_agent_settings() correctly handles migration from legacy payloads
  4. ✅ Agent-server persistence layer successfully uses validate_agent_settings() instead of the removed AgentSettings.from_persisted()

Additionally verified that core agent runtime functionality remains intact with no breaking changes.

Phase Result
Environment Setup ✅ Built successfully with make build
CI Status ✅ All core tests passing (sdk-tests, agent-server-tests, tools-tests, workspace-tests, cross-tests, windows-tests, pre-commit)
Functional Verification ✅ All manual tests passed
Functional Verification

Test 1: Verify AgentSettings is no longer importable

Attempt to import the removed alias:

from openhands.sdk import AgentSettings

Result:

ImportError: cannot import name 'AgentSettings' from 'openhands.sdk'

Confirmed: The deprecated AgentSettings alias has been successfully removed from the public API.


Test 2: Verify LLMAgentSettings still exists with deprecation warning

Import the still-supported alias:

import warnings
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")
    from openhands.sdk.settings import LLMAgentSettings

Result:

DeprecationWarning: Importing 'LLMAgentSettings' from openhands.sdk.settings is deprecated as of 1.19.0 and will be removed in 1.24.0. Use OpenHandsAgentSettings directly. LLMAgentSettings was renamed in v1.19.0.

Confirmed: LLMAgentSettings remains available with proper deprecation notice through v1.24.0.


Test 3: Verify validate_agent_settings handles migration

Test 3a - Legacy v0 payload (no schema_version):

from openhands.sdk.settings import validate_agent_settings

legacy_v0 = {"llm": {"model": "test-model"}}
settings = validate_agent_settings(legacy_v0)

print(f"Model: {settings.llm.model}")
print(f"Schema version: {settings.schema_version}")
print(f"Agent kind: {settings.agent_kind}")

Result:

Model: test-model
Schema version: 3
Agent kind: openhands

Confirmed: Legacy v0 payloads are correctly migrated to current schema version.

Test 3b - Legacy 'llm' agent_kind (should canonicalize to 'openhands'):

legacy_llm_kind = {
    "schema_version": 1,
    "agent_kind": "llm",
    "llm": {"model": "legacy-model"}
}
settings = validate_agent_settings(legacy_llm_kind)

print(f"Agent kind: {settings.agent_kind}")
print(f"Model: {settings.llm.model}")

Result:

Agent kind: openhands
Model: legacy-model

Confirmed: Legacy agent_kind: "llm" payloads are migrated to the canonical "openhands" discriminator.

Test 3c - Current OpenHandsAgentSettings payload:

current_payload = {
    "schema_version": 3,
    "agent_kind": "openhands",
    "llm": {"model": "current-model"}
}
settings = validate_agent_settings(current_payload)

print(f"Agent kind: {settings.agent_kind}")
print(f"Model: {settings.llm.model}")

Result:

Agent kind: openhands
Model: current-model

Confirmed: Current payloads validate successfully without modification.


Test 4: Verify core agent creation still works

Create an agent using OpenHandsAgentSettings:

from openhands.sdk import LLM, Tool
from openhands.sdk.settings import OpenHandsAgentSettings
from openhands.tools.terminal import TerminalTool

llm = LLM(
    model="anthropic/claude-3-5-sonnet-20241022",
    api_key="dummy-key-for-testing"
)

settings = OpenHandsAgentSettings(
    llm=llm,
    tools=[Tool(name=TerminalTool.name)]
)

agent = settings.create_agent()

print(f"Agent class: {agent.__class__.__name__}")
print(f"LLM model: {agent.llm.model}")
print(f"Tools: {[tool.name for tool in agent.tools]}")

Result:

Agent class: Agent
LLM model: anthropic/claude-3-5-sonnet-20241022
Tools: ['terminal']

Confirmed: Core agent creation works without breaking changes.

Test settings serialization round-trip:

settings_dict = settings.model_dump()
restored_settings = validate_agent_settings(settings_dict)

print(f"Original agent kind: {settings.agent_kind}")
print(f"Restored agent kind: {restored_settings.agent_kind}")
print(f"Models match: {restored_settings.llm.model == settings.llm.model}")

Result:

Original agent kind: openhands
Restored agent kind: openhands
Models match: True

Confirmed: Settings serialization and deserialization round-trip successfully.


Test 5: Verify agent-server persistence logic

Test PersistedSettings.update with legacy payload:

from openhands.agent_server.persistence.models import PersistedSettings

settings = PersistedSettings()

update_payload = {
    "agent_settings_diff": {
        "schema_version": 1,
        "agent_kind": "llm",
        "llm": {"model": "updated-model"}
    }
}

settings.update(update_payload)

print(f"Agent kind: {settings.agent_settings.agent_kind}")
print(f"Model: {settings.agent_settings.llm.model}")

Result:

Agent kind: openhands
Model: updated-model

Confirmed: The agent-server persistence layer correctly uses validate_agent_settings() to handle legacy payloads. The key change in persistence/models.py line 139 works as intended.

Issues Found

None.


Summary: This PR successfully removes the deprecated AgentSettings alias as planned, migrates all internal usage to validate_agent_settings(), and maintains full backward compatibility for persisted settings. All goals achieved with no breaking changes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@neubig neubig changed the title Remove legacy AgentSettings alias Prepare AgentSettings removal helpers May 11, 2026
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig force-pushed the remove-legacy-agent-settings branch from 6a766ec to 455fdd6 Compare May 14, 2026 01:51
@neubig neubig requested a review from all-hands-bot May 14, 2026 01:53
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: 🟢 Good taste

Clean refactoring that consolidates migration logic into validate_agent_settings() as the canonical entry point. The deprecated AgentSettings.from_persisted() still works via delegation, preserving backward compatibility while simplifying the codebase.

Key improvements:

  • Migration logic centralized in one place (validate_agent_settings)
  • Agent-server persistence paths now use the canonical helper
  • Tests properly updated to focus on the canonical entry point
  • The semantic change (validate_agent_settings now always applies migrations) improves robustness by handling legacy payloads automatically

[RISK ASSESSMENT]
⚠️ Risk Level: 🟢 LOW

Internal refactoring with no behavior changes. Migration logic moved but functionality identical. Tests comprehensive, backward compatibility preserved via delegation. No eval risk - this is purely internal reorganization.

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.

✅ QA Report: PASS

Successfully verified that the refactoring consolidates migration logic into validate_agent_settings() while preserving backward compatibility through the deprecated AgentSettings.from_persisted() alias.

Does this PR achieve its stated goal?

Yes. The PR successfully moves persisted settings migration logic from AgentSettings.from_persisted() into validate_agent_settings(), making it the canonical entry point for all agent settings validation and migration. The deprecated AgentSettings.from_persisted() now simply delegates to validate_agent_settings(), maintaining backward compatibility until the scheduled v1.23.0 removal. Both agent-server persistence paths (PersistedSettings.update() and PersistedSettings._normalize_inputs()) now correctly use validate_agent_settings() for migration and validation.

Phase Result
Environment Setup make build completed successfully
CI Status ✅ 22 checks passed, 7 pending (Docker builds), 0 failing
Functional Verification ✅ All 10 functional tests passed
Functional Verification

Verification Strategy

This PR is a refactoring that moves migration logic from one location to another while maintaining identical behavior. To verify correctness, I:

  1. Established the baseline: Confirmed the refactoring goal from the PR description
  2. Exercised the new code path: Tested validate_agent_settings() directly with various payloads requiring migration
  3. Verified backward compatibility: Tested AgentSettings.from_persisted() still works and produces identical results
  4. Tested integration points: Verified agent-server persistence models use the new function correctly

Test 1: Core Migration Functionality (6 tests)

Ran comprehensive tests of validate_agent_settings() migration behavior:

$ uv run python test_pr_validation.py

Results:

  • ✅ v0 payloads (no schema_version, no agent_kind) migrate to current schema (v3, agent_kind='openhands')
  • ✅ Legacy agent_kind='llm' payloads canonicalize to agent_kind='openhands'
  • AgentSettings.from_persisted() still works (backward compatibility)
  • ✅ Both methods produce identical results for all payload types
  • ✅ ACP agent settings validate correctly through discriminated union
  • ✅ Already-validated instances pass through as no-op

Key observation: The migration logic now lives in validate_agent_settings() (lines 1327-1332 in model.py), and AgentSettings.from_persisted() simply delegates to it (line 1375).

Test 2: Persistence Layer Integration (4 tests)

Verified agent-server persistence models use the refactored function:

$ uv run python test_persistence_integration.py

Results:

  • PersistedSettings.update() correctly uses validate_agent_settings() for merging and migrating partial updates
  • PersistedSettings._normalize_inputs() applies migrations during deserialization via validate_agent_settings()
  • ✅ ACP agent settings deserialize correctly through the persistence layer
  • PersistedSettings.from_persisted() applies nested migrations (both top-level and agent settings migrations)

Key observation: Both code paths in the agent-server that previously called AgentSettings.from_persisted() now call validate_agent_settings() directly:

  • Line 142: new_agent = validate_agent_settings(agent_merged)
  • Line 232: data["agent_settings"] = validate_agent_settings(coerced, context=info.context)

Before/After Evidence

Before (pre-refactoring behavior):

  • Migration logic lived in AgentSettings.from_persisted()
  • validate_agent_settings() only validated, did not migrate
  • Agent-server called AgentSettings.from_persisted() for migrations

After (current PR):

  • Migration logic lives in validate_agent_settings() (consolidated)
  • AgentSettings.from_persisted() delegates to validate_agent_settings()
  • Agent-server calls validate_agent_settings() directly
  • Result: Same migrations applied, identical output, simpler call graph

Example migration flow:

# Input: v0 payload (no schema_version)
{"llm": {"model": "test-model"}}

# After validate_agent_settings():
OpenHandsAgentSettings(
    schema_version=3,          # migrated
    agent_kind="openhands",    # migrated
    llm=LLM(model="test-model")
)

Test Coverage Summary

10 functional tests passed:

  • 6 tests for core validate_agent_settings() behavior
  • 4 tests for agent-server persistence integration

All test scenarios:

  1. v0 → v3 migration (no schema_version)
  2. v1 → v3 migration (legacy agent_kind='llm')
  3. Current v3 payloads (no migration needed)
  4. ACP agent settings discriminated union
  5. Already-validated instance pass-through
  6. Backward compatibility via AgentSettings.from_persisted()
  7. Equivalence of both methods
  8. PersistedSettings.update() merge and migrate
  9. PersistedSettings._normalize_inputs() during deserialization
  10. Nested migrations in PersistedSettings.from_persisted()

Issues Found

None.

@neubig neubig merged commit b716e58 into main May 14, 2026
40 checks passed
@neubig neubig deleted the remove-legacy-agent-settings branch May 14, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-this This label triggers a PR review by OpenHands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants