fix: constrain speculative domain discovery (#199)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70200b7..5bcfb51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,10 @@
 
 ### Fixed
 
+- Added explicit, mutually exclusive top-level domain boundaries and a
+  single-domain discovery rule, preventing Hosts from speculatively expanding
+  unrelated manifests for ambiguous operational questions. Unqualified cluster
+  history requests now preserve every usable recorded series.
 - Declared the exact `storage`, `query_volume`, and `user_activity` selectors
   for resource-growth analysis, including their recorded evidence sources, so
   Hosts reject ambiguous values before dispatching a Doris query.
diff --git a/docs/integrations/hosts.md b/docs/integrations/hosts.md
index d535745..dacd715 100644
--- a/docs/integrations/hosts.md
+++ b/docs/integrations/hosts.md
@@ -60,6 +60,25 @@
 child list and exact schemas are now present. The Server does not use
 probabilistic intent routing to guess a child.
 
+### Single-domain-first discipline
+
+A hierarchical Host should select one best-matching domain before it opens a
+manifest. It must not speculatively discover several domains merely to make an
+answer look more complete. This prevents repeated manifest payloads from being
+carried through every model step.
+
+- An unqualified current or historical cluster-operation question starts with
+  `doris_cluster` only.
+- Catalog, Query, Pipeline, and Governance are added only when the user
+  explicitly asks for object metadata, SQL/query history, ingestion, or
+  audit/lineage evidence.
+- For an unqualified cluster-history request, call
+  `analyze_resource_growth` without `resource`; the Server then preserves every
+  currently usable recorded series and reports unavailable series separately.
+
+This is a deterministic Host orchestration rule, not probabilistic Server-side
+routing. A later user turn can immediately select a different registered domain.
+
 ## Fast intent switching
 
 Example conversation:
diff --git a/docs/integrations/hosts.zh-CN.md b/docs/integrations/hosts.zh-CN.md
index bfecd7a..2ec7c77 100644
--- a/docs/integrations/hosts.zh-CN.md
+++ b/docs/integrations/hosts.zh-CN.md
@@ -52,6 +52,20 @@
 二级选择可以由模型完成,因为完整授权 Child 与精确 Schema 已经进入上下文。
 Server 不使用概率 Intent Routing 去猜 Child。
 
+### 单领域优先纪律
+
+Hierarchical Host 应先选择唯一最匹配的领域,再展开 Manifest。不能为了让答案看起来
+更完整而推测性地发现多个领域,否则每轮模型调用都会重复携带多份 Manifest。
+
+- 未限定范围的当前或历史集群运行问题,只从 `doris_cluster` 开始;
+- 只有用户明确要求对象元数据、SQL/查询历史、摄入任务或审计/血缘证据时,才增加
+  Catalog、Query、Pipeline 或 Governance;
+- 对未限定具体资源的集群历史问题,调用 `analyze_resource_growth` 时省略
+  `resource`,由 Server 保留当前所有可用记录序列,并分别报告不可用序列。
+
+这是确定性的 Host 编排规则,不是概率 Server-side Routing。用户下一轮改变问题后,
+仍可立即选择另一个已注册领域。
+
 ## 快速意图切换
 
 示例对话:
diff --git a/doris_mcp_server/tools/domain_catalog.py b/doris_mcp_server/tools/domain_catalog.py
index a515aaf..d15a490 100644
--- a/doris_mcp_server/tools/domain_catalog.py
+++ b/doris_mcp_server/tools/domain_catalog.py
@@ -1029,8 +1029,9 @@
     _domain(
         "doris_catalog",
         "Doris Catalog",
-        "Explore Doris catalogs, databases, tables, schemas, comments, indexes, "
-        "key models, storage metadata, and object sizes.",
+        "Object metadata only: catalogs, databases, tables, schemas, comments, "
+        "indexes, key models, storage metadata, and sizes. Excludes SQL results, "
+        "cluster operations, pipelines, governance, search, and semantics.",
         (
             _child(
                 "doris_catalog",
@@ -1125,9 +1126,9 @@
     _domain(
         "doris_query",
         "Doris Query",
-        "Execute read-only Doris SQL, inspect plans and profiles, review slow "
-        "queries, and use advanced ADBC only when the end user explicitly "
-        "requests ADBC or Arrow Flight SQL.",
+        "Read-only SQL only: execution, plans, profiles, slow-query history, and "
+        "diagnosis. ADBC requires an explicit end-user request. Excludes general "
+        "cluster operations, pipelines, object discovery, and governance.",
         (
             _child(
                 "doris_query",
@@ -1365,8 +1366,10 @@
     _domain(
         "doris_cluster",
         "Doris Cluster",
-        "Inspect Doris health, nodes, tasks, monitoring, memory, cache, "
-        "compaction, workload, compute groups, and runtime capabilities.",
+        "Default and sufficient for unqualified current or historical Doris "
+        "cluster-operation questions: health, nodes, tasks, metrics, memory, "
+        "cache, compaction, workloads, compute groups, resource growth, and "
+        "capabilities. Open other domains only when explicitly requested.",
         (
             _child(
                 "doris_cluster",
@@ -1506,7 +1509,9 @@
                 "analyze_resource_growth",
                 "Analyze resource growth",
                 "Analyze recorded resource-growth evidence without inventing "
-                "missing history.",
+                "missing history. For an unqualified cluster-history request, "
+                "omit resource so every currently usable recorded series is "
+                "attempted and partial evidence is preserved.",
                 _input_schema(
                     {
                         "resource": _string(
@@ -1550,8 +1555,10 @@
     _domain(
         "doris_pipeline",
         "Doris Pipeline",
-        "Inspect ingestion, load health, materialized views, data freshness, "
-        "and upstream or downstream dependencies.",
+        "Data-pipeline operations only: ingestion and load health, "
+        "materialized-view refresh, data freshness, and dependencies. Excludes "
+        "general cluster health or history, arbitrary SQL, catalog discovery, "
+        "audit, and lineage.",
         (
             _child(
                 "doris_pipeline",
@@ -1676,8 +1683,9 @@
     _domain(
         "doris_search",
         "Doris Search",
-        "Search Doris data with text, vector, or hybrid retrieval and inspect "
-        "or diagnose search analyzers and indexes.",
+        "Search only: text, vector, or hybrid retrieval plus analyzer and index "
+        "diagnosis. Excludes catalog discovery, arbitrary SQL, cluster "
+        "operations, pipelines, governance, and semantic metrics.",
         (
             _child(
                 "doris_search",
@@ -1751,8 +1759,10 @@
     _domain(
         "doris_governance",
         "Doris Governance",
-        "Analyze columns and storage, inspect lineage and access evidence, "
-        "read audit metadata, and review UDF or authentication mappings.",
+        "Data governance only: column or table analysis, lineage, audit and "
+        "access evidence, UDF metadata, and authentication mappings. Excludes "
+        "general cluster operation or history, SQL performance, pipelines, and "
+        "catalog discovery.",
         (
             _child(
                 "doris_governance",
@@ -1904,8 +1914,10 @@
     _domain(
         "doris_lakehouse",
         "Doris Lakehouse",
-        "Inspect external catalogs, lakehouse tables, snapshots, partitions, "
-        "pushdown behavior, and Variant semi-structured columns.",
+        "External lakehouse objects only: catalogs, tables, snapshots, "
+        "partitions, pushdown, and Variant columns. Excludes internal catalog "
+        "discovery, arbitrary SQL, cluster operations, pipelines, governance, "
+        "and semantics.",
         (
             _child(
                 "doris_lakehouse",
@@ -1973,9 +1985,10 @@
     _domain(
         "doris_semantic",
         "Doris Semantic",
-        "Discover and use validated Ossie or MetricFlow models bound to Doris. "
-        "MetricFlow compiles SQL only; MCP executes it through the guarded "
-        "Doris Query runtime.",
+        "Semantic models only: an exact authorized Ossie or MetricFlow model is "
+        "required. MetricFlow compiles SQL; MCP executes it through guarded Doris "
+        "Query. Never infer a model. Excludes ordinary schema, SQL, and cluster "
+        "requests.",
         (
             _child(
                 "doris_semantic",
diff --git a/doris_mcp_server/tools/domain_manifest.py b/doris_mcp_server/tools/domain_manifest.py
index f4cca7a..2de94ed 100644
--- a/doris_mcp_server/tools/domain_manifest.py
+++ b/doris_mcp_server/tools/domain_manifest.py
@@ -56,9 +56,9 @@
 MANIFEST_FORMAT_VERSION = "1"
 
 DOMAIN_DISCOVERY_DESCRIPTION_SUFFIX = (
-    " Call with an empty object to discover the exact child tools and schemas "
-    "available in this domain. Then call this same tool with the returned "
-    "child_tool name and arguments."
+    " Choose one matching domain; do not speculatively open others. Call with "
+    "an empty object to discover its exact child tools and schemas, then call "
+    "it again with the returned child_tool and arguments."
 )
 
 DOMAIN_TOOL_INPUT_SCHEMA: dict[str, Any] = {
diff --git a/test/tools/test_domain_dispatcher.py b/test/tools/test_domain_dispatcher.py
index 11ea194..3cbedef 100644
--- a/test/tools/test_domain_dispatcher.py
+++ b/test/tools/test_domain_dispatcher.py
@@ -164,6 +164,8 @@
         "query_volume",
         "user_activity",
     )
+    assert "unqualified cluster-history request" in child.canonical_description
+    assert "omit resource" in child.canonical_description
 
 
 @pytest.mark.asyncio
diff --git a/test/tools/test_domain_manifest.py b/test/tools/test_domain_manifest.py
index 8b2cd0a..ef9e25d 100644
--- a/test/tools/test_domain_manifest.py
+++ b/test/tools/test_domain_manifest.py
@@ -149,6 +149,7 @@
     for tool in tools:
         assert tool.description is not None
         assert tool.description.endswith(DOMAIN_DISCOVERY_DESCRIPTION_SUFFIX)
+        assert "do not speculatively open others" in tool.description
         assert tool.annotations is not None
         assert tool.annotations.read_only_hint is True
         assert tool.annotations.destructive_hint is False
@@ -157,6 +158,20 @@
         for child_name in EXPECTED_DOMAIN_CHILDREN[tool.name]:
             assert child_name not in tool.description
 
+    descriptions = {tool.name: tool.description for tool in tools}
+    assert "Default and sufficient for unqualified" in descriptions[
+        "doris_cluster"
+    ]
+    assert "current or historical Doris cluster-operation" in descriptions[
+        "doris_cluster"
+    ]
+    assert "Excludes general cluster operation or history" in descriptions[
+        "doris_governance"
+    ]
+    assert "Excludes general cluster health or history" in descriptions[
+        "doris_pipeline"
+    ]
+
 
 def test_manager_mixin_lazily_builds_discovery_service() -> None:
     manager = DomainManifestManagerMixin()