You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ship Agent Profiles: a library of named, reusable agent setups so a user can save everything about how an agent runs — which agent (built-in OpenHands/CodeAct vs. an ACP CLI: Claude Code / Codex / Gemini), the model + credentials, MCP servers, skills, and personality — then pick one to start a conversation. Easy UX is the product goal: Settings → Agents becomes a profile library; you launch with a profile and, where it's safe, switch the model live.
This epic tracks the implementation. It is local-first, cloud-ready: build on the SDK agent-server + agent-canvas first, with the cloud/SaaS surface following the same contract.
Status (2026-06-17)
Phase 0 ✅ · Phase 1 ✅ COMPLETE — #3715 (PR #3757), #3716 (PR #3775), #3717 (PR #3780) all merged; Skill secret-masking prerequisite #3774 merged. Phases 1–3 ✅ COMPLETE. SDK v1.29.0 released (#3787, 2026-06-18) — agent-server backend now has /api/agent-profiles. One gate remains before canvas: cut a ts-client release — #213 is on ts-client main but unreleased (latest v1.25.0), and canvas pins it by npm version (1.25.0). Once ts-client ships, canvas #3726 (Phase 4 centerpiece) unblocks → then #3727/#3728 → cloud #3730. Downstream of 1.29.0: watch for the OpenHands app-server + openhands-cli SDK bumps (needed for cloud #3730).
Canonical design
#3710 — "Agent Profiles: reference composition with profile-local secrets" is the design of record (it supersedes #3708 value-composition and the closed #3709), with author amendments folded in. Non-negotiable invariants every sub-issue must honor:
Two name-addressed profile types.LLMProfile (exists) · AgentProfile (new, kind-discriminated openhands | acp). OpenHands variant references one llm_profile_ref + 0+ server names via mcp_server_refs; ACP variant has acp_* config fields + 0+ server names via mcp_server_refs and nollm_profile_ref. The AgentProfile itself stores no secrets.
mcp_server_refs: list[str] | None — filter, not a separate store. Names servers in the user's existing global mcp_config.mcpServers (already a named dict). null = use all configured servers; non-null = filter to exactly those named keys. Checked at resolve-time; dangling name → hard error. SSE/shttp servers get a user-given name field in the canvas editor ([AgentProfile][canvas] AgentProfile library + kind-aware editor (dissolve ACP nav lockout) #3726) so they become referenceable.
Single FK lifecycle: llm_profile_ref only. Delete of a referenced LLM profile → 409 naming referrers; rename → cascade-update refs under lock; dangling ref → hard error. MCP server refs are names in the user's mutable global mcp_config — checked at resolve-time only.
Secrets stay out of the generic global panel.LLMProfile embeds its own secrets encrypted in-file. ACP provider creds are not stored on a profile — derived from acp_server via ACP_PROVIDERS registry and ride the single consolidated channel state.secret_registry ← request.secrets (Fix error in 422 logs due to blank bearer token #1022/Fix Laminar span stack warning in LocalConversation #1039). ACP profiles are secret-free / portable at rest.
resolve_agent_profile(profile, *, llm_store, mcp_config, cipher) → AgentSettingsConfig is the only join point. The existing create_agent / apply_agent_settings_diff / validate_agent_settings execution path is unchanged.
Creation-time only. No whole-profile mid-conversation switch. Runtime mutation stays narrow: OpenHands via switch_llm/switch_profile; ACP via session/set_model when supports_runtime_model_switch.
active_agent_profile_id is a NEW field — do not reuse/generalize active_profile (= active LLM profile).
Conservative migration. Seed exactly one default AgentProfile lazily (first GET /api/agent-profiles on empty store) — not one per existing LLM profile.
Release gate (next SDK action): cut 1.29.0 via the Prepare Release workflow. It freezes + publishes the surface and unblocks downstream consumption (OpenHands app-server bump, ts-client/canvas). ⚠️ The deprecation-deadline gate forces deleting the 7 expired removed_in=1.29.0 shims (acp_env on ACPAgentSettings/ACPAgent; _return_metrics on LLM.completion/acompletion/responses/aresponses; return_metrics on RouterLLM.completion) in the rel-1.29.0 PR (the version bump makes the removal due; doing it earlier trips the API-breakage gate).
All sub-issues are tracked here in software-agent-sdk for unified epic tracking; each title is tagged with its target repo ([sdk], [agent-server], [ts-client], [canvas], [cloud] → OpenHands). Phase 1 substrate (#3715, #3716) landed on main 2026-06-17.
Goal
Ship Agent Profiles: a library of named, reusable agent setups so a user can save everything about how an agent runs — which agent (built-in OpenHands/CodeAct vs. an ACP CLI: Claude Code / Codex / Gemini), the model + credentials, MCP servers, skills, and personality — then pick one to start a conversation. Easy UX is the product goal: Settings → Agents becomes a profile library; you launch with a profile and, where it's safe, switch the model live.
This epic tracks the implementation. It is local-first, cloud-ready: build on the SDK agent-server + agent-canvas first, with the cloud/SaaS surface following the same contract.
Status (2026-06-17)
Phase 0 ✅ · Phase 1 ✅ COMPLETE — #3715 (PR #3757), #3716 (PR #3775), #3717 (PR #3780) all merged; Skill secret-masking prerequisite #3774 merged. Phases 1–3 ✅ COMPLETE. SDK v1.29.0 released (#3787, 2026-06-18) — agent-server backend now has
/api/agent-profiles. One gate remains before canvas: cut a ts-client release — #213 is on ts-clientmainbut unreleased (latest v1.25.0), and canvas pins it by npm version (1.25.0). Once ts-client ships, canvas #3726 (Phase 4 centerpiece) unblocks → then #3727/#3728 → cloud #3730. Downstream of 1.29.0: watch for the OpenHands app-server + openhands-cli SDK bumps (needed for cloud #3730).Canonical design
#3710 — "Agent Profiles: reference composition with profile-local secrets" is the design of record (it supersedes #3708 value-composition and the closed #3709), with author amendments folded in. Non-negotiable invariants every sub-issue must honor:
LLMProfile(exists) ·AgentProfile(new, kind-discriminatedopenhands | acp). OpenHands variant references onellm_profile_ref+ 0+ server names viamcp_server_refs; ACP variant hasacp_*config fields + 0+ server names viamcp_server_refsand nollm_profile_ref. TheAgentProfileitself stores no secrets.mcp_server_refs: list[str] | None— filter, not a separate store. Names servers in the user's existing globalmcp_config.mcpServers(already a named dict).null= use all configured servers; non-null = filter to exactly those named keys. Checked at resolve-time; dangling name → hard error. SSE/shttp servers get a user-givennamefield in the canvas editor ([AgentProfile][canvas] AgentProfile library + kind-aware editor (dissolve ACP nav lockout) #3726) so they become referenceable.llm_profile_refonly. Delete of a referenced LLM profile →409naming referrers; rename → cascade-update refs under lock; dangling ref → hard error. MCP server refs are names in the user's mutable globalmcp_config— checked at resolve-time only.LLMProfileembeds its own secrets encrypted in-file. ACP provider creds are not stored on a profile — derived fromacp_serverviaACP_PROVIDERSregistry and ride the single consolidated channelstate.secret_registry←request.secrets(Fix error in 422 logs due to blank bearer token #1022/Fix Laminar span stack warning in LocalConversation #1039). ACP profiles are secret-free / portable at rest.resolve_agent_profile(profile, *, llm_store, mcp_config, cipher) → AgentSettingsConfigis the only join point. The existingcreate_agent/apply_agent_settings_diff/validate_agent_settingsexecution path is unchanged.switch_llm/switch_profile; ACP viasession/set_modelwhensupports_runtime_model_switch.active_agent_profile_idis a NEW field — do not reuse/generalizeactive_profile(= active LLM profile).GET /api/agent-profileson empty store) — not one per existing LLM profile.Phase 0 — Foundation (DONE ✅)
_merge_custom_mcp_configfix deferred to Phase 5 / [AgentProfile][cloud] OpenHands cloud/SaaS surface (umbrella — needs its own design pass) #3730)apply_agent_settings_diffeverywhere (SDKPersistedSettings.updatedone — PR feat(settings): delegate PersistedSettings.update to apply_agent_settings_diff #3712; OpenHands app-server + canvas portions deferred to Phase 5)Sub-issues
Built bottom-up.
←= depends on.Phase 1 — SDK substrate (
software-agent-sdk)[sdk]AgentProfile union (OpenHands | ACP) — reference-bearing, secret-free (PR [AgentProfile][sdk] AgentProfile kind-discriminated union #3757, merged; secret-free-at-rest enforced via Skill mask fix(skills): mask Skill.mcp_tools credentials at rest #3774)[sdk]AgentProfileStore + FK lifecycle (restrict-on-delete / cascade-rename forllm_profile_ref) (PR [AgentProfile][sdk] AgentProfileStore + FK lifecycle #3775, merged) ← [AgentProfile][sdk] AgentProfile kind-discriminated union (OpenHands | ACP) — reference-bearing, secret-free #3715[sdk]resolve_agent_profile()— filter-based MCP composition + resource-specific secret channels + dry-run (PR [AgentProfile][sdk] resolve_agent_profile() #3780, merged; shipsresolve_agent_profile+resolve_agent_profile_dry_run+AgentProfileDiagnostics) ← [AgentProfile][sdk] AgentProfileStore + FK lifecycle (restrict-on-delete, cascade-rename, referrer scan) #3716Phase 2 — SDK agent-server (
software-agent-sdk/openhands-agent-server)[agent-server]active_agent_profile_id+/api/agent-profilesrouter + migration seed (PR [AgentProfile][agent-server] active_agent_profile_id + /api/agent-profiles router + migration seed #3781, merged;POST /{id}/materializedeferred — now unblocked by [AgentProfile][sdk] resolve_agent_profile(): collision-checked composition + resource-specific secret channels #3717, tracked as a fast-follow) ← [AgentProfile][sdk] resolve_agent_profile(): collision-checked composition + resource-specific secret channels #3717, [AgentProfile][sdk] AgentProfileStore + FK lifecycle (restrict-on-delete, cascade-rename, referrer scan) #3716[agent-server]Acceptagent_profile_idat conversation start + stampLaunchedProfileprovenance (PR [AgentProfile][agent-server] agent_profile_id at conversation start + LaunchedProfile provenance #3784, merged) ← [AgentProfile][agent-server] active_agent_profile_id + /api/agent-profiles router + migration seed #3719, [AgentProfile][sdk] resolve_agent_profile(): collision-checked composition + resource-specific secret channels #3717materializeendpoint (PR [AgentProfile][agent-server] materialize endpoint (resolve dry-run) #3783, merged) —POST /api/agent-profiles/{name}/materialize→AgentProfileDiagnostics(dry-run; dangling refs reported in body, not 4xx)[agent-server]Cascademcp_server_refson MCP-server rename — closed, no code: no server-side rename signal exists (settings PATCH is a blob diff); resolve-timeDanglingMcpServerRefis the backstop, and cascade-on-rename (if wanted) belongs canvas-side ([AgentProfile][canvas] AgentProfile library + kind-aware editor (dissolve ACP nav lockout) #3726)Release gate (next SDK action): cut 1.29.0 via the Prepare Release workflow. It freezes + publishes the surface and unblocks downstream consumption (OpenHands app-server bump, ts-client/canvas).⚠️ The deprecation-deadline gate forces deleting the 7 expired
removed_in=1.29.0shims (acp_envon ACPAgentSettings/ACPAgent;_return_metricsonLLM.completion/acompletion/responses/aresponses;return_metricsonRouterLLM.completion) in the rel-1.29.0 PR (the version bump makes the removal due; doing it earlier trips the API-breakage gate).Phase 3 — typescript-client (
typescript-client)[ts-client]AgentProfile types + client + resolve/materialize types + purederiveSwitchPlan(PR Proposal: Spec-only Tools + Explicit RunLoop (Local/Remote) with Remote Execution #213, merged — awaiting a ts-client release before canvas can consume) ← [AgentProfile][agent-server] active_agent_profile_id + /api/agent-profiles router + migration seed #3719, [AgentProfile][agent-server] Accept agent_profile_id at conversation start + stamp LaunchedProfile provenance #3720Phase 4 — agent-canvas UX (
agent-canvas)[canvas]AgentProfile library + kind-aware editor (dissolves the ACP nav lockout) + SSE/shttp server naming fix ← [AgentProfile][ts-client] AgentProfile types + client + resolve/materialize types + deriveSwitchPlan #3722, [AgentProfile][agent-server] active_agent_profile_id + /api/agent-profiles router + migration seed #3719[canvas]Unified capability-gated chat-input model/profile picker ← [AgentProfile][canvas] AgentProfile library + kind-aware editor (dissolve ACP nav lockout) #3726, [AgentProfile][ts-client] AgentProfile types + client + resolve/materialize types + deriveSwitchPlan #3722[canvas]Provider-driven ACP Authentication section ← [AgentProfile][canvas] AgentProfile library + kind-aware editor (dissolve ACP nav lockout) #3726, [AgentProfile][ts-client] AgentProfile types + client + resolve/materialize types + deriveSwitchPlan #3722Phase 5 — OpenHands cloud / SaaS (
OpenHands) — later milestone, own design pass needed[cloud]OpenHands cloud/SaaS surface (umbrella) — includes: OpenHands_merge_custom_mcp_configACP fix (Foundation: codify ACP MCP forwarding across SDK and integrations #3705 remainder) · app-serverapply_agent_settings_diffadoption (Foundation: adopt apply_agent_settings_diff in all settings stores #3706 remainder) · AgentProfiles container+column+router+FK ·resolve_agent_profilewiring ·LaunchedProfileprovenance. ← local-first milestoneCritical path:
#3715…#3720→#3722 (ts-client)→ merge rel-1.29.0 #3787 + cut ts-client release → #3726 (canvas) → #3727/#3728 → #3730 (cloud)Repos
All sub-issues are tracked here in
software-agent-sdkfor unified epic tracking; each title is tagged with its target repo ([sdk],[agent-server],[ts-client],[canvas],[cloud]→ OpenHands). Phase 1 substrate (#3715, #3716) landed onmain2026-06-17.