The notebook chat panel hardcodes agent_type = "notebook_assistant". The notebook assistant is good at document editing but knows nothing about Galaxy's visualization plugin system, Vega-Lite spec generation, or chart type recommendations. Users doing data exploration want AI help picking and configuring visualizations — a different skill set from "rewrite my Methods section."
The architecture already supports multi-agent at every layer except one frontend constant:
| Layer | Status |
|---|---|
| DB schema | agent_type stored per-message in JSON blob, not a column. No constraint on mixing. |
| Chat API | POST /api/chat?agent_type=X — already a dynamic query param routed by AgentService.route_and_execute() |
| Agent registry | AgentType enum + _registry dict. New agents register with a decorator. |
| Agent config | Per-agent model, temperature, prompt, tools via galaxy.yml cascade |
| Notebook context | Chat API loads history_id + notebook_content for any agent when notebook_id present |
| Frontend display | ChatMessageCell already renders per-message agentType with distinct icons/labels from agentTypes.ts |
| Diff views | ProposalDiffView/SectionPatchView only render when edit_mode present in agent response metadata. Non-edit agents gracefully render as plain chat. |
The one hardcoded line: NotebookChatPanel.vue:34 → const AGENT_TYPE = "notebook_assistant"
- Replace
const AGENT_TYPEwithconst selectedAgent = ref("notebook_assistant") - Add agent picker dropdown in chat header — filter
agentTypes.tsregistry to notebook-compatible agents - Pass
selectedAgent.valueinsubmitQuery()instead of constant - Show agent icon/label per-message in conversation thread (already works via
ChatMessageCell) - Persist selected agent in
userLocalStorageper-notebook
Nothing. The routing, context injection, and storage all work already.
- Vitest: agent picker renders, switching changes submitted agent_type
- Vitest: messages from different agents render with correct icons
- API test: send messages with different agent_types to same notebook exchange, verify both stored
# lib/galaxy/agents/visualization.py
class VisualizationAgent(BaseGalaxyAgent):
agent_type = AgentType.VISUALIZATION
name = "visualization"Add VISUALIZATION = "visualization" to AgentType enum in base.py.
| Tool | Purpose |
|---|---|
list_history_datasets |
Reuse from history_tools.py — same dataset discovery |
get_dataset_peek |
Reuse — preview tabular data |
get_column_metadata |
NEW — column names, types, cardinality for a tabular dataset |
search_visualizations |
NEW — query /api/plugins?embeddable=True, return available viz types with descriptions |
get_visualization_schema |
NEW — given a plugin name, return its config schema (from XML <param> definitions) |
resolve_hid |
Reuse — HID → directive ID translation |
class VegaSpecProposal(BaseModel):
mode: Literal["vega_spec"] = "vega_spec"
description: str # what the chart shows
spec: dict # Vega-Lite JSON spec
target_section: str | None # heading to insert under (optional)
class PluginVisualizationProposal(BaseModel):
mode: Literal["plugin_viz"] = "plugin_viz"
description: str
plugin_name: str
dataset_hid: int
config: dict # plugin-specific config
class DirectiveInsert(BaseModel):
mode: Literal["directive_insert"] = "directive_insert"
directive: str # e.g. "history_dataset_display(history_dataset_id=42)"
target_section: str | NonePlain str for conversational responses (same pattern as notebook assistant).
- "You help users visualize their data"
- Knows Vega-Lite spec syntax and common chart patterns (bar, scatter, line, heatmap, histogram)
- Knows Galaxy plugin visualization types and their configs
- Knows Galaxy markdown directive syntax for embedding
- Can read dataset peeks to suggest appropriate chart types based on column structure
- Produces insertable directives or Vega-Lite specs ready to paste into the notebook
New component: VisualizationProposalView.vue
- For
vega_spec: render a live Vega-Lite preview (reuseVegaWrapper.vue) + "Insert into notebook" button - For
plugin_viz: render plugin description + config summary + "Insert directive" button - For
directive_insert: render directive preview + insert button
The NotebookChatPanel would check agent_response.mode and dispatch to the appropriate view — same pattern as existing isProposalVisible / isSectionPatch checks.
If a user talks to the notebook assistant about their ChIP-seq analysis, then switches to the visualization agent, the viz agent has no memory of the prior conversation.
-
Separate context (simplest): Each agent sees only its own prior messages. Conversation history filtering by
agent_typein the JSON blob. Users switch agents like switching tools. -
Shared context (richer): All messages in the exchange are visible to both agents. The viz agent sees the notebook assistant's conversation and vice versa. Risk: prompt confusion, wasted tokens on irrelevant context.
-
Summary handoff (middle ground): When switching agents, inject a one-paragraph summary of the prior conversation into the new agent's context. Could be auto-generated or just the last N messages.
Recommendation: start with option 1 (separate context). It's the simplest, avoids prompt pollution, and matches user mental model of "I'm now talking to a different assistant." Revisit if users report friction.
| File | Change |
|---|---|
NotebookChatPanel.vue |
~30 lines: ref, dropdown, pass to API |
agentTypes.ts |
~5 lines: add notebook_compatible: true filter flag |
historyNotebookStore.ts |
~5 lines: persist selected agent per-notebook |
| Tests (3-4 vitest files) | ~80 lines |
| Total | ~120 lines |
| File | Change |
|---|---|
lib/galaxy/agents/visualization.py |
~300 lines: agent class, tools, output types |
lib/galaxy/agents/prompts/visualization.md |
~100 lines: system prompt |
lib/galaxy/agents/base.py |
+1 line: enum value |
client/src/components/HistoryNotebook/VisualizationProposalView.vue |
~150 lines |
NotebookChatPanel.vue |
~20 lines: proposal view dispatch |
agentTypes.ts |
~5 lines: new entry |
| Backend tests | ~200 lines |
| Frontend tests | ~150 lines |
| Total | ~930 lines |
- Should switching agents mid-exchange create a new
ChatExchangerow, or continue the same one? Per-message agent_type in JSON supports either, but "New Chat" semantics may be cleaner. - Should the
autorouter agent be offered in the notebook picker? It doesn't have notebook tools registered — would need wiring. - Should the visualization agent be able to produce
SectionPatchEdit(reuse notebook assistant's output type) for inserting directives, or always use its own output types? - For
get_column_metadatatool: should it use the dataset API or parse the peek directly? Peek is already loaded; API gives richer metadata but requires additional request. - Should the visualization agent know about the Charts external plugin (charts.galaxyproject.org), or only plugins registered in the local Galaxy instance?