Short version: the target architecture is LLM Profiles everywhere. Local and remote agent-server backends already expose profiles; OpenHands Cloud still exposes raw LLM settings, and that second shape is the tech debt.
Uses saved profiles through the SDK ProfilesClient. The settings page, active default, /model, and in-chat switch affordance all speak profile language.
Same as local. In Agent Canvas, a remote agent-server is still a kind: "local" backend because it speaks the same agent-server protocol.
Still exposes raw LLM settings from /api/v1/settings. That forces Cloud to be “LLM Settings” while other backends are “LLM Profiles”.
Two user-facing concepts mean backend details leak into routes, navigation copy, chat labels, /model, and switching behavior.
The clean boundary is the LLM Profile, not the raw runtime config. Users should choose, edit, activate, and switch named profiles no matter which backend is active. Backend-specific storage and secret handling can differ behind that profile contract.
llm_profile: {
name: "fast-review",
model: "openhands/minimax-m2.7",
credentials: "server-owned secret reference",
base_url: "optional provider endpoint"
}
Raw agent_settings.llm or Cloud LLM payloads should be materialization details: the selected profile gets flattened only at the boundary where a specific backend starts or switches a runtime conversation.
| Area | Current design |
|---|---|
| Profile lifecycle | ProfilesService wraps SDK ProfilesClient calls for list/get/save/activate on agent-server backends. |
| Conversation start | Agent-server start fetches encrypted settings, then builds the runtime payload from the active profile's materialized config. |
| Cloud settings | Cloud response stays mostly flat and drives a raw settings form. This is the shape that should move behind a profile abstraction. |
| Switching | Agent-server profile switch fetches encrypted profile config and calls ConversationClient.switchLLM. Cloud needs equivalent profile semantics. |
| UI copy | Local says “LLM Profiles”; Cloud still says “LLM Settings”. This visible split is the smell. |
/model, active default, and in-conversation switch.