Skip to content

feat: surface conversation tags in the conversation panel#1926

Merged
neubig merged 3 commits into
OpenHands:mainfrom
harish-chandramowli:feat/conversation-tags-metadata
Jul 25, 2026
Merged

feat: surface conversation tags in the conversation panel#1926
neubig merged 3 commits into
OpenHands:mainfrom
harish-chandramowli:feat/conversation-tags-metadata

Conversation

@harish-chandramowli

@harish-chandramowli harish-chandramowli commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

HUMAN:
A small quality of life improvement which i thought might help community. But pelase feel free to reject the changes if you dont see fit

  • A human has tested these changes.

AGENT:

This PR was implemented by an AI agent (Claude Code) working with @harish-chandramowli; the HUMAN section above is left for him to fill in after testing on our self-hosted deployment.


Why

Agent-server conversations carry server-side key-value tags (settable at creation and via PATCH /api/conversations/{id}), and the frontend already fetches and normalizes them — but the UI only ever consumes the reserved acpserver routing tag. Tags stamped by automations or API clients for attribution (e.g. origin=slack, owner=alice) are invisible, so in a panel with Slack-automation-born, API-born, and browser-born conversations there is no way to tell which is which.

Discussed in #1925.

Summary

  • Expose the already-normalized tags map on AppConversation (local adapter; null for Cloud conversations) and add getDisplayConversationTags() which filters reserved keys (acpserver) and returns sorted entries
  • Render non-reserved tags as key: value chips in the conversation card footer with a fixed display budget: at most 3 chips (sorted by key) plus a +N overflow chip whose tooltip lists the hidden tags — an API-controlled tag map can't grow a card unboundedly. The full map stays on AppConversation.tags for non-display consumers
  • Gate the chips behind a new "Tags" toggle in the conversation panel's Metadata filter-menu section, persisted exactly like the existing metadata toggles; i18n key added for all 15 languages

Issue Number

Part of #1925 — this PR delivers the chip-display half. The issue's second item ("match tags in the text filter") turned out to rest on a wrong premise of mine: there is no free-text conversation filter in the panel today (Filter conversations is only the aria-label of the filter-menu button), so tag filtering means building a text-filter feature first and is left as follow-up scope on the issue.

How to Test

  1. npm install && npm run dev against a local agent-server backend
  2. Create a conversation with tags, e.g.:
    curl -X POST localhost:8000/api/conversations -H 'Content-Type: application/json' -d '{ ..., "tags": {"origin": "slack", "owner": "alice"} }'
    To tag an existing conversation, note that PATCH replaces the complete tags map (it does not merge keys) — read the current map first, merge, and send the full result, e.g. GET /api/conversations/{id} → merge → PATCH with {"tags": {"acpserver": "claude-code", "origin": "slack", "owner": "alice"}}. Sending only the new key would silently drop every existing tag, including the reserved acpserver routing tag
  3. In the sidebar the card shows origin: slack / owner: alice chips; the reserved acpserver tag never renders as a chip; with more than 3 non-reserved tags the card shows the first 3 (sorted by key) plus a +N chip whose tooltip lists the rest
  4. Filter menu → Metadata → "Tags" toggles the chip row on/off (persisted per browser)

Automated evidence from this branch (macOS, Node 22):

  • npm run lint — typecheck + eslint + prettier all pass
  • npm test — 3941 passed / 12 failed; all 12 failures (recommended-automations.test.tsx, conversation-runtime-info.test.ts, use-websocket.test.ts) reproduce identically on a clean main worktree in the same environment, i.e. pre-existing/env-specific and untouched by this diff. New coverage: 6 tag-chip tests in conversation-card.test.tsx (including the display-budget cap and +N overflow tooltip), 3 wire-boundary tests in agent-server-conversation-service.test.ts asserting a wire payload's valid/malformed/absent tags reach AppConversation.tags normalized, plus store-default/persist and filter-menu prop updates
  • npm run build — succeeds
  • lint-staged pre-commit (eslint --fix, staged typecheck, check-translation-completeness) passed on commit

Live end-to-end verification (after the initial push): ran npm run dev:frontend on this branch against our real self-hosted agent-server (openhands-agent-server 1.36.1) over an SSH tunnel, PATCHed {"tags": {"origin": "review", "owner": "harish"}} onto an existing conversation, and drove the UI with Playwright: the card renders both chips, the reserved acpserver tag never renders, and the filter-menu "Tags" toggle hides/re-shows the row (chip count 2 → 0 → 2). Screenshots/video from that session are attached below.

Video/Screenshots

Agent-recorded Playwright walkthrough against our live self-hosted backend (agent-server 1.36.1, real conversations): video (mp4) (webm) — chips visible → Tags toggle off → back on.

Chips on a tagged conversation "Tags" toggle in the filter menu Chips hidden after toggling off
tag chips visible on card Tags row in Metadata menu chips hidden
tags-demo.mp4

Type

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

Notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01F5QQqqrqmVBXv3kdSFKpAy

Agent-server conversations carry server-side key-value tags (settable at
creation and via PATCH), but the UI only ever consumed the reserved
acpserver routing tag — tags stamped by automations or API clients for
attribution (e.g. origin=slack, owner=alice) were invisible.

- expose the normalized tags map on AppConversation (local adapter;
  null for Cloud conversations)
- render non-reserved tags as sorted key: value chips in the
  conversation card footer, reusing the metadata indent
- gate the chips behind a new "Tags" toggle in the panel's Metadata
  filter-menu section (persisted like the existing metadata toggles)
- filter reserved keys through getDisplayConversationTags so acpserver
  never double-renders next to the agent chip

Closes OpenHands#1925

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5QQqqrqmVBXv3kdSFKpAy
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the openhands Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the type: feat A new feature label Jul 24, 2026
@harish-chandramowli
harish-chandramowli marked this pull request as ready for review July 24, 2026 04:12
@VascoSch92

Copy link
Copy Markdown
Member

Just a couple of questions: where are tags saved? Because i think the correct thing to do would be to save tags in the trajectories i.e. in the agent server. And then you visualize just that with the UI

@neubig neubig left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Acceptable — The data plumbing is straightforward, and keeping Tags as a visible option in the Metadata menu is the right product direction. The unbounded card rendering and incomplete #1925 scope need rework before merge.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM
    This renders API-controlled metadata in every conversation card. The main risk is sidebar layout/performance degradation from unbounded input; there are no security-sensitive or dependency changes.

VERDICT:
Needs rework: Bound chip rendering, preserve access to overflow tags, and either implement the issue's tag-filter acceptance criterion or stop closing #1925.

KEY INSIGHT:
Server-owned metadata needs an explicit UI display budget and an end-to-end assertion at the adapter boundary.

Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.

Comment thread src/components/features/conversation-panel/conversation-panel.tsx
Comment thread src/api/agent-server-adapter.ts
@VascoSch92

Copy link
Copy Markdown
Member

I will rather open a Pr in agent-server to add tags and then this one will just display them

…emantics

Review follow-ups from OpenHands#1926:

- cap the card's tag-chip row at MAX_VISIBLE_TAG_CHIPS (3) sorted keys
  and fold the remainder into a "+N" chip whose tooltip lists the hidden
  tags, so an API-controlled tag map can't grow a card unboundedly; the
  full map stays on AppConversation.tags for non-display consumers
- extend the conversation-service tests to assert the wire →
  AppConversation.tags boundary end-to-end: well-formed payloads arrive
  intact, non-string values are dropped, an absent wire field surfaces
  as null (the malformed-tags test previously only asserted acp_server)
- document that agent-server PATCH replaces the complete tags map
  rather than merging keys, so writers must read-merge-write

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012t7NV3kLhJv3BWCp8vimsm
@harish-chandramowli

Copy link
Copy Markdown
Contributor Author

@VascoSch92 Tags are already stored server-side — no agent-server change is needed. agent-server has ConversationInfo.tags: Record[str, str] today, settable at conversation creation and via PATCH /api/conversations/{id} (that is how Canvas itself stamps the reserved acpserver routing tag), and Canvas already fetches and normalizes the map (normalizeTags in agent-server-conversation-service.api.ts). This PR is exactly the "just display them" half you describe: it surfaces the already-stored, already-fetched map in the UI. One server-side nuance worth knowing if you do touch that area: PATCH replaces the complete tags map rather than merging keys, which #1926 now documents on the frontend type.

@VascoSch92

Copy link
Copy Markdown
Member

Ah ok perfect.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Mock-LLM E2E Tests

60/60 passed

Commit: f56b8684 · Workflow run · Test artifacts

Details
Status Test Duration
automations/mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 1: setup LLM profile and register automation trajectory 6.7s
automations/mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 28.5s
automations/mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 6.3s
automations/mock-llm-preset-automation.spec.ts › preset automation → slash command conversation › automation card sends the correct slash command to a conversation 15.7s
automations/mock-llm-preset-automation.spec.ts › preset automation → slash command conversation › direct slash command from home page triggers skill activation 13.8s
backends/mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.6s
backends/mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 5.3s
backends/mock-llm-auth-modes.spec.ts › auth mode: public gate › shows first-run onboarding before the auth screen when no key is configured 1.4s
backends/mock-llm-auth-modes.spec.ts › auth mode: public gate › rejects an incorrect key with an inline error 1.6s
backends/mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.6s
backends/mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 796ms
backends/mock-llm-auth-modes.spec.ts › auth mode: public gate › re-prompts when the server rotates its key (stale localStorage) 1.5s
backends/mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 17.0s
backends/mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 20.7s
backends/mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes 7.4s
backends/mock-llm-partial-stack.spec.ts › partial stack: --backend-only › serves backend APIs but returns 503 for the frontend root 15.3s
backends/mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied 104ms
backends/mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict 6.0s
conversations/mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 6.4s
conversations/mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 6.6s
conversations/mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 7.3s
conversations/mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 5.9s
conversations/mock-llm-image-upload.spec.ts › mock-LLM image upload › attaching an image embeds it as base64 in the LLM completion call 13.1s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 1: ensure mock LLM profile is configured 6.7s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata 12.4s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 3: git control bar shows workspace pill and git actions 25.3s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 4: files tab can enable diff view for attached workspace 5.9s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 5: browser tab shows empty state 6.3s
files/mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 6: files tab defaults to file-tree view without attached workspace 8.2s
home/mock-llm-folder-workspace.spec.ts › mock-LLM folder browser → workspace → conversation › step 1: browse to a folder, add it as a workspace, and launch a conversation with the correct working_dir 8.6s
mcp/mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 1: GitHub card is visible on the MCP marketplace page 5.6s
mcp/mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 2: clicking GitHub add control opens the install modal with correct fields 5.6s
mcp/mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 3: full install flow — fill PAT, submit, verify installed 12.5s
mcp/mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 4: installed GitHub server can be deleted 5.9s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › install: invalid Slack credentials are blocked with a credential-check error 5.8s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › install: a valid token missing only a scope still installs (missing_scope is not a credential failure) 5.9s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › install: an older agent server that omits tool_result still installs (compat) 6.0s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › edit: Test Connection verifies the stored credentials and surfaces a credential failure 5.7s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › edit: Test Connection reports success for valid stored credentials 5.8s
mcp/mock-llm-mcp-slack-credentials.spec.ts › MCP Test Connection credential verification (Slack) › custom (non-catalog) server: Test Connection attaches no verification probe 5.8s
onboarding/mock-llm-onboarding-happy-path.spec.ts › onboarding happy path › completes the full onboarding flow and launches a conversation 4.6s
onboarding/mock-llm-onboarding-regressions.spec.ts › onboarding recent regressions › keeps the modal open on backdrop click and Escape 1.5s
onboarding/mock-llm-onboarding-regressions.spec.ts › onboarding recent regressions › defaults the LLM setup step to OpenAI GPT-5.5 1.6s
regressions/mock-llm-ui-regressions.spec.ts › UI regressions › scopes standalone styles to the agent-server-ui shell 1.4s
regressions/mock-llm-ui-regressions.spec.ts › UI regressions › renders critic results on agent messages and finish actions 1.5s
regressions/mock-llm-ui-regressions.spec.ts › UI regressions › loads older events when scrolling up 1.8s
regressions/mock-llm-ui-regressions.spec.ts › UI regressions › selected workspace persists after navigating away and returning 2.6s
regressions/mock-llm-ui-regressions.spec.ts › UI regressions › cleared sessionStorage yields empty workspace selection 1.5s
settings/mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 1: configure ACP agent via Settings → Agent UI 13.3s
settings/mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 2: reload and verify ACP settings are persisted in UI 5.7s
settings/mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply 6.7s
settings/mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 4: resume ACP conversation from sidebar after navigating away 5.7s
settings/mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 12.7s
settings/mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 7.7s
settings/mock-llm-profile-management.spec.ts › active profile deletion + reconciliation › active profile is deletable and reconciliation activates another profile 10.2s
settings/mock-llm-profile-management.spec.ts › same-model profile identity › chat header shows the correct profile when two profiles share the same model 15.6s
settings/mock-llm-profile-management.spec.ts › OpenHands provider hidden base_url preservation › re-saving an OpenHands profile from Basic view preserves hidden base_url 7.4s
skills/mock-llm-skills.spec.ts › skill loading: project, user, and deletion › project skill in workspace/.agents/skills/ triggers on matching keyword 13.7s
skills/mock-llm-skills.spec.ts › skill loading: project, user, and deletion › user skill in ~/.openhands/skills/ triggers on matching keyword 13.7s
skills/mock-llm-skills.spec.ts › skill loading: project, user, and deletion › deleting a user skill removes it from subsequent conversations 13.7s

Posted by the Mock-LLM E2E workflow · results are deterministic (scripted LLM responses)

@neubig neubig left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the final head after the requested fixes and merge from main. All four review concerns are addressed, and the required Linux/Windows CI, full unit suite, Mock-LLM E2E, builds, and package verification pass.

@neubig
neubig merged commit b071f3d into OpenHands:main Jul 25, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants