Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions docs-site/content/kagent/1.x/agents/agent-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,48 @@ Tracing carries the prompts and replies that an agent exchanges with a model, an

```yaml
otel:
captureSensitiveContent: false
logging:
captureRawApiBodies: false
capture:
messageContent: false
rawApiBodies: false
```

| Setting | What it includes | Applies to |
| ------- | ---------------- | ---------- |
| `otel.captureSensitiveContent` | Prompts, tool details, and assistant replies in the runtime's telemetry. On the `kagent` runtime, the content appears in the spans for each model call. On the `claude` runtime, tool results require tracing, and assistant replies require log export through `otel.logging`. | `kagent`, `codex`, `claude` |
| `otel.logging.captureRawApiBodies` | The complete provider API request and response bodies. This setting returns more than `otel.captureSensitiveContent` does, and it takes effect only when `otel.logging.enabled` is `true`. | `claude` |
| `otel.capture.messageContent` | Prompts and assistant replies in the runtime's telemetry. On the `kagent` runtime, the content appears in the span for each model call. That runtime records a tool call's arguments and reply on the span for the tool call instead, whatever this setting holds. On the `claude` runtime, tool results require tracing, and assistant replies require log export through `otel.logs`. | `kagent`, `codex`, `claude` |
| `otel.capture.rawApiBodies` | The complete provider API request and response bodies. This setting returns more than `otel.capture.messageContent` does, and it takes effect only when `otel.logs.enabled` is `true`. | `claude` |

The controller sets `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` in every compiled runtime from `otel.captureSensitiveContent`, so setting that variable in the Harness `spec.env` field has no effect.
The controller sets `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` in every compiled runtime from `otel.capture.messageContent`, so a Harness cannot change the capture decision for itself. Naming that variable in the Harness `spec.env` field fails the `claude` and `codex` runtimes outright, and is discarded on the `kagent` runtime. For the rest of the variables that behave this way, see [Controller-owned telemetry variables](#controller-owned-telemetry-variables).

The controller sends the `byo` runtime no telemetry configuration, so neither setting reaches it. A `byo` image that implements OpenTelemetry itself reads whatever the Harness `spec.env` field holds. For more information, see [Tracing]({{< link path="observability/tracing#about-trace-coverage" >}}).
The controller compiles the telemetry environment into the `byo` runtime as well whenever any signal is enabled, so `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` reaches a `byo` image. The `byo` runtime is the exception to the ownership rule. The controller applies the Harness `spec.env` field after the telemetry environment, so a `spec.env` entry wins where the two name the same variable, and an image that exports to its own backend keeps doing so. Whether the image acts on either setting depends on its own instrumentation. For more information, see [Tracing]({{< link path="observability/tracing#about-trace-coverage" >}}).

## Controller-owned telemetry variables

The controller compiles an installation's telemetry decisions into every runtime revision, so a Harness cannot override them through its `spec.env` field. The controller owns the following variables.

* `OTEL_SDK_DISABLED` and `OTEL_SERVICE_NAME`
* `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`, and `OTEL_EXPORTER_OTLP_TIMEOUT`
* `OTEL_TRACES_EXPORTER`, `OTEL_METRICS_EXPORTER`, and `OTEL_LOGS_EXPORTER`
* `OTEL_EXPORTER_OTLP_<SIGNAL>_ENDPOINT` and `OTEL_EXPORTER_OTLP_<SIGNAL>_PROTOCOL`, where `<SIGNAL>` is `TRACES`, `METRICS`, or `LOGS`
* `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`

A runtime that meets one of these names in `spec.env` either refuses the Harness or discards the entry, as described in the following table.

| Runtime | Result of naming a controller-owned variable in `spec.env` |
| ------- | ---------------------------------------------------------- |
| `claude` | The Harness fails to compile, and the controller reports `Harness env "<name>" conflicts with Claude-owned runtime configuration`. |
| `codex` | The Harness fails to compile, and the controller reports `Harness env "<name>" conflicts with Codex's compiled configuration`. |
| `kagent` | The entry is dropped and the controller's own value is compiled in its place. No error is reported. |

Two further variables fall outside that table:

* `OTEL_RESOURCE_ATTRIBUTES` is merged rather than refused. The value in `spec.env` is kept, and the agent identity attributes that the controller adds win where the two name the same attribute.
* `OTEL_EXPORTER_OTLP_HEADERS` is neither owned nor rendered. No Helm setting produces it, so the controller never adds it to a runtime, but a value in the Harness `spec.env` field reaches every runtime unchanged. To attach headers without holding them in an Actor environment, export to an in-cluster collector that adds them.

Every other `OTEL_*` variable, such as the `OTEL_BSP_*` batch span processor settings, stays available for per-Harness tuning.

### Baggage propagation

kagent runtimes set `OTEL_PROPAGATORS` to `tracecontext` when the environment leaves it unset, so a caller's baggage reaches neither the tools an agent calls nor its model provider. Trace context still propagates. To carry baggage as well, set `OTEL_PROPAGATORS` to `tracecontext,baggage` in the Harness `spec.env` field.

## Check that a Harness is ready

Expand Down
37 changes: 19 additions & 18 deletions docs-site/content/kagent/1.x/observability/audit-prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,32 @@ Each runtime records the content on its own instrumentation, so where the conten

| Runtime | Where the content goes | Settings |
| ------- | ---------------------- | -------- |
| `kagent` | The `generate_content` span of each model call, in two attributes. See [What a record holds](#what-a-record-holds). | `otel.captureSensitiveContent` |
| `codex` | The runtime's own spans. | `otel.captureSensitiveContent` |
| `claude` | Prompts and tool details on spans, and assistant replies in the runtime's own log records. With `otel.logging.captureRawApiBodies`, the log records also carry the complete provider request and response bodies, which is a fuller record than the spans give you. | `otel.captureSensitiveContent`, `otel.logging.captureRawApiBodies` |
| `byo` | Nowhere. The controller sends this runtime no telemetry configuration. | None |
| `kagent` | The `generate_content` span of each model call, in three attributes. See [What a record holds](#what-a-record-holds). | `otel.capture.messageContent` |
| `codex` | The runtime's own spans. | `otel.capture.messageContent` |
| `claude` | Prompts and tool details on spans, and assistant replies in the runtime's own log records. With `otel.capture.rawApiBodies`, the log records also carry the complete provider request and response bodies, which is a fuller record than the spans give you. | `otel.capture.messageContent`, `otel.capture.rawApiBodies` |
| `byo` | Wherever the image's own instrumentation records it. The controller sets `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` in this runtime, but an image that does not read the variable records nothing. | `otel.capture.messageContent` |

For each setting, see the agent harness [telemetry content settings]({{< link path="agents/agent-harness#telemetry-content-settings" >}}).

### What a record holds

On the `kagent` runtime, each `generate_content` span carries the following two attributes, as JSON.
On the `kagent` runtime, each `generate_content` span carries the following three attributes, as JSON.

| Attribute | What it holds |
| --------- | ------------- |
| `gcp.vertex.agent.llm_request` | The whole request that the runtime sent to the model. |
| `gcp.vertex.agent.llm_response` | The model's reply. On a turn that calls a tool, the reply holds the tool call and its arguments instead of text. |
| `gen_ai.system_instructions` | The `systemPrompt` field of your AgentTemplate, followed by the instructions that the runtime appends. |
| `gen_ai.input.messages` | The chat history sent to the model, as a list of messages with a `role` and typed `parts`. |
| `gen_ai.output.messages` | The model's reply, with its `finish_reason`. On a turn that calls a tool, the reply holds the tool call and its arguments instead of text. |

The request holds more than the prompts that your team wrote.
The record holds more than the prompts that your team wrote.

- **The system instruction**, which holds the `systemPrompt` field of your AgentTemplate followed by instructions that the runtime appends.
- **The message history**, including the person's messages, the agent's earlier turns, and tool results.
- **The tools** that the agent offered the model, with their definitions.
The system instruction holds the instructions that the runtime appends as well as the prompt your team wrote, and the message history holds the person's messages, the agent's earlier turns, and the model's tool calls.

A tool's own arguments and reply are recorded separately, on the `execute_tool` span of each tool call, in the `gcp.vertex.agent.tool_call_args` and `gcp.vertex.agent.tool_response` attributes. `otel.capture.messageContent` does not govern those two attributes. The runtime records them whenever tracing is on. Treat them as part of the audit record, and as a disclosure to account for on an agent whose tools return sensitive data.

Each model call carries the full history again, so a long conversation repeats its earlier messages in every span. Account for that volume when you set a retention period.

A payload larger than 32 KiB is cut to a prefix. The attribute then holds a JSON object with `truncated` set to `true`, the `original_size` of the payload in bytes, and the first 32 KiB in `payload_prefix`. Your tracing backend might also limit the size of an attribute, so check its limits before you rely on it for long conversations.
The `kagent` runtime does not shorten these attributes. The `claude` and `codex` runtimes bound each captured prompt and reply to the `otel.capture.maxBytes` budget, which defaults to 16384 bytes, and report a shortened capture with the `kagent.capture.input_truncated` and `kagent.capture.output_truncated` attributes. Your tracing backend might also limit the size of an attribute, so check its limits before you rely on it for long conversations.

### Delivery

Expand Down Expand Up @@ -82,10 +83,10 @@ Turn on content capture in the kagent Helm release, then create a Session that p
--version {{< reuse "kagent-docs/versions/kagent.md" >}} \
--namespace kagent \
--reuse-values \
--set otel.captureSensitiveContent=true
--set otel.capture.messageContent=true
```

For an agent on the `claude` runtime, also set `otel.logging.enabled` to `true`, and send the logs to a backend that stores them, such as Loki in the [OTel stack]({{< link path="observability/otel-stack" >}}). Without log export, the replies of a `claude` agent are not recorded.
For an agent on the `claude` runtime, also set `otel.logs.enabled` to `true`, and send the logs to a backend that stores them, such as Loki in the [OTel stack]({{< link path="observability/otel-stack" >}}). Without log export, the replies of a `claude` agent are not recorded.

3. Wait for the controller to roll out.
```bash
Expand Down Expand Up @@ -141,9 +142,9 @@ Send a request that contains a distinctive phrase, then find that phrase in the
3. Open **Explore**, select the **Tempo** data source, and select the **TraceQL** query type.
4. Run the following query, which returns the model calls whose request contains the phrase.
```text
{ span.gcp.vertex.agent.llm_request =~ ".*Audit check.*" }
{ span.gen_ai.input.messages =~ ".*Audit check.*" }
```
5. Open a trace, and select its `generate_content` span. The **Span Attributes** section shows the two attributes that [What a record holds](#what-a-record-holds) describes.
5. Open a trace, and select its `generate_content` span. The **Span Attributes** section shows the three attributes that [What a record holds](#what-a-record-holds) describes.
{{% /tab %}}
{{% tab name="Jaeger" %}}
1. Forward the Jaeger query port, and leave the command running.
Expand All @@ -152,7 +153,7 @@ Send a request that contains a distinctive phrase, then find that phrase in the
```
2. In your browser, open Jaeger at [http://localhost:16686](http://localhost:16686).
3. From the **Service** list, select `my-first-agent`. From the **Operation** list, select the `generate_content` operation for your model, such as `generate_content gpt-4.1-mini`, and click **Find Traces**.
4. Open the most recent trace, and expand the `generate_content` span. The **Tags** section shows the two attributes that [What a record holds](#what-a-record-holds) describes.
4. Open the most recent trace, and expand the `generate_content` span. The **Tags** section shows the three attributes that [What a record holds](#what-a-record-holds) describes.
{{% /tab %}}
{{< /tabs >}}

Expand All @@ -173,7 +174,7 @@ Turn off content capture, then create a new Session so that the change takes eff
{{< reuse "kagent-docs/snippets/helm-path.md" >}}/{{< reuse "kagent-docs/snippets/helm-kagent.md" >}} \
--version {{< reuse "kagent-docs/versions/kagent.md" >}} \
--namespace kagent --reuse-values \
--set otel.captureSensitiveContent=false
--set otel.capture.messageContent=false
```

2. Create a new Session, because an existing Actor keeps the configuration that it started with. The spans of a Session that still captures content keep carrying it until you delete the Session.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,14 @@ Turn on the kagent trace and log exporters, and point both at the collector. Als
--reuse-values \
--values - <<EOF
otel:
tracing:
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
traces:
enabled: true
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
insecure: true
logging:
logs:
enabled: true
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
insecure: true
controller:
metrics:
enabled: true
Expand Down Expand Up @@ -323,8 +317,8 @@ Check each signal in turn: traces in Jaeger, metrics in Prometheus, and logs in
{{< reuse "kagent-docs/snippets/helm-path.md" >}}/{{< reuse "kagent-docs/snippets/helm-kagent.md" >}} \
--version {{< reuse "kagent-docs/versions/kagent.md" >}} \
--namespace kagent --reuse-values \
--set otel.tracing.enabled=false \
--set otel.logging.enabled=false \
--set otel.traces.enabled=false \
--set otel.logs.enabled=false \
--set controller.metrics.enabled=false
helm upgrade substrate \
oci://ghcr.io/kagent-dev/substrate/helm/substrate \
Expand Down
27 changes: 11 additions & 16 deletions docs-site/content/kagent/1.x/observability/otel-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,20 +340,14 @@ Turn on the kagent trace and log exporters, and point both at the collector. Als
--reuse-values \
--values - <<EOF
otel:
tracing:
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
traces:
enabled: true
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
insecure: true
logging:
logs:
enabled: true
exporter:
otlp:
endpoint: http://otel-collector.telemetry.svc.cluster.local:4317
protocol: grpc
insecure: true
controller:
metrics:
enabled: true
Expand All @@ -369,8 +363,9 @@ Turn on the kagent trace and log exporters, and point both at the collector. Als

| Setting | Description |
| ------- | ----------- |
| `otel.tracing` | Exports traces from the controller and from the agent runtimes that it starts. For each field, see [Tracing]({{< link path="observability/tracing#enable-tracing" >}}). |
| `otel.logging` | Exports log records from the agent runtimes. The `kagent` runtime never puts prompts or replies in a log record. For where that content goes, see [Audit prompts]({{< link path="observability/audit-prompts" >}}). |
| `otel.exporter.otlp` | The OTLP endpoint and protocol that every signal exports to. An `http://` endpoint sends plaintext. For each field, see [Tracing]({{< link path="observability/tracing#enable-tracing" >}}). |
| `otel.traces` | Exports traces from the controller and from the agent runtimes that it starts. |
| `otel.logs` | Exports log records from the agent runtimes. The `kagent` runtime never puts prompts or replies in a log record. For where that content goes, see [Audit prompts]({{< link path="observability/audit-prompts" >}}). |
| `controller.metrics.enabled` | Serves the controller's Prometheus metrics on port `8443` over HTTPS. |
| `controller.metrics.serviceMonitor` | Creates a ServiceMonitor for the metrics endpoint. The `prometheusServiceAccount` setting binds the metrics reader role to the Prometheus ServiceAccount, which authorizes the scrape. |

Expand Down Expand Up @@ -500,8 +495,8 @@ Log in to Grafana, and query each backend from the **Explore** view.
{{< reuse "kagent-docs/snippets/helm-path.md" >}}/{{< reuse "kagent-docs/snippets/helm-kagent.md" >}} \
--version {{< reuse "kagent-docs/versions/kagent.md" >}} \
--namespace kagent --reuse-values \
--set otel.tracing.enabled=false \
--set otel.logging.enabled=false \
--set otel.traces.enabled=false \
--set otel.logs.enabled=false \
--set controller.metrics.enabled=false \
--set controller.metrics.serviceMonitor.enabled=false
helm upgrade substrate \
Expand Down
Loading
Loading