Skip to content

feat(agent-profiles): one launch pipeline for every conversation start - #5154

Open
simonrosenberg wants to merge 5 commits into
mainfrom
agent-profile-unified-launch
Open

simonrosenberg wants to merge 5 commits into
mainfrom
agent-profile-unified-launch

Conversation

@simonrosenberg

@simonrosenberg simonrosenberg commented Sep 17, 2026

Copy link
Copy Markdown
Member

HUMAN:

Filing the SDK half of #5141: the default profile and named profiles have to build the same agent from one pipeline, so new profile fields stop needing an implementation per launch path. Canvas and cloud adoption follow separately.


AGENT:

Why

Launching the default Agent Profile and launching a named one built different agents from the same stored settings, because the launch had several code paths and each set agent fields its own way: a Pydantic validator for agent_settings, the agent_profile_id branch of conversation_service, a second copy of that branch in the Docker runtime's prepare_start, and a separate dry-run for materialize. Every profile field had to be implemented, and kept correct, once per path — #3967, #4014, #4016 and #4542 were that cost paid one field at a time.

This is step 1–3 of #5141 (the SDK steps). Canvas and cloud adoption are the follow-ups listed below.

Summary

  • One function builds every launch. openhands.sdk.profiles.prepare_agent_launch(source, *, catalog, runtime, additions, profile_origin, build_agent) resolves a profile's llm_profile_ref / mcp_server_refs / disabled_skills and owns the launch-time fields: tool defaults and browser injection, forced streaming, skill catalog and ACP skill sourcing, project-skill loading, suffix + additions, current_datetime, load_memory, and the secret scope. Runtime-dependent answers come in as an explicit AgentLaunchRuntime, so the Docker runtime can pass its container's answer instead of the host's. conversation_service, Docker mediation and materialize all call it through the new agent_server/agent_launch.py; _resolve_agent_from_profile, _with_load_memory and _apply_acp_skill_sourcing are gone, as is the duplicated profile branch in prepare_start.
  • materialize is the same call with side effects off (build_agent=False, which skips create_agent() — the only step that can refresh a subscription LLM's credentials over the network). Its verdict and resolved_settings now come from the launch itself.
  • New request shapes. StartConversationRequest accepts an inline agent_profile draft (resolved exactly like a stored one, never saved), and agent_settings is deprecated (deprecated: true in OpenAPI, removal target v1.55.0). During the window the server converts it into an inline profile with the payload's own LLM/MCP/skills as its catalog, so it takes the same pipeline instead of the validator shortcut; fields no profile models (critic_api_key, user_message_suffix, agent_context.secrets, acp_isolate_data_dir, …) are carried through, not dropped.
  • AgentLaunchAdditions.llm_profile_ref gives the chat LLM picker a per-launch override, recorded in LaunchedAgentProfile (which also gained inline). Additions stay additive: they carry no tools, MCP servers, skills or secrets, and a test asserts a scoped profile's tools/MCP/skills/secret scope are byte-identical with and without them.
  • One structured failure. A dangling LLM profile or MCP server ref raises UnresolvedProfileReferences and the start endpoint returns 422 with {"code": "unresolved_profile_references", "message", "dangling_llm_profile_ref", "dangling_mcp_server_refs"} — no silent fallback to a different path.

REST API contract changes

Compared with base OpenAPI c00f26a0c602 for public /api/** paths.

--- base public OpenAPI
+++ head public OpenAPI
@@ -752,0 +753,15 @@
+schema ACPAgentProfile property acp_args optional schema=anyOf=[type="array" items=type="string",type="null"]
+schema ACPAgentProfile property acp_command optional schema=anyOf=[type="string",type="null"]
+schema ACPAgentProfile property acp_model optional schema=anyOf=[type="string",type="null"]
+schema ACPAgentProfile property acp_prompt_timeout optional schema=type="number" default=1800.0 exclusiveMinimum=0.0
+schema ACPAgentProfile property acp_server optional schema=type="string" enum=["claude-code","codex","gemini-cli","kimi-code","pi","opencode","custom"] default="claude-code"
+schema ACPAgentProfile property acp_session_mode optional schema=anyOf=[type="string",type="null"]
+schema ACPAgentProfile property acp_startup_timeout optional schema=type="number" default=90.0 exclusiveMinimum=0.0
+schema ACPAgentProfile property agent_kind optional schema=type="string" const="acp" default="acp"
+schema ACPAgentProfile property id optional schema=type="string" format="uuid"
+schema ACPAgentProfile property mcp_server_refs optional schema=anyOf=[type="array" items=type="string",type="null"]
+schema ACPAgentProfile property name required schema=type="string" minLength=1
+schema ACPAgentProfile property revision optional schema=type="integer" default=0 minimum=0.0
+schema ACPAgentProfile property schema_version optional schema=type="integer" default=2 minimum=1.0
+schema ACPAgentProfile property secret_refs optional schema=anyOf=[type="array" items=type="string",type="null"]
+schema ACPAgentProfile type="object" additionalProperties=false
@@ -938,0 +954 @@
+schema AgentLaunchAdditions property llm_profile_ref optional schema=anyOf=[type="string" minLength=1,type="null"]
@@ -1883,0 +1900,9 @@
+schema LLMSummarizingCondenserSettings property condenser_kind optional schema=type="string" const="llm_summarizing" default="llm_summarizing"
+schema LLMSummarizingCondenserSettings property enabled optional schema=type="boolean" default=true
+schema LLMSummarizingCondenserSettings property hard_context_reset_context_scaling optional schema=type="number" default=0.8 exclusiveMinimum=0.0 exclusiveMaximum=1.0
+schema LLMSummarizingCondenserSettings property hard_context_reset_max_retries optional schema=type="integer" default=5 exclusiveMinimum=0.0
+schema LLMSummarizingCondenserSettings property keep_first optional schema=type="integer" default=2 minimum=0.0
+schema LLMSummarizingCondenserSettings property max_size optional schema=type="integer" default=240 minimum=20.0
+schema LLMSummarizingCondenserSettings property max_tokens optional schema=anyOf=[type="integer" exclusiveMinimum=0.0,type="null"]
+schema LLMSummarizingCondenserSettings property minimum_progress optional schema=type="number" default=0.1 exclusiveMinimum=0.0 exclusiveMaximum=1.0
+schema LLMSummarizingCondenserSettings type="object"
@@ -1884,0 +1910,2 @@
+schema LaunchedAgentProfile property inline optional schema=type="boolean" default=false
+schema LaunchedAgentProfile property llm_profile_ref optional schema=anyOf=[type="string",type="null"]
@@ -2170,0 +2198,3 @@
+schema NoOpCondenserSettings property condenser_kind optional schema=type="string" const="no_op" default="no_op"
+schema NoOpCondenserSettings property enabled optional schema=type="boolean" default=true
+schema NoOpCondenserSettings type="object"
@@ -2182,0 +2213,18 @@
+schema OpenHandsAgentProfile property agent optional schema=type="string" default="CodeActAgent"
+schema OpenHandsAgentProfile property agent_kind optional schema=type="string" const="openhands" default="openhands"
+schema OpenHandsAgentProfile property condenser optional schema=oneOf=[LLMSummarizingCondenserSettings,NoOpCondenserSettings]
+schema OpenHandsAgentProfile property disabled_skills optional schema=type="array" items=type="string"
+schema OpenHandsAgentProfile property enable_sub_agents optional schema=type="boolean" default=false
+schema OpenHandsAgentProfile property enable_switch_llm_tool optional schema=type="boolean" default=true
+schema OpenHandsAgentProfile property id optional schema=type="string" format="uuid"
+schema OpenHandsAgentProfile property llm_profile_ref required schema=type="string" minLength=1
+schema OpenHandsAgentProfile property mcp_server_refs optional schema=anyOf=[type="array" items=type="string",type="null"]
+schema OpenHandsAgentProfile property name required schema=type="string" minLength=1
+schema OpenHandsAgentProfile property revision optional schema=type="integer" default=0 minimum=0.0
+schema OpenHandsAgentProfile property schema_version optional schema=type="integer" default=2 minimum=1.0
+schema OpenHandsAgentProfile property secret_refs optional schema=anyOf=[type="array" items=type="string",type="null"]
+schema OpenHandsAgentProfile property system_message_suffix optional schema=anyOf=[type="string",type="null"]
+schema OpenHandsAgentProfile property tool_concurrency_limit optional schema=type="integer" default=1 minimum=1.0
+schema OpenHandsAgentProfile property tools optional schema=anyOf=[type="array" items=Tool-Input,type="null"]
+schema OpenHandsAgentProfile property verification optional schema=ProfileVerificationSettings
+schema OpenHandsAgentProfile type="object" additionalProperties=false
@@ -2289,0 +2338,8 @@
+schema ProfileVerificationSettings property critic_enabled optional schema=type="boolean" default=false
+schema ProfileVerificationSettings property critic_mode optional schema=type="string" enum=["finish_and_message","all_actions"] default="finish_and_message"
+schema ProfileVerificationSettings property critic_model_name optional schema=anyOf=[type="string",type="null"]
+schema ProfileVerificationSettings property critic_server_url optional schema=anyOf=[type="string",type="null"]
+schema ProfileVerificationSettings property critic_threshold optional schema=type="number" default=0.6 minimum=0.0 maximum=1.0
+schema ProfileVerificationSettings property enable_iterative_refinement optional schema=type="boolean" default=false
+schema ProfileVerificationSettings property max_refinement_iterations optional schema=type="integer" default=3 minimum=1.0
+schema ProfileVerificationSettings type="object"
@@ -2489,0 +2546 @@
+schema StartConversationRequest property agent_profile optional schema=anyOf=[oneOf=[OpenHandsAgentProfile,ACPAgentProfile],type="null"]

Issue Number

Fixes #5141

How to Test

Unit tests:

uv run pytest tests/sdk/profiles tests/agent_server/test_agent_launch_parity.py \
  tests/agent_server/test_agent_profile_conv_start.py \
  tests/agent_server/test_agent_launch_additions.py \
  tests/agent_server/test_acp_skill_sourcing.py \
  tests/agent_server/docker_runtime tests/agent_server/test_conversation_router.py -q

End-to-end against a real agent-server (this is the interesting one — it reproduces the table in #5141 without canvas):

uv run python .pr/launch_parity_e2e.py

It boots python -m openhands.agent_server on a temp persistence dir, stores two identically-configured profiles (default and default-copy), launches a conversation through agent_profile_id for each, through an inline agent_profile draft, and through the deprecated agent_settings, then diffs the agents the server actually built against the materialize preview of the same profile. Output in .pr/launch_parity_e2e_output.txt:

legacy current_datetime sent 2020-01-01T00:00, launched with 2026-09-17T14:08:55.977969-04:00
default-copy (agent_profile_id): MATCH
inline (agent_profile): MATCH
legacy (agent_settings): MATCH
materialize (default): MATCH
dangling refs -> HTTP 422 {"detail":{"code":"unresolved_profile_references","message":"LLM profile 'gone' not found; MCP server(s) not configured: nope","dangling_llm_profile_ref":"gone","dangling_mcp_server_refs":["nope"]}}
RESULT: PASS

Compared field by field: llm (whole dump), tools, MCP keys, skills, suffix, disabled_skills, load_project_skills, load_memory, condenser, critic, concurrency, switch-LLM and whether a timestamp is present. The one deliberate exception is the skill catalog on the agent_settings path: that payload carries the client's own catalog (canvas assembles one today), which is exactly what the canvas follow-up removes.

Type

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

Notes

Behavior changes reviewers should weigh:

  • A dangling LLM ref at start is now 422, not 404, and the 422 detail gained code / dangling_llm_profile_ref (the old message / dangling_mcp_server_refs keys are unchanged). Canvas never reads that status — it pre-checks the profile list and downgrades to agent_settings — and that rule is what the follow-up deletes.
  • agent_settings is no longer converted in the validator, so StartConversationRequest(agent_settings=...).agent is None until the server resolves it, and an invalid payload is rejected by the start endpoint (422) rather than at parse time. The field also lost exclude=True so it round-trips over the wire. An explicit "agent": null alongside another source no longer crashes.
  • The agent_settings path now gets the launch-owned fields too, because it goes through the same pipeline: streaming forced on, load_project_skills=True, browser added when the payload's tools is null and the runtime has it, and a fresh current_datetime instead of the saved one. Canvas already sends stream: true, load_project_skills: true and an explicit tool list, so its payload is unaffected; a hand-rolled REST client that relied on those staying off would see the change.
  • The Docker runtime now resolves twice: once before the container starts (build_agent=False, so a dangling ref still fails fast without paying for a container) and once after, with the container's own runtime answer (GET /server_info for browser availability, openhands_managed skills). Previously the host's answers were used for a container agent, which meant an ACP profile in Docker got no managed skills.
  • /server_info advertises unified_agent_launch_v1.

Not fixed here, found while testing: a condenser's max_tokens inheritance keys off model_fields_set, so a stored profile (loaded from JSON, every field "set") does not inherit the LLM's token limit while an in-memory one does. It is consistent across today's product paths (both read persisted JSON) and predates this PR, so I left it alone rather than widen the diff.

Follow-ups, per #5141:

🤖 Generated with Claude Code


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

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-slim amd64, arm64 python-node-runtime Link
python-minimal amd64, arm64 python-node-runtime Link
python amd64, arm64 python-node-runtime 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:97f40ab-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:97f40ab-golang-amd64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-golang-amd64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-golang-amd64
ghcr.io/openhands/agent-server:97f40ab-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:97f40ab-golang-arm64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-golang-arm64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-golang-arm64
ghcr.io/openhands/agent-server:97f40ab-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:97f40ab-java-amd64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-java-amd64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-java-amd64
ghcr.io/openhands/agent-server:97f40ab-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:97f40ab-java-arm64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-java-arm64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-java-arm64
ghcr.io/openhands/agent-server:97f40ab-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:97f40ab-python-amd64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-amd64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-amd64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-amd64
ghcr.io/openhands/agent-server:97f40ab-python-arm64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-arm64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-arm64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-arm64
ghcr.io/openhands/agent-server:97f40ab-python-minimal-amd64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-minimal-amd64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-minimal-amd64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-minimal-amd64
ghcr.io/openhands/agent-server:97f40ab-python-minimal-arm64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-minimal-arm64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-minimal-arm64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-minimal-arm64
ghcr.io/openhands/agent-server:97f40ab-python-slim-amd64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-slim-amd64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-slim-amd64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-slim-amd64
ghcr.io/openhands/agent-server:97f40ab-python-slim-arm64
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-slim-arm64
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-slim-arm64
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-slim-arm64
ghcr.io/openhands/agent-server:97f40ab-golang
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-golang
ghcr.io/openhands/agent-server:agent-profile-unified-launch-golang
ghcr.io/openhands/agent-server:97f40ab-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:97f40ab-java
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-java
ghcr.io/openhands/agent-server:agent-profile-unified-launch-java
ghcr.io/openhands/agent-server:97f40ab-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:97f40ab-python-minimal
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-minimal
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-minimal
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-minimal
ghcr.io/openhands/agent-server:97f40ab-python-slim
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python-slim
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python-slim
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime-slim
ghcr.io/openhands/agent-server:97f40ab-python
ghcr.io/openhands/agent-server:97f40abbf1634e6239b09069b40844b42ccadca6-python
ghcr.io/openhands/agent-server:agent-profile-unified-launch-python
ghcr.io/openhands/agent-server:97f40ab-python-node-runtime

About Multi-Architecture Support

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

Collapse the several code paths that built a launch agent into
prepare_agent_launch(), the single SDK function that resolves an Agent
Profile's references and applies the runtime-dependent and per-launch
pieces. conversation_service, the Docker runtime's mediation and the
materialize preview all call it, so a profile named `default` and a named
one build the same agent, and a preview can no longer disagree with a
launch.

Adds an inline `agent_profile` draft and a per-launch `llm_profile_ref`
override, deprecates `agent_settings` (converted into an inline profile so
it takes the same pipeline), and returns one structured error for dangling
LLM/MCP references.

Fixes #5141

Co-authored-by: openhands <openhands@all-hands.dev>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📁 PR Artifacts Notice

This PR contains a .pr/ directory with temporary PR-specific documents. The directory will be automatically removed when the PR is approved.

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

Co-authored-by: openhands <openhands@all-hands.dev>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   agent_launch.py851385%53–55, 83–84, 87–88, 103–104, 144–145, 183–184
   agent_profiles_router.py2141792%166, 170, 174, 193, 196, 219, 332, 336, 429, 469–470, 535–537, 548–550
   conversation_router.py2691594%190, 316, 398, 444, 504, 674–677, 689–692, 732, 770
   conversation_service.py119412689%187–188, 197, 224–225, 229–230, 235, 431–432, 493, 596, 603–604, 705, 791, 842–843, 850, 882–883, 899, 930, 934, 946, 966, 978–981, 987–988, 997, 999, 1065, 1075, 1100, 1106–1107, 1111–1112, 1120, 1147, 1153, 1247, 1253, 1258, 1264, 1272–1273, 1282–1285, 1294, 1306, 1314, 1360, 1366–1367, 1370–1372, 1399, 1451, 1500–1501, 1505, 1551–1552, 1628, 1683–1685, 1687–1688, 1691–1692, 1729, 1803–1804, 1836, 1839, 1846–1848, 1851–1852, 1856–1858, 1861–1862, 1866–1868, 1871–1872, 1901, 1910, 1953, 1963–1965, 2025, 2028, 2055, 2065, 2070–2073, 2087, 2098, 2110–2111, 2143, 2238, 2295, 2353, 2368–2369, 2501, 2747, 2800, 2803
   server_details_router.py61297%27–28
openhands-agent-server/openhands/agent_server/docker_runtime
   mediation.py63690%74–77, 106–107
   routers.py1879748%49, 56–62, 80–85, 87–88, 92–94, 96–97, 100–103, 105–114, 116–118, 121–123, 127–129, 135–140, 143–146, 148–151, 153, 156, 167, 176, 186, 192–194, 204–206, 215, 240, 256, 262–263, 285, 306–308, 327, 329–343, 349, 360, 369
openhands-sdk/openhands/sdk/conversation
   message_request.py9189%19
   request.py84693%306, 326, 333, 347, 353, 362
openhands-sdk/openhands/sdk/profiles
   agent_profile.py110794%332, 343, 346, 413, 419, 424, 452
   resolver.py257498%284, 333, 472, 661
openhands-sdk/openhands/sdk/settings
   model.py8146992%313, 331, 540, 557, 567–570, 573, 586, 590, 596, 606, 612, 617, 733, 736–737, 743–746, 751, 755, 770, 773–775, 823, 828–829, 834, 855, 867, 916, 925, 972, 1160, 1185, 1187, 1189, 1191, 1193, 1195, 1197, 1199, 1201, 1203, 1469, 1471, 1773, 1793, 1930, 2059, 2098, 2253–2255, 2257, 2343, 2353, 2355, 2360, 2378, 2391, 2393, 2395, 2397, 2404
TOTAL44297816182% 

@simonrosenberg
simonrosenberg marked this pull request as ready for review September 17, 2026 18:19
simonrosenberg and others added 3 commits September 17, 2026 14:31
…agent

Co-authored-by: openhands <openhands@all-hands.dev>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 97f40abbf1634e6239b09069b40844b42ccadca6
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/f993aa67-a43b-4257-a8c0-ae63d9370c21

This comment was posted by an AI agent (OpenHands).

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR unifies every conversation-start path (stored profile, inline profile, raw agent, deprecated agent_settings, materialize preview, and the Docker runtime) behind a single prepare_agent_launch function. I reviewed the resolver, the agent-server launch glue, the Docker mediation double-resolve, the request model changes, and the test suite.

No material bugs found. The design is clean and achieves what it set out to do: each profile field is now implemented once instead of once-per-launch-path.

What holds up under scrutiny

  • Exception hierarchy is correct. UnresolvedProfileReferences -> AgentLaunchError -> ValueError, and both routers catch AgentLaunchError before ValueError, so the structured 422 detail (code/dangling_llm_profile_ref/dangling_mcp_server_refs) is preserved rather than collapsed to a plain string. ProfileNotFound stays a separate Exception -> 404.
  • Docker double-resolve is sound. prepare_start runs with build_agent=False (and browser_available=False) so a dangling ref fails before any container is provisioned; finish_start re-resolves the same LaunchSource/catalog with the container's real browser_available from GET /server_info (usable_tools field -- verified correct). Skill discovery happens once in the catalog and is reused, not re-run. Container cleanup (registry.stop) is wired on every failure branch.
  • Secret scoping is enforced server-side in apply_launch (plan.allowed_secrets filters request.secrets), and agent_settings_launch_source correctly sets allowed_secrets=None (unrestricted) to match the legacy path. The additions-cannot-widen-scope invariant is asserted by a real test.
  • Backward compatibility is preserved. LaunchedAgentProfile.inline/llm_profile_ref and AgentLaunchAdditions.llm_profile_ref are additive fields with defaults; LaunchedAgentProfile has no extra="forbid", so old persisted conversations load. agent_settings is deprecated with deprecated_in=1.50.0 -> removed_in=1.55.0 (5 minor releases, meeting the policy) and is converted to an inline profile through the same pipeline rather than dropped.
  • Tests exercise real code paths, not mock wiring: test_launch.py covers runtime pieces, additions, provenance, dangling refs, and the deprecated agent_settings round-trip (verifying non-profile fields like critic_api_key, user_message_suffix, agent_context.secrets survive). The parity test diffs every launch path field-by-field.

Eval / benchmark risk -- flagging for a human maintainer

This PR changes agent launch behavior in ways that could plausibly move benchmark numbers, and there is no eval-monitor link or maintainer eval confirmation in the PR description or comments:

  • The agent_settings path now goes through the unified pipeline, so it gains forced stream=True, load_project_skills=True, browser injection (when the runtime has it and tools is null), and a fresh current_datetime instead of the saved timestamp. Canvas already sends these explicitly so it's unaffected, but a hand-rolled REST client relying on the old defaults would see a change.
  • ACP skill sourcing in Docker switched from the host's answer to the container's (openhands_managed), so an ACP profile in Docker now gets managed skills where it previously got none.

Per the repo's review policy I'm leaving a COMMENT rather than approving. Recommend a maintainer run lightweight evals (or confirm Canvas-only impact) before merging.

Minor note (non-blocking)

warn_deprecated(..., deprecated_in="1.50.0") is called from the agent-server while the current SDK version is 1.49.1. _should_warn compares current >= deprecated_in, so the runtime warning won't actually fire until the SDK ships as 1.50.0 -- which is presumably the release this PR targets, so this is consistent, just worth knowing the warning is effectively inert until then.

Risk assessment: MEDIUM -- no correctness/security issues, but behavior changes on the agent_settings/ACP-in-Docker paths that warrant eval confirmation.

Verdict: Worth merging after a maintainer confirms no eval regression.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing. See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Was this review helpful? React with thumbs up or thumbs down to give feedback.

@simonrosenberg

Copy link
Copy Markdown
Member Author

Thanks — on the eval-risk flag, here is what I can evidence from this branch so a maintainer has the facts to decide. I have not run evals.

Who actually sees the agent_settings behavior changes. Canvas's payload builder (buildConfiguredOpenHandsAgentSettings / buildAgentContext on OpenHands/OpenHands@main) already sends llm.stream = true, load_project_skills: true, load_user_skills: true and an explicit tools list from getAgentTools. So of the four changes, three are no-ops for canvas and the fourth is the fix itself: current_datetime is now computed at launch instead of being the value saved with settings. Verified live — the e2e script sent 2020-01-01T00:00 and the launched agent came back with the launch timestamp (.pr/launch_parity_e2e_output.txt).

Cloud is not on this path. The enterprise app server builds a concrete agent (create_kwargs = {'agent': agent, ...} in live_status_app_conversation_service.py) and never sets the request's agent_settings, so neither the pipeline change nor the removed validator conversion reaches it.

ACP-in-Docker. Previously the host's acp_skill_sourcing decided what a container's ACP agent got, so with the default host config (native) an ACP profile in Docker launched with no managed skills while the container image itself sets OH_ACP_SKILL_SOURCING=openhands_managed. That is the drift this PR removes by passing the container's answer. It does change the ACP prompt in Docker, and it is the one change I would point an eval at if you want one.

Unchanged for stored-profile launches (the path evals exercise): the parity e2e diffs llm, tools, MCP keys, skills, suffix, disabled_skills, load_project_skills, load_memory, condenser, critic, concurrency and switch-LLM between two identically-configured profiles, an inline draft, the legacy payload and the materialize preview — all MATCH.

On the warn_deprecated note: agreed and intentional — 1.50.0 is the next minor, so the warning arms exactly when the deprecation takes effect.

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.

[Agent Profile] The default profile and named profiles build different agents — collapse launch into one pipeline

2 participants