Conversation
Agent profiles were accepted and advertised only when the service runs against a local Agent Server. In cloud mode a profile on an automation was rejected with 422 and `agentProfiles` was missing from the capabilities, so hosts refused every template that selects one, although the id is already exported to the run in both modes and the OpenHands app server can start a conversation with a profile. - Advertise `agentProfiles` on every ready deployment. - Validate a profile on create and update through validate_agent_profile. Local mode still passes the id to the Agent Server. Cloud mode checks it against the caller's organization (GET /api/agent-profiles with the caller's own credential), because the app server falls back to default settings for an id it does not know instead of failing. - Build the upstream auth headers in one place so the check and authentication forward the same credential. Git sync imports keep requiring a local Agent Server for profiles: the loop has no user credential to check an id with. Refs OHE-3160
|
Warning Your comment is too long (maximum is 65536 characters), so the coverage report was not added. See the job log for how to reduce it. |
Pin the images and the extensions ref under test for OHE-3160: - agent-canvas: sha-9f60167 (OpenHands/OpenHands#17830) - automation: sha-bc3b170 (OpenHands/automation#541) - EXTENSIONS_REF: hieptl/ohe-3160 (OpenHands/extensions#712), so sandboxes load the skills from that branch Not for merge.
Preflight answered every draft that selects an agent profile with `agent_profile_id: Extra inputs are not permitted`: it normalizes the body through the saved-draft shape, which does not carry the field, before the creation model sees it. A setup form that offers a profile could not get past validation. The profile now skips the draft shape and is validated the way creation validates it, including the organization check in cloud mode. An upstream failure during that check is returned as an error instead of being reported as an invalid profile.
Picks up the draft preflight fix added to OpenHands/automation#541. Not for merge.
Merge order (OHE-3160)This PR is one of four that together bring the automation templates to OpenHands Cloud and Enterprise. Please merge them in this order:
Required
Recommended
After merging
|
Follow-ups to the cloud profile support in this branch: - The check asked the OpenHands API from the middle of the create and update handlers, with a database transaction already open. It now runs before the first query, so a slow app server no longer pins a pooled connection. - A 200 response that is not the expected shape was an unhandled error. It is now reported as a bad gateway, like every other upstream fault, and a 401 or 403 from the app server is passed on as such. - Preflight let an upstream failure discard the verdicts it had already reached. The profile is now checked last and left unjudged when the app server cannot be asked; creation still makes the check. - The model-with-profile rule lived in two validators. It is one function now, and the upstream lookup is its own, so each caller names what it needs. Git sync keeps refusing profiles in cloud mode, where it has no caller to check them against, and says so accurately. - `agentProfiles` is no longer listed among the features that depend on nothing but packaged code.
Creation, update and preflight against a stubbed OpenHands API: a profile of the caller's organization is accepted, an unknown one and a model alongside a profile are refused, an upstream failure is a bad gateway on creation and leaves preflight's other verdicts intact, and the capability is offered without an Agent Server.
Pre-commit runs pyright over the tests as well, and it rejected the fixture's return annotation.
Second review pass on the cloud profile support in this branch: - The lookup was scoped by the request's headers. A cookie session without an X-Org-Id could be validated against the organization the session had moved to while the automation was stored under the cached one. It now names the authenticated user's organization. - Moving the check ahead of every query made a repeat enable of a template depend on the OpenHands API, and made an update look a profile up before it knew the automation existed or that the profile had changed. Create now checks after the template lookup; update checks only a profile that differs from the current one, so sending the current one back cannot fail on a deleted profile or an unreachable app server. - Preflight asks before it touches the database and reports last, so it neither holds a connection across the call nor loses its other verdicts. - One list entry without an id failed the lookup for the whole organization. Entries without one are skipped again. - Profiles an organization was just seen to have are remembered for a minute. Only a hit is trusted, so a new profile is still found at once. - An unexpected upstream answer is logged with its status.
The stub now answers only the lookup the service should make, and the tests cover the organization it is made in, answers that are not the expected shape, a credential the app server refuses, updates that select or keep a profile, and a selection that is validated repeatedly. The capability test forces cloud mode instead of relying on the environment.
all-hands-bot
left a comment
There was a problem hiding this comment.
This review was posted by an AI agent (OpenHands).
Scope. This change belongs in OpenHands/automation: it extends the automation service's own capability advertisement, request validation, and preflight contract. It does not move agent/tool behavior, conversation handling, or Canvas UI into this repository, and the profile id continues to be passed through to the run rather than resolved here.
What I verified on d53b6b28c4dc1c1fb697f8e79490fa25ed53d188
- Current PR state re-read: head still matches, non-draft, open, label
type: feat. No existing reviews or review threads. CI check runs on this exact head are green (unit-tests,backend,Build and Push Automation Image, PR title/description checks). uv run ruff check openhands/automationclean;uv run pyrighton the four changed modules clean. Pure-logic paths of the new validator exercised directly (combination rejection, None-profile short-circuits). The DB-backed suite could not run in this sandbox (no Docker for the Postgres fixture); CI'sunit-testsjob covers it on this head.- Cloud existence check uses the caller's own credential plus
X-Org-Idset touser.org_id, i.e. the organization the automation is stored under, not a re-derived session org. The shared_upstream_headershelper keeps authentication and the new lookup consistent. - Create checks the profile after the template lookup (a repeat enable stays one query and does not depend on the app server); update re-checks only a profile that differs from the current one; preflight strips
agent_profile_idfrom the saved-draft shape and re-adds it for the creation model, then reports the existence verdict last so an upstream fault leaves the profile unjudged instead of discarding other verdicts. All three are consistent with the models creation actually uses. - Upstream responses are handled explicitly: 401/403 passed through, other non-200 and unexpected-shape 200 answered as 502, unknown id as 422. Local mode is unchanged.
Notes (acknowledged in the PR, not blocking). A profile deleted after creation still falls back at run time on the app server; git-sync import continues to refuse a profile in cloud mode for lack of a caller credential; and a cache-missing lookup inside an open transaction holds that connection for the upstream call. These are documented trade-offs with a clear owner, and none is a correctness, security, or compatibility defect demonstrated on this head.
No material findings. ✅ APPROVED
Why
Agent profiles were accepted and advertised only when the service runs against a local Agent Server:
validate_agent_profile_selectionrejected anyagent_profile_idin cloud mode with422 Agent profiles require a configured Agent Server.GET /v1/capabilitieslistedagentProfilesonly in local mode.POST /v1/validateanswered every draft that selects a profile withagent_profile_id: Extra inputs are not permitted, in both modes. Since feat: add automation draft lifecycle, endpoints, and synthetic event payloads #439 it normalizes the body through the saved-draft shape, which has no such field, before the creation model sees it. Agent Canvas runs this preflight before it lets a setup form continue, so a form with a profile selected could not get past validation.On OpenHands Cloud and Enterprise that makes Agent Canvas refuse every automation template that selects a profile (GitHub code review, GitHub issue to PR, GitHub issue triage) with "Not available: agentProfiles". Nothing else needs a local Agent Server: the dispatcher already exports
AUTOMATION_AGENT_PROFILE_IDin both modes, and the OpenHands app server starts a conversation with a profile throughPOST /api/v1/app-conversations(agent_profile_id).Summary
agentProfilesis advertised on every ready deployment. In cloud mode it relies on the app server serving/api/agent-profiles, which the enterprise server does from 1.59.0.validate_agent_profile_combination: a profile owns its model, so the two cannot be selected together. The same in both modes.ensure_agent_profile_exists: in cloud mode, checks the id against the caller's organization withGET {openhands_api_base_url}/api/agent-profiles, using the caller's own credential andX-Org-Idset to the authenticated user's organization, the one the automation is stored under. The app server falls back to default settings for an id it does not know instead of failing, so an unchecked id would silently run with the wrong agent. Local mode is unchanged: the Agent Server resolves the id.422 Agent profile <id> not found. A 401 or 403 from the app server is passed on; any other upstream fault, including a 200 that is not the expected shape, returns 502.POST /v1/validateacceptsagent_profile_idon the raw/v1endpoint again. The saved-draft shape is unchanged: the profile skips it and is validated by the model creation uses. The existence check is made before preflight touches the database and reported last, and an upstream failure leaves the profile unjudged instead of discarding the other verdicts; creation still makes the check. The preset endpoints still reject the field, as their creation endpoints do.upstream_auth_headers), shared by authentication and the new check.Video
demo.mov
How to Test
uv run ruff check openhands/automation,uv run pyrighton the changed files: clean.uv run python -m pytest tests/test_capabilities_router.py tests/test_auth.py tests/test_router.py tests/test_git_sync.py tests/test_dispatcher.py tests/test_git_sync_serializer.py: 356 passed, 1 failed. The failure istest_create_automation_shares_template_identity_with_presets(botocore ... EndpointConnectionError), which fails the same way onmainon the machine I used because it has no S3 endpoint.tests/test_capabilities_router.py tests/test_router.py tests/test_draft_router.py tests/test_draft_execution_regressions.py tests/test_auth.py: 236 passed, 2 failed, both with the sameEndpointConnectionError(the second istest_incomplete_draft_dispatch_returns_validation_errors).httpx.MockTransport) in cloud configuration:POST /v1/validatein cloud configuration, through the app with the same stubbed OpenHands API:Before the fix the first line answered
agent_profile_id / extra_forbidden.Deployed to the Replicated fleet VM
shared-4(clean install) with the OHE test procedure, together with the Canvas, extensions and chart changes:hieple/deploy-2026-10-01(ac80547) ismainplus:AUTOMATION_KV_SECRET);automationpinned tosha-03687e7(feat: support agent profiles in cloud mode #541; image revision label 03687e7);agent-canvaspinned tosha-9f60167(feat(automations): show templates on cloud backends and offer native git integrations OpenHands#17830; image revision label 9f601672d942), which ships the extensions npm package at feat(automations): run the automation templates on OpenHands Cloud and Enterprise extensions#712's head (bf7e3212);EXTENSIONS_REF: hieptl/ohe-3160underglobal.agentServerEnv, so sandboxes load the skills from the extensions branch.hieple/deploy-2026-10-01. The fleet run succeeded: https://github.com/OpenHands/infra/actions/runs/36819742198https://app.shared-4.replicated.all-hands-testing.devwithout signing in:/server_info:app_version1.67.0,sdk_version1.49.6./api/automation/healthand/api/automation/ready: 200./api/automation/openapi.jsonreports version 1.16.0; the chart's default image is 1.15.1, so the pinned build is the one running./canvas/bundle (278 assets) contains the script bundles of the extensions branch (CloudConversations,_deliver_cloud, the$AUTH_HEADERskill text) and the Canvas changes (mcp-native-panel, thecloud-providers-configuredquery).sha-03687e7(the first two commits of this PR):GET /api/automation/v1/capabilitieslistedagentProfiles,kvStoreandwebhookDelivery.uv run python -m pytest tests: 1862 passed, 7 skipped, with the two tests that need an S3 endpoint deselected on the machine I used.Notes
TestAgentProfileInCloudModeintests/test_router.pyand five tests intests/test_capabilities_router.pyrun creation, update, preflight and capabilities in cloud mode against a stubbed OpenHands API (agent_profiles_apifixture) that answers only the lookup the service should make. They cover the organization the lookup is made in, answers that are not the expected shape, a refused credential, updates that select or keep a profile, and a selection validated repeatedly./v1/drafts) still cannot carry a profile; only preflight and direct creation accept one.validate_agent_profile_selectionkeeps that rule. A profile-backed automation created through the API in cloud mode is therefore exported to git but cannot be edited from it.