Skip to content

feat(agent-profiles): give a profile its own persona or extra instructions - #17843

Merged
simonrosenberg merged 65 commits into
mainfrom
feat-profile-system-prompt
Oct 5, 2026
Merged

simonrosenberg merged 65 commits into
mainfrom
feat-profile-system-prompt

Conversation

@simonrosenberg

@simonrosenberg simonrosenberg commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

HUMAN:

Adds one System prompt section to the Agent Profile editor: keep the OpenHands default, add your own instructions, or give the agent a custom persona. The persona replaces only who the agent is and how it codes, so security, risk-assessment, memory and tool guidance stay. I had the agent run the full local Canvas stack on software-agent-sdk#5449 and launch real conversations in each mode. The System Message shows the persona followed by the kept guidance (screenshots below). npm run build passes.


AGENT:

Builds on #17516 (merged). That PR pinned agent-server 1.53.0 and raised minimumAgentServer to 1.51.0, the release that shipped profile persona (software-agent-sdk#5449). Every supported local server therefore accepts persona.

Why

A task-tuned agent (a read-only explorer, a planner, a triage bot, a non-coding agent) needs a different identity and way of working. It still needs OpenHands' operating rules:

  • the security policy and the risk definitions that "confirm risky actions" relies on;
  • browser and terminal rules;
  • memory instructions.

software-agent-sdk#5449 adds a profile persona that replaces just the persona and coding-workflow part of the prompt. The profile already had system_message_suffix for adding instructions. This section exposes both as one choice.

Summary

Issue Number

Part of #17234

How to Test

npm ci
npx vitest run                     # 8078 passed; 3 fail identically on a clean origin/main: docker-session-key-policy (exit 127)
                                   # and run-phase formatRunPhaseAge (local timezone/locale), both environmental
npm run typecheck                  # clean
npx eslint <changed files>         # 0 errors; the warnings are pre-existing on main
node scripts/check-translation-completeness.cjs   # all 15 locales complete
npm run build                      # passes

Unit tests cover:

  • the builder: each mode's fields, blank text, the hidden section, ACP;
  • readSystemPromptSeed;
  • the form: hidden where launches wouldn't apply it, seeding each mode clean, instructions saved as the suffix, empty-text validation, switching back to default, the default hint;
  • the shell seed and the gates.

End to end, full local stack (npm run dev: ingress, automation and Vite) on this head, with the released agent-server 1.53.0 and automation 1.18.0. State is in a scratch dir, the agent-server runs with an isolated HOME, and the LLM is the recording mock. Playwright drove the real UI:

  1. Author explorer (Custom persona) and helper (Default + your instructions), and create a paired plain (default).
  2. Reopen both authored profiles.
  3. Launch each from the home chat and open Agent Tools & Metadata → System Message.

Results are in .pr/persona-live-check.md. Each row is taken from the mock LLM's recorded requests:

Profile Starts with persona Kept sections sent Persona-layer sections sent Instructions sent
explorer (Custom persona) yes <MEMORY>, <SECURITY>, <SECURITY_RISK_ASSESSMENT>, <BROWSER_TOOLS>, <EXTERNAL_SERVICES>, <PROCESS_MANAGEMENT>, <SKILLS> none n/a
helper (Default + your instructions) no all of the above full default set (<SOUL>, <ROLE>, …) yes, after </SKILLS>
plain (OpenHands default) no all of the above full default set n/a

There were no page errors, apart from an unrelated 404 from the workspace picker probing /projects.

Video/Screenshots

All shots were taken on this head against agent-server 1.53.0. The editor shows the Tools section from #17516; the sub-agent and LLM-switching toggles no longer exist.

The three choices:

modes

Editor: Custom persona Editor: Default + your instructions Editor: default profile
persona instructions default

The default profile shows hints instead of the System prompt and Tools controls: local launches send it through the global settings (#17991).

System Message of the conversation launched from the Custom persona profile. The persona comes first, and the memory, security policy and risk-assessment guidance are still there:

Top: persona, then <MEMORY> and <SECURITY> Scrolled: <SECURITY_RISK_ASSESSMENT> kept
persona kept
Default + your instructions: the text follows </SKILLS> OpenHands default: <SOUL>, <ROLE>
instructions default

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

🤖 Generated with Claude Code


🐳 Docker images for this PR

• GHCR package: https://github.com/OpenHands/OpenHands/pkgs/container/agent-canvas

Component Value
Image ghcr.io/openhands/agent-canvas
Architectures amd64, arm64
Agent Server ghcr.io/openhands/agent-server:1.53.0-python
Automation openhands-automation==1.18.0
Commit 2a6126b8996553767005fef8eb5d171f7490a39c

Pull (multi-arch manifest)

# Multi-arch manifest — Docker automatically pulls the correct architecture
docker pull ghcr.io/openhands/agent-canvas:sha-2a6126b

Run

docker run -it --rm \
  -p 8000:8000 \
  ghcr.io/openhands/agent-canvas:sha-2a6126b

All tags pushed for this build

ghcr.io/openhands/agent-canvas:sha-2a6126b-amd64
ghcr.io/openhands/agent-canvas:feat-profile-system-prompt-amd64
ghcr.io/openhands/agent-canvas:pr-17843-amd64
ghcr.io/openhands/agent-canvas:sha-2a6126b-arm64
ghcr.io/openhands/agent-canvas:feat-profile-system-prompt-arm64
ghcr.io/openhands/agent-canvas:pr-17843-arm64
ghcr.io/openhands/agent-canvas:sha-2a6126b
ghcr.io/openhands/agent-canvas:feat-profile-system-prompt
ghcr.io/openhands/agent-canvas:pr-17843

About Multi-Architecture Support

  • Each tag (e.g., sha-2a6126b) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., sha-2a6126b-amd64) are also available if needed

simonrosenberg and others added 30 commits September 28, 2026 11:58
The editor asks the agent-server which tools may be picked and what a
profile resolves to, so canvas keeps no tool list of its own.

- Tools section in the profile editor: Standard shows the resolved set the
  server reports for this profile; Choose tools lists the catalog's
  selectable, runnable entries.
- Gated on the tool_catalog_v1 capability; without it the section is hidden
  and the stored selection is left untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both switches controlled which tools an agent gets, which the tool picker
now does. Two controls over the same thing meant a profile could say
"delegation on" and "tools: [glob]" at once, and the picker could not show
the tools the switches owned.

`task_tool_set` and `SwitchLLMTool` are ordinary catalog entries in the
picker now (software-agent-sdk#5151), so the editor drops both toggles and
the version gate that guarded sending `enable_switch_llm_tool` on a
profile save. The `agent_settings` launch path keeps its own
`enable_sub_agents`, which the SDK still honours.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Canvas attaches this tool itself for every conversation it hosts, so it is
not a per-profile capability, but the catalog offered it like one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ending answer

The picker listed bare tool names, so a user had to know what `task_tool_set`
or `ask_oracle` does. The server now sends a one-line blurb per tool
(software-agent-sdk#5151); `ProfileScopeList` already renders one, so both the
standard preview and the picker pass it through.

Switching to "choose tools" while the standard set was still resolving seeded
the selection from `undefined`, saving a profile with no tools at all. The
mode control stays locked until the server has answered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…file

The guard against seeding from an unresolved standard set keyed off the
query's `isPending`, which react-query also reports for a query disabled for
want of a draft — so a profile being created, with no name typed yet, had its
mode control locked with nothing to wait for.

It now keys off the actual invariant: a draft exists but the server has not
answered for it. The test mock models the disabled case, which is what hid
this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A draft whose `llm_profile_ref` dangles materialises 200 with no tools. The
guard only covered `undefined`, so that answer counted as resolved and
switching to "choose tools" seeded an empty selection — the very thing the
guard exists to prevent. The standard set is never legitimately empty, so an
empty answer now keeps the mode locked, as does a failed request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`tools: []` is a bare agent the user asked for, but the seed treated an
empty list as "nothing chosen yet" — so toggling to Standard and back
silently refilled it with the standard set, un-baring the agent.

The selection now tracks whether it is the user's: a stored list counts as
chosen even when empty, and so does any toggle. Seeding happens once, when
there is genuinely nothing to preserve.

Raised three times in review, and independently by @rajshah4 testing
against the SDK PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…atalog service

The standard tool set now comes from the catalog's in_default_set flag, so
the editor no longer POSTs a stand-in draft per keystroke.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…l toggles without one

- Standard tools = catalog entries with in_default_set && usable; the mode
  dropdown only waits for the catalog, not a name/LLM-dependent materialize.
- Backends without tool_catalog_v1 (cloud, older agent-servers) get the
  sub-agents and LLM-switching toggles back, writing enable_sub_agents /
  enable_switch_llm_tool exactly as on main. The global settings page keeps
  them too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rom a failed catalog

- Where the server no longer stores enable_sub_agents /
  enable_switch_llm_tool (cloud once it moves to the tool-catalog SDK),
  seed the legacy toggles from the profile's `tools` rather than from
  hard-coded defaults, so a save round-trips what the profile launches.
- Show an error with a Retry button when the tool catalog fails to load,
  instead of leaving the tools mode disabled with an empty standard list.
- Drop a comment describing Canvas behaviour from CanvasUITool.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ools

- Without a catalog (cloud), the sub-agent and switch_llm toggles now edit
  a stored explicit `tools` list instead of relying on the server to fold
  switches it ignores. switch_llm is only written into the list for schema
  v3 profiles, since older servers attach it from the switch alone.
- Seed the sub-agents toggle from list membership when `tools` is explicit.
- Standard → custom → standard no longer leaves the form dirty.
- readProfileTools dedups with a Map, so tools named like Object.prototype
  members survive, and it reads SwitchLLMTool as switch_llm.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The profile editor had two controls over the same thing again: the tool
picker, and sub-agent / LLM-switching toggles kept for backends without a
catalog. Tools are now only chosen in the picker. A backend without a
catalog gets no tool control, and a save leaves the stored `tools` as is.

Cloud advertises no capabilities, so the editor asks it for
`/api/tools/catalog` and reads a 404 as "no catalog". The profile-model
version gate for `enable_switch_llm_tool` goes with the toggle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The capability came from a module-level cache with no host check and no
re-render, so the picker could stay hidden after server info loaded or
reflect the previous backend after a switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…able

Drop the tool_catalog_v1 capability gate (and the server-info query added
for it): every supported backend is expected to serve the catalog, and the
query stays keyed on the backend host.

When the catalog 404s, fails, or is still loading, the draft leaves out
tools, so the whole-profile save carries the stored tools and legacy tool
switches through unchanged. The editor says the tools section is
unavailable and that saving keeps the current tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The server now derives enable_sub_agents / enable_switch_llm_tool from tools
and reads a changed switch as a toggle, so carrying the stored switches next
to an edited tools list could re-add a tool the user removed. Without a
tools draft the stored values still round-trip unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/i18n/translation.json
Canvas now requires the SDK release that serves /api/tools/catalog and stores
profile tools only as `tools`, so remove the no-catalog 404 state, the stored
tool-switch stripping on save, and the SwitchLLMTool class-name alias. Add a
mock /api/tools/catalog handler so dev:mock can exercise the picker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`tools` is the only tool control now. Global agent settings leave it unset, so
new conversations get the standard set (with switch_llm); sub-agents and a
custom tool list are chosen on an agent profile. The adapter sends a configured
tools list as given instead of adding it to the defaults, and never sends the
retired flags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An agent_settings launch with no tools selection omits `tools`; the server
  resolves the standard set, adding the browser where it can run. An explicit
  list is sent as given.
- Drop the client copy of the standard set and the advertised-tool gating
  (isAgentServerToolAvailable) it relied on.
- VITE_ENABLE_BROWSER_TOOLS=false now starts the agent-server with
  OH_ENABLE_BROWSER=false instead of trimming the payload, so the flag is read
  when the stack starts rather than at build time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DEFAULT_SETTINGS carried agent_settings schema 6, which the server now migrates
by adding switch_llm to an explicit tools list. The .pr notes and scripts cover
the server-resolved default set, the browser-off switch and the dry run's
valid flag.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ocal comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…SATION_IMAGE_HAS_BROWSER

Settings migrated from the retired tool switches carry a pinned tools
list that no editor shows. The agent-profile library now surfaces it
with a reset to the standard set (tools: null).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
simonrosenberg and others added 5 commits October 2, 2026 17:39
…rompt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alog

# Conflicts:
#	src/routes/agent-settings.tsx
…gh ToolClient

Raise minimumAgentServer to 1.51.0, the first release serving /api/tools/catalog.
Tools stay local-only: cloud does not serve the catalog or honour profile tools yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stack on #17516, which pins agent-server 1.52.0 and raises the floor to 1.51.0.
Every supported local server now accepts persona, so the profile_persona_v1 gate goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonrosenberg
simonrosenberg changed the base branch from main to feat-profile-tool-catalog October 5, 2026 13:05
simonrosenberg and others added 4 commits October 5, 2026 09:05
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Local launches send the default profile through agent_settings, so its stored
tools never reach the agent; the editor now says so instead of offering them.
The secret-scope capability probe goes (every supported server enforces it), and
test fixtures move above the new minimum agent-server version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…feat-profile-system-prompt

# Conflicts:
#	__tests__/api/agent-profiles-service/profile-field-support.test.ts
#	src/routes/agent-settings.tsx
simonrosenberg and others added 3 commits October 5, 2026 11:00
Automation 1.18.0 is built on SDK 1.53.0, so the SDK version check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
allhands-bot and others added 3 commits October 5, 2026 16:01
Taken on this head (stacked on #17516), so the editor shows the tool
picker instead of the removed sub-agent and LLM-switch toggles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feat-profile-tool-catalog to main October 5, 2026 16:17
…rompt

# Conflicts:
#	__tests__/api/agent-profiles-service/profile-field-support.test.ts
#	__tests__/routes/agent-settings.test.tsx
#	__tests__/routes/build-agent-profile-fields.test.ts
#	src/api/agent-profiles-service/profile-field-support.ts
#	src/routes/agent-settings.tsx
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in v1.25.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released: v1.25.0 Shipped in v1.25.0 type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Expose AgentProfile system_message_suffix in Agent settings

4 participants