diff --git a/.github/workflows/test_chat_agent.yml b/.github/workflows/test_chat_agent.yml
index 30373cd59..a9fb8bc86 100644
--- a/.github/workflows/test_chat_agent.yml
+++ b/.github/workflows/test_chat_agent.yml
@@ -1,8 +1,9 @@
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
-# This workflow tests the GAIA Chat Agent functionality
-# Tests include: Session persistence, chat history, RAG, and path validation
+# This workflow tests the GAIA Chat Agent, which ships as the standalone
+# gaia-agent-chat wheel (#1102). Tests include the wheel's own smoke tests
+# plus the framework-side session, RAG, and path-validation suites.
name: Chat Agent Tests
@@ -11,8 +12,9 @@ on:
push:
branches: [ main ]
paths:
- - 'src/gaia/agents/chat/**'
+ - 'hub/agents/python/chat/**'
- 'src/gaia/agents/base/**'
+ - 'src/gaia/agents/tools/**'
- 'src/gaia/rag/**'
- 'src/gaia/chat/**'
- 'tests/test_chat_agent.py'
@@ -23,8 +25,9 @@ on:
branches: [ main ]
types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/gaia/agents/chat/**'
+ - 'hub/agents/python/chat/**'
- 'src/gaia/agents/base/**'
+ - 'src/gaia/agents/tools/**'
- 'src/gaia/rag/**'
- 'src/gaia/chat/**'
- 'tests/test_chat_agent.py'
@@ -67,6 +70,19 @@ jobs:
uv pip install --system -e .[dev,rag]
# Install pytest-mock for mocking tests
uv pip install --system pytest-mock
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102)
+ uv pip install --system -e hub/agents/python/chat
+
+ - name: Run Chat Agent Package Tests
+ env:
+ GAIA_MEMORY_DISABLED: "1"
+ run: |
+ echo "================================================================"
+ echo " CHAT AGENT PACKAGE TESTS"
+ echo "================================================================"
+ echo "Testing registration shapes, lazy re-exports, and discovery..."
+ echo ""
+ python -m pytest hub/agents/python/chat/tests/ -v --tb=short
- name: Run Chat Agent Unit Tests
env:
diff --git a/.github/workflows/test_security.yml b/.github/workflows/test_security.yml
index d56288410..113d58e29 100644
--- a/.github/workflows/test_security.yml
+++ b/.github/workflows/test_security.yml
@@ -58,7 +58,11 @@ jobs:
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install dependencies
- run: uv pip install --system -e .[dev,rag]
+ run: |
+ uv pip install --system -e .[dev,rag]
+ # verify_*.py instantiate ChatAgent, which ships as the standalone
+ # gaia-agent-chat wheel (#1102).
+ uv pip install --system -e hub/agents/python/chat
- name: Run Path Validator Security Tests
env:
@@ -155,7 +159,12 @@ jobs:
shell: pwsh
- name: Install dependencies
- run: uv pip install --system -e .[dev,rag]
+ run: |
+ uv pip install --system -e .[dev,rag]
+ # verify_*.py instantiate ChatAgent, which ships as the standalone
+ # gaia-agent-chat wheel (#1102).
+ uv pip install --system -e hub/agents/python/chat
+ shell: pwsh
- name: Run Path Validator Security Tests
shell: pwsh
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 7de2d9f68..9fec5201a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -53,7 +53,7 @@
"name": "Chat Agent Debug - Model Selection",
"type": "debugpy",
"request": "launch",
- "module": "gaia.agents.chat.app",
+ "module": "gaia_agent_chat.app",
"args": ["--query", "hi"],
"cwd": "${workspaceFolder}",
"env": {
diff --git a/docs/guides/chat.mdx b/docs/guides/chat.mdx
index e6ae87650..b211732b1 100644
--- a/docs/guides/chat.mdx
+++ b/docs/guides/chat.mdx
@@ -208,7 +208,7 @@ gaia chat --index document.pdf --debug
```python Python Debug
# Python SDK with debug — ChatAgent takes a single ChatAgentConfig
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
config = ChatAgentConfig(
rag_documents=['document.pdf'],
@@ -322,7 +322,7 @@ is set, the UI toggle reflects the effective value and disables itself — which
handy for the eval harness:
```python
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
agent = ChatAgent(ChatAgentConfig(prompt_profile="doc", dynamic_tools=True))
```
diff --git a/docs/plans/agent-ui-eval-benchmark.md b/docs/plans/agent-ui-eval-benchmark.md
index 80b1b9ff5..78dc780ca 100644
--- a/docs/plans/agent-ui-eval-benchmark.md
+++ b/docs/plans/agent-ui-eval-benchmark.md
@@ -1059,7 +1059,7 @@ Single-scenario run: ~$0.10-0.15.
"root_cause": "Smart Discovery workflow uses query keywords as file search patterns. Needs to extract likely document names, not just topic keywords.",
"recommended_fix": {
"target": "system_prompt",
- "file": "src/gaia/agents/chat/agent.py",
+ "file": "hub/agents/python/chat/gaia_agent_chat/agent.py",
"description": "In Smart Discovery section, instruct agent to search for common document names related to the topic, not just the exact query terms."
}
}
diff --git a/docs/plans/email-triage-agent.mdx b/docs/plans/email-triage-agent.mdx
index 5aa8da637..8b71340b1 100644
--- a/docs/plans/email-triage-agent.mdx
+++ b/docs/plans/email-triage-agent.mdx
@@ -567,7 +567,7 @@ v0.20.0 for GaiaAgent broadly. The email-agent path explicitly opts out:
flips red loudly if an email-content payload is ever seen heading to a cloud
backend. This is the alarm, not the defense — the defense is the tag check.
- An integration test asserts this invariant on every PR touching `gaia/llm/`
- or `gaia/agents/chat/`.
+ or `hub/agents/python/chat/`.
Nothing in this spec relies on the user "just trusting" the local-only claim.
diff --git a/docs/plans/security-model.mdx b/docs/plans/security-model.mdx
index 202a52c5e..a031d12a4 100644
--- a/docs/plans/security-model.mdx
+++ b/docs/plans/security-model.mdx
@@ -783,8 +783,8 @@ The following security measures are already implemented in the codebase:
| Measure | Location | Description |
|---------|----------|-------------|
-| Shell command whitelist | `src/gaia/agents/chat/tools/shell_tools.py` | `ALLOWED_COMMANDS` set restricts CLI tool to read-only commands |
-| Git command whitelist | `src/gaia/agents/chat/tools/shell_tools.py` | Only read-only git subcommands (`status`, `log`, `diff`, etc.) |
+| Shell command whitelist | `src/gaia/agents/tools/shell_tools.py` | `ALLOWED_COMMANDS` set restricts CLI tool to read-only commands |
+| Git command whitelist | `src/gaia/agents/tools/shell_tools.py` | Only read-only git subcommands (`status`, `log`, `diff`, etc.) |
| Localhost-only MCP bridge | `src/gaia/mcp/mcp.json` | `GAIA_MCP_HOST` defaults to `localhost` |
| Subprocess timeout | `src/gaia/mcp/external_services.py` | `timeout=30` on MCP subprocess calls |
| Tool registry validation | `src/gaia/agents/base/agent.py` | Rejects unregistered tool names |
diff --git a/docs/plans/tool-loader.mdx b/docs/plans/tool-loader.mdx
index a8bc9af73..26b7f9083 100644
--- a/docs/plans/tool-loader.mdx
+++ b/docs/plans/tool-loader.mdx
@@ -3,7 +3,7 @@ title: "Dynamic Tool Loader"
---
- **Source Code:** [`src/gaia/agents/base/tool_loader.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/base/tool_loader.py) · bundles [`src/gaia/agents/chat/tool_bundles.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tool_bundles.py)
+ **Source Code:** [`src/gaia/agents/base/tool_loader.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/base/tool_loader.py) · bundles [`hub/agents/python/chat/gaia_agent_chat/tool_bundles.py`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/tool_bundles.py)
@@ -272,7 +272,7 @@ when set — the toggle then reflects the effective value and disables — so τ
the cap stay env-only tuning.
**CORE (10, always-on, cap- & eviction-exempt)** — defined in
-[`tool_bundles.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tool_bundles.py):
+[`tool_bundles.py`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/tool_bundles.py):
`remember`, `recall`, `update_memory`, `forget`, `search_past_conversations`,
`read_file`, `query_documents`, `query_specific_file`, `set_loop_state`,
`request_user_input`.
@@ -351,7 +351,7 @@ baseline — meaning **CORE-only is the ~60%-reduction best case** and a full
#### How Part 2 shipped (implementation reference)
**`load_tools` is always-on via CORE.** `load_tools` is added to
-[`DOC_CORE_TOOLS`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tool_bundles.py)
+[`DOC_CORE_TOOLS`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/tool_bundles.py)
(CORE = 11), so once registered it renders in **both** the text prompt and the
native `tools=` schema every active turn and is cap-/eviction-exempt. It is
registered **only when the loader is active** (`self.tool_loader is not None`),
@@ -481,7 +481,7 @@ needs a seeded procedure matching a scenario goal.
These were open in the design sketch; Part 1 (#1449) decided them as follows:
1. **Bundle definitions and CORE membership** — *decided.* CORE = 10 names and 12
- bundles, in [`tool_bundles.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tool_bundles.py)
+ bundles, in [`tool_bundles.py`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/tool_bundles.py)
(see [How Part 1 shipped](#part-1-selection-dual-path-filtering-landed-1449)),
pinned to cover the 37-tool `doc` registry exactly.
2. **Similarity threshold τ / cap** — *decided.* τ = `0.20` inclusive, cap = `14`
diff --git a/docs/playbooks/chat-agent/part-1-getting-started.mdx b/docs/playbooks/chat-agent/part-1-getting-started.mdx
index ec4f0bd38..367dff7a7 100644
--- a/docs/playbooks/chat-agent/part-1-getting-started.mdx
+++ b/docs/playbooks/chat-agent/part-1-getting-started.mdx
@@ -141,7 +141,7 @@ Get a working agent running to understand the basic flow.
```python title="my_chat_agent.py"
import json
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# Create agent with a document
config = ChatAgentConfig(
@@ -383,7 +383,7 @@ flowchart TD
```python Example: Multiple Mixins
from gaia.agents.base.agent import Agent
-from gaia.agents.chat.tools import RAGToolsMixin, FileToolsMixin
+from gaia.agents.tools import RAGToolsMixin, FileToolsMixin
class MyAgent(Agent, RAGToolsMixin, FileToolsMixin):
def _register_tools(self):
diff --git a/docs/playbooks/chat-agent/part-2-advanced-features.mdx b/docs/playbooks/chat-agent/part-2-advanced-features.mdx
index 65523441f..b31365b88 100644
--- a/docs/playbooks/chat-agent/part-2-advanced-features.mdx
+++ b/docs/playbooks/chat-agent/part-2-advanced-features.mdx
@@ -30,7 +30,7 @@ Use GAIA's built-in mixins instead of implementing tools from scratch.
```python step4_with_mixins.py
from gaia.agents.base.agent import Agent
from gaia.agents.base.console import AgentConsole
-from gaia.agents.chat.tools import RAGToolsMixin, FileToolsMixin
+from gaia.agents.tools import RAGToolsMixin, FileToolsMixin
from gaia.agents.tools import FileSearchToolsMixin
from gaia.rag.sdk import RAGSDK, RAGConfig
@@ -101,7 +101,7 @@ agent.process_query("Find research papers in my Documents folder, index them, an
- `list_indexed_documents()` - List currently indexed files
- `rag_status()` - Get index statistics
- **Import:** `from gaia.agents.chat.tools import RAGToolsMixin`
+ **Import:** `from gaia.agents.tools import RAGToolsMixin`
@@ -117,7 +117,7 @@ agent.process_query("Find research papers in my Documents folder, index them, an
**Directory monitoring:**
- `add_watch_directory(directory)` - Monitor and auto-index changes
- **Import:** `from gaia.agents.chat.tools import FileToolsMixin`
+ **Import:** `from gaia.agents.tools import FileToolsMixin`
@@ -142,7 +142,7 @@ Add file system monitoring to automatically reindex documents when they change.
```python step5_with_monitoring.py
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# The full ChatAgent includes file monitoring!
config = ChatAgentConfig(
@@ -210,7 +210,7 @@ Implement session persistence to avoid re-indexing on every restart.
```python step6_create_session.py
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
config = ChatAgentConfig(
rag_documents=["./manual.pdf"]
@@ -227,7 +227,7 @@ if agent.save_current_session():
```
```python step6_load_session.py
-from gaia.agents.chat.agent import ChatAgent
+from gaia_agent_chat.agent import ChatAgent
agent = ChatAgent()
@@ -288,7 +288,7 @@ The `ChatAgent` class combines all components. Here's how to configure and use i
```python title="complete_agent.py"
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
from pathlib import Path
# Complete configuration
@@ -437,7 +437,7 @@ Extend the agent by adding domain-specific tools.
```python title="custom_tools.py"
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
from gaia.agents.base.tools import tool
class CustomDocAgent(ChatAgent):
@@ -568,7 +568,7 @@ Override system prompts to create domain-specific behavior.
```python title="research_agent.py"
- from gaia.agents.chat.agent import ChatAgent
+ from gaia_agent_chat.agent import ChatAgent
class ResearchAgent(ChatAgent):
"""Academic research specialist."""
@@ -591,7 +591,7 @@ Override system prompts to create domain-specific behavior.
```python title="support_agent.py"
- from gaia.agents.chat.agent import ChatAgent
+ from gaia_agent_chat.agent import ChatAgent
class CustomerSupportAgent(ChatAgent):
"""Customer support specialist."""
@@ -637,7 +637,7 @@ Override system prompts to create domain-specific behavior.
```python title="research_assistant.py"
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
from pathlib import Path
research_folder = Path.home() / "Research" / "AI-Papers"
@@ -684,7 +684,7 @@ Override system prompts to create domain-specific behavior.
```python title="knowledge_base.py"
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
import os
docs_root = "/company/shared/documentation"
@@ -709,7 +709,7 @@ Override system prompts to create domain-specific behavior.
```python title="load_shared_session.py"
- from gaia.agents.chat.agent import ChatAgent
+ from gaia_agent_chat.agent import ChatAgent
# Team member loads shared session
agent = ChatAgent()
@@ -739,7 +739,7 @@ Override system prompts to create domain-specific behavior.
```python title="personal_assistant.py"
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
from pathlib import Path
config = ChatAgentConfig(
diff --git a/docs/playbooks/chat-agent/part-3-deployment.mdx b/docs/playbooks/chat-agent/part-3-deployment.mdx
index 1bfe03905..14f4fa10f 100644
--- a/docs/playbooks/chat-agent/part-3-deployment.mdx
+++ b/docs/playbooks/chat-agent/part-3-deployment.mdx
@@ -249,11 +249,11 @@ def _generate_search_keys(self, query: str) -> List[str]:
},
# 👇 Your new agent
"doc-qa": {
- "class_name": "gaia.agents.chat.agent.ChatAgent",
+ "class_name": "gaia_agent_chat.agent.ChatAgent",
"init_params": {
"silent_mode": True,
# Only kwargs recognised by ChatAgentConfig will be applied;
- # see ChatAgentConfig in src/gaia/agents/chat/agent.py.
+ # see ChatAgentConfig in hub/agents/python/chat/gaia_agent_chat/agent.py.
"rag_documents": ["./company_docs"],
},
"description": "Document Q&A agent backed by RAG",
@@ -320,7 +320,7 @@ def _generate_search_keys(self, query: str) -> List[str]:
```python title="cli.py"
#!/usr/bin/env python3
import sys
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
def main():
if len(sys.argv) < 2:
@@ -674,15 +674,15 @@ def _generate_search_keys(self, query: str) -> List[str]:
## Source Code Reference
-
+
Main agent implementation with session management and file monitoring
-
+
Document indexing and query tools
-
+
Directory monitoring implementation
diff --git a/docs/reference/agent-core-loop-architecture.md b/docs/reference/agent-core-loop-architecture.md
index 7aeacd880..980c38e04 100644
--- a/docs/reference/agent-core-loop-architecture.md
+++ b/docs/reference/agent-core-loop-architecture.md
@@ -1,7 +1,7 @@
# Agent Core Loop — Architecture Review & Improvement Roadmap
**Date:** 2026-03-22
-**Scope:** `src/gaia/agents/base/agent.py` + `src/gaia/agents/chat/agent.py`
+**Scope:** `src/gaia/agents/base/agent.py` + `hub/agents/python/chat/gaia_agent_chat/agent.py`
**Context:** Analysis driven by failures surfaced in the Agent UI eval benchmark (34-scenario suite).
---
diff --git a/docs/sdk/advanced-patterns.mdx b/docs/sdk/advanced-patterns.mdx
index a961333d2..d96923969 100644
--- a/docs/sdk/advanced-patterns.mdx
+++ b/docs/sdk/advanced-patterns.mdx
@@ -12,7 +12,7 @@ icon: "diagram-project"
```python
from gaia.agents.base.agent import Agent
from gaia.agents.base.api_agent import ApiAgent
-from gaia.agents.chat.tools.file_tools import FileToolsMixin
+from gaia.agents.tools.file_monitor_tools import FileToolsMixin
from gaia.agents.tools.file_tools import FileSearchToolsMixin
from gaia.agents.tools.rag_tools import RAGToolsMixin
from gaia.agents.tools.shell_tools import ShellToolsMixin
diff --git a/docs/sdk/agents/specialized.mdx b/docs/sdk/agents/specialized.mdx
index 576db7d0d..e848bd9d7 100644
--- a/docs/sdk/agents/specialized.mdx
+++ b/docs/sdk/agents/specialized.mdx
@@ -3,11 +3,11 @@ title: "Specialized"
---
- **Source Code:** [`src/gaia/agents/chat/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/), [`src/gaia/agents/docker/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/docker/), [`src/gaia/agents/jira/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/jira/), [`src/gaia/agents/blender/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/blender/)
+ **Source Code:** [`hub/agents/python/chat/gaia_agent_chat/`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/), [`src/gaia/agents/docker/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/docker/), [`src/gaia/agents/jira/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/jira/), [`src/gaia/agents/blender/`](https://github.com/amd/gaia/blob/main/src/gaia/agents/blender/)
-**Import:** `from gaia.agents.chat.agent import ChatAgent`
+**Import:** `from gaia_agent_chat.agent import ChatAgent`
---
@@ -19,7 +19,7 @@ title: "Specialized"
**Purpose:** General-purpose conversational agent with file operations, RAG, and shell command capabilities.
```python
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# Configure the chat agent (use ChatAgentConfig; ChatAgent takes a single
# `config` argument rather than loose kwargs)
diff --git a/docs/sdk/api-reference.mdx b/docs/sdk/api-reference.mdx
index a2fa28991..da89103e1 100644
--- a/docs/sdk/api-reference.mdx
+++ b/docs/sdk/api-reference.mdx
@@ -64,7 +64,7 @@ icon: "book-open"
| Mixin | Import Path | Provides |
|-------|-------------|----------|
-| `FileToolsMixin` | `gaia.agents.chat.tools.file_tools` | Directory watching (`add_watch_directory`) |
+| `FileToolsMixin` | `gaia.agents.tools.file_monitor_tools` | Directory watching (`add_watch_directory`) |
| `FileSearchToolsMixin` | `gaia.agents.tools.file_tools` | File read/write/search (`read_file`, `write_file`, `search_file`, `search_file_content`, `browse_directory`, `get_file_info`, `analyze_data_file`, `list_recent_files`) |
| `RAGToolsMixin` | `gaia.agents.tools.rag_tools` | Document indexing & search (10 tools) |
| `ShellToolsMixin` | `gaia.agents.tools.shell_tools` | `run_shell_command` |
diff --git a/docs/sdk/mixins/database-mixin.mdx b/docs/sdk/mixins/database-mixin.mdx
index 8b0cda50a..8d9e42627 100644
--- a/docs/sdk/mixins/database-mixin.mdx
+++ b/docs/sdk/mixins/database-mixin.mdx
@@ -47,7 +47,7 @@ class MyAgent(DatabaseAgent):
```python
from gaia import DatabaseAgent
-from gaia.agents.chat.tools import RAGToolsMixin, FileToolsMixin
+from gaia.agents.tools import RAGToolsMixin, FileToolsMixin
class MyAgent(DatabaseAgent, RAGToolsMixin, FileToolsMixin):
"""Agent with database, RAG, and file tools."""
diff --git a/docs/sdk/mixins/tool-mixins.mdx b/docs/sdk/mixins/tool-mixins.mdx
index 24e6abbb9..82188fb24 100644
--- a/docs/sdk/mixins/tool-mixins.mdx
+++ b/docs/sdk/mixins/tool-mixins.mdx
@@ -10,11 +10,11 @@ icon: "puzzle-piece"
- **Source Code:** [`src/gaia/agents/chat/tools/`](https://github.com/amd/gaia/tree/main/src/gaia/agents/chat/tools/), [`src/gaia/agents/code/tools/`](https://github.com/amd/gaia/tree/main/src/gaia/agents/code/tools/)
+ **Source Code:** [`src/gaia/agents/tools/`](https://github.com/amd/gaia/tree/main/src/gaia/agents/tools/), [`src/gaia/agents/code/tools/`](https://github.com/amd/gaia/tree/main/src/gaia/agents/code/tools/)
-**Import:** `from gaia.agents.chat.tools.file_tools import FileToolsMixin`
+**Import:** `from gaia.agents.tools.file_monitor_tools import FileToolsMixin`
---
@@ -33,7 +33,7 @@ for new documents; it does not register read/write/list tools. Use
```python
from gaia.agents.base.agent import Agent
-from gaia.agents.chat.tools.file_tools import FileToolsMixin
+from gaia.agents.tools.file_monitor_tools import FileToolsMixin
class MyAgent(Agent, FileToolsMixin):
"""Agent that can watch directories for new files."""
diff --git a/docs/sdk/sdks/agent-ui.mdx b/docs/sdk/sdks/agent-ui.mdx
index 5d7885589..424981827 100644
--- a/docs/sdk/sdks/agent-ui.mdx
+++ b/docs/sdk/sdks/agent-ui.mdx
@@ -1381,7 +1381,7 @@ def test_message_ordering():
The Agent UI server delegates to the GAIA [Agent](/sdk/core/agent-system) for LLM communication and tool execution:
```python
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# The server creates a ChatAgent instance per request
config = ChatAgentConfig(
diff --git a/docs/sdk/sdks/rag.mdx b/docs/sdk/sdks/rag.mdx
index 1f9fb37bd..694a333fc 100644
--- a/docs/sdk/sdks/rag.mdx
+++ b/docs/sdk/sdks/rag.mdx
@@ -467,7 +467,7 @@ Think of the difference like this:
GAIA's agents use RAG as a **tool**, not just a pipeline. This is the key architectural difference:
```python
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# Create an agent with RAG capability
config = ChatAgentConfig(
diff --git a/docs/spec/browser-tools.mdx b/docs/spec/browser-tools.mdx
index fbe626232..511e2ed34 100644
--- a/docs/spec/browser-tools.mdx
+++ b/docs/spec/browser-tools.mdx
@@ -543,7 +543,7 @@ Single phase — this is a focused, self-contained feature.
- `WebClient` utility class (rate limiting, timeouts, extraction)
- `BrowserToolsMixin` with `register_browser_tools()` containing 4 tools
- [ ] Update `src/gaia/agents/tools/__init__.py` to export `BrowserToolsMixin`
-- [ ] Update `src/gaia/agents/chat/agent.py`:
+- [ ] Update `hub/agents/python/chat/gaia_agent_chat/agent.py`:
- Add `BrowserToolsMixin` to class MRO
- Add `enable_browser` + config fields to `ChatAgentConfig`
- Initialize `WebClient` in `__init__`
diff --git a/docs/spec/chat-agent.mdx b/docs/spec/chat-agent.mdx
index ad9d6ec6d..50138771e 100644
--- a/docs/spec/chat-agent.mdx
+++ b/docs/spec/chat-agent.mdx
@@ -3,12 +3,12 @@ title: "ChatAgent"
---
- **Source Code:** [`src/gaia/agents/chat/agent.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/agent.py)
+ **Source Code:** [`hub/agents/python/chat/gaia_agent_chat/agent.py`](https://github.com/amd/gaia/blob/main/hub/agents/python/chat/gaia_agent_chat/agent.py)
**Component:** ChatAgent - Reference Conversational AI Implementation
-**Module:** `gaia.agents.chat.agent`
+**Module:** `gaia_agent_chat.agent`
**Inherits:** Agent, RAGToolsMixin, FileToolsMixin, ShellToolsMixin, FileSearchToolsMixin, FileIOToolsMixin, VLMToolsMixin, ScreenshotToolsMixin, SDToolsMixin, MCPClientMixin
> **Upcoming rename (v0.20.0):** `ChatAgent` will be renamed to `GaiaAgent` as part of the broader branding update (#696). The import path and class name will change; all other behavior remains the same.
@@ -422,7 +422,7 @@ chat = [
### Example 1: Basic Chat with RAG
```python
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
# Configure agent
config = ChatAgentConfig(
diff --git a/docs/spec/file-change-handler.mdx b/docs/spec/file-change-handler.mdx
index bc9eb704c..81889d286 100644
--- a/docs/spec/file-change-handler.mdx
+++ b/docs/spec/file-change-handler.mdx
@@ -489,7 +489,7 @@ class AutoIndexAgent(Agent):
### Extraction from ChatAgent (Completed)
-`FileChangeHandler` was originally embedded in `src/gaia/agents/chat/agent.py` as a tightly-coupled inner class. It has been extracted to `src/gaia/utils/file_watcher.py` as a generic, callback-based implementation. ChatAgent now imports it from there:
+`FileChangeHandler` was originally embedded in `hub/agents/python/chat/gaia_agent_chat/agent.py` as a tightly-coupled inner class. It has been extracted to `src/gaia/utils/file_watcher.py` as a generic, callback-based implementation. ChatAgent now imports it from there:
```python
from gaia.utils.file_watcher import FileChangeHandler
diff --git a/docs/spec/file-system-agent.mdx b/docs/spec/file-system-agent.mdx
index 31d489fbf..10bdaf803 100644
--- a/docs/spec/file-system-agent.mdx
+++ b/docs/spec/file-system-agent.mdx
@@ -1747,7 +1747,7 @@ class ScratchpadToolsMixin:
### 7.3 ChatAgent Integration
```python
-# src/gaia/agents/chat/agent.py
+# hub/agents/python/chat/gaia_agent_chat/agent.py
class ChatAgent(
Agent,
diff --git a/docs/spec/file-tools-mixin.mdx b/docs/spec/file-tools-mixin.mdx
index c87468305..5cb43d204 100644
--- a/docs/spec/file-tools-mixin.mdx
+++ b/docs/spec/file-tools-mixin.mdx
@@ -3,13 +3,13 @@ title: "FileToolsMixin"
---
- **Source Code:** [`src/gaia/agents/chat/tools/file_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tools/file_tools.py)
+ **Source Code:** [`src/gaia/agents/tools/file_monitor_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/tools/file_monitor_tools.py)
**Component:** FileToolsMixin
-**Module:** `gaia.agents.chat.tools.file_tools`
-**Import:** `from gaia.agents.chat.tools.file_tools import FileToolsMixin`
+**Module:** `gaia.agents.tools.file_monitor_tools`
+**Import:** `from gaia.agents.tools.file_monitor_tools import FileToolsMixin`
---
@@ -73,7 +73,7 @@ FileToolsMixin provides directory monitoring capabilities for the Chat Agent, en
### File Location
```
-src/gaia/agents/chat/tools/file_tools.py
+src/gaia/agents/tools/file_monitor_tools.py
```
### Public Interface
@@ -228,7 +228,7 @@ def add_watch_directory(directory: str) -> Dict[str, Any]:
```python
import pytest
from pathlib import Path
-from gaia.agents.chat.tools.file_tools import FileToolsMixin
+from gaia.agents.tools.file_monitor_tools import FileToolsMixin
from gaia.agents.base import Agent
class MockChatAgent(Agent, FileToolsMixin):
@@ -341,7 +341,7 @@ def test_add_watch_directory_auto_save_called(tmp_path, monkeypatch):
def test_file_tools_mixin_integration():
"""Test FileToolsMixin can be mixed into Chat Agent."""
- from gaia.agents.chat import ChatAgent
+ from gaia_agent_chat.agent import ChatAgent
# Verify ChatAgent has FileToolsMixin
assert hasattr(ChatAgent, "register_file_tools")
@@ -426,7 +426,7 @@ except Exception as e:
### Example 1: Adding Watch Directory
```python
-from gaia.agents.chat import ChatAgent
+from gaia_agent_chat.agent import ChatAgent
# Create Chat Agent
agent = ChatAgent(model="Qwen3-0.6B-GGUF")
@@ -487,7 +487,7 @@ FileToolsMixin requires a PathValidator instance for security:
```python
# In ChatAgent.__init__()
-from gaia.agents.chat.path_validator import PathValidator
+from gaia.security import PathValidator
self.path_validator = PathValidator(
session_manager=self.session_manager,
diff --git a/docs/spec/rag-tools-mixin.mdx b/docs/spec/rag-tools-mixin.mdx
index 8d6e4e4d0..05671dfe5 100644
--- a/docs/spec/rag-tools-mixin.mdx
+++ b/docs/spec/rag-tools-mixin.mdx
@@ -3,7 +3,7 @@ title: "RAGToolsMixin"
---
- **Source Code:** [`src/gaia/agents/chat/tools/rag_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tools/rag_tools.py)
+ **Source Code:** [`src/gaia/agents/tools/rag_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/tools/rag_tools.py)
@@ -102,7 +102,7 @@ RAGToolsMixin provides comprehensive document retrieval and query capabilities f
### File Location
```
-src/gaia/agents/chat/tools/rag_tools.py
+src/gaia/agents/tools/rag_tools.py
```
### Public Interface
diff --git a/docs/spec/shell-tools-mixin.mdx b/docs/spec/shell-tools-mixin.mdx
index 1dbb6e8f7..8726fa7ea 100644
--- a/docs/spec/shell-tools-mixin.mdx
+++ b/docs/spec/shell-tools-mixin.mdx
@@ -3,7 +3,7 @@ title: "ShellToolsMixin"
---
- **Source Code:** [`src/gaia/agents/chat/tools/shell_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/chat/tools/shell_tools.py)
+ **Source Code:** [`src/gaia/agents/tools/shell_tools.py`](https://github.com/amd/gaia/blob/main/src/gaia/agents/tools/shell_tools.py)
@@ -86,7 +86,7 @@ ShellToolsMixin provides secure shell command execution with comprehensive rate
### File Location
```
-src/gaia/agents/chat/tools/shell_tools.py
+src/gaia/agents/tools/shell_tools.py
```
### Public Interface
diff --git a/hub/agents/python/chat/README.md b/hub/agents/python/chat/README.md
new file mode 100644
index 000000000..9c2cab62b
--- /dev/null
+++ b/hub/agents/python/chat/README.md
@@ -0,0 +1,24 @@
+# gaia-agent-chat
+
+Standalone GAIA agent — the conversational ChatAgent, shipped under three prompt
+profiles: `chat` (general conversation), `doc` (document Q&A with RAG), and
+`file` (file-system navigation/search). Depends on the published `amd-gaia`
+framework wheel.
+
+## Install
+
+```bash
+pip install gaia-agent-chat # from PyPI (once published)
+pip install -e hub/agents/python/chat # editable, for development
+```
+
+Installing registers the `chat`, `doc`, and `file` agents via the `gaia.agent`
+entry-point group; the GAIA registry discovers them automatically, so
+`gaia chat` (including `gaia chat --ui`) resolves the agent through the registry.
+
+## Develop / test
+
+```bash
+pip install -e ".[test]"
+pytest hub/agents/python/chat/tests/ -x
+```
diff --git a/hub/agents/python/chat/gaia-agent.yaml b/hub/agents/python/chat/gaia-agent.yaml
new file mode 100644
index 000000000..c4423a3ef
--- /dev/null
+++ b/hub/agents/python/chat/gaia-agent.yaml
@@ -0,0 +1,32 @@
+id: chat
+name: Chat
+version: 0.1.0
+description: "GAIA chat agent — conversation, document Q&A (RAG), and file-system profiles"
+author: AMD
+license: MIT
+
+category: conversation
+tags: [chat, general, personality, rag, files]
+icon: message-circle
+tools_count: 0
+
+language: python
+min_gaia_version: "0.20.0"
+models: []
+
+python:
+ entry_module: gaia_agent_chat
+ entry_class: ChatAgent
+ dependencies:
+ - "amd-gaia>=0.20.0"
+
+requirements:
+ min_memory_gb: 8
+ platforms: [win-x64, linux-x64, darwin-arm64]
+
+interfaces:
+ tui: true
+ cli: true
+ pipe: true
+ api_server: true
+ mcp_server: true
diff --git a/hub/agents/python/chat/gaia_agent_chat/__init__.py b/hub/agents/python/chat/gaia_agent_chat/__init__.py
new file mode 100644
index 000000000..d35535011
--- /dev/null
+++ b/hub/agents/python/chat/gaia_agent_chat/__init__.py
@@ -0,0 +1,147 @@
+# Copyright(C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.
+# SPDX-License-Identifier: MIT
+"""GAIA Chat agent — standalone hub package.
+
+Ships the conversational ChatAgent under three prompt profiles, each registered
+as its own agent id via the ``gaia.agent`` entry-point group:
+
+* ``chat`` — general conversation (lean prompt, no document tools)
+* ``doc`` — document Q&A with RAG
+* ``file`` — file-system navigation/search
+
+Public names are re-exported lazily so registry discovery stays cheap. The
+registry's ``_discover_installed_agents`` stamps ``source="installed"``, the
+``installed:`` namespaced id, and the namespaced-id factory wrapper.
+"""
+
+__all__ = ["build_chat", "build_doc", "build_file"]
+
+__version__ = "0.1.0"
+
+_LAZY = {
+ "ChatAgent": "agent",
+ "ChatAgentConfig": "agent",
+ "ChatAgentLite": "lite_agent",
+}
+
+
+def __getattr__(name):
+ if name in _LAZY:
+ import importlib
+
+ module = importlib.import_module(f"gaia_agent_chat.{_LAZY[name]}")
+ return getattr(module, name)
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
+def _make_factory(profile, extra=None, tiers=None):
+ """ChatAgent factory honouring a ``model_tier`` kwarg (#1162)."""
+ import dataclasses
+
+ from gaia.agents.registry import _select_tier_model
+
+ _extra = dict(extra or {})
+ _tiers = list(tiers or [])
+
+ def factory(**kwargs):
+ tier = kwargs.pop("model_tier", None)
+ if tier:
+ preset = _select_tier_model(_tiers, tier)
+ if preset:
+ kwargs.setdefault("model_id", preset)
+
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+
+ valid_fields = {f.name for f in dataclasses.fields(ChatAgentConfig)}
+ filtered = {k: v for k, v in kwargs.items() if k in valid_fields}
+ filtered.setdefault("prompt_profile", profile)
+ for k, v in _extra.items():
+ filtered.setdefault(k, v)
+ return ChatAgent(config=ChatAgentConfig(**filtered))
+
+ return factory
+
+
+def build_chat():
+ """Return the :class:`AgentRegistration` for the ``chat`` profile."""
+ from gaia.agents.registry import AgentRegistration, build_model_tiers
+
+ tiers = build_model_tiers("Full")
+ return AgentRegistration(
+ id="chat",
+ name="Chat",
+ description="General conversation — fast, personality-first, no document tools",
+ source="installed",
+ conversation_starters=[
+ "What can you help me with?",
+ "Tell me about yourself",
+ "What's new today?",
+ ],
+ factory=_make_factory("chat", tiers=tiers),
+ agent_dir=None,
+ models=[],
+ required_connections=[],
+ # Mirrors ChatAgent.CONSUMES_MCP_SERVERS — the lazy factory must not
+ # import the chat module at discovery time. A guard test keeps these
+ # in sync.
+ consumes_mcp_servers=True,
+ category="conversation",
+ tags=["chat", "general", "personality"],
+ icon="message-circle",
+ tools_count=0,
+ model_tiers=tiers,
+ )
+
+
+def build_doc():
+ """Return the :class:`AgentRegistration` for the ``doc`` profile."""
+ from gaia.agents.registry import AgentRegistration, build_model_tiers
+
+ tiers = build_model_tiers("Full")
+ return AgentRegistration(
+ id="doc",
+ name="Doc Agent",
+ description="Document Q&A with RAG — ask questions about PDFs, reports, and manuals",
+ source="installed",
+ conversation_starters=[
+ "Search my documents for...",
+ "Summarize this document",
+ "What does the report say about...",
+ ],
+ factory=_make_factory("doc", tiers=tiers),
+ agent_dir=None,
+ models=[],
+ required_connections=[],
+ category="documents",
+ tags=["rag", "files", "search", "mcp"],
+ icon="file-text",
+ tools_count=15,
+ model_tiers=tiers,
+ )
+
+
+def build_file():
+ """Return the :class:`AgentRegistration` for the ``file`` profile."""
+ from gaia.agents.registry import AgentRegistration, build_model_tiers
+
+ tiers = build_model_tiers("Full")
+ return AgentRegistration(
+ id="file",
+ name="File Agent",
+ description="File system navigation, search, and analysis",
+ source="installed",
+ conversation_starters=[
+ "Find files related to...",
+ "What's in my Documents folder?",
+ "Show me the project structure",
+ ],
+ factory=_make_factory("file", extra={"enable_filesystem": True}, tiers=tiers),
+ agent_dir=None,
+ models=[],
+ required_connections=[],
+ category="productivity",
+ tags=["files", "search", "filesystem", "shell"],
+ icon="folder-search",
+ tools_count=10,
+ model_tiers=tiers,
+ )
diff --git a/src/gaia/agents/chat/agent.py b/hub/agents/python/chat/gaia_agent_chat/agent.py
similarity index 99%
rename from src/gaia/agents/chat/agent.py
rename to hub/agents/python/chat/gaia_agent_chat/agent.py
index 52d343b53..1ff3ed777 100644
--- a/src/gaia/agents/chat/agent.py
+++ b/hub/agents/python/chat/gaia_agent_chat/agent.py
@@ -16,20 +16,26 @@
except ImportError:
Observer = None
+from gaia_agent_chat.session import SessionManager
+from gaia_agent_chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
+
from gaia.agents.base.agent import Agent, default_max_steps
from gaia.agents.base.console import AgentConsole
from gaia.agents.base.memory import EMBEDDING_MODEL, MemoryMixin
+
+# dynamic_tools_env_override is re-exported so callers importing it from
+# gaia_agent_chat.agent keep working; its canonical home is the core tool_loader
+# so the UI settings router shares it without depending on this wheel (#1102).
+from gaia.agents.base.tool_loader import dynamic_tools_env_override # noqa: F401
from gaia.agents.base.tool_loader import ToolLoader
from gaia.agents.base.tools import _TOOL_REGISTRY
-from gaia.agents.chat.session import SessionManager
-from gaia.agents.chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
-from gaia.agents.chat.tools import FileToolsMixin
from gaia.agents.tools import FileSystemToolsMixin # Enhanced file system navigation
from gaia.agents.tools import ScratchpadToolsMixin # Structured data analysis
from gaia.agents.tools import ( # Web browsing and search; Shared tools
BrowserToolsMixin,
FileIOToolsMixin,
FileSearchToolsMixin,
+ FileToolsMixin,
RAGToolsMixin,
ScreenshotToolsMixin,
ShellToolsMixin,
@@ -46,21 +52,6 @@
logger = get_logger(__name__)
-def dynamic_tools_env_override() -> Optional[bool]:
- """Parse the ``GAIA_DYNAMIC_TOOLS`` override, or ``None`` when it is unset.
-
- Returns the parsed boolean (truthy set ``1``/``true``/``yes``/``on``,
- case-insensitive) when the env var is set, else ``None`` to signal "no
- override — fall back to the persisted/config value". The UI settings
- router reuses this so the env-wins precedence and the truthy set never
- drift between the agent resolver and the toggle that surfaces it.
- """
- raw = os.getenv("GAIA_DYNAMIC_TOOLS")
- if raw is None:
- return None
- return raw.strip().lower() in ("1", "true", "yes", "on")
-
-
@dataclass
class ChatAgentConfig:
"""Configuration for ChatAgent."""
diff --git a/src/gaia/agents/chat/app.py b/hub/agents/python/chat/gaia_agent_chat/app.py
similarity index 99%
rename from src/gaia/agents/chat/app.py
rename to hub/agents/python/chat/gaia_agent_chat/app.py
index 45d8acf26..49143145e 100644
--- a/src/gaia/agents/chat/app.py
+++ b/hub/agents/python/chat/gaia_agent_chat/app.py
@@ -9,7 +9,7 @@
import sys
from pathlib import Path
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
from gaia.logger import get_logger
logger = get_logger(__name__)
diff --git a/src/gaia/agents/chat/lite_agent.py b/hub/agents/python/chat/gaia_agent_chat/lite_agent.py
similarity index 100%
rename from src/gaia/agents/chat/lite_agent.py
rename to hub/agents/python/chat/gaia_agent_chat/lite_agent.py
diff --git a/src/gaia/agents/chat/session.py b/hub/agents/python/chat/gaia_agent_chat/session.py
similarity index 100%
rename from src/gaia/agents/chat/session.py
rename to hub/agents/python/chat/gaia_agent_chat/session.py
diff --git a/src/gaia/agents/chat/tool_bundles.py b/hub/agents/python/chat/gaia_agent_chat/tool_bundles.py
similarity index 100%
rename from src/gaia/agents/chat/tool_bundles.py
rename to hub/agents/python/chat/gaia_agent_chat/tool_bundles.py
diff --git a/hub/agents/python/chat/pyproject.toml b/hub/agents/python/chat/pyproject.toml
new file mode 100644
index 000000000..2303a276c
--- /dev/null
+++ b/hub/agents/python/chat/pyproject.toml
@@ -0,0 +1,24 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "gaia-agent-chat"
+version = "0.1.0"
+description = "GAIA chat agent — conversation, document Q&A (RAG), and file-system profiles"
+authors = [{ name = "AMD" }]
+license = { text = "MIT" }
+readme = "README.md"
+requires-python = ">=3.10"
+dependencies = ["amd-gaia>=0.20.0"]
+
+[project.entry-points."gaia.agent"]
+chat = "gaia_agent_chat:build_chat"
+doc = "gaia_agent_chat:build_doc"
+file = "gaia_agent_chat:build_file"
+
+[project.optional-dependencies]
+test = ["pytest"]
+
+[tool.setuptools.packages.find]
+include = ["gaia_agent_chat*"]
diff --git a/hub/agents/python/chat/tests/test_chat_agent.py b/hub/agents/python/chat/tests/test_chat_agent.py
new file mode 100644
index 000000000..e60db009c
--- /dev/null
+++ b/hub/agents/python/chat/tests/test_chat_agent.py
@@ -0,0 +1,79 @@
+# Copyright(C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.
+# SPDX-License-Identifier: MIT
+"""Smoke tests for the standalone gaia-agent-chat package.
+
+ChatAgent ships under three prompt-profile ids — ``chat``/``doc``/``file`` —
+each registered via its own ``gaia.agent`` entry point (#1102). These tests
+assert the registration shape, lazy re-exports, and registry discovery without
+constructing a full agent (which would need a live RAG/LLM backend).
+"""
+
+
+def test_build_chat_registration_shape():
+ import gaia_agent_chat as m
+
+ reg = m.build_chat()
+ assert reg.id == "chat"
+ assert reg.source == "installed"
+ # The chat profile loads MCP servers dynamically, so the registration must
+ # advertise that to the connectors activation panel (#1005).
+ assert reg.consumes_mcp_servers is True
+ tier_names = [t.name for t in reg.model_tiers]
+ assert tier_names == ["full", "lite"]
+
+
+def test_build_doc_registration_shape():
+ import gaia_agent_chat as m
+
+ reg = m.build_doc()
+ assert reg.id == "doc"
+ assert reg.source == "installed"
+ tier_names = [t.name for t in reg.model_tiers]
+ assert tier_names == ["full", "lite"]
+
+
+def test_build_file_registration_shape():
+ import gaia_agent_chat as m
+
+ reg = m.build_file()
+ assert reg.id == "file"
+ assert reg.source == "installed"
+ tier_names = [t.name for t in reg.model_tiers]
+ assert tier_names == ["full", "lite"]
+
+
+def test_lazy_reexports():
+ from gaia_agent_chat import ChatAgent, ChatAgentConfig, ChatAgentLite
+
+ assert ChatAgent is not None
+ assert ChatAgentConfig is not None
+ assert ChatAgentLite is not None
+
+
+def test_config_defaults():
+ from gaia_agent_chat import ChatAgentConfig
+
+ cfg = ChatAgentConfig()
+ assert cfg.use_claude is False
+ assert cfg.model_id is None
+ assert cfg.prompt_profile == "full"
+
+
+def test_discovered_when_installed():
+ from gaia.agents.registry import AgentRegistry
+
+ reg = AgentRegistry()
+ reg.discover()
+ ids = {a.id for a in reg.list()}
+ assert {"chat", "doc", "file"} <= ids
+
+
+def test_discovery_stamps_installed_namespace():
+ from gaia.agents.registry import AgentRegistry
+
+ reg = AgentRegistry()
+ reg.discover()
+ chat = reg.get("chat")
+ assert chat is not None
+ assert chat.source == "installed"
+ assert chat.namespaced_agent_id == "installed:chat"
diff --git a/hub/agents/python/docqa/gaia_agent_docqa/agent.py b/hub/agents/python/docqa/gaia_agent_docqa/agent.py
index 27be61dc9..d82e8e3fa 100644
--- a/hub/agents/python/docqa/gaia_agent_docqa/agent.py
+++ b/hub/agents/python/docqa/gaia_agent_docqa/agent.py
@@ -4,8 +4,12 @@
from typing import List, Optional
from gaia.agents.base.agent import Agent, default_max_steps
-from gaia.agents.chat.tools import FileToolsMixin
-from gaia.agents.tools import FileIOToolsMixin, FileSearchToolsMixin, RAGToolsMixin
+from gaia.agents.tools import (
+ FileIOToolsMixin,
+ FileSearchToolsMixin,
+ FileToolsMixin,
+ RAGToolsMixin,
+)
from gaia.mcp.mixin import MCPClientMixin
diff --git a/setup.py b/setup.py
index 6954944c8..71aaa395b 100644
--- a/setup.py
+++ b/setup.py
@@ -51,8 +51,6 @@
"gaia.agents",
"gaia.agents.base",
"gaia.agents.tools",
- "gaia.agents.chat",
- "gaia.agents.chat.tools",
"gaia.agents.builder",
"gaia.agents.code_index",
"gaia.agents.code_index.tools",
@@ -271,6 +269,7 @@
"agent-email": ["gaia-agent-email"],
"agent-docqa": ["gaia-agent-docqa"],
"agent-routing": ["gaia-agent-routing"],
+ "agent-chat": ["gaia-agent-chat"],
"agents": [
"gaia-agent-summarize",
"gaia-agent-sd",
@@ -286,6 +285,7 @@
"gaia-agent-email",
"gaia-agent-docqa",
"gaia-agent-routing",
+ "gaia-agent-chat",
],
},
classifiers=[
diff --git a/src/gaia/agents/base/errors.py b/src/gaia/agents/base/errors.py
index 7633054da..95e689b2b 100644
--- a/src/gaia/agents/base/errors.py
+++ b/src/gaia/agents/base/errors.py
@@ -18,11 +18,14 @@
FRAMEWORK_PATHS: Set[str] = {
"gaia/agents/base",
"gaia/agents/blender",
- "gaia/agents/chat",
"gaia/agents/code",
"gaia/agents/docker",
"gaia/agents/jira",
"gaia/agents/tools",
+ # Hub-migrated agents (#1102): wheel installs land under site-packages
+ # (covered below); editable hub checkouts show as hub/agents/python/.
+ "hub/agents/python/",
+ "gaia_agent_chat",
"site-packages/",
}
diff --git a/src/gaia/agents/base/tool_loader.py b/src/gaia/agents/base/tool_loader.py
index cf1ff0553..02cd459c3 100644
--- a/src/gaia/agents/base/tool_loader.py
+++ b/src/gaia/agents/base/tool_loader.py
@@ -50,6 +50,7 @@
import hashlib
import json
import logging
+import os
import time
from dataclasses import dataclass, field
from typing import Callable, Dict, FrozenSet, List, Optional, Sequence
@@ -71,6 +72,21 @@
DEFAULT_MAX_TOOLS = 14
+def dynamic_tools_env_override() -> Optional[bool]:
+ """Parse the ``GAIA_DYNAMIC_TOOLS`` override, or ``None`` when it is unset.
+
+ Returns the parsed boolean (truthy set ``1``/``true``/``yes``/``on``,
+ case-insensitive) when the env var is set, else ``None`` to signal "no
+ override — fall back to the persisted/config value". Lives in the core
+ tool-loader module so the UI settings router and the (hub-packaged) chat
+ agent share one truthy set without the router depending on the chat wheel.
+ """
+ raw = os.getenv("GAIA_DYNAMIC_TOOLS")
+ if raw is None:
+ return None
+ return raw.strip().lower() in ("1", "true", "yes", "on")
+
+
@dataclass(frozen=True)
class ToolBundle:
"""An immutable cohesion group of tools — no activation policy.
diff --git a/src/gaia/agents/chat/README.md b/src/gaia/agents/chat/README.md
deleted file mode 100644
index f1c6cc567..000000000
--- a/src/gaia/agents/chat/README.md
+++ /dev/null
@@ -1,656 +0,0 @@
-# Chat Agent - Intelligent RAG-Powered Assistant
-
-[](IMPLEMENTATION.md)
-[](USAGE_GUIDE.md#security-features)
-[](IMPLEMENTATION.md#performance-benchmarks)
-
-## 🎯 Overview
-
-The Chat Agent is a **production-ready**, autonomous assistant that combines conversational AI with advanced document retrieval (RAG), featuring enterprise-grade security, semantic chunking, and intelligent decision-making.
-
-### 🆕 What's New (v2.0.0 - January 2025)
-
-**Security Hardening:**
-- ✅ Fixed TOCTOU vulnerability with O_NOFOLLOW
-- ✅ Shell command whitelist (blocks 90% more attacks)
-- ✅ Rate limiting (10 cmd/min, 3 cmd/10sec)
-- ✅ Path validation hardened
-
-**Performance Optimization:**
-- ⚡ **10x faster** per-file searches (cached embeddings)
-- ⚡ **100x faster** document removal (O(N²) → O(N×M))
-- ⚡ **85% better** answer quality (semantic chunking)
-- ⚡ Content-based cache (no stale data)
-
-**New Features:**
-- 📊 Comprehensive metadata tracking
-- 📈 File operation telemetry
-- ⏱️ Progress reporting for compute ops
-- 🧹 Session auto-cleanup (30-day TTL)
-- 📏 Document size limits (100MB default)
-- 🔄 Auto-handles file deletions
-
-**Reliability:**
-- 🛠️ Fixed summarize_document crash
-- 🔁 Connection retry with exponential backoff
-- 🎯 Graceful degradation on failures
-- ✅ Consistent error handling
-
-**See detailed documentation:**
-- [USAGE_GUIDE.md](USAGE_GUIDE.md) - How to use all features
-- [IMPLEMENTATION.md](IMPLEMENTATION.md) - Technical deep dive
-- [FIXES_APPLIED.md](FIXES_APPLIED.md) - All fixes documented
-
-The Chat Agent is an autonomous, intelligent assistant that combines free form conversation with document retrieval (RAG), file search, and iterative refinement capabilities. It makes smart decisions about when to retrieve information vs. when to use its general knowledge.
-
-## Key Features
-
-### 1. **Autonomous Decision-Making**
-- Agent decides when retrieval is needed
-- Distinguishes between general questions and document-specific queries
-- Fast responses for casual conversation
-- Intelligent retrieval for factual queries
-
-### 2. **Granular Retrieval Control**
-- **Broad Search**: Query all documents (`query_documents`)
-- **Targeted Search**: Query specific files (`query_specific_file`)
-- **Exact Match**: Search for specific text (`search_file_content`)
-- **File Discovery**: Find files by pattern (`search_files`)
-
-### 3. **Iterative Retrieval**
-- Evaluate retrieved information quality
-- Decide if more retrieval is needed
-- Combine multiple retrieval strategies
-- Refine answers iteratively
-
-### 4. **High Performance**
-- Fast streaming responses
-- Efficient chunk retrieval
-- Targeted queries when possible
-- Parallel search key generation
-
-### 5. **Security & Control**
-- Path validation for file operations
-- Configurable allowed directories
-- Auto-indexing with directory monitoring
-- Safe file system access
-
-## Architecture
-
-```
-User Query → Agent Analysis
- ↓
- Decision: Retrieve or Answer?
- ↓
- ┌─────────┴─────────┐
- ↓ ↓
-Answer Directly Retrieval Strategy
- ↓
- ┌───────┼───────┐
- ↓ ↓ ↓
- Broad Targeted Exact
- Query Query Search
- ↓ ↓ ↓
- Chunks Retrieved
- ↓
- Evaluate Sufficiency
- ↓
- ┌──────────┴──────────┐
- ↓ ↓
- Sufficient Insufficient
- ↓ ↓
- Generate Answer More Retrieval
-```
-
-## Tools Reference
-
-### Query Tools
-
-#### `query_documents` - Broad Search
-Query ALL indexed documents for information.
-
-**Use when:**
-- User asks about general topics across documents
-- Unsure which document contains info
-- Need comprehensive coverage
-
-**Example:**
-```json
-{
- "tool": "query_documents",
- "tool_args": {
- "query": "What are the key features?"
- }
-}
-```
-
-#### `query_specific_file` - Targeted Search (FAST)
-Query ONE specific document by name.
-
-**Use when:**
-- User mentions a specific file
-- Need fast, focused retrieval
-- Previous answer insufficient
-
-**Example:**
-```json
-{
- "tool": "query_specific_file",
- "tool_args": {
- "file_name": "manual.pdf",
- "query": "How to install?"
- }
-}
-```
-
-#### `search_file_content` - Exact Match (FASTEST)
-Search for exact text or keywords (grep-like).
-
-**Use when:**
-- Looking for specific terms/phrases
-- Need exact matches
-- Fastest option for known keywords
-
-**Example:**
-```json
-{
- "tool": "search_file_content",
- "tool_args": {
- "pattern": "API key configuration"
- }
-}
-```
-
-### Evaluation Tool
-
-#### `evaluate_retrieval` - Quality Check
-Evaluate if retrieved information is sufficient.
-
-**Use when:**
-- Before providing final answer
-- Answer seems incomplete
-- Deciding if more retrieval needed
-
-**Example:**
-```json
-{
- "tool": "evaluate_retrieval",
- "tool_args": {
- "question": "How to configure the system?",
- "retrieved_info": "The system requires API keys..."
- }
-}
-```
-
-**Returns:**
-- `sufficient`: true/false
-- `confidence`: high/medium/low
-- `recommendation`: Next steps
-- `keyword_overlap`: Match quality score
-
-### File Management Tools
-
-#### `index_document` - Add to Index
-Add a new document to the RAG index.
-
-#### `list_indexed_documents` - Show Indexed Files
-List all currently indexed documents.
-
-#### `search_files` - Find Files
-Search for files by name pattern.
-
-#### `add_watch_directory` - Auto-Index
-Monitor directory for new/modified files.
-
-#### `rag_status` - System Status
-Get RAG system status and statistics.
-
-## Usage Examples
-
-### Basic Usage
-
-```bash
-# Interactive mode with documents
-python -m gaia.agents.chat.app --index doc1.pdf doc2.pdf
-
-# With directory watching
-python -m gaia.agents.chat.app --watch /data/docs
-
-# With security (allowed paths)
-python -m gaia.agents.chat.app \
- --allowed-paths /data /documents \
- --watch /data
-
-# Single query mode
-python -m gaia.agents.chat.app \
- --index manual.pdf \
- --query "How do I configure the API?"
-```
-
-### Programmatic Usage
-
-```python
-from gaia.agents.chat.agent import ChatAgent
-
-# Create agent
-agent = ChatAgent(
- rag_documents=["manual.pdf", "guide.pdf"],
- watch_directories=["/data/docs"],
- allowed_paths=["/data", "/documents"],
- chunk_size=500,
- max_chunks=3,
- streaming=True # Enable fast streaming
-)
-
-# Freeform conversation (no retrieval)
-result = agent.process_query("Hello, how are you?")
-# Agent answers directly without retrieval
-
-# Document-specific query (triggers retrieval)
-result = agent.process_query("What does the manual say about installation?")
-# Agent uses query_specific_file for fast targeted search
-
-# Cleanup
-agent.stop_watching()
-```
-
-## Decision-Making Guide
-
-The agent follows these decision rules:
-
-### When to Retrieve
-
-✅ User asks about document content:
-- "What does the manual say about...?"
-- "According to the document..."
-- "In the file X..."
-
-✅ Query mentions specific files:
-- "Check manual.pdf for..."
-- "What's in guide.pdf?"
-
-✅ Needs factual verification:
-- "What are the exact requirements?"
-- "Show me the specifications"
-
-### When NOT to Retrieve
-
-❌ General knowledge:
-- "What is machine learning?"
-- "Explain how encryption works"
-
-❌ Casual conversation:
-- "Hello!"
-- "How are you?"
-
-❌ Agent capabilities:
-- "What can you do?"
-- "How does RAG work?"
-
-## Performance Optimization
-
-### Speed Hierarchy (Fastest → Slowest)
-
-1. **No Retrieval** - Direct answer (~instant)
-2. **`search_file_content`** - Exact text match (~100ms)
-3. **`query_specific_file`** - One file RAG (~200ms)
-4. **`query_documents`** - All files RAG (~500ms)
-
-### Best Practices
-
-1. **Use Targeted Queries**
- ```python
- # Good - specific and fast
- query_specific_file("manual.pdf", "installation")
-
- # Slower - searches all documents
- query_documents("installation")
- ```
-
-2. **Evaluate Before Expanding**
- ```python
- # Check if answer is good
- eval_result = evaluate_retrieval(question, answer)
- if not eval_result["sufficient"]:
- # Only then do more retrieval
- more_info = query_specific_file(...)
- ```
-
-3. **Use Exact Search for Known Terms**
- ```python
- # Fast exact match
- search_file_content("API_KEY")
-
- # vs slower semantic search
- query_documents("API key configuration")
- ```
-
-## Configuration
-
-### Performance Tuning
-
-```python
-agent = ChatAgent(
- chunk_size=300, # Smaller = more precise, more chunks
- max_chunks=5, # More chunks = better coverage, slower
- streaming=True, # Enable for fast user experience
- show_stats=True # Monitor performance
-)
-```
-
-### Security Configuration
-
-```python
-agent = ChatAgent(
- allowed_paths=[
- "/home/user/documents",
- "/data/public"
- ] # Only allow access to these directories
-)
-```
-
-### Directory Monitoring
-
-```python
-agent = ChatAgent(
- watch_directories=["/data/docs"], # Auto-index new files
- rag_documents=["initial.pdf"] # Index immediately
-)
-```
-
-## Integration with Evaluation Framework
-
-Use the agent eval framework to test ChatAgent behavior:
-
-```bash
-# Run agent eval scenarios for RAG quality
-gaia eval agent --category rag_quality
-```
-
-See `eval/scenarios/` for scenario YAML files and `src/gaia/eval/runner.py` for the eval runner.
-
-## Troubleshooting
-
-### Common Issues
-
-**1. Slow Retrieval**
-- Use `query_specific_file` instead of `query_documents`
-- Reduce `max_chunks`
-- Use `search_file_content` for exact matches
-
-**2. Poor Results**
-- Increase `max_chunks` for more context
-- Use `evaluate_retrieval` to check quality
-- Try multiple search strategies
-
-**3. Path Access Denied**
-- Check `allowed_paths` configuration
-- Ensure documents are in allowed directories
-
-**4. File Not Found**
-- Use `search_files` to locate file
-- Check if file is indexed with `list_indexed_documents`
-- Index file with `index_document`
-
-## Advanced Usage
-
-### Hybrid Search Strategy
-
-```python
-# 1. Try exact match first (fastest)
-exact = agent.process_query("search for 'version 2.0' in files")
-
-# 2. If not found, use semantic search
-if no_results:
- semantic = agent.process_query("query documents about version information")
-
-# 3. Combine results if needed
-```
-
-### Iterative Refinement
-
-```python
-# 1. Initial retrieval
-result1 = agent.process_query("What are the requirements?")
-
-# 2. Evaluate
-eval = agent.process_query("evaluate if requirements answer is complete")
-
-# 3. If insufficient, get more specific
-if not eval["sufficient"]:
- result2 = agent.process_query(
- "query manual.pdf specifically for system requirements"
- )
-```
-
-## 🚀 Quick Start
-
-### Installation
-
-```bash
-# Install with RAG dependencies
-pip install -e .[rag]
-
-# Or install separately
-pip install pypdf sentence-transformers faiss-cpu watchdog
-```
-
-### Basic Usage
-
-```python
-from gaia.agents.chat.agent import ChatAgent
-
-# Simple Q&A
-agent = ChatAgent(
- rag_documents=["manual.pdf", "guide.pdf"],
- show_stats=True # See progress
-)
-
-agent.run("What are the installation steps?")
-```
-
-### With Security
-
-```python
-# Production configuration
-agent = ChatAgent(
- rag_documents=["docs/manual.pdf"],
- allowed_paths=["/app/data", "/app/docs"], # Security boundary
- watch_directories=["/app/docs"], # Auto-index changes
- max_file_size_mb=100, # Size limit
- show_stats=False # Less verbose
-)
-```
-
-### CLI Usage
-
-```bash
-# Interactive mode
-python -m gaia.agents.chat.app
-
-# With documents
-python -m gaia.agents.chat.app --index doc1.pdf doc2.pdf
-
-# With file watching
-python -m gaia.agents.chat.app --watch /data/docs
-
-# With security
-python -m gaia.agents.chat.app \
- --allowed-paths /data /documents \
- --watch /data
-```
-
----
-
-## 📖 Documentation
-
-- **[README.md](README.md)** (this file) - Quick start and usage
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - System architecture and algorithms
-- **[IMPLEMENTATION.md](IMPLEMENTATION.md)** - Technical implementation details
-- **[USAGE_GUIDE.md](USAGE_GUIDE.md)** - Comprehensive usage guide
-- **[ALL_FIXES_2025_01_29.md](ALL_FIXES_2025_01_29.md)** - Complete fix log
-- **[FIXES_APPLIED.md](FIXES_APPLIED.md)** - Original P0 fixes
-- **[REVIEW.md](REVIEW.md)** - Initial code review
-- **[FINAL_CRITIQUE.md](FINAL_CRITIQUE.md)** - Critical assessment
-
----
-
-## ⚙️ Configuration Reference
-
-### Performance Tuning
-
-```python
-from gaia.rag.sdk import RAGConfig
-
-# For speed
-config = RAGConfig(
- chunk_size=300, # Smaller chunks
- max_chunks=2, # Less context
- max_indexed_files=50 # Less memory
-)
-
-# For quality
-config = RAGConfig(
- chunk_size=800, # Larger chunks
- max_chunks=10, # More context
- max_indexed_files=200 # More documents
-)
-```
-
-### Security Hardening
-
-```python
-agent = ChatAgent(
- allowed_paths=["/app/data"], # Minimal access
- watch_directories=[], # No auto-watch
- rag_documents=[], # Manual indexing only
- debug=False # No debug logs
-)
-
-# Verify whitelist
-# Default allows: ls, cat, grep, git status, etc.
-# Blocks: rm, curl, chmod, sudo, bash, etc.
-```
-
-### Memory Management
-
-```python
-config = RAGConfig(
- max_indexed_files=100, # File limit
- max_total_chunks=10000, # Chunk limit
- enable_lru_eviction=True, # Auto-evict old docs
- max_file_size_mb=100, # Size limit
- warn_file_size_mb=50 # Warning threshold
-)
-```
-
----
-
-## 🔍 Troubleshooting
-
-### "Rate limit exceeded"
-
-```python
-# Wait for specified time
-Error: Rate limit: max 3 commands per 10 seconds. Wait 5.3s
-
-# Or reduce command frequency
-```
-
-### "File too large"
-
-```python
-# Option 1: Split file
-split -l 10000 large_file.txt chunk_
-
-# Option 2: Increase limit
-RAGConfig(max_file_size_mb=200)
-
-# Option 3: Use more RAM
-```
-
-### "Access denied"
-
-```python
-# Add path to allowed_paths
-agent = ChatAgent(
- allowed_paths=[
- "/current/path",
- "/new/path" # Add this
- ]
-)
-```
-
-### "No relevant information found"
-
-```python
-# 1. Check what's indexed
-agent.run("list indexed documents")
-
-# 2. Index missing documents
-agent.run("index document /path/to/file.pdf")
-
-# 3. Try different search terms
-# Instead of: "What is X?"
-# Try: "X definition", "X features", "how X works"
-```
-
----
-
-## 📊 Performance Characteristics
-
-| Operation | Time (Cold) | Time (Cached) | Quality |
-|-----------|-------------|---------------|---------|
-| Index PDF (100pg) | ~25s | ~2s | N/A |
-| Global search | 0.3s | 0.3s | 65% |
-| Per-file search | 0.2s | 0.2s | 70% |
-| Exact match | 0.1s | 0.1s | 95% |
-| Summary | 5-10s | N/A | 60% |
-
-**Memory Usage:**
-- Default config: ~65MB
-- Light config: ~30MB
-- Heavy config: ~150MB
-
----
-
-## 🎯 Status
-
-### Production Readiness: 92/100
-
-✅ **Secure** - All P0 vulnerabilities fixed
-✅ **Fast** - Major performance optimizations
-✅ **Reliable** - Crashes eliminated, retry logic added
-✅ **Observable** - Comprehensive metadata and telemetry
-✅ **Maintainable** - Type hints, docs, consistent errors
-
-### Known Limitations
-
-- ⚠️ Test coverage needs improvement (placeholders exist)
-- ⚠️ Session files not encrypted
-- ⚠️ No multi-language sentence splitting
-
-### Recommended Use Cases
-
-✅ **Excellent for:**
-- Technical documentation Q&A
-- Code repository search
-- FAQ systems
-- Research paper analysis
-- Log file analysis
-
-⚠️ **Use with caution for:**
-- Legal documents (requires validation)
-- Medical records (compliance concerns)
-- Financial data (no encryption)
-
----
-
-## 🤝 Contributing
-
-See [ARCHITECTURE.md](ARCHITECTURE.md) for technical details and [IMPLEMENTATION.md](IMPLEMENTATION.md) for implementation specifics.
-
-## License
-
-Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
-SPDX-License-Identifier: MIT
diff --git a/src/gaia/agents/chat/__init__.py b/src/gaia/agents/chat/__init__.py
deleted file mode 100644
index f85b382bb..000000000
--- a/src/gaia/agents/chat/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
-# SPDX-License-Identifier: MIT
-"""
-Chat Agent with RAG and file search capabilities.
-"""
-
-from gaia.agents.chat.agent import ChatAgent
-
-__all__ = ["ChatAgent"]
diff --git a/src/gaia/agents/chat/tools/__init__.py b/src/gaia/agents/chat/tools/__init__.py
deleted file mode 100644
index d86be4351..000000000
--- a/src/gaia/agents/chat/tools/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
-# SPDX-License-Identifier: MIT
-"""
-Chat Agent Tools - Mixins for RAG, file operations, and shell commands.
-"""
-
-from gaia.agents.chat.tools.file_tools import FileToolsMixin
-from gaia.agents.tools.rag_tools import RAGToolsMixin
-from gaia.agents.tools.shell_tools import ShellToolsMixin
-
-__all__ = [
- "RAGToolsMixin",
- "FileToolsMixin",
- "ShellToolsMixin",
-]
diff --git a/src/gaia/agents/chat/tools/rag_tools.py b/src/gaia/agents/chat/tools/rag_tools.py
deleted file mode 100644
index 02acbbd57..000000000
--- a/src/gaia/agents/chat/tools/rag_tools.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
-# SPDX-License-Identifier: MIT
-"""
-Deprecated re-export shim.
-
-``RAGToolsMixin`` moved to ``gaia.agents.tools.rag_tools`` when shared tool
-mixins were promoted to the framework (#1396). Import from the new location.
-"""
-
-import warnings
-
-from gaia.agents.tools.rag_tools import ( # noqa: F401
- RAGToolsMixin,
- extract_page_from_chunk,
-)
-
-warnings.warn(
- "gaia.agents.chat.tools.rag_tools is deprecated; import from "
- "gaia.agents.tools.rag_tools instead.",
- DeprecationWarning,
- stacklevel=2,
-)
-
-__all__ = ["RAGToolsMixin", "extract_page_from_chunk"]
diff --git a/src/gaia/agents/chat/tools/shell_tools.py b/src/gaia/agents/chat/tools/shell_tools.py
deleted file mode 100644
index 0e68a42b1..000000000
--- a/src/gaia/agents/chat/tools/shell_tools.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
-# SPDX-License-Identifier: MIT
-"""
-Deprecated re-export shim.
-
-``ShellToolsMixin`` moved to ``gaia.agents.tools.shell_tools`` when shared
-tool mixins were promoted to the framework (#1396). Import from the new
-location.
-"""
-
-import warnings
-
-from gaia.agents.tools.shell_tools import ( # noqa: F401
- ALLOWED_COMMANDS,
- DANGEROUS_PS_PATTERNS,
- DANGEROUS_SHELL_OPERATORS,
- SAFE_GIT_COMMANDS,
- SAFE_PS_CMDLET_PREFIXES,
- ShellToolsMixin,
-)
-
-warnings.warn(
- "gaia.agents.chat.tools.shell_tools is deprecated; import from "
- "gaia.agents.tools.shell_tools instead.",
- DeprecationWarning,
- stacklevel=2,
-)
-
-__all__ = [
- "ALLOWED_COMMANDS",
- "DANGEROUS_PS_PATTERNS",
- "DANGEROUS_SHELL_OPERATORS",
- "SAFE_GIT_COMMANDS",
- "SAFE_PS_CMDLET_PREFIXES",
- "ShellToolsMixin",
-]
diff --git a/src/gaia/agents/registry.py b/src/gaia/agents/registry.py
index 157939903..155a95569 100644
--- a/src/gaia/agents/registry.py
+++ b/src/gaia/agents/registry.py
@@ -61,19 +61,13 @@
# "-lite" / ``gaia-lite`` aliases — those no longer register their own card
# (#1162) but remain reserved so a custom agent can't claim the old ID and
# shadow the alias resolution in ``_LEGACY_ID_ALIASES``.
-# Only ids that resolve to a framework *builtin* belong here. data/web (and
-# their -lite aliases) and email migrated to standalone hub wheels (#1102), so
-# they are no longer reserved builtins — they register via the gaia.agent entry
-# point.
+# Only ids that resolve to a framework *builtin* belong here. The chat/doc/file
+# profiles, data, web, email (and all their -lite / gaia-lite aliases) migrated
+# to standalone hub wheels (#1102), so they register via the gaia.agent entry
+# point and are no longer reserved builtins. ``builder`` is the only remaining
+# framework agent.
_RESERVED_BUILTIN_IDS: frozenset[str] = frozenset(
{
- "chat",
- "doc",
- "file",
- "chat-lite",
- "doc-lite",
- "file-lite",
- "gaia-lite",
"builder",
}
)
@@ -553,137 +547,13 @@ def discover(self) -> None:
def _register_builtin_agents(self) -> None:
"""Register built-in agents (ChatAgent, BuilderAgent, etc.)."""
- # --- Model-size tiers (issue #1162) ---
- # Each conversation/analysis agent exposes a "full" (its own default
- # model) and a "lite" (~4B) tier. The Agent UI renders a SINGLE card
- # per agent with a model-size selector instead of duplicate "… Lite"
- # cards. The lite model list is platform-conditional:
- # macOS: Qwen3.5-4B-GGUF (tool-calling label, OpenAI format)
- # Linux/Windows: Gemma-4-E4B-it-GGUF (tool-calling label)
- # Shared full+lite tier builder, promoted to module level so hub agent
- # packages reuse the identical preset (#1102).
- _model_tiers = build_model_tiers
-
- def _make_chat_factory(profile, extra=None, tiers=None):
- """ChatAgent factory that honours a ``model_tier`` kwarg (#1162)."""
- _extra = dict(extra or {})
- _tiers = list(tiers or [])
-
- def factory(**kwargs):
- tier = kwargs.pop("model_tier", None)
- if tier:
- preset = _select_tier_model(_tiers, tier)
- if preset:
- kwargs.setdefault("model_id", preset)
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
-
- valid_fields = {f.name for f in dataclasses.fields(ChatAgentConfig)}
- filtered = {k: v for k, v in kwargs.items() if k in valid_fields}
- filtered.setdefault("prompt_profile", profile)
- for k, v in _extra.items():
- filtered.setdefault(k, v)
- config = ChatAgentConfig(**filtered)
- return ChatAgent(config=config)
-
- return factory
-
- # --- Chat Agent (conversation-only, lean prompt) ---
- _chat_tiers = _model_tiers("Full")
- self._register(
- AgentRegistration(
- id="chat",
- name="Chat",
- description="General conversation — fast, personality-first, no document tools",
- source="builtin",
- conversation_starters=[
- "What can you help me with?",
- "Tell me about yourself",
- "What's new today?",
- ],
- factory=_wrap_factory_with_namespaced_id(
- _make_chat_factory("chat", tiers=_chat_tiers), "builtin:chat"
- ),
- agent_dir=None,
- models=[],
- required_connections=[],
- # Hardcoded to mirror ``ChatAgent.CONSUMES_MCP_SERVERS`` — the
- # lazy factory must not import the chat module at discovery
- # time. A guard test keeps the two in sync.
- consumes_mcp_servers=True,
- namespaced_agent_id="builtin:chat",
- category="conversation",
- tags=["chat", "general", "personality"],
- icon="message-circle",
- tools_count=0,
- model_tiers=_chat_tiers,
- )
- )
- logger.info(
- "registry: Registered built-in agent: chat (ChatAgent, profile=chat)"
- )
-
- # --- Doc Agent (document Q&A with RAG) ---
- _doc_tiers = _model_tiers("Full")
- self._register(
- AgentRegistration(
- id="doc",
- name="Doc Agent",
- description="Document Q&A with RAG — ask questions about PDFs, reports, and manuals",
- source="builtin",
- conversation_starters=[
- "Search my documents for...",
- "Summarize this document",
- "What does the report say about...",
- ],
- factory=_wrap_factory_with_namespaced_id(
- _make_chat_factory("doc", tiers=_doc_tiers), "builtin:doc"
- ),
- agent_dir=None,
- models=[],
- required_connections=[],
- namespaced_agent_id="builtin:doc",
- category="documents",
- tags=["rag", "files", "search", "mcp"],
- icon="file-text",
- tools_count=15,
- model_tiers=_doc_tiers,
- )
- )
- logger.info("registry: Registered built-in agent: doc (ChatAgent, profile=doc)")
-
- # --- File Agent (file system operations) ---
- _file_tiers = _model_tiers("Full")
- self._register(
- AgentRegistration(
- id="file",
- name="File Agent",
- description="File system navigation, search, and analysis",
- source="builtin",
- conversation_starters=[
- "Find files related to...",
- "What's in my Documents folder?",
- "Show me the project structure",
- ],
- factory=_wrap_factory_with_namespaced_id(
- _make_chat_factory(
- "file", extra={"enable_filesystem": True}, tiers=_file_tiers
- ),
- "builtin:file",
- ),
- agent_dir=None,
- models=[],
- required_connections=[],
- namespaced_agent_id="builtin:file",
- category="productivity",
- tags=["files", "search", "filesystem", "shell"],
- icon="folder-search",
- tools_count=10,
- model_tiers=_file_tiers,
- )
- )
- logger.info(
- "registry: Registered built-in agent: file (ChatAgent, profile=file)"
- )
+ # ChatAgent ships as the standalone ``gaia-agent-chat`` wheel (#1102)
+ # exposing three prompt-profile ids — ``chat``/``doc``/``file`` — each
+ # via its own ``gaia.agent`` entry point, discovered in
+ # ``_discover_installed_agents``. The full+lite model tiers live in the
+ # package's ``build_chat``/``build_doc``/``build_file`` using the
+ # module-level ``build_model_tiers`` helper. No built-in registration
+ # here.
# AnalystAgent (id="data") and BrowserAgent (id="web") ship as the
# standalone ``gaia-agent-analyst`` / ``gaia-agent-browser`` wheels
diff --git a/src/gaia/agents/tools/__init__.py b/src/gaia/agents/tools/__init__.py
index 6df5588fa..747911b44 100644
--- a/src/gaia/agents/tools/__init__.py
+++ b/src/gaia/agents/tools/__init__.py
@@ -9,6 +9,7 @@
from .browser_tools import BrowserToolsMixin
from .code_index_tools import CodeIndexToolsMixin
from .file_io_tools import FileIOToolsMixin
+from .file_monitor_tools import FileToolsMixin
from .file_tools import FileSearchToolsMixin
from .filesystem_tools import FileSystemToolsMixin
from .rag_tools import RAGToolsMixin
@@ -21,6 +22,7 @@
"CodeIndexToolsMixin",
"FileIOToolsMixin",
"FileSearchToolsMixin",
+ "FileToolsMixin",
"FileSystemToolsMixin",
"RAGToolsMixin",
"ScratchpadToolsMixin",
diff --git a/src/gaia/agents/chat/tools/file_tools.py b/src/gaia/agents/tools/file_monitor_tools.py
similarity index 92%
rename from src/gaia/agents/chat/tools/file_tools.py
rename to src/gaia/agents/tools/file_monitor_tools.py
index 359374686..9c1360d71 100644
--- a/src/gaia/agents/chat/tools/file_tools.py
+++ b/src/gaia/agents/tools/file_monitor_tools.py
@@ -1,10 +1,12 @@
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
"""
-File Tools Mixin for Chat Agent.
+File monitoring tool mixin (shared framework tool).
-Provides directory monitoring for auto-indexing.
-NOTE: File search is handled by shell_tools.run_shell_command for flexibility.
+Provides directory monitoring for auto-indexing. Promoted from the chat agent
+to ``gaia.agents.tools`` (#1102) so agents consume it without depending on the
+chat package. NOTE: file search is handled by
+``shell_tools.run_shell_command`` for flexibility.
"""
import logging
diff --git a/src/gaia/cli.py b/src/gaia/cli.py
index b7d394234..d83bd7c42 100644
--- a/src/gaia/cli.py
+++ b/src/gaia/cli.py
@@ -595,9 +595,17 @@ async def async_main(action, **kwargs):
return {"response": response, "stats": stats}
return {"response": response}
elif action == "chat":
- # Use Chat Agent with RAG, file search, and shell execution
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
- from gaia.agents.chat.app import interactive_mode
+ # Use Chat Agent with RAG, file search, and shell execution.
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+ try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.app import interactive_mode
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Install it with "
+ '`pip install gaia-agent-chat` (or `pip install "amd-gaia[agents]"` '
+ "for all agents), then re-run `gaia chat`."
+ ) from e
try:
# Use silent mode when debug is off to hide intermediate processing
@@ -1011,8 +1019,16 @@ def _launch_interactive_cli(log=None):
if not success:
sys.exit(1)
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
- from gaia.agents.chat.app import interactive_mode
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+ try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.app import interactive_mode
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Install it with "
+ '`pip install gaia-agent-chat` (or `pip install "amd-gaia[agents]"` '
+ "for all agents), then re-run `gaia chat`."
+ ) from e
config = ChatAgentConfig(
base_url=base_url or os.getenv("LEMONADE_BASE_URL", DEFAULT_LEMONADE_URL),
diff --git a/src/gaia/eval/tool_cost.py b/src/gaia/eval/tool_cost.py
index 05e97b6d1..82d56247f 100644
--- a/src/gaia/eval/tool_cost.py
+++ b/src/gaia/eval/tool_cost.py
@@ -51,7 +51,7 @@
from unittest.mock import MagicMock, patch
if TYPE_CHECKING: # pragma: no cover - typing only
- from gaia.agents.chat.agent import ChatAgent
+ from gaia_agent_chat.agent import ChatAgent
# Tokenizer proxy — see module docstring for why tiktoken (not Gemma's own).
TOKENIZER_ENCODING = "cl100k_base"
@@ -156,9 +156,9 @@ def _build_skeleton_tool_loader(dynamic_tools: bool):
if not dynamic_tools:
return None
import numpy as np
+ from gaia_agent_chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
from gaia.agents.base.tool_loader import ToolLoader
- from gaia.agents.chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
return ToolLoader(
core_tools=DOC_CORE_TOOLS,
@@ -204,7 +204,7 @@ def build_doc_agent_skeleton(
"""
stubbed = _ensure_optional_deps_stubbed()
try:
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
cfg = ChatAgentConfig(
rag_documents=[],
diff --git a/src/gaia/mcp/mixin.py b/src/gaia/mcp/mixin.py
index 0295627e0..942251f90 100644
--- a/src/gaia/mcp/mixin.py
+++ b/src/gaia/mcp/mixin.py
@@ -25,8 +25,8 @@ class MCPClientMixin:
mixin's ``__init__`` is unreachable through the MRO. Set
``self._mcp_manager`` before calling ``super().__init__(...)`` —
``Agent.__init__`` runs ``_register_tools()`` which loads MCP tools.
- See ``ChatAgent.__init__`` (``src/gaia/agents/chat/agent.py``) for
- the canonical pattern.
+ See ``ChatAgent.__init__`` (the gaia-agent-chat wheel,
+ ``gaia_agent_chat/agent.py``) for the canonical pattern.
class MyAgent(Agent, MCPClientMixin):
def __init__(self, ...):
diff --git a/src/gaia/ui/_chat_helpers.py b/src/gaia/ui/_chat_helpers.py
index ae07e37f0..9d5795e06 100644
--- a/src/gaia/ui/_chat_helpers.py
+++ b/src/gaia/ui/_chat_helpers.py
@@ -37,8 +37,8 @@
logger = logging.getLogger(__name__)
-def _stamp_builtin_chat_identity(config) -> None:
- """Inject ``namespaced_agent_id="builtin:chat"`` into a ``ChatAgentConfig``
+def _stamp_chat_identity(config) -> None:
+ """Inject ``namespaced_agent_id="installed:chat"`` into a ``ChatAgentConfig``
BEFORE ``ChatAgent(config)`` is constructed.
Must be applied to the *config*, not to the instance after construction.
@@ -53,15 +53,16 @@ def _stamp_builtin_chat_identity(config) -> None:
``ChatAgent.__init__`` reads ``config.namespaced_agent_id`` at its top
and sets ``self._gaia_namespaced_agent_id`` from it before invoking
``super().__init__``. This helper just centralises the
- ``"builtin:chat"`` literal so every direct-construction site in this
- module uses the same value and a fifth caller can't forget.
+ ``"installed:chat"`` literal so every direct-construction site in this
+ module uses the same value the registry assigns the gaia-agent-chat wheel
+ (#1102) and a fifth caller can't forget.
Idempotent — only sets the field if it is still its default ``None``,
so callers that already set a custom namespaced id (e.g. a future
custom-Chat wrapper) are not clobbered.
"""
if getattr(config, "namespaced_agent_id", None) is None:
- config.namespaced_agent_id = "builtin:chat"
+ config.namespaced_agent_id = "installed:chat"
def _register_agent_memory_ops(agent) -> None:
@@ -1227,7 +1228,14 @@ def _do_chat():
agent_type,
)
elif agent_type == "chat":
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Run "
+ "`pip install gaia-agent-chat` (or `pip install "
+ '"amd-gaia[agents]"`), then restart the server.'
+ ) from e
logger.info(
"chat: Creating new chat agent (ChatAgent) for session %s",
@@ -1248,7 +1256,7 @@ def _do_chat():
min_context_size=device_ctx,
**_session_kwargs,
)
- _stamp_builtin_chat_identity(config)
+ _stamp_chat_identity(config)
agent = ChatAgent(config)
_store_agent(session_id, model_id, document_ids, agent, agent_type)
_register_agent_memory_ops(agent)
@@ -1636,7 +1644,17 @@ def _run_agent():
elif agent_type == "chat":
# -- Cache miss: ChatAgent --
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ try:
+ from gaia_agent_chat.agent import (
+ ChatAgent,
+ ChatAgentConfig,
+ )
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Run "
+ "`pip install gaia-agent-chat` (or `pip install "
+ '"amd-gaia[agents]"`), then restart the server.'
+ ) from e
logger.info(
"chat: Creating new chat agent (ChatAgent) for session %s",
@@ -1659,7 +1677,7 @@ def _run_agent():
**_session_kwargs,
)
- _stamp_builtin_chat_identity(config)
+ _stamp_chat_identity(config)
t_construct = _time.monotonic()
agent = ChatAgent(config)
logger.info(
diff --git a/src/gaia/ui/agent_loop.py b/src/gaia/ui/agent_loop.py
index 22f6e1193..3a78c898a 100644
--- a/src/gaia/ui/agent_loop.py
+++ b/src/gaia/ui/agent_loop.py
@@ -328,10 +328,18 @@ async def _execute_tick(
def _run_agent() -> None:
try:
- import gaia.ui._chat_helpers as _helpers
+ # Run the heavier sync work inline (we're in a thread).
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+ try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Run "
+ "`pip install gaia-agent-chat` (or `pip install "
+ '"amd-gaia[agents]"`), then restart the server.'
+ ) from e
- # Run the heavier sync work inline (we're in a thread)
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ import gaia.ui._chat_helpers as _helpers
# Reuse cached agent if available; build fresh if not.
# We bypass the full _stream_chat_response pipeline to avoid
diff --git a/src/gaia/ui/routers/system.py b/src/gaia/ui/routers/system.py
index e7a77318e..560b5b3f1 100644
--- a/src/gaia/ui/routers/system.py
+++ b/src/gaia/ui/routers/system.py
@@ -832,10 +832,10 @@ def _resolve_dynamic_tools_setting(db: ChatDatabase) -> tuple[bool, bool]:
Delegates the env parse to ``dynamic_tools_env_override`` so the UI and
``ChatAgent._resolve_dynamic_tools_enabled`` share one truthy set.
"""
- # Function-local import keeps the agents layer off the router's import
- # path (no cycle, no module-load cost): ui/ → agents/chat/ stays a lazy,
- # downward dependency.
- from gaia.agents.chat.agent import dynamic_tools_env_override
+ # Function-local import keeps the agents layer off the router's import path
+ # (no cycle, no module-load cost). The helper lives in the core tool_loader
+ # (not the chat wheel) so this core UI setting works without gaia-agent-chat.
+ from gaia.agents.base.tool_loader import dynamic_tools_env_override
persisted = db.get_setting("dynamic_tools", "false") == "true"
override = dynamic_tools_env_override()
diff --git a/src/gaia/ui/server.py b/src/gaia/ui/server.py
index f8ab0878e..d73810fee 100644
--- a/src/gaia/ui/server.py
+++ b/src/gaia/ui/server.py
@@ -368,7 +368,16 @@ async def _schedule_executor(prompt: str) -> str:
)
def _run() -> str:
- from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+ try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+ except ImportError as e:
+ raise RuntimeError(
+ "The chat agent is not installed. Install it with "
+ "`pip install gaia-agent-chat` (or `pip install "
+ '"amd-gaia[agents]"` for all agents) to run scheduled '
+ "chat tasks."
+ ) from e
# Beta dynamic tool loader (#1798). Inert here: scheduled runs
# use the default "full" prompt profile and the loader only
diff --git a/tests/test_chat_agent.py b/tests/test_chat_agent.py
index 832a65bdb..56262944c 100644
--- a/tests/test_chat_agent.py
+++ b/tests/test_chat_agent.py
@@ -10,7 +10,11 @@
import pytest
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
class TestChatAgent:
diff --git a/tests/test_sdk.py b/tests/test_sdk.py
index 289fb9976..6a96df36b 100644
--- a/tests/test_sdk.py
+++ b/tests/test_sdk.py
@@ -618,7 +618,7 @@ class TestToolMixins:
def test_file_tools_mixin_exists(self):
"""Verify FileToolsMixin can be imported."""
- from gaia.agents.chat.tools.file_tools import FileToolsMixin
+ from gaia.agents.tools import FileToolsMixin
assert FileToolsMixin is not None
@@ -738,7 +738,7 @@ def test_agent_with_multiple_mixins(self):
from gaia.agents.base.agent import Agent
from gaia.agents.base.api_agent import ApiAgent
from gaia.agents.base.console import SilentConsole
- from gaia.agents.chat.tools.file_tools import FileToolsMixin
+ from gaia.agents.tools import FileToolsMixin
class MultiMixinAgent(ApiAgent, Agent, FileToolsMixin):
def _get_system_prompt(self) -> str:
@@ -887,7 +887,7 @@ def test_all_imports_in_sdk_are_valid(self):
# Tool Mixins
try:
- from gaia.agents.chat.tools.file_tools import FileToolsMixin # noqa: F401
+ from gaia.agents.tools import FileToolsMixin # noqa: F401
from gaia.agents.tools.file_tools import FileSearchToolsMixin # noqa: F401
from gaia.agents.tools.rag_tools import RAGToolsMixin # noqa: F401
from gaia.agents.tools.shell_tools import ShellToolsMixin # noqa: F401
@@ -1266,7 +1266,8 @@ class TestSpecializedAgents:
def test_chat_agent_exists(self):
"""Verify ChatAgent can be imported."""
- from gaia.agents.chat.agent import ChatAgent
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgent
assert ChatAgent is not None
@@ -1298,11 +1299,12 @@ def test_specialized_agents_inherit_from_base(self):
"""Verify all specialized agents inherit from Agent base class."""
pytest.importorskip("gaia_agent_blender")
pytest.importorskip("gaia_agent_jira")
+ pytest.importorskip("gaia_agent_chat")
from gaia_agent_blender.agent import BlenderAgent
+ from gaia_agent_chat.agent import ChatAgent
from gaia_agent_jira.agent import JiraAgent
from gaia.agents.base.agent import Agent
- from gaia.agents.chat.agent import ChatAgent
assert issubclass(ChatAgent, Agent)
assert issubclass(JiraAgent, Agent)
diff --git a/tests/unit/agents/test_default_max_steps.py b/tests/unit/agents/test_default_max_steps.py
index e6546b9c9..a3fcb26bc 100644
--- a/tests/unit/agents/test_default_max_steps.py
+++ b/tests/unit/agents/test_default_max_steps.py
@@ -43,8 +43,14 @@ def test_non_positive_raises_loudly(self):
default_max_steps()
def test_configs_inherit_the_override_at_construction(self):
+ import pytest
+
+ # ChatAgentConfig ships with the standalone gaia-agent-chat wheel (#1102).
+ pytest.importorskip("gaia_agent_chat")
+
+ from gaia_agent_chat.agent import ChatAgentConfig
+
from gaia.agents.builder.agent import BuilderAgentConfig
- from gaia.agents.chat.agent import ChatAgentConfig
with mock.patch.dict(os.environ, {"GAIA_AGENT_MAX_STEPS": "42"}):
self.assertEqual(ChatAgentConfig().max_steps, 42)
diff --git a/tests/unit/agents/test_registry.py b/tests/unit/agents/test_registry.py
index bda242ef7..4e1f603a8 100644
--- a/tests/unit/agents/test_registry.py
+++ b/tests/unit/agents/test_registry.py
@@ -54,24 +54,31 @@ def _purge_custom_agent_modules():
class TestBuiltinRegistration:
- def test_chat_always_registered(self):
+ # chat/doc/file ship as the standalone gaia-agent-chat wheel (#1102),
+ # discovered via the gaia.agent entry point. The chat-specific tests below
+ # importorskip the wheel so a framework-only env tolerates its absence.
+ def test_chat_registered_when_wheel_installed(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
assert registry.get("chat") is not None
- def test_chat_source_is_builtin(self):
+ def test_chat_source_is_installed(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
reg = registry.get("chat")
- assert reg.source == "builtin"
+ assert reg.source == "installed"
def test_chat_has_conversation_starters(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
reg = registry.get("chat")
assert len(reg.conversation_starters) > 0
def test_list_returns_all_builtins(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
ids = [r.id for r in registry.list()]
@@ -107,11 +114,11 @@ def test_builder_not_in_visible_list(self):
# gaia-lite were collapsed into a "lite" model TIER of the single base
# agent. The old IDs survive only as legacy aliases.
- # chat/doc/file are ChatAgent profiles still resident in the framework
- # wheel. data (AnalystAgent) and web (BrowserAgent) now ship as the
- # standalone gaia-agent-analyst / gaia-agent-browser wheels (#1102); their
- # full+lite tiers are verified in those packages' own tests, so the
- # framework-only suite asserts tiers on the resident profiles only.
+ # chat/doc/file (ChatAgent profiles), data (AnalystAgent) and web
+ # (BrowserAgent) all now ship as standalone hub wheels (#1102); their
+ # full+lite tiers are verified in those packages' own tests. The
+ # framework-only suite asserts tiers on chat/doc/file only when the
+ # gaia-agent-chat wheel is installed (importorskip below).
_BASE_AGENTS = ["chat", "doc", "file"]
_LEGACY_LITE_IDS = [
"chat-lite",
@@ -131,6 +138,7 @@ def test_no_separate_lite_registrations(self):
assert legacy not in ids, f"{legacy} must no longer be its own card"
def test_base_agents_declare_full_and_lite_tiers(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
for agent_id in self._BASE_AGENTS:
@@ -171,9 +179,10 @@ def test_legacy_lite_ids_resolve_to_base_agent(self):
def test_legacy_chat_lite_create_agent_uses_lite_tier(self):
"""``create_agent('chat-lite')`` builds chat with the ~4B preset."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
- with patch("gaia.agents.chat.agent.ChatAgent") as mock_agent:
+ with patch("gaia_agent_chat.agent.ChatAgent") as mock_agent:
registry.create_agent("chat-lite")
mock_agent.assert_called_once()
config = mock_agent.call_args.kwargs["config"]
@@ -182,9 +191,10 @@ def test_legacy_chat_lite_create_agent_uses_lite_tier(self):
def test_legacy_gaia_lite_create_agent_uses_doc_profile(self):
"""gaia-lite resolves to the doc agent on the lite tier."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
- with patch("gaia.agents.chat.agent.ChatAgent") as mock_agent:
+ with patch("gaia_agent_chat.agent.ChatAgent") as mock_agent:
registry.create_agent("gaia-lite")
config = mock_agent.call_args.kwargs["config"]
assert config.model_id == _EXPECTED_PRIMARY
@@ -192,27 +202,30 @@ def test_legacy_gaia_lite_create_agent_uses_doc_profile(self):
def test_explicit_model_id_overrides_lite_tier(self):
"""A caller-pinned ``model_id`` wins over the alias's lite preset."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
- with patch("gaia.agents.chat.agent.ChatAgent") as mock_agent:
+ with patch("gaia_agent_chat.agent.ChatAgent") as mock_agent:
registry.create_agent("chat-lite", model_id="Custom-Model-Override")
config = mock_agent.call_args.kwargs["config"]
assert config.model_id == "Custom-Model-Override"
def test_explicit_model_tier_selects_lite_on_base_id(self):
"""The base id + ``model_tier='lite'`` selects the ~4B preset (#1162)."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
- with patch("gaia.agents.chat.agent.ChatAgent") as mock_agent:
+ with patch("gaia_agent_chat.agent.ChatAgent") as mock_agent:
registry.create_agent("chat", model_tier="lite")
config = mock_agent.call_args.kwargs["config"]
assert config.model_id == _EXPECTED_PRIMARY
def test_base_chat_create_agent_uses_default_model(self):
"""Plain ``chat`` (full tier) leaves model_id unset for the agent default."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
- with patch("gaia.agents.chat.agent.ChatAgent") as mock_agent:
+ with patch("gaia_agent_chat.agent.ChatAgent") as mock_agent:
registry.create_agent("chat")
config = mock_agent.call_args.kwargs["config"]
assert config.model_id is None
@@ -224,6 +237,7 @@ def test_legacy_chat_lite_resolve_model_returns_4b(self):
still resolve to the 4B preset even though the base ``chat`` agent
carries no top-level ``models`` preference (#1162).
"""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
resolved = registry.resolve_model(
@@ -233,6 +247,7 @@ def test_legacy_chat_lite_resolve_model_returns_4b(self):
assert resolved == _EXPECTED_PRIMARY
def test_legacy_gaia_lite_resolve_model_returns_4b(self):
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
resolved = registry.resolve_model(
@@ -243,6 +258,7 @@ def test_legacy_gaia_lite_resolve_model_returns_4b(self):
def test_base_chat_resolve_model_returns_none(self):
"""The full tier defers to the agent default — resolve_model is None."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
resolved = registry.resolve_model("chat", available_models=[_EXPECTED_PRIMARY])
@@ -262,6 +278,7 @@ def test_canonical_id_maps_aliases_and_passes_through_known_ids(self):
def test_chat_has_no_memory_requirement_by_default(self):
"""Chat (full tier) leaves min_memory_gb unset — existing behaviour."""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
registry.discover()
reg = registry.get("chat")
diff --git a/tests/unit/agents/test_registry_python_factory.py b/tests/unit/agents/test_registry_python_factory.py
index 4b703edc3..657dbf862 100644
--- a/tests/unit/agents/test_registry_python_factory.py
+++ b/tests/unit/agents/test_registry_python_factory.py
@@ -434,7 +434,8 @@ def test_chat_agent_config_declares_session_fields(self):
"""
import dataclasses as _dc
- from gaia.agents.chat.agent import ChatAgentConfig
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgentConfig
valid_fields = {f.name for f in _dc.fields(ChatAgentConfig)}
assert "rag_documents" in valid_fields
@@ -450,8 +451,9 @@ def test_chat_factory_filters_unknown_kwargs_via_create_agent(self):
guard for AC item "built-in chat continues to receive its session
kwargs unchanged".
"""
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
- registry._register_builtin_agents()
+ registry.discover()
# Exercise the literal production-injection path: model_id +
# session kwargs + an unknown kwarg the factory must drop.
diff --git a/tests/unit/chat/ui/test_chat_helpers.py b/tests/unit/chat/ui/test_chat_helpers.py
index 73278a051..d33cbd757 100644
--- a/tests/unit/chat/ui/test_chat_helpers.py
+++ b/tests/unit/chat/ui/test_chat_helpers.py
@@ -496,7 +496,7 @@ def test_streaming_registered_agent_passes_empty_rag_file_paths(self):
src = (
_Path(__file__).parents[4] / "src" / "gaia" / "ui" / "_chat_helpers.py"
- ).read_text()
+ ).read_text(encoding="utf-8")
# Locate the streaming registered-agent factory call. It's the only
# ``registry.create_agent(`` invocation in ``_stream_chat_response``
@@ -525,7 +525,7 @@ def test_streaming_registered_agent_passes_empty_rag_file_paths(self):
)
-# ── _stamp_builtin_chat_identity ─────────────────────────────────────────────
+# ── _stamp_chat_identity ─────────────────────────────────────────────
#
# Regression coverage for the activation-filter bypass discovered during #1005
# UI verification: ``_chat_helpers`` constructs ``ChatAgent(config)`` directly
@@ -534,12 +534,12 @@ def test_streaming_registered_agent_passes_empty_rag_file_paths(self):
# activation filter as ``_gaia_namespaced_agent_id is None``, and
# ``Agent._active_mcp_servers`` falls back to "ad-hoc agent — show every MCP
# server unfiltered". The activations.json ledger gets written but ignored,
-# so deactivating ``mcp-github`` for ``builtin:chat`` has no effect on what the
+# so deactivating ``mcp-github`` for ``installed:chat`` has no effect on what the
# UI's Chat sees.
class TestStampBuiltinChatIdentity:
- """Tests for _stamp_builtin_chat_identity().
+ """Tests for _stamp_chat_identity().
The stamp must hit the ChatAgentConfig BEFORE ChatAgent.__init__ runs,
because ``__init__`` calls ``_register_tools`` which reads the namespaced
@@ -548,22 +548,26 @@ class TestStampBuiltinChatIdentity:
"""
def test_stamps_field_on_fresh_config(self):
- from gaia.agents.chat.agent import ChatAgentConfig
- from gaia.ui._chat_helpers import _stamp_builtin_chat_identity
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgentConfig
+
+ from gaia.ui._chat_helpers import _stamp_chat_identity
config = ChatAgentConfig()
assert config.namespaced_agent_id is None
- _stamp_builtin_chat_identity(config)
- assert config.namespaced_agent_id == "builtin:chat"
+ _stamp_chat_identity(config)
+ assert config.namespaced_agent_id == "installed:chat"
def test_is_idempotent_no_overwrite_when_already_set(self):
# Callers that pre-fill the field with a custom id (e.g. a future
# custom-Chat wrapper) must NOT be clobbered.
- from gaia.agents.chat.agent import ChatAgentConfig
- from gaia.ui._chat_helpers import _stamp_builtin_chat_identity
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgentConfig
+
+ from gaia.ui._chat_helpers import _stamp_chat_identity
config = ChatAgentConfig(namespaced_agent_id="custom:abc:chat")
- _stamp_builtin_chat_identity(config)
+ _stamp_chat_identity(config)
assert config.namespaced_agent_id == "custom:abc:chat"
def test_chat_agent_init_propagates_config_to_instance_attr_before_super(
@@ -583,9 +587,17 @@ def test_chat_agent_init_propagates_config_to_instance_attr_before_super(
"""
from pathlib import Path as _Path
+ # ChatAgent ships as the standalone gaia-agent-chat wheel (#1102); its
+ # source now lives under hub/agents/python/chat/.
src = (
- _Path(__file__).parents[4] / "src" / "gaia" / "agents" / "chat" / "agent.py"
- ).read_text()
+ _Path(__file__).parents[4]
+ / "hub"
+ / "agents"
+ / "python"
+ / "chat"
+ / "gaia_agent_chat"
+ / "agent.py"
+ ).read_text(encoding="utf-8")
init_start = src.index("def __init__(self, config: Optional[ChatAgentConfig]")
super_init = src.index("super().__init__(", init_start)
prelude = src[init_start:super_init]
@@ -601,7 +613,7 @@ def test_chat_agent_init_propagates_config_to_instance_attr_before_super(
def test_every_direct_ChatAgent_construction_is_pre_stamped(self):
"""Every ``agent = ChatAgent(config)`` in _chat_helpers.py must be
- PRECEDED by ``_stamp_builtin_chat_identity(config)`` (within the
+ PRECEDED by ``_stamp_chat_identity(config)`` (within the
last 5 non-blank lines).
Structural guard: a fifth direct construction site added without
@@ -620,7 +632,7 @@ def test_every_direct_ChatAgent_construction_is_pre_stamped(self):
src = (
_Path(__file__).parents[4] / "src" / "gaia" / "ui" / "_chat_helpers.py"
- ).read_text()
+ ).read_text(encoding="utf-8")
lines = src.splitlines()
offenders = []
@@ -633,15 +645,15 @@ def test_every_direct_ChatAgent_construction_is_pre_stamped(self):
if lines[j].strip():
window.append(lines[j])
j -= 1
- if not any("_stamp_builtin_chat_identity(config)" in w for w in window):
+ if not any("_stamp_chat_identity(config)" in w for w in window):
offenders.append(
f"line {i + 1}: {line.strip()!r} — missing prior "
- f"_stamp_builtin_chat_identity(config)"
+ f"_stamp_chat_identity(config)"
)
assert not offenders, (
"Every direct ``agent = ChatAgent(config)`` in _chat_helpers.py "
- "must be PRECEDED by ``_stamp_builtin_chat_identity(config)`` "
+ "must be PRECEDED by ``_stamp_chat_identity(config)`` "
"(within the last 5 non-blank lines) so the per-agent "
"activation filter (#1005) fires correctly. Post-construction "
"stamping is too late — ChatAgent.__init__ runs "
diff --git a/tests/unit/chat/ui/test_chat_helpers_model_resolution.py b/tests/unit/chat/ui/test_chat_helpers_model_resolution.py
index 40ea4d091..e1ed8cec7 100644
--- a/tests/unit/chat/ui/test_chat_helpers_model_resolution.py
+++ b/tests/unit/chat/ui/test_chat_helpers_model_resolution.py
@@ -18,6 +18,8 @@
from pathlib import Path
from unittest.mock import MagicMock, patch
+import pytest
+
from gaia.ui.database import SESSION_DEFAULT_MODEL as _DB_DEFAULT
# ── Helpers ──────────────────────────────────────────────────────────────────
@@ -503,11 +505,12 @@ def test_chat_agent_type_bypasses_registry(self):
fake_agent.indexed_files = set()
fake_agent.rag = None
+ pytest.importorskip("gaia_agent_chat")
with (
patch("gaia.ui._chat_helpers._agent_registry", registry),
patch("gaia.ui._chat_helpers._maybe_load_expected_model"),
- patch("gaia.agents.chat.agent.ChatAgent", return_value=fake_agent),
- patch("gaia.agents.chat.agent.ChatAgentConfig"),
+ patch("gaia_agent_chat.agent.ChatAgent", return_value=fake_agent),
+ patch("gaia_agent_chat.agent.ChatAgentConfig"),
):
_call_non_streaming(session, db, agent_type_override=None)
diff --git a/tests/unit/chat/ui/test_history_limits.py b/tests/unit/chat/ui/test_history_limits.py
index 1b233dcb4..b62f13f80 100644
--- a/tests/unit/chat/ui/test_history_limits.py
+++ b/tests/unit/chat/ui/test_history_limits.py
@@ -17,6 +17,8 @@
from pathlib import Path
from unittest.mock import MagicMock, patch
+import pytest
+
# ── helpers ──────────────────────────────────────────────────────────────────
@@ -75,12 +77,13 @@ def process_query(self, msg):
db = _make_mock_db(messages)
session = {"document_ids": [], "model": None}
- # ChatAgent/ChatAgentConfig are lazy-imported inside _do_chat(), so
- # patch them at their source module (gaia.agents.chat.agent) which
- # is the target of "from gaia.agents.chat.agent import ChatAgent, ..."
+ # ChatAgent/ChatAgentConfig are lazy-imported inside _do_chat() from the
+ # standalone gaia-agent-chat wheel (#1102), so patch them at their
+ # source module (gaia_agent_chat.agent).
+ pytest.importorskip("gaia_agent_chat")
with (
- patch("gaia.agents.chat.agent.ChatAgent", return_value=FakeAgent()),
- patch("gaia.agents.chat.agent.ChatAgentConfig"),
+ patch("gaia_agent_chat.agent.ChatAgent", return_value=FakeAgent()),
+ patch("gaia_agent_chat.agent.ChatAgentConfig"),
):
_run_sync(_get_chat_response(db, session, request))
diff --git a/tests/unit/test_agent_hub_api.py b/tests/unit/test_agent_hub_api.py
index 5d2057808..1b7baf800 100644
--- a/tests/unit/test_agent_hub_api.py
+++ b/tests/unit/test_agent_hub_api.py
@@ -69,15 +69,22 @@ def test_native_source_type(self):
class TestBuiltinAgentHubMetadata:
- """Verify builtin agents have Hub metadata populated."""
+ """Verify framework + installed agents have Hub metadata populated.
+
+ chat/doc migrated to the gaia-agent-chat wheel (#1102), so they are now
+ discovered via the ``gaia.agent`` entry point rather than registered as
+ builtins — the fixture runs full discovery and the chat/doc cases skip when
+ the wheel is absent.
+ """
@pytest.fixture()
def registry(self):
r = AgentRegistry()
- r._register_builtin_agents()
+ r.discover()
return r
def test_chat_agent_metadata(self, registry):
+ pytest.importorskip("gaia_agent_chat")
reg = registry.get("chat")
assert reg is not None
assert reg.category == "conversation"
@@ -86,6 +93,7 @@ def test_chat_agent_metadata(self, registry):
assert reg.language == "python"
def test_gaia_lite_resolves_to_doc_with_lite_tier(self, registry):
+ pytest.importorskip("gaia_agent_chat")
# #1162: gaia-lite is a legacy alias for the doc agent on the lite tier,
# not a standalone registration.
assert registry.canonical_id("gaia-lite") == "doc"
@@ -194,17 +202,19 @@ class TestConsumesMcpServersExposure:
Settings "Active for" panel can list dynamic MCP consumers."""
def test_reg_to_info_exposes_flag_for_chat(self):
+ pytest.importorskip("gaia_agent_chat")
from gaia.ui.routers.agents import _reg_to_info
registry = AgentRegistry()
- registry._register_builtin_agents()
+ registry.discover()
info = _reg_to_info(registry.get("chat"))
assert info.consumes_mcp_servers is True
def test_reg_to_info_defaults_false_for_non_consumer(self):
+ pytest.importorskip("gaia_agent_chat")
from gaia.ui.routers.agents import _reg_to_info
registry = AgentRegistry()
- registry._register_builtin_agents()
+ registry.discover()
info = _reg_to_info(registry.get("doc"))
assert info.consumes_mcp_servers is False
diff --git a/tests/unit/test_agent_required_connectors.py b/tests/unit/test_agent_required_connectors.py
index a7ee51375..93af8c91b 100644
--- a/tests/unit/test_agent_required_connectors.py
+++ b/tests/unit/test_agent_required_connectors.py
@@ -152,8 +152,11 @@ def test_base_default_is_false(self):
assert isinstance(Agent.CONSUMES_MCP_SERVERS, bool)
def test_builtin_chat_consumes_mcp_servers(self):
+ # chat ships as the standalone gaia-agent-chat wheel (#1102), discovered
+ # via the gaia.agent entry point — use discover(), not the builtin pass.
+ pytest.importorskip("gaia_agent_chat")
registry = AgentRegistry()
- registry._register_builtin_agents()
+ registry.discover()
chat = registry.get("chat")
assert chat is not None
assert chat.consumes_mcp_servers is True
@@ -162,10 +165,11 @@ def test_builtin_chat_registration_matches_class(self):
# The lazy factory cannot import the chat module at discovery time, so
# the registration hardcodes the flag. Guard it against the class-level
# source of truth drifting apart.
- from gaia.agents.chat.agent import ChatAgent
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgent
registry = AgentRegistry()
- registry._register_builtin_agents()
+ registry.discover()
chat = registry.get("chat")
assert ChatAgent.CONSUMES_MCP_SERVERS is True
assert chat.consumes_mcp_servers == ChatAgent.CONSUMES_MCP_SERVERS
@@ -288,26 +292,29 @@ def test_origin_hash_changes_when_agent_py_changes(self, tmp_path, monkeypatch):
assert ns1 != ns2
def test_reserved_id_is_blocked(self, tmp_path, monkeypatch, caplog):
+ # ``builder`` is the canonical reserved framework built-in (chat/doc/file
+ # migrated to the gaia-agent-chat wheel, #1102, and are no longer
+ # reserved). A custom agent must not be able to claim a reserved id.
agents_root = tmp_path / ".gaia" / "agents"
agent_dir = agents_root / "trojan"
agent_dir.mkdir(parents=True)
(agent_dir / "agent.py").write_text(
- CUSTOM_AGENT_TEMPLATE.format(agent_id="chat")
+ CUSTOM_AGENT_TEMPLATE.format(agent_id="builder")
)
monkeypatch.setattr("gaia.agents.registry.Path.home", lambda: tmp_path)
registry = AgentRegistry()
# discover() should log a warning and skip the trojan agent — it must
- # not register under id "chat" and overwrite the built-in.
- registry._register_builtin_agents() # registers built-in chat
+ # not register under id "builder" and overwrite the built-in.
+ registry._register_builtin_agents() # registers built-in builder
with caplog.at_level("WARNING"):
registry.discover()
- chat_reg = registry.get("chat")
- # The built-in chat is the one that survives.
- assert chat_reg.source == "builtin"
- assert chat_reg.namespaced_agent_id == "builtin:chat"
+ builder_reg = registry.get("builder")
+ # The built-in builder is the one that survives.
+ assert builder_reg.source == "builtin"
+ assert builder_reg.namespaced_agent_id == "builtin:builder"
class TestAgentInfoSerialization:
diff --git a/tests/unit/test_agents_split.py b/tests/unit/test_agents_split.py
index 15461fa89..c194ed1c1 100644
--- a/tests/unit/test_agents_split.py
+++ b/tests/unit/test_agents_split.py
@@ -5,12 +5,12 @@
def test_instantiate_new_agents():
- # fileio/docqa ship as the standalone gaia-agent-fileio / gaia-agent-docqa
- # wheels (#1102).
+ # fileio / docqa / chat ship as standalone gaia-agent-* wheels (#1102).
pytest.importorskip("gaia_agent_fileio")
pytest.importorskip("gaia_agent_docqa")
+ pytest.importorskip("gaia_agent_chat")
# Import without triggering heavy optional deps by relying on skip_lemonade
- chat_mod = import_module("gaia.agents.chat.lite_agent")
+ chat_mod = import_module("gaia_agent_chat.lite_agent")
docqa_mod = import_module("gaia_agent_docqa.agent")
fileio_mod = import_module("gaia_agent_fileio.agent")
@@ -159,13 +159,13 @@ def test_get_mcp_status_report_does_not_raise(tmp_path):
pytest.importorskip("gaia_agent_browser")
pytest.importorskip("gaia_agent_analyst")
pytest.importorskip("gaia_agent_docqa")
+ pytest.importorskip("gaia_agent_chat")
from gaia_agent_analyst.agent import AnalystAgent, AnalystAgentConfig
from gaia_agent_browser.agent import BrowserAgent
+ from gaia_agent_chat.lite_agent import ChatAgentLite
from gaia_agent_docqa.agent import DocumentQAAgent
from gaia_agent_fileio.agent import FileIOAgent
- from gaia.agents.chat.lite_agent import ChatAgentLite
-
agents = [
BrowserAgent(),
AnalystAgent(AnalystAgentConfig(scratchpad_db_path=str(tmp_path / "s.db"))),
diff --git a/tests/unit/test_browser_tools.py b/tests/unit/test_browser_tools.py
index 75d28d400..1fa0fd554 100644
--- a/tests/unit/test_browser_tools.py
+++ b/tests/unit/test_browser_tools.py
@@ -9,7 +9,14 @@
import pytest
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102). The
+# WebClient / BrowserToolsMixin tests below don't need it, so import it lazily
+# and skip only the ChatAgent integration class when the wheel is absent.
+try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+except ImportError:
+ ChatAgent = ChatAgentConfig = None
+
from gaia.web.client import WebClient
# ===== WebClient Tests =====
@@ -912,6 +919,11 @@ def test_download_file_size_formatting_bytes(self):
class TestChatAgentBrowserIntegration:
"""Test ChatAgent initializes and registers browser tools correctly."""
+ @pytest.fixture(autouse=True)
+ def _require_chat_wheel(self):
+ if ChatAgent is None:
+ pytest.skip("gaia-agent-chat wheel not installed (#1102)")
+
def test_web_client_initialized_when_enabled(self):
"""ChatAgent creates WebClient when enable_browser=True."""
config = ChatAgentConfig(
@@ -921,8 +933,8 @@ def test_web_client_initialized_when_enabled(self):
enable_scratchpad=False,
)
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
):
agent = ChatAgent(config)
assert agent._web_client is not None
@@ -937,8 +949,8 @@ def test_web_client_none_when_disabled(self):
enable_scratchpad=False,
)
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
):
agent = ChatAgent(config)
assert agent._web_client is None
@@ -955,8 +967,8 @@ def test_browser_config_fields_passed_to_webclient(self):
enable_scratchpad=False,
)
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
):
agent = ChatAgent(config)
assert agent._web_client._timeout == 60
@@ -973,8 +985,8 @@ def test_browser_tools_in_registered_tools(self):
enable_scratchpad=False,
)
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
):
agent = ChatAgent(config)
@@ -994,8 +1006,8 @@ def test_system_prompt_includes_browser_section(self):
enable_scratchpad=False,
)
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
):
agent = ChatAgent(config)
diff --git a/tests/unit/test_chat_agent_integration.py b/tests/unit/test_chat_agent_integration.py
index 1f5348601..4f4c4d49d 100644
--- a/tests/unit/test_chat_agent_integration.py
+++ b/tests/unit/test_chat_agent_integration.py
@@ -7,7 +7,11 @@
import pytest
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
# ---------------------------------------------------------------------------
# Helpers
@@ -16,8 +20,8 @@
# All ChatAgent construction in these tests patches RAGSDK and RAGConfig so
# that no real LLM or RAG backend is needed.
_RAG_PATCHES = (
- "gaia.agents.chat.agent.RAGSDK",
- "gaia.agents.chat.agent.RAGConfig",
+ "gaia_agent_chat.agent.RAGSDK",
+ "gaia_agent_chat.agent.RAGConfig",
)
@@ -87,8 +91,8 @@ def test_fs_index_none_when_disabled(self):
def test_fs_index_graceful_import_error(self):
"""If FileSystemIndexService cannot be imported, _fs_index stays None."""
with (
- patch("gaia.agents.chat.agent.RAGSDK"),
- patch("gaia.agents.chat.agent.RAGConfig"),
+ patch("gaia_agent_chat.agent.RAGSDK"),
+ patch("gaia_agent_chat.agent.RAGConfig"),
patch.dict(
"sys.modules",
{"gaia.filesystem.index": None},
diff --git a/tests/unit/test_chat_dynamic_tools.py b/tests/unit/test_chat_dynamic_tools.py
index cb9da3faa..ce7e45f74 100644
--- a/tests/unit/test_chat_dynamic_tools.py
+++ b/tests/unit/test_chat_dynamic_tools.py
@@ -23,6 +23,12 @@
import numpy as np
import pytest
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it. Must run BEFORE the optional-
+# dep stubbing below — otherwise a skip would leave MagicMock stand-ins for
+# faiss/sentence_transformers in sys.modules and poison later modules in the run.
+pytest.importorskip("gaia_agent_chat")
+
# Stub heavy optional deps only when genuinely absent (mirrors the budget test).
_stubbed: list[str] = []
for _mod in ("faiss", "sentence_transformers", "pdfplumber", "pypdf", "pypdfium2"):
@@ -31,8 +37,9 @@
sys.modules[_mod] = MagicMock()
_stubbed.append(_mod)
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
+
from gaia.agents.base.tool_loader import ToolLoader # noqa: E402
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
from gaia.eval.tool_cost import build_doc_agent_skeleton # noqa: E402
for _mod in _stubbed:
@@ -99,7 +106,7 @@ def test_env_override_helper_none_when_unset(monkeypatch):
"""``dynamic_tools_env_override`` returns ``None`` so callers fall back to
the persisted/config value — the single source of truth the UI router and
the agent resolver both read (#1798)."""
- from gaia.agents.chat.agent import dynamic_tools_env_override
+ from gaia_agent_chat.agent import dynamic_tools_env_override
monkeypatch.delenv("GAIA_DYNAMIC_TOOLS", raising=False)
assert dynamic_tools_env_override() is None
@@ -122,7 +129,7 @@ def test_env_override_helper_none_when_unset(monkeypatch):
def test_env_override_helper_parses_truthy_set(monkeypatch, raw, expected):
"""Same truthy set the resolver used to inline — pinned so the UI toggle
and the agent never disagree on what counts as "on"."""
- from gaia.agents.chat.agent import dynamic_tools_env_override
+ from gaia_agent_chat.agent import dynamic_tools_env_override
monkeypatch.setenv("GAIA_DYNAMIC_TOOLS", raw)
assert dynamic_tools_env_override() is expected
diff --git a/tests/unit/test_chat_system_prompt_budget.py b/tests/unit/test_chat_system_prompt_budget.py
index 3ce1aaf27..3b2384bc8 100644
--- a/tests/unit/test_chat_system_prompt_budget.py
+++ b/tests/unit/test_chat_system_prompt_budget.py
@@ -26,6 +26,10 @@
import pytest
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
# Stub heavy optional deps so this test can run in a minimal env, but ONLY
# for modules that can't actually be imported in the current environment.
# Stubbing an installed module poisons every transitive consumer's cache —
@@ -52,10 +56,10 @@
sys.modules[_mod] = MagicMock()
_stubbed_modules.append(_mod)
-# Import once: ``gaia.agents.chat.agent`` resolves its faiss/numpy/etc.
+# Import once: ``gaia_agent_chat.agent`` resolves its faiss/numpy/etc.
# references at this point, so the cached module keeps working even after
# we remove the stubs from ``sys.modules`` below.
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
+from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig # noqa: E402
# Roll back the temporary stubs AND evict GAIA modules that bound them so
# subsequent tests get a fresh import that resolves against the real
@@ -65,7 +69,7 @@
if _stubbed_modules:
for _gaia_mod in (
"gaia.rag.sdk",
- "gaia.agents.chat.agent",
+ "gaia_agent_chat.agent",
"gaia.agents.tools.rag_tools",
):
sys.modules.pop(_gaia_mod, None)
diff --git a/tests/unit/test_chat_tool_bundles.py b/tests/unit/test_chat_tool_bundles.py
index 6782115d6..7a9aaa3b2 100644
--- a/tests/unit/test_chat_tool_bundles.py
+++ b/tests/unit/test_chat_tool_bundles.py
@@ -16,8 +16,15 @@
from __future__ import annotations
-from gaia.agents.chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
-from gaia.eval.tool_cost import build_doc_agent_skeleton
+import pytest
+
+# DOC_BUNDLES ships with the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS # noqa: E402
+
+from gaia.eval.tool_cost import build_doc_agent_skeleton # noqa: E402
def _bundle_union() -> set[str]:
@@ -41,7 +48,8 @@ def test_core_and_bundles_cover_doc_registry_exactly():
assert not uncovered, (
f"doc-profile tools not covered by CORE or any bundle: {uncovered}. "
- "Add each to a bundle (or CORE) in src/gaia/agents/chat/tool_bundles.py "
+ "Add each to a bundle (or CORE) in "
+ "hub/agents/python/chat/gaia_agent_chat/tool_bundles.py "
"— an uncovered tool would never be surfaced by the loader."
)
assert not dangling, (
diff --git a/tests/unit/test_dynamic_tool_filtering.py b/tests/unit/test_dynamic_tool_filtering.py
index c3ef676bc..e607a491d 100644
--- a/tests/unit/test_dynamic_tool_filtering.py
+++ b/tests/unit/test_dynamic_tool_filtering.py
@@ -18,7 +18,13 @@
import json
-from gaia.eval.tool_cost import build_doc_agent_skeleton
+import pytest
+
+# build_doc_agent_skeleton builds a doc-profile ChatAgent, which ships in the
+# standalone gaia-agent-chat wheel (#1102); skip when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia.eval.tool_cost import build_doc_agent_skeleton # noqa: E402
def _agent():
diff --git a/tests/unit/test_errors.py b/tests/unit/test_errors.py
index 8a62f34f9..bc1a39a27 100644
--- a/tests/unit/test_errors.py
+++ b/tests/unit/test_errors.py
@@ -71,11 +71,14 @@ def test_framework_paths_includes_all_agents(self):
expected_paths = [
"gaia/agents/base",
"gaia/agents/blender",
- "gaia/agents/chat",
"gaia/agents/code",
"gaia/agents/docker",
"gaia/agents/jira",
"gaia/agents/tools",
+ # Hub-migrated agents (#1102): chat ships as the gaia-agent-chat
+ # wheel, filtered via gaia_agent_chat / the hub editable path.
+ "hub/agents/python/",
+ "gaia_agent_chat",
"site-packages/",
]
for path in expected_paths:
diff --git a/tests/unit/test_hub_manifest.py b/tests/unit/test_hub_manifest.py
index 06c972215..62435cbb7 100644
--- a/tests/unit/test_hub_manifest.py
+++ b/tests/unit/test_hub_manifest.py
@@ -281,7 +281,9 @@ def test_valid_id_accepted(good_id):
assert AgentManifest.from_dict(data).id == good_id
-@pytest.mark.parametrize("reserved", ["chat", "builder", "gaia-lite", "doc"])
+# Only ``builder`` remains a framework builtin; chat/doc/data/web/email migrated
+# to standalone hub wheels (#1102) and are no longer reserved ids.
+@pytest.mark.parametrize("reserved", ["builder"])
def test_reserved_id_rejected(reserved):
data = dict(VALID_PYTHON_MANIFEST, id=reserved)
with pytest.raises(ManifestError, match="reserved"):
diff --git a/tests/unit/test_hub_router.py b/tests/unit/test_hub_router.py
index 4242a48b2..80e517347 100644
--- a/tests/unit/test_hub_router.py
+++ b/tests/unit/test_hub_router.py
@@ -190,8 +190,9 @@ def test_uninstall_success(client, monkeypatch):
def test_uninstall_builtin_refused(client):
- # Real uninstall refuses builtins -> 400 (no mock needed).
- resp = client.delete("/api/agents/chat", headers=UI)
+ # Real uninstall refuses builtins -> 400 (no mock needed). ``builder`` is the
+ # only remaining framework builtin after the #1102 hub migrations.
+ resp = client.delete("/api/agents/builder", headers=UI)
assert resp.status_code == 400
diff --git a/tests/unit/test_multi_device_wiring.py b/tests/unit/test_multi_device_wiring.py
index 491be3071..539563d83 100644
--- a/tests/unit/test_multi_device_wiring.py
+++ b/tests/unit/test_multi_device_wiring.py
@@ -254,14 +254,16 @@ def _register_tools(self):
class TestChatAgentConfigDeviceField:
def test_config_has_device_and_ctx(self):
- from gaia.agents.chat.agent import ChatAgentConfig
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgentConfig
cfg = ChatAgentConfig(device="npu", min_context_size=4096)
assert cfg.device == "npu"
assert cfg.min_context_size == 4096
def test_config_device_defaults_none(self):
- from gaia.agents.chat.agent import ChatAgentConfig
+ pytest.importorskip("gaia_agent_chat")
+ from gaia_agent_chat.agent import ChatAgentConfig
cfg = ChatAgentConfig()
assert cfg.device is None
diff --git a/tests/unit/test_prompt_size_budget.py b/tests/unit/test_prompt_size_budget.py
index 1005119ce..363c9b7bd 100644
--- a/tests/unit/test_prompt_size_budget.py
+++ b/tests/unit/test_prompt_size_budget.py
@@ -1,4 +1,9 @@
-from gaia.agents.chat.agent import ChatAgent
+import pytest
+
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.agent import ChatAgent # noqa: E402
def test_system_prompt_size_under_limit():
diff --git a/tests/unit/test_tool_loader_disambiguation.py b/tests/unit/test_tool_loader_disambiguation.py
index 8147a5b57..67427f78b 100644
--- a/tests/unit/test_tool_loader_disambiguation.py
+++ b/tests/unit/test_tool_loader_disambiguation.py
@@ -21,13 +21,19 @@
from __future__ import annotations
import numpy as np
+import pytest
from gaia.agents.base.tool_loader import (
DEFAULT_MAX_TOOLS,
DEFAULT_THRESHOLD,
ToolLoader,
)
-from gaia.agents.chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS
+
+# DOC_BUNDLES ships with the standalone gaia-agent-chat wheel (#1102); skip the
+# whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.tool_bundles import DOC_BUNDLES, DOC_CORE_TOOLS # noqa: E402
DIM = 768
diff --git a/tests/unit/test_tool_loader_token_budget.py b/tests/unit/test_tool_loader_token_budget.py
index 5c9cb433f..94e58832d 100644
--- a/tests/unit/test_tool_loader_token_budget.py
+++ b/tests/unit/test_tool_loader_token_budget.py
@@ -30,8 +30,13 @@
import pytest
-from gaia.agents.chat.tool_bundles import DOC_CORE_TOOLS
-from gaia.eval.tool_cost import (
+# DOC_CORE_TOOLS ships with the standalone gaia-agent-chat wheel (#1102); skip
+# the whole module when a framework-only env lacks it.
+pytest.importorskip("gaia_agent_chat")
+
+from gaia_agent_chat.tool_bundles import DOC_CORE_TOOLS # noqa: E402
+
+from gaia.eval.tool_cost import ( # noqa: E402
FIXED_SUBSET_DEFAULT,
build_doc_agent_skeleton,
get_tokenizer,
diff --git a/tests/verify_path_validator.py b/tests/verify_path_validator.py
index c978fb077..5229eee87 100644
--- a/tests/verify_path_validator.py
+++ b/tests/verify_path_validator.py
@@ -12,7 +12,14 @@
# the framework; their path-validation is covered by those packages' own tests
# (e.g. ``hub/agents/python/code/tests/test_file_io_guardrails.py``). The
# chat/rag cases below still exercise the shared PathValidator contract here.
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+except ImportError:
+ raise SystemExit(
+ "gaia-agent-chat is not installed — run: pip install gaia-agent-chat"
+ )
+
from gaia.rag.sdk import RAGSDK, RAGConfig
from gaia.security import PathValidator
diff --git a/tests/verify_shell_security.py b/tests/verify_shell_security.py
index 4583c8fd9..93bc91084 100644
--- a/tests/verify_shell_security.py
+++ b/tests/verify_shell_security.py
@@ -3,8 +3,15 @@
import os
+# ChatAgent ships as the standalone gaia-agent-chat wheel (#1102).
+try:
+ from gaia_agent_chat.agent import ChatAgent, ChatAgentConfig
+except ImportError:
+ raise SystemExit(
+ "gaia-agent-chat is not installed — run: pip install gaia-agent-chat"
+ )
+
from gaia.agents.base.tools import _TOOL_REGISTRY
-from gaia.agents.chat.agent import ChatAgent, ChatAgentConfig
def test_shell_injection():
diff --git a/util/lint.ps1 b/util/lint.ps1
index ff4891e21..e42caf0af 100644
--- a/util/lint.ps1
+++ b/util/lint.ps1
@@ -347,7 +347,7 @@ function Invoke-ImportTests {
@{Import="from gaia.agents.base import tool"; Desc="Tool decorator"; Optional=$false},
# Specialized Agents
- @{Import="from gaia.agents.chat import ChatAgent"; Desc="Chat agent"; Optional=$false},
+ @{Import="from gaia_agent_chat import ChatAgent"; Desc="Chat agent"; Optional=$true},
@{Import="from gaia.agents.code import CodeAgent"; Desc="Code agent"; Optional=$false},
@{Import="from gaia.agents.jira import JiraAgent"; Desc="Jira agent"; Optional=$false},
@{Import="from gaia.agents.docker import DockerAgent"; Desc="Docker agent"; Optional=$false},
diff --git a/util/lint.py b/util/lint.py
index 2b2c016cb..a4f0e32cc 100644
--- a/util/lint.py
+++ b/util/lint.py
@@ -353,7 +353,7 @@ def check_imports() -> CheckResult:
("from", "gaia.agents.base", "MCPAgent", "MCP agent mixin", False),
("from", "gaia.agents.base", "tool", "Tool decorator", False),
# Specialized Agents
- ("from", "gaia.agents.chat", "ChatAgent", "Chat agent", False),
+ ("from", "gaia_agent_chat", "ChatAgent", "Chat agent", True),
("from", "gaia_agent_code", "CodeAgent", "Code agent", True),
("from", "gaia_agent_jira", "JiraAgent", "Jira agent", True),
("from", "gaia_agent_docker", "DockerAgent", "Docker agent", True),