Product decision · Agent Canvas composer

One LLM picker. Three layers of state.

For OpenHands, the conversation composer should stay an LLM Profile picker before conversation start and during the conversation. Agent Profiles remain the agent configuration behind the scene and in Settings. Selecting an LLM Profile should always make it the LLM for the conversation currently shown in Canvas and save it as the user’s preference for future conversations. A truly single-user installation may additionally mirror that choice into only its hidden well-known default Agent Profile.

Current source snapshots: OpenHands/agent-canvas @ bc77e113 · OpenHands/OpenHands @ 1747b395 · decision record, June 2026

1Recommendation in one screen

Choose Refined Option C—with Option 1B as the canonical model. OpenHands always shows an LLM Profile picker. Selecting an LLM Profile always makes it the LLM for the conversation currently shown in Canvas and saves it as the user’s preference for future conversations. Explicitly single-user backends may also update only the hidden well-known default Agent Profile. Named Agent Profiles never change from a composer click. ACP keeps its provider model picker.
Same action invariant. Selecting Claude always means “use Claude for this conversation and remember Claude for future conversations.” If the conversation has not started, Canvas uses Claude when the first prompt starts it. If it is running, Canvas applies Claude immediately. Those are two execution paths for one behavior—not two picker meanings.
Visible controlLLM

Stable before conversation start and during the conversation.

Agent configurationprofile

Selected behind the scene; edited in Settings → Agent.

Future choiceuser

Preferred LLM belongs to a person, not a shared org profile.

Local refinementdefault

Only the hidden default may mirror the choice on single-user servers.

Constraints behind the recommendation: the current conversation-start request sends agent_profile_id and resolves its saved LLM reference agent-server-conversation-service.api.ts:368–410; current Cloud Agent Profiles are organization-owned while activation writes only the calling member’s pointer agent_profiles.py:1–24 and :386–427.

Explicit precedence

Current composer selection
The LLM the person just selected before conversation start or during the conversation.
Per-user preferred LLM
The last interactive choice saved for future Canvas conversations.
Active Agent Profile fallback
The profile’s stable llm_profile_ref, used when no user preference applies.
System default
The final seed/fallback when neither user nor Agent Profile provides a resolvable LLM.

The existing Cloud resolver already demonstrates the lower-level fallback pattern for Agent Profiles: explicit conversation-start profile → member active profile → org default → composed settings saas_settings_store.py:190–235. The proposed LLM preference layer would sit above the resolved Agent Profile’s LLM when interactive Canvas conversations start.

2Current UX: the control changes identity

Today the picker is selected by conversation phase. Before conversation start—or while a blank conversation has not substantively started—Canvas prefers Agent Profiles. During an OpenHands conversation, it uses LLM Profiles. ACP uses the model picker.

Verified in the pure picker matrix: !hasConversation || !hasStartedConversation returns agent-profile when profiles exist; a started conversation returns model for ACP and llm-profile otherwise resolve-picker-kind.ts:27–46. The renderer maps those three values to three controls chat-input-actions.tsx:411–468.

Current composer picker compared with proposed stable picker The top lane shows Agent Profile before conversation start changing to LLM Profile during the conversation. The bottom lane shows the proposed design with an LLM Profile in both phases while the Agent Profile remains a hidden conversation-start input. CURRENT — VISIBLE CONCEPT CHANGES blank composerAgent Profile · Default send Resolve profilellm_profile_ref = GPTstart conversation running conversationLLM Profile · GPT PROPOSED — VISIBLE CONCEPT STAYS PUT blank composerLLM Profile · Claude send Compose startAgent Profile · Default+ LLM override · ClaudeAgent Profile stays in Settings running conversationLLM Profile · Claude
The UX delta is deliberately small: keep the visible LLM choice stable; compose it with the Agent Profile when the conversation starts.

Current conversation-start contract: Canvas sends only agent_profile_id on Cloud agent-server-conversation-service.api.ts:378–410. Current Cloud resolution replaces composed settings with the Agent Profile’s resolved settings at conversation start saas_settings_store.py:470–500. The LLM conversation-start override shown below is a proposal, not current behavior.

3Three concepts, three owners

Agent configuration

Agent Profile

A reusable agent definition: kind, tools/skills/MCP choices, limits, and an OpenHands llm_profile_ref fallback. Named profiles should be stable because other conversation starts and automations can refer to them.

Current Cloud summaries expose agent_kind, revision, llm_profile_ref, and MCP refs agent_profiles.py:75–99.

Runtime state

Conversation LLM Profile

The profile used by one running conversation. Switching it should remain immediate and conversation-scoped.

Current Canvas passes the conversation id into switchProfile, updates conversation queries/metadata, and does not save an Agent Profile use-switch-llm-profile.ts:51–103.

Separate agent family

ACP model

ACP does not use OpenHands LLM Profiles. Its provider/session model selector remains the composer control in ACP contexts.

Current picker resolution returns model for a started ACP conversation resolve-picker-kind.ts:41–46; ACP context is derived from the active Agent Profile or conversation kind use-acp-model-context.ts:34–59.

4Four options side by side

Option 1 · inconsistent semantics — reject

Conversation-start override, conversation only

Behavior. The composer always shows LLM Profiles. The selected LLM overrides the Agent Profile for that conversation start; later selections switch only the running conversation. The next blank composer falls back to the Agent Profile again.

Changes. Add an optional LLM profile name to the start contract; resolve the Agent Profile, replace only its LLM in memory, never save it.

Good. Stable named profiles, no hidden writes, safe for shared Cloud profiles and automations.

Cost. It gives the same picker two persistence meanings before and during a conversation. That is the inconsistency this design should remove.

Option 2 · not recommended generally

Mutate the active Agent Profile

Behavior. Every composer LLM selection rewrites the active Agent Profile’s llm_profile_ref; future conversation starts naturally inherit it.

Changes. Prefer a narrow revision-aware patch. A whole-profile save must fetch and preserve every field, then coordinate profile persistence with the live runtime switch.

Good. One saved object appears to contain the effective configuration.

Cost. A chat control silently edits Settings. Named/shared profiles drift, revisions churn, automations can change behavior, and runtime-switch/profile-save partial failures become visible.

Current constraints: in-conversation OpenHands switching is conversation-scoped use-chat-input-llm-profile-state.ts:11–24 and :75–90; Cloud Agent Profile activation is pointer-only, not a settings write agent_profiles.py:386–427; Cloud resolves profile references only at conversation start saas_settings_store.py:473–500.

CriterionOption 1Option 1BOption 2Refined C
Stable visible pickerYesYesYesYes
Last-used starts next chatNoYesYesYes
Named Agent Profiles stay stableYesYesNoYes
Safe for shared Cloud profilesYesYesNo without private/copy-on-write profilesYes
One persistence modelYes, no preferenceYesYes, but destructiveNo: optional local compatibility mirror
Long-term migration pathWeak UXcleanRequires undoing shared mutationclean local mirror can disappear later

5Single-user vs multi-user: branch on ownership, not packaging

Deployment shapeDeclared scopeFuture LLM persistenceMay mirror hidden default?
Standalone SDK embedded in a personal appinstallation only if the embedding app declares itApp-owned preference or direct agent configuration.Yes, if it also adopts the well-known default Agent Profile convention.
Canvas + local agent-serverinstallationInstallation/user preference; same user in current product shape.yes only for seeded hidden default.
Canvas + all-in-one DockerServer must declare installation or multi-userFollow declared scope.Only when the server explicitly declares single-user ownership.
Canvas + remote VM agent-serverServer must declare scope; hostname is irrelevant.Follow declared scope.Only when explicitly single-user.
OpenHands Cloud / Canvas Cloud backendorganization profiles + member preferencePer-member preferred LLM + conversation-start override.no shared profile state stays stable.
Automation or API conversation start by Agent Profile idCaller-selected reusable profileIgnore interactive preference unless the API explicitly supplies an LLM override.No implicit mutation from an interactive user’s preference.

Current Canvas exposes only the coarse transport kinds local | cloud backend-registry/types.ts:1–13. Current local Agent Profiles lazily seed a well-known default profile and active pointer agent_profiles_router.py:219–259. Current Cloud stores the collection with the organization and activation with the member agent_profiles.py:1–24. The proposed capability should describe ownership explicitly, for example profile_scope: "installation" | "organization".

Why “Docker” is not a capability. A container can serve one human or an office. A VM can be personal or shared. localhost can front a multi-user reverse proxy. The mutation rule must follow declared ownership semantics, not deployment folklore.

6Target state ownership and mutation rules

StateOwnerWritten byConsumed byMust not be changed by
LLM Profile definitionsInstallation or organizationLLM Profile SettingsConversation-start resolver and in-conversation switchA normal composer selection
Agent Profile definitionInstallation or organizationSettings → Agent; explicit profile APIsInteractive, automation, and API conversation startsComposer selection—except optional hidden-default mirror on declared single-user servers
Active Agent Profile pointerInstallation or memberSettings → Agent activationConversation-start fallbackLLM picker
Preferred LLM Profile proposedUser/member; installation when no identity existsSuccessful interactive picker selectionBlank composer + interactive conversation-start overrideAutomation or another teammate
Conversation LLM ProfileConversationConversation-start resolution or live switchRunning runtime and visible pickerAnother conversation
ACP modelACP Agent Profile fallback + ACP sessionACP settings/model pickerACP subprocessOpenHands LLM Profile picker

Current state boundaries support this separation: Cloud Agent Profile activation writes a member pointer only agent_profiles.py:386–427; an explicit start request may already override the Agent Profile id without changing that pointer app_conversation_models.py:221–238; running OpenHands LLM switching targets the current conversation agent-server-conversation-service.api.ts:781–829.

Recommended implementation boundary

1
Canvas owns one intent: selectComposerLlmProfile(name).The component never decides Cloud vs local persistence.
2
Switch the running conversation when one exists.Keep the current runtime endpoint and conversation metadata behavior.

use-switch-llm-profile.ts:55–103

3
Persist the future interactive choice according to advertised scope.Canonical path: preferred LLM. Optional compatibility path: narrow, revision-aware patch of only the hidden local default Agent Profile.
4
Pass the selected LLM whenever an interactive conversation starts.Resolve Agent Profile normally, then replace only its LLM in memory. Preserve profile id/revision provenance.

Current start already carries one-off Agent Profile provenance through resolution live_status_app_conversation_service.py:530–548; the LLM override is the proposed adjacent contract.

5
Serialize selection and Send.Disable or queue Send while future-state persistence is pending so a quick choose-then-send cannot start with the old LLM. Surface split failures instead of pretending runtime and preference are identical.
Migration posture. Build Option 1B first as the durable model. Add local default-profile mirroring behind the explicit installation-scope capability. If local backends gain real identities later, remove the mirror without changing the composer UX or conversation-start contract.

7Unresolved decisions before implementation

DecisionRecommended defaultWhy it remains explicit
Capability name and sourceServer-advertised ownership, such as profile_scope.Current backend.kind describes transport, not who owns mutable state.
Preferred LLM storageBackend-owned per-member field on Cloud; persisted app preference on identity-less installations.It must roam correctly for Cloud users without becoming organization state.
Explicit named Agent Profile conversation startInteractive Canvas preference overrides only the LLM; automation/API profile starts ignore it unless an override is explicitly supplied.Keeps named profiles deterministic outside interactive UX.
Deleted or renamed preferred LLMClear stale preference, fall back to Agent Profile, and make the fallback visible.A silent stale name would make the picker lie again.
Switch succeeds, preference save failsKeep the runtime switch; report that the next-conversation preference did not persist and allow retry.Rolling back a live model switch is more disruptive than acknowledging split state.
Local mirror lifetimeCompatibility optimization, not a second canonical architecture.Prevents single-user convenience from hardening into a multi-user mutation rule.
Settings copyLabel Agent Profile’s field “Default LLM Profile” or “Fallback LLM Profile.”The per-user interactive preference has higher precedence.

The need for explicit stale-reference and fallback handling is grounded in current Cloud resolution, which falls back rather than blocking conversation start when an active profile id is missing saas_settings_store.py:213–239. The rest of this section records product decisions, not existing behavior.

Decision summary. Preserve the user’s visible mental model—“I choose an LLM here”—while keeping the system’s richer agent configuration stable and reusable. One UI, explicit ownership underneath.
Prepared by OpenHands (AI) on behalf of Engel Nyst. Verified behavior is source-linked; recommendations and unresolved choices are explicitly labeled.