diff --git a/.gitignore b/.gitignore index 6a58d60..c7607e1 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ kuttl-report-openstack-lightspeed.xml # Vulnerability scan output vuln_output.json + +# Sphinx docs build artifacts +/docs/_build +/docs/.venv +__pycache__/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..897fc7c --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +python: + install: + - requirements: docs/requirements.txt diff --git a/Makefile b/Makefile index cf00dab..526f960 100644 --- a/Makefile +++ b/Makefile @@ -430,3 +430,30 @@ catalog-build: opm ## Build a catalog image. .PHONY: catalog-push catalog-push: ## Push a catalog image. $(MAKE) docker-push IMG=$(CATALOG_IMG) + +##@ Documentation + +DOCS_VENV = docs/.venv +DOCS_PYTHON = $(DOCS_VENV)/bin/python + +.PHONY: .docs-venv +.docs-venv: + if ! command -v python3 > /dev/null; then \ + echo "python3 not found. Install Python 3 to build the docs."; \ + exit 1; \ + fi + test -d $(DOCS_VENV) || python3 -m venv $(DOCS_VENV) + $(DOCS_PYTHON) -m pip install --quiet --upgrade pip + $(DOCS_PYTHON) -m pip install --quiet -r docs/requirements.txt + +.PHONY: docs +docs: .docs-venv ## Build docs (Sphinx, matching the Read the Docs build) + $(DOCS_VENV)/bin/sphinx-build -W -b html docs docs/_build/html + +.PHONY: docs-preview +docs-preview: docs ## Build docs and open them in a browser + open docs/_build/html/index.html || xdg-open docs/_build/html/index.html + +.PHONY: docs-clean +docs-clean: ## Remove built docs + rm -rf docs/_build diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1daabf9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,12 @@ +# Configuration file for the Sphinx documentation builder. +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +project = "OpenStack Lightspeed Operator" +copyright = "OpenStack Lightspeed contributors" +author = "OpenStack Lightspeed contributors" + +extensions = ["sphinxcontrib.mermaid"] + +exclude_patterns = ["_build", ".venv", "Thumbs.db", ".DS_Store"] + +html_theme = "sphinx_rtd_theme" diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 0000000..a17bcb3 --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,200 @@ +Configuration +============= + +Everything is configured through the ``OpenStackLightspeed`` custom +resource (``lightspeed.openstack.org/v1beta1``). This page documents every +field in its ``spec``. + +Core fields +----------- + +.. list-table:: + :header-rows: 1 + :widths: 20 10 70 + + * - Field + - Required + - Description + * - ``llmEndpoint`` + - Yes + - URL of the LLM endpoint (e.g. ``https://api.openai.com/v1``). Must + start with ``http://`` or ``https://``. + * - ``llmEndpointType`` + - Yes + - Provider type. See :ref:`supported-providers`. + * - ``modelName`` + - Yes + - Model name to use at ``llmEndpoint``. + * - ``llmCredentials`` + - Yes + - ``Secret`` name (same namespace) with the API token under key + ``apitoken``. + * - ``tlsCACertBundle`` + - No + - ``ConfigMap`` name (same namespace) with a CA bundle, for + self-signed endpoints. + * - ``maxTokensForResponse`` + - No + - Max response tokens. Minimum ``1``. Defaults to ``2048``. + * - ``llmProjectID`` + - No + - Required by some providers (e.g. WatsonX). + * - ``llmDeploymentName`` + - No + - Required by some providers (e.g. Azure OpenAI). + * - ``llmAPIVersion`` + - No + - Required by some providers (e.g. Azure OpenAI). + * - ``feedbackEnabled`` + - No + - User feedback collection. Defaults to ``true``. + * - ``transcriptsEnabled`` + - No + - Conversation transcript collection. Defaults to ``false``. + +.. _supported-providers: + +Supported LLM providers (``llmEndpointType``) +------------------------------------------------ + +* ``openai`` — OpenAI-compatible endpoints (Ollama, vLLM, etc.) +* ``azure_openai`` — Azure OpenAI (needs ``llmDeploymentName``, ``llmAPIVersion``) +* ``watsonx`` — IBM watsonx.ai (needs ``llmProjectID``) +* ``rhoai_vllm`` — vLLM via Red Hat OpenShift AI +* ``rhelai_vllm`` — vLLM via RHEL AI +* ``gemini`` — Google Gemini + +.. tip:: + + This list is enforced by the CRD schema and grows over time. Check + ``oc explain openstacklightspeed.spec.llmEndpointType`` on your cluster + for the current, authoritative list. + +Logging (``logging``) +----------------------- + +.. list-table:: + :header-rows: 1 + :widths: 25 15 60 + + * - Field + - Default + - Description + * - ``logging.ogxLogLevel`` + - ``all=info`` + - llama-stack/OGX container. Standard level, or + ``component=level`` pairs (e.g. ``core=debug,providers=info``). + * - ``logging.lightspeedStackLogLevel`` + - ``INFO`` + - lightspeed-service-api container. ``DEBUG``/``INFO``/``WARNING``/``ERROR``/``CRITICAL``. + * - ``logging.dataverseExporterLogLevel`` + - ``INFO`` + - Feedback/transcript exporter sidecar. Same values as above. + * - ``logging.postgresLogLevel`` + - ``INFO`` + - PostgreSQL container. ``DEBUG`` also logs every SQL statement. + +Persistent storage (``database``) +------------------------------------ + +Omit for an ``emptyDir`` volume (data lost on pod reschedule), or set to +provision a PVC: + +.. code-block:: yaml + + spec: + database: + size: "5Gi" # default: 1Gi + class: "my-storage-class" # default: cluster's default StorageClass + +Container resources (``resources``) +-------------------------------------- + +Every container has a default request/limit. Setting one replaces its +default entirely: + +.. code-block:: yaml + + spec: + resources: + llamaStack: + requests: {cpu: "500m", memory: "2Gi"} + limits: {cpu: "2", memory: "8Gi"} + lightspeedService: + requests: {cpu: "250m", memory: "512Mi"} + limits: {cpu: "1", memory: "2Gi"} + postgres: + requests: {cpu: "30m", memory: "300Mi"} + limits: {cpu: "500m", memory: "2Gi"} + okp: + requests: {cpu: "500m", memory: "2Gi"} + limits: {cpu: "2", memory: "4Gi"} + consolePlugin: + requests: {cpu: "50m", memory: "64Mi"} + limits: {cpu: "200m", memory: "256Mi"} + mcp: + requests: {cpu: "50m", memory: "64Mi"} + limits: {memory: "200Mi"} + +.. _offline-knowledge-portal: + +Offline Knowledge Portal (``okp``) +-------------------------------------- + +.. important:: + + OKP is deployed on **every** install — ``spec.okp`` configures it, it + doesn't gate whether it's deployed. Pulling its image needs the same + free ``registry.redhat.io`` account as :ref:`redhat-registry-access`. + +.. code-block:: yaml + + spec: + okp: {} # no access key: browsing works, search doesn't + +.. code-block:: yaml + + spec: + okp: + accessKey: okp-access-key-secret # Secret key: "access_key" + +* **No ``accessKey``** (default) — browse docs and product lifecycle + content; no search, Solutions, or Articles. What upstream users run on. +* **With ``accessKey``** — full search and the encrypted knowledgebase. + Needs an active Red Hat Satellite subscription (`get one + `_) — a bonus if you already + have one, not something every user needs. + +By default, **RAG grounding is OKP-only** — the bundled community +documentation is disabled unless you set ``dev.okpRagOnly: false`` (below). + +Developer / experimental options (``dev``) +----------------------------------------------- + +.. warning:: + + Not part of the stable API — may change without notice. + +.. code-block:: yaml + + spec: + dev: + featureFlags: + - rhoso_mcps # enables the read-only MCP introspection sidecar + okpChunkFilterQuery: "product:(*openstack* OR *openshift*)" # example override + okpRagOnly: false # include bundled community docs too, not just OKP + rhosMCPConfig: | + debug: true + workers: 4 + +* ``okpChunkFilterQuery`` and ``okpRagOnly`` take effect immediately, with + no ``featureFlags`` entry needed — they're independent of + ``rhoso_mcps``. If unset, ``okpChunkFilterQuery`` auto-detects your + OpenShift/RHOSO versions instead of using the literal example above. +* ``rhoso_mcps`` — the one flag that does need to be set. Deploys the MCP + introspection sidecar, which is read-only **by default**. See + :doc:`usage`. +* ``rhosMCPConfig`` is deep-merged on top of the operator's own defaults + — it can override anything the default config sets, including the + ``allow_write`` flags that keep introspection read-only. Only set this + if you understand exactly what you're overriding. diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..aa7cbb2 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,68 @@ +Development +============ + +This page is for contributors and anyone testing changes locally — not +needed if you're just installing and using the operator. + +.. _dont-have-a-cluster-yet-crc: + +Local cluster (CRC) +----------------------- + +For local development/testing only (not for trying the assistant for real +— CRC is resource-constrained). Deploy a CRC cluster before +:ref:`installing-the-operator`: + +.. code-block:: bash + + git clone https://github.com/openstack-k8s-operators/install_yamls.git + cd install_yamls/devsetup + make download_tools + + CRC_VERSION=2.51.0 PULL_SECRET=~/work/pull-secret CRC_MONITORING_ENABLED=true CPUS=12 MEMORY=25600 DISK=100 make crc + make crc_attach_default_interface + eval $(crc oc-env) + cd ../.. + +``PULL_SECRET`` is the same pull secret from :ref:`redhat-registry-access`. + +CRC's console is always at a fixed address: +`console-openshift-console.apps-crc.testing +`_ — not something you +look up with ``oc whoami --show-console``. + +Running CRC remotely? Reach that console with ``sshuttle``: + +* Add to your local ``/etc/hosts`` (keep the IP as-is): + ``192.168.130.11 api.crc.testing canary-openshift-ingress-canary.apps-crc.testing console-openshift-console.apps-crc.testing default-route-openshift-image-registry.apps-crc.testing downloads-openshift-console.apps-crc.testing oauth-openshift.apps-crc.testing`` +* Run ``sshuttle -r $remote_username@$remote_server 192.168.130.0/24``. + +Architecture +--------------- + +.. mermaid:: + + graph TB + User[System Administrator] -->|uses console widget| Plugin + + subgraph ns["openstack-lightspeed namespace"] + CR[OpenStackLightspeed CR] --> Operator[lightspeed-operator] + Operator --> Plugin[Console Plugin] + Operator --> DB[(PostgreSQL)] + Operator --> OKP[OKP] + Operator --> Pod + + subgraph Pod["lightspeed-stack pod"] + API[lightspeed-service-api] --> OGX[llama-stack] + OGX -.-> MCP[MCP tools sidecar] + end + end + + Plugin --> API + OGX --> OKP + OGX --> LLM[Your LLM endpoint] + MCP -.->|read-only, optional| OSP[Your OpenStack / OpenShift APIs] + +**OKP is deployed on every install, not opt-in.** It's the default RAG +source; the bundled community documentation is available too, but only if +you explicitly opt in. See :doc:`configuration` for details. diff --git a/docs/images/.gitkeep b/docs/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..88ae2ec --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,38 @@ +OpenStack Lightspeed Operator documentation +============================================ + +OpenStack Lightspeed is an AI-powered assistant, tailored for Red Hat +OpenStack Services on OpenShift (RHOSO), that lives inside the OpenShift +web console and answers questions in plain English — grounded in real +documentation, not guesses. + +Ask it something like *"How do I create a VM using the OpenStack CLI?"* or +*"Why would a Nova compute service show as down?"* — see :doc:`usage` for +more. + +You don't need an existing RHOSO deployment to try it — an OpenShift +cluster and an LLM you can point it at is enough (see :doc:`quickstart`). + +.. important:: + + This is a community release. Support is provided **upstream only**, + via GitHub Issues — there is no separate commercial support channel + for this project: + + * `lightspeed-operator issues `_ + * `lightspeed-rag-content issues `_ + * `lightspeed-mcps issues `_ + + See :doc:`troubleshooting` for what to check, and what to include, + before filing an issue. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + quickstart + install_guide + configuration + development + troubleshooting + usage diff --git a/docs/install_guide.rst b/docs/install_guide.rst new file mode 100644 index 0000000..f5ef7b8 --- /dev/null +++ b/docs/install_guide.rst @@ -0,0 +1,190 @@ +Installation Guide +=================== + +This page covers prerequisites, installing the operator, setting up LLM +credentials, and deploying ``OpenStackLightspeed``. No cluster yet? See +:doc:`development`. + +Prerequisites +------------- + +* An OpenShift cluster (4.16+). + + .. warning:: + + Known issue: the console UI does not currently work on OpenShift 4.20 + or newer. Stick to 4.18 until this is resolved upstream. + +* An LLM endpoint and API key — any provider from + :ref:`supported-providers` works. +* A free Red Hat Developer account, to pull some images from + ``registry.redhat.io`` — see :ref:`redhat-registry-access` below. +* Optional: RHOSO installed (so an ``OpenStackControlPlane`` exists), + only needed for the experimental cluster-introspection feature + (:doc:`usage`). + +.. _redhat-registry-access: + +Access to registry.redhat.io images +------------------------------------ + +The console plugin and OKP images (both always deployed) come from +``registry.redhat.io`` rather than ``quay.io``. This requires a **free** +account, not a paid subscription: + +#. Create a free account at `developers.redhat.com + `_. +#. Download a pull secret from the `Hybrid Cloud Console + `_. +#. Add it to your cluster: + + * **CRC**: pass it as ``PULL_SECRET`` when creating the cluster — see + :ref:`dont-have-a-cluster-yet-crc`. + * **Existing cluster**: merge it into the cluster-wide pull secret: + + .. code-block:: bash + + oc get secret/pull-secret -n openshift-config -o jsonpath='{.data.\.dockerconfigjson}' \ + | base64 -d > pull-secret.json + # merge the downloaded auths into pull-secret.json, then: + oc set data secret/pull-secret -n openshift-config \ + --from-file=.dockerconfigjson=pull-secret.json + +#. Optional quick sanity check of your credentials (this only checks + *your* machine, not the cluster): + + .. code-block:: console + + $ podman login registry.redhat.io + Login Succeeded! + +#. Verify the cluster itself can pull, using its own pull secret: + + .. code-block:: console + + $ oc run registry-pull-test --image=registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12 --restart=Never + $ oc get pod registry-pull-test + + Any status other than ``ImagePullBackOff``/``ErrImagePull`` means it's + working — clean up with ``oc delete pod registry-pull-test``. If you do + see it, the secret from the previous step didn't propagate — see + :ref:`console-widget-not-appearing`. + +.. _installing-the-operator: + +Installing the operator +------------------------ + +#. **Operators → OperatorHub**, search for **"OpenStack Lightspeed + (Community)"**. +#. Click **Install**, choosing the ``openstack-lightspeed`` namespace. +#. Track progress under **Operators → Installed Operators**, or: + + .. code-block:: console + + $ oc get -n openstack-lightspeed pods + NAME READY STATUS RESTARTS AGE + openstack-lightspeed-operator-controller-manager-76df7fbfb5wggr 1/1 Running 0 72s + +.. note:: + + Currently published for OpenShift 4.16 and 4.18 specifically — on + other versions it won't appear in OperatorHub search. Use the + alternative below instead. + +**Alternative — deploy from source** (for testing an unreleased build, or +if your OpenShift version isn't in the catalog yet): + +.. code-block:: bash + + git clone https://github.com/openstack-k8s-operators/lightspeed-operator.git + cd lightspeed-operator + make openstack-lightspeed-deploy + +This sets up its own ``CatalogSource``, namespace, and ``Subscription`` — +bypassing OperatorHub entirely. + +Setting up LLM credentials +---------------------------- + +You need an API key, endpoint URL, and model name. + +Create the API key secret — the key **must** be named ``apitoken``: + +.. code-block:: bash + + oc apply -f - < + EOF + +Using a self-hosted endpoint with a self-signed certificate (e.g. vLLM, +Ollama)? Add its CA bundle too — any key name works, PEM data is all +that's parsed: + +.. code-block:: bash + + oc apply -f - <:/v1 + llmEndpointType: + llmCredentials: openstack-lightspeed-apitoken + modelName: + tlsCACertBundle: openstack-lightspeed-certs # optional + +This deploys the full stack: the AI engine (lightspeed-stack and +llama-stack/OGX), PostgreSQL, OKP, and the console plugin. + +Verifying the deployment +--------------------------- + +.. code-block:: bash + + oc describe -n openstack-lightspeed openstacklightspeed + oc get -n openstack-lightspeed deployments,pods + +Not reaching ``Ready``? See :doc:`troubleshooting`. + +Accessing the assistant +--------------------------- + +.. code-block:: bash + + oc whoami --show-console + +Open that URL and use the Lightspeed widget (lower-right corner). First +time activating the plugin, you may need to click **refresh** on the +console notification that appears. diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..0123c7a --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,65 @@ +Quickstart +========== + +Already have an OpenShift cluster and an LLM endpoint? Three steps and +you're running. No cluster yet? See :ref:`dont-have-a-cluster-yet-crc`. + +Install the operator +------------------------ + +**Operators → OperatorHub**, search **"OpenStack Lightspeed +(Community)"**, click **Install**. Currently published for OpenShift 4.16 +and 4.18 — on other versions, or if it's not showing up, see +:doc:`install_guide` for the source-based alternative. + +Create the secret and CR +------------------------------ + +Save as ``secret.yaml``, with your own LLM API key: + +.. code-block:: yaml + + apiVersion: v1 + kind: Secret + type: Opaque + metadata: + name: openstack-lightspeed-apitoken + namespace: openstack-lightspeed + stringData: + apitoken: + +Save as ``cr.yaml``, with your own endpoint, model, and provider type +(see :ref:`supported-providers` for valid values): + +.. code-block:: yaml + + apiVersion: lightspeed.openstack.org/v1beta1 + kind: OpenStackLightspeed + metadata: + name: openstack-lightspeed + namespace: openstack-lightspeed + spec: + llmEndpoint: https://:/v1 + llmEndpointType: + llmCredentials: openstack-lightspeed-apitoken + modelName: + +Then apply both: + +.. code-block:: bash + + oc apply -f secret.yaml + oc apply -f cr.yaml + +Self-hosted endpoint with a self-signed certificate? See +:doc:`install_guide` and :doc:`configuration` for the full field +reference. + +Open the console +--------------------- + +.. code-block:: bash + + oc whoami --show-console + +Open that URL and use the Lightspeed widget (lower-right corner). diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..2d2f3d9 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx>=7.0,<8.0 +sphinx-rtd-theme>=2.0,<3.0 +sphinxcontrib-mermaid>=0.9,<1.0 diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000..fd62a1d --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,98 @@ +Troubleshooting +=============== + +Common failures, how to diagnose them, and where to get help if none of +this resolves it. + +Start with the resource's conditions +---------------------------------------- + +.. code-block:: bash + + oc describe -n openstacklightspeed + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Condition + - Meaning + * - ``OpenStackLightspeedReady`` + - Overall readiness. ``False``/``Unknown``: engine, database, OKP, or + console plugin hasn't converged yet. + * - ``OpenStackLightspeedMCPServerReady`` + - Only relevant with ``rhoso_mcps`` enabled. Tracks the MCP sidecar. + +Deployment-specific issues +------------------------------ + +lightspeed-stack (engine) pod not becoming healthy ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: bash + + oc logs -n deploy/lightspeed-stack-deployment -c lightspeed-service-api + oc logs -n deploy/lightspeed-stack-deployment -c llama-stack + +Usual causes: bad/unreachable ``llmEndpoint``, invalid ``apitoken``, or a +missing ``tlsCACertBundle`` for a self-signed endpoint. llama-stack logs +the actual auth/TLS error from the provider. + +PostgreSQL pod not starting +++++++++++++++++++++++++++++ + +.. code-block:: bash + + oc logs -n deploy/lightspeed-postgres-server + +Shrinking ``spec.database.size`` below the existing PVC is rejected (not +supported in place). Revert the size, or delete/recreate the PVC to +actually shrink it (loses data). + +.. _console-widget-not-appearing: + +Console widget not appearing ++++++++++++++++++++++++++++++ + +* Confirm the ``ConsolePlugin`` (``lightspeed-console-plugin``) exists and + is listed under ``spec.plugins`` on ``oc get + console.operator.openshift.io cluster -o yaml``. +* Newly-activated plugins need a moment — click **refresh** on the + console notification. +* Check the plugin's pod logs for TLS errors — its service-ca certificate + can take a few seconds to appear after first deploy. + +.. _imagepullbackoff: + +ImagePullBackOff on any operator-managed pod ++++++++++++++++++++++++++++++++++++++++++++++ + +The console plugin and OKP pods come from ``registry.redhat.io``, not +``quay.io``: + +.. code-block:: console + + Failed to pull image "registry.redhat.io/...": unauthorized: Please login to the Red Hat Registry using your Customer Portal credentials. + +Means the pull secret is missing ``registry.redhat.io`` credentials — see +:ref:`redhat-registry-access` to fix and verify with Podman. + +CA bundle errors +++++++++++++++++++ + +If ``tlsCACertBundle`` causes a CA parsing error, check every key in the +ConfigMap's ``data`` for valid PEM data and no stray whitespace (all keys +are parsed, not just one named ``cert``). + +Getting operator logs +------------------------- + +.. code-block:: bash + + oc logs -n deploy/openstack-lightspeed-operator-controller-manager + +Still stuck? See :doc:`index` for the repos to file an issue against. +When filing one, include ``oc describe -n openstack-lightspeed +openstacklightspeed`` output, pod logs, and your CR spec — **redact API +tokens, endpoint URLs/hostnames, and any retrieved context from all +three** before posting, since these are public issue trackers. diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..cbb1938 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,47 @@ +Available Features +==================== + +Day-to-day use: asking questions, optional cluster introspection, and +feedback/transcripts. + +Asking questions +-------------------- + +Open the Lightspeed widget (bottom-right corner of the OpenShift console) +once ``OpenStackLightspeed`` is ``Ready``: + +* "How can I spin up a VM using the OpenStack CLI?" +* "Why would a Nova compute service show as down?" + +Answers are grounded via RAG, with references you can verify. By default, +grounding comes from :ref:`offline-knowledge-portal` (always deployed, no +credentials needed to browse — see :doc:`configuration` for the free vs. +keyed tiers). The bundled community documentation is also available, but +only if you set ``dev.okpRagOnly: false``. + +Cluster introspection (optional) +------------------------------------ + +Enabling the ``rhoso_mcps`` dev flag (:doc:`configuration`) gives the +assistant read-only tools to inspect your actual OpenStack/OpenShift +resources instead of relying on docs alone. + +* **Strictly read-only by default** — only list/get/describe-style + ``openstack`` and ``oc`` commands are exposed as tools; nothing that + creates, updates, or deletes resources is available to the assistant + out of the box. +* Introspection stays local to your cluster; only the query and retrieved + context go to your LLM provider. +* Credentials are automatic — the operator provisions a scoped Keystone + Application Credential when an ``OpenStackControlPlane`` is detected. + +Disabled by default; still evolving. + +Feedback and transcripts +---------------------------- + +* ``feedbackEnabled`` (default ``true``) — thumbs-up/down on responses. +* ``transcriptsEnabled`` (default ``false``) — full conversation transcripts. + +Both configured on the CR (:doc:`configuration`). Used to improve answer +quality — disable either if that doesn't fit your data policy.