OpenHands / OpenHands/software-agent-sdk
[Agent Profile] The profile model isn't self-describing — clients hardcode version numbers to guess which fields a server accepts
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
The problem
AgentSettingsConfig is self-describing. export_agent_settings_schema() walks both variants and GET /api/settings/agent-schema serves the result, so a client can render a settings form off the server's own description — prominence tiers, depends_on, choices, coercion, defaults.
AgentProfile is not. agent_profiles_router has list / get / create / delete / rename / activate / materialize, and nothing that describes the model.
The mechanism is already generic — export_settings_schema(model) accepts any Pydantic model — it simply returns nothing for a profile, because no profile field carries a SETTINGS_METADATA annotation:
export_settings_schema(OpenHandsAgentProfile) -> sections: 0 fields: 0
export_settings_schema(ACPAgentProfile) -> sections: 0 fields: 0
OpenHandsAgentProfile.model_fields -> 16
What clients do instead
They hardcode a version number per field. AgentProfileBase is extra="forbid", so posting a field an older server doesn't know 422s and loses the entire save — the gate can't be optimistic, and every new profile field needs one.
Canvas's src/api/agent-profiles-service/profile-field-support.ts today:
| field | gate | where the number comes from |
|---|---|---|
enable_switch_llm_tool |
1.31.0 |
sdk#3870 |
tools |
1.31.2 |
sdk#3968 |
secret_refs |
1.47.0 |
sdk#4931 — unreleased; this is a forward guess |
mcp_server_refs |
none | on AgentProfileBase since 1.29.0, below the minimum supported server |
The secret_refs row is the sharp end. OpenHands/OpenHands#17237 ships a constant naming a release that does not exist yet, to be corrected by hand once sdk#4931 actually releases. If it lands on 1.48.0 instead, the control silently disappears for a release and nothing fails loudly.
Why the existing settings schema can't answer this
Worth stating, because it's the obvious first idea and it's wrong:
tools is in the agent settings schema today (general section, array, MAJOR) — and has been since long before it reached OpenHandsAgentProfile in 1.31.2. A 1.30 server advertises tools in the schema and 422s a profile that sets it. The settings schema describes AgentSettingsConfig; the profile is a separate model with its own history. Schema presence there is necessary, not sufficient.
And for most of the fields there is nothing to consult at all: llm_profile_ref, mcp_server_refs, secret_refs and disabled_skills have no settings analogue, and system_message_suffix lives on AgentContext, whose schema section emits only load_memory.
Proposal
export_agent_profile_schema() beside export_agent_settings_schema(), served at GET /api/agent-profiles/schema, merging the two variants with the same variant tagging. Annotating the 16 fields with SettingsFieldMetadata is most of the work; the export and transport already exist.
A client's question then becomes "is secret_refs in the schema?" — a fact from the server — instead of "is this server newer than a number I guessed."
Deliberately not in scope
Rendering. Generic rendering of an array field is a JSON textarea, which can't express the profile editor's tri-state (null = server default vs [] vs explicit list) or enumerate pickable values. The tool/MCP/secret pickers should stay purpose-built — exactly the arrangement SPECIALLY_RENDERED_KEYS already encodes for llm.model / llm.api_key. This is for capability detection, not for auto-generating the editor.
What tools an agent will actually get — that's #4958, a question about values at resolve time. This one is about which fields the model accepts. Neither subsumes the other.
Cloud. A schema endpoint answers "will this save be accepted", not "will the restriction hold". Canvas excludes cloud from agentProfileSupportsSecretRefs for the second reason — cloud resolves profiles itself and sends a resolved agent, so the agent-server's filtering branch never runs — and that short-circuit stays until OpenHands/enterprise#364 ships.
Payoff
Deletes the version table, removes the forward guess, and makes every future profile field self-announcing instead of requiring a coordinated constant in each client.
Related: #4953, #4956, #4958.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AgentProfileBase, OpenHandsAgentProfile, ACPAgentProfile, agent_profiles_router, and export_settings_schema. Trace the existing settings-schema export and profile routes, then identify how the 16 profile fields and both variants are represented. Done means GET /api/agent-profiles/schema exposes the profile fields with variant tagging and clients can detect accepted fields without version gates.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, backend-api-design
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100