← Home

Agent Canvas LLM Profiles design note

Notes for OpenHands/agent-canvas#533. Prepared by OpenHands (AI) on behalf of the user, June 2026.

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.

Agent Canvas LLM profiles agent-server Cloud settings /model

Current shape

Local agent-server

Uses saved profiles through the SDK ProfilesClient. The settings page, active default, /model, and in-chat switch affordance all speak profile language.

Remote agent-server

Same as local. In Agent Canvas, a remote agent-server is still a kind: "local" backend because it speaks the same agent-server protocol.

OpenHands Cloud

Still exposes raw LLM settings from /api/v1/settings. That forces Cloud to be “LLM Settings” while other backends are “LLM Profiles”.

Tech debt

Two user-facing concepts mean backend details leak into routes, navigation copy, chat labels, /model, and switching behavior.

Target boundary: profiles everywhere

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.

Code shape today

AreaCurrent design
Profile lifecycleProfilesService wraps SDK ProfilesClient calls for list/get/save/activate on agent-server backends.
Conversation startAgent-server start fetches encrypted settings, then builds the runtime payload from the active profile's materialized config.
Cloud settingsCloud response stays mostly flat and drives a raw settings form. This is the shape that should move behind a profile abstraction.
SwitchingAgent-server profile switch fetches encrypted profile config and calls ConversationClient.switchLLM. Cloud needs equivalent profile semantics.
UI copyLocal says “LLM Profiles”; Cloud still says “LLM Settings”. This visible split is the smell.

What I would clean up next

  1. Make profiles the UI contract for every backend: settings page, picker, /model, active default, and in-conversation switch.
  2. Choose the Cloud profile storage owner. App/backend-owned storage is safer than browser localStorage for anything secret-bearing.
  3. Define profile switch semantics once: local has “activate for future conversations” on home and “switch current conversation” in chat; Cloud should match that model when possible.
  4. Keep raw LLM config out of product-level code. It should appear only where a selected profile is materialized for a specific backend runtime.
← Back to studies