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 91dd69d7..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 @@ -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,19 @@ 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" >}} - ``` +2. {{< reuse "kagent-docs/snippets/install-cli.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 +77,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 +90,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 +116,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 +127,9 @@ 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`. 4. Move a file in, run against it, and read the result back out. ```bash @@ -139,17 +138,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: @@ -166,15 +165,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. -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. +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, 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 6c463c72..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,10 +54,23 @@ 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. An AgentTemplate cannot override the value. 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. @@ -101,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 @@ -123,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" >}}). @@ -143,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. @@ -160,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 { @@ -172,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 { @@ -193,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 @@ -224,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 4223f161..500715ad 100644 --- a/docs-site/content/kagent/1.x/agents/structured-output.md +++ b/docs-site/content/kagent/1.x/agents/structured-output.md @@ -12,8 +12,9 @@ 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). | ## Before you begin @@ -26,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 @@ -113,7 +113,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 - <`, 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 `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. -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. +A Gemini model answers by calling the `set_model_response` tool of the Go Agent Development Kit (ADK), 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,9 +200,51 @@ 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. Azure AI 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`. +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. @@ -221,31 +269,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 @@ -280,17 +330,22 @@ 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. | +| `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..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,29 +326,29 @@ 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. ## 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. +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 e5267624..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,13 +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. | -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. +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. > [!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. @@ -85,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' --- @@ -110,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 @@ -140,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 @@ -160,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 @@ -188,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 @@ -244,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 \ @@ -264,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. @@ -281,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 @@ -336,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}' @@ -364,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 \ @@ -378,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. @@ -413,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/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. 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..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. +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 23c00325..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. 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 1117c82d..93a34ebe 100644 --- a/docs-site/content/kagent/1.x/reference/tools-ecosystem.md +++ b/docs-site/content/kagent/1.x/reference/tools-ecosystem.md @@ -1,65 +1,60 @@ --- 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. 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 | | ---- | ----------- | @@ -102,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 | | ---- | ----------- | @@ -122,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 | | ---- | ----------- | @@ -137,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 | | ---- | ----------- | @@ -202,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 | | ---- | ----------- | @@ -214,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 | | ---- | ----------- | @@ -231,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 | | ---- | ----------- | @@ -241,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: @@ -268,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" >}}). @@ -276,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: @@ -290,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. @@ -302,50 +297,22 @@ 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. +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 | | ------ | -------------- | ----------- | | [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. +> 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 37ec24bf..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 @@ -101,7 +103,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 @@ -139,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 ``` @@ -148,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 ``` @@ -185,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 08736842..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. 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 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