From cd4988ee62dedb4dc53c88bf2d9e3618abb39d8f Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Tue, 27 Oct 2026 09:38:30 -0500 Subject: [PATCH 1/9] alpha18 docs Signed-off-by: Rachael Graham --- .../substrate-runtime/standalone-sandboxes.md | 4 ++ .../kagent/1.x/agents/human-in-the-loop.md | 13 +++++ .../kagent/1.x/agents/structured-output.md | 56 +++++++++++++++++- .../content/kagent/1.x/examples/a2a-agents.md | 2 +- .../content/kagent/1.x/examples/skills.md | 2 + .../kagent/1.x/observability/launch-ui.md | 58 ++++++++++--------- .../kagent/1.x/operations/upgrade-from-0x.md | 2 +- .../kagent/1.x/reference/release-notes/1.0.md | 2 +- .../kagent/1.x/reference/tools-ecosystem.md | 45 ++------------ .../content/kagent/1.x/setup/installation.md | 1 - .../1.x/setup/model-providers/byo-openai.md | 2 +- 11 files changed, 113 insertions(+), 74 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index 91dd69d7..c0c9e291 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md @@ -132,6 +132,8 @@ Each `kagent sandbox` command makes one lifecycle attempt rather than retrying f A timeout stops the command from waiting rather than stopping the remote process. To reconnect to a process that outlived its `exec`, pass its process ID to `kagent sandbox wait`. + `kagent sandbox kill` sends `SIGKILL` to a process and returns when the guest delivers the signal. The command does not wait for the process to exit, so its output reports the process state and omits the exit code. To read the exit code, pass the process ID to `kagent sandbox wait`. + 4. Move a file in, run against it, and read the result back out. ```bash kagent sandbox upload $SANDBOX_ID ./script.py /data/workspace/script.py @@ -170,6 +172,8 @@ An agent that creates a Sandbox owns it under whatever identity the MCP connecti The MCP transfer limits are tighter than the command line's. A gRPC file transfer is bounded at 64 MiB. `write_sandbox_file` and `read_sandbox_outputs` are bounded at 1 MiB and use base64. Output reads return continuation byte offsets. +An agent that starts a process with `start_sandbox_process` polls `get_sandbox_process` to learn when the process finishes. The `state` field reads `PROCESS_STATE_RUNNING` until the process exits, then `PROCESS_STATE_EXITED`. The `exit_code` field appears only in the exited state, and reads `0` for a successful exit. `kill_sandbox_process` sends `SIGKILL` in the same way as `kagent sandbox kill`, so the agent must poll `get_sandbox_process` for the exit code. + When an agent must inspect a result file, use `read_sandbox_file`. The tool returns content that the model reads directly rather than `data_base64`, in one of three shapes. Text returns as numbered lines. `offset` takes the first line to read, counting from 1, and `limit` takes the number of lines, which defaults to 2000. A page also stops at 32 KiB, whichever bound it reaches first. A line longer than 2000 bytes is truncated to 2000 bytes and marked `[line cut]`. A page that stops before the end of the file reports the offset to continue from. diff --git a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md index 6c463c72..fccaa7a6 100644 --- a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md +++ b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md @@ -58,6 +58,19 @@ The `kagent` harness composes this text from the pending calls. The wording depe The `codex` and `claude` harnesses compose the text differently. Both pass through the hint that Codex or Claude Code supplied with the request, so the wording comes from the coding agent rather than from kagent. +### Disable questions + +When an agent runs unattended, nobody answers its question, so the task waits at `INPUT_REQUIRED`. To stop agents on the `kagent` harness from asking questions, set the `config.runtimeDefaults.builtinTools.disableAskUser` Helm value to `true`. The value removes the `ask_user` tool from every agent in the installation and from each subagent, and an AgentTemplate cannot override it. The value defaults to `false`. + +```yaml +config: + runtimeDefaults: + builtinTools: + disableAskUser: true +``` + +The value removes only the question tool. A binding that sets `requireApproval` still pauses the agent for approval, and a remote A2A agent that the agent calls can still pause the turn with its own question. The `codex` and `claude` harnesses do not read the value, because the coding agent supplies its own tools. A `byo` image decides whether to read it. + ## Require approval for a tool Approval is a property of a tool binding. For example, if you set `requireApproval` on an `mcp` tool binding in the AgentTemplate, the agent stops before each call to a tool that the binding exposes. diff --git a/docs-site/content/kagent/1.x/agents/structured-output.md b/docs-site/content/kagent/1.x/agents/structured-output.md index 4223f161..69526862 100644 --- a/docs-site/content/kagent/1.x/agents/structured-output.md +++ b/docs-site/content/kagent/1.x/agents/structured-output.md @@ -14,6 +14,7 @@ The schema goes in one of two AgentTemplate fields, `spec.outputSchema` or `spec | `outputSchema` | The schema, written inline in the AgentTemplate. | | `outputSchemaFrom.name` | The ConfigMap holding the schema as JSON, in the AgentTemplate's namespace. | | `outputSchemaFrom.key` | The key within that ConfigMap. | +| `outputSchemaStrict` | Whether the model provider constrains generation to the schema. Defaults to `false`. For the providers that honor it, see [Strict mode](#strict-mode). | ## Before you begin @@ -175,14 +176,20 @@ Use `outputSchemaFrom` to keep the schema outside the AgentTemplate, so that sev The root of the schema must declare `type: object`. kagent accepts the following subset of JSON Schema, which its error messages call the portable output profile. -- `type`, with a single value: `object`, `array`, `string`, `number`, `integer`, `boolean`, or `null` +- `type`, with one value or a list of values such as `["string", "null"]`. Each value is `object`, `array`, `string`, `number`, `integer`, `boolean`, or `null`. - `properties`, `required`, `additionalProperties`, and `items` - `enum`, `const`, and `anyOf` - `title` and `description` - `$defs`, and references to it in the form `#/$defs/`, which cannot be recursive - `$schema` and `$id` +- `pattern`, in RE2 syntax, on a schema whose `type` includes `string` +- `format`, on a schema whose `type` includes `string`, with one of the values `date`, `date-time`, `duration`, `email`, `hostname`, `ipv4`, `ipv6`, `time`, or `uuid` -Anything else is rejected, including `oneOf` and `allOf`, conditional schemas such as `if` and `then`, tuple arrays, references outside `$defs`, and validation keywords such as `pattern`, `format`, `minimum`, and `minLength`. To allow `null` alongside another type, use `anyOf`, because `type` takes one value. +Anything else is rejected, including `oneOf` and `allOf`, conditional schemas such as `if` and `then`, tuple arrays, references outside `$defs`, and validation keywords such as `minimum` and `minLength`. Claude structured outputs do not support word boundaries, so kagent also rejects a `pattern` that uses `\b` or `\B`. + +JSON Schema treats `format` as an annotation. kagent forwards `format` to the model provider, but the runtime does not check it on the answer. + +A Gemini model calls Go ADK's `set_model_response` tool to answer, and that tool checks each property against one type. For a model whose name starts with `gemini-`, kagent therefore rejects a property that uses `anyOf`, `type: null`, or a type list with more than one non-null type. kagent also rejects an array property that does not set `items`. To allow `null` in such a property, use a type list such as `["string", "null"]`. The check skips models on the `GeminiVertexAI` provider. kagent also enforces the following limits when it builds the revision. A schema that exceeds a limit is rejected, before any agent runs with it. @@ -194,6 +201,48 @@ kagent also enforces the following limits when it builds the revision. A schema kagent counts depth and nodes through `properties`, `items`, and `anyOf`, and counts a `$defs` entry again each time that it is referenced. The size limit applies before kagent normalizes the schema, so whitespace in a ConfigMap value counts toward it. +## Strict mode + +OpenAI models follow `format` and `pattern` during generation only in strict mode. To constrain generation to the schema, set `outputSchemaStrict` to `true` in the AgentTemplate. + +```yaml +spec: + outputSchemaStrict: true + outputSchema: + type: object + properties: + ticket: + type: string + pattern: "^OPS-[0-9]+$" + due: + type: [string, "null"] + format: date + required: + - ticket + - due + additionalProperties: false +``` + +The example schema meets every strict mode rule in this section. + +| Field | Description | +| ----- | ----------- | +| `outputSchemaStrict` | Sends the schema in strict mode to the OpenAI, Azure OpenAI, and Azure AI Foundry providers. Foundry receives it only with the `OpenAI` API format. Other providers ignore the field. | +| `outputSchema.properties.ticket.pattern` | Requires a ticket ID such as `OPS-123`. | +| `outputSchema.properties.due.type` | Allows `null`, which makes `due` optional. | +| `outputSchema.properties.due.format` | Asks for a date such as `2026-11-03`. | + +An AgentTemplate that sets `outputSchemaStrict` to `true` without a schema is rejected when you apply it, with the message `outputSchemaStrict requires outputSchema or outputSchemaFrom`. The runtime validates every answer against the schema in both modes. + +Strict mode accepts fewer schemas. kagent checks the following rules for every provider, so a schema stays valid when you change the provider of the ModelConfig. + +- The root does not use `anyOf`. +- Every node sets `type`, `anyOf`, `enum`, or `$ref`. +- A node that sets `$ref` does not set any other keyword. To add `null`, wrap the reference in `anyOf`, such as `anyOf: [{$ref: "#/$defs/item"}, {type: "null"}]`. +- Every object sets `additionalProperties` to `false` and lists all of its properties in `required`. A map with free-form keys cannot meet this rule. + +To make a property optional in strict mode, list it in `required` and add `null` to its type list. + ## Read the answer A successful answer is published as one {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) `DataPart` with the media type `application/json`. The part's metadata carries the SHA-256 digest of the schema that the answer was validated against, under the key `kagent.dev/a2a/output-schema-sha256`. kagent computes the digest after it normalizes the schema, so two schemas that differ only in formatting or key order have the same digest. The Agent's card lists `application/json` as its default output mode, instead of `text`. @@ -291,6 +340,9 @@ For example, if the `shared-schemas` ConfigMap no longer has the `arithmetic-ans | `invalid output schema: schema must contain one JSON value` | `Compatible`, `UnsupportedConfiguration` | The value holds more than one JSON value, such as two objects in a row. | | `invalid output schema: schema does not match the portable output profile: ...` | `Compatible`, `UnsupportedConfiguration` | The schema uses a keyword or form outside the [portable output profile](#supported-schemas), or its root is not an object. | | `invalid output schema: resolve schema: ...` | `Compatible`, `UnsupportedConfiguration` | kagent cannot resolve the schema, for example because a `$ref` names a `$defs` entry that does not exist. | +| `invalid output schema: pattern ...` | `Compatible`, `UnsupportedConfiguration` | A `pattern` is not valid RE2, uses a word boundary, or the schema's patterns compile to more than 100,000 regexp instructions. | +| `output schema for Gemini: ...` | `Compatible`, `UnsupportedConfiguration` | A property breaks the [Gemini rule](#supported-schemas) for a single type, or an array property does not set `items`. | +| `outputSchemaStrict: ...` | `Compatible`, `UnsupportedConfiguration` | The schema breaks a [strict mode](#strict-mode) rule. | | `output schema is incompatible with Go ADK: ...` | `Compatible`, `UnsupportedConfiguration` | The schema has a recursive reference, or exceeds the depth or node limit. | | `Harness runtime "x" does not support structured output` | `Compatible`, `UnsupportedConfiguration` | The Harness does not use the `kagent` harness. | diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index 2401463d..0bfe4439 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -346,7 +346,7 @@ By default, callback URLs must use HTTPS. Callback URLs must not resolve to priv ## When an agent needs a person -An agent can stop mid-task to ask a question or to request approval for a tool call. The task then reports `TASK_STATE_INPUT_REQUIRED` and waits until a caller answers it. Every kagent agent can raise the question kind, because the runtime gives each one a built-in `ask_user` tool, so a system prompt that tells an agent to ask before it answers is enough to see a pause. +An agent can stop mid-task to ask a question or to request approval for a tool call. The task then reports `TASK_STATE_INPUT_REQUIRED` and waits until a caller answers it. By default, every agent on the `kagent` harness can raise the question kind, because the runtime gives each one a built-in `ask_user` tool. A system prompt that tells an agent to ask before it answers is therefore enough to see a pause. Answering a pause needs the human-in-the-loop extension, which a caller requests per call. The extension is a versioned URI, and a request names it in two places: the `A2A-Extensions` header, and the message's own `extensions` list. diff --git a/docs-site/content/kagent/1.x/examples/skills.md b/docs-site/content/kagent/1.x/examples/skills.md index e5267624..f40e6cd1 100644 --- a/docs-site/content/kagent/1.x/examples/skills.md +++ b/docs-site/content/kagent/1.x/examples/skills.md @@ -54,6 +54,8 @@ config: | `disableBash` | Set to `true` to remove the `bash` tool from the root agent and from every subagent. The agent keeps the file tools, and still loads skills. Defaults to `false`. | | `bashVenvPath` | The path to a Python virtual environment in the Harness image. The `bash` tool adds the environment's `bin` directory to the start of `PATH`, and sets `VIRTUAL_ENV`. The kagent runtime image does not include Python. To use this setting, build a Harness image that includes the environment. Defaults to empty. | +The `builtinTools` Helm values also include `disableAskUser`, which removes the question tool. For more information, see [Disable questions]({{< link path="agents/human-in-the-loop#disable-questions" >}}). + Attaching a skill also changes what the agent is told. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call `load_skill` before acting on one, so a skill reaches the model even before any tool is called. > [!IMPORTANT] diff --git a/docs-site/content/kagent/1.x/observability/launch-ui.md b/docs-site/content/kagent/1.x/observability/launch-ui.md index b81d0666..9d7c1480 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -5,7 +5,7 @@ weight: 10 author: kagent.dev --- -The {{< reuse "kagent-docs/snippets/name-product.md" >}} UI is a read-and-write console for everything the controller knows about: the agents you defined, the conversations the agents hold, and the Agent Substrate capacity that those conversations run on. The kagent chart installs the UI alongside the controller, so a cluster that follows [Install kagent]({{< link path="setup/installation" >}}) already runs a UI instance. +The {{< reuse "kagent-docs/snippets/name-product.md" >}} UI is a read-and-write console for everything that the controller knows about, including the agents that you defined, the conversations that the agents hold, and the Agent Substrate capacity that those conversations run on. The kagent chart installs the UI alongside the controller, so a cluster that follows [Install kagent]({{< link path="setup/installation" >}}) already runs a UI instance. ## Before you begin @@ -57,27 +57,27 @@ The UI service is a `ClusterIP` by default, so it is reachable only from inside 2. Open [http://localhost:8082](http://localhost:8082) in your browser. -3. When you are done, press `Ctrl+C` in the terminal that holds the port-forward. +3. When you are done, press `Ctrl+C` in the terminal that runs the port-forward. {{% /tab %}} {{< /tabs >}} -The dashboard opens on a summary of what the controller has loaded. A fresh installation counts zero agents and zero conversations, and already counts the model configuration and the {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) servers that the chart installed. +The dashboard opens on a summary of what the controller has loaded. On a fresh installation, the dashboard counts zero agents and zero conversations. The dashboard already counts the model configuration and the {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) servers that the chart installed. {{< reuse-image-light src="img/kagent-ui-dashboard.png" alt="The kagent dashboard after a fresh installation" caption="Figure: The kagent dashboard after a fresh installation" >}} {{< reuse-image-dark srcDark="img/kagent-ui-dashboard-dark.png" alt="The kagent dashboard after a fresh installation" caption="Figure: The kagent dashboard after a fresh installation" >}} ## Explore the UI -The left menu groups the console by the resource each page reads, so the page you want follows from the resource you are asking about. +The left menu groups the console by the resource that each page reads, so the page that you want follows from the resource that you are asking about. | Page | Shows | | ---- | ----- | | **Dashboard** | Counts of agents, model configurations, MCP servers, and discovered tools, plus recent conversations. | | **Agents** | Every {{< gloss "Agent" >}}Agent{{< /gloss >}}, {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, and {{< gloss "Harness" >}}Harness{{< /gloss >}}, and the conversations held with each Agent. | -| **Schedules** | Agents that run automatically, each execution starting a new conversation. | -| **Models** | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} resources that agents name, and the providers behind them. | +| **Schedules** | Agents that run automatically. Each execution starts a new conversation. | +| **Models** | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} resources that agents reference, and the providers behind them. | | **MCP Servers** | Connected {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} resources and the tools discovered from each. | -| **Prompts** | Prompt libraries, which hold reusable fragments that an AgentTemplate includes in its instructions. | +| **Prompts** | Prompt libraries, which store reusable fragments that an AgentTemplate includes in its instructions. | | **Substrate** | WorkerPools, Actors, and Workers, read from both Kubernetes and the Agent Substrate API. | Start at the **Agents** page to review your agents and the resources behind them. The page lists each resource kind on its own tab: **Agents**, **Templates**, and **Harnesses**. @@ -85,11 +85,11 @@ Start at the **Agents** page to review your agents and the resources behind them {{< reuse-image-light src="img/kagent-ui-agents.png" alt="The Agents page, listing agents, templates, and harnesses" caption="Figure: The Agents page" >}} {{< reuse-image-dark srcDark="img/kagent-ui-agents-dark.png" alt="The Agents page, listing agents, templates, and harnesses" caption="Figure: The Agents page" >}} -The **Agents** tab authors the Agent custom resource directly. Create one to pair a template with a Harness, edit one to change either side, and delete one to retire the pairing. Deleting an Agent leaves its Sessions in place, along with the AgentTemplate and Harness that it named. For CLI steps, see [Create your first agent]({{< link path="get-started/your-first-agent" >}}). +The **Agents** tab authors the Agent custom resource directly. Create one to pair a template with a Harness, edit one to change either side, and delete one to retire the pairing. Deleting an Agent leaves its Sessions in place, along with the AgentTemplate and Harness that it referenced. For CLI steps, see [Create your first agent]({{< link path="get-started/your-first-agent" >}}). ## Read a conversation -Opening an agent and sending a message creates a {{< gloss "Session" >}}Session{{< /gloss >}}, which is one conversation scheduled onto an Agent Substrate {{< gloss "Actor" >}}Actor{{< /gloss >}}. The conversation view shows the transcript, the tool calls the agent made along the way, and the checkpoint controls for the conversation. +Opening an agent and sending a message creates a {{< gloss "Session" >}}Session{{< /gloss >}}, which is one conversation scheduled onto an Agent Substrate {{< gloss "Actor" >}}Actor{{< /gloss >}}. The conversation view shows the transcript, the tool calls that the agent made along the way, and the checkpoint controls for the conversation. {{< reuse-image-light src="img/kagent-ui-chat.png" alt="A conversation with an agent, showing a named snapshot and the controls on its mark" caption="Figure: A conversation with an agent" >}} {{< reuse-image-dark srcDark="img/kagent-ui-chat-dark.png" alt="A conversation with an agent, showing a named snapshot and the controls on its mark" caption="Figure: A conversation with an agent" >}} @@ -98,11 +98,11 @@ The UI labels a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} **Snapshot**. To take a checkpoint, select the save icon beside the message box. kagent pins the snapshot that the Session most recently suspended to, and records how far the transcript advanced. To understand how pinning works, and why a turn must be complete first, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). -A checkpoint appears in the transcript as a mark carrying the snapshot name and three controls. Select the mark to open the record behind it. The record holds the checkpoint's ID, turn, state, and age. +A checkpoint appears in the transcript as a mark that shows the snapshot name and the following controls. Select the mark to open the record behind it. The record shows the checkpoint's ID, turn, state, and age. -- **Fork** starts a second Session from the checkpoint. The new Session replays the conversation through the pinned turn and then continues on its own. For what a fork keeps from its source, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). -- **Rename** replaces the snapshot name that kagent generates on creation from the Session ID and the turn the checkpoint pins. Clearing the name restores that generated default. -- **Delete** removes the checkpoint and releases the snapshot it pinned. Agent Substrate is then free to collect that snapshot. +- **Fork** starts a new Session from the checkpoint. The new Session replays the conversation through the pinned turn and then continues on its own. For what a fork keeps from its source, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). +- **Rename** replaces the snapshot name that kagent generates on creation from the Session ID and the turn that the checkpoint pins. Clearing the name restores that generated default. +- **Delete** removes the checkpoint and releases the snapshot that it pinned. Agent Substrate is then free to collect that snapshot. > [!NOTE] > A checkpoint is always taken at the latest turn boundary, so the UI offers checkpoint controls on your own most recent message and not on earlier ones. @@ -117,7 +117,7 @@ Stage files in any of the following ways. - Select the paperclip beside the message box, then choose the files. - Drop files anywhere on the chat page. -- Paste a file or an image from your clipboard. A clipboard that also holds plain text pastes the text instead, so that a copy from a word processor keeps its text rather than its image rendering. +- Paste a file or an image from your clipboard. A clipboard that also contains plain text pastes the text instead, so that a copy from a word processor keeps its text rather than its image rendering. Sent files stay in the transcript as download chips, and reloading the page redraws them. How a file reaches the model depends on its type and on the agent's model provider. @@ -127,25 +127,25 @@ Sent files stay in the transcript as download chips, and reloading the page redr When the message text includes a link to a file of an accepted type other than HTML, such as `https://example.com/q3-report.pdf`, the UI also sends the link as a file link. When the ModelConfig sets `apiFormat: responses`, or the provider is Gemini, the model fetches the file itself. The provider must therefore be able to reach the link. The UI does not send a link to an IP address or to a local hostname, such as one that ends in `.svc` or `.local`, because the provider cannot reach such a host. Every other model receives the link as text, and an agent with a fetch tool can read the file from it. Only the message that includes a link sends it as a file. In later turns, the model receives the bare URL as text. -An empty file, or one that holds only whitespace, reaches the model as the note `[Uploaded file "notes.txt" (text/plain) contained no extractable text.]` instead of contents, so the agent reports the gap rather than answering from nothing. +An empty file, or one that contains only whitespace, reaches the model as the note `[Uploaded file "notes.txt" (text/plain) contained no extractable text.]`, so the agent reports the gap. -The following limits apply to attachments: +Different components between the browser and the model enforce the attachment limits: | Limit | Value | Enforced by | | ----- | ----- | ----------- | -| Accepted file types | Plain text, Markdown, CSV, JSON, XML, YAML, HTML, PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files, and PNG, JPEG, GIF, and WebP images | The UI, as you stage each file. A rejected file is reported by name. | +| Accepted file types | Plain text, Markdown, CSV, JSON, XML, YAML, HTML, PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files, and PNG, JPEG, GIF, and WebP images | The UI, as you stage each file. The UI reports a rejected file by name. | | Total size of the files in one message | 10 MB | The UI, as you stage each file. | | Text read from one file | 200,000 characters | The runtime, which appends `[truncated]` to a longer file. | -| gRPC message that the kagent API accepts | 16 MiB | The controller's gRPC server, and every kagent gRPC client. The limit is a compiled-in constant that no Helm value changes. | +| gRPC message that the kagent API accepts | 16 MiB | The controller's gRPC server, and every kagent gRPC client. The limit is a compiled-in constant that a Helm value cannot change. | | Request body that the UI's nginx accepts | `ui.nginx.clientMaxBodySize`, default `20m` | The UI pod's nginx. The default sits just above the 16 MiB gRPC message limit. | The UI falls back to the file extension when the browser does not report a media type for a file, or reports the wrong one. For example, browsers do not report a media type for `.md` files. -If you host a reverse proxy in front of the UI, the proxy applies its own body-size limit, and a default nginx allows only `1m`. Raise the limit as shown in [Serve the UI under a sub-path]({{< link path="setup/reverse-proxy#proxy-shapes" >}}). +If you host a reverse proxy in front of the UI, the proxy applies its own body-size limit. A default nginx allows only `1m`. To raise the limit, see [Serve the UI under a sub-path]({{< link path="setup/reverse-proxy#proxy-shapes" >}}). ## Check Agent Substrate capacity -The **Substrate** page shows whether there is capacity for an agent to run. It reads WorkerPools and ActorTemplates from Kubernetes, and live Actors and Worker assignments from the Agent Substrate API. +The **Substrate** page shows whether Agent Substrate has capacity to run an agent. It reads WorkerPools and ActorTemplates from Kubernetes, and live Actors and Worker assignments from the Agent Substrate API. {{< reuse-image-light src="img/kagent-ui-substrate.png" alt="The Substrate page, showing one worker pool and no running actors" caption="Figure: The Substrate page" >}} {{< reuse-image-dark srcDark="img/kagent-ui-substrate-dark.png" alt="The Substrate page, showing one worker pool and no running actors" caption="Figure: The Substrate page" >}} @@ -154,16 +154,18 @@ Read the page from the top tiles down. | Tile | Means | | ---- | ----- | -| **Worker pools** | {{< gloss "WorkerPool" >}}WorkerPools{{< /gloss >}} that an operator has provisioned. No Harness can run an agent until at least one exists. | +| **Worker pools** | {{< gloss "WorkerPool" >}}WorkerPools{{< /gloss >}} that an operator has provisioned. A Harness cannot run an agent until at least one WorkerPool exists. | | **Templates ready** | {{< gloss "ActorTemplate" >}}ActorTemplates{{< /gloss >}} that have compiled and are ready to be instantiated. | -| **Actors running** | Live Actors, each one a Session holding a conversation. | +| **Actors running** | Live Actors, each one a Session. | | **Workers busy** | {{< gloss "Worker" >}}Workers{{< /gloss >}} currently assigned to an Actor, against the total provisioned. | -| **Scope** | The filters this page asked for, rather than what the API returned. Reads `all` until you set a filter, then `K8s: ; ATE: `. | +| **Requested filters** | The filters this page asked for, not what the API returned. Reads `none` until you set a filter, then `K8s: ; ATE: `, with `any` in place of a filter that you leave empty. | -A fresh installation reports one worker pool, no actor templates, and no actors, because nothing has created an agent yet. The **Workers busy** tile reads `0/1`, and the Workers table lists the pool's single worker with the pod that backs it. That unassigned worker confirms that capacity is provisioned and waiting for the first agent. The Actors table reads `No actors on this page.` +On a fresh installation, the page reports one WorkerPool, zero ActorTemplates, and zero Actors, because an agent does not exist yet. The **Workers busy** tile reads `0/1`, and the Workers table lists the pool's single worker with the pod that backs it. That unassigned worker confirms that capacity is provisioned and waiting for the first agent. The Actors table reads `No actors on this page.` > [!NOTE] -> When the Substrate page cannot reach the Agent Substrate API, it reports the failure rather than an empty result. A warning above the tables carries the error, and the Actors table reads `This read could not reach ate-api, so there may be actors it did not see.` On a cluster that does have agents, `No actors on this page.` means that the read succeeded and matched nothing, so check the **Kubernetes namespace** and **ATE atespace** filters. +> When the Substrate page cannot reach the Agent Substrate API, it reports the failure rather than an empty result. A warning above the tables shows the error, and the Actors table reads `This read could not reach ate-api, so there may be actors it did not see.` On a cluster that does have agents, `No actors on this page.` means that the read succeeded and matched nothing, so check the **Kubernetes namespace** and **ATE atespace** filters. + +When you [restrict kagent to specific namespaces]({{< link path="setup/installation#restrict-kagent-to-specific-namespaces" >}}), several kagent installations can share one Agent Substrate. The Substrate page then reads only the watched namespaces and the atespaces that kagent names after them. Empty filters cover every watched namespace. A filter that specifies any other namespace or atespace fails with a `PermissionDenied` error. An installation that watches every namespace reads the entire Agent Substrate. ## Expose the UI outside the cluster @@ -187,9 +189,9 @@ kubectl get svc -n kagent {{< reuse "kagent-docs/snippets/name-ui.md" >}} ### OpenShift route -On OpenShift clusters kagent creates an edge-terminated `Route` for the UI whenever the `route.openshift.io/v1` API is present. The route is enabled by default through `ui.route.enabled`. +On OpenShift clusters, kagent creates an edge-terminated `Route` for the UI whenever the `route.openshift.io/v1` API is present. The route is enabled by default through `ui.route.enabled`. -The default HAProxy timeout is raised to 120 minutes, because agent conversations hold long-lived streaming connections that a shorter timeout terminates mid-reply. To change it, set the annotation. +The chart raises the default HAProxy timeout to 120 minutes, because agent conversations keep long-lived streaming connections open, and a shorter timeout terminates them mid-reply. To change the timeout, set the annotation. ```yaml ui: @@ -198,7 +200,7 @@ ui: haproxy.router.openshift.io/timeout: 60m ``` -To front the UI with your own ingress instead, set `ui.route.enabled: false` to stop kagent creating the route. +To front the UI with your own ingress instead, set `ui.route.enabled: false` so that kagent does not create the route. ### Gateway API HTTPRoute diff --git a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md index f910ac68..328de9fc 100644 --- a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md +++ b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md @@ -173,7 +173,7 @@ kagent 1.0 cannot pass a mounted credential or certificate file to an agent. In 1. Recreate the Secrets that your ModelConfigs name before you apply them, or the ModelConfigs resolve to nothing. -2. Convert these resources to `v1alpha3`. The export also holds Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. The command does not delete the ModelConfig fields that `v1alpha3` removed, so delete them from the converted file before you apply it. Otherwise, the apply fails. +2. Convert these resources to `v1alpha3`. The export also holds Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. The command does not delete the ModelConfig fields that `v1alpha3` removed, so delete them from the converted file before you apply it. Otherwise, the apply fails. If the converted file includes the `kagent-grafana-mcp` RemoteMCPServer, delete it too, because 1.0 does not deploy the Grafana MCP server behind it. ```bash yq '.items |= map(select(.kind == "ModelConfig" or .kind == "ModelProviderConfig" or .kind == "RemoteMCPServer")) | .items[].apiVersion = "api.kagent.dev/v1alpha3" diff --git a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md index 23c00325..2883d2eb 100644 --- a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md +++ b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md @@ -32,7 +32,7 @@ The single 0.10.x `Agent` resource described both what an agent does and how it Nothing pairs a Harness and an AgentTemplate implicitly. Neither resource carries a field naming the other, so an Agent is what makes a template runnable on a Harness. For the full model, see [Core concepts]({{< link path="about/core-concepts#agent" >}}). -The CRD API group also moves from `kagent.dev/v1alpha2` to `api.kagent.dev/v1alpha3`, with no conversion between the two. 1.0 no longer bundles kmcp, so it does not install the `mcpservers.kagent.dev` CRD. The two releases still cannot run side by side on one cluster, because both charts name their ClusterRoles after the release. +The CRD API group also moves from `kagent.dev/v1alpha2` to `api.kagent.dev/v1alpha3`, with no conversion between the two. 1.0 no longer bundles kmcp, so it does not install the `mcpservers.kagent.dev` CRD. 1.0 also no longer bundles the Grafana MCP server, so it does not create the `kagent-grafana-mcp` RemoteMCPServer. The two releases still cannot run side by side on one cluster, because both charts name their ClusterRoles after the release. ### Removed and replaced resources 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 1117c82d..79e687a9 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -1,20 +1,15 @@ --- title: Tools ecosystem -description: Look up the MCP servers that a kagent installation ships with, the tools that each one serves, and the community servers that you can add. +description: Look up the MCP server that a kagent installation ships with, the tools that it serves, and the community servers that you can add. weight: 40 author: kagent.dev --- -A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation ships with two {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) servers already registered, and you can register more of your own. This page is the catalog: what is installed, what each server serves, and how to narrow the set. For the schema that binds a server to an agent, see [About tools]({{< link path="skills-and-mcp/about-tools#mcp-tools" >}}). +A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation ships with one {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) server already registered, and you can register more of your own. This page lists what is installed, what the installed server serves, and how to narrow the set. For the schema that binds a server to an agent, see [About tools]({{< link path="skills-and-mcp/about-tools#mcp-tools" >}}). -## Servers that a kagent installation registers +## Server that a kagent installation registers -Both servers arrive as subcharts of the kagent chart, and each one creates its own {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace. Neither is bound to an agent for you, so an agent reaches a server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. - -| RemoteMCPServer | Helm value | Serves | -| --------------- | ---------- | ------ | -| [`kagent-tool-server`](#kagent-tool-server) | `kagent-tools.enabled` | 126 tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. | -| [`kagent-grafana-mcp`](#kagent-grafana-mcp) | `grafana-mcp.enabled` | Grafana's own MCP server, for dashboards, data sources, alerts, Loki logs, and incidents. | +The kagent chart installs the tool server as a subchart, which the `kagent-tools.enabled` Helm value turns on. The subchart creates the [`kagent-tool-server`](#kagent-tool-server) {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace, which serves 126 tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. kagent does not bind the server to an agent, so an agent reaches the server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. The kagent release pins the `kagent-tool-server` version, which is currently {{< reuse "kagent-docs/versions/kagent-tools.md" >}}. For the full set of pinned component versions, see [Version support]({{< link path="reference/versions#what-a-kagent-release-includes" >}}). @@ -302,34 +297,6 @@ kubectl label namespace my-agents kagent-tools=allowed > [!NOTE] > Admitting a namespace grants every agent in that namespace the server's full cluster authority, because all of the server's callers share one ServiceAccount. Narrow the server with `rbac.readOnly` before you widen `allowedNamespaces`. -## kagent-grafana-mcp - -The `kagent-grafana-mcp` server runs [mcp-grafana](https://github.com/grafana/mcp-grafana), Grafana's own MCP server. The kagent chart packages it as a subchart. Its tools fall into four groups: - -- Grafana dashboards, data sources, and alert rules. -- PromQL queries against Grafana's Prometheus data sources. -- LogQL queries against Loki. -- Grafana incidents and on-call schedules. - -Set the Grafana instance that the server queries, along with its credentials, at install time. - -```yaml -grafana-mcp: - enabled: true - grafana: - url: "grafana.kagent:3000/api" - serviceAccountToken: "" -``` - -| Field | Description | -| ----- | ----------- | -| `grafana.url` | The Grafana API endpoint that the server queries. Defaults to `grafana.kagent:3000/api`, which matches the Grafana instance in `contrib/addons/grafana.yaml`. | -| `grafana.serviceAccountToken` | A Grafana service account token. Empty by default, so the server starts and answers discovery while every tool call fails until you supply a token. | -| `grafana.secretRef` | The name of an existing Secret that holds `GRAFANA_SERVICE_ACCOUNT_TOKEN`. Use this instead of `serviceAccountToken` to keep the token out of your Helm values. | - -> [!NOTE] -> Upstream does not publish a tag other than `latest`, so this subchart tracks the `mcp/grafana:latest` image on Docker Hub. A kagent release therefore does not pin which tools this server serves, and the set can change without a kagent upgrade. Read `status.discoveredTools` on `kagent-grafana-mcp` for the tools that your installation has, rather than relying on a published list. - ## Community and contributed servers kagent's repository carries example servers in [contrib/tools](https://github.com/kagent-dev/kagent/tree/main/contrib/tools). Each one is a starting point rather than a supported component, and their manifests were written against different kagent API versions, so check the `apiVersion` before you apply one. @@ -339,10 +306,10 @@ kagent's repository carries example servers in [contrib/tools](https://github.co | [k8sgpt](https://github.com/kagent-dev/kagent/tree/main/contrib/tools/k8sgpt-mcp-server) | Yes | K8sGPT integration, for diagnosing cluster problems. Declares `RemoteMCPServer` on `api.kagent.dev/v1alpha3`. | | [server-everything](https://github.com/kagent-dev/kagent/tree/main/contrib/tools/server-everything) | Yes | The MCP reference server, useful for testing a binding end to end. Declares `RemoteMCPServer` on `api.kagent.dev/v1alpha3`. | | [GitHub MCP Server](https://github.com/kagent-dev/kagent/tree/main/contrib/tools/github-mcp-server) | No | Tools for issues, pull requests, repositories, and actions. Its chart still declares the removed `ToolServer` kind. | -| [mcp-grafana](https://github.com/kagent-dev/kagent/tree/main/contrib/tools/mcp-grafana) | No | Superseded by the `grafana-mcp` subchart that a kagent installation already registers. Its chart still declares the removed `ToolServer` kind. | +| [mcp-grafana](https://github.com/kagent-dev/kagent/tree/main/contrib/tools/mcp-grafana) | No | Tools for Grafana dashboards, data sources, alerts, and Loki logs. Its chart still declares the removed `ToolServer` kind. | | [context7](https://github.com/kagent-dev/kagent/blob/main/contrib/tools/context7.mcp.yaml) | No | Documentation lookup over stdio. Both the `ToolServer` kind and the stdio transport were removed. | -To register any other MCP server, write a RemoteMCPServer that points at its URL. [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-your-own-mcp-server" >}}) works through a complete example. +To register any other MCP server, write a RemoteMCPServer that points at its URL. For a complete example, see [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-your-own-mcp-server" >}}). To give agents Grafana tools, run Grafana's own server from the upstream [mcp-grafana](https://github.com/grafana/mcp-grafana) project, and register it in the same way. > [!NOTE] > A RemoteMCPServer takes a URL, so kagent connects only to servers that speak streamable HTTP or Server-Sent Events (SSE). To reach a server that runs over stdio, wrap it in a deployment that exposes an HTTP endpoint. [kmcp](https://kagent.dev/docs/kmcp/) builds and runs such servers. diff --git a/docs-site/content/kagent/1.x/setup/installation.md b/docs-site/content/kagent/1.x/setup/installation.md index 37ec24bf..521c5231 100644 --- a/docs-site/content/kagent/1.x/setup/installation.md +++ b/docs-site/content/kagent/1.x/setup/installation.md @@ -101,7 +101,6 @@ The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertif NAME READY STATUS RESTARTS AGE kagent-controller-56c67b6cd7-hqj5z 1/1 Running 3 2m kagent-default-774d4496bd-sfx4b 1/1 Running 0 2m - kagent-grafana-mcp-7c67f5697c-4mf78 1/1 Running 0 2m kagent-postgresql-856b475f59-n258n 2/2 Running 0 2m kagent-tools-54959b659c-lfkrz 1/1 Running 0 2m kagent-ui-5fdb6fd85c-vzb2z 1/1 Running 0 2m diff --git a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md index 08736842..7d4becb7 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md @@ -55,7 +55,7 @@ For the rest of the settings that the `openAI` block accepts, see [OpenAI]({{< l ### Enable tool calling in vLLM -kagent sends a `tools` array with `tool_choice: "auto"` on every request. kagent's runtime registers a built-in `ask_user` tool on every agent, so that array is sent even when you bind no tools yourself. Launch vLLM with automatic tool choice enabled, or every agent turn fails. +kagent sends a `tools` array with `tool_choice: "auto"` on every request. By default, kagent's runtime registers a built-in `ask_user` tool on every agent, so that array is sent even when you do not bind any tools yourself. Launch vLLM with automatic tool choice enabled, or every agent turn fails. ```bash vllm serve Qwen/Qwen2.5-7B-Instruct \ From 4ed620ec0bcc90006be6e5607d55fb0dfa48b610 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Tue, 27 Oct 2026 09:46:16 -0500 Subject: [PATCH 2/9] full-file wording updates based on conventions Signed-off-by: Rachael Graham --- .../substrate-runtime/standalone-sandboxes.md | 48 +++++++------- .../kagent/1.x/agents/human-in-the-loop.md | 50 +++++++------- .../kagent/1.x/agents/structured-output.md | 30 +++++---- .../content/kagent/1.x/examples/a2a-agents.md | 58 ++++++++-------- .../content/kagent/1.x/examples/skills.md | 66 ++++++++++--------- .../kagent/1.x/operations/upgrade-from-0x.md | 50 +++++++------- .../kagent/1.x/reference/release-notes/1.0.md | 26 ++++---- .../kagent/1.x/reference/tools-ecosystem.md | 48 +++++++------- .../content/kagent/1.x/setup/installation.md | 32 ++++----- .../1.x/setup/model-providers/byo-openai.md | 27 +++----- 10 files changed, 219 insertions(+), 216 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index c0c9e291..8415890e 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md @@ -1,13 +1,13 @@ --- title: Standalone sandboxes -description: Run commands and transfer files in a scratch environment that no agent conversation owns. +description: Run commands and transfer files in a scratch environment that is independent of any agent Session. weight: 25 author: kagent.dev aliases: - /kagent/1.x/substrate-runtime/standalone-sandboxes/ --- -A **Sandbox** is a scratch environment for running commands and working with files. It is scoped to the caller who created it, it expires on a timer, and no agent conversation owns it. You create one from a **SandboxTemplate**, run processes and move files in it, then delete it or let it expire. +A **Sandbox** is a scratch environment for running commands and working with files. A Sandbox is scoped to the caller who created it, and it expires on a timer. You create one from a **SandboxTemplate**, run processes and move files in it, then delete it or let it expire. A Sandbox and a {{< gloss "Session" >}}Session{{< /gloss >}} are separate runtimes with separate configuration and separate lifetimes. Neither resource references the other. @@ -16,11 +16,11 @@ A Sandbox and a {{< gloss "Session" >}}Session{{< /gloss >}} are separate runtim | An {{< gloss "Agent" >}}Agent{{< /gloss >}} that pairs an AgentTemplate with a Harness | A Session | A2A interactions and tasks | | A SandboxTemplate that defines a tools environment | A Sandbox | Guest process and file operations | -An agent can create a Sandbox of its own through the kagent {{< gloss "MCP" >}}MCP{{< /gloss >}} server, using the same service that a person calls. The agent's conversation and the Sandbox's lifetime stay independent of each other. +An agent can create a Sandbox of its own through the kagent Model Context Protocol ({{< gloss "MCP" >}}MCP{{< /gloss >}}) server, using the same service that a person calls. The Sandbox's lifetime stays independent of the agent's Session. ## Before you begin -1. [Install kagent]({{< link path="setup/installation" >}}) and set the following `config.sandboxes` values on the installation. Sandbox preparation needs a guest image, and the chart does not ship a default, so a SandboxTemplate never becomes ready until you set one. The remaining values allocate the compute that each Sandbox runs on and bound how long it lives. +1. [Install kagent]({{< link path="setup/installation" >}}) and set the following `config.sandboxes` values on the installation. Sandbox preparation needs a guest image. The chart does not ship a default guest image, so a SandboxTemplate does not become ready until you set one. The remaining values allocate the compute that each Sandbox runs on and bound how long it lives. ```yaml config: sandboxes: @@ -31,22 +31,22 @@ An agent can create a Sandbox of its own through the kagent {{< gloss "MCP" >}}M maxTtl: 86400s ``` - A SandboxTemplate cannot override these values, so they are the installation's policy rather than a per-template choice. + A SandboxTemplate cannot override these values, so they apply to every Sandbox in the installation. | Value | Default | Description | | ----- | ------- | ----------- | - | `config.sandboxes.guestImage` | `""` | The digest-pinned guest image, as a complete reference. Required: preparation fails without it. The controller passes the reference to Agent Substrate unchanged and does not resolve tags, so pin a digest rather than a tag. Setting `global.imageRegistry` rewrites the registry host of this reference. | + | `config.sandboxes.guestImage` | `""` | The digest-pinned guest image, as a complete reference. This value is required. The controller passes the reference to Agent Substrate unchanged and does not resolve tags, so pin a digest. Setting `global.imageRegistry` rewrites the registry host of this reference. | | `config.sandboxes.cpu` | `1` | CPU allocated to each Sandbox. | | `config.sandboxes.memory` | `1Gi` | Memory allocated to each Sandbox. | | `config.sandboxes.defaultTtl` | `3600s` | Lifetime applied when `kagent sandbox create` omits `--ttl`. Durations in the `config` block take protobuf seconds notation, so `3600s` rather than `1h`. | - | `config.sandboxes.maxTtl` | `86400s` | Upper bound on any requested lifetime, which the controller caps at `86400s`. Note that activity does not extend a Sandbox's lifetime. A Sandbox expires the configured interval after it is created, however recently a command ran in it. | + | `config.sandboxes.maxTtl` | `86400s` | Upper bound on any requested lifetime, which the controller caps at `86400s`. A Sandbox expires the configured interval after it is created, regardless of how recently a command ran in it. | 2. Download the kagent CLI. The `--version` flag matches the CLI to the release that these docs cover. ```bash curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v{{< reuse "kagent-docs/versions/kagent.md" >}} ``` -3. Install [`jq`](https://jqlang.org/download/) to read the Sandbox ID out of the CLI's JSON output. +3. To read the Sandbox ID from the CLI's JSON output, install [`jq`](https://jqlang.org/download/). ## Create a SandboxTemplate @@ -80,12 +80,12 @@ A SandboxTemplate is a namespaced `api.kagent.dev/v1alpha3` resource that prepar | Field | Required | Description | | ----- | -------- | ----------- | | `workload.image` | Yes | The tools image, pinned by `sha256` digest. A tag alone is rejected, because a prepared revision must be reproducible. | - | `env` | No | Environment defaults for the guest, up to 100. Each entry sets a literal `value`, which is required and may be an empty string. The schema does not define a secret-backed source, so the API server rejects a `credentialRef` entry as an unknown field. | + | `env` | No | Environment defaults for the guest, up to 100. Each entry sets a literal `value`, which is required and can be an empty string. The schema does not define a secret-backed source, so the API server rejects a `credentialRef` entry as an unknown field. | | `substrate.workerPoolRef.name` | Yes | The WorkerPool that this template's Actors are scheduled onto. | | `substrate.snapshotPolicy.location` | Yes | The object storage location for Actor {{< gloss "Snapshot" >}}snapshots{{< /gloss >}}. | - | `egressPolicyRefs` | No | Up to 16 EgressPolicy resources in the template's namespace that grant the Sandbox outbound access. When this field is omitted, the Sandbox has no outbound access. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). | + | `egressPolicyRefs` | No | Up to 16 EgressPolicy resources in the template's namespace that grant the Sandbox outbound access. When this field is omitted, the Sandbox does not have outbound access. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). | - A SandboxTemplate does not take a startup command or a guest toggle. kagent supplies the guest entrypoint from the image that `config.sandboxes.guestImage` names. That image replaces the tools image's own entrypoint. Your tools image contributes the installed programs and nothing else. + A SandboxTemplate does not take a startup command or a guest toggle. kagent supplies the guest entrypoint from the image that `config.sandboxes.guestImage` references. That image replaces the tools image's own entrypoint. Your tools image contributes the installed programs and nothing else. 2. Confirm that the template prepared a revision before you create a Sandbox from it. ```bash @@ -93,15 +93,15 @@ A SandboxTemplate is a namespaced `api.kagent.dev/v1alpha3` resource that prepar -o jsonpath='{range .status.conditions[?(@.type=="Ready")]}{.status} {.reason} {.message}{end}' ``` -Editing a template prepares a new revision. A Sandbox that already exists keeps the revision it was created from, and deleting the template retires preparation without removing the Sandboxes that pinned it. +Editing a template prepares a new revision. A Sandbox that already exists keeps the revision that it was created from. Deleting the template retires preparation but does not remove the Sandboxes that pinned it. ## Run a command +Each `kagent sandbox` command makes one lifecycle attempt. The `create` command takes a stable `--request-id` value. To retry the same creation, reuse that value. + > [!WARNING] > **By default, the gateway denies every outbound connection from a Sandbox.** When the template does not select an EgressPolicy resource, kagent compiles an empty [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}) for the Sandbox. A command that fetches a package, clones a repository, or calls an API then fails. To allow a destination, select an EgressPolicy in the SandboxTemplate's `spec.egressPolicyRefs` field, as described in [Grant access to a destination]({{< link path="agents/egress-policies#grant-access-to-a-destination" >}}). Otherwise, move what a command needs into the Sandbox with `kagent sandbox upload`. -Each `kagent sandbox` command makes one lifecycle attempt rather than retrying for you, so `create` takes a stable `--request-id` that you reuse to retry the same creation. - 1. List the templates that your installation prepared. ```bash kagent sandbox templates @@ -119,7 +119,7 @@ Each `kagent sandbox` command makes one lifecycle attempt rather than retrying f echo $SANDBOX_ID ``` - Run the command without `-o json` to see the table instead. `STATE` and `OPERATION` both matter, because lifecycle work can still be pending when a call returns. + To see the table, run the command without `-o json`. `STATE` and `OPERATION` both matter, because lifecycle work can still be pending when a call returns. ```console ID TEMPLATE STATE OPERATION EXPIRES FAILURE 0198c3f1-2a44-7c90-b5e1-9d8f3a7b2c04 kagent/scratch RUNTIME_STATE_READY RUNTIME_OPERATION_NONE 2026-10-01T16:30:00Z @@ -130,7 +130,7 @@ Each `kagent sandbox` command makes one lifecycle attempt rather than retrying f kagent sandbox exec $SANDBOX_ID -- python --version ``` - A timeout stops the command from waiting rather than stopping the remote process. To reconnect to a process that outlived its `exec`, pass its process ID to `kagent sandbox wait`. + A timeout stops the command from waiting, but the remote process keeps running. To reconnect to a process that outlived its `exec`, pass its process ID to `kagent sandbox wait`. `kagent sandbox kill` sends `SIGKILL` to a process and returns when the guest delivers the signal. The command does not wait for the process to exit, so its output reports the process state and omits the exit code. To read the exit code, pass the process ID to `kagent sandbox wait`. @@ -141,17 +141,17 @@ Each `kagent sandbox` command makes one lifecycle attempt rather than retrying f kagent sandbox download $SANDBOX_ID /data/workspace/out.txt ./out.txt ``` -5. Delete the Sandbox when you are finished. Deleting is not required, because the Sandbox expires on its own. Deleting it releases the compute immediately. +5. When you are finished, delete the Sandbox to release its compute immediately. Deletion is optional, because the Sandbox expires on its own. ```bash kagent sandbox delete $SANDBOX_ID ``` > [!IMPORTANT] -> **Suspending a Sandbox interrupts whatever it is doing.** `kagent sandbox suspend` waits for no process, output stream, or file transfer, so a command can be cut off mid-run and a file can be left partly written. Resuming restores the durable files under `/data`, but a process handle does not survive, because the guest holds it in memory. Suspend a Sandbox only when you can repeat whatever it was running. +> **Suspending a Sandbox interrupts whatever it is doing.** `kagent sandbox suspend` does not wait for any process, output stream, or file transfer, so a command can be cut off mid-run and a file can be left partly written. Resuming restores the durable files under `/data`, but a process handle does not survive, because the guest holds it in memory. Suspend a Sandbox only when you can repeat whatever it was running. -## Reach a sandbox from an agent +## Reach a Sandbox from an agent -kagent's Helm chart installs a `RemoteMCPServer` named `kagent-api` in the controller's namespace, pointing at the controller's own `/mcp` endpoint. This server exposes the Session and checkpoint tools alongside the sandbox tools, so an AgentTemplate reaches sandboxes through an ordinary tool binding. +kagent's Helm chart installs a `RemoteMCPServer` named `kagent-api` in the controller's namespace, pointing at the controller's own `/mcp` endpoint. This server exposes the Session and checkpoint tools alongside the sandbox tools, so an AgentTemplate reaches Sandboxes through an ordinary tool binding. ```yaml tools: @@ -168,17 +168,17 @@ tools: - read_sandbox_outputs ``` -An agent that creates a Sandbox owns it under whatever identity the MCP connection authenticated, not under the identity of the person it is talking to. A Session share token does not grant access to a Sandbox. To have an agent act for the person who invoked it, configure credential propagation. Set `config.runtimeDefaults.tokenPropagation.enabled` to `true`, and the kagent runtime passes the caller's credentials and identity to the MCP servers that you trust. +An agent that creates a Sandbox owns it under the identity that the MCP connection authenticated, not under the identity of the person that the agent is talking to. A Session share token does not grant access to a Sandbox. To have an agent act for the person who invoked it, configure credential propagation by setting `config.runtimeDefaults.tokenPropagation.enabled` to `true`. The kagent runtime then passes the caller's credentials and identity to the MCP servers that you trust. The MCP transfer limits are tighter than the command line's. A gRPC file transfer is bounded at 64 MiB. `write_sandbox_file` and `read_sandbox_outputs` are bounded at 1 MiB and use base64. Output reads return continuation byte offsets. An agent that starts a process with `start_sandbox_process` polls `get_sandbox_process` to learn when the process finishes. The `state` field reads `PROCESS_STATE_RUNNING` until the process exits, then `PROCESS_STATE_EXITED`. The `exit_code` field appears only in the exited state, and reads `0` for a successful exit. `kill_sandbox_process` sends `SIGKILL` in the same way as `kagent sandbox kill`, so the agent must poll `get_sandbox_process` for the exit code. -When an agent must inspect a result file, use `read_sandbox_file`. The tool returns content that the model reads directly rather than `data_base64`, in one of three shapes. +When an agent must inspect a result file, use `read_sandbox_file`. The tool returns content that the model reads directly, not `data_base64`. The shape of the content depends on the file type. -Text returns as numbered lines. `offset` takes the first line to read, counting from 1, and `limit` takes the number of lines, which defaults to 2000. A page also stops at 32 KiB, whichever bound it reaches first. A line longer than 2000 bytes is truncated to 2000 bytes and marked `[line cut]`. A page that stops before the end of the file reports the offset to continue from. +Text returns as numbered lines. `offset` sets the first line to read, counting from 1. `limit` sets the number of lines, and defaults to 2000. A page also stops at 32 KiB, whichever bound it reaches first. A line longer than 2000 bytes is truncated to 2000 bytes and marked `[line cut]`. A page that stops before the end of the file reports the offset to continue from. -A PNG, JPEG, or GIF file of at most 10 MiB returns as an image. The Claude and Codex harnesses resize those images for their models. A larger image returns a message asking the agent to write a smaller copy in the Sandbox. Every other file type returns a description of the file rather than its bytes. +A PNG, JPEG, or GIF file of at most 10 MiB returns as an image. The `claude` and `codex` Harness runtimes resize those images for their models. A larger image returns a message asking the agent to write a smaller copy in the Sandbox. Every other file type returns a description of the file, not its bytes. ## Next steps diff --git a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md index fccaa7a6..c1b13bc2 100644 --- a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md +++ b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md @@ -7,11 +7,11 @@ author: kagent.dev Some agents only answer questions, and those can run unattended. Others call tools that change a cluster, spend money, or read sensitive information. An agent that takes actions like those should get a person's approval first. The human in the loop (HITL) mechanism lets an agent stop mid-turn, return a question or a pending tool call to a person, and continue once that person answers. -A working HITL setup needs two halves. The agent must pause, which you configure by setting `requireApproval` on a tool binding in an {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. The client must support the A2A human-in-the-loop extension, which presents the request to a person and returns the decision to the agent. A client without the extension still sees the agent stop, and cannot answer. Confirm that the client you plan to use supports the extension before you configure an agent to pause. For which clients qualify, see [Client support](#client-support). +A working HITL setup needs support from both the agent and the client. The agent must pause, which you configure by setting `requireApproval` on a tool binding in an {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. The client must support the Agent-to-Agent (A2A) human-in-the-loop extension, so that the client can present the request to a person and return the decision to the agent. A client without the extension still sees the agent stop, but cannot answer. Confirm that the client you plan to use supports the extension before you configure an agent to pause. For the clients that support the extension, see [Client support](#client-support). -## How a pause works +## Pause lifecycle -A client opens every turn by telling the agent which optional A2A extensions it can handle. A client that requests the HITL extension on that message can answer a pause later in the turn. kagent activates the extension only for the calls that request it, and attaches the pause payload only to those calls. A client that does not request the extension still receives a reply when the turn finishes. When the agent stops for a person, that client has no payload to answer. +A client opens every turn by telling the agent which optional A2A extensions it can handle. A client that requests the HITL extension on that message can answer a pause later in the turn. A client that does not request the extension still receives a reply when the turn finishes. When the agent stops for a person, that client does not receive a payload to answer. The following diagram traces one turn in which the agent stops. @@ -38,14 +38,14 @@ The agent works until it either finishes the turn or needs a person. When it nee ## Pause kinds -An agent pauses either to get permission before it acts or to ask a question. A tool approval and a question raise different requests, but both pause and resume the same way. A client that handles one handles the other with a different payload. +An agent pauses either to get permission before it acts or to ask a question. A tool approval and a question raise different requests, but both pause and resume in the same way. A client that handles one handles the other with a different payload. | Request | Raised when | The client answers with | | ------- | ----------- | ----------------------- | | `tool_approval_request` | The agent wants to call a tool from a binding that sets `requireApproval`. | `tool_approval_response` | -| `ask_user_request` | The agent calls the built-in `ask_user` tool because it needs information only a person has. | `ask_user_response` | +| `ask_user_request` | The agent calls the built-in `ask_user` tool because it needs information that only a person has. | `ask_user_response` | -Every pause also adds a plain text part to its status message that states why the agent stopped. A client that never activated the extension receives that text and nothing else. It can show a person why the agent stopped, but it has no `id` to answer with, so the task stays at `INPUT_REQUIRED`. Treat the text as a diagnostic rather than as a fallback, and send the turn again from a client that requests the extension. The `hint` field of an activated payload holds the same string. +Every pause also adds a plain text part to its status message that states why the agent stopped. A client that never activated the extension receives that text and nothing else. The client can show a person why the agent stopped. The client does not have an `id` to answer with, so the task stays at `INPUT_REQUIRED`. Treat the text as a diagnostic rather than as a fallback, and send the turn again from a client that requests the extension. The `hint` field of an activated payload contains the same string. The `kagent` harness composes this text from the pending calls. The wording depends on what the agent stopped for. @@ -54,13 +54,13 @@ The `kagent` harness composes this text from the pending calls. The wording depe | `ask_user` | The questions the agent asked, joined with `; `. | | A tool pause whose tools supplied hints | The hints, followed by the tool names in parentheses, such as `Deleting this file requires approval (delete_file, restart_pod)`. | | A tool pause with no hints | `Approval is required for tool(s): delete_file` | -| A pause carrying no tool call | `Human input is required before the agent can continue.` | +| A pause that does not include a tool call | `Human input is required before the agent can continue.` | The `codex` and `claude` harnesses compose the text differently. Both pass through the hint that Codex or Claude Code supplied with the request, so the wording comes from the coding agent rather than from kagent. ### Disable questions -When an agent runs unattended, nobody answers its question, so the task waits at `INPUT_REQUIRED`. To stop agents on the `kagent` harness from asking questions, set the `config.runtimeDefaults.builtinTools.disableAskUser` Helm value to `true`. The value removes the `ask_user` tool from every agent in the installation and from each subagent, and an AgentTemplate cannot override it. The value defaults to `false`. +When an agent runs unattended, nobody answers its question, so the task waits at `INPUT_REQUIRED`. To stop agents on the `kagent` harness from asking questions, set the `config.runtimeDefaults.builtinTools.disableAskUser` Helm value to `true`. The value removes the `ask_user` tool from every agent in the installation and from each subagent. An AgentTemplate cannot override the value. The value defaults to `false`. ```yaml config: @@ -114,15 +114,15 @@ spec: | Field | Description | | ----- | ----------- | -| `mcp.tools` | The names of the tools to bind. Omit the list, or leave it empty, to bind every tool that the server offers. | -| `mcp.requireApproval` | Pauses before each invocation of a tool that this binding exposes. The pause covers the tools in `mcp.tools`, or every tool on the server when `mcp.tools` is omitted or empty. Omit to run the bound tools without approval. | +| `mcp.tools` | The names of the tools to bind. To bind every tool that the server offers, omit the list or leave it empty. | +| `mcp.requireApproval` | Pauses before each invocation of a tool that this binding exposes. The pause covers the tools in `mcp.tools`, or every tool on the server when `mcp.tools` is omitted or empty. To run the bound tools without approval, omit the field. | Approval belongs to the binding rather than to the tool name, so one server can supply tools that require approval alongside tools that do not. Bind the tools that require approval in a binding that sets `requireApproval`, and bind the tools that do not in a second binding that omits it. The `codex` and `claude` harnesses reject a second binding to the same server. An agent on those harnesses applies one approval setting to every tool that the server offers. > [!NOTE] > kagent 0.x named the tools that needed approval in a `requireApproval` list on the `Agent` resource, which matched tool names across every server. In 1.x, approval is a property of one binding, so the same tool name can pause for one server and run freely for another. -A tool that no binding covers with `requireApproval` runs without a pause. That includes every built-in tool, such as file access, shell, and web search, and every MCP tool on a binding that omits the field. The sandbox contains those calls. For more information, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}). +A tool that no binding covers with `requireApproval` runs without a pause. Such tools include every built-in tool, such as file access, shell, and web search, and every Model Context Protocol (MCP) tool on a binding that omits the field. The sandbox contains those calls. For more information, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}). ## Implement a client that supports human approval @@ -136,7 +136,7 @@ A client requests the extension by setting the extension URI as the `A2A-Extensi A2A-Extensions: https://kagent.dev/extensions/hitl/v1 ``` -kagent activates the extension only for calls that request it. It then echoes the activated URI back, and attaches a structured pause payload only to an activated call. An agent's card declares the extension, so a client can discover HITL support before it sends anything. +kagent activates the extension only for calls that request it. kagent then echoes the activated URI back and attaches a structured pause payload only to an activated call. An agent's card declares the extension, so a client can discover HITL support before it sends anything. A gRPC call names the {{< gloss "Agent" >}}Agent{{< /gloss >}} in the standard A2A `tenant` field, as `/`, and requests the extension through a header. Port-forward the controller's gRPC port first, as in [Install kagent]({{< link path="setup/installation" >}}). @@ -156,11 +156,11 @@ grpcurl -plaintext \ A message that omits both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}. To continue an existing one, set the message's `contextId` to the Session ID. Over HTTP, the URL path `/agents/{namespace}/{name}` selects the Agent instead, and no tenant is needed. For both transports, see [Call an agent over A2A]({{< link path="examples/a2a-agents" >}}). > [!WARNING] -> A send that omits the header produces a pause that no one can answer. The turn still stops, and its status message states the question as prose, with no `metadata` and no correlation `id`. The client therefore has nothing to render and no `id` to answer with. Re-reading that task with the header does not recover the payload, because kagent never attached one. To prevent this, send the header on every call. Sending it on a read is harmless, and missing it on a send cannot be undone. +> A send that omits the header produces a pause that no one can answer. The turn still stops, and its status message states the question as prose, without `metadata` or a correlation `id`. The client therefore does not have anything to render or an `id` to answer with. Re-reading that task with the header does not recover the payload, because kagent never attached one. To prevent an unanswerable pause, send the header on every call. Sending it on a read is harmless, and missing it on a send cannot be undone. ### Read the pause -When the agent pauses, the task moves to `INPUT_REQUIRED`. The payload arrives in the status message's `metadata`, keyed by the extension URI. The message's `extensions` array lists the same URI. Each payload carries a `type` field that names its shape. kagent stores an attached payload with the task. A later read returns it whether or not that read requests the extension. +When the agent pauses, the task moves to `INPUT_REQUIRED`. The payload arrives in the status message's `metadata`, keyed by the extension URI. The message's `extensions` array lists the same URI. Each payload includes a `type` field that identifies its shape. kagent stores an attached payload with the task. A later read returns it whether or not that read requests the extension. Each type travels in one direction. An agent raises a request, and a client answers with the matching response. @@ -173,7 +173,7 @@ Each type travels in one direction. An agent raises a request, and a client answ ### Approve or reject a tool -A `tool_approval_request` lists the pending calls, each with an `id`, the tool `name`, and the `args` the agent chose. Present those to a person, then send one `tool_approval_response` that decides every call in the request. +A `tool_approval_request` lists the pending calls, each with an `id`, the tool `name`, and the `args` that the agent chose. Present the pending calls to a person, then send one `tool_approval_response` that decides every call in the request. ```json { @@ -185,11 +185,11 @@ A `tool_approval_request` lists the pending calls, each with an `id`, the tool ` } ``` -A rejection reason is optional and worth sending because the agent receives it and can adapt rather than fail. +A rejection reason is optional. The agent receives the reason and can adapt to it rather than fail. ### Answer a question -An `ask_user_request` carries an `id` and a list of `questions`. The response echoes the same `id` and answers the questions in order. +An `ask_user_request` contains an `id` and a list of `questions`. The response echoes the same `id` and answers the questions in order. ```json { @@ -206,28 +206,28 @@ An `ask_user_request` carries an `id` and a list of `questions`. The response ec ## Resume a paused task -A paused task waits for the client to send a message that carries the response payload, on the same task and context. kagent rejects a resume attempt on a task that is not waiting. +A paused task waits for the client to send a message that contains the response payload, on the same task and context. kagent rejects a resume attempt on a task that is not waiting. While the task waits, kagent suspends the {{< gloss "Actor" >}}Actor{{< /gloss >}} to a durable {{< gloss "Snapshot" >}}snapshot{{< /gloss >}} in the same way as at the end of a turn. The snapshot keeps the durable volume and discards the running process. Each harness records the pending request in that volume and continues the turn when the answer arrives. -The `claude` harness resumes its deferred tool call. The `codex` harness runs the approved MCP call itself and continues with the result. The {{< gloss "Worker" >}}Worker{{< /gloss >}} is released in the meantime, so a conversation that sits at `INPUT_REQUIRED` does not consume pool capacity. The wait also survives the loss of the node. For more information, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}). +The `claude` harness resumes its deferred tool call. The `codex` harness runs the approved MCP call itself and continues with the result. The {{< gloss "Worker" >}}Worker{{< /gloss >}} is released in the meantime, so a task that waits at `INPUT_REQUIRED` does not consume pool capacity. The wait also survives the loss of the node. For more information, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}). -On the `codex` and `claude` harnesses, only one pending request survives the suspension. The `codex` harness cancels any other request along with the interrupted turn. The `claude` harness denies a protected call that arrives in a parallel batch, but a single remaining call can still pause for approval. If an approved call fails after it starts, the harness cannot resume the task. To continue the conversation, start a new Session. +On the `codex` and `claude` harnesses, only one pending request survives the suspension. The `codex` harness cancels any other request along with the interrupted turn. The `claude` harness denies a protected call that arrives in a parallel batch, but a single remaining call can still pause for approval. If an approved call fails after it starts, the harness cannot resume the task. To continue the work, start a new Session. -Because the {{< gloss "Transcript" >}}transcript{{< /gloss >}} only grows, the question and the answer both stay in the task history, so a later reader can see what was asked and what a person decided. +The {{< gloss "Transcript" >}}transcript{{< /gloss >}} only grows, so the question and the answer both stay in the task history. A later reader can see what was asked and what a person decided. ## Task states -An A2A task moves through several states over its life. The following states mean that the task has stopped and is waiting, rather than working. +An A2A task moves through several states over its life. The following states mean that the task has stopped and is waiting. | State | Meaning | | ----- | ------- | -| `INPUT_REQUIRED` | The agent is waiting for a person. This is the state that a tool approval or a question produces. | +| `INPUT_REQUIRED` | The agent is waiting for a person. A tool approval or a question produces this state. | | `AUTH_REQUIRED` | The agent is waiting for credentials. kagent's own harnesses never set this state. The gateway accepts a resume from it, so a `byo` image that produces it works. | ## Agents bound as tools -An agent that a parent binds as a tool can raise a pause of its own. The request then includes a `nested` block naming the subagent, along with its task and context. A client can therefore tell the person which agent is actually asking rather than attributing the question to the parent. +An agent that a parent binds as a tool can raise a pause of its own. The request then includes a `nested` block that identifies the subagent, along with its task and context. A client can therefore tell the person which agent is asking rather than attributing the question to the parent. ## Client support @@ -237,7 +237,7 @@ The AgentTemplate decides that a turn pauses, and the client that raised the tur | ------ | ---- | | Your own A2A client | Full. Request the extension URI and handle every payload type. | | An {{< gloss "MCP" >}}MCP{{< /gloss >}} client that supports tasks | Supported. The `invoke_session` tool returns a task, and input requests surface as MCP elicitations. | -| The kagent CLI | Not supported, and a turn that pauses is stranded. `kagent agent invoke` does not request the extension, so an agent that needs a person parks the task at `INPUT_REQUIRED` with nothing to answer it by. The CLI reports `Input required to continue this Session.` and stops there. Send the turn again from a client that requests the extension. | +| The kagent CLI | Not supported. `kagent agent invoke` does not request the extension, so a turn that pauses stays at `INPUT_REQUIRED` and the CLI cannot answer it. The CLI reports `Input required to continue this Session.` and stops there. Send the turn again from a client that requests the extension. | ## Next steps diff --git a/docs-site/content/kagent/1.x/agents/structured-output.md b/docs-site/content/kagent/1.x/agents/structured-output.md index 69526862..6eddddaf 100644 --- a/docs-site/content/kagent/1.x/agents/structured-output.md +++ b/docs-site/content/kagent/1.x/agents/structured-output.md @@ -12,7 +12,7 @@ The schema goes in one of two AgentTemplate fields, `spec.outputSchema` or `spec | Field | Description | | ----- | ----------- | | `outputSchema` | The schema, written inline in the AgentTemplate. | -| `outputSchemaFrom.name` | The ConfigMap holding the schema as JSON, in the AgentTemplate's namespace. | +| `outputSchemaFrom.name` | The ConfigMap that stores the schema as JSON, in the AgentTemplate's namespace. | | `outputSchemaFrom.key` | The key within that ConfigMap. | | `outputSchemaStrict` | Whether the model provider constrains generation to the schema. Defaults to `false`. For the providers that honor it, see [Strict mode](#strict-mode). | @@ -114,7 +114,7 @@ Set `spec.outputSchema` to keep the schema in the AgentTemplate, so that the sch Use `outputSchemaFrom` to keep the schema outside the AgentTemplate, so that several AgentTemplates can share one schema. The value in the ConfigMap must be JSON, even though the ConfigMap itself is written in YAML. -1. Create a ConfigMap holding the schema. +1. Create a ConfigMap that stores the schema. ```yaml kubectl apply -f - <}}A2A{{< /gloss >}} (Agent-to-Agent) `DataPart` with the media type `application/json`. The part's metadata carries the SHA-256 digest of the schema that the answer was validated against, under the key `kagent.dev/a2a/output-schema-sha256`. kagent computes the digest after it normalizes the schema, so two schemas that differ only in formatting or key order have the same digest. The Agent's card lists `application/json` as its default output mode, instead of `text`. +A successful answer is published as one {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) `DataPart` with the media type `application/json`. The part's metadata includes the SHA-256 digest of the schema that the answer was validated against, under the key `kagent.dev/a2a/output-schema-sha256`. kagent computes the digest after it normalizes the schema, so two schemas that differ only in formatting or key order have the same digest. The Agent's card lists `application/json` as its default output mode, instead of `text`. To see the part and its metadata, print the task as JSON. @@ -270,31 +270,33 @@ Example output: ] ``` -The runtime holds back the agent's partial output while the agent writes its answer, and publishes only the complete answer. Progress updates, tool calls, approval requests, and input-required messages keep their usual form, and the agent can call tools before it answers. A streaming client that needs only the answer reads the last artifact with content before the task reaches `TASK_STATE_COMPLETED`. That artifact's part carries the `kagent.dev/a2a/output-schema-sha256` metadata key. +The runtime holds back the agent's partial output while the agent writes its answer, and publishes only the complete answer. Progress updates, tool calls, approval requests, and input-required messages keep their usual form, and the agent can call tools before it answers. A streaming client that needs only the answer reads the last artifact with content before the task reaches `TASK_STATE_COMPLETED`. That artifact's part includes the `kagent.dev/a2a/output-schema-sha256` metadata key. ## Agents as tools The schema applies only to the root agent, which is the agent of the AgentTemplate that the Agent names. An AgentTemplate [bound to it as a subagent]({{< link path="skills-and-mcp/about-tools#subagents-as-tools" >}}) does not inherit the schema. If the bound AgentTemplate has a schema of its own, that schema applies only to Agents that name it directly. -With a `Shared` binding, the root agent can hand the conversation to the bound agent, which then answers in its place, in that turn and in the turns that follow. Those tasks fail with `output_validation_failed: root agent produced no result artifact`, because the answers do not come from the root agent. The bound agent's answers still reach the caller as text. Give a schema only to an AgentTemplate whose own agent writes the final answer. +If the subagent uses a `Shared` binding, the root agent can hand the conversation to the bound agent, which then answers in its place, in that turn and in the turns that follow. Those tasks fail with `output_validation_failed: root agent produced no result artifact`, because the answers do not come from the root agent. The bound agent's answers still reach the caller as text. Give a schema only to an AgentTemplate whose own agent writes the final answer. ## Answers that fail validation -Structured output has no fallback to text. If the model refuses, stops early, or returns an answer that is not JSON or does not match the schema, the task fails. kagent does not publish the invalid answer or include it in the failure message, because it can contain sensitive data. With a Gemini model on the `Gemini` provider, however, the agent is told to give its answer as the arguments of a `set_model_response` tool call. kagent publishes that call like any other tool call, before it validates the answer, so an answer given that way reaches the caller even when it fails validation. +Structured output does not have a fallback to text. If the model refuses, stops early, or returns an answer that is not JSON or does not match the schema, the task fails. kagent does not publish the invalid answer or include it in the failure message, because it can contain sensitive data. When the agent uses a Gemini model on the `Gemini` provider, however, the agent is told to give its answer as the arguments of a `set_model_response` tool call. kagent publishes that call like any other tool call, before it validates the answer, so an answer given that way reaches the caller even when it fails validation. -For example, an answer that does not match the schema fails the `kagent invoke` command with output like the following. The `output_validation_failed:` message names the cause. +For example, an answer that does not match the schema fails the `kagent agent invoke` command with output like the following. The `output_validation_failed:` message states the cause. ```console processor failed: output_validation_failed: root agent output does not conform to its schema Error: Session task 01a0ec27-157f-7034-bb89-09855f485b2a ended in TASK_STATE_FAILED ``` +Each message follows the `processor failed:` prefix. + | Message | Cause | | ------- | ----- | | `output_validation_failed: root agent output does not conform to its schema` | The answer is JSON, but it does not match the schema. | | `output_validation_failed: root agent output is not valid JSON` | The answer is not JSON. | | `output_validation_failed: root agent did not complete structured output` | The model stopped for a reason other than finishing normally, such as reaching its token limit. | -| `output_validation_failed: root agent produced no structured value` | The model returned no answer text. | +| `output_validation_failed: root agent produced no structured value` | The model did not return any answer text. | | `output_validation_failed: root agent produced no result artifact` | The task ended without a structured answer from the root agent, for example because the root agent handed the conversation to an [agent bound as a tool](#agents-as-tools). | ## Troubleshooting @@ -329,15 +331,17 @@ For example, if the `shared-schemas` ConfigMap no longer has the `arithmetic-ans ] ``` +The `message` field of the failing condition identifies the cause. + | Message | Condition and reason | Cause | | ------- | -------------------- | ----- | | `resolve output schema ConfigMap "x": not found` | `ResolvedRefs`, `ReferenceResolutionFailed` | The ConfigMap does not exist in the AgentTemplate's namespace. | -| `resolve output schema ConfigMap "x": key "y" not found` | `ResolvedRefs`, `ReferenceResolutionFailed` | The ConfigMap has no such key under `data`. | -| `invalid output schema: schema is empty` | `Compatible`, `UnsupportedConfiguration` | The key holds an empty value. | +| `resolve output schema ConfigMap "x": key "y" not found` | `ResolvedRefs`, `ReferenceResolutionFailed` | The ConfigMap does not have that key under `data`. | +| `invalid output schema: schema is empty` | `Compatible`, `UnsupportedConfiguration` | The key's value is empty. | | `invalid output schema: schema exceeds 65536 bytes` | `Compatible`, `UnsupportedConfiguration` | The schema is larger than 64 KiB. | | `invalid output schema: decode JSON: ...` | `Compatible`, `UnsupportedConfiguration` | The value is not valid JSON. | | `invalid output schema: decode trailing JSON: ...` | `Compatible`, `UnsupportedConfiguration` | Text that is not JSON follows the JSON value. | -| `invalid output schema: schema must contain one JSON value` | `Compatible`, `UnsupportedConfiguration` | The value holds more than one JSON value, such as two objects in a row. | +| `invalid output schema: schema must contain one JSON value` | `Compatible`, `UnsupportedConfiguration` | The value contains more than one JSON value, such as two objects in a row. | | `invalid output schema: schema does not match the portable output profile: ...` | `Compatible`, `UnsupportedConfiguration` | The schema uses a keyword or form outside the [portable output profile](#supported-schemas), or its root is not an object. | | `invalid output schema: resolve schema: ...` | `Compatible`, `UnsupportedConfiguration` | kagent cannot resolve the schema, for example because a `$ref` names a `$defs` entry that does not exist. | | `invalid output schema: pattern ...` | `Compatible`, `UnsupportedConfiguration` | A `pattern` is not valid RE2, uses a word boundary, or the schema's patterns compile to more than 100,000 regexp instructions. | diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index 0bfe4439..62938223 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -7,7 +7,7 @@ author: kagent.dev Every {{< gloss "Agent" >}}Agent{{< /gloss >}} is reachable over the {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) protocol through the kagent controller. This example calls that service directly to read an Agent's card, send it a message, stream a reply, and answer the agent when it pauses for a person. -In {{< reuse "kagent-docs/snippets/name-product.md" >}}, agent traffic runs over A2A. The CLI and the [MCP server]({{< link path="examples/agents-via-mcp" >}}) reach an Agent through the same service. +In {{< reuse "kagent-docs/snippets/name-product.md" >}}, agent traffic runs over A2A. The CLI and the [Model Context Protocol (MCP) server]({{< link path="examples/agents-via-mcp" >}}) reach an Agent through the same service. This example uses [grpcurl](https://github.com/fullstorydev/grpcurl) to show the requests and replies directly. Real callers use an A2A client library rather than assembling requests by hand. @@ -15,18 +15,18 @@ This example uses [grpcurl](https://github.com/fullstorydev/grpcurl) to show the The controller serves `lf.a2a.v1.A2AService` on port `8083`, alongside its REST API and its MCP endpoint. The kagent CLI reaches the same port and the same service. -A gRPC caller names the Agent in the standard A2A `tenant` field, as `/`, and the controller routes the call to that Agent. The same controller serves A2A over HTTP JSON-RPC, where the URL path selects the Agent instead: +A gRPC caller identifies the Agent in the standard A2A `tenant` field, as `/`, and the controller routes the call to that Agent. The same controller serves A2A over HTTP JSON-RPC, where the URL path selects the Agent instead: | Transport | How a caller selects the Agent | | --------- | ----------------------------- | | gRPC | The `tenant` field on each request, as `/` | | HTTP JSON-RPC | `POST /agents/{namespace}/{name}`, with the card at `GET /agents/{namespace}/{name}/.well-known/agent-card.json` | -An HTTP request that also sets the `tenant` field must set a value that matches its URL. There is no deployment-wide agent card, because the controller serves many Agents. +An HTTP request that also sets the `tenant` field must set a value that matches its URL. The controller does not serve a deployment-wide agent card, because it serves many Agents. Both forms replace the `/api/a2a///` URL paths that kagent 0.x served over HTTP. -A caller addresses the Agent rather than one conversation. The message's `contextId` selects the conversation. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. +A caller addresses the Agent rather than one conversation. The message's `contextId` selects the Session. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. If the Agent, the Session, or the task does not exist, an A2A call fails with `TaskNotFound` (JSON-RPC code `-32001`, or gRPC `NOT_FOUND`). Over HTTP, a request for the card of an Agent that does not exist returns `404`. To learn when kagent deletes a Session, see [Expire idle Sessions]({{< link path="operations/operational-considerations#expire-idle-sessions" >}}). @@ -102,19 +102,19 @@ An A2A client typically starts by reading the agent card, which tells it what th ``` 2. Read the card for what a caller acts on. - * Both `name` and `description` come from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. `name` replaces hyphens with underscores, and `description` is `spec.description` verbatim, so a caller sees the description you wrote. + * Both `name` and `description` come from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. In `name`, kagent replaces hyphens with underscores. The `description` value is `spec.description` verbatim, so a caller sees the description that you wrote. * Each `supportedInterfaces` URL is the controller's in-cluster address rather than the Actor's, because a caller reaches the agent through the controller. The card advertises JSON-RPC first, then gRPC. The JSON-RPC URL identifies the Agent in its path. The gRPC entry sets the `tenant` value instead. * The `capabilities.pushNotifications` value advertises task callbacks. Each callback belongs to one task, not to the whole Session. - * The `capabilities.extensions` list names the optional extensions that the agent supports. A client opts into the human-in-the-loop extension per call to answer pauses. With the usage extension, the agent reports the tokens that a task uses. For the payload, see [Read token usage](#read-token-usage). + * The `capabilities.extensions` list identifies the optional extensions that the agent supports. A client opts into the human-in-the-loop extension per call to answer pauses. Through the usage extension, the agent reports the tokens that a task uses. For the payload, see [Read token usage](#read-token-usage). > [!NOTE] -> The card carries no `skills`. kagent 0.x let you declare agent card skills in an `a2aConfig` block. However, v1alpha3 has no such field, so kagent generates the card from the AgentTemplate's name and description alone. An AgentTemplate's `spec.skills` field is a different feature: those are [Agent Skills]({{< link path="skills-and-mcp/skills" >}}) that the agent can use, not advertisements to a caller. +> The card does not include `skills`. kagent 0.x let you declare agent card skills in an `a2aConfig` block. However, v1alpha3 does not have that field, so kagent generates the card only from the AgentTemplate's name and description. An AgentTemplate's `spec.skills` field is a different feature: those are [Agent Skills]({{< link path="skills-and-mcp/skills" >}}) that the agent can use, not advertisements to a caller. ## Send a message `SendMessage` blocks until the agent finishes the turn, or pauses to ask a person, and then returns the whole task. A message needs its own ID, a role, and at least one part. -A message part can also carry a file. Put the file's bytes in the part's `raw` field, which JSON encodes as base64, and set `filename` and `mediaType` on the same part. To send a link to a file, set the part's `url` field in place of `raw`. When the ModelConfig sets `apiFormat: responses`, or the provider is Gemini, the model fetches the file from the link. Every other model receives the link as text. +A message part can also contain a file. Put the file's bytes in the part's `raw` field, which JSON encodes as base64, and set `filename` and `mediaType` on the same part. To send a link to a file, set the part's `url` field in place of `raw`. When the ModelConfig sets `apiFormat: responses`, or the provider is Gemini, the model fetches the file from the link. Every other model receives the link as text. > [!NOTE] > Unlike the UI, which checks each file as you stage it, this path accepts any media type. When the ModelConfig sets `apiFormat: responses`, a PDF, `.docx`, `.xlsx`, or `.pptx` file reaches the model as a file. Any other file whose media type is not text-like reaches the model as the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of contents. The controller's gRPC server rejects any message over 16 MiB, framing included. For how files reach the model, and the file types and sizes that the UI accepts, see [Attach files to a message]({{< link path="observability/launch-ui#attach-files-to-a-message" >}}). @@ -148,11 +148,11 @@ A message part can also carry a file. Put the file's bytes in the part's `raw` f ``` The task returns two identifiers, `id` and `contextId`, and a caller uses them differently. * The `id` identifies one turn, and every message returns a new one. - * The `contextId` identifies the conversation, and is the Session's own ID. A second message that repeats this `contextId` therefore continues the conversation rather than starting a new one. + * The `contextId` is the Session ID. A second message that repeats this `contextId` continues the same Session. - Task metadata uses the `kagent.dev/a2a/` namespace. An artifact carries `kagent.dev/a2a/timeline-position`, an RFC 3339 timestamp that orders task history, and the task itself carries `kagent.dev/a2a/task-created-at`. A runtime that reports per-event model usage publishes it as `kagent.dev/a2a/usage` on a task event rather than on the artifact. For task-lifetime usage, see [Read token usage](#read-token-usage). + Task metadata uses the `kagent.dev/a2a/` namespace. An artifact includes `kagent.dev/a2a/timeline-position`, an RFC 3339 timestamp that orders task history. The task itself includes `kagent.dev/a2a/task-created-at`. A runtime that reports per-event model usage publishes it as `kagent.dev/a2a/usage` on a task event rather than on the artifact. For task-lifetime usage, see [Read token usage](#read-token-usage). -2. Save the task's `id` and the conversation's `contextId`, so that later steps can read the task and continue the same Session. +2. Save the task's `id` and the Session's `contextId`, so that later steps can read the task and continue the same Session. ```bash export TASK_ID= export SESSION_ID= @@ -169,7 +169,7 @@ A message part can also carry a file. Put the file's bytes in the part's `raw` f localhost:8083 lf.a2a.v1.A2AService/SendStreamingMessage ``` -2. Read the event sequence. The stream opens with the task at `TASK_STATE_SUBMITTED`, moves to `TASK_STATE_WORKING`, and then emits an artifact update for each chunk of the reply. Every chunk shares one `artifactId`, so a client appends them into a single artifact rather than treating each as a separate answer. Example output, abbreviated to the text of each event: +2. Read the event sequence. The stream opens with the task at `TASK_STATE_SUBMITTED`, moves to `TASK_STATE_WORKING`, and then emits an artifact update for each chunk of the reply. Every chunk shares one `artifactId`, so a client appends the chunks into a single artifact. Example output, abbreviated to the text of each event: ```console "state": "TASK_STATE_SUBMITTED" "state": "TASK_STATE_WORKING" @@ -181,7 +181,7 @@ A message part can also carry a file. Put the file's bytes in the part's `raw` f ## Read a task later -A task outlives the call that created it, so a caller that lost its connection can read the result rather than asking the agent again. +A task outlives the call that created it, so a caller that lost its connection can still read the result. 1. Read the task by ID. ```bash @@ -209,7 +209,7 @@ A task outlives the call that created it, so a caller that lost its connection c } ``` -A task that is still running reports `TASK_STATE_WORKING` and has no artifacts yet. To stop a task that is still running, call `CancelTask` with the same `id`. +A task that is still running reports `TASK_STATE_WORKING` and does not have artifacts yet. To stop a task that is still running, call `CancelTask` with the same `id`. ## Read token usage {#read-token-usage} @@ -261,7 +261,7 @@ A task callback lets a webhook learn when a task needs input or reaches a final kagent keeps one pending delivery for each callback, and a newer state replaces a delivery that has not yet succeeded. When a task resumes, kagent discards the pending delivery for the state that the task left, so a receiver does not see every state. -The kagent CLI wraps the three push notification configuration methods, so the steps that follow use the CLI rather than building each request by hand. The CLI defaults to port 8083, which the prerequisites already forward. +The kagent CLI wraps the push notification configuration methods, so the steps that follow use the CLI. The CLI defaults to port 8083, which the prerequisites already forward. 1. To register a callback before a new task is dispatched, add `--push-url` to the kagent CLI invocation. The command returns immediately with the task ID and callback ID. ```bash @@ -300,7 +300,7 @@ The kagent CLI wraps the three push notification configuration methods, so the s A callback URL is a public endpoint that receives task state from kagent. Before you register a callback in production, decide how the receiver establishes that a delivery came from kagent. > [!IMPORTANT] -> kagent signs deliveries only when the installation names a signing key, and `controller.push.signing.secretName` is empty by default. Until you set it, kagent posts each callback without an `Authorization` header, and the controller serves `404` at `/.well-known/jwks.json`. A receiver cannot distinguish an unsigned kagent delivery from any other caller, so treat the default configuration as suitable for a trusted network only. +> kagent signs deliveries only when the installation sets a signing key, and `controller.push.signing.secretName` is empty by default. Until you set it, kagent posts each callback without an `Authorization` header, and the controller returns `404` at `/.well-known/jwks.json`. A receiver cannot distinguish an unsigned kagent delivery from any other caller, so use the default configuration only on a trusted network. kagent does not generate its own signing key, so you are responsible for creating the key, keeping it, and rotating it. The following steps turn signing on. @@ -312,13 +312,13 @@ kagent does not generate its own signing key, so you are responsible for creatin > [!NOTE] > The `openssl` binary that ships with macOS is LibreSSL, which rejects this command with `Algorithm ed25519 not found`. Install OpenSSL 3, or generate the key on another machine. -2. Store the key in a Secret. kagent reads the `private-key.pem` entry, so the entry name is not optional. +2. Store the key in a Secret. kagent reads only the `private-key.pem` entry, so the entry must have that name. ```bash kubectl create secret generic kagent-push-signing -n kagent \ --from-file=private-key.pem=private-key.pem ``` -3. Name the Secret in `controller.push.signing.secretName`, and set `config.push.issuer` to a URL that reaches the controller. Use the Helm release name and version that you installed kagent with. +3. Set `controller.push.signing.secretName` to the Secret name, and set `config.push.issuer` to a URL that reaches the controller. Use the Helm release name and version that you installed kagent with. ```bash helm upgrade kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent \ --namespace kagent --reuse-values \ @@ -326,21 +326,21 @@ kagent does not generate its own signing key, so you are responsible for creatin --set config.push.issuer=https://kagent.example ``` - The issuer that a receiver trusts must match the host that it fetches the JWKS from. When you omit `config.push.issuer`, Helm derives it from `ui.externalUrl` and then from the in-cluster A2A gateway URL. Either fallback can name a host that does not serve the JWKS. + The issuer that a receiver trusts must match the host that it fetches the JSON Web Key Set (JWKS) from. When you omit `config.push.issuer`, Helm derives it from `ui.externalUrl` and then from the in-cluster A2A gateway URL. Either fallback can point to a host that does not serve the JWKS. 4. Confirm that the controller publishes a verification key. Before you set the Secret, this path returns `404`. ```bash curl -s http://localhost:8083/.well-known/jwks.json ``` - Example output, which carries the public key alone: + Example output, which includes only the public key: ```json {"keys":[{"kty":"OKP","crv":"Ed25519","alg":"EdDSA","use":"sig","kid":"B1BP8zPNOTEGYyBUVgUAeTyA5hPlnrNc0OB416NY-CM","x":"xIc8dv_I-NrYSvWKg4aKm_X1CjHmiUBSMf9cDXEKesM"}]} ``` -Receivers verify a signed delivery against the JSON Web Key Set (JWKS). The controller serves the path on the same port as its gRPC, A2A, and MCP endpoints, so a receiver outside of the cluster needs a stable HTTPS URL that routes to the controller. The UI does not proxy the path, so publishing only the UI leaves a receiver with no key to verify against. +Receivers verify a signed delivery against the JWKS. The controller serves the path on the same port as its gRPC, A2A, and MCP endpoints, so a receiver outside of the cluster needs a stable HTTPS URL that routes to the controller. The UI does not proxy the path, so publishing only the UI leaves a receiver without a key to verify against. -Receivers must not send a token or credential when they register a callback. kagent signs each delivery attempt with a fresh five-minute JWT whose audience is the exact callback URL, including the query string. Before you acknowledge the request with a 2xx response, verify the JWT signature, issuer, audience, expiry, and `taskId` claim. The `taskId` claim repeats the `statusUpdate.taskId` value in the body, so a receiver that compares them rejects a token replayed against a different task. +Receivers must not send a token or credential when they register a callback. kagent signs each delivery attempt with a fresh five-minute JSON Web Token (JWT) whose audience is the exact callback URL, including the query string. Before you acknowledge the request with a 2xx response, verify the JWT signature, issuer, audience, expiry, and `taskId` claim. The `taskId` claim repeats the `statusUpdate.taskId` value in the body, so a receiver that compares them rejects a token replayed against a different task. By default, callback URLs must use HTTPS. Callback URLs must not resolve to private, loopback, link-local, unspecified, or multicast addresses. To reach a test receiver inside the cluster, set `config.push.allowHttp=true` or `config.push.allowPrivateNetworks=true`. kagent does not follow redirects, and failed attempts are retried from the database-backed queue. @@ -348,7 +348,7 @@ By default, callback URLs must use HTTPS. Callback URLs must not resolve to priv An agent can stop mid-task to ask a question or to request approval for a tool call. The task then reports `TASK_STATE_INPUT_REQUIRED` and waits until a caller answers it. By default, every agent on the `kagent` harness can raise the question kind, because the runtime gives each one a built-in `ask_user` tool. A system prompt that tells an agent to ask before it answers is therefore enough to see a pause. -Answering a pause needs the human-in-the-loop extension, which a caller requests per call. The extension is a versioned URI, and a request names it in two places: the `A2A-Extensions` header, and the message's own `extensions` list. +Answering a pause needs the human-in-the-loop extension, which a caller requests per call. The extension is a versioned URI. A request must include the URI in both the `A2A-Extensions` header and the message's own `extensions` list. 1. Send a message that requests the extension. ```bash @@ -358,7 +358,7 @@ Answering a pause needs the human-in-the-loop extension, which a caller requests localhost:8083 lf.a2a.v1.A2AService/SendMessage ``` -2. Read the pause. The task stops at `TASK_STATE_INPUT_REQUIRED`, and the status message's `metadata` holds the request, keyed by the extension URI. Example output: +2. Read the pause. The task stops at `TASK_STATE_INPUT_REQUIRED`, and the status message's `metadata` contains the request, keyed by the extension URI. Example output: ```json { "task": { @@ -387,14 +387,14 @@ Answering a pause needs the human-in-the-loop extension, which a caller requests } ``` -3. Save the task ID, the conversation's `contextId`, and the request `id` to environment variables. The request `id` is within the extension metadata and starts with `adk-`. +3. Save the task ID, the Session's `contextId`, and the request `id` to environment variables. The request `id` is within the extension metadata and starts with `adk-`. ```bash export PAUSED_TASK_ID= export PAUSED_SESSION_ID= export REQUEST_ID= ``` -4. Answer on the same task. The response goes in the same metadata key, names the request `id` it answers, and sets `taskId` so that it answers the paused task rather than starting a new turn. +4. Answer on the same task. The response goes in the same metadata key, references the request `id` that it answers, and sets `taskId` so that it answers the paused task rather than starting a new turn. ```bash grpcurl -plaintext \ -H "A2A-Extensions: https://kagent.dev/extensions/hitl/v1" \ @@ -409,15 +409,15 @@ Answering a pause needs the human-in-the-loop extension, which a caller requests ``` > [!IMPORTANT] -> A caller that does not request the extension is still interrupted. The task stops at `TASK_STATE_INPUT_REQUIRED` exactly as before, and the question arrives as ordinary text on the status message, but the metadata does not include a request `id`. Without that `id` there is nothing to answer, so the task waits until something cancels it. Request the extension on any call to an agent that can pause. +> A caller that does not request the extension is still interrupted. The task still stops at `TASK_STATE_INPUT_REQUIRED`, and the question arrives as ordinary text on the status message. However, the metadata does not include a request `id`. Without that `id`, the caller cannot answer the pause, so the task waits until something cancels it. Request the extension on any call to an agent that can pause. -A tool approval works the same way with a different payload, `tool_approval_request` answered by `tool_approval_response`. For the pause kinds, the approval model, and what a nested agent's pause looks like, see [Human in the loop]({{< link path="agents/human-in-the-loop" >}}). +A tool approval works in the same way, with a different payload, `tool_approval_request` answered by `tool_approval_response`. For the pause kinds, the approval model, and what a nested agent's pause looks like, see [Human in the loop]({{< link path="agents/human-in-the-loop" >}}). ## Clean up * This example does not create Kubernetes resources, so you have nothing to delete. * You can stop the 8083 port-forward for the kagent-controller service with `Ctrl+C`. -* The tasks that your messages created stay on the Session as part of its conversation, and deleting the Session removes them. +* The tasks that your messages created stay on the Session, and deleting the Session removes them. ## Next steps diff --git a/docs-site/content/kagent/1.x/examples/skills.md b/docs-site/content/kagent/1.x/examples/skills.md index f40e6cd1..1e7d4fa5 100644 --- a/docs-site/content/kagent/1.x/examples/skills.md +++ b/docs-site/content/kagent/1.x/examples/skills.md @@ -5,17 +5,17 @@ weight: 40 author: kagent.dev --- -A {{< gloss "Skill" >}}skill{{< /gloss >}} packages know-how that an agent picks up at run time: a `SKILL.md` file of instructions, together with the scripts and reference files those instructions depend on. This example builds a skill that turns raw commit subjects into release notes, publishes it as an OCI image, and attaches it to an {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. +A {{< gloss "Skill" >}}skill{{< /gloss >}} packages know-how that an agent picks up at run time. A skill consists of a `SKILL.md` file of instructions, together with the scripts and reference files that those instructions depend on. This example builds a skill that turns raw commit subjects into release notes, publishes it as an Open Container Initiative (OCI) image, and attaches it to an {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. For the fields that attach a skill and the rules that govern their names, see [Skills]({{< link path="skills-and-mcp/skills" >}}). For the format of a plugin, see [Plugins]({{< link path="skills-and-mcp/plugins" >}}). ## About skills at run time -kagent does not fetch a skill when you apply an AgentTemplate. The compiled revision records the source of each skill, and the skill arrives when the agent starts. For an `oci` source, Agent Substrate pulls the image from the Worker and mounts it as read-only into the {{< gloss "Actor" >}}Actor{{< /gloss >}} before the runtime starts. For a `git` or `bucket` source, the Actor fetches the files itself. The runtime then copies each enabled skill into the directory that its harness reads, which is `/skills/` on a `kagent` Harness. +kagent does not fetch a skill when you apply an AgentTemplate. The compiled revision records the source of each skill, and the skill arrives when the agent starts. For an `oci` source, Agent Substrate pulls the image from the Worker and mounts it as read-only into the {{< gloss "Actor" >}}Actor{{< /gloss >}} before the runtime starts. For a `git` or `bucket` source, the Actor fetches the files itself. The runtime then copies each enabled skill into the directory that its Harness reads, which is `/skills/` on a `kagent` Harness. Because kagent fetches a skill this late, review the following considerations. -* **A wrong source still compiles.** kagent validates skill names before it accepts an AgentTemplate, but it never checks that the artifact exists or that it holds a `SKILL.md` file. A bad digest produces a revision that reports `Ready`, and the agent then fails to start. +* **A wrong source still compiles.** kagent validates skill names before it accepts an AgentTemplate, but it never checks that the artifact exists or that it contains a `SKILL.md` file. A bad digest produces a revision that reports `Ready`, and the agent then fails to start. * **A private registry needs Worker credentials.** Agent Substrate pulls an `oci` source with the registry credentials that the Worker uses for workload images. Kubernetes `imagePullSecrets` on a pod do not configure that pull. * **Each Actor mounts up to 29 distinct skill images.** Images shared by several skills or plugins count once. A revision that needs more fails when kagent builds its ActorTemplate. * **Scripts run in the runtime image.** A skill's scripts get whatever the Harness image provides. The kagent runtime image is Alpine Linux with `bash`, `git`, and the standard Alpine utilities, and it does **not** include Python. @@ -48,15 +48,15 @@ config: disableBash: true ``` -| Setting | Description | -| ------- | ----------- | +| Value | Description | +| ----- | ----------- | | `disableFileSearch` | Set to `true` to remove `list_files` and `grep_file` from the root agent and from every subagent. Defaults to `false`. This value replaces `skillTools.fileSearchTools` from earlier releases, with the opposite meaning. | | `disableBash` | Set to `true` to remove the `bash` tool from the root agent and from every subagent. The agent keeps the file tools, and still loads skills. Defaults to `false`. | -| `bashVenvPath` | The path to a Python virtual environment in the Harness image. The `bash` tool adds the environment's `bin` directory to the start of `PATH`, and sets `VIRTUAL_ENV`. The kagent runtime image does not include Python. To use this setting, build a Harness image that includes the environment. Defaults to empty. | +| `bashVenvPath` | The path to a Python virtual environment in the Harness image. The `bash` tool adds the environment's `bin` directory to the start of `PATH`, and sets `VIRTUAL_ENV`. The kagent runtime image does not include Python. To use this value, build a Harness image that includes the environment. Defaults to empty. | The `builtinTools` Helm values also include `disableAskUser`, which removes the question tool. For more information, see [Disable questions]({{< link path="agents/human-in-the-loop#disable-questions" >}}). -Attaching a skill also changes what the agent is told. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call `load_skill` before acting on one, so a skill reaches the model even before any tool is called. +Attaching a skill also changes the model request. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call `load_skill` before the agent acts on a skill. The model therefore sees each skill before the agent calls any tool. > [!IMPORTANT] > The `bash` tool gives the agent shell access inside its own Actor sandbox, and the sandbox is the boundary that contains the agent. The file tools are not limited to the workspace. They read and write anywhere in the sandbox, including `/skills`, so one skill's instructions can change another skill's files. Review a skill before you attach it, and treat the [egress]({{< link path="about/substrate-runtime/sandboxing" >}}) that you grant the Actor as the reach that the skill has. @@ -87,7 +87,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th cd release-notes ``` -2. Write `SKILL.md`. The YAML front matter must carry a `name` and a `description`, and the body holds the instructions the agent follows. +2. Write `SKILL.md`. The YAML front matter must include a `name` and a `description`. The body contains the instructions that the agent follows. ```bash cat > SKILL.md <<'EOF' --- @@ -112,7 +112,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th EOF ``` - The model reads the `description` and decides whether to load the skill. The model sees the name and description of every attached skill, and chooses among them in the same way that it chooses any other tool. State plainly when the skill applies. The instructions in the body are only read after the agent calls `load_skill`. + The model reads the `description` to decide whether to load the skill, in the same way that it chooses any other tool. In the `description`, state plainly when the skill applies. The agent reads the instructions in the body only after it calls `load_skill`. 3. Add the script that the instructions call. The script runs in the agent's runtime image, so it uses `bash` rather than Python. ```bash @@ -142,7 +142,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th chmod +x scripts/group.sh ``` -4. Confirm that the script works before you publish it. The `bash` tool returns a failed command's error to the model rather than to you, so a broken script produces an unreliable answer rather than a failed resource. +4. Before you publish the script, confirm that it works. The `bash` tool returns a failed command's error to the model, not to you. A broken script therefore produces an unreliable answer, not a failed resource. ```bash printf 'feat: add checkpoint API\nfix: correct revision digest\ndocs: update install guide\n' > /tmp/commits.txt bash scripts/group.sh /tmp/commits.txt @@ -162,7 +162,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th ## Publish the skill as an OCI image -Agent Substrate mounts an `oci` source as a container image. The image must hold the skill directory and nothing else. Build it from `scratch`, which produces an image whose root **is** the skill root. +Agent Substrate mounts an `oci` source as a container image. The image must contain the skill directory and nothing else. Build it from `scratch`, which produces an image whose root **is** the skill root. 1. Create the Dockerfile. ```bash @@ -190,6 +190,8 @@ Agent Substrate mounts an `oci` source as a container image. The image must hold ## Attach the skill to an AgentTemplate +To give an agent the skill, reference the skill image from the AgentTemplate that the agent runs, and start a Session on the revision that kagent compiles. + 1. Add a `skills` entry to an AgentTemplate, and pair it with your Harness through an Agent. Keep the model configuration that your existing template uses, and change only the name and the skill. ```bash kubectl apply -f - <@sha256:`. | -2. Confirm that the Agent compiled. The revision is ready when `desiredRevision` and `latestSuccessfulRevision` hold the same value. +2. Confirm that the Agent compiled. The revision is ready when `desiredRevision` and `latestSuccessfulRevision` report the same value. ```bash kubectl get agent release-writer -n kagent \ -o jsonpath='{.status.desiredRevision}{"\t"}{.status.latestSuccessfulRevision}{"\n"}' ``` > [!NOTE] - > A ready revision means that kagent accepted the reference, not that the image exists. kagent fetches the skill when the agent starts, so a wrong digest surfaces in the next step rather than this one. + > A ready revision means that kagent accepted the reference, not that the image exists. A wrong digest surfaces only when the agent starts. -3. Create a {{< gloss "Session" >}}Session{{< /gloss >}}, and save its ID to an environment variable. A Session pins the revision that it was created on, so a session that already exists does not pick up the skill. +3. Create a {{< gloss "Session" >}}Session{{< /gloss >}}, and save its ID to an environment variable. A Session pins the revision that it was created on, so a Session that already exists does not pick up the skill. ```bash export SESSION_ID=$(kagent agent session create --agent release-writer -o json | jq -r '.session.id') echo $SESSION_ID @@ -246,6 +248,8 @@ Agent Substrate mounts an `oci` source as a container image. The image must hold ## Ask the agent to use the skill +Send requests in the Session that you created, and check that the agent runs the skill's script. + 1. Send the agent a request that matches the skill's description. ```bash kagent agent invoke --session $SESSION_ID \ @@ -266,14 +270,14 @@ Agent Substrate mounts an `oci` source as a container image. The image must hold - update install guide ``` -3. Ask the agent what skills it holds, to confirm the attachment from the agent's own side. +3. To confirm the attachment from the agent's own side, ask the agent which skills it has. ```bash kagent agent invoke --session $SESSION_ID --task "What skills do you have?" ``` ## Publish a new version of the skill -A source is immutable, so changing a skill is a two-step change: publish new content, then point the AgentTemplate at it. +A source is immutable. To change a skill, publish new content, and then point the AgentTemplate at the new digest. 1. Edit the skill. @@ -283,13 +287,13 @@ A source is immutable, so changing a skill is a two-step change: publish new con export SKILL_OCI="$SKILL_REPO@$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$SKILL_REPO:1.1.0")" ``` -3. Update `skills[].source.oci` on the AgentTemplate with the new digest, which compiles a new revision. +3. Update `skills[].source.oci` on the AgentTemplate with the new digest. kagent compiles a new revision. -4. Create a new Session. Conversations that are already running keep the skill content they started with, because their revision is pinned. +4. Create a new Session. Sessions that already exist keep the skill content of their pinned revision. ## Bundle the skill in a plugin -While a standalone source contains one skill, a {{< gloss "Plugin" >}}plugin{{< /gloss >}} contains multiple skills. An AgentTemplate attaches the plugin once and lists the skills that it wants. Use a plugin when you ship a set of skills together, or when you want the same artifact to contribute [MCP servers]({{< link path="skills-and-mcp/plugins#mcp-servers" >}}) as well. +While a standalone source contains one skill, a {{< gloss "Plugin" >}}plugin{{< /gloss >}} contains multiple skills. An AgentTemplate attaches the plugin once and lists the skills that it wants. To ship a set of skills together, or to have the same artifact contribute [Model Context Protocol (MCP) servers]({{< link path="skills-and-mcp/plugins#mcp-servers" >}}) as well, use a plugin. 1. Restructure the directory so that each skill sits under `skills/`, and add the manifest that makes it a plugin. ```bash @@ -338,21 +342,21 @@ While a standalone source contains one skill, a {{< gloss "Plugin" >}}plugin{{< ``` > [!IMPORTANT] - > A plugin enables only the skills that you list. If you omit `plugins[].skills`, or leave it empty, the agent gets none of them, and kagent accepts an empty list rather than reporting an error. For why an explicit list is the safer default, see [Plugins]({{< link path="skills-and-mcp/plugins#skill-allowlist" >}}). + > A plugin enables only the skills that you list. If you omit `plugins[].skills`, or leave it empty, the agent does not get any of the skills, and kagent accepts the empty list without an error. For why an explicit list is the safer default, see [Plugins]({{< link path="skills-and-mcp/plugins#skill-allowlist" >}}). -4. Create a Session on the new revision, and confirm that the agent still has the skill. The session from the previous section is pinned to the revision that carried the standalone skill. +4. Create a Session on the new revision, and confirm that the agent still has the skill. The `$SESSION_ID` Session is pinned to the revision that included the standalone skill. ```bash export PLUGIN_SESSION_ID=$(kagent agent session create --agent release-writer -o json | jq -r '.session.id') kagent agent invoke --session $PLUGIN_SESSION_ID --task "What skills do you have?" ``` - The agent reports `release-notes` exactly as before. A skill behaves the same whether it arrives on its own or inside a plugin, because kagent copies both into `/skills` before the agent starts. + The agent reports `release-notes` exactly as before. A skill behaves in the same way whether it arrives on its own or inside a plugin, because kagent copies both into `/skills` before the agent starts. ## Troubleshoot a skill that does not load A skill that kagent cannot fetch stops the agent from starting at all, rather than producing an agent without that skill. The agent logs the failure and exits, and the Agent never becomes ready. -1. Check the Agent's `Ready` condition. A skill that cannot be fetched leaves the Agent waiting, because the Actor that builds the golden snapshot is the Actor that fetches the skill. +1. Check the Agent's `Ready` condition. A skill that cannot be fetched leaves the Agent waiting, because the Actor that builds the golden snapshot also fetches the skill. ```bash kubectl get agent release-writer -n kagent \ -o jsonpath='{range .status.conditions[?(@.type=="Ready")]}{.status} {.reason} {.message}{end}' @@ -366,7 +370,7 @@ A skill that kagent cannot fetch stops the agent from starting at all, rather th > [!NOTE] > This condition does not name the skill, and reports the same reason for any Actor that has not yet produced a snapshot. An agent that is merely still starting looks identical to one whose skill cannot be fetched. -2. Find the WorkerPool that the Harness runs on, and read its Workers' logs. The Actor writes the failure there rather than to the Agent. +2. Find the WorkerPool that the Harness runs on, and read its Workers' logs. The Actor writes the failure to the Worker logs, not to the Agent. ```bash export WORKER_POOL=$(kubectl get harness my-first-harness -n kagent \ @@ -380,17 +384,17 @@ A skill that kagent cannot fetch stops the agent from starting at all, rather th {"error":"materialize agent plugins: materialize skill \"release-notes\": pull ghcr.io/example-org/release-notes@sha256:3091b91...: Get \"https://ghcr.io/v2/\": EOF","labels":{"ate.atespace":"ate-golden","ate.template.name":"release-writer-my-first-harness-23c20dcb296d"},"level":"ERROR","msg":"failed to materialize Agent Plugins"} ``` - A pool runs the Workers for every agent on it, so filter by the `ate.template.name` label to find one agent. Its value is the AgentTemplate name, the Harness name, and the revision's short form, joined by hyphens. + A pool runs the Workers for every agent on it. To find the entries for one agent, filter by the `ate.template.name` label. Its value is the AgentTemplate name, the Harness name, and the revision's short form, joined by hyphens. 3. Match the message to its cause. | Message | Cause | | ------- | ----- | | `pull : ... 401 Unauthorized` | The registry needs credentials that the cluster does not have. | - | `pull : ... x509` or a TLS error | The registry does not serve HTTPS with a certificate the runtime trusts. | - | `SKILL.md is required` | The artifact was fetched, but no `SKILL.md` file sits at the root that `source.path` selects. | + | `pull : ... x509` or a Transport Layer Security (TLS) error | The registry does not serve HTTPS with a certificate that the runtime trusts. | + | `SKILL.md is required` | The artifact was fetched, but the root that `source.path` selects does not contain a `SKILL.md` file. | | `symlink "..." escapes artifact root` | A symlink in the artifact points outside it. | - | `artifact contains more than 10000 filesystem entries`, or `artifact exceeds 104857600 bytes` | The artifact is over one of the plugin limits. | + | `artifact contains more than 10000 filesystem entries`, or `artifact exceeds 104857600 bytes` | The artifact exceeds one of the plugin limits. | > [!TIP] > Build the skill image with `--platform` set to the architecture of your worker nodes. kagent asks the registry for the `linux/amd64` or `linux/arm64` manifest that matches the node it runs on, so an image published for one architecture alone fails on the other. @@ -415,7 +419,7 @@ A skill that kagent cannot fetch stops the agent from starting at all, rather th rm -rf release-notes release-tools ``` -4. Delete the images that you pushed, `$SKILL_REPO` and `$PLUGIN_REPO`, using your registry's own tooling. +4. Delete the images that you pushed, `$SKILL_REPO` and `$PLUGIN_REPO`, by using your registry's own tooling. ## Next steps diff --git a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md index 328de9fc..ff4b79f5 100644 --- a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md +++ b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md @@ -5,19 +5,19 @@ weight: 40 author: kagent.dev --- -{{< reuse "kagent-docs/snippets/name-product.md" >}} 1.0 has no in-place upgrade path from the 0.10.x line. Moving to 1.0 means installing kagent fresh against a new database, and recreating your resources on it. When your installation runs 1.0, you can perform regular in-place upgrades for later minor releases. To review upgrade paths and versions, see [Version support]({{< link path="reference/versions#release-support-and-compatibility" >}}). For a summary of what 1.0 changes before you plan the move, see [Release notes]({{< link path="reference/release-notes/1.0#100" >}}). +{{< reuse "kagent-docs/snippets/name-product.md" >}} 1.0 does not have an in-place upgrade path from the 0.10.x line. Moving to 1.0 means installing kagent fresh against a new database and recreating your resources on it. When your installation runs 1.0, you can perform regular in-place upgrades for later minor releases. To review upgrade paths and versions, see [Version support]({{< link path="reference/versions#release-support-and-compatibility" >}}). For a summary of what 1.0 changes before you plan the move, see [Release notes]({{< link path="reference/release-notes/1.0#100" >}}). ## In-place upgrade blockers -Three independent changes each rule out `helm upgrade`, so working around any one of them does not help. +Each of the following independent changes rules out `helm upgrade`, so working around any one of them does not help. | Change | Consequence | | ------ | ----------- | -| The database schema is a clean baseline | 1.0 replaces golang-migrate with goose and starts from a single baseline migration. An existing 0.10.x database has no bridge to it, and 1.0 refuses to run against one. | -| The custom resources moved to a new API group | 1.0 serves its resources under `api.kagent.dev`, where 0.10.x serves them under `kagent.dev`. The groups share no conversion, so nothing stored under the old group is visible through the new CRDs. | -| The resource model is replaced | 0.10.x's `Agent` is gone. What it described is now split between an AgentTemplate and a {{< gloss "Harness" >}}Harness{{< /gloss >}}, paired by a new {{< gloss "Agent" >}}Agent{{< /gloss >}} resource in the `api.kagent.dev` group, and a conversation is a {{< gloss "Session" >}}Session{{< /gloss >}} created against that Agent. The two `Agent` kinds share a name and nothing else. For the model itself, see [Core concepts]({{< link path="about/core-concepts" >}}). | +| The database schema is a clean baseline | 1.0 replaces golang-migrate with goose and starts from a single baseline migration. An existing 0.10.x database does not have a migration path to that baseline, and 1.0 refuses to run against a 0.10.x database. | +| The custom resources moved to a new API group | 1.0 serves its resources under `api.kagent.dev`, where 0.10.x serves them under `kagent.dev`. The groups do not share a conversion, so resources stored under the old group are not visible through the new custom resource definitions (CRDs). | +| The resource model is replaced | 0.10.x's `Agent` is gone. What the 0.10.x `Agent` described is now split between an AgentTemplate and a {{< gloss "Harness" >}}Harness{{< /gloss >}}. A new {{< gloss "Agent" >}}Agent{{< /gloss >}} resource in the `api.kagent.dev` group pairs the two. A conversation is a {{< gloss "Session" >}}Session{{< /gloss >}} that is created against that Agent. The two `Agent` kinds share a name and nothing else. For the model itself, see [Core concepts]({{< link path="about/core-concepts" >}}). | -The two releases also cannot run side by side on one cluster. The group rename separates the custom resources, but both charts name their cluster-scoped ClusterRoles after the release, such as `kagent-getter-role`. When both releases use the default `kagent` release name, the 1.0 install collides with the ClusterRoles that 0.10.x owns. A second cluster keeps the old installation intact while you work. +The two releases also cannot run side by side on one cluster. The group rename separates the custom resources, but both charts name their cluster-scoped ClusterRoles after the release, such as `kagent-getter-role`. When both releases use the default `kagent` release name, the 1.0 install collides with the ClusterRoles that 0.10.x owns. > [!WARNING] > Downgrading from 1.0 back to 0.10.x is unsupported. Treat the cutover as one-way, and keep the 0.10.x installation running until you have verified the new one. @@ -28,12 +28,12 @@ The two releases also cannot run side by side on one cluster. The group rename s 2. Decide where you will install 1.0. A second cluster is the safer choice, because it leaves your 0.10.x installation untouched. Installing on the same cluster means uninstalling 0.10.x first, and that step is not reversible. -3. Provision an empty PostgreSQL database for 1.0. It must be a new database rather than a new schema in the old one, and pointing 1.0 at a 0.10.x database fails at startup with a clear error. +3. Provision an empty PostgreSQL database for 1.0. The database must be a new database rather than a new schema in the old one. If you point 1.0 at a 0.10.x database, the startup fails with the following error. ```console source core uses an unsupported migration table. Use a new PostgreSQL database ``` -4. Back up your 0.10.x database. The backup is a restore point for the old installation rather than an input to the new one, because no procedure loads it into 1.0. +4. Back up your 0.10.x database. The backup is a restore point for the old installation only. A procedure that loads the backup into 1.0 does not exist. ```bash pg_dump "postgres://:@:5432/" \ --format=custom \ @@ -52,7 +52,7 @@ Your Kubernetes resources do carry forward, so export them before anything repla -n kagent -o yaml > kagent-0.10-resources.yaml ``` -2. Note which Secrets your ModelConfigs reference. Secrets are ordinary Kubernetes resources that kagent does not own, so recreate them on the new cluster by whatever means you created them originally. +2. Note the Secrets that your ModelConfigs reference. Secrets are ordinary Kubernetes resources that kagent does not own, so recreate them on the new cluster by whatever means you created them originally. ```bash kubectl get modelconfigs -n kagent \ -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.apiKeySecret}{"\n"}{end}' @@ -64,7 +64,7 @@ Install 1.0 against the empty database that you provisioned. kagent and Agent Su Before you start, complete the [Before you begin]({{< link path="setup/installation#before-you-begin" >}}) steps of the installation guide. Those steps prepare the kagent CLI, your provider API key, and a cluster at Kubernetes 1.37 or later. -1. Create the namespaces that hold the connection Secrets. `kagent install` uses the namespaces when they already exist. +1. Create the namespaces that contain the connection Secrets. `kagent install` uses the namespaces when they already exist. ```bash kubectl create namespace kagent kubectl create namespace ate-system @@ -87,7 +87,7 @@ Before you start, complete the [Before you begin]({{< link path="setup/installat --file=substrate-setup.sql ``` -4. Create the Agent Substrate connection Secrets. The Agent Substrate chart reads these names and keys by default, so its release needs no extra values. +4. Create the Agent Substrate connection Secrets. The Agent Substrate chart reads these names and keys by default, so its release does not need extra values. ```bash kubectl create secret generic substrate-postgres-owner \ --namespace ate-system \ @@ -125,9 +125,9 @@ Before you start, complete the [Before you begin]({{< link path="setup/installat | `config.database.vectorEnabled` | Applies the pgvector migrations, which [long-term memory]({{< link path="agents/agent-memory" >}}) depends on. If your database does not have the pgvector extension installed, omit the value. | | `config.server.grpcReflection` | Lets the guides that use `grpcurl` discover the controller API, as in the installation guide. | - The controller applies migrations at startup. To apply them from a pipeline instead, set `config.database.skipMigrations=true` and run `kagent db migrate up` with the owner login yourself. The controller then verifies the schema and fails if it is not already current. + The controller applies migrations at startup. To apply them from a pipeline instead, set `config.database.skipMigrations=true` and run `kagent db migrate up` with the owner login yourself. The controller then verifies the schema and fails if the schema is not already current. -6. Save a CLI configuration file that references those values. The `kagent db` commands read `database.vectorEnabled` from this file, not from the Helm values. Set it to match `config.database.vectorEnabled`, or omit it when you omit that value. +6. Save a CLI configuration file that references those values. The `kagent db` commands read `database.vectorEnabled` from this file, not from the Helm values. Set `database.vectorEnabled` to match `config.database.vectorEnabled`. If you omitted `config.database.vectorEnabled`, omit `database.vectorEnabled` too. ```bash cat > kagent-config.yaml <}}). -1. Recreate the Secrets that your ModelConfigs name before you apply them, or the ModelConfigs resolve to nothing. +1. Before you apply the ModelConfigs, recreate the Secrets that they reference. Otherwise, the ModelConfigs cannot resolve their Secrets. -2. Convert these resources to `v1alpha3`. The export also holds Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. The command does not delete the ModelConfig fields that `v1alpha3` removed, so delete them from the converted file before you apply it. Otherwise, the apply fails. If the converted file includes the `kagent-grafana-mcp` RemoteMCPServer, delete it too, because 1.0 does not deploy the Grafana MCP server behind it. +2. Convert these resources to `v1alpha3`. The export also contains Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. The command does not delete the ModelConfig fields that `v1alpha3` removed, so delete them from the converted file before you apply it. Otherwise, the apply fails. If the converted file includes the `kagent-grafana-mcp` RemoteMCPServer, delete it too, because 1.0 does not deploy the Grafana Model Context Protocol (MCP) server behind it. ```bash yq '.items |= map(select(.kind == "ModelConfig" or .kind == "ModelProviderConfig" or .kind == "RemoteMCPServer")) | .items[].apiVersion = "api.kagent.dev/v1alpha3" @@ -189,7 +189,7 @@ kagent 1.0 cannot pass a mounted credential or certificate file to an agent. In ### Agents become an AgentTemplate, a Harness, and an Agent -A 0.10.x `Agent` described both what the agent does and how it runs. In 1.0 these concepts are separated into two resources: an AgentTemplate holds the agent's behavior, and a Harness holds the runtime and infrastructure. A third resource, 1.0's own `Agent`, pairs one of each. One Harness serves many Agents, so expect fewer Harnesses than you had 0.10.x Agents. +A 0.10.x `Agent` described both what the agent does and how it runs. In 1.0, an AgentTemplate defines the agent's behavior, and a Harness defines the runtime and infrastructure. The 1.0 `Agent` resource pairs one AgentTemplate with one Harness. One Harness serves many Agents, so expect fewer Harnesses than you had 0.10.x Agents. | 0.10.x `Agent` field | Equivalent field in 1.0 | | -------------------- | -------------------- | @@ -203,18 +203,18 @@ A 0.10.x `Agent` described both what the agent does and how it runs. In 1.0 thes | `spec.declarative.runtime` | `Harness.spec.workload.image`, through the runtime that the Harness selects | | `spec.declarative.deployment` | `Harness.spec.workload` and `Harness.spec.substrate`. Agents no longer run as Deployments. | | `spec.type`, `spec.byo` | The `byo` runtime on a Harness. For more information, see [Bring your own agent]({{< link path="agents/bring-your-own-agent" >}}). | -| `spec.declarative.a2aConfig` | Nothing. A2A is always on, and callers address an Agent at `/agents/{namespace}/{name}`. | +| `spec.declarative.a2aConfig` | Nothing. Agent2Agent (A2A) is always on, and callers address an Agent at `/agents/{namespace}/{name}`. | | `spec.iconUrl`, `spec.documentationUrl`, `spec.version`, `spec.provider` | Nothing. kagent builds the agent card from the AgentTemplate's name and description. | -| `spec.declarative.stream`, `executeCodeBlocks`, `shareTools`, `context` | Nothing. `v1alpha3` has no equivalent field. | +| `spec.declarative.stream`, `executeCodeBlocks`, `shareTools`, `context` | Nothing. `v1alpha3` does not have an equivalent field. | > [!NOTE] -> Tool bindings changed shape. A 0.10.x tool set `type: McpServer` or `type: Agent` alongside a matching block. A 1.0 `ToolBinding` carries an `mcp` or `subAgent` block and no discriminator, so the block you set is the binding's kind. For what each binding does, see [About tools]({{< link path="skills-and-mcp/about-tools" >}}). +> Tool bindings changed shape. A 0.10.x tool set `type: McpServer` or `type: Agent` alongside a matching block. A 1.0 `ToolBinding` sets an `mcp` or `subAgent` block and does not set a discriminator, so the block that you set determines the binding's kind. For what each binding does, see [About tools]({{< link path="skills-and-mcp/about-tools" >}}). -Write one Harness for each distinct runtime and infrastructure combination that your agents need. Then write one Agent for each 0.10.x Agent, naming the AgentTemplate and the Harness that it pairs. Neither reusable resource names the other, so the Agent makes a template runnable. For the full field reference, see [Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}), and for a worked example, see [Your first agent]({{< link path="get-started/your-first-agent#create-a-harness-an-agenttemplate-and-an-agent" >}}). +Write one Harness for each distinct runtime and infrastructure combination that your agents need. Then write one Agent for each 0.10.x Agent, that references the AgentTemplate and the Harness that it pairs. Neither reusable resource references the other, so the Agent makes a template runnable. For the full field reference, see [Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}), and for a worked example, see [Your first agent]({{< link path="get-started/your-first-agent#create-a-harness-an-agenttemplate-and-an-agent" >}}). ### Resources with no 1.0 equivalent -Four resource kinds are removed rather than replaced, so plan for each one before you cut over. +The following resource kinds are removed without a replacement, so plan for each one before you cut over. | Removed resource | What to do | | ---------------- | ---------- | @@ -232,7 +232,7 @@ Confirm that the resources resolved before you retire anything. Harness and Agen kagent agent list ``` -2. Read the conditions on any Agent that is not ready. `ResolvedRefs` reports a missing reference, and a Harness that names a WorkerPool that does not exist is the most common cause. +2. Read the conditions on any Agent that is not ready. `ResolvedRefs` reports a missing reference. The most common cause is a Harness that references a WorkerPool that does not exist. ```bash kubectl describe agent -n kagent ``` @@ -244,9 +244,9 @@ Confirm that the resources resolved before you retire anything. Harness and Agen ## Retire the 0.10.x installation -Once the new installation answers correctly, remove the old one. Follow the 0.x procedure rather than the one in these docs: a 0.10.x installation has no Agent Substrate, no `ate.dev` resources, and no identity material to clean up. For those steps, see [Uninstall kagent]({{< relref "/kagent/0.x/operations/uninstall" >}}#uninstall-with-helm) in the 0.x documentation. +After the new installation answers correctly, remove the old one. Follow the 0.x procedure rather than the 1.x procedure, because a 0.10.x installation does not have Agent Substrate, `ate.dev` resources, or identity material to clean up. For those steps, see [Uninstall kagent]({{< relref "/kagent/0.x/operations/uninstall" >}}#uninstall-with-helm) in the 0.x documentation. -Keep the database backup after the uninstall for your own records. It is the only remaining copy of the 0.10.x conversation history, and nothing in 1.0 can read it. +Keep the database backup after the uninstall for your own records. The backup is the only remaining copy of the 0.10.x conversation history, and nothing in 1.0 can read it. ## Next steps diff --git a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md index 2883d2eb..039204c8 100644 --- a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md +++ b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md @@ -9,7 +9,7 @@ Each section covers one patch release in the kagent 1.0 line, newest first. For ## 1.0.0 -kagent 1.0 is a clean break from the 0.10.x line rather than an incremental release. It changes how an agent runs, how you declare one, and which custom resources exist. Because 1.0 does not share a database schema with 0.10.x, moving an existing installation means standing up a new one rather than running `helm upgrade`. +kagent 1.0 is a clean break from the 0.10.x line rather than an incremental release. It changes how an agent runs, how you declare one, and which custom resources exist. For the component versions that this release pins and the Kubernetes version that it is tested against, see [Version support]({{< link path="reference/versions#what-a-kagent-release-includes" >}}). @@ -19,24 +19,24 @@ The runtime moved off Kubernetes Deployments, so the cost of an idle agent and t - **Each conversation runs as an {{< gloss "Actor" >}}Actor{{< /gloss >}} on {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}}**, rather than as a long-lived Deployment that serves every conversation for one agent. - **Conversations suspend between turns.** When a turn ends, Agent Substrate writes the Actor's memory and filesystem to a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}} and releases the {{< gloss "Worker" >}}Worker{{< /gloss >}} that hosted it. The next message restores that snapshot onto whichever Worker is free. For the lifecycle, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume#suspension-between-turns" >}}). -- **Every agent runs in a gVisor sandbox by default.** 0.10.x relied on a Kubernetes `securityContext` to constrain an agent, and 1.0 provides process, network, and filesystem isolation without per-agent security context configuration. For what the sandbox separates, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing#what-the-sandbox-separates" >}}). +- **Every agent runs in a gVisor sandbox by default.** 0.10.x relied on a Kubernetes `securityContext` to constrain an agent. 1.0 provides process, network, and filesystem isolation without per-agent security context configuration. For what the sandbox separates, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing#what-the-sandbox-separates" >}}). ### How you declare an agent The single 0.10.x `Agent` resource described both what an agent does and how it runs. 1.0 separates those concerns across two reusable resources, names each pairing with a third, and adds a fourth object for a running conversation. -- **A {{< gloss "Harness" >}}Harness{{< /gloss >}} defines how an agent is allowed to run**: its runtime, workload image, {{< gloss "WorkerPool" >}}WorkerPool{{< /gloss >}}, and snapshot storage. One Harness serves many agents, so expect fewer Harnesses than you had 0.10.x Agents. -- **An {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}} defines what an agent does**: its model, system prompt, tools, skills, and plugins. -- **An {{< gloss "Agent" >}}Agent{{< /gloss >}} pairs one AgentTemplate with one Harness.** `spec.template` takes either a reference to an existing AgentTemplate or a complete inline spec, while `spec.harnessRef` takes a reference only. The Agent owns readiness, and callers address the Agent rather than the AgentTemplate or the Harness. -- **A {{< gloss "Session" >}}Session{{< /gloss >}} is one running conversation** with an Agent. Unlike the other three, a Session is not a custom resource: kagent's gRPC API creates it and kagent's database tracks it. +- **A {{< gloss "Harness" >}}Harness{{< /gloss >}} defines how an agent is allowed to run**, including its runtime, workload image, {{< gloss "WorkerPool" >}}WorkerPool{{< /gloss >}}, and snapshot storage. One Harness serves many agents, so expect fewer Harnesses than you had 0.10.x Agents. +- **An {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}} defines what an agent does**, including its model, system prompt, tools, skills, and plugins. +- **An {{< gloss "Agent" >}}Agent{{< /gloss >}} pairs one AgentTemplate with one Harness.** `spec.template` takes either a reference to an existing AgentTemplate or a complete inline spec. `spec.harnessRef` takes only a reference. The Agent owns readiness. Callers address the Agent, not the AgentTemplate or the Harness. +- **A {{< gloss "Session" >}}Session{{< /gloss >}} is one running conversation** with an Agent. Unlike the other three, a Session is not a custom resource. kagent's gRPC API creates each Session, and kagent's database tracks it. -Nothing pairs a Harness and an AgentTemplate implicitly. Neither resource carries a field naming the other, so an Agent is what makes a template runnable on a Harness. For the full model, see [Core concepts]({{< link path="about/core-concepts#agent" >}}). +kagent does not pair a Harness and an AgentTemplate implicitly. Neither resource has a field that references the other. An Agent makes a template runnable on a Harness. For the full model, see [Core concepts]({{< link path="about/core-concepts#agent" >}}). -The CRD API group also moves from `kagent.dev/v1alpha2` to `api.kagent.dev/v1alpha3`, with no conversion between the two. 1.0 no longer bundles kmcp, so it does not install the `mcpservers.kagent.dev` CRD. 1.0 also no longer bundles the Grafana MCP server, so it does not create the `kagent-grafana-mcp` RemoteMCPServer. The two releases still cannot run side by side on one cluster, because both charts name their ClusterRoles after the release. +The custom resource definition (CRD) API group also moves from `kagent.dev/v1alpha2` to `api.kagent.dev/v1alpha3`. kagent does not convert resources between the two groups. 1.0 no longer bundles kmcp, so it does not install the `mcpservers.kagent.dev` CRD. 1.0 also no longer bundles the Grafana Model Context Protocol (MCP) server, so it does not create the `kagent-grafana-mcp` RemoteMCPServer. The two releases still cannot run side by side on one cluster, because both charts name their ClusterRoles after the release. ### Removed and replaced resources -Six resource kinds are removed or replaced and three carry forward. The distinction matters when you plan a move, because a replaced kind has somewhere for its configuration to go, while a removed kind has none. +Each 0.10.x resource kind is replaced, removed, or carried forward in 1.0. The distinction matters when you plan a move. A replaced kind has a destination for its configuration. A removed kind does not. | 0.10.x resource | Status in 1.0 | | --------------- | ------------- | @@ -51,14 +51,14 @@ Six resource kinds are removed or replaced and three carry forward. The distinct > [!IMPORTANT] > 1.0's `Harness` and 0.10.x's `AgentHarness` share part of a name and nothing else. `AgentHarness` provisioned coding-agent sandboxes, while `Harness` governs how any agent is allowed to run. Read `Harness` as a new resource rather than a renamed one. -Several `Agent` fields have no `v1alpha3` equivalent at all, including `a2aConfig`, `executeCodeBlocks`, `shareTools`, and `context`. A2A is always on in 1.0. A caller addresses an Agent at `/agents/{namespace}/{name}` over HTTP, or through the A2A `tenant` field over gRPC. Tool bindings also changed shape. A 1.0 `ToolBinding` carries an `mcp` or `subAgent` block and no `type` discriminator. For the field-by-field mapping, see [Agents become an AgentTemplate, a Harness, and an Agent]({{< link path="operations/upgrade-from-0x#agents-become-an-agenttemplate-a-harness-and-an-agent" >}}). +Several `Agent` fields do not have a `v1alpha3` equivalent, including `a2aConfig`, `executeCodeBlocks`, `shareTools`, and `context`. Agent-to-Agent (A2A) is always on in 1.0. A caller addresses an Agent at `/agents/{namespace}/{name}` over HTTP, or through the A2A `tenant` field over gRPC. Tool bindings also changed shape. A 1.0 `ToolBinding` contains an `mcp` or `subAgent` block and does not have a `type` discriminator. For the field-by-field mapping, see [Agents become an AgentTemplate, a Harness, and an Agent]({{< link path="operations/upgrade-from-0x#agents-become-an-agenttemplate-a-harness-and-an-agent" >}}). ### Upgrade paths -Compatibility promises start at 1.0 rather than continuing from the 0.10.x line, so plan a migration rather than an upgrade. +Compatibility promises start at 1.0. Plan a migration from 0.10.x, not an upgrade. -- **0.10.x to 1.0 in place is not supported.** An existing 0.10.x database has no migration bridge to 1.0 and 1.0 requires a new PostgreSQL database. +- **0.10.x to 1.0 in place is not supported.** kagent does not migrate a 0.10.x database to 1.0. 1.0 requires a new PostgreSQL database. - **Downgrade from 1.0 to 0.10.x is not supported.** - **1.0 to a later minor release is supported.** From 1.0 onward, migrations are append-only and each minor release retains compatibility with the previous release line. -Moving an existing installation therefore means installing 1.0 alongside the old one, recreating your resources on it, and retiring 0.10.x once the new installation answers correctly. Conversation history does not carry across, because nothing in 1.0 can read a 0.10.x database. For the full procedure, see [Upgrade from 0.x]({{< link path="operations/upgrade-from-0x#in-place-upgrade-blockers" >}}). \ No newline at end of file +Moving an existing installation therefore means installing 1.0 alongside the old one, recreating your resources on it, and retiring 0.10.x once the new installation answers correctly. Conversation history does not carry across, because 1.0 cannot read a 0.10.x database. For the full procedure, see [Upgrade from 0.x]({{< link path="operations/upgrade-from-0x#in-place-upgrade-blockers" >}}). \ No newline at end of file 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 79e687a9..93a34ebe 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -9,52 +9,52 @@ A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation ships with o ## Server that a kagent installation registers -The kagent chart installs the tool server as a subchart, which the `kagent-tools.enabled` Helm value turns on. The subchart creates the [`kagent-tool-server`](#kagent-tool-server) {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace, which serves 126 tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. kagent does not bind the server to an agent, so an agent reaches the server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. +The kagent chart installs the tool server as a subchart, which the `kagent-tools.enabled` Helm value turns on. The subchart creates the [`kagent-tool-server`](#kagent-tool-server) {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace. The server serves tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. kagent does not bind the server to an agent, so an agent reaches the server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. The kagent release pins the `kagent-tool-server` version, which is currently {{< reuse "kagent-docs/versions/kagent-tools.md" >}}. For the full set of pinned component versions, see [Version support]({{< link path="reference/versions#what-a-kagent-release-includes" >}}). ## Read the tools that a server serves -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. +The kagent controller connects to each RemoteMCPServer, asks the server which tools it serves, and records the answer in `status.discoveredTools`. That status reports the tools of the server that your cluster actually runs, so the status is more reliable than any list on this page. ```sh kubectl get remotemcpserver kagent-tool-server -n kagent \ -o jsonpath='{range .status.discoveredTools[*]}{.name}{"\t"}{.description}{"\n"}{end}' ``` -[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. +For the same command as the first step of binding one of these tools to an agent, see [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-the-tool-to-your-agenttemplate" >}}). > [!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. 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. +> `status.discoveredTools` stays empty until the controller completes a discovery pass. `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. To tell the two apart, read the `Accepted` condition's reason, as described in [Turn tool discovery off for a server](#turn-tool-discovery-off-for-a-server). ### 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, does not accept any 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. +The controller lists a server's tools with the controller's own credentials. A server that authenticates every caller individually, such as one that expects each agent's own propagated token, does not accept any credential that the controller can present. Discovery against that server fails. Because the listing never succeeds, the RemoteMCPServer stays un-`Accepted` indefinitely, even though agents can reach the server at runtime. -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. +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 their own credentials. ```sh kubectl label remotemcpserver -n kagent.dev/discovery=disabled ``` -The label changes three things about the server: +The label has the following effects on 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. +- `status.discoveredTools` stays empty, so the command in [Read the tools that a server serves](#read-the-tools-that-a-server-serves) does not return tools 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 and without tools. > [!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. +> Use this label only when controller-side discovery cannot succeed. Binding specific tools by name in an AgentTemplate still works against a server that has discovery turned off. However, admission does not validate those names, so a typo surfaces as a failed tool call at runtime instead of a rejected binding. ## kagent-tool-server -One Go binary serves every tool in this section, grouped into eight providers. A provider's tools shell out to the matching command line tool inside the server's own container, so the Kubernetes, Helm, Istio, and Cilium providers act on your cluster with the server's ServiceAccount rather than with the caller's credentials. +One Go binary serves every tool in this section, grouped into providers. A provider's tools shell out to the matching command line tool inside the server's own container, so the Kubernetes, Helm, Istio, and Cilium providers act on your cluster with the server's ServiceAccount rather than with the caller's credentials. Tools marked `*` perform write operations, and `--read-only` skips them at registration. The following sections list each provider's tools by the name that an agent sees. ### Kubernetes -Agents use the Kubernetes provider the most, and it covers both inspection and modification of any resource that `kubectl` can reach. +Agents use the Kubernetes provider the most. The provider covers both inspection and modification of any resource that `kubectl` can reach. | Tool | Description | | ---- | ----------- | @@ -97,7 +97,7 @@ The Helm provider manages releases and chart repositories with the Helm configur ### Istio -Istio's tools wrap `istioctl`, and most of them read mesh state rather than change it. Waypoint management is the exception, as generating a waypoint resource and applying it are separate tools. +Istio's tools wrap `istioctl`, and most of them read mesh state rather than change it. Waypoint management is the exception, because generating a waypoint resource and applying it are separate tools. | Tool | Description | | ---- | ----------- | @@ -117,7 +117,7 @@ Istio's tools wrap `istioctl`, and most of them read mesh state rather than chan ### Argo Rollouts -Argo Rollouts tools cover progressive delivery. A rollout tool fails confusingly when the controller or one of its plugins is missing, so four of the eight tools verify the installation instead of acting on a rollout. +Argo Rollouts tools cover progressive delivery. A rollout tool fails confusingly when the controller or one of its plugins is missing, so several of the tools verify the installation instead of acting on a rollout. | Tool | Description | | ---- | ----------- | @@ -132,7 +132,7 @@ Argo Rollouts tools cover progressive delivery. A rollout tool fails confusingly ### Cilium -Cilium is the largest provider, with 58 tools ranging from installation to individual BPF maps. An agent that can see every tool spends its context on tool descriptions, so bind only the handful that an agent needs rather than the whole provider. +Cilium is the largest provider, with tools ranging from installation to individual BPF maps. An agent that can see every tool spends its context on tool descriptions, so bind only the tools that an agent needs. | Tool | Description | | ---- | ----------- | @@ -197,7 +197,7 @@ Cilium is the largest provider, with 58 tools ranging from installation to indiv ### Prometheus -The Prometheus provider queries a Prometheus server directly. Each tool takes the server's URL as an argument and defaults to `http://localhost:9090`, which does not resolve from inside the tool server's container, so have the agent pass the in-cluster address. +The Prometheus provider queries a Prometheus server directly. Each tool takes the server's URL as an argument, and that argument defaults to `http://localhost:9090`. The default address does not resolve from inside the tool server's container, so have the agent pass the in-cluster address. | Tool | Description | | ---- | ----------- | @@ -209,7 +209,7 @@ The Prometheus provider queries a Prometheus server directly. Each tool takes th ### Kubescape -Kubescape's tools read scan results that the Kubescape operator has already produced, so they return nothing useful until that operator is installed and has completed a scan. Start with `kubescape_check_health`, which reports whether the rest of the provider can work. +Kubescape's tools read scan results that the Kubescape operator has already produced, so the tools do not return useful results until that operator is installed and has completed a scan. Start with `kubescape_check_health`, which reports whether the rest of the provider can work. | Tool | Description | | ---- | ----------- | @@ -226,7 +226,7 @@ Kubescape's tools read scan results that the Kubescape operator has already prod ### Utilities -Three tools sit outside any product. `shell` runs an arbitrary command in the server's container with the server's ServiceAccount, so treat it as the widest permission that this server grants. +The utility tools do not belong to any product. `shell` runs an arbitrary command in the server's container with the server's ServiceAccount, so treat it as the widest permission that this server grants. | Tool | Description | | ---- | ----------- | @@ -236,7 +236,7 @@ Three tools sit outside any product. `shell` runs an arbitrary command in the se ## Narrow what kagent-tool-server serves -Serving all 126 tools costs an agent context on every turn, and it grants the server broad authority over your cluster. Two independent settings narrow it, and each one acts at a different layer. +Serving every tool costs an agent context on every turn, and the full tool set grants the server broad authority over your cluster. Independent settings narrow the server, and each one acts at a different layer. ```yaml kagent-tools: @@ -263,7 +263,7 @@ kagent-tools: | `rbac.additionalRules` | Applies only where `rbac.readOnly` is `true`. Extra rules to append to the read-only ClusterRole, for the CRDs that the Istio, Cilium, and Argo providers read. | > [!IMPORTANT] -> `--read-only` and `rbac.readOnly` are separate controls, and setting only one leaves a gap. `--read-only` stops the write tools from being registered, so an agent cannot call them. `rbac.readOnly` removes the cluster permissions that back them. Set only the flag, and the ServiceAccount keeps `cluster-admin`, which anything else in the container can still use. Set only the RBAC value, and the write tools stay in the catalog and fail at call time with permission errors that the agent then tries to work around. +> `--read-only` and `rbac.readOnly` are separate controls, and setting only one leaves a gap. `--read-only` stops the write tools from being registered, so an agent cannot call them. `rbac.readOnly` removes the cluster permissions that back them. If you set only the flag, the ServiceAccount keeps `cluster-admin`, which anything else in the container can still use. If you set only the role-based access control (RBAC) value, the write tools stay in the catalog and fail at call time with permission errors that the agent then tries to work around. Because the settings are per-installation, they change what every agent sees. To give one agent a smaller set while leaving the server intact, list the tools on the tool binding instead, as described in [About tools]({{< link path="skills-and-mcp/about-tools#mcp-tools" >}}). @@ -271,7 +271,7 @@ The kagent and Codex harnesses both honor this tool list. On the Claude {{< glos ## Reach kagent-tool-server from another namespace -The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references from its own namespace only. Where an AgentTemplate in another namespace binds the server, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. +The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references only from its own namespace. Where an AgentTemplate in another namespace binds the server, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. ```yaml kagent-tools: @@ -285,7 +285,7 @@ kagent-tools: | Field | Description | | ----- | ----------- | -| `allowedNamespaces.from` | Where a reference may originate, one of `All`, `Same`, or `Selector`. Defaults to `Same`. The default admits the release namespace only. | +| `allowedNamespaces.from` | Where a reference may originate, one of `All`, `Same`, or `Selector`. Defaults to `Same`. The default admits only the release namespace. | | `allowedNamespaces.selector` | A label selector over namespaces. The server reads this field only where `from` is `Selector`, and the selector takes `matchLabels` and `matchExpressions`. | The selector follows the Gateway API pattern for cross-namespace attachment, so the label belongs on the Namespace rather than on the AgentTemplate that binds the server. @@ -299,7 +299,7 @@ kubectl label namespace my-agents kagent-tools=allowed ## Community and contributed servers -kagent's repository carries example servers in [contrib/tools](https://github.com/kagent-dev/kagent/tree/main/contrib/tools). Each one is a starting point rather than a supported component, and their manifests were written against different kagent API versions, so check the `apiVersion` before you apply one. +kagent's repository contains example servers in [contrib/tools](https://github.com/kagent-dev/kagent/tree/main/contrib/tools). Each server is an unsupported starting point. The manifests target different kagent API versions, so check the `apiVersion` before you apply one. | Server | Ready to apply | Description | | ------ | -------------- | ----------- | @@ -312,7 +312,7 @@ kagent's repository carries example servers in [contrib/tools](https://github.co To register any other MCP server, write a RemoteMCPServer that points at its URL. For a complete example, see [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-your-own-mcp-server" >}}). To give agents Grafana tools, run Grafana's own server from the upstream [mcp-grafana](https://github.com/grafana/mcp-grafana) project, and register it in the same way. > [!NOTE] -> A RemoteMCPServer takes a URL, so kagent connects only to servers that speak streamable HTTP or Server-Sent Events (SSE). To reach a server that runs over stdio, wrap it in a deployment that exposes an HTTP endpoint. [kmcp](https://kagent.dev/docs/kmcp/) builds and runs such servers. +> A RemoteMCPServer takes a URL, so kagent connects only to servers that speak streamable HTTP or Server-Sent Events (SSE). To reach a server that runs over stdio, wrap it in a deployment that exposes an HTTP endpoint. To build and run such servers, use [kmcp](https://kagent.dev/docs/kmcp/). ## Next steps diff --git a/docs-site/content/kagent/1.x/setup/installation.md b/docs-site/content/kagent/1.x/setup/installation.md index 521c5231..60fec734 100644 --- a/docs-site/content/kagent/1.x/setup/installation.md +++ b/docs-site/content/kagent/1.x/setup/installation.md @@ -5,10 +5,10 @@ weight: 10 author: kagent.dev --- -{{< reuse "kagent-docs/snippets/name-product.md" >}} 1.0 runs every agent on [Agent Substrate]({{< link path="about/architecture/agent-substrate" >}}), so an installation sets up two systems in the same cluster. Agent Substrate provides the sandboxed compute that agents run on, and kagent provides the Harness, AgentTemplate, and Agent API that you author against. The `kagent install` command installs kagent, Agent Substrate, and the development PostgreSQL database together. +{{< reuse "kagent-docs/snippets/name-product.md" >}} 1.0 runs every agent on [Agent Substrate]({{< link path="about/architecture/agent-substrate" >}}), so an installation sets up two systems in the same cluster. Agent Substrate provides the sandboxed compute that agents run on. kagent provides the Harness, AgentTemplate, and Agent API that you author against. > [!NOTE] -> These steps install kagent 1.0 fresh. kagent 1.0 has no in-place upgrade from the 0.10.x version line, and installing its custom resource definitions replaces the ones that a 0.10.x installation uses. To move an existing installation, start with [Upgrade from 0.x]({{< link path="operations/upgrade-from-0x#in-place-upgrade-blockers" >}}). +> These steps install kagent 1.0 fresh. kagent 1.0 does not support an in-place upgrade from the 0.10.x version line, and installing its custom resource definitions (CRDs) replaces the ones that a 0.10.x installation uses. To move an existing installation, start with [Upgrade from 0.x]({{< link path="operations/upgrade-from-0x#in-place-upgrade-blockers" >}}). ## Before you begin @@ -30,14 +30,14 @@ author: kagent.dev {{< tabs >}} {{% tab name="Local kind cluster" %}} - For local testing and development, create a [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster at Kubernetes 1.37 or later. Use kind v0.32.0 or later. No extra API configuration is needed. + For local testing and development, create a [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster at Kubernetes 1.37 or later. Use kind v0.32.0 or later. The cluster does not need extra API configuration. ```bash kind create cluster --name kagent --image kindest/node:v1.37.0 ``` {{% /tab %}} {{% tab name="Existing 1.37+ cluster" %}} - Kubernetes 1.37 and later serve the required APIs at `certificates.k8s.io/v1` by default, so you do not need to change the API server configuration. + You do not need to change the API server configuration. Confirm that the cluster serves the required APIs. ```bash @@ -56,7 +56,7 @@ author: kagent.dev ## Install kagent -The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertificate controller, a development PostgreSQL database, and kagent in the correct order. It also creates the Substrate identity material and database connection Secrets that the releases need. +The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertificate controller, a development PostgreSQL database, and kagent in the correct order. The CLI also creates the Agent Substrate identity material and database connection Secrets that the releases need. 1. Save a Helm values file that enables gRPC server reflection, so that guides that use `grpcurl` can discover the controller API. ```bash @@ -81,7 +81,7 @@ The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertif kagent --config kagent-config.yaml install --namespace kagent ``` - The command reads your provider API key, installs Agent Substrate into `ate-system`, and deploys PostgreSQL as `kagent-postgresql` in the kagent namespace. It then connects kagent and Agent Substrate to that database with Pod Certificates. The development database runs the stock `postgres` image, which omits the `pgvector` extension that [long-term memory]({{< link path="agents/agent-memory" >}}) needs. + The command reads your provider API key, installs Agent Substrate into `ate-system`, and deploys PostgreSQL as `kagent-postgresql` in the kagent namespace. It then connects kagent and Agent Substrate to that database with Pod Certificates. The development database runs the stock `postgres` image. That image does not include the `pgvector` extension that [long-term memory]({{< link path="agents/agent-memory" >}}) needs. 4. Wait for the controller to roll out. ```bash @@ -92,6 +92,8 @@ The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertif ## Verify the installation +Before you create an agent, confirm that kagent and Agent Substrate are running, and find the address that clients use to reach the kagent API. + 1. Confirm that the kagent pods are running. ```bash kubectl get pods -n kagent @@ -138,7 +140,7 @@ The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertif {{% /tab %}} {{< /tabs >}} -4. Optional: Open the kagent UI. The UI is a separate `{{< reuse "kagent-docs/snippets/name-ui.md" >}}` service, so it needs its own port-forward in a second terminal. The dashboard is then available at [http://localhost:8082](http://localhost:8082). For the `kagent dashboard` shortcut and a tour of the UI, see [Launch the UI]({{< link path="observability/launch-ui" >}}). +4. Optional: Open the kagent UI. The UI is a separate `{{< reuse "kagent-docs/snippets/name-ui.md" >}}` service, so it needs its own port-forward in a second terminal. The UI is then available at [http://localhost:8082](http://localhost:8082). For the `kagent dashboard` shortcut and a tour of the UI, see [Launch the UI]({{< link path="observability/launch-ui" >}}). ```bash kubectl port-forward -n kagent svc/{{< reuse "kagent-docs/snippets/name-ui.md" >}} 8082:8080 ``` @@ -147,29 +149,29 @@ The kagent CLI installs the CRDs, Agent Substrate, the Agent Substrate PodCertif ## Restrict kagent to specific namespaces -By default the controller watches every namespace, and the chart creates cluster-scoped RBAC (ClusterRoles and ClusterRoleBindings). To restrict kagent to a set of namespaces, set `global.watchNamespaces` in your Helm values: +By default the controller watches every namespace, and the chart creates cluster-scoped role-based access control (RBAC) resources, which are ClusterRoles and ClusterRoleBindings. To restrict kagent to a set of namespaces, set `global.watchNamespaces` in your Helm values: ```yaml global: watchNamespaces: [prod-agents, staging-agents] ``` -Prefer `global.watchNamespaces` over the per-surface keys. The chart derives both surfaces from this one value, so they cannot disagree: it replaces every kagent ClusterRole with a Role and RoleBinding in each listed namespace, and it limits the controller's watch to the same list. The chart appends the install namespace to the list automatically, so the release's own objects remain managed. +The chart derives both surfaces from `global.watchNamespaces`, so the surfaces cannot disagree. For that reason, use `global.watchNamespaces` in preference to the per-surface keys. The chart replaces every kagent ClusterRole with a Role and RoleBinding in each listed namespace. The chart also limits the controller's watch to the same list. The chart appends the install namespace to the list automatically, so the release's own resources remain managed. -The per-surface keys still exist for installs that scope the surfaces independently, and the validation rules bind them: +The per-surface keys still exist for installations that scope the surfaces independently, and the validation rules bind them: -- `rbac.namespaces` takes precedence over `global.watchNamespaces` whenever the key is present. An explicit `rbac.namespaces: []` restores cluster-scoped RBAC. When `rbac.namespaces` is set, it must include the install namespace or the render fails. -- `config.kubernetes.watchNamespaces` is an explicit watch override. Under `rbac.namespaces`, a watch entry that names a namespace outside the RBAC scope fails the render, because the controller would loop on `Forbidden` errors at runtime. Under `global.watchNamespaces`, the chart folds the entries into the watch scope instead of failing. In both cases the watch list replaces the default, so the install namespace is not added back once `config.kubernetes.watchNamespaces` is set — list it explicitly if the controller should keep watching it. +- `rbac.namespaces` takes precedence over `global.watchNamespaces` whenever the key is present. An explicit `rbac.namespaces: []` restores cluster-scoped RBAC. When `rbac.namespaces` is set, it must include the install namespace, or the render fails. +- `config.kubernetes.watchNamespaces` is an explicit watch override. When `rbac.namespaces` is set, a watch entry that references a namespace outside the RBAC scope fails the render, because the controller would loop on `Forbidden` errors at runtime. When `global.watchNamespaces` is set, the chart folds the entries into the watch scope and does not fail the render. In both cases, the watch list replaces the default. When `config.kubernetes.watchNamespaces` is set, the chart does not add the install namespace back. For the controller to keep watching the install namespace, list it explicitly. - The `kagent-tool-server` RemoteMCPServer that the chart creates admits references from its own namespace only. An Agent in another watched namespace that binds it does not become ready until you set `allowedNamespaces` on that server. See [Reach kagent-tool-server from another namespace]({{< link path="reference/tools-ecosystem#reach-kagent-tool-server-from-another-namespace" >}}). -Verify a namespaced install the same way as the default one, plus the negative case. The [create your first agent]({{< link path="get-started/your-first-agent" >}}) guide's `kubectl get agents` and conditions checks apply to each namespace; the difference is that an Agent the controller does not watch stays `UNKNOWN` forever. +Verify a namespaced installation in the same way as the default one, and also check that an unwatched namespace stays unmanaged. The `kubectl get agents` and conditions checks from the guide to [create your first agent]({{< link path="get-started/your-first-agent" >}}) apply to each watched namespace. 1. Create one Agent in each watched namespace. ```bash kubectl apply -n prod-agents -f my-agent.yaml kubectl apply -n staging-agents -f my-agent.yaml ``` -2. Create one Agent in a namespace the list does not cover. +2. Create one Agent in a namespace that the list does not cover. ```bash kubectl apply -n dev-agents -f my-agent.yaml ``` @@ -184,7 +186,7 @@ Verify a namespaced install the same way as the default one, plus the negative c my-agent 30s True my-agent 30s True ``` -4. Confirm that the Agent in the unwatched namespace stays unmanaged: its `READY` value stays `UNKNOWN`, and its conditions stay empty, because the controller has no watch entry for that namespace. +4. Confirm that the Agent in the unwatched namespace stays unmanaged. The Agent's `READY` value stays `UNKNOWN` and its conditions stay empty, because the controller does not have a watch entry for that namespace. ```bash kubectl get agents -n dev-agents kubectl get agent my-agent -n dev-agents -o jsonpath='{.status.conditions}' diff --git a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md index 7d4becb7..31f13b6a 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md @@ -16,7 +16,7 @@ The following example uses [Cohere](https://cohere.com/), which serves an OpenAI export PROVIDER_API_KEY= ``` -2. Create a Kubernetes Secret that stores the API key. Create it in the same namespace as the AgentTemplates that use it, such as `kagent`. +2. Create a Kubernetes Secret that stores the API key. Create the Secret in the same namespace as the ModelConfig, such as `kagent`. ```bash kubectl create secret generic kagent-my-provider -n kagent --from-literal PROVIDER_API_KEY=$PROVIDER_API_KEY ``` @@ -42,7 +42,7 @@ The following example uses [Cohere](https://cohere.com/), which serves an OpenAI | Field | Description | | ----- | ----------- | | `apiKeySecret` | The name of the Kubernetes Secret that stores the API key, in the same namespace as this ModelConfig. | - | `apiKeySecretKey` | The key within that Secret that holds the API key. | + | `apiKeySecretKey` | The key within that Secret that stores the API key. | | `model` | The model identifier that your provider routes on. Consult your provider's documentation. | | `provider` | The provider to use, `OpenAI`. | | `openAI.baseUrl` | Your provider's OpenAI-compatible endpoint. Providers often serve this at a dedicated path, such as `/compatibility/v1`. | @@ -55,7 +55,7 @@ For the rest of the settings that the `openAI` block accepts, see [OpenAI]({{< l ### Enable tool calling in vLLM -kagent sends a `tools` array with `tool_choice: "auto"` on every request. By default, kagent's runtime registers a built-in `ask_user` tool on every agent, so that array is sent even when you do not bind any tools yourself. Launch vLLM with automatic tool choice enabled, or every agent turn fails. +kagent sends a `tools` array with `tool_choice: "auto"` on every request. By default, kagent's runtime registers a built-in `ask_user` tool on every agent, so kagent sends that array even when you do not bind any tools yourself. To prevent every agent turn from failing, launch vLLM with automatic tool choice enabled. ```bash vllm serve Qwen/Qwen2.5-7B-Instruct \ @@ -63,11 +63,11 @@ vllm serve Qwen/Qwen2.5-7B-Instruct \ --tool-call-parser hermes ``` -The correct `--tool-call-parser` depends on your model family. Qwen2.5 uses `hermes` and Llama 3.1 uses `llama3_json`. Parser names change across vLLM releases, so check the [vLLM tool calling docs](https://docs.vllm.ai/en/latest/features/tool_calling.html) for the current name for your model. +The correct `--tool-call-parser` depends on your model family. Qwen2.5 uses `hermes`. Llama 3.1 uses `llama3_json`. Parser names change across vLLM releases, so check the [vLLM tool calling docs](https://docs.vllm.ai/en/latest/features/tool_calling.html) for the current name for your model. ### Use the gateway's model identifier -Set `model` to the identifier that your gateway routes on, which is often provider-prefixed and can differ from the bare model name that vLLM serves internally. Point `openAI.baseUrl` at the gateway. LiteLLM defaults to port `4000`, and Bifrost to `8080`. +Set `model` to the identifier that your gateway routes on, which is often provider-prefixed and can differ from the bare model name that vLLM serves internally. Point `openAI.baseUrl` at the gateway. LiteLLM defaults to port `4000`. Bifrost defaults to port `8080`. ```yaml spec: @@ -81,23 +81,16 @@ spec: ### Troubleshooting a 400 from the provider -When every agent message fails with a generic `provider API error (status 400)`, the most common cause is a vLLM server started without `--enable-auto-tool-choice` and a matching `--tool-call-parser`. Because kagent always sends `tool_choice: "auto"`, vLLM rejects the request until automatic tool choice is enabled. Restart vLLM with both flags and try again. +When every agent message fails with a generic `provider API error (status 400)`, the most common cause is a vLLM server started without `--enable-auto-tool-choice` and a matching `--tool-call-parser`. Restart vLLM with both flags and try again. ## TLS -A provider on your own network may present a certificate that the agent does not already trust. The `tls` block adjusts how the agent verifies it. - -| Field | Description | -| ----- | ----------- | -| `disableVerify` | Turns off certificate verification entirely. Defaults to `false`. | -| `disableSystemCAs` | Trusts only the named CA bundle rather than the system trust store. Defaults to `false`. | -| `caCertSecretRef` | The name of a Secret holding a PEM certificate authority bundle. | -| `caCertSecretKey` | The key within that Secret that holds the bundle. | +A provider on your own network might present a certificate that the agent does not already trust. The `tls` block defines one field, `disableVerify`, which turns off certificate verification entirely when you set it to `true`. The field defaults to `false`. > [!WARNING] -> **Pinning a certificate authority is not currently supported.** The `tls` block defines no field for a CA bundle, because an agent running on {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} cannot mount files. The API server rejects `caCertSecretRef` and `caCertSecretKey` as unknown fields. Use a certificate that chains to a public authority, or terminate TLS at a gateway that the agent can trust. +> **The `tls` block does not accept a certificate authority (CA) bundle.** An agent running on {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} cannot mount files, so the API server rejects `caCertSecretRef`, `caCertSecretKey`, and `disableSystemCAs` as unknown fields. Use a certificate that chains to a public authority, or terminate Transport Layer Security (TLS) at a gateway that the agent can trust. -Turning verification off does not mount anything, so it does compile. +The following ModelConfig turns off verification for a provider on an internal network. ```yaml spec: @@ -112,7 +105,7 @@ spec: ``` > [!WARNING] -> Disabling verification removes the guarantee that the agent is talking to the server that it thinks it is. Use it for local testing, never in production. +> Disabling verification removes the guarantee that the agent connects to the intended server. Disable verification only for local testing, never in production. ## Use the ModelConfig From ee815d681b913ede7ab0af8a619e901a9327a9f5 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Thu, 29 Oct 2026 12:41:26 -0500 Subject: [PATCH 3/9] install cli snippets Signed-off-by: Rachael Graham --- docs-site/assets/kagent-docs/snippets/install-cli.md | 5 +++++ .../1.x/about/substrate-runtime/standalone-sandboxes.md | 5 +---- docs-site/content/kagent/1.x/agents/structured-output.md | 7 +++---- .../content/kagent/1.x/get-started/your-first-agent.md | 5 +---- 4 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 docs-site/assets/kagent-docs/snippets/install-cli.md diff --git a/docs-site/assets/kagent-docs/snippets/install-cli.md b/docs-site/assets/kagent-docs/snippets/install-cli.md new file mode 100644 index 00000000..5a1ec64b --- /dev/null +++ b/docs-site/assets/kagent-docs/snippets/install-cli.md @@ -0,0 +1,5 @@ +Download the kagent CLI. The `--version` flag matches the CLI to the release that these docs cover. + +```bash +curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v{{< reuse "kagent-docs/versions/kagent.md" >}} +``` diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index 8415890e..e7520e8e 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md @@ -41,10 +41,7 @@ An agent can create a Sandbox of its own through the kagent Model Context Protoc | `config.sandboxes.defaultTtl` | `3600s` | Lifetime applied when `kagent sandbox create` omits `--ttl`. Durations in the `config` block take protobuf seconds notation, so `3600s` rather than `1h`. | | `config.sandboxes.maxTtl` | `86400s` | Upper bound on any requested lifetime, which the controller caps at `86400s`. A Sandbox expires the configured interval after it is created, regardless of how recently a command ran in it. | -2. Download the kagent CLI. The `--version` flag matches the CLI to the release that these docs cover. - ```bash - curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v{{< reuse "kagent-docs/versions/kagent.md" >}} - ``` +2. {{< reuse "kagent-docs/snippets/install-cli.md" >}} 3. To read the Sandbox ID from the CLI's JSON output, install [`jq`](https://jqlang.org/download/). diff --git a/docs-site/content/kagent/1.x/agents/structured-output.md b/docs-site/content/kagent/1.x/agents/structured-output.md index 6eddddaf..500715ad 100644 --- a/docs-site/content/kagent/1.x/agents/structured-output.md +++ b/docs-site/content/kagent/1.x/agents/structured-output.md @@ -27,10 +27,9 @@ The schema goes in one of two AgentTemplate fields, `spec.outputSchema` or `spec kagent version ``` - If `kagent_version` in the output is not {{< reuse "kagent-docs/versions/kagent.md" >}}, install that version. - ```bash - curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v{{< reuse "kagent-docs/versions/kagent.md" >}} - ``` + If `kagent_version` in the output is not {{< reuse "kagent-docs/versions/kagent.md" >}}, install the CLI for this release. + + {{< reuse "kagent-docs/snippets/install-cli.md" >}} ## Set the schema inline diff --git a/docs-site/content/kagent/1.x/get-started/your-first-agent.md b/docs-site/content/kagent/1.x/get-started/your-first-agent.md index fd2c4b90..ddc6acd9 100644 --- a/docs-site/content/kagent/1.x/get-started/your-first-agent.md +++ b/docs-site/content/kagent/1.x/get-started/your-first-agent.md @@ -10,10 +10,7 @@ This guide walks you through creating an agent, from applying a Harness and an A ## Before you begin 1. [Install kagent with a WorkerPool provisioned]({{< link path="setup/installation" >}}). -2. Download the kagent CLI. The `--version` flag matches the CLI to the release that these docs cover. - ```bash - curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v{{< reuse "kagent-docs/versions/kagent.md" >}} - ``` +2. {{< reuse "kagent-docs/snippets/install-cli.md" >}} 3. Install [`jq`](https://jqlang.org/download/), to read the Session ID out of the CLI's JSON output. From 88e75e36b92c9335b2a3a4caee8518bb06c21044 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Fri, 30 Oct 2026 16:19:56 -0500 Subject: [PATCH 4/9] rc1 release small updates and removals Signed-off-by: Rachael Graham --- .github/workflows/update-ref-docs.yaml | 41 +-- .../kagent/1.x/agents/agent-harness.md | 2 +- .../content/kagent/1.x/agents/agent-memory.md | 4 +- .../kagent/1.x/agents/context-management.md | 8 +- .../kagent/1.x/agents/declarative-agents.md | 2 +- .../1.x/get-started/your-first-mcp-tool.md | 14 +- .../1.x/operations/tune-agent-substrate.md | 8 +- .../content/kagent/1.x/reference/env-vars.md | 198 ------------ .../kagent/1.x/reference/tools-ecosystem.md | 2 +- .../content/kagent/1.x/setup/installation.md | 1 + scripts/generate-env-docs.py | 290 ------------------ 11 files changed, 19 insertions(+), 551 deletions(-) delete mode 100644 docs-site/content/kagent/1.x/reference/env-vars.md delete mode 100644 scripts/generate-env-docs.py diff --git a/.github/workflows/update-ref-docs.yaml b/.github/workflows/update-ref-docs.yaml index 95b3a18e..d40bef75 100644 --- a/.github/workflows/update-ref-docs.yaml +++ b/.github/workflows/update-ref-docs.yaml @@ -303,9 +303,6 @@ 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 @@ -326,7 +323,6 @@ jobs: KAGENT_CLI_LINK_PREFIX=reference/cli WITH_RUNTIME_IMAGES=true WITH_KMCP=true - WITH_ENV_DOCS=true ;; *) echo "Error: unknown line '$LINE'." @@ -353,8 +349,6 @@ 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" @@ -761,12 +755,6 @@ 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 @@ -1200,31 +1188,6 @@ 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" @@ -1244,7 +1207,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 and environment variable 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 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 }} @@ -1252,8 +1215,6 @@ 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/agents/agent-harness.md b/docs-site/content/kagent/1.x/agents/agent-harness.md index 7faa0db9..0045cb73 100644 --- a/docs-site/content/kagent/1.x/agents/agent-harness.md +++ b/docs-site/content/kagent/1.x/agents/agent-harness.md @@ -395,7 +395,7 @@ When an agent requires an MCP server, the first tool listing or tool call opens ## Check that an agent can run -To check the status of a Harness, AgentTemplate, or Agent, check the status of only the Agent. It reports for all three resources, because neither a Harness nor an AgentTemplate reports a status of its own. For example, the `READY` column in `kubectl get harness` output is always empty. +To check the status of a Harness, AgentTemplate, or Agent, check the status of only the Agent. It reports for all three resources, because neither a Harness nor an AgentTemplate reports a status of its own. An Agent that is not `Ready` states the reason in its conditions. diff --git a/docs-site/content/kagent/1.x/agents/agent-memory.md b/docs-site/content/kagent/1.x/agents/agent-memory.md index fd966945..cb7efe72 100644 --- a/docs-site/content/kagent/1.x/agents/agent-memory.md +++ b/docs-site/content/kagent/1.x/agents/agent-memory.md @@ -106,7 +106,7 @@ spec: Add memory to a Harness that already exists. The examples in these steps use `my-first-harness` in the `kagent` namespace, which you create in [Your first agent]({{< link path="get-started/your-first-agent" >}}). -1. Create a `ModelConfig` for the embedding model in the same namespace as your Harness. The model is an embedding model rather than a chat model. +1. Create a `ModelConfig` for the embedding model in the same namespace as your Harness. To use a ModelConfig from another namespace instead, set `modelConfigRef.namespace`, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). The model is an embedding model rather than a chat model. ```yaml kubectl apply -f - <}} To review other available Harness fields, see [Agent harness]({{< link path="agents/agent-harness" >}}). | Field | Description | | ----- | ----------- | - | `memory.modelConfigRef.name` | The ModelConfig supplying the embedding model, in the Harness's namespace. Required when `memory` is set. | + | `memory.modelConfigRef.name` | The ModelConfig supplying the embedding model. Required when `memory` is set. Because the reference omits `namespace`, kagent looks up the ModelConfig in the Harness's namespace. | | `memory.ttlDays` | How many days a stored memory stays valid. Minimum 1. When omitted, the server applies a default of 15 days. | 3. Create a new Session against an {{< gloss "Agent" >}}Agent{{< /gloss >}} that uses this Harness. Editing the Harness builds a new {{< gloss "Revision" >}}revision{{< /gloss >}}, and an existing Session keeps running the revision it was created from. An agent that was already running does not gain memory until you start a new Session. diff --git a/docs-site/content/kagent/1.x/agents/context-management.md b/docs-site/content/kagent/1.x/agents/context-management.md index bd6af69b..a1ef7c0e 100644 --- a/docs-site/content/kagent/1.x/agents/context-management.md +++ b/docs-site/content/kagent/1.x/agents/context-management.md @@ -37,8 +37,8 @@ spec: # but keep the 10 most recent events uncompacted. tokenThreshold: 24000 eventRetentionSize: 10 - # Optional: name a ModelConfig (in this Harness's namespace) to write the - # summaries. When you omit it, the agent's own model summarizes. + # Optional: name a ModelConfig to write the summaries. Add namespace to use + # a shared ModelConfig. When you omit it, the agent's own model summarizes. summarizer: modelConfigRef: name: summarizer-model-config @@ -67,7 +67,7 @@ The table below lists the compaction fields and their requirements: | `overlapSize` | Optional | The number of already-compacted invocations pulled back into the next sliding window so consecutive summaries overlap. Requires `compactionInterval`. Minimum 0. | | `tokenThreshold` | Optional | The prompt token count at which tail-retention compaction summarizes the history before the next model call. Requires `eventRetentionSize`. Minimum 1. | | `eventRetentionSize` | Optional | The number of most recent events that tail retention keeps uncompacted. Requires `tokenThreshold`. Minimum 1. | -| `summarizer.modelConfigRef` | Optional | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} in the Harness namespace that writes the summaries. When you omit it, the agent's own model summarizes. | +| `summarizer.modelConfigRef` | Optional | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} that writes the summaries, by `name` and optional `namespace`. The namespace defaults to the Harness namespace. A ModelConfig in another namespace must admit the Harness namespace, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). When you omit the field, the agent's own model summarizes. | | `summarizer.promptTemplate` | Optional | Replaces the runtime's default summarization prompt. Must contain `{conversation_history}`, which the runtime replaces with the rendered events. | ## Admission rules @@ -87,7 +87,7 @@ Naming an AgentTemplate's own model in `summarizer.modelConfigRef` is semantical ## Verify the configuration -If the summarizer names a ModelConfig that does not exist in the Harness namespace, the Agent is not ready. The failure surfaces on the Agent as `ResolvedRefs=False`, with a reason such as `resolve summarizer ModelConfig "x": model config "x" not found`. Confirm that the Agent is ready before relying on compaction: +If the summarizer names a ModelConfig that does not exist, the Agent is not ready. The failure surfaces on the Agent as `ResolvedRefs=False`, with a reason such as `resolve summarizer ModelConfig "x": model config "x" not found`. Confirm that the Agent is ready before relying on compaction: ```bash kagent agent get diff --git a/docs-site/content/kagent/1.x/agents/declarative-agents.md b/docs-site/content/kagent/1.x/agents/declarative-agents.md index 61e2c1d0..6f445d0e 100644 --- a/docs-site/content/kagent/1.x/agents/declarative-agents.md +++ b/docs-site/content/kagent/1.x/agents/declarative-agents.md @@ -43,7 +43,7 @@ Compaction offers two strategies. Tail retention summarizes the older history on | Field | Description | | ----- | ----------- | -| `memory.modelConfigRef.name` | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} that supplies the embedding model, in the Harness's namespace. Required when `memory` is set. Setting `memory` gives every agent on this Harness memory that persists across conversations. For how agents store and retrieve it, see [Agent memory]({{< link path="agents/agent-memory" >}}). | +| `memory.modelConfigRef.name` | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} that supplies the embedding model. Required when `memory` is set. To use a ModelConfig outside the Harness's namespace, also set `memory.modelConfigRef.namespace`, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). Setting `memory` gives every agent on this Harness memory that persists across conversations. For how agents store and retrieve it, see [Agent memory]({{< link path="agents/agent-memory" >}}). | | `memory.ttlDays` | How many days a stored memory entry stays valid. Minimum 1. When omitted, the server applies a default of 15 days. | | `compaction.tokenThreshold` | Tail retention. The prompt token count that triggers a summary. Once the prompt passes this count, kagent summarizes the older history before the next model call. Requires `compaction.eventRetentionSize`. | | `compaction.eventRetentionSize` | Tail retention. The number of most recent events that kagent leaves out of that summary, so the latest exchanges reach the model in full. Requires `compaction.tokenThreshold`. | diff --git a/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md b/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md index 90f6c864..b4f0919a 100644 --- a/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md +++ b/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md @@ -26,8 +26,8 @@ kagent records what it discovered on the server's status, so the tool names come Example output: The `ACCEPTED` column reports whether kagent reached the server and read its catalog. No tool can be bound from a server that is not `True`. ```console - NAME PROTOCOL URL ACCEPTED AGE - kagent-tool-server STREAMABLE_HTTP http://kagent-tools.kagent:8084/mcp True 14m + NAME PROTOCOL URL ACCEPTED + kagent-tool-server STREAMABLE_HTTP http://kagent-tools.kagent.svc.cluster.local:8084/mcp True ``` To see the tools that the server offers, read the discovered set from its status. @@ -39,14 +39,6 @@ kagent records what it discovered on the server's status, so the tool names come > [!NOTE] > The built-in server is installed only when the `kagent-tools.enabled` Helm value is `true`, which is the default. If the command does not return any resources, either re-install with that value enabled, or use your own server as described in [Bind your own MCP server](#bind-your-own-mcp-server). - > [!WARNING] - > At v1.0.0-alpha17, an agent's sandbox cannot resolve the short Service name in the built-in server's URL. Every tool call then fails with a `no such host` error. For more information, see [kagent#3315](https://github.com/kagent-dev/kagent/issues/3315). Until a fix ships, run the following command to point the server at the fully qualified Service name. The kagent Helm chart owns the `kagent-tool-server` resource, and a `helm upgrade` reverts the patch, so run the command again after each upgrade. - - ```bash - kubectl patch remotemcpserver kagent-tool-server -n kagent --type=merge \ - -p '{"spec":{"url":"http://kagent-tools.kagent.svc.cluster.local:8084/mcp"}}' - ``` - 2. Re-apply the `my-first-template` AgentTemplate with a `spec.tools` list and a system prompt that tells the model what the tools are for. ```yaml kubectl apply -f - <}}revision{{< /gloss >}} for the Agent that uses the edited template. Every edit produces a new desired revision, and the Agent is current when the latest successful revision matches it. diff --git a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md index f4154e9b..aed273c0 100644 --- a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md +++ b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md @@ -173,7 +173,7 @@ substrateWorkerPool: kagent does not label a pool that you manage outside the chart. Set both keys yourself, and match each value to the pool's name and namespace. ```yaml -apiVersion: ate.dev/v1alpha1 +apiVersion: ate.dev/v1 kind: WorkerPool metadata: name: external-pool @@ -183,6 +183,8 @@ metadata: kagent.dev/worker-pool-namespace: team-a spec: replicas: 2 + sandboxClasses: + - name: gvisor workerImage: "ghcr.io/kagent-dev/substrate/ateom-gvisor:v{{< reuse "kagent-docs/versions/agent-substrate.md" >}}" ``` @@ -311,7 +313,7 @@ One rule governs the change. A pool's class, its Worker image, and the cluster's | `sandboxClass` | The sandbox runtime family for this pool's Workers, and for every ActorTemplate that kagent generates for the pool. Omitting the field selects `gvisor`. | | `workerImage` | The Worker build that provides the runtime. `ateom-gvisor` serves the `gvisor` class and `ateom-microvm` serves `microvm`. A build that does not match its pool's class never registers a Worker. | -2. Confirm that the cluster has a SandboxConfig for this class. kagent names the configuration directly rather than resolving a default, so a `gvisor` pool needs a config named `gvisor-default` and a `microvm` pool needs a config named `microvm`. +2. Confirm that the cluster has a SandboxConfig for this class. The chart names the configuration in the pool's `configRef` rather than relying on the class default, so a `gvisor` pool needs a config named `gvisor-default` and a `microvm` pool needs a config named `microvm`. To use a SandboxConfig with another name, set `substrateWorkerPool.sandboxConfigRef`. ```bash kubectl get sandboxconfigs @@ -337,7 +339,7 @@ One rule governs the change. A pool's class, its Worker image, and the cluster's Each of the three settings fails differently when it disagrees with the others. - A Worker whose pool class does not match an ActorTemplate's class reports `RevisionInvalid` with `unsupported sandbox class ""`, and placement of this Actor fails with `WorkerPoolNotFound`. -- A missing SandboxConfig fails template preparation with `SandboxConfig "" not found`. +- A missing SandboxConfig leaves the pool's class unresolved. The `Resolved` condition under the WorkerPool's `status.sandboxClasses` is `False`, with the reason `SandboxConfigNotFound` for a missing `configRef` or `DefaultConfigNotFound` when no class default exists. - A Worker image built for another class never registers, so the pool reports fewer ready Workers than its replica count. ### Considerations before changing a running pool diff --git a/docs-site/content/kagent/1.x/reference/env-vars.md b/docs-site/content/kagent/1.x/reference/env-vars.md deleted file mode 100644 index 5250432e..00000000 --- a/docs-site/content/kagent/1.x/reference/env-vars.md +++ /dev/null @@ -1,198 +0,0 @@ ---- -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 | -|----------|------|---------|-------------| -| `KUBECONFIG` | String | `(none)` | Kubernetes client configuration file list. When unset, client-go uses its normal in-cluster or user kubeconfig discovery. | -| `OTEL_EXPORTER_OTLP_CERTIFICATE` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_CLIENT_KEY` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_COMPRESSION` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_HEADERS` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | String | `(none)` | SDK histogram aggregation; defaults to base2_exponential_bucket_histogram. | -| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_PROTOCOL` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_TIMEOUT` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_LOGS_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_METRICS_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_PROPAGATORS` | String | `(none)` | SDK propagator selection; defaults to tracecontext without baggage. | -| `OTEL_RESOURCE_ATTRIBUTES` | String | `(none)` | Comma-separated key=value resource attributes. Override file attributes per key; compiler-owned runtime identity remains authoritative. | -| `OTEL_SDK_DISABLED` | String | `(none)` | Disable all SDK telemetry for this process when true. | -| `OTEL_SERVICE_NAME` | String | `(none)` | Override the SDK service name for this process. | -| `OTEL_TRACES_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_TRACES_SAMPLER` | String | `(none)` | SDK trace sampler selection. | -| `OTEL_TRACES_SAMPLER_ARG` | String | `(none)` | SDK trace sampler argument. | - -## 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 | -|----------|------|---------|-------------| -| `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. | -| `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 OpenTelemetry bridge. | -| `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_CERTIFICATE` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_CLIENT_KEY` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_COMPRESSION` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_HEADERS` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | String | `(none)` | SDK histogram aggregation; defaults to base2_exponential_bucket_histogram. | -| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_PROTOCOL` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_TIMEOUT` | String | `(none)` | Generic OTLP SDK override for this process, ahead of file defaults. Timeouts are milliseconds. Native SDK support varies. | -| `OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_COMPRESSION` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | String | `(none)` | Signal-specific SDK override; takes precedence over generic OTLP environment settings and file defaults. Timeouts are milliseconds; HTTP endpoints include the signal path. | -| `OTEL_EXPORTER_PROMETHEUS_HOST` | String | `(none)` | Claude Code telemetry readiness listener host, managed by the harness. | -| `OTEL_EXPORTER_PROMETHEUS_PORT` | String | `(none)` | Claude Code telemetry readiness listener port, managed by the harness. | -| `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | String | `(none)` | Override runtime content capture: SPAN_ONLY enables prompts and responses; NO_CONTENT disables capture. | -| `OTEL_LOGS_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_LOG_ASSISTANT_RESPONSES` | String | `(none)` | Claude Code response capture flag, managed by the harness content-capture configuration. | -| `OTEL_LOG_RAW_API_BODIES` | String | `(none)` | Claude Code raw API body capture flag, managed by the harness captureRawAPIBodies configuration. | -| `OTEL_LOG_TOOL_CONTENT` | String | `(none)` | Claude Code tool-content capture flag, managed by the harness content-capture configuration. | -| `OTEL_LOG_TOOL_DETAILS` | String | `(none)` | Claude Code tool-detail capture flag, managed by the harness content-capture configuration. | -| `OTEL_LOG_USER_PROMPTS` | String | `(none)` | Claude Code prompt capture flag, managed by the harness content-capture configuration. | -| `OTEL_METRICS_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_PROPAGATORS` | String | `(none)` | SDK propagator selection; defaults to tracecontext without baggage. | -| `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS` | String | `(none)` | Python instrumentation entry point names to skip, such as httpx or openai. | -| `OTEL_PYTHON_FASTAPI_EXCLUDED_URLS` | String | `(none)` | Python inbound HTTP paths left untraced; defaults exclude health and agent-card endpoints. | -| `OTEL_RESOURCE_ATTRIBUTES` | String | `(none)` | Comma-separated key=value resource attributes. Override file attributes per key; compiler-owned runtime identity remains authoritative. | -| `OTEL_SDK_DISABLED` | String | `(none)` | Disable all SDK telemetry for this process when true. | -| `OTEL_SEMCONV_STABILITY_OPT_IN` | String | `(none)` | Python semantic convention opt-in; defaults to gen_ai_latest_experimental,http. | -| `OTEL_SERVICE_NAME` | String | `(none)` | Override the SDK service name for this process. | -| `OTEL_TRACES_EXPORTER` | String | `(none)` | Override the file's exporter selection for this process. none disables the signal; an endpoint or file exporter enables OTLP by default. | -| `OTEL_TRACES_SAMPLER` | String | `(none)` | SDK trace sampler selection. | -| `OTEL_TRACES_SAMPLER_ARG` | String | `(none)` | SDK trace sampler argument. | -| `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. | - -## 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. | -| `KUBECONFIG` | String | `(none)` | Kubernetes client configuration file list. 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 93a34ebe..1ecda363 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -271,7 +271,7 @@ The kagent and Codex harnesses both honor this tool list. On the Claude {{< glos ## Reach kagent-tool-server from another namespace -The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references only from its own namespace. Where an AgentTemplate in another namespace binds the server, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. +The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references only from its own namespace by default. An AgentTemplate in another namespace binds the server by setting `mcp.server.namespace` to the release namespace. Until the server admits that namespace, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. ```yaml kagent-tools: diff --git a/docs-site/content/kagent/1.x/setup/installation.md b/docs-site/content/kagent/1.x/setup/installation.md index 60fec734..eaaab916 100644 --- a/docs-site/content/kagent/1.x/setup/installation.md +++ b/docs-site/content/kagent/1.x/setup/installation.md @@ -205,4 +205,5 @@ Verify a namespaced installation in the same way as the default one, and also ch {{< card link=`{{< link path="setup/model-providers" >}}` title="Configure model providers" subtitle="Point kagent at OpenAI, Anthropic, Gemini, or a provider of your own." >}} {{< card link=`{{< link path="operations/operational-considerations" >}}` title="Operational considerations" subtitle="Replace the evaluation defaults for the database, controller replicas, and Worker node pools." >}} {{< card link=`{{< link path="operations/operational-considerations#rotate-the-model-provider-api-key" >}}` title="Rotate the model provider API key" subtitle="Change the API key in the kagent-openai Secret, which Helm owns, without breaking running agents." >}} + {{< card link=`{{< link path="operations/export-to-helm" >}}` title="Export the installation to Helm" subtitle="Write the Helm commands and values that kagent install applies, and manage the installation with Helm." >}} {{< /cards >}} diff --git a/scripts/generate-env-docs.py b/scripts/generate-env-docs.py deleted file mode 100644 index 7fed7867..00000000 --- a/scripts/generate-env-docs.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/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()) From a684dfd92f6ed51b9a935d2fd9756a8ee9f69ae0 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Fri, 30 Oct 2026 16:30:37 -0500 Subject: [PATCH 5/9] conventions Signed-off-by: Rachael Graham --- .../substrate-runtime/standalone-sandboxes.md | 4 ++-- .../kagent/1.x/agents/agent-harness.md | 2 +- .../content/kagent/1.x/agents/agent-memory.md | 4 ++-- .../kagent/1.x/agents/declarative-agents.md | 2 +- .../kagent/1.x/agents/human-in-the-loop.md | 8 +++---- .../kagent/1.x/agents/structured-output.md | 24 +++++++++---------- .../content/kagent/1.x/examples/a2a-agents.md | 8 +++---- .../content/kagent/1.x/examples/skills.md | 13 +++++----- .../1.x/get-started/your-first-mcp-tool.md | 2 +- .../kagent/1.x/observability/launch-ui.md | 16 +++++++------ .../1.x/operations/tune-agent-substrate.md | 4 ++-- .../kagent/1.x/operations/upgrade-from-0x.md | 16 +++++++------ .../kagent/1.x/reference/release-notes/1.0.md | 2 +- .../kagent/1.x/reference/tools-ecosystem.md | 10 ++++---- .../content/kagent/1.x/setup/installation.md | 6 ++--- .../1.x/setup/model-providers/byo-openai.md | 6 ++--- 16 files changed, 65 insertions(+), 62 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index e7520e8e..efd54e96 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md @@ -16,7 +16,7 @@ A Sandbox and a {{< gloss "Session" >}}Session{{< /gloss >}} are separate runtim | An {{< gloss "Agent" >}}Agent{{< /gloss >}} that pairs an AgentTemplate with a Harness | A Session | A2A interactions and tasks | | A SandboxTemplate that defines a tools environment | A Sandbox | Guest process and file operations | -An agent can create a Sandbox of its own through the kagent Model Context Protocol ({{< gloss "MCP" >}}MCP{{< /gloss >}}) server, using the same service that a person calls. The Sandbox's lifetime stays independent of the agent's Session. +An agent can create a Sandbox of its own through the kagent Model Context Protocol ({{< gloss "MCP" >}}MCP{{< /gloss >}}) server, by using the same service that a person calls. The Sandbox's lifetime stays independent of the agent's Session. ## Before you begin @@ -175,7 +175,7 @@ When an agent must inspect a result file, use `read_sandbox_file`. The tool retu Text returns as numbered lines. `offset` sets the first line to read, counting from 1. `limit` sets the number of lines, and defaults to 2000. A page also stops at 32 KiB, whichever bound it reaches first. A line longer than 2000 bytes is truncated to 2000 bytes and marked `[line cut]`. A page that stops before the end of the file reports the offset to continue from. -A PNG, JPEG, or GIF file of at most 10 MiB returns as an image. The `claude` and `codex` Harness runtimes resize those images for their models. A larger image returns a message asking the agent to write a smaller copy in the Sandbox. Every other file type returns a description of the file, not its bytes. +A PNG, JPEG, or GIF file of at most 10 MiB returns as an image. The `claude` and `codex` harnesses resize those images for their models. A larger image returns a message asking the agent to write a smaller copy in the Sandbox. Every other file type returns a description of the file, not its bytes. ## Next steps diff --git a/docs-site/content/kagent/1.x/agents/agent-harness.md b/docs-site/content/kagent/1.x/agents/agent-harness.md index 0045cb73..8226af91 100644 --- a/docs-site/content/kagent/1.x/agents/agent-harness.md +++ b/docs-site/content/kagent/1.x/agents/agent-harness.md @@ -395,7 +395,7 @@ When an agent requires an MCP server, the first tool listing or tool call opens ## Check that an agent can run -To check the status of a Harness, AgentTemplate, or Agent, check the status of only the Agent. It reports for all three resources, because neither a Harness nor an AgentTemplate reports a status of its own. +To check the status of a Harness, AgentTemplate, or Agent, check the status of only the Agent. The Agent's status reports for all three resources, because neither a Harness nor an AgentTemplate reports a status of its own. An Agent that is not `Ready` states the reason in its conditions. diff --git a/docs-site/content/kagent/1.x/agents/agent-memory.md b/docs-site/content/kagent/1.x/agents/agent-memory.md index cb7efe72..3d30cdc8 100644 --- a/docs-site/content/kagent/1.x/agents/agent-memory.md +++ b/docs-site/content/kagent/1.x/agents/agent-memory.md @@ -106,7 +106,7 @@ spec: Add memory to a Harness that already exists. The examples in these steps use `my-first-harness` in the `kagent` namespace, which you create in [Your first agent]({{< link path="get-started/your-first-agent" >}}). -1. Create a `ModelConfig` for the embedding model in the same namespace as your Harness. To use a ModelConfig from another namespace instead, set `modelConfigRef.namespace`, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). The model is an embedding model rather than a chat model. +1. Create a `ModelConfig` for the embedding model in the same namespace as your Harness. The model is an embedding model rather than a chat model. ```yaml kubectl apply -f - <}} To review other available Harness fields, see [Agent harness]({{< link path="agents/agent-harness" >}}). | Field | Description | | ----- | ----------- | - | `memory.modelConfigRef.name` | The ModelConfig supplying the embedding model. Required when `memory` is set. Because the reference omits `namespace`, kagent looks up the ModelConfig in the Harness's namespace. | + | `memory.modelConfigRef.name` | The ModelConfig supplying the embedding model. Required when `memory` is set. The namespace defaults to the Harness's namespace. To use a ModelConfig in another namespace, set `memory.modelConfigRef.namespace`. That ModelConfig must admit the Harness's namespace, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). | | `memory.ttlDays` | How many days a stored memory stays valid. Minimum 1. When omitted, the server applies a default of 15 days. | 3. Create a new Session against an {{< gloss "Agent" >}}Agent{{< /gloss >}} that uses this Harness. Editing the Harness builds a new {{< gloss "Revision" >}}revision{{< /gloss >}}, and an existing Session keeps running the revision it was created from. An agent that was already running does not gain memory until you start a new Session. diff --git a/docs-site/content/kagent/1.x/agents/declarative-agents.md b/docs-site/content/kagent/1.x/agents/declarative-agents.md index 6f445d0e..43d1e5c7 100644 --- a/docs-site/content/kagent/1.x/agents/declarative-agents.md +++ b/docs-site/content/kagent/1.x/agents/declarative-agents.md @@ -43,7 +43,7 @@ Compaction offers two strategies. Tail retention summarizes the older history on | Field | Description | | ----- | ----------- | -| `memory.modelConfigRef.name` | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} that supplies the embedding model. Required when `memory` is set. To use a ModelConfig outside the Harness's namespace, also set `memory.modelConfigRef.namespace`, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). Setting `memory` gives every agent on this Harness memory that persists across conversations. For how agents store and retrieve it, see [Agent memory]({{< link path="agents/agent-memory" >}}). | +| `memory.modelConfigRef.name` | The {{< gloss "ModelConfig" >}}ModelConfig{{< /gloss >}} that supplies the embedding model. Required when `memory` is set. To use a ModelConfig outside the Harness's namespace, also set `memory.modelConfigRef.namespace`. That ModelConfig must admit the Harness's namespace, as described in [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). Setting `memory` gives every agent on this Harness memory that persists across conversations. For how agents store and retrieve it, see [Agent memory]({{< link path="agents/agent-memory" >}}). | | `memory.ttlDays` | How many days a stored memory entry stays valid. Minimum 1. When omitted, the server applies a default of 15 days. | | `compaction.tokenThreshold` | Tail retention. The prompt token count that triggers a summary. Once the prompt passes this count, kagent summarizes the older history before the next model call. Requires `compaction.eventRetentionSize`. | | `compaction.eventRetentionSize` | Tail retention. The number of most recent events that kagent leaves out of that summary, so the latest exchanges reach the model in full. Requires `compaction.tokenThreshold`. | diff --git a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md index c1b13bc2..6ed15512 100644 --- a/docs-site/content/kagent/1.x/agents/human-in-the-loop.md +++ b/docs-site/content/kagent/1.x/agents/human-in-the-loop.md @@ -45,7 +45,7 @@ An agent pauses either to get permission before it acts or to ask a question. A | `tool_approval_request` | The agent wants to call a tool from a binding that sets `requireApproval`. | `tool_approval_response` | | `ask_user_request` | The agent calls the built-in `ask_user` tool because it needs information that only a person has. | `ask_user_response` | -Every pause also adds a plain text part to its status message that states why the agent stopped. A client that never activated the extension receives that text and nothing else. The client can show a person why the agent stopped. The client does not have an `id` to answer with, so the task stays at `INPUT_REQUIRED`. Treat the text as a diagnostic rather than as a fallback, and send the turn again from a client that requests the extension. The `hint` field of an activated payload contains the same string. +Every pause also adds a plain text part to its status message that states why the agent stopped. A client that never activated the extension receives that text and nothing else. The client can show a person why the agent stopped. The client does not have an `id` to answer with, so the task stays at `INPUT_REQUIRED`. Treat the text as a diagnostic, and send the turn again from a client that requests the extension. The `hint` field of an activated payload contains the same string. The `kagent` harness composes this text from the pending calls. The wording depends on what the agent stopped for. @@ -60,7 +60,7 @@ The `codex` and `claude` harnesses compose the text differently. Both pass throu ### Disable questions -When an agent runs unattended, nobody answers its question, so the task waits at `INPUT_REQUIRED`. To stop agents on the `kagent` harness from asking questions, set the `config.runtimeDefaults.builtinTools.disableAskUser` Helm value to `true`. The value removes the `ask_user` tool from every agent in the installation and from each subagent. An AgentTemplate cannot override the value. The value defaults to `false`. +When an agent runs unattended, nobody answers its question, so the task waits at `INPUT_REQUIRED`. To stop agents on the `kagent` harness from asking questions, set the `config.runtimeDefaults.builtinTools.disableAskUser` Helm value to `true`. The value removes the `ask_user` tool from every agent and subagent in the installation, and an AgentTemplate cannot override it. The value defaults to `false`. ```yaml config: @@ -122,7 +122,7 @@ Approval belongs to the binding rather than to the tool name, so one server can > [!NOTE] > kagent 0.x named the tools that needed approval in a `requireApproval` list on the `Agent` resource, which matched tool names across every server. In 1.x, approval is a property of one binding, so the same tool name can pause for one server and run freely for another. -A tool that no binding covers with `requireApproval` runs without a pause. Such tools include every built-in tool, such as file access, shell, and web search, and every Model Context Protocol (MCP) tool on a binding that omits the field. The sandbox contains those calls. For more information, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}). +A tool that no binding covers with `requireApproval` runs without a pause. These tools include every built-in tool, for example file access, shell, and web search, and every Model Context Protocol (MCP) tool on a binding that omits the field. The sandbox contains those calls. For more information, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}). ## Implement a client that supports human approval @@ -185,7 +185,7 @@ A `tool_approval_request` lists the pending calls, each with an `id`, the tool ` } ``` -A rejection reason is optional. The agent receives the reason and can adapt to it rather than fail. +A rejection reason is optional. The agent receives the reason and can adjust its next action. ### Answer a question diff --git a/docs-site/content/kagent/1.x/agents/structured-output.md b/docs-site/content/kagent/1.x/agents/structured-output.md index 500715ad..13675046 100644 --- a/docs-site/content/kagent/1.x/agents/structured-output.md +++ b/docs-site/content/kagent/1.x/agents/structured-output.md @@ -27,7 +27,7 @@ The schema goes in one of two AgentTemplate fields, `spec.outputSchema` or `spec kagent version ``` - If `kagent_version` in the output is not {{< reuse "kagent-docs/versions/kagent.md" >}}, install the CLI for this release. + When `kagent_version` in the output differs from {{< reuse "kagent-docs/versions/kagent.md" >}}, replace the CLI. {{< reuse "kagent-docs/snippets/install-cli.md" >}} @@ -204,6 +204,15 @@ kagent counts depth and nodes through `properties`, `items`, and `anyOf`, and co OpenAI models follow `format` and `pattern` during generation only in strict mode. To constrain generation to the schema, set `outputSchemaStrict` to `true` in the AgentTemplate. +Strict mode accepts fewer schemas. kagent checks the following rules for every provider, so a schema stays valid when you change the provider of the ModelConfig. + +- The root does not use `anyOf`. +- Every node sets `type`, `anyOf`, `enum`, or `$ref`. +- A node that sets `$ref` does not set any other keyword. To add `null`, wrap the reference in `anyOf`, such as `anyOf: [{$ref: "#/$defs/item"}, {type: "null"}]`. +- Every object sets `additionalProperties` to `false` and lists all of its properties in `required`. A map with free-form keys cannot meet this rule. + +To make a property optional in strict mode, list it in `required` and add `null` to its type list. + ```yaml spec: outputSchemaStrict: true @@ -222,7 +231,7 @@ spec: additionalProperties: false ``` -The example schema meets every strict mode rule in this section. +The following fields turn on strict mode, make `due` optional, and constrain both values. | Field | Description | | ----- | ----------- | @@ -233,15 +242,6 @@ The example schema meets every strict mode rule in this section. An AgentTemplate that sets `outputSchemaStrict` to `true` without a schema is rejected when you apply it, with the message `outputSchemaStrict requires outputSchema or outputSchemaFrom`. The runtime validates every answer against the schema in both modes. -Strict mode accepts fewer schemas. kagent checks the following rules for every provider, so a schema stays valid when you change the provider of the ModelConfig. - -- The root does not use `anyOf`. -- Every node sets `type`, `anyOf`, `enum`, or `$ref`. -- A node that sets `$ref` does not set any other keyword. To add `null`, wrap the reference in `anyOf`, such as `anyOf: [{$ref: "#/$defs/item"}, {type: "null"}]`. -- Every object sets `additionalProperties` to `false` and lists all of its properties in `required`. A map with free-form keys cannot meet this rule. - -To make a property optional in strict mode, list it in `required` and add `null` to its type list. - ## Read the answer A successful answer is published as one {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) `DataPart` with the media type `application/json`. The part's metadata includes the SHA-256 digest of the schema that the answer was validated against, under the key `kagent.dev/a2a/output-schema-sha256`. kagent computes the digest after it normalizes the schema, so two schemas that differ only in formatting or key order have the same digest. The Agent's card lists `application/json` as its default output mode, instead of `text`. @@ -288,7 +288,7 @@ processor failed: output_validation_failed: root agent output does not conform t Error: Session task 01a0ec27-157f-7034-bb89-09855f485b2a ended in TASK_STATE_FAILED ``` -Each message follows the `processor failed:` prefix. +In the CLI output, each of the following messages follows the `processor failed:` prefix. | Message | Cause | | ------- | ----- | diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index 62938223..b88b53ac 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -26,7 +26,7 @@ An HTTP request that also sets the `tenant` field must set a value that matches Both forms replace the `/api/a2a///` URL paths that kagent 0.x served over HTTP. -A caller addresses the Agent rather than one conversation. The message's `contextId` selects the Session. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. +A caller addresses the Agent rather than one Session. The message's `contextId` selects the Session. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. If the Agent, the Session, or the task does not exist, an A2A call fails with `TaskNotFound` (JSON-RPC code `-32001`, or gRPC `NOT_FOUND`). Over HTTP, a request for the card of an Agent that does not exist returns `404`. To learn when kagent deletes a Session, see [Expire idle Sessions]({{< link path="operations/operational-considerations#expire-idle-sessions" >}}). @@ -261,7 +261,7 @@ A task callback lets a webhook learn when a task needs input or reaches a final kagent keeps one pending delivery for each callback, and a newer state replaces a delivery that has not yet succeeded. When a task resumes, kagent discards the pending delivery for the state that the task left, so a receiver does not see every state. -The kagent CLI wraps the push notification configuration methods, so the steps that follow use the CLI. The CLI defaults to port 8083, which the prerequisites already forward. +The kagent CLI wraps the push notification configuration methods, so this section uses the CLI. The CLI defaults to port 8083, which the prerequisites already forward. 1. To register a callback before a new task is dispatched, add `--push-url` to the kagent CLI invocation. The command returns immediately with the task ID and callback ID. ```bash @@ -409,9 +409,9 @@ Answering a pause needs the human-in-the-loop extension, which a caller requests ``` > [!IMPORTANT] -> A caller that does not request the extension is still interrupted. The task still stops at `TASK_STATE_INPUT_REQUIRED`, and the question arrives as ordinary text on the status message. However, the metadata does not include a request `id`. Without that `id`, the caller cannot answer the pause, so the task waits until something cancels it. Request the extension on any call to an agent that can pause. +> A caller that does not request the extension is still interrupted. The task stops at `TASK_STATE_INPUT_REQUIRED`, and the question arrives as ordinary text on the status message. However, the metadata does not include a request `id`. Without that `id`, the caller cannot answer the pause, so the task waits until the task is canceled. Request the extension on any call to an agent that can pause. -A tool approval works in the same way, with a different payload, `tool_approval_request` answered by `tool_approval_response`. For the pause kinds, the approval model, and what a nested agent's pause looks like, see [Human in the loop]({{< link path="agents/human-in-the-loop" >}}). +A tool approval works in the same way, but the agent sends `tool_approval_request`, and the caller answers with `tool_approval_response`. For the pause kinds, the approval model, and what a nested agent's pause looks like, see [Human in the loop]({{< link path="agents/human-in-the-loop" >}}). ## Clean up diff --git a/docs-site/content/kagent/1.x/examples/skills.md b/docs-site/content/kagent/1.x/examples/skills.md index 1e7d4fa5..d8960f6f 100644 --- a/docs-site/content/kagent/1.x/examples/skills.md +++ b/docs-site/content/kagent/1.x/examples/skills.md @@ -52,11 +52,10 @@ config: | ----- | ----------- | | `disableFileSearch` | Set to `true` to remove `list_files` and `grep_file` from the root agent and from every subagent. Defaults to `false`. This value replaces `skillTools.fileSearchTools` from earlier releases, with the opposite meaning. | | `disableBash` | Set to `true` to remove the `bash` tool from the root agent and from every subagent. The agent keeps the file tools, and still loads skills. Defaults to `false`. | +| `disableAskUser` | Set to `true` to remove the `ask_user` tool. Defaults to `false`. For more information, see [Disable questions]({{< link path="agents/human-in-the-loop#disable-questions" >}}). | | `bashVenvPath` | The path to a Python virtual environment in the Harness image. The `bash` tool adds the environment's `bin` directory to the start of `PATH`, and sets `VIRTUAL_ENV`. The kagent runtime image does not include Python. To use this value, build a Harness image that includes the environment. Defaults to empty. | -The `builtinTools` Helm values also include `disableAskUser`, which removes the question tool. For more information, see [Disable questions]({{< link path="agents/human-in-the-loop#disable-questions" >}}). - -Attaching a skill also changes the model request. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call `load_skill` before the agent acts on a skill. The model therefore sees each skill before the agent calls any tool. +When you attach a skill, the runtime appends the name and description of each attached skill to the model request, together with an instruction to call `load_skill` before the agent acts on a skill. The model therefore sees each skill before the agent calls any tool. > [!IMPORTANT] > The `bash` tool gives the agent shell access inside its own Actor sandbox, and the sandbox is the boundary that contains the agent. The file tools are not limited to the workspace. They read and write anywhere in the sandbox, including `/skills`, so one skill's instructions can change another skill's files. Review a skill before you attach it, and treat the [egress]({{< link path="about/substrate-runtime/sandboxing" >}}) that you grant the Actor as the reach that the skill has. @@ -112,7 +111,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th EOF ``` - The model reads the `description` to decide whether to load the skill, in the same way that it chooses any other tool. In the `description`, state plainly when the skill applies. The agent reads the instructions in the body only after it calls `load_skill`. + The model reads the `description` to decide whether to load the skill, in the same way as the model decides whether to call a tool. In the `description`, state plainly when the skill applies. The agent reads the instructions in the body only after it calls `load_skill`. 3. Add the script that the instructions call. The script runs in the agent's runtime image, so it uses `bash` rather than Python. ```bash @@ -142,7 +141,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th chmod +x scripts/group.sh ``` -4. Before you publish the script, confirm that it works. The `bash` tool returns a failed command's error to the model, not to you. A broken script therefore produces an unreliable answer, not a failed resource. +4. Before you publish the skill, confirm that the script works. The `bash` tool returns a failed command's error to the model, not to you. A broken script therefore produces an unreliable answer, not a failed resource. ```bash printf 'feat: add checkpoint API\nfix: correct revision digest\ndocs: update install guide\n' > /tmp/commits.txt bash scripts/group.sh /tmp/commits.txt @@ -248,7 +247,7 @@ To give an agent the skill, reference the skill image from the AgentTemplate tha ## Ask the agent to use the skill -Send requests in the Session that you created, and check that the agent runs the skill's script. +Send requests to the Session that you created, and check that the agent runs the skill's script. 1. Send the agent a request that matches the skill's description. ```bash @@ -277,7 +276,7 @@ Send requests in the Session that you created, and check that the agent runs the ## Publish a new version of the skill -A source is immutable. To change a skill, publish new content, and then point the AgentTemplate at the new digest. +A source is immutable. To change a skill, publish new content under a new digest. 1. Edit the skill. diff --git a/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md b/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md index b4f0919a..34a6b7cc 100644 --- a/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md +++ b/docs-site/content/kagent/1.x/get-started/your-first-mcp-tool.md @@ -69,7 +69,7 @@ kagent records what it discovered on the server's status, so the tool names come | Field | Description | | ----- | ----------- | | `mcp.server.kind` | The kind of server resource. `RemoteMCPServer` is the only accepted value. | - | `mcp.server.name` | The server's name. Because the binding omits `namespace`, kagent looks up the server in the AgentTemplate's namespace. | + | `mcp.server.name` | The server's name. kagent looks up the server in the AgentTemplate's namespace. To bind a server in another namespace, see [MCP tools]({{< link path="skills-and-mcp/about-tools#mcp-tools" >}}). | | `mcp.tools` | Optional. The names of the tools to bind, up to 50. An omitted or empty list exposes every tool on the server. An AgentTemplate takes at most 50 bindings in total. | 3. Confirm that kagent compiled a new {{< gloss "Revision" >}}revision{{< /gloss >}} for the Agent that uses the edited template. Every edit produces a new desired revision, and the Agent is current when the latest successful revision matches it. diff --git a/docs-site/content/kagent/1.x/observability/launch-ui.md b/docs-site/content/kagent/1.x/observability/launch-ui.md index 9d7c1480..489bca15 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -61,14 +61,14 @@ The UI service is a `ClusterIP` by default, so it is reachable only from inside {{% /tab %}} {{< /tabs >}} -The dashboard opens on a summary of what the controller has loaded. On a fresh installation, the dashboard counts zero agents and zero conversations. The dashboard already counts the model configuration and the {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) servers that the chart installed. +The dashboard opens on a summary of what the controller has loaded. On a fresh installation, the dashboard counts zero agents and zero conversations. The dashboard also counts the model configuration and the {{< gloss "Model Context Protocol" >}}Model Context Protocol{{< /gloss >}} (MCP) servers that the chart installed. {{< reuse-image-light src="img/kagent-ui-dashboard.png" alt="The kagent dashboard after a fresh installation" caption="Figure: The kagent dashboard after a fresh installation" >}} {{< reuse-image-dark srcDark="img/kagent-ui-dashboard-dark.png" alt="The kagent dashboard after a fresh installation" caption="Figure: The kagent dashboard after a fresh installation" >}} ## Explore the UI -The left menu groups the console by the resource that each page reads, so the page that you want follows from the resource that you are asking about. +The left menu groups the console pages by the resource that each page reads. | Page | Shows | | ---- | ----- | @@ -98,12 +98,14 @@ The UI labels a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} **Snapshot**. To take a checkpoint, select the save icon beside the message box. kagent pins the snapshot that the Session most recently suspended to, and records how far the transcript advanced. To understand how pinning works, and why a turn must be complete first, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). -A checkpoint appears in the transcript as a mark that shows the snapshot name and the following controls. Select the mark to open the record behind it. The record shows the checkpoint's ID, turn, state, and age. +A checkpoint appears in the transcript as a mark that shows the snapshot name and the following controls. - **Fork** starts a new Session from the checkpoint. The new Session replays the conversation through the pinned turn and then continues on its own. For what a fork keeps from its source, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). -- **Rename** replaces the snapshot name that kagent generates on creation from the Session ID and the turn that the checkpoint pins. Clearing the name restores that generated default. +- **Rename** replaces the default snapshot name, which kagent generates from the Session ID and the pinned turn when it creates the checkpoint. Clearing the name restores that generated default. - **Delete** removes the checkpoint and releases the snapshot that it pinned. Agent Substrate is then free to collect that snapshot. +Select the mark to open the record behind it. The record shows the checkpoint's ID, turn, state, and age. + > [!NOTE] > A checkpoint is always taken at the latest turn boundary, so the UI offers checkpoint controls on your own most recent message and not on earlier ones. @@ -156,17 +158,17 @@ Read the page from the top tiles down. | ---- | ----- | | **Worker pools** | {{< gloss "WorkerPool" >}}WorkerPools{{< /gloss >}} that an operator has provisioned. A Harness cannot run an agent until at least one WorkerPool exists. | | **Templates ready** | {{< gloss "ActorTemplate" >}}ActorTemplates{{< /gloss >}} that have compiled and are ready to be instantiated. | -| **Actors running** | Live Actors, each one a Session. | +| **Actors running** | Live Actors. Each Actor runs one Session. | | **Workers busy** | {{< gloss "Worker" >}}Workers{{< /gloss >}} currently assigned to an Actor, against the total provisioned. | | **Requested filters** | The filters this page asked for, not what the API returned. Reads `none` until you set a filter, then `K8s: ; ATE: `, with `any` in place of a filter that you leave empty. | On a fresh installation, the page reports one WorkerPool, zero ActorTemplates, and zero Actors, because an agent does not exist yet. The **Workers busy** tile reads `0/1`, and the Workers table lists the pool's single worker with the pod that backs it. That unassigned worker confirms that capacity is provisioned and waiting for the first agent. The Actors table reads `No actors on this page.` +When you [restrict kagent to specific namespaces]({{< link path="setup/installation#restrict-kagent-to-specific-namespaces" >}}), several kagent installations can share one Agent Substrate. The Substrate page then reads only the watched namespaces and the atespaces whose names kagent derives from those namespaces. Empty filters cover every watched namespace. A filter that specifies any other namespace or atespace fails with a `PermissionDenied` error. An installation that watches every namespace reads the entire Agent Substrate. + > [!NOTE] > When the Substrate page cannot reach the Agent Substrate API, it reports the failure rather than an empty result. A warning above the tables shows the error, and the Actors table reads `This read could not reach ate-api, so there may be actors it did not see.` On a cluster that does have agents, `No actors on this page.` means that the read succeeded and matched nothing, so check the **Kubernetes namespace** and **ATE atespace** filters. -When you [restrict kagent to specific namespaces]({{< link path="setup/installation#restrict-kagent-to-specific-namespaces" >}}), several kagent installations can share one Agent Substrate. The Substrate page then reads only the watched namespaces and the atespaces that kagent names after them. Empty filters cover every watched namespace. A filter that specifies any other namespace or atespace fails with a `PermissionDenied` error. An installation that watches every namespace reads the entire Agent Substrate. - ## Expose the UI outside the cluster Port-forwarding suits local access and stops when you close the terminal. For a deployment that a team reaches, change how the chart publishes the service. Add any of the following values to the `helm upgrade --install kagent` command from [Install kagent]({{< link path="setup/installation#install-kagent" >}}). diff --git a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md index aed273c0..3c625e65 100644 --- a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md +++ b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md @@ -313,7 +313,7 @@ One rule governs the change. A pool's class, its Worker image, and the cluster's | `sandboxClass` | The sandbox runtime family for this pool's Workers, and for every ActorTemplate that kagent generates for the pool. Omitting the field selects `gvisor`. | | `workerImage` | The Worker build that provides the runtime. `ateom-gvisor` serves the `gvisor` class and `ateom-microvm` serves `microvm`. A build that does not match its pool's class never registers a Worker. | -2. Confirm that the cluster has a SandboxConfig for this class. The chart names the configuration in the pool's `configRef` rather than relying on the class default, so a `gvisor` pool needs a config named `gvisor-default` and a `microvm` pool needs a config named `microvm`. To use a SandboxConfig with another name, set `substrateWorkerPool.sandboxConfigRef`. +2. Confirm that the cluster has a SandboxConfig for this class. The chart sets the pool's `configRef` to a fixed SandboxConfig name for each class, `gvisor-default` for a `gvisor` pool and `microvm` for a `microvm` pool. To use another SandboxConfig, set `substrateWorkerPool.sandboxConfigRef`. ```bash kubectl get sandboxconfigs @@ -339,7 +339,7 @@ One rule governs the change. A pool's class, its Worker image, and the cluster's Each of the three settings fails differently when it disagrees with the others. - A Worker whose pool class does not match an ActorTemplate's class reports `RevisionInvalid` with `unsupported sandbox class ""`, and placement of this Actor fails with `WorkerPoolNotFound`. -- A missing SandboxConfig leaves the pool's class unresolved. The `Resolved` condition under the WorkerPool's `status.sandboxClasses` is `False`, with the reason `SandboxConfigNotFound` for a missing `configRef` or `DefaultConfigNotFound` when no class default exists. +- A missing SandboxConfig leaves the pool's class unresolved. The `Resolved` condition under the WorkerPool's `status.sandboxClasses` is `False`. The reason is `SandboxConfigNotFound` when the SandboxConfig that `configRef` references does not exist, or `DefaultConfigNotFound` when the pool sets no `configRef` and the class has no default. - A Worker image built for another class never registers, so the pool reports fewer ready Workers than its replica count. ### Considerations before changing a running pool diff --git a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md index ff4b79f5..da938037 100644 --- a/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md +++ b/docs-site/content/kagent/1.x/operations/upgrade-from-0x.md @@ -14,7 +14,7 @@ Each of the following independent changes rules out `helm upgrade`, so working a | Change | Consequence | | ------ | ----------- | | The database schema is a clean baseline | 1.0 replaces golang-migrate with goose and starts from a single baseline migration. An existing 0.10.x database does not have a migration path to that baseline, and 1.0 refuses to run against a 0.10.x database. | -| The custom resources moved to a new API group | 1.0 serves its resources under `api.kagent.dev`, where 0.10.x serves them under `kagent.dev`. The groups do not share a conversion, so resources stored under the old group are not visible through the new custom resource definitions (CRDs). | +| The custom resources moved to a new API group | 1.0 serves its resources under `api.kagent.dev`, where 0.10.x serves them under `kagent.dev`. kagent does not convert resources between the groups, so resources stored under the old group are not visible through the new custom resource definitions (CRDs). | | The resource model is replaced | 0.10.x's `Agent` is gone. What the 0.10.x `Agent` described is now split between an AgentTemplate and a {{< gloss "Harness" >}}Harness{{< /gloss >}}. A new {{< gloss "Agent" >}}Agent{{< /gloss >}} resource in the `api.kagent.dev` group pairs the two. A conversation is a {{< gloss "Session" >}}Session{{< /gloss >}} that is created against that Agent. The two `Agent` kinds share a name and nothing else. For the model itself, see [Core concepts]({{< link path="about/core-concepts" >}}). | The two releases also cannot run side by side on one cluster. The group rename separates the custom resources, but both charts name their cluster-scoped ClusterRoles after the release, such as `kagent-getter-role`. When both releases use the default `kagent` release name, the 1.0 install collides with the ClusterRoles that 0.10.x owns. @@ -33,7 +33,7 @@ The two releases also cannot run side by side on one cluster. The group rename s source core uses an unsupported migration table. Use a new PostgreSQL database ``` -4. Back up your 0.10.x database. The backup is a restore point for the old installation only. A procedure that loads the backup into 1.0 does not exist. +4. Back up your 0.10.x database. The backup is a restore point for the old installation only. kagent 1.0 cannot load the backup. ```bash pg_dump "postgres://:@:5432/" \ --format=custom \ @@ -52,7 +52,7 @@ Your Kubernetes resources do carry forward, so export them before anything repla -n kagent -o yaml > kagent-0.10-resources.yaml ``` -2. Note the Secrets that your ModelConfigs reference. Secrets are ordinary Kubernetes resources that kagent does not own, so recreate them on the new cluster by whatever means you created them originally. +2. Note the Secrets that your ModelConfigs reference. Secrets are ordinary Kubernetes resources that kagent does not own, so recreate them on the new cluster by the same method that you used originally. ```bash kubectl get modelconfigs -n kagent \ -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.apiKeySecret}{"\n"}{end}' @@ -173,7 +173,7 @@ kagent 1.0 cannot pass a mounted credential or certificate file to an agent. In 1. Before you apply the ModelConfigs, recreate the Secrets that they reference. Otherwise, the ModelConfigs cannot resolve their Secrets. -2. Convert these resources to `v1alpha3`. The export also contains Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. The command does not delete the ModelConfig fields that `v1alpha3` removed, so delete them from the converted file before you apply it. Otherwise, the apply fails. If the converted file includes the `kagent-grafana-mcp` RemoteMCPServer, delete it too, because 1.0 does not deploy the Grafana Model Context Protocol (MCP) server behind it. +2. Convert these resources to `v1alpha3`. The export also contains Agents and other kinds that `v1alpha3` does not serve, so the command keeps only ModelConfig, ModelProviderConfig, and RemoteMCPServer resources. The command sets each `apiVersion`, renames the `apiKeyPassthrough` field, and removes the metadata that the 0.10.x API server set. ```bash yq '.items |= map(select(.kind == "ModelConfig" or .kind == "ModelProviderConfig" or .kind == "RemoteMCPServer")) | .items[].apiVersion = "api.kagent.dev/v1alpha3" @@ -182,7 +182,9 @@ kagent 1.0 cannot pass a mounted credential or certificate file to an agent. In kagent-0.10-resources.yaml > kagent-1.0-resources.yaml ``` -3. Apply the converted resources. +3. Delete the ModelConfig fields that `v1alpha3` removed from the converted file. The conversion command does not delete them, and the apply fails while they remain. If the file includes the `kagent-grafana-mcp` RemoteMCPServer, delete it too, because 1.0 does not deploy the Grafana Model Context Protocol (MCP) server behind it. + +4. Apply the converted resources. ```bash kubectl apply -f kagent-1.0-resources.yaml ``` @@ -208,9 +210,9 @@ A 0.10.x `Agent` described both what the agent does and how it runs. In 1.0, an | `spec.declarative.stream`, `executeCodeBlocks`, `shareTools`, `context` | Nothing. `v1alpha3` does not have an equivalent field. | > [!NOTE] -> Tool bindings changed shape. A 0.10.x tool set `type: McpServer` or `type: Agent` alongside a matching block. A 1.0 `ToolBinding` sets an `mcp` or `subAgent` block and does not set a discriminator, so the block that you set determines the binding's kind. For what each binding does, see [About tools]({{< link path="skills-and-mcp/about-tools" >}}). +> Tool bindings changed shape. A 0.10.x tool set `type: McpServer` or `type: Agent` alongside a matching block. A 1.0 `ToolBinding` contains either an `mcp` block or a `subAgent` block and has no `type` field, so the block that you set determines the binding's kind. For what each binding does, see [About tools]({{< link path="skills-and-mcp/about-tools" >}}). -Write one Harness for each distinct runtime and infrastructure combination that your agents need. Then write one Agent for each 0.10.x Agent, that references the AgentTemplate and the Harness that it pairs. Neither reusable resource references the other, so the Agent makes a template runnable. For the full field reference, see [Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}), and for a worked example, see [Your first agent]({{< link path="get-started/your-first-agent#create-a-harness-an-agenttemplate-and-an-agent" >}}). +Write one Harness for each distinct runtime and infrastructure combination that your agents need. Then write one Agent for each 0.10.x Agent. Each Agent references the AgentTemplate and the Harness that it pairs. Neither reusable resource references the other, so the Agent makes a template runnable. For the full field reference, see [Agent harness]({{< link path="agents/agent-harness#configure-a-harness" >}}), and for a worked example, see [Your first agent]({{< link path="get-started/your-first-agent#create-a-harness-an-agenttemplate-and-an-agent" >}}). ### Resources with no 1.0 equivalent diff --git a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md index 039204c8..20306423 100644 --- a/docs-site/content/kagent/1.x/reference/release-notes/1.0.md +++ b/docs-site/content/kagent/1.x/reference/release-notes/1.0.md @@ -30,7 +30,7 @@ The single 0.10.x `Agent` resource described both what an agent does and how it - **An {{< gloss "Agent" >}}Agent{{< /gloss >}} pairs one AgentTemplate with one Harness.** `spec.template` takes either a reference to an existing AgentTemplate or a complete inline spec. `spec.harnessRef` takes only a reference. The Agent owns readiness. Callers address the Agent, not the AgentTemplate or the Harness. - **A {{< gloss "Session" >}}Session{{< /gloss >}} is one running conversation** with an Agent. Unlike the other three, a Session is not a custom resource. kagent's gRPC API creates each Session, and kagent's database tracks it. -kagent does not pair a Harness and an AgentTemplate implicitly. Neither resource has a field that references the other. An Agent makes a template runnable on a Harness. For the full model, see [Core concepts]({{< link path="about/core-concepts#agent" >}}). +kagent does not pair a Harness and an AgentTemplate implicitly. Neither resource has a field that references the other. An Agent makes an AgentTemplate runnable on a Harness. For the full model, see [Core concepts]({{< link path="about/core-concepts#agent" >}}). The custom resource definition (CRD) API group also moves from `kagent.dev/v1alpha2` to `api.kagent.dev/v1alpha3`. kagent does not convert resources between the two groups. 1.0 no longer bundles kmcp, so it does not install the `mcpservers.kagent.dev` CRD. 1.0 also no longer bundles the Grafana Model Context Protocol (MCP) server, so it does not create the `kagent-grafana-mcp` RemoteMCPServer. The two releases still cannot run side by side on one cluster, because both charts name their ClusterRoles after the release. 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 1ecda363..0cbef686 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -9,7 +9,7 @@ A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation ships with o ## Server that a kagent installation registers -The kagent chart installs the tool server as a subchart, which the `kagent-tools.enabled` Helm value turns on. The subchart creates the [`kagent-tool-server`](#kagent-tool-server) {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace. The server serves tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. kagent does not bind the server to an agent, so an agent reaches the server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. +The kagent chart installs the tool server as a subchart. The `kagent-tools.enabled` Helm value turns the subchart on. The subchart creates the [`kagent-tool-server`](#kagent-tool-server) {{< gloss "RemoteMCPServer" >}}RemoteMCPServer{{< /gloss >}} in the kagent namespace. The server serves tools for Kubernetes, Helm, Istio, Cilium, Argo Rollouts, Prometheus, and Kubescape. kagent does not bind the server to an agent, so an agent reaches the server's tools only through a {{< gloss "Tool binding" >}}tool binding{{< /gloss >}} that you add to its {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. The kagent release pins the `kagent-tool-server` version, which is currently {{< reuse "kagent-docs/versions/kagent-tools.md" >}}. For the full set of pinned component versions, see [Version support]({{< link path="reference/versions#what-a-kagent-release-includes" >}}). @@ -22,14 +22,14 @@ kubectl get remotemcpserver kagent-tool-server -n kagent \ -o jsonpath='{range .status.discoveredTools[*]}{.name}{"\t"}{.description}{"\n"}{end}' ``` -For the same command as the first step of binding one of these tools to an agent, see [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-the-tool-to-your-agenttemplate" >}}). +To see this command used as the first step of binding a tool to an agent, see [Your first MCP tool]({{< link path="get-started/your-first-mcp-tool#bind-the-tool-to-your-agenttemplate" >}}). > [!NOTE] -> `status.discoveredTools` stays empty until the controller completes a discovery pass. `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. To tell the two apart, read the `Accepted` condition's reason, as described in [Turn tool discovery off for a server](#turn-tool-discovery-off-for-a-server). +> `status.discoveredTools` stays empty until the controller completes a discovery pass. `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. To tell a server that opted out of discovery from one that has not completed discovery, read the `Accepted` condition's reason, as described in [Turn tool discovery off for a server](#turn-tool-discovery-off-for-a-server). ### Turn tool discovery off for a server -The controller lists a server's tools with the controller's own credentials. A server that authenticates every caller individually, such as one that expects each agent's own propagated token, does not accept any credential that the controller can present. Discovery against that server fails. Because the listing never succeeds, the RemoteMCPServer stays un-`Accepted` indefinitely, even though agents can reach the server at runtime. +The controller lists a server's tools with the controller's own credentials. A server that authenticates every caller individually, such as one that expects each agent's own propagated token, does not accept any credential that the controller can present. Discovery against that server fails. Because the listing never succeeds, the RemoteMCPServer never reports the `Accepted` condition as `True`, even though agents can reach the server at runtime. 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 their own credentials. @@ -271,7 +271,7 @@ The kagent and Codex harnesses both honor this tool list. On the Claude {{< glos ## Reach kagent-tool-server from another namespace -The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references only from its own namespace by default. An AgentTemplate in another namespace binds the server by setting `mcp.server.namespace` to the release namespace. Until the server admits that namespace, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. +The chart creates the `kagent-tool-server` RemoteMCPServer in the release namespace, and a RemoteMCPServer admits references only from its own namespace by default. An AgentTemplate in another namespace binds the server by setting `mcp.server.namespace` to the release namespace. Until the server admits the AgentTemplate's namespace, the controller cannot resolve the binding, and the Agent that runs that template does not become ready. To admit another namespace, set `allowedNamespaces` on the RemoteMCPServer that the chart creates. ```yaml kagent-tools: diff --git a/docs-site/content/kagent/1.x/setup/installation.md b/docs-site/content/kagent/1.x/setup/installation.md index eaaab916..8d816f31 100644 --- a/docs-site/content/kagent/1.x/setup/installation.md +++ b/docs-site/content/kagent/1.x/setup/installation.md @@ -149,19 +149,19 @@ Before you create an agent, confirm that kagent and Agent Substrate are running, ## Restrict kagent to specific namespaces -By default the controller watches every namespace, and the chart creates cluster-scoped role-based access control (RBAC) resources, which are ClusterRoles and ClusterRoleBindings. To restrict kagent to a set of namespaces, set `global.watchNamespaces` in your Helm values: +By default the controller watches every namespace, and the chart creates ClusterRoles and ClusterRoleBindings for cluster-scoped role-based access control (RBAC). To restrict kagent to a set of namespaces, set `global.watchNamespaces` in your Helm values: ```yaml global: watchNamespaces: [prod-agents, staging-agents] ``` -The chart derives both surfaces from `global.watchNamespaces`, so the surfaces cannot disagree. For that reason, use `global.watchNamespaces` in preference to the per-surface keys. The chart replaces every kagent ClusterRole with a Role and RoleBinding in each listed namespace. The chart also limits the controller's watch to the same list. The chart appends the install namespace to the list automatically, so the release's own resources remain managed. +Use `global.watchNamespaces` in preference to the per-surface keys. The chart derives both the RBAC scope and the controller's watch scope from this one value, so the two scopes cannot disagree. For each listed namespace, the chart replaces every kagent ClusterRole with a Role and RoleBinding and adds the namespace to the watch. The chart also appends the install namespace, so the release's own resources remain managed. The per-surface keys still exist for installations that scope the surfaces independently, and the validation rules bind them: - `rbac.namespaces` takes precedence over `global.watchNamespaces` whenever the key is present. An explicit `rbac.namespaces: []` restores cluster-scoped RBAC. When `rbac.namespaces` is set, it must include the install namespace, or the render fails. -- `config.kubernetes.watchNamespaces` is an explicit watch override. When `rbac.namespaces` is set, a watch entry that references a namespace outside the RBAC scope fails the render, because the controller would loop on `Forbidden` errors at runtime. When `global.watchNamespaces` is set, the chart folds the entries into the watch scope and does not fail the render. In both cases, the watch list replaces the default. When `config.kubernetes.watchNamespaces` is set, the chart does not add the install namespace back. For the controller to keep watching the install namespace, list it explicitly. +- `config.kubernetes.watchNamespaces` is an explicit watch override. When `rbac.namespaces` is set, a watch entry that references a namespace outside the RBAC scope fails the render, because the controller would loop on `Forbidden` errors at runtime. When `global.watchNamespaces` is set, the chart folds the entries into the watch scope and does not fail the render. In both cases, the watch list replaces the default list, so the chart does not add the install namespace. To keep watching the install namespace, list it explicitly. - The `kagent-tool-server` RemoteMCPServer that the chart creates admits references from its own namespace only. An Agent in another watched namespace that binds it does not become ready until you set `allowedNamespaces` on that server. See [Reach kagent-tool-server from another namespace]({{< link path="reference/tools-ecosystem#reach-kagent-tool-server-from-another-namespace" >}}). Verify a namespaced installation in the same way as the default one, and also check that an unwatched namespace stays unmanaged. The `kubectl get agents` and conditions checks from the guide to [create your first agent]({{< link path="get-started/your-first-agent" >}}) apply to each watched namespace. diff --git a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md index 31f13b6a..1c73a44c 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/byo-openai.md @@ -85,9 +85,9 @@ When every agent message fails with a generic `provider API error (status 400)`, ## TLS -A provider on your own network might present a certificate that the agent does not already trust. The `tls` block defines one field, `disableVerify`, which turns off certificate verification entirely when you set it to `true`. The field defaults to `false`. +A provider on your own network might present a Transport Layer Security (TLS) certificate that the agent does not already trust. To skip verification of that certificate, set `tls.disableVerify` to `true`. The field defaults to `false`. -> [!WARNING] +> [!NOTE] > **The `tls` block does not accept a certificate authority (CA) bundle.** An agent running on {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} cannot mount files, so the API server rejects `caCertSecretRef`, `caCertSecretKey`, and `disableSystemCAs` as unknown fields. Use a certificate that chains to a public authority, or terminate Transport Layer Security (TLS) at a gateway that the agent can trust. The following ModelConfig turns off verification for a provider on an internal network. @@ -109,7 +109,7 @@ spec: ## Use the ModelConfig -Reference the ModelConfig by name from an AgentTemplate in the same namespace. +Reference the ModelConfig by name from an AgentTemplate in the same namespace. To use the ModelConfig from another namespace, see [Share a ModelConfig across namespaces]({{< link path="setup/model-providers/about-model-providers#share-a-modelconfig-across-namespaces" >}}). ```yaml spec: From d79c15933043348638187f150358586b463545d0 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Fri, 30 Oct 2026 17:27:35 -0500 Subject: [PATCH 6/9] edits Signed-off-by: Rachael Graham --- .../1.x/about/substrate-runtime/identity.md | 106 +++++++++-- .../1.x/about/substrate-runtime/sandboxing.md | 71 ++++--- .../kagent/1.x/agents/egress-policies.md | 71 ++++++- .../content/kagent/1.x/operations/debug.md | 54 +++--- .../operations/operational-considerations.md | 111 +++++++---- .../kagent/1.x/reference/tools-ecosystem.md | 10 +- .../model-providers/about-model-providers.md | 177 +++++++++++++++++- 7 files changed, 465 insertions(+), 135 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/identity.md b/docs-site/content/kagent/1.x/about/substrate-runtime/identity.md index d1aed138..9503e7ec 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/identity.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/identity.md @@ -1,6 +1,6 @@ --- title: Identity -description: Understand how kagent resolves a caller's identity, scopes a Session to its creator, and how Agent Substrate identifies its own components. +description: Understand how kagent identifies each caller, scopes a Session to its creator, and identifies an agent to the services that it calls. weight: 30 author: kagent.dev aliases: @@ -11,7 +11,9 @@ A {{< reuse "kagent-docs/snippets/name-product.md" >}} installation identifies e - An operator applying a {{< gloss "Harness" >}}Harness{{< /gloss >}} is authenticated by [Kubernetes](#the-kubernetes-plane). - A caller that reaches [kagent's gRPC API](#the-kagent-control-plane) is assigned a principal by kagent, both for a {{< gloss "Session" >}}Session{{< /gloss >}} and for the resources that the API writes. +- A workload with no person signed in can call the API as its [Kubernetes ServiceAccount](#service-account-callers). - An agent's runtime calls the same API with a [token that Agent Substrate issues](#agent-runtime-calls). +- A service that an agent calls can receive an [Actor token](#actor-tokens-for-other-services) that identifies the agent. - The components inside [Agent Substrate](#the-agent-substrate-plane) authenticate each other. ## The Kubernetes plane @@ -34,22 +36,60 @@ The `config.server.auth.mode` Helm value selects which authenticator the control | Mode | How a principal is resolved | | ---- | --------------------------- | -| `insecure` | Every request is admitted. The caller's identity is read from the `X-User-Id` header, or from a `user_id` query parameter that takes precedence over it. A caller that supplies neither is named `admin@kagent.dev`. | -| `trusted-proxy` | The request must carry a bearer token in the `Authorization` header. The identity comes from the claim that `config.server.auth.userIdClaim` names, defaulting to `sub`. A missing custom claim falls back to `sub`, and a request with no identity is rejected. | +| `insecure` | Every request is admitted. The caller's identity is read from the `X-User-Id` header, or from a `user_id` query parameter that takes precedence over it. A caller that supplies neither is named `admin@kagent.dev`. **The caller selects its own principal.** The authenticator verifies neither the header nor the query parameter, and the same authenticator guards the controller's `/mcp` endpoint. Treat the principal on a call as a label that the caller chose, and restrict network access to both endpoints. | +| `trusted-proxy` | The request must carry a bearer token in the `Authorization` header. The identity comes from the claim that `config.server.auth.userIdClaim` names, defaulting to `sub`. A missing custom claim falls back to `sub`, and a request with no identity is rejected. **The authenticator decodes the token without verifying it.** It checks neither the signature nor the expiry, because it assumes that an upstream boundary already validated the credential. A caller that reaches the controller directly can present a token that it wrote itself. Use this mode only where ingress validates every token before forwarding it, and network policy stops anything else from reaching port `8083`. | -Any other value fails controller startup rather than falling back to a default. +Any other value stops the controller from starting. -> [!WARNING] -> **`trusted-proxy` decodes the token without verifying it.** The authenticator reads the bearer token's payload and checks neither its signature nor its expiry, because it assumes that an upstream boundary already validated the credential. A caller that reaches the controller directly can therefore present a token that it wrote itself. This mode is safe only where ingress validates every token before forwarding it, and network policy stops anything else from reaching port `8083`. +Authorization is separate from both modes. For user calls, kagent installs an authorizer that permits every check regardless of the authentication mode, so neither mode constrains what an authenticated user can do. For agent runtime calls, kagent applies a fixed policy that limits each Actor to its own resources. For more information, see [Agent runtime calls](#agent-runtime-calls). -> [!WARNING] -> **`insecure` lets a caller select its own principal.** It verifies neither the header nor the query parameter, and the same authenticator guards the controller's `/mcp` endpoint. Treat the principal on a call as a label that the caller chose, and restrict network access to both endpoints rather than relying on it. +### Service account callers -Authorization is separate from both modes. For user calls, kagent installs an authorizer that permits every check regardless of the authentication mode, so neither mode constrains what an authenticated user can do. For agent runtime calls, kagent applies a fixed policy that limits each Actor to its own resources. For more information, see [Agent runtime calls](#agent-runtime-calls). +Automation that runs with no person signed in, such as a CI pipeline, a scheduled job, or another service in the cluster, needs its own identity to create and use Sessions. Neither user authentication mode gives it a verified one. In `insecure` mode, the workload names itself. In `trusted-proxy` mode, the controller trusts a token that an upstream proxy validated, and does not verify the token itself. Service account authentication lets the workload call the controller as its Kubernetes ServiceAccount, and the controller verifies that identity with Kubernetes. + +Service account authentication is off by default and works alongside either user authentication mode. To turn it on, set `config.server.auth.serviceAccount.authority` in your Helm values. The authority is a name that you choose for the cluster that issues the tokens. The chart then grants the controller permission to create TokenReviews. + +```yaml +config: + server: + auth: + serviceAccount: + authority: prod-cluster +``` + +The workload sends a ServiceAccount token whose audience is `kagent-invocation` as a bearer token in the `Authorization` header. A workload usually mounts the token as a projected volume. To test from a terminal, create a token with `kubectl`, port-forward the controller's port `8083`, and call the API. The `grpcurl` command relies on gRPC reflection, so your installation must set `config.server.grpcReflection=true`. + +```bash +kubectl port-forward -n kagent deployment/kagent-controller 8083:8083 & +TOKEN=$(kubectl create token scheduler -n ci-bots --audience kagent-invocation) +grpcurl -plaintext -H "Authorization: Bearer $TOKEN" -d '{}' \ + localhost:8083 kagent.api.v1.SystemService/GetCurrentUser +``` + +Example output: + +```console +{ + "claims": { + "groups": [ + "system:serviceaccounts", + "system:serviceaccounts:ci-bots", + "system:authenticated" + ], + "username": "system:serviceaccount:ci-bots:scheduler" + } +} +``` + +The controller verifies the token through the Kubernetes TokenReview API of its own cluster. A token that has a `kubernetes.io` claim, or a subject that starts with `system:serviceaccount:`, always goes to this verifier. When verification fails, the controller rejects the call as unauthenticated and does not fall back to the user authentication mode. A ServiceAccount token whose audience is not `kagent-invocation` therefore fails, including a token for the default API server audience. Each controller caches a successful review for up to 30 seconds, so a deleted ServiceAccount or a revoked token keeps working for that interval. + +kagent builds the caller's principal from the authority and the ServiceAccount's UID, and records that principal as an opaque creator ID, such as `a3ViZXJuZXRlczpwcm9kLWNsdXN0ZXI.MTZkYjk1N2Qt...`. The [creator ownership](#creator-ownership) rules then scope the workload's Sessions in the same way as a user's, and the workload gets the same authorization as a user. Rotating the ServiceAccount's token keeps the principal so that the workload keeps its Sessions. Deleting and recreating the ServiceAccount gives it a new UID and a new principal, and the workload loses access to the Sessions that it created. + +When an agent forwards the caller's token to a model or MCP server that sets `tokenPassthrough`, that destination receives the ServiceAccount token. The token keeps its kagent authority until it expires. Set `tokenPassthrough` only on destinations that you trust with that authority. ### Resources that the API writes -kagent's gRPC API reaches the same resources by a second path. The AgentTemplate service creates, updates, and deletes AgentTemplates, and the Harness service creates and deletes Harnesses, both through the kagent controller. The `kagent apply -f` command calls the AgentTemplate service, and any client that reaches the gRPC endpoint can call either service. The controller writes these resources with its own service account rather than the caller's, so Kubernetes RBAC never evaluates the caller. The authenticator above resolves the caller instead. +kagent's gRPC API also writes Harness and AgentTemplate resources. The AgentTemplate service creates, updates, and deletes AgentTemplates, and the Harness service creates and deletes Harnesses, both through the kagent controller. The `kagent apply -f` command calls the AgentTemplate service, and any client that reaches the gRPC endpoint can call either service. The controller writes these resources with its own service account rather than the caller's, so Kubernetes RBAC never evaluates the caller. The controller's authenticator resolves the caller instead, as described in [Controller authentication modes](#controller-authentication-modes). > [!WARNING] > By default, kagent neither authenticates nor authorizes this path. The `insecure` authenticator admits every request, and the authorizer it installs permits every check. Any caller that reaches the gRPC endpoint can author an agent's Harness and AgentTemplate, regardless of their Kubernetes permissions. Do not expose port `8083` outside the cluster. For the identity that this mode gives an anonymous caller, and for the mode that changes it, see [Controller authentication modes](#controller-authentication-modes). @@ -69,9 +109,9 @@ A share lets a Session's owner give another account access to that one conversat Every share is read-only. The holder can read the conversation, but cannot send a message, answer an approval, cancel a task, or retry a turn. kagent enforces the restriction in its services and transports. A shared conversation in the UI does not show controls for those actions. -A client that creates a share can set `CreateSessionShareRequest.ttl` to a positive lifetime, which starts when the share is created. Once that lifetime ends, the token does not grant access, and a token lookup returns not found. The owner still sees the expired share in `ListSessionShares` and can revoke it. +A client that creates a share can set `CreateSessionShareRequest.ttl` to a positive lifetime. The lifetime starts when the share is created. Once that lifetime ends, the token does not grant access, and a token lookup returns not found. The owner still sees the expired share in `ListSessionShares` and can revoke it. -The returned `SessionShare.expiresAt` field records when the token stops granting access. Omit `ttl` and `expiresAt` stays unset. The share then stays valid until it is revoked or the Session is deleted. An operator caps share lifetimes with `config.sessions.shareMaxTtl`. For more information, see [Expire idle Sessions]({{< link path="operations/operational-considerations#expire-idle-sessions" >}}). +The returned `SessionShare.expiresAt` field records when the token stops granting access. When `ttl` is omitted, `expiresAt` stays unset, and the share stays valid until it is revoked or the Session is deleted. An operator caps share lifetimes with `config.sessions.shareMaxTtl`. For more information, see [Expire idle Sessions]({{< link path="operations/operational-considerations#expire-idle-sessions" >}}). A share widens what the holder can reach to what the owner can see, and the underlying record is read as the owner rather than as the visitor. Revoking the share withdraws that access. @@ -87,13 +127,51 @@ The runtime, the Agent Substrate egress gateway, and the controller each take on An authenticated Actor can read its own Agent and use its own task state and memories. The Actor can also read, update, and send messages to its own Session. The Actor cannot create or delete a Session, use a share, reach any other Session, or act with its owner's permissions. That restriction includes other Sessions of the same agent and the same owner. -The controller trusts the issuer in the `config.substrate.actorJwtIssuer` Helm value, which must match the `ateApi.actorJWTIssuer` value of the Agent Substrate chart. Both values default to `https://idp.ate-system.svc`, so change them only together. The controller fetches the issuer's signing keys at startup, and a controller that cannot reach the issuer does not start. +The controller trusts the issuer in the `config.substrate.actorJwtIssuer` Helm value. This value must match the `ateApi.actorJWTIssuer` value of the Agent Substrate chart. Both values default to `https://idp.ate-system.svc`, so change them only together. The controller fetches the issuer's signing keys at startup, and a controller that cannot reach the issuer does not start. Upgrade the controller and the runtime images together. An Actor snapshot from an earlier runtime image still sends an identity header that the controller no longer accepts, so you must recreate that snapshot after an upgrade. +### Actor tokens for other services + +An internal service that an agent calls, such as a large language model (LLM) gateway, an MCP server, or an API, might need to know which agent is calling so that it can authorize or audit the call. The other credentials do not identify the agent. A Secret-backed API key is the same for every agent that uses it, and `tokenPassthrough` forwards the identity of the user who started the Session. An unattended run has no such user. + +An Actor token identifies the calling {{< gloss "Actor" >}}Actor{{< /gloss >}} instead. The token is a short-lived JWT from the same Agent Substrate issuer that signs the tokens in [Agent runtime calls](#agent-runtime-calls), minted for an audience that you name. The egress gateway mints, injects, and refreshes the token, and the token never enters the runtime, so no Secret needs to be stored or rotated. + +Turn the token on for one destination in one of the following places: + +- A ModelConfig, in its `actorJwt` field. For more information, see [Authenticate as the Actor]({{< link path="setup/model-providers/about-model-providers#authenticate-as-the-actor" >}}). +- A RemoteMCPServer, in its `actorJwt` field. For more information, see [Actor token authentication]({{< link path="skills-and-mcp/about-tools#actor-token-authentication" >}}). +- An EgressPolicy destination, in its `authentication.actorJwt` field. For more information, see [Inject the Actor's token]({{< link path="agents/egress-policies#inject-the-actors-token" >}}). + +All three fields accept the same two settings, and only `audiences` is required. + +| Field | Description | +| ----- | ----------- | +| `audiences` | Required. The services that can accept the token, between 1 and 16 unique values. The API server rejects `kagent-runtime`, because that audience is reserved for calls to the controller. | +| `expirationSeconds` | Optional. The token lifetime, between 300 and 3600 seconds. Defaults to 900. | + +The receiving service gets a token that is signed with ES256 and contains the following claims: + +```json +{ + "iss": "https://idp.ate-system.svc", + "sub": "actor/kagent/session-01a18e6c-4454-7597-ba2b-e962d0f53967", + "aud": ["ticket-api"], + "actor.ate.dev": { + "atespace": "kagent", + "actorName": "session-01a18e6c-4454-7597-ba2b-e962d0f53967", + "actorUID": "3d0d0f07-40fb-4493-a7cd-3cb7f2d79c3b" + } +} +``` + +The `actor.ate.dev` claim names the Actor's {{< gloss "Atespace" >}}atespace{{< /gloss >}}, name, and UID. The atespace is the namespace of the Agent or SandboxTemplate. The token also contains the standard `exp`, `nbf`, `iat`, and `jti` claims. Each Session runs as its own Actor, so the Actor's name and UID change with every Session. To authorize a team or an environment, base the decision on the atespace. + +The receiving service must check the issuer, the signature, its own audience, and the time claims. The issuer is the `ateApi.actorJWTIssuer` value of the Agent Substrate chart. The issuer publishes its OpenID Connect discovery document at `/.well-known/openid-configuration` and its signing keys at `/openid/v1/jwks`. When `ateApi.actorJWTIssuer` is set to its default, the service reaches both over TLS inside the cluster, so it must also trust the certificate that the issuer serves. + ## The Agent Substrate plane -{{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} authenticates its own components rather than authenticating end users. Its API server accepts Kubernetes ServiceAccount tokens issued for its audience, and the components that carry traffic to an Actor authenticate each other with mutual Transport Layer Security (mTLS). The [kagent installation guide]({{< link path="setup/installation" >}}) covers creating the certificate authority pools and the JSON Web Token (JWT) authority pool that these identities are issued from, which is a required step that no Helm chart performs. +{{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} authenticates its own components rather than authenticating end users. Its API server accepts Kubernetes ServiceAccount tokens issued for its audience, and the components that carry traffic to an Actor authenticate each other with mutual Transport Layer Security (mTLS). `kagent install` creates the certificate authority pools and the JWT authority pool that these identities are issued from. No Helm chart creates these pools. For an installation that you manage with Helm, see [Export the installation to Helm]({{< link path="operations/export-to-helm" >}}). Each Actor also carries an identity of its own, addressed as its {{< gloss "Atespace" >}}atespace{{< /gloss >}} and name together. [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}) covers how the router uses that identity to reach the right Worker over mTLS. diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md index 9a92c614..dd17c964 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md @@ -33,74 +33,83 @@ flowchart LR A **sandbox class** is the sandbox runtime family that a Worker uses. {{< gloss "Agent Substrate" >}}Agent Substrate{{< /gloss >}} supports two. -- **`gvisor`** (default): Runs the workload against a [gVisor](https://gvisor.dev) user-space kernel, which keeps the workload's system calls from reaching the host kernel. -- **`microvm`**: Runs the workload inside a lightweight virtual machine, which places a hypervisor boundary between the workload and the host. +- **`gvisor`** (default): Runs the workload against a [gVisor](https://gvisor.dev) user-space kernel. The user-space kernel keeps the workload's system calls from reaching the host kernel. +- **`microvm`**: Runs the workload inside a lightweight virtual machine. A hypervisor boundary separates the workload from the host. -A {{< gloss "WorkerPool" >}}WorkerPool{{< /gloss >}}'s `sandboxClass` field sets the sandbox class for its Workers, and defaults to `gvisor`. That value does more than name a runtime. Agent Substrate builds the pool's Worker pods to match it, mounting the virtualization devices that a micro-VM needs and placing those pods on nodes that provide them. +A {{< gloss "WorkerPool" >}}WorkerPool{{< /gloss >}}'s `sandboxClasses` list sets the sandbox class for its Workers. Each entry names a class and, optionally, the SandboxConfig that serves it. The kagent Helm chart creates the default pool with one `gvisor` entry. Each class does more than name a runtime. Agent Substrate builds the pool's Worker pods to match it, mounting the virtualization devices that a micro-VM needs and placing those pods on nodes that provide them. > [!NOTE] -> kagent copies a pool's sandbox class onto every {{< gloss "ActorTemplate" >}}ActorTemplate{{< /gloss >}} that it generates for this pool. Each template then names the SandboxConfig that serves its class. A `gvisor` template names `gvisor-default`, and a `microvm` template names `microvm`. +> kagent copies the class of a pool's first `sandboxClasses` entry onto every {{< gloss "ActorTemplate" >}}ActorTemplate{{< /gloss >}} that it generates for this pool. The pool, not the template, resolves the SandboxConfig for that class. ## Sandbox configuration -A **SandboxConfig** is a cluster-scoped resource that describes the runtime files a sandbox class requires to start. The resource stores none of those files. Instead, it records the location that the node agent fetches each file from, keyed by processor architecture. It also names the pause image, which holds the sandbox's namespaces as its root container. +A **SandboxConfig** is a cluster-scoped resource that describes the runtime files a sandbox class requires to start. The resource stores none of those files. Instead, it records the location that the node agent fetches each file from, keyed by processor architecture. A SandboxConfig stores up to 16 named versions of these files. -Every ActorTemplate must name a SandboxConfig, and Agent Substrate does not supply a name of its own. A template cannot be prepared until the configuration that it names exists in the cluster. Many ActorTemplates can share one SandboxConfig. The runtime version is then pinned in a single place, rather than repeated on every template. +A WorkerPool resolves one SandboxConfig for each entry in its `sandboxClasses` list. An entry that sets `configRef` uses the named configuration. An entry without `configRef` uses the newest SandboxConfig of its class that has the `sandboxconfig.ate.dev/is-class-default: "true"` annotation. The pool reports the result in the `Resolved` condition under `status.sandboxClasses`. Agent Substrate blocks deletion of a SandboxConfig that a pool uses. -A default installation creates one SandboxConfig, named `gvisor-default`. kagent writes this name onto every ActorTemplate that it generates, so each template names it explicitly. The configuration looks like the following: +A default installation creates one SandboxConfig, named `gvisor-default`, and marks it as the `gvisor` class default. The kagent Helm chart also names it in the default pool's `configRef`. The configuration looks like the following: ```yaml -apiVersion: ate.dev/v1alpha1 +apiVersion: ate.dev/v1 kind: SandboxConfig metadata: name: gvisor-default + annotations: + sandboxconfig.ate.dev/is-class-default: "true" spec: sandboxClass: gvisor - pauseImage: registry.k8s.io/pause:3.10.2@sha256: - assets: - amd64: - gvisor: - url: gs://gvisor/releases/release/20260803/x86_64/gvisor.tar.bz2 - sha256: - arm64: - gvisor: - url: gs://gvisor/releases/release/20260803/aarch64/gvisor.tar.bz2 - sha256: + defaultVersion: v6 + versions: + - name: v6 + state: Enabled + assets: + amd64: + gvisor: + url: gs://gvisor/releases/release/20261026.0/x86_64/gvisor.tar.zstd + sha256: + arm64: + gvisor: + url: gs://gvisor/releases/release/20261026.0/aarch64/gvisor.tar.zstd + sha256: ``` +To read the configuration that your own cluster installed, including the pinned digests, run `kubectl get sandboxconfig gvisor-default -o yaml`. + | Field | Description | | ----- | ----------- | -| `sandboxClass` | The sandbox runtime family that this configuration applies to, `gvisor` or `microvm`. An ActorTemplate only uses configurations whose class matches its own, and preparation fails if the two disagree. | -| `pauseImage` | The image for the root sandbox container, which holds the sandbox's namespaces and does not run workload code. It must be pinned to a digest, because the snapshot manifest records it, and changing the image invalidates the snapshots that were taken with it. | -| `assets` | The files that the node agent fetches, keyed first by processor architecture and then by asset name. A `gvisor` class expects one `gvisor` asset, the release archive that the node agent extracts. A `microvm` class expects several, such as `cloud-hypervisor`, `kata-kernel`, and `kata-image`. | -| `assets...sha256` | The lowercase hex digest of the file. The node agent verifies each download against it, and caches the result under a path that includes the digest, so changing the digest fetches the new asset instead of reusing the cached one. To read the configuration that your own cluster installed, including the pinned digests, run `kubectl get sandboxconfig gvisor-default -o yaml`.| +| `sandboxClass` | The sandbox runtime family that this configuration applies to, `gvisor` or `microvm`. Defaults to `gvisor`. A WorkerPool uses only configurations whose class matches its entry. | +| `defaultVersion` | The version that new sandboxes boot with. It must name a version that exists and is not `Disabled`. | +| `versions[].name` | The version's name, a DNS label. | +| `versions[].state` | `Enabled` or `Disabled`. Defaults to `Enabled`. Worker pods keep every enabled version available, so a snapshot that was taken on an earlier version still restores. | +| `versions[].assets` | The files that the node agent fetches, keyed first by processor architecture and then by asset name. A `gvisor` class expects one `gvisor` asset, the release archive that the node agent extracts. A `microvm` class expects `cloud-hypervisor`, `virtiofsd`, `kata-kernel`, and `kata-image`. | +| `versions[].assets...sha256` | The lowercase hex digest of the file. The node agent verifies each download against it, so changing the digest fetches the new asset instead of reusing the cached one. | -## What the sandbox separates +## Sandbox boundaries {#what-the-sandbox-separates} -The sandbox draws a boundary in three places. +The sandbox separates the Actor from the Worker node in the following places. - **Process and kernel**: The Actor's processes run against the sandbox runtime rather than the Worker node's kernel. A system call that the workload makes is handled by {{< gloss "gVisor" >}}gVisor{{< /gloss >}}'s user-space kernel, or by the guest kernel inside a micro-VM, instead of reaching the host directly. - **Filesystem**: The Actor sees the filesystem assembled from its container image, plus whatever durable volume its ActorTemplate declares. Writes to the root filesystem are a layer on top of the image, captured in a `Full` {{< gloss "Snapshot" >}}snapshot{{< /gloss >}} and discarded by a `Data` one. For what each scope keeps, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}). - **Network**: The Actor does not share the Worker pod's network position. The node agent gives the active Actor a private, point-to-point virtual network inside the Worker pod, so reaching the Actor means going through Agent Substrate's own network path rather than connecting to the Worker directly. -## How traffic reaches a sandboxed Actor +## Traffic path to a sandboxed Actor {#how-traffic-reaches-a-sandboxed-actor} -Every Actor is addressed by its {{< gloss "Atespace" >}}atespace{{< /gloss >}} and name, at `..actors.resources.substrate.ate.dev`. Reaching it involves several hops, and each one keeps a sandboxed Actor addressable without exposing the Worker that it happens to be running on. +Every Actor is addressed by its {{< gloss "Atespace" >}}atespace{{< /gloss >}} and name, at `..actors.resources.substrate.ate.dev`. Reaching it involves several hops, and each one keeps a sandboxed Actor addressable without exposing the Worker that runs it. 1. Agent Substrate runs its own Domain Name System (DNS) service that answers queries for this address pattern with the address of the router, rather than any individual Worker. -2. The router reads the Actor name and atespace from the request, asks the Agent Substrate API to resume this Actor and report which Worker it is now assigned to, then selects this Worker as the destination. +2. The router reads the Actor name and atespace from the request, asks the Agent Substrate API to resume this Actor and report which Worker it is now assigned to, and then selects this Worker as the destination. 3. The router connects to a listener on the Worker over mutual Transport Layer Security (mTLS). The listener validates that the caller is the router, and forwards traffic only to the Actor currently assigned to this Worker. Because the router resolves the Worker assignment on every request, an Actor keeps a stable address across suspends, resumes, and moves between Workers. -A kagent user never uses this address. A message sent to a {{< gloss "Session" >}}Session{{< /gloss >}} through the kagent API or the web console reaches the kagent controller, which resolves the Session to its Actor and passes the request to Agent Substrate. Every hop after that stays inside the runtime. +A kagent user never uses this address. A message sent to a {{< gloss "Session" >}}Session{{< /gloss >}} through the kagent API or the web console reaches the kagent controller. The controller resolves the Session to its Actor and passes the request to Agent Substrate. Every hop after that stays inside the runtime. Traffic in the other direction leaves through a separate egress gateway rather than going straight out from the Worker. Routing Actor egress through one gateway provides a single place to apply outbound controls. ## Default network posture -Agent Substrate creates a Kubernetes NetworkPolicy for each WorkerPool, selecting this pool's Worker pods. The policy restricts **ingress** to the Agent Substrate router alone. No other pod in the cluster can open a connection to a Worker, so an Actor is not reachable by anything that bypasses the routing path. +Agent Substrate creates a Kubernetes NetworkPolicy for each WorkerPool that selects the pool's Worker pods. The policy restricts **ingress** to the Agent Substrate router alone. No other pod in the cluster can open a connection to a Worker, so an Actor is not reachable by anything that bypasses the routing path. -That policy governs inbound traffic only. Outbound traffic is governed separately by an egress policy that the gateway enforces on every connection that an Actor opens. That policy is default-deny. An Actor reaches a destination only when a rule allows it, and the gateway denies the connection when no rule does. +The NetworkPolicy governs inbound traffic only. Outbound traffic is governed separately by an egress policy that the gateway enforces on every connection that an Actor opens. The egress policy is default-deny. An Actor reaches a destination only when a rule allows it, and the gateway denies the connection when no rule does. -kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations it allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. To allow a destination that the AgentTemplate does not name, select an EgressPolicy resource on the Agent, as described in [Grant access to a destination]({{< link path="agents/egress-policies#grant-access-to-a-destination" >}}). For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). +kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations that the egress policy allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. To allow a destination that the AgentTemplate does not name, select an EgressPolicy resource on the Agent or SandboxTemplate, as described in [Grant access to a destination]({{< link path="agents/egress-policies#grant-access-to-a-destination" >}}). For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). diff --git a/docs-site/content/kagent/1.x/agents/egress-policies.md b/docs-site/content/kagent/1.x/agents/egress-policies.md index e7516c09..4435e2e1 100644 --- a/docs-site/content/kagent/1.x/agents/egress-policies.md +++ b/docs-site/content/kagent/1.x/agents/egress-policies.md @@ -7,9 +7,9 @@ author: kagent.dev An {{< gloss "Agent" >}}Agent{{< /gloss >}} reaches only the destinations that its AgentTemplate needs, such as its model provider, its Model Context Protocol (MCP) servers, and its skill sources. {{< reuse "kagent-docs/snippets/name-product.md" >}} infers these destinations and compiles them into the agent's [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}). The egress gateway denies every other destination. -To grant an Agent or SandboxTemplate access to destinations beyond the inferred set, you can create an EgressPolicy resource. The Agent or SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. An EgressPolicy only adds access, and cannot remove a destination that kagent inferred from the model or the tools. +To grant an Agent or SandboxTemplate access to destinations beyond the inferred set, create an EgressPolicy resource. The Agent or SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. An EgressPolicy only adds access, and cannot remove a destination that kagent inferred from the model or the tools. -A policy can also reference a credential in a Kubernetes Secret. The gateway adds the credential to each request, so the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. +A policy can also have the gateway add a credential to each request, either a value from a Kubernetes Secret or a token that identifies the calling agent. The credential never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. ## Before you begin @@ -93,7 +93,7 @@ A wildcard can replace the leftmost label of the hostname. For example, `https:/ A redirect to another origin needs its own grant. For example, a package registry often serves downloads from a separate host. An install fails at that host until the policy grants it as well. -The API server rejects a policy or an `egressPolicyRefs` list that exceeds the first two of the following limits. The compiled policy limit also counts the destinations that kagent inferred, so an Agent with many tools reaches it sooner. +The API server rejects an `egressPolicyRefs` list that selects more than 16 policies and a policy that lists more than 64 destinations. The compiled policy limit also counts the destinations that kagent inferred, so an Agent with many tools reaches it sooner. | Limit | Value | | ----- | ----- | @@ -104,9 +104,9 @@ The API server rejects a policy or an `egressPolicyRefs` list that exceeds the f ## Inject a credential -The gateway can add a credential from a Kubernetes Secret to each request for an origin. The agent sends the header with a placeholder value, and the gateway replaces this value as the request passes through. The Secret value is not stored in the agent's revision and never reaches the sandbox. +The gateway can add a credential to each request for an origin. The agent sends the header with a placeholder value, and the gateway replaces this value as the request passes through. The credential is not stored in the agent's revision and never reaches the sandbox. The following steps inject a value from a Kubernetes Secret. To inject a token that identifies the calling agent, see [Inject the Actor's token](#inject-the-actors-token). -1. Create the Secret in the namespace of the EgressPolicy. +1. Create the Secret in the namespace of the EgressPolicy. To rotate the credential later, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. ```bash kubectl create secret generic ticket-auth -n kagent \ --from-literal=token= @@ -139,7 +139,7 @@ The gateway can add a credential from a Kubernetes Secret to each request for an | `origin` | The destination. An origin with a credential must be exact, so the API server rejects a wildcard origin. On a cleartext `http` origin, the credential is readable on the path between the gateway and the destination. Use an `http` origin with a credential only for an in-cluster service whose traffic a service mesh encrypts. | | `authentication.header` | The request header that carries the credential, such as `Authorization` or `X-API-Key`. The API server rejects the headers that control routing and framing, such as `Host` and `Content-Length`, and the `Proxy-Authorization`, `Proxy-Authenticate`, and `Cookie` headers. | | `authentication.prefix` | Optional. Text that the gateway puts before the Secret value. The prefix is literal. Include the trailing space, as in `"Bearer "`. When the prefix is omitted or empty, the header contains only the Secret value. | - | `authentication.secretKeyRef` | The Secret and key that store the credential. Both `name` and `key` are required, and the reference cannot be optional. | + | `authentication.secretKeyRef` | The Secret and key that store the credential. Both `name` and `key` are required, and the reference cannot be optional. A destination sets exactly one of `secretKeyRef` and `actorJwt`. For the token alternative, see [Inject the Actor's token](#inject-the-actors-token). | 3. Select the policy. A merge patch replaces the whole `egressPolicyRefs` list, so the command also keeps `node-dependencies`. Include every policy that the resource needs. {{< tabs >}} @@ -157,13 +157,66 @@ The gateway can add a credential from a Kubernetes Secret to each request for an {{% /tab %}} {{< /tabs >}} -4. In the script or software development kit (SDK) that sends the request, send the header with a placeholder value. The gateway replaces a header that the request already sends, and it does not add a header that is missing. Selecting a policy does not configure a tool or set an environment variable. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). +4. In the script or SDK that sends the request, send the header with a placeholder value. The gateway replaces a header that the request already sends, and it does not add a header that is missing. Selecting a policy does not configure a tool or set an environment variable. The client must also trust the egress gateway's certificate authority. kagent provides this certificate authority to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). ```bash curl -H 'Authorization: Bearer kagent-credential-injected' \ https://tickets.example.com/tickets ``` -To rotate the credential, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. +### Inject the Actor's token + +A Secret-backed credential is the same for every agent that selects the policy, so the destination cannot tell the agents apart. When an internal API must authorize or audit each agent, inject a token that identifies the calling {{< gloss "Actor" >}}Actor{{< /gloss >}} instead. This method covers the requests that an agent's own code sends, such as a `curl` command or a script that a skill runs. For model and MCP server calls, set `actorJwt` on the ModelConfig or the RemoteMCPServer, as described in [Actor tokens for other services]({{< link path="about/substrate-runtime/identity#actor-tokens-for-other-services" >}}). + +The gateway mints, injects, and refreshes the token, so the policy needs no Secret, and you have no credential to rotate. + +1. Create an EgressPolicy whose `authentication` block sets `actorJwt`. The gateway replaces the header with the prefix followed by a newly issued Actor token. + ```yaml + kubectl apply -f - <}} For the token's claims and how the destination validates it, see [Actor tokens for other services]({{< link path="about/substrate-runtime/identity#actor-tokens-for-other-services" >}}). + + | Field | Description | + | ----- | ----------- | + | `authentication.actorJwt.audiences` | The services that can accept the token, between 1 and 16 values. The destination checks that its own audience is in the list. `kagent-runtime` is reserved and rejected. | + | `authentication.actorJwt.expirationSeconds` | Optional. The token lifetime, between 300 and 3600 seconds. Defaults to 900. | + +2. Select the policy. A merge patch replaces the whole `egressPolicyRefs` list, so include every policy that the resource needs. + {{< tabs >}} + {{% tab name="Agent" %}} + ```bash + kubectl patch agent my-first-agent -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' + ``` + {{% /tab %}} + {{% tab name="Standalone sandbox" %}} + ```bash + kubectl patch sandboxtemplate scratch -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' + ``` + {{% /tab %}} + {{< /tabs >}} + +3. In the client that sends the request, send the header with a placeholder value. + ```bash + curl -H 'Authorization: Bearer kagent-credential-injected' \ + https://tickets.example.com/tickets + ``` ## Troubleshoot a grant @@ -191,7 +244,7 @@ In a standalone Sandbox, a destination that no grant allows does not resolve. A ## Access control -An EgressPolicy is namespace-scoped, and kagent does not check who may select it. A user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. For more information about how Kubernetes role-based access control (RBAC) governs who can write these resources, see [The Kubernetes plane]({{< link path="about/substrate-runtime/identity#the-kubernetes-plane" >}}). +An EgressPolicy is namespace-scoped, and kagent does not check who may select it. A user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in that namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. For more information about how Kubernetes role-based access control (RBAC) governs who can write these resources, see [The Kubernetes plane]({{< link path="about/substrate-runtime/identity#the-kubernetes-plane" >}}). To control who can grant access, give the permission to create and edit EgressPolicy resources only to administrators. The kagent controller needs only read access to EgressPolicy resources. diff --git a/docs-site/content/kagent/1.x/operations/debug.md b/docs-site/content/kagent/1.x/operations/debug.md index ebd3259d..8e8921b1 100644 --- a/docs-site/content/kagent/1.x/operations/debug.md +++ b/docs-site/content/kagent/1.x/operations/debug.md @@ -5,24 +5,11 @@ weight: 30 author: kagent.dev --- -Most {{< reuse "kagent-docs/snippets/name-product.md" >}} failures surface in one place: the readiness conditions on the {{< gloss "Agent" >}}Agent{{< /gloss >}}. This page explains where to read those conditions, how to tell the real failure from the ones that follow it, and how to work back from the symptoms that report nothing useful on their own. +Most {{< reuse "kagent-docs/snippets/name-product.md" >}} failures surface in the readiness conditions on the {{< gloss "Agent" >}}Agent{{< /gloss >}}. This page explains where to read those conditions, how to tell the real failure from the ones that follow it, and how to work back from the symptoms that report nothing useful on their own. ## Where kagent reports status -Two habits from Kubernetes lead nowhere here, so it is worth knowing what reports nothing before you start. - -A {{< gloss "Harness" >}}Harness{{< /gloss >}} has a `READY` column, but because nothing writes the Harness status, the column is always empty. - -```bash -kubectl get harness -n kagent -``` -Example output: -```console -NAME READY AGE -kagent 30h -``` - -An {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}} has **no status at all**, and its list view has no status column, so a healthy template and a broken one look identical. +A {{< gloss "Harness" >}}Harness{{< /gloss >}} and an {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}} have **no status at all**, and their list views have no status column, so a healthy resource and a broken one look identical. ```bash kubectl get agenttemplate -n kagent @@ -85,9 +72,15 @@ Compatible=False Blocked: blocked by ResolvedRefs Ready=False Blocked: blocked by ResolvedRefs ``` -The Agent's status reports two revision fields that answer a different question. `desiredRevision` is the {{< gloss "Revision" >}}revision{{< /gloss >}} that the current spec should produce, and `latestSuccessfulRevision` is the newest revision that finished preparing. A missing `latestSuccessfulRevision` means that the Agent was never ready, and two differing values mean that the newest edit is not yet prepared. +A failed golden snapshot is not final. When Agent Substrate fails to build the golden snapshot, kagent deletes the failed ActorTemplate and rebuilds it after 15 seconds, 1 minute, and 4 minutes, and then every 10 minutes indefinitely. While a rebuild waits, `Ready` is `False` with the reason `ActorTemplateRetrying` and a message such as `golden build failed (GoldenActorCrashed), retry 1 at