diff --git a/.github/workflows/update-ref-docs.yaml b/.github/workflows/update-ref-docs.yaml index f19a48ba..73e46716 100644 --- a/.github/workflows/update-ref-docs.yaml +++ b/.github/workflows/update-ref-docs.yaml @@ -295,6 +295,9 @@ jobs: # read none of the 1.x-only conrefs. WITH_RUNTIME_IMAGES=false WITH_KMCP=false + # 0.x predates the environment variable registry: docs/env.md + # does not exist at v0.10.1, and no 0.x page documents env vars. + WITH_ENV_DOCS=false ;; 1.x) SECTION=reference @@ -315,6 +318,7 @@ jobs: KAGENT_CLI_LINK_PREFIX=reference/cli WITH_RUNTIME_IMAGES=true WITH_KMCP=true + WITH_ENV_DOCS=true ;; *) echo "Error: unknown line '$LINE'." @@ -341,6 +345,8 @@ jobs: echo "CLI_WEIGHT=$CLI_WEIGHT" echo "WITH_RUNTIME_IMAGES=$WITH_RUNTIME_IMAGES" echo "WITH_KMCP=$WITH_KMCP" + echo "WITH_ENV_DOCS=$WITH_ENV_DOCS" + echo "KAGENT_ENV_PAGE=docs-site/content/kagent/$LINE/$SECTION/env-vars.md" } >> $GITHUB_ENV echo "Line $LINE -> content/kagent/$LINE/$SECTION/, $API_DIR" @@ -721,6 +727,12 @@ jobs: } >> $GITHUB_ENV fi + if [ "$WITH_ENV_DOCS" = "true" ]; then + echo "ENV_DOCS_NOTE=The environment variable reference is regenerated from \`docs/env.md\` in the kagent checkout, which upstream generates from the registry in \`go/core/pkg/env\` and holds to it in CI. The \`testing\` section is dropped as repository-facing; pass \`--include-section testing\` to publish it." >> $GITHUB_ENV + else + echo "ENV_DOCS_NOTE=No environment variable reference is generated on a $LINE run: \`docs/env.md\` does not exist at that line's releases." >> $GITHUB_ENV + fi + - name: Flag whether the conrefs moved run: | set -euo pipefail @@ -1154,6 +1166,31 @@ jobs: --out-dir "$KMCP_CLI_DIR" \ --url-prefix "$KMCP_CLI_URL_PREFIX" + - name: Generate environment variable reference + if: env.WITH_ENV_DOCS == 'true' + # kagent generates docs/env.md from the registry in go/core/pkg/env, + # and its own CI fails the build when the two drift (make + # env-docs-check). So the checked-out file is authoritative and this + # step only reshapes it into a page; it does not re-derive anything + # from Go source, and needs no build. + run: | + set -euo pipefail + cd "$GITHUB_WORKSPACE/website" + SOURCE="$GITHUB_WORKSPACE/kagent/docs/env.md" + # Fail loudly rather than skipping. A missing file here means the + # release being documented dropped or moved the registry output, + # which is a change this job must not paper over by silently + # leaving the published page on its previous contents. + if [ ! -f "$SOURCE" ]; then + echo "Error: $SOURCE not found in the kagent checkout at $KAGENT_VERSION." + echo "kagent generates it with 'make env-docs'. If upstream moved or" + echo "removed it, update this step and scripts/generate-env-docs.py." + exit 1 + fi + python3 scripts/generate-env-docs.py \ + --source "$SOURCE" \ + --out "$KAGENT_ENV_PAGE" + - name: Audit generated CLI docs run: | cd "$GITHUB_WORKSPACE/website" @@ -1173,7 +1210,7 @@ jobs: signoff: true title: "Update kagent ${{ env.LINE }} reference docs at ${{ env.KAGENT_TAG }}${{ env.CONREFS_CHANGED && ' (version conrefs changed)' || '' }}" body: | - Regenerates the kagent **${{ env.LINE }}** API and Helm references, its CLI pages, and the version conrefs at a released tag. Nothing here was read from either repository's `main`: + Regenerates the kagent **${{ env.LINE }}** API and Helm references, its CLI and environment variable pages, and the version conrefs at a released tag. Nothing here was read from either repository's `main`: - **kagent**: [`${{ env.KAGENT_TAG }}`](https://github.com/${{ github.repository_owner }}/kagent/releases/tag/${{ env.KAGENT_TAG }}) (`${{ env.KAGENT_COMMIT }}`) ${{ env.KMCP_SOURCE_LINE }} @@ -1181,6 +1218,8 @@ jobs: ${{ env.LINE_SCOPE_NOTE }} + ${{ env.ENV_DOCS_NOTE }} + ${{ env.CONREF_NOTE }} ${{ env.RUNTIME_IMAGE_NOTE }} diff --git a/docs-site/content/kagent/1.x/reference/_index.md b/docs-site/content/kagent/1.x/reference/_index.md index 0019d898..c968209c 100644 --- a/docs-site/content/kagent/1.x/reference/_index.md +++ b/docs-site/content/kagent/1.x/reference/_index.md @@ -1,6 +1,6 @@ --- title: Reference -description: Look up the API and Helm reference, the built-in tool catalog, version support, FAQs, release notes, the glossary, and community links. +description: Look up the reference material for a kagent installation. weight: 100 author: kagent.dev --- diff --git a/docs-site/content/kagent/1.x/reference/env-vars.md b/docs-site/content/kagent/1.x/reference/env-vars.md new file mode 100644 index 00000000..5e2fb2d1 --- /dev/null +++ b/docs-site/content/kagent/1.x/reference/env-vars.md @@ -0,0 +1,227 @@ +--- +title: Environment variables +description: Look up the environment variables that each kagent component reads, including their types and defaults. +weight: 35 +--- + + + +Review the environment variables that a {{< reuse "kagent-docs/snippets/name-product.md" >}} installation reads, grouped by the component that reads them. + +Most of the following variables have a [Helm chart setting]({{< link path="reference/helm#values" >}}) that writes them for you, and the chart setting is the supported way to set them. Use a variable directly only whenever you run a component outside the cluster, such as `kagent db` against a database from your own machine, or when a variable has no chart setting. + +Default values describe the component on its own. Helm, or an agent's {{< gloss "Harness" >}}Harness{{< /gloss >}} `spec.env`, might supply a different value, so the default listed here is not the guaranteed value that a running installation might use. `(none)` means the variable has no fixed default, so read the description for what happens when it is unset. A variable that more than one component reads appears under each of them. + +Credentials, controller-generated runtime payloads, and internal process wiring are not configurable settings and are not listed. + +## Controller + +The kagent controller reads these variables at startup. Helm writes most of them into the controller ConfigMap, so prefer the matching chart value where one exists. Set the variable directly only for a setting the chart does not expose. For the chart values, see the [Helm reference]({{< link path="reference/helm#values" >}}). + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `KAGENT_AUTH_MODE` | String | `insecure` | Controller authentication mode: insecure or trusted-proxy. trusted-proxy requires an upstream credential-validating proxy and network isolation preventing bypass. | +| `KAGENT_AUTH_USER_ID_CLAIM` | String | `(none)` | JWT claim used for the caller identity in trusted-proxy mode. Empty uses sub; a missing or empty custom claim falls back to sub. | +| `KAGENT_CONTROLLER_NAME` | String | `kagent-controller` | Name of the kagent controller service. | +| `KAGENT_DATABASE_VECTOR_ENABLED` | Boolean | `false` | Enable vector database migrations and vector-backed database functionality. The controller defaults to false. When unset in the CLI, migrations read the controller ConfigMap and fall back to true if it is unavailable. | +| `KAGENT_GATEWAY_URL` | String | `(none)` | Base URL for A2A and MCP traffic. The controller falls back to http://127.0.0.1:8083; Python runtimes require a value. | +| `KAGENT_GRPC_REFLECTION` | Boolean | `false` | Enable gRPC server reflection on the controller. | +| `KAGENT_HTTP_BIND_ADDRESS` | String | `:8083` | Listen address for the controller HTTP, gRPC, A2A, and MCP server. | +| `KAGENT_LEADER_ELECT` | Boolean | `true` | Enable controller leader election, including during single-replica rolling updates. Required for sandbox lifecycle coordination. | +| `KAGENT_LOG_LEVEL` | String | `info` | Logging level for the controller, CLI, and Go/Python runtimes, including the Python ADK HTTP server: debug, info, warn, or error. Python also accepts standard Python logging levels. | +| `KAGENT_METRICS_BIND_ADDRESS` | String | `0` | Address the controller-runtime metrics server binds to, e.g. :8080. "0" (the default) serves no metrics, so an installation that does not set this is unchanged. The Helm chart renders this variable, and its ServiceMonitor, from controller.metrics. | +| `KAGENT_METRICS_SECURE` | Boolean | `false` | Serve the metrics endpoint over HTTPS with authentication and authorization. A scraper then needs a token bound to the metrics-reader ClusterRole. | +| `KAGENT_NAMESPACE` | String | `kagent` | Kubernetes namespace where kagent resources are deployed. The controller injects the agent namespace into runtimes; Python runtimes require it. | +| `KAGENT_OTEL_CAPTURE_RAW_API_BODIES` | Boolean | `false` | Set to true, t, or 1 (case-insensitive) to enable native Claude raw API body logging when log export is enabled. Independent of span content capture; bodies may contain sensitive data. | +| `KAGENT_OTEL_MAX_CAPTURE_BYTES` | Integer | `16384` | Per-input/output content capture budget in bytes when capture is enabled. Valid values are 1 through 65536; absent or invalid values use 16384. | +| `KAGENT_OTEL_RESOURCE_ATTRIBUTES` | String | `(none)` | Resource attributes, as key=value pairs, added to every agent runtime. | +| `KAGENT_POSTGRES_DATABASE_MAX_CONNS` | Integer | `Greater of 4 and number of CPUs` | Maximum size of the PostgreSQL connection pool | +| `KAGENT_POSTGRES_DATABASE_MAX_CONN_IDLE_TIME` | Duration | `30m0s` | Duration after which an idle connection will be automatically closed | +| `KAGENT_POSTGRES_DATABASE_MAX_CONN_LIFETIME` | Duration | `1h0m0s` | Duration since creation after which a connection will be automatically closed | +| `KAGENT_POSTGRES_DATABASE_MIN_CONNS` | Integer | `0` | Minimum size of the PostgreSQL connection pool | +| `KAGENT_POSTGRES_DATABASE_URL` | String | `postgres://postgres:kagent@kagent-postgresql.kagent.svc.cluster.local:5432/postgres` | PostgreSQL connection URL. The default applies only to the controller; kagent db requires this variable or --db-url. Helm supplies its configured connection URL. | +| `KAGENT_POSTGRES_DATABASE_URL_FILE` | String | `(none)` | File containing the PostgreSQL connection URL; takes precedence over KAGENT_POSTGRES_DATABASE_URL in the controller. | +| `KAGENT_RUNTIME_REVISION_GC_INTERVAL` | Duration | `1m0s` | Interval between unreferenced runtime revision cleanup sweeps. Must be positive. | +| `KAGENT_SANDBOX_CPU` | String | `1` | CPU limit for standalone sandbox runtimes. | +| `KAGENT_SANDBOX_DEFAULT_TTL` | Duration | `1h0m0s` | Default standalone sandbox lifetime. | +| `KAGENT_SANDBOX_EXPIRATION_POLL_INTERVAL` | Duration | `1s` | Interval between expired sandbox cleanup batches. Must be positive; longer intervals delay deletion after TTL expiry. | +| `KAGENT_SANDBOX_GUEST_IMAGE` | String | `(none)` | Guest package image pinned by sha256 digest. Required for sandbox preparation and passed unchanged to Substrate. | +| `KAGENT_SANDBOX_MAX_TTL` | Duration | `24h0m0s` | Maximum standalone sandbox lifetime, at most 24h. | +| `KAGENT_SANDBOX_MEMORY` | String | `1Gi` | Memory limit for standalone sandbox runtimes. | +| `KAGENT_SCHEDULED_RUN_EXECUTION_POLL_INTERVAL` | Duration | `1s` | Interval between scheduled execution reconciliation attempts. Must be positive; longer intervals delay dispatch, status updates, deadline enforcement, and cleanup. | +| `KAGENT_SCHEDULED_RUN_POLL_INTERVAL` | Duration | `1s` | Interval between reserving due scheduled runs. Must be positive; occurrences more than 30 seconds late are skipped. | +| `KAGENT_SESSION_EXPIRATION_POLL_INTERVAL` | Duration | `1m0s` | Interval between idle session expiration sweeps. Must be positive. | +| `KAGENT_SESSION_IDLE_TTL` | Duration | `168h0m0s` | Delete sessions after this idle duration. Zero disables expiration; running and waiting tasks are retained. | +| `KAGENT_SESSION_SHARE_MAX_TTL` | Duration | `0s` | Longest lifetime a session share may request. Shares created without a ttl receive it. Zero leaves shares unbounded. | +| `KAGENT_SKIP_MIGRATIONS` | Boolean | `false` | Verify required database migrations at startup without applying them. | +| `KAGENT_SUBSTRATE_ATENET_ROUTER_URL` | String | `http://atenet-router.ate-system.svc:80` | Substrate router endpoint for agent and sandbox guest traffic. | +| `KAGENT_SUBSTRATE_ATE_API_CA_FILE` | String | `(none)` | PEM CA bundle used to verify the Substrate API server. Empty uses system trust roots. | +| `KAGENT_SUBSTRATE_ATE_API_CLIENT_CERT_FILE` | String | `(none)` | PEM bundle containing both the client certificate and private key for Substrate API mTLS. Reloaded for each TLS handshake. | +| `KAGENT_SUBSTRATE_ATE_API_ENDPOINT` | String | `dns:///api.ate-system.svc:443` | Substrate control-plane gRPC endpoint. | +| `KAGENT_WATCH_NAMESPACES` | String | `(none)` | Comma-separated namespaces to watch. Empty watches all namespaces. | +| `KUBECONFIG` | String | `(none)` | Kubernetes client configuration file list for the controller, CLI Kubernetes operations, and tests. When unset, client-go uses its normal in-cluster or user kubeconfig discovery. | +| `OTEL_EXPORTER_OTLP_COMPRESSION` | String | `gzip` | OTLP compression default applied by kagent. The native Codex process has this variable removed because its exporter does not support gzip. | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | String | `(none)` | OTLP endpoint for every signal. `OTEL_EXPORTER_OTLP__ENDPOINT` overrides it for one signal. | +| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | String | `(none)` | Log endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | String | `(none)` | Log protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` | String | `(none)` | Log SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | String | `base2_exponential_bucket_histogram` | Default SDK histogram aggregation applied by kagent and supplied to managed runtimes. | +| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | String | `(none)` | Metric endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | String | `(none)` | Metric protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` | String | `(none)` | Metric SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_EXPORTER_OTLP_PROTOCOL` | String | `grpc` | OTLP protocol, grpc or http/protobuf. `OTEL_EXPORTER_OTLP__PROTOCOL` overrides it for one signal. | +| `OTEL_EXPORTER_OTLP_TIMEOUT` | String | `(none)` | OTLP export timeout in milliseconds. The controller forwards positive integers; absent values use each SDK's default (normally 10000 ms). | +| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | String | `(none)` | Trace endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | String | `(none)` | Trace protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | String | `(none)` | Trace SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | String | `NO_CONTENT` | SPAN_ONLY records prompts and responses on agent spans. NO_CONTENT disables capture. Managed runtimes support these two modes; standalone Python ADK also recognizes SPAN_AND_EVENT. Captured content may be sensitive. | +| `OTEL_LOGS_EXPORTER` | String | `(none)` | Log exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | +| `OTEL_METRICS_EXPORTER` | String | `(none)` | Metric exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | +| `OTEL_PROPAGATORS` | String | `tracecontext` | SDK trace propagators. Kagent defaults to W3C tracecontext without baggage and supplies that default to managed runtimes. | +| `OTEL_RESOURCE_ATTRIBUTES` | String | `(none)` | Comma-separated SDK resource attributes for the current process. Helm injects controller identity; kagent constructs runtime identity separately. Use KAGENT_OTEL_RESOURCE_ATTRIBUTES for attributes shared with managed agents. | +| `OTEL_SDK_DISABLED` | String | `false` | Disable SDK telemetry and forwarding to managed runtimes when true (case-insensitive). Other values are treated as false. | +| `OTEL_SERVICE_NAME` | String | `(none)` | SDK service name for the current process. Defaults to kagent-controller in the controller; the controller supplies the agent name to managed runtimes. | +| `OTEL_TRACES_EXPORTER` | String | `(none)` | Trace exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | + +## Agent runtime + +The kagent controller supplies these variables to each agent runtime that it schedules. Setting one of them in a Harness `spec.env` does not override the controller on a managed runtime, because the controller writes its own value into every revision it compiles. The `byo` runtime is the exception because the controller sends it no configuration, so it reads whatever `spec.env` holds. For more information, see [Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}). + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS` | String | `(none)` | Python Google ADK span content capture. When absent, derived from OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT (true for SPAN_ONLY or SPAN_AND_EVENT, false otherwise). | +| `ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN` | String | `2` | Python Google ADK telemetry schema version; set by kagent when absent. | +| `ANTHROPIC_API_KEY` | String | `(none)` | API key for Anthropic. | +| `AWS_ACCESS_KEY_ID` | String | `(none)` | AWS access key ID for IAM authentication with Bedrock. | +| `AWS_BEARER_TOKEN_BEDROCK` | String | `(none)` | Bearer token for authentication with AWS Bedrock. | +| `AWS_DEFAULT_REGION` | String | `(none)` | Preferred region for Python Bedrock models and Go/Python Bedrock embeddings, before AWS_REGION and the us-east-1 fallback. | +| `AWS_REGION` | String | `(none)` | AWS region for Bedrock. Python Bedrock and Go Bedrock embeddings prefer AWS_DEFAULT_REGION, then AWS_REGION, then us-east-1. | +| `AWS_SECRET_ACCESS_KEY` | String | `(none)` | AWS secret access key for IAM authentication with Bedrock. | +| `AWS_SESSION_TOKEN` | String | `(none)` | AWS session token for temporary/SSO credentials with Bedrock. | +| `AZURE_AD_TOKEN` | String | `(none)` | Azure Active Directory authentication token for Azure OpenAI. | +| `AZURE_OPENAI_API_KEY` | String | `(none)` | API key for Azure OpenAI. | +| `AZURE_OPENAI_ENDPOINT` | String | `(none)` | Endpoint URL for Azure OpenAI service. | +| `FOUNDRY_API_KEY` | String | `(none)` | API key for Azure AI Foundry. | +| `FOUNDRY_API_VERSION` | String | `2024-10-21` | Azure AI Foundry OpenAI-compatible data-plane API version. | +| `FOUNDRY_DEPLOYMENT` | String | `(none)` | Azure AI Foundry model deployment name. | +| `FOUNDRY_ENDPOINT` | String | `(none)` | Endpoint URL for Azure AI Foundry or an Azure AI Services account. | +| `GEMINI_API_KEY` | String | `(none)` | Fallback Gemini API key when GOOGLE_API_KEY is unset; supported by the CLI and Go/Python ADKs. | +| `GOOGLE_API_KEY` | String | `(none)` | API key for Google Gemini. | +| `GOOGLE_APPLICATION_CREDENTIALS` | String | `(none)` | Path to Google Cloud service account JSON key file. | +| `GOOGLE_CLOUD_LOCATION` | String | `(none)` | Google Cloud region/location for Vertex AI. | +| `GOOGLE_CLOUD_PROJECT` | String | `(none)` | Google Cloud project ID for Vertex AI. | +| `GOOGLE_CLOUD_REGION` | String | `(none)` | Go ADK Vertex AI region fallback when GOOGLE_CLOUD_LOCATION is unset. | +| `GOOGLE_GENAI_USE_VERTEXAI` | String | `(none)` | When set to 'true', use Vertex AI for Gemini models. | +| `KAGENT_A2A_MAX_CONTENT_LENGTH` | String | `10485760` | Maximum A2A request size in bytes for Go/Python servers. 0, none, or unlimited disables the limit; invalid values use the default. | +| `KAGENT_API_URL` | String | `(none)` | Base URL for kagent control-plane API calls. Required by Python runtimes and supplied by the controller in managed runtimes; also used as the E2E test URL when KAGENT_E2E_API_URL is unset. | +| `KAGENT_BASH_VENV_PATH` | String | `(none)` | Virtual environment used for Python skills shell commands; its bin directory is prepended to PATH and VIRTUAL_ENV is set. | +| `KAGENT_CONFIG_DIR` | String | `/config` | Go ADK configuration directory; --filepath takes precedence. | +| `KAGENT_ENABLE_FILE_SEARCH_TOOLS` | Boolean | `false` | When true, t, or 1 (case-insensitive), enables the list_files and grep_file skills tools, which let an agent enumerate and search the filesystem under its session/skills roots without a shell. Disabled by default; set in Harness env to opt in. | +| `KAGENT_GATEWAY_URL` | String | `(none)` | Base URL for A2A and MCP traffic. The controller falls back to http://127.0.0.1:8083; Python runtimes require a value. | +| `KAGENT_LOG_LEVEL` | String | `info` | Logging level for the controller, CLI, and Go/Python runtimes, including the Python ADK HTTP server: debug, info, warn, or error. Python also accepts standard Python logging levels. | +| `KAGENT_NAME` | String | `(none)` | Agent name for standalone runtimes. Required by Python runtimes; supplied by the controller in managed runtimes. | +| `KAGENT_NAMESPACE` | String | `kagent` | Kubernetes namespace where kagent resources are deployed. The controller injects the agent namespace into runtimes; Python runtimes require it. | +| `KAGENT_OPENAI_AGENTS_NATIVE_TRACING` | Boolean | `false` | Keep the OpenAI Agents SDK native tracing processor alongside kagent OpenTelemetry export in the Python OpenAI runtime. | +| `KAGENT_PORT` | String | `(none)` | ADK A2A listen port: the Go HTTP/gRPC listener defaults to 8080; the Python gRPC listener defaults to 80. Explicit Go --port/AppConfig.Port or Python a2a_grpc_address takes precedence. The controller sets 80 for managed kagent runtimes. Python's HTTP --port is separate. | +| `KAGENT_PROPAGATE_TOKEN` | String | `(none)` | Set to true to propagate authentication tokens to downstream services. Unset or any other value disables propagation. | +| `KAGENT_SKILLS_FOLDER` | String | `/skills` | Skills directory for standalone Python skills tools. The Python ADK adds skills tools when set; managed Go ADK runtimes use their compiled skill configuration. | +| `KAGENT_STS_AUDIENCE` | String | `(none)` | Comma-separated RFC 8693 audiences sent on STS token-exchange requests. Alternate to KAGENT_STS_RESOURCE for servers that key on audience. | +| `KAGENT_STS_RESOURCE` | String | `(none)` | Comma-separated RFC 8707 resource indicators sent on STS token-exchange requests to scope issued tokens to target backends. | +| `KAGENT_STS_WELL_KNOWN_URI` | String | `(none)` | Well-known endpoint for the Security Token Service (STS) used for token exchange. | +| `MISTRAL_API_BASE` | String | `(none)` | Custom base URL for the Mistral AI API (defaults to https://api.mistral.ai/v1). | +| `MISTRAL_API_KEY` | String | `(none)` | API key for Mistral AI. | +| `OLLAMA_API_BASE` | String | `(none)` | Base URL for the Ollama API endpoint; falls back to http://localhost:11434 when model configuration and this variable are unset. | +| `OLLAMA_API_KEY` | String | `(none)` | API key for Ollama Cloud. When set, a cloud-tagged model reaches api.ollama.com directly. | +| `OPENAI_AGENTS_DISABLE_TRACING` | Boolean | `false` | Disable OpenAI Agents SDK tracing, including the kagent bridge. The Python OpenAI runtime accepts true or 1. | +| `OPENAI_API_BASE` | String | `(none)` | Custom base URL for the OpenAI API. | +| `OPENAI_API_KEY` | String | `(none)` | API key for OpenAI. Upgrade tests fall back to a placeholder when unset or empty. | +| `OPENAI_API_VERSION` | String | `(none)` | Azure OpenAI API version. The Go and Python ADKs fall back to 2024-02-15-preview when model configuration and this variable are unset. | +| `OPENAI_ORGANIZATION` | String | `(none)` | OpenAI organization identifier. | +| `OTEL_EXPORTER_OTLP_COMPRESSION` | String | `gzip` | OTLP compression default applied by kagent. The native Codex process has this variable removed because its exporter does not support gzip. | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | String | `(none)` | OTLP endpoint for every signal. `OTEL_EXPORTER_OTLP__ENDPOINT` overrides it for one signal. | +| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | String | `(none)` | Log endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | String | `(none)` | Log protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` | String | `(none)` | Log SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | String | `base2_exponential_bucket_histogram` | Default SDK histogram aggregation applied by kagent and supplied to managed runtimes. | +| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | String | `(none)` | Metric endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | String | `(none)` | Metric protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` | String | `(none)` | Metric SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_EXPORTER_OTLP_PROTOCOL` | String | `grpc` | OTLP protocol, grpc or http/protobuf. `OTEL_EXPORTER_OTLP__PROTOCOL` overrides it for one signal. | +| `OTEL_EXPORTER_OTLP_TIMEOUT` | String | `(none)` | OTLP export timeout in milliseconds. The controller forwards positive integers; absent values use each SDK's default (normally 10000 ms). | +| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | String | `(none)` | Trace endpoint override. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT; an HTTP override must include its signal path. | +| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | String | `(none)` | Trace protocol override: grpc or http/protobuf. Falls back to OTEL_EXPORTER_OTLP_PROTOCOL. | +| `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | String | `(none)` | Trace SDK timeout in milliseconds, overriding OTEL_EXPORTER_OTLP_TIMEOUT. Not forwarded by the controller. | +| `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | String | `NO_CONTENT` | SPAN_ONLY records prompts and responses on agent spans. NO_CONTENT disables capture. Managed runtimes support these two modes; standalone Python ADK also recognizes SPAN_AND_EVENT. Captured content may be sensitive. | +| `OTEL_LOGS_EXPORTER` | String | `(none)` | Log exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | +| `OTEL_METRICS_EXPORTER` | String | `(none)` | Metric exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | +| `OTEL_PROPAGATORS` | String | `tracecontext` | SDK trace propagators. Kagent defaults to W3C tracecontext without baggage and supplies that default to managed runtimes. | +| `OTEL_RESOURCE_ATTRIBUTES` | String | `(none)` | Comma-separated SDK resource attributes for the current process. Helm injects controller identity; kagent constructs runtime identity separately. Use KAGENT_OTEL_RESOURCE_ATTRIBUTES for attributes shared with managed agents. | +| `OTEL_SDK_DISABLED` | String | `false` | Disable SDK telemetry and forwarding to managed runtimes when true (case-insensitive). Other values are treated as false. | +| `OTEL_SEMCONV_STABILITY_OPT_IN` | String | `gen_ai_latest_experimental` | Python Google ADK semantic-convention opt-in; set by kagent when absent. | +| `OTEL_SERVICE_NAME` | String | `(none)` | SDK service name for the current process. Defaults to kagent-controller in the controller; the controller supplies the agent name to managed runtimes. | +| `OTEL_TRACES_EXPORTER` | String | `(none)` | Trace exporter, otlp or none. Managed runtime export requires explicit otlp and an endpoint; unset disables forwarding. Standalone SDKs may default to otlp. | +| `SAP_AI_CORE_CLIENT_ID` | String | `(none)` | OAuth2 client ID for SAP AI Core authentication. | +| `SAP_AI_CORE_CLIENT_SECRET` | String | `(none)` | OAuth2 client secret for SAP AI Core authentication. | + +## Database + +Both the kagent controller and `kagent db` read these variables. The controller takes its connection settings from Helm, so set these directly only when you run a database command outside of the cluster. + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `KAGENT_DATABASE_VECTOR_ENABLED` | Boolean | `false` | Enable vector database migrations and vector-backed database functionality. The controller defaults to false. When unset in the CLI, migrations read the controller ConfigMap and fall back to true if it is unavailable. | +| `KAGENT_POSTGRES_DATABASE_MAX_CONNS` | Integer | `Greater of 4 and number of CPUs` | Maximum size of the PostgreSQL connection pool | +| `KAGENT_POSTGRES_DATABASE_MAX_CONN_IDLE_TIME` | Duration | `30m0s` | Duration after which an idle connection will be automatically closed | +| `KAGENT_POSTGRES_DATABASE_MAX_CONN_LIFETIME` | Duration | `1h0m0s` | Duration since creation after which a connection will be automatically closed | +| `KAGENT_POSTGRES_DATABASE_MIN_CONNS` | Integer | `0` | Minimum size of the PostgreSQL connection pool | +| `KAGENT_POSTGRES_DATABASE_URL` | String | `postgres://postgres:kagent@kagent-postgresql.kagent.svc.cluster.local:5432/postgres` | PostgreSQL connection URL. The default applies only to the controller; kagent db requires this variable or --db-url. Helm supplies its configured connection URL. | +| `KAGENT_POSTGRES_DATABASE_URL_FILE` | String | `(none)` | File containing the PostgreSQL connection URL; takes precedence over KAGENT_POSTGRES_DATABASE_URL in the controller. | +| `KAGENT_SKIP_MIGRATIONS` | Boolean | `false` | Verify required database migrations at startup without applying them. | + +## CLI + +The `kagent` command line tool reads these variables from the environment that it runs in. Each one has an equivalent flag where the command takes a flag, and the flag wins when both are set. + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `ANTHROPIC_API_KEY` | String | `(none)` | API key for Anthropic. | +| `AZURE_OPENAI_API_KEY` | String | `(none)` | API key for Azure OpenAI. | +| `GEMINI_API_KEY` | String | `(none)` | Fallback Gemini API key when GOOGLE_API_KEY is unset; supported by the CLI and Go/Python ADKs. | +| `GOOGLE_API_KEY` | String | `(none)` | API key for Google Gemini. | +| `KAGENT_DATABASE_VECTOR_ENABLED` | Boolean | `false` | Enable vector database migrations and vector-backed database functionality. The controller defaults to false. When unset in the CLI, migrations read the controller ConfigMap and fall back to true if it is unavailable. | +| `KAGENT_DEFAULT_MODEL_PROVIDER` | String | `openAI` | Default LLM provider for agents (e.g. openAI, anthropic, ollama, azureOpenAI). | +| `KAGENT_HELM_EXTRA_ARGS` | String | `(none)` | Additional arguments to pass to Helm commands. | +| `KAGENT_HELM_REPO` | String | `oci://ghcr.io/kagent-dev/kagent/helm/` | Helm repository URL for kagent charts. | +| `KAGENT_HELM_VERSION` | String | `(none)` | Helm chart version to deploy. When unset, the CLI uses its own version. | +| `KAGENT_LOG_LEVEL` | String | `info` | Logging level for the controller, CLI, and Go/Python runtimes, including the Python ADK HTTP server: debug, info, warn, or error. Python also accepts standard Python logging levels. | +| `KAGENT_POSTGRES_DATABASE_URL` | String | `postgres://postgres:kagent@kagent-postgresql.kagent.svc.cluster.local:5432/postgres` | PostgreSQL connection URL. The default applies only to the controller; kagent db requires this variable or --db-url. Helm supplies its configured connection URL. | +| `KUBECONFIG` | String | `(none)` | Kubernetes client configuration file list for the controller, CLI Kubernetes operations, and tests. When unset, client-go uses its normal in-cluster or user kubeconfig discovery. | +| `OLLAMA_API_KEY` | String | `(none)` | API key for Ollama Cloud. When set, a cloud-tagged model reaches api.ollama.com directly. | +| `OPENAI_API_KEY` | String | `(none)` | API key for OpenAI. Upgrade tests fall back to a placeholder when unset or empty. | + +## UI + +The UI container and the Vite development server read these variables. Values prefixed `KAGENT_UI_` that reach the browser are public, so none of them can carry a secret. + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `KAGENT_UI_API_BASE_URL` | String | `/api` | Browser API base URL for UI containers and Vite development. | +| `KAGENT_UI_BASE_PATH` | String | `(none)` | UI public path prefix, such as /ui; empty serves at the root. Applies in containers and Vite development; the container falls back to the root for invalid or reserved prefixes. | +| `KAGENT_UI_DEV_CONTROLLER_URL` | String | `http://127.0.0.1:8083` | Vite development proxy target for /api and /a2a; not sent to the browser. | +| `KAGENT_UI_ENABLE_MOCK` | Boolean | `false` | Serve the development UI from in-browser fixtures when true. Overrides backend settings; no user is signed in. Release bundles do not include the mock backend. | +| `KAGENT_UI_EXTENSION_` | String | `(none)` | UI extension settings forwarded to the browser at runtime. Each installed extension owns its keys and defaults; these values are public. | +| `KAGENT_UI_SSO_REDIRECT_PATH` | String | `/oauth2/start` | UI path used by Sign in with SSO. | +| `KAGENT_UI_STREAM_TIMEOUT_MS` | String | `1800000` | UI chat stream inactivity timeout in milliseconds. 0 disables the timeout. Applies in containers and Vite development. | +| `KAGENT_UI_VITE_API_MODE` | String | `(none)` | Build-time API mode override, mock or live, used by UI tests. Overrides KAGENT_UI_ENABLE_MOCK; leave unset for normal development. | +| `KAGENT_UI_VITE_EXAMPLE_EXTENSION` | Boolean | `false` | Build-time switch enabling the bundled example UI extension. | diff --git a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md index b0db6f0d..bb0ab004 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -20,7 +20,7 @@ The kagent release pins the `kagent-tool-server` version, which is currently {{< ## Read the tools that a server serves -The controller connects to each RemoteMCPServer, asks it what it serves, and records the answer in `status.discoveredTools`. That status reports the server that your cluster actually runs, so it is more reliable than any list on this page. +The kagent controller connects to each RemoteMCPServer, asks it what it serves, and records the answer in `status.discoveredTools`. That status reports the server that your cluster actually runs, so it is more reliable than any list on this page. ```sh kubectl get remotemcpserver kagent-tool-server -n kagent \ @@ -30,7 +30,28 @@ kubectl get remotemcpserver kagent-tool-server -n kagent \ [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-the-tool-to-your-agenttemplate" >}}) runs the same command as the first step of binding one of these tools to an agent. > [!NOTE] -> `status.discoveredTools` stays empty until the controller completes a discovery pass, and `status.observedGeneration` tells you whether the recorded set matches the current spec. A server whose `Accepted` condition is not `True` has not been reached at all. +> `status.discoveredTools` stays empty until the controller completes a discovery pass, and `status.observedGeneration` tells you whether the recorded set matches the current spec. A server whose `Accepted` condition is not `True` has not been reached at all. A server that opts out of discovery is the exception, because it is `Accepted` and its tool list stays empty permanently. Read the `Accepted` condition's reason to tell the two apart, as described in the next section. + +### Turn tool discovery off for a server + +The controller lists a server's tools with the credentials that the controller itself holds. A server that authenticates every caller individually, such as one that expects each agent's own propagated token, accepts no credential that the controller can present. Discovery against that server fails, and because the listing never succeeds, the RemoteMCPServer stays un-`Accepted` indefinitely even though agents can reach it at run time. + +To resolve this issue, label the server with `kagent.dev/discovery=disabled`. The controller accepts the server without listing its tools, and the agents that bind it resolve the tool list at runtime with the credentials that they carry. + +```sh +kubectl label remotemcpserver -n kagent.dev/discovery=disabled +``` + +The label changes three things about the server: + +- The `Accepted` condition becomes `True` with the reason `DiscoveryDisabled`, rather than reporting a discovery failure. +- `status.discoveredTools` stays empty, so the command in [Read the tools that a server serves](#read-the-tools-that-a-server-serves) returns nothing for this server. The server's own documentation becomes the only list of what it offers. +- The catalog keeps the server but records it as disconnected, with no tools. + +You can also use this label on a kmcp `MCPServer` when agentgateway fronts the server. Agents bind a RemoteMCPServer that points at the gateway rather than binding the `MCPServer` itself, so discovery against the `MCPServer` serves no purpose and the label turns it off. + +> [!NOTE] +> Use this label only when controller-side discovery cannot succeed. Binding specific tools by name in an AgentTemplate still works against a server with discovery off, but nothing validates those names at admission, so a typo surfaces as a failed tool call at runtime instead of a rejected binding. ## kagent-tool-server diff --git a/docs-site/content/kagent/1.x/setup/model-providers/anthropic.md b/docs-site/content/kagent/1.x/setup/model-providers/anthropic.md index 7270b35f..3d7eacef 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/anthropic.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/anthropic.md @@ -33,7 +33,7 @@ The `Anthropic` provider calls the Anthropic API directly. spec: apiKeySecret: kagent-anthropic apiKeySecretKey: ANTHROPIC_API_KEY - model: claude-sonnet-4-20250514 + model: claude-sonnet-5 provider: Anthropic anthropic: {} EOF @@ -58,6 +58,48 @@ The `anthropic` block takes the following optional settings. For every field, in | `temperature` | How much randomness the model applies when it picks the next token. | | `topP` | The nucleus sampling cutoff. | | `topK` | How many candidate tokens to sample from. | +| `promptCaching` | Whether to bill the reusable prefix of each request as a cache read instead of fresh input. Defaults to `false`. For more information, see [Prompt caching](#prompt-caching). | +| `cacheTTL` | How long Anthropic retains a cached prefix. Applies only when `promptCaching` is `true`. Supported values are `5m` (default) or `1h`. | + +## Prompt caching + +An agent that calls a model many times for one task resends the same prefix every time, including the tool definitions, the system prompt, and the turns already taken. When `promptCaching` is `true`, kagent marks that prefix with `cache_control` breakpoints, and Anthropic bills a later request that reuses it at a fraction of the normal input price. Because the conversation breakpoint moves with every turn, each call in an agent loop reads the whole previous history from the cache and writes only the new turn. + +Enable the field wherever a tool-using agent makes many model calls per task against a stable system prompt and tool set. Without it, the full history is billed as fresh input on every call. + +```yaml +spec: + anthropic: + promptCaching: true + cacheTTL: "5m" +``` + +Two properties of Anthropic's pricing decide whether caching reduces cost. Neither one produces an error or a warning when caching ends up costing more. + +- **A cache write costs more than ordinary input.** A prefix must be read at least once before the saving on reads exceeds the premium charged on the write, so caching a prompt that is used once costs more than not caching it. +- **Each model sets a minimum cacheable prefix**, between 1024 and 4096 tokens depending on the model. Under that minimum Anthropic ignores the breakpoints silently, so the request succeeds, the response is normal, and nothing is cached. + +> [!IMPORTANT] +> `1h` is not an improvement on `5m`. Anthropic bills 1-hour cache writes at a higher per-token rate than 5-minute writes, and every cache hit refreshes the window, so an agent loop whose calls are less than 5 minutes apart keeps its prefix cached for the whole task on `5m`. Choose `1h` only when a task's model calls are spaced far enough apart that a 5-minute cache would expire between them. Otherwise, the higher write rate adds cost without reducing it anywhere else. + +For the models that support caching, the current minimum prefix sizes, and the pricing, see the [Anthropic prompt caching docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching). + +> [!WARNING] +> **The `claude` harness rejects `promptCaching: true`.** That harness accepts no `anthropic` settings beyond `baseUrl`, so a ModelConfig that enables caching fails to compile for it, and the AgentTemplate reports `Claude does not support Anthropic provider options beyond baseUrl yet` rather than becoming ready. Claude Code caches its own prefix on a 5-minute window regardless, so the setting gains nothing there. Where a `claude` agent and a `kagent` agent must share one installation, give the `claude` agent a ModelConfig that leaves `promptCaching` unset rather than enabling the field chart-wide. For the settings that each harness takes, see [Agent harness]({{< link path="agents/agent-harness#model-provider-support" >}}). + +### Prompt caching at install time + +Setting `providers.anthropic.config` in the Helm chart writes these fields into the ModelConfig that the chart generates, which saves editing that resource after every install. + +```yaml +providers: + anthropic: + config: + promptCaching: true + cacheTTL: "5m" +``` + +The setting reaches only the generated ModelConfig. A ModelConfig that you create yourself, including the one in [Create the ModelConfig](#create-the-modelconfig), takes the fields in its own `spec.anthropic` block. ## Use the ModelConfig diff --git a/scripts/generate-env-docs.py b/scripts/generate-env-docs.py new file mode 100644 index 00000000..7fed7867 --- /dev/null +++ b/scripts/generate-env-docs.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +""" +Generate the Hugo environment variable reference from kagent's docs/env.md. + +Upstream already generates that file from the registry in go/core/pkg/env +(`make env-docs`), and upstream CI fails when it drifts from the registry +(`make env-docs-check`). So this script does not re-derive anything from Go +source: it reads the generated file out of a kagent checkout and reshapes it +into a published page. + +Usage: + generate-env-docs.py --source kagent/docs/env.md \ + --out docs-site/content/kagent/1.x/reference/env-vars.md + +What the reshaping does, and why: + + * Replaces the upstream intro. The generated one tells a *contributor* to + "edit the registrations there, run make env-docs, and commit the result", + which is wrong for a reader of the published site. + + * Drops sections that are not reader-facing. `testing` documents the E2E + and build harness (KAGENT_E2E_*, KAGENT_TEST_*, mock server ports), which + belongs to people working in the kagent repo, not to people running + kagent. Override with --include-section if that judgment changes. + + * Renames section slugs to titles the site's style allows (`agent-runtime` + -> `Agent runtime`, `cli` -> `CLI`). + + * Gives every table the one-sentence introduction the site requires, since + a bare table under a heading is a style violation here. + +The script fails rather than guessing when upstream's structure changes: an +unknown section heading, a section that lost its table, or a missing expected +section all abort with a message naming the section. That turns an upstream +restructure into a failed docs run instead of a silently malformed page. +""" +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +import yaml + +# Upstream section slug -> (heading on the published page, introduction). +# +# The introduction is written here rather than taken from upstream because +# upstream emits none: every section there is a bare `## slug` followed +# straight by a table. +SECTIONS: dict[str, tuple[str, str]] = { + "controller": ( + "Controller", + "The kagent controller reads these variables at startup. Helm writes " + "most of them into the controller ConfigMap, so prefer the matching " + "chart value where one exists. Set the variable directly only for a " + "setting the chart does not expose. For the chart values, see the " + '[Helm reference]({{< link path="reference/helm#values" >}}).', + ), + "agent-runtime": ( + "Agent runtime", + "The kagent controller supplies these variables to each agent runtime " + "that it schedules. Setting one of them in a Harness `spec.env` does not " + "override the controller on a managed runtime, because the controller " + "writes its own value into every revision it compiles. The `byo` runtime " + "is the exception because the controller sends it no configuration, so " + "it reads whatever `spec.env` holds. For more information, see " + '[Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}).', + ), + "cli": ( + "CLI", + "The `kagent` command line tool reads these variables from the " + "environment that it runs in. Each one has an equivalent flag where the " + "command takes a flag, and the flag wins when both are set.", + ), + "database": ( + "Database", + "Both the kagent controller and `kagent db` read these variables. The " + "controller takes its connection settings from Helm, so set these " + "directly only when you run a database command outside of the cluster.", + ), + "ui": ( + "UI", + "The UI container and the Vite development server read these variables. " + "Values prefixed `KAGENT_UI_` that reach the browser are public, so none " + "of them can carry a secret.", + ), + # Deliberately excluded by default; see the module docstring. + "testing": ( + "Testing", + "kagent's own end-to-end and integration suites read these. They apply " + "to work in the kagent repository rather than to a running installation.", + ), +} + +DEFAULT_EXCLUDED = ("testing",) + +# Order on the published page. Upstream emits alphabetical section order, +# which puts `agent-runtime` first and buries `controller` in the middle. +# A reader configuring an installation wants the controller first. +PAGE_ORDER = ("controller", "agent-runtime", "database", "cli", "ui", "testing") + +INTRO = """\ +Review the environment variables that a {product} installation reads, grouped \ +by the component that reads them. + +Most of the following variables have a {helm} that writes them for you, and \ +the chart setting is the supported way to set them. Use a variable directly \ +only whenever you run a component outside the cluster, such as `kagent db` \ +against a database from your own machine, or when a variable has no chart \ +setting. + +Default values describe the component on its own. Helm, or an agent's \ +{harness} `spec.env`, might supply a different value, so the default listed \ +here is not the guaranteed value that a running installation might use. \ +`(none)` means the variable has no fixed default, so read the description for \ +what happens when it is unset. A variable that more than one component reads \ +appears under each of them. + +Credentials, controller-generated runtime payloads, and internal process \ +wiring are not configurable settings and are not listed.\ +""" + +# Warn the next person off editing the page itself. The page reads like +# ordinary prose, unlike the API and CLI references, so nothing about it +# signals that an edit here is reverted by the next reference docs run. +# Precedent: the conref note at the foot of reference/versions.md. +MARKER = """\ +\ +""" + +SECTION_HEADING = re.compile(r"^##\s+(?P\S+)\s*$") + + +def parse_sections(text: str) -> dict[str, list[str]]: + """Split the upstream file into {slug: body lines}, ignoring the preamble.""" + sections: dict[str, list[str]] = {} + current: str | None = None + for line in text.splitlines(): + match = SECTION_HEADING.match(line) + if match: + current = match.group("slug") + if current in sections: + sys.exit(f"error: duplicate section '{current}' in the source file") + sections[current] = [] + continue + if current is not None: + sections[current].append(line) + return sections + + +def table_of(slug: str, lines: list[str]) -> str: + """Return the section's markdown table, or abort if it has none.""" + body = "\n".join(lines).strip() + if not body.startswith("| Variable |"): + sys.exit( + f"error: section '{slug}' does not start with the expected variable " + "table. Upstream's docs/env.md format changed; update " + "scripts/generate-env-docs.py to match before regenerating." + ) + return body + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--source", + required=True, + type=Path, + help="Path to docs/env.md in a kagent checkout.", + ) + parser.add_argument( + "--out", + required=True, + type=Path, + help="Path of the page to write.", + ) + parser.add_argument( + "--title", + default="Environment variables", + help="Frontmatter title.", + ) + parser.add_argument( + "--weight", + type=int, + default=35, + help=( + "Hugo weight. Defaults to 35, which places the page after the CLI " + "reference (30) and before the tools ecosystem (40), keeping the " + "generated references together." + ), + ) + parser.add_argument( + "--product-name", + default='{{< reuse "kagent-docs/snippets/name-product.md" >}}', + help="Product name, as a literal or a reuse shortcode.", + ) + parser.add_argument( + "--include-section", + action="append", + default=[], + metavar="SLUG", + help=( + "Publish a section excluded by default (currently: " + f"{', '.join(DEFAULT_EXCLUDED)}). Repeatable." + ), + ) + args = parser.parse_args() + + if not args.source.is_file(): + sys.exit(f"error: source file not found: {args.source}") + + sections = parse_sections(args.source.read_text(encoding="utf-8")) + if not sections: + sys.exit( + f"error: no '## section' headings found in {args.source}. The source " + "is empty or its format changed." + ) + + unknown = sorted(set(sections) - set(SECTIONS)) + if unknown: + sys.exit( + "error: unrecognized section(s) in the source file: " + f"{', '.join(unknown)}. Upstream added a component. Add it to " + "SECTIONS and PAGE_ORDER in scripts/generate-env-docs.py, with an " + "introduction, so the new variables are published rather than " + "dropped." + ) + + excluded = set(DEFAULT_EXCLUDED) - set(args.include_section) + publish = [ + slug for slug in PAGE_ORDER if slug in sections and slug not in excluded + ] + if not publish: + sys.exit("error: every section was excluded; nothing to publish") + + parts = [ + "---\n" + + yaml.safe_dump( + { + "title": args.title, + "description": ( + "Look up the environment variables that each kagent " + "component reads, including their types and defaults." + ), + "weight": args.weight, + }, + sort_keys=False, + allow_unicode=True, + # Keep each frontmatter value on one line; the default width wraps + # a long description onto a continuation line, which is valid YAML + # but does not match the site's hand-written pages. + width=float("inf"), + ).strip() + + "\n---", + MARKER, + INTRO.format( + product=args.product_name, + helm='[Helm chart setting]({{< link path="reference/helm#values" >}})', + harness='{{< gloss "Harness" >}}Harness{{< /gloss >}}', + ), + ] + + for slug in publish: + heading, intro = SECTIONS[slug] + parts.append(f"## {heading}\n\n{intro}\n\n{table_of(slug, sections[slug])}") + + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text("\n\n".join(parts) + "\n", encoding="utf-8") + + skipped = sorted(set(sections) - set(publish)) + print(f"Wrote {args.out} with {len(publish)} section(s): {', '.join(publish)}") + if skipped: + print(f"Skipped section(s): {', '.join(skipped)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())