Require SolrCloud mode: enforce at runtime and document clearly (#43)
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 247d47e..2643d88 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -17,10 +17,14 @@
 
 ## Set up a Solr cluster
 
+{: .important }
+Solr Orbit requires Solr to run in **SolrCloud mode**. Standalone/user-managed mode is not supported. For Solr 9.x, start Solr with the `-c` flag to enable SolrCloud mode. For Solr 10.0.0 and later, SolrCloud is the default and no extra flag is required.
+
 If you don't already have a running Solr cluster, the easiest way to start one is with Docker:
 
 ```bash
-docker run -d --name solr-orbit -p 8983:8983 solr:9 solr-demo
+# Solr 9.x — the -c flag enables SolrCloud mode
+docker run -d --name solr-orbit -p 8983:8983 solr:9 -c
 ```
 
 Verify that Solr is running by opening [http://localhost:8983/solr/](http://localhost:8983/solr/) in your browser, or with:
diff --git a/docs/reference/workloads/test-procedures.md b/docs/reference/workloads/test-procedures.md
index 8cf439b..a4db6b9 100644
--- a/docs/reference/workloads/test-procedures.md
+++ b/docs/reference/workloads/test-procedures.md
@@ -60,6 +60,9 @@
 
 ## Selecting a test procedure at run time
 
+{: .important }
+The target Solr cluster must be running in **SolrCloud (ZooKeeper) mode**. For Solr 9.x, start with `-c` (e.g. `docker run -d -p 8983:8983 solr:9 -c`). For Solr 10.0.0+, SolrCloud is the default.
+
 ```bash
 solr-orbit run \
   --pipeline benchmark-only \
diff --git a/docs/user-guide/working-with-workloads/running-workloads.md b/docs/user-guide/working-with-workloads/running-workloads.md
index bb7eb2e..3af8fd3 100644
--- a/docs/user-guide/working-with-workloads/running-workloads.md
+++ b/docs/user-guide/working-with-workloads/running-workloads.md
@@ -29,6 +29,9 @@
   [--workload WORKLOAD | --workload-path PATH] [OPTIONS]
 ```
 
+{: .important }
+When using the `benchmark-only` pipeline, the target Solr cluster must be running in **SolrCloud (ZooKeeper) mode** — standalone/user-managed mode is not supported. For Solr 9.x, start Solr with the `-c` flag (e.g. `bin/solr start -c` or `docker run -d -p 8983:8983 solr:9 -c`). For Solr 10.0.0 and later, SolrCloud is the default.
+
 ---
 
 ## Using a named workload
diff --git a/solrorbit/client.py b/solrorbit/client.py
index b872003..7353871 100644
--- a/solrorbit/client.py
+++ b/solrorbit/client.py
@@ -330,6 +330,38 @@
             return resp.text
         return resp.json()
 
+    def is_cloud_mode(self) -> bool:
+        """
+        Return True if this Solr node is running in SolrCloud (ZooKeeper) mode.
+
+        Calls the Collections CLUSTERSTATUS API, which only succeeds in cloud mode.
+        Returns False if Solr responds with a 400 indicating standalone/user-managed mode.
+        Raises SolrClientError on connection errors or unexpected failures.
+        """
+        try:
+            resp = self._get_session().get(
+                f"{self.base_url}/solr/admin/collections",
+                params={"action": "CLUSTERSTATUS", "wt": "json"},
+                timeout=self.timeout,
+            )
+        except requests.exceptions.RequestException as exc:
+            raise SolrClientError(
+                f"Could not connect to Solr at {self.base_url}: {exc}"
+            ) from exc
+        if resp.ok:
+            return True
+        # HTTP 400 with a message about SolrCloud/user-managed → definitely standalone mode
+        body = self._try_parse_json(resp)
+        msg = str(body.get("error", {}).get("msg", "")).lower()
+        if resp.status_code == 400 and any(
+            kw in msg for kw in ("solrcloud", "user-managed", "collections api")
+        ):
+            return False
+        # Auth error, unexpected status, etc. — surface to caller
+        raise SolrClientError(
+            f"Unexpected response from CLUSTERSTATUS (HTTP {resp.status_code}): {resp.text[:300]}"
+        )
+
     # ------------------------------------------------------------------
     # Raw request (for the raw-request workload operation)
     # ------------------------------------------------------------------
@@ -478,6 +510,9 @@
     def wait_for_cluster_ready(self, **kwargs):
         return self._admin.wait_for_cluster_ready(**kwargs)
 
+    def is_cloud_mode(self) -> bool:
+        return self._admin.is_cloud_mode()
+
     def raw_request(self, method, path, body=None, headers=None):
         return self._admin.raw_request(method, path, body=body, headers=headers)
 
diff --git a/solrorbit/test_run_orchestrator.py b/solrorbit/test_run_orchestrator.py
index 7ff1af4..9b6f02c 100644
--- a/solrorbit/test_run_orchestrator.py
+++ b/solrorbit/test_run_orchestrator.py
@@ -377,9 +377,45 @@
         cfg.add(config.Scope.benchmark, "client", "hosts", default_host_object)
 
 
+def _check_cloud_mode(cfg):
+    """Fail fast with a clear error if the target Solr cluster is not in SolrCloud mode."""
+    from solrorbit.client import SolrAdminClient, SolrClientError  # local import to avoid circular
+
+    configured_hosts = cfg.opts("client", "hosts")
+    hosts = configured_hosts.default
+    if not hosts:
+        return
+
+    entry = hosts[0]
+    if isinstance(entry, dict):
+        host = entry.get("host", "localhost")
+        port = int(entry.get("port", 8983))
+    else:
+        parts = str(entry).rsplit(":", 1)
+        host = parts[0]
+        port = int(parts[1]) if len(parts) == 2 and parts[1].isdigit() else 8983
+
+    client = SolrAdminClient(host=host, port=port)
+    try:
+        if not client.is_cloud_mode():
+            raise exceptions.SystemSetupError(
+                f"Solr at {host}:{port} is not running in SolrCloud (ZooKeeper) mode.\n"
+                "Solr Orbit currently only supports SolrCloud mode.\n"
+                "  \u2022 For Solr 9.x: start with the '-c' flag, e.g.:\n"
+                "      docker run -d -p 8983:8983 solr:9 -c\n"
+                "      bin/solr start -c\n"
+                "  \u2022 For Solr 10.0.0+: SolrCloud is the default; no extra flag is needed."
+            )
+    except SolrClientError as exc:
+        raise exceptions.SystemSetupError(
+            f"Could not verify Solr mode at {host}:{port}: {exc}"
+        ) from exc
+
+
 # Poor man's curry
 def benchmark_only(cfg):
     set_default_hosts(cfg)
+    _check_cloud_mode(cfg)
     return run_test(cfg, external=True)
 
 
diff --git a/tests/unit/solr/test_client.py b/tests/unit/solr/test_client.py
index c89ddf8..bf2e221 100644
--- a/tests/unit/solr/test_client.py
+++ b/tests/unit/solr/test_client.py
@@ -241,5 +241,56 @@
         self.assertTrue(any("solrconfig.xml" in n for n in names))
 
 
+class TestSolrAdminClientIsCloudMode(unittest.TestCase):
+    def _make_client_with_mock_session(self):
+        client = SolrAdminClient("localhost")
+        client._session = MagicMock()
+        return client
+
+    def test_returns_true_when_clusterstatus_succeeds(self):
+        client = self._make_client_with_mock_session()
+        resp = _make_response(status_code=200, json_data={"cluster": {"collections": {}}})
+        client._session.get.return_value = resp
+        self.assertTrue(client.is_cloud_mode())
+
+    def test_returns_false_when_solrcloud_not_running(self):
+        client = self._make_client_with_mock_session()
+        resp = _make_response(
+            status_code=400,
+            json_data={"error": {"msg": "Solr instance is not running in SolrCloud mode."}},
+            text='{"error": {"msg": "Solr instance is not running in SolrCloud mode."}}',
+        )
+        client._session.get.return_value = resp
+        self.assertFalse(client.is_cloud_mode())
+
+    def test_returns_false_when_user_managed_mode(self):
+        client = self._make_client_with_mock_session()
+        resp = _make_response(
+            status_code=400,
+            json_data={"error": {"msg": "Collections API is disabled in user-managed mode."}},
+            text='{"error": {"msg": "Collections API is disabled in user-managed mode."}}',
+        )
+        client._session.get.return_value = resp
+        self.assertFalse(client.is_cloud_mode())
+
+    def test_raises_on_unexpected_error_status(self):
+        client = self._make_client_with_mock_session()
+        resp = _make_response(
+            status_code=503,
+            json_data={},
+            text="Service Unavailable",
+        )
+        client._session.get.return_value = resp
+        with self.assertRaises(SolrClientError):
+            client.is_cloud_mode()
+
+    def test_raises_on_connection_error(self):
+        import requests as _requests
+        client = self._make_client_with_mock_session()
+        client._session.get.side_effect = _requests.exceptions.ConnectionError("refused")
+        with self.assertRaises(SolrClientError):
+            client.is_cloud_mode()
+
+
 if __name__ == "__main__":
     unittest.main()