From af209e712c930c2ad443c4985e74ca8a07f875be Mon Sep 17 00:00:00 2001 From: Rachael-Graham <29711887+Rachael-Graham@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:06:53 +0000 Subject: [PATCH 01/10] docs: Update kagent 1.x reference docs at v1.0.0-alpha16 Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../kagent-docs/versions/agent-substrate.md | 2 +- .../assets/kagent-docs/versions/kagent.md | 2 +- .../versions/runtime-image-claude.md | 2 +- .../versions/runtime-image-codex.md | 2 +- .../kagent-docs/versions/runtime-image.md | 2 +- .../content/kagent/1.x/reference/api-ref.md | 64 +++++++++++++++++-- .../content/kagent/1.x/reference/helm.md | 5 +- 7 files changed, 68 insertions(+), 11 deletions(-) diff --git a/docs-site/assets/kagent-docs/versions/agent-substrate.md b/docs-site/assets/kagent-docs/versions/agent-substrate.md index fb4164f2..83afda0c 100644 --- a/docs-site/assets/kagent-docs/versions/agent-substrate.md +++ b/docs-site/assets/kagent-docs/versions/agent-substrate.md @@ -1 +1 @@ -{{< version include-if="0.x" >}}0.0.9{{< /version >}}{{< version include-if="1.x" >}}0.5.0-alpha2{{< /version >}} \ No newline at end of file +{{< version include-if="0.x" >}}0.0.9{{< /version >}}{{< version include-if="1.x" >}}0.6.0-alpha3{{< /version >}} \ No newline at end of file diff --git a/docs-site/assets/kagent-docs/versions/kagent.md b/docs-site/assets/kagent-docs/versions/kagent.md index 9ac70af4..cb57443d 100644 --- a/docs-site/assets/kagent-docs/versions/kagent.md +++ b/docs-site/assets/kagent-docs/versions/kagent.md @@ -1 +1 @@ -{{< version include-if="0.x" >}}0.10.3{{< /version >}}{{< version include-if="1.x" >}}1.0.0-alpha14{{< /version >}} \ No newline at end of file +{{< version include-if="0.x" >}}0.10.3{{< /version >}}{{< version include-if="1.x" >}}1.0.0-alpha16{{< /version >}} \ No newline at end of file diff --git a/docs-site/assets/kagent-docs/versions/runtime-image-claude.md b/docs-site/assets/kagent-docs/versions/runtime-image-claude.md index 7bf60767..cd7cfed7 100644 --- a/docs-site/assets/kagent-docs/versions/runtime-image-claude.md +++ b/docs-site/assets/kagent-docs/versions/runtime-image-claude.md @@ -1 +1 @@ -ghcr.io/kagent-dev/kagent/claude-harness@sha256:b33b9e3ab90bcba19f0ec719a249b35321cf7508f90cfe7fe1af550f09c9310c \ No newline at end of file +ghcr.io/kagent-dev/kagent/claude-harness@sha256:6b947b7f13938d49f0e9f8af39621a95e861d5576aad1d934141a438eebabb32 \ No newline at end of file diff --git a/docs-site/assets/kagent-docs/versions/runtime-image-codex.md b/docs-site/assets/kagent-docs/versions/runtime-image-codex.md index 3cddc826..d0ab3fb0 100644 --- a/docs-site/assets/kagent-docs/versions/runtime-image-codex.md +++ b/docs-site/assets/kagent-docs/versions/runtime-image-codex.md @@ -1 +1 @@ -ghcr.io/kagent-dev/kagent/codex-harness@sha256:69f02eff6f78a5f78724f6e5ec856119856ea4d479e0f06455115e45ad2de6fc \ No newline at end of file +ghcr.io/kagent-dev/kagent/codex-harness@sha256:852fd192f5e2f2e76f17d018de0a1c1eeaeecac72c69b132fbdfb98516ae85cd \ No newline at end of file diff --git a/docs-site/assets/kagent-docs/versions/runtime-image.md b/docs-site/assets/kagent-docs/versions/runtime-image.md index 2b4df44a..1859f9a2 100644 --- a/docs-site/assets/kagent-docs/versions/runtime-image.md +++ b/docs-site/assets/kagent-docs/versions/runtime-image.md @@ -1 +1 @@ -ghcr.io/kagent-dev/kagent/golang-adk@sha256:eb1b0c7f635f31d464cff7cf212275f2642057a629ed20b39661e9f644f71f21 \ No newline at end of file +ghcr.io/kagent-dev/kagent/golang-adk@sha256:2240ef5d46b71b4850d072d40f34a5f778e9342cc97df34a635b594540ec62af \ No newline at end of file diff --git a/docs-site/content/kagent/1.x/reference/api-ref.md b/docs-site/content/kagent/1.x/reference/api-ref.md index c889f05c..98ebf0ec 100644 --- a/docs-site/content/kagent/1.x/reference/api-ref.md +++ b/docs-site/content/kagent/1.x/reference/api-ref.md @@ -15,6 +15,7 @@ Package v1alpha3 contains API Schema definitions for the api.kagent.dev v1alpha3 ### Resource Types - [Agent](#agent) - [AgentTemplate](#agenttemplate) +- [EgressPolicy](#egresspolicy) - [Harness](#harness) - [ModelConfig](#modelconfig) - [ModelProviderConfig](#modelproviderconfig) @@ -48,6 +49,7 @@ _Appears in:_ | --- | --- | --- | --- | | `template` _[AgentTemplateSource](#agenttemplatesource)_ | Template selects the portable behavior this Agent exposes. | | ExactlyOneOf: [inline ref]
**Required**
| | `harnessRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#localobjectreference-v1-core)_ | HarnessRef selects the platform-managed runtime configuration that executes
the behavior. | | **Required**
| +| `egressPolicyRefs` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#localobjectreference-v1-core) array_ | EgressPolicyRefs grants additional access to the whole runtime, including
shared subagents. Policies resolve in this Agent's namespace. Integration
access inferred from models and tools is always included. Changes update
existing Sessions without a new invocation or runtime restart. | | MaxItems: 16
| #### AgentStatus @@ -63,7 +65,7 @@ _Appears in:_ | `latestSuccessfulRevision` _string_ | | | MinLength: 1
| | `workloadImage` _string_ | WorkloadImage is the digest-pinned image selected for the desired revision.
Existing Sessions may still use an older revision and image. | | Pattern: `^[^[:space:]@]+@sha256:[a-f0-9]\{64\}$`
| | `warnings` _string array_ | Warnings reports non-blocking compatibility decisions made while compiling
this Agent. | | MaxItems: 100
| -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | | | MaxItems: 4
| +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | Conditions report runtime readiness and independent egress grant admission.
EgressPoliciesApplied is False for EgressPolicyNotFound, InvalidEgressPolicy,
or EgressGrantConflict; Applied means all grants are admitted. Grant issues
do not affect Ready. | | MaxItems: 5
| #### AgentTemplate @@ -323,6 +325,56 @@ _Appears in:_ | `name` _string_ | | | MinLength: 1
**Required**
| | `key` _string_ | | | MinLength: 1
**Required**
| +#### EgressAuthentication + +EgressAuthentication replaces a request header with a prefix and Secret value. + +_Appears in:_ +- [EgressDestination](#egressdestination) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `header` _string_ | Header is the request header carrying the credential, for example
Authorization or X-API-Key. | | MaxLength: 256
Pattern: `^[!#$%&'*+.^_\|~0-9A-Za-z-]+$`
**Required**
| +| `prefix` _string_ | Prefix is prepended verbatim to the Secret value, for example "Bearer ".
Include any separating space. Omitted or empty means no prefix. | | MaxLength: 256
Pattern: `^[^\x00-\x08\x0a-\x1f\x7f]*$`
| +| `secretKeyRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#secretkeyselector-v1-core)_ | SecretKeyRef selects a required key in the EgressPolicy's namespace. | | **Required**
| + +#### EgressDestination + +EgressDestination grants access to one HTTP(S) origin, including all its paths. + +_Appears in:_ +- [EgressPolicySpec](#egresspolicyspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `origin` _string_ | Origin is a scheme, DNS hostname, and optional port. A wildcard replaces
exactly one leftmost DNS label, for example https://*.example.com. | | MaxLength: 270
Pattern: `^https?://(\*\.([a-z0-9]([-a-z0-9]\{0,61\}[a-z0-9])?\.)+\|([a-z0-9]([-a-z0-9]\{0,61\}[a-z0-9])?\.)*)[a-z]([-a-z0-9]\{0,61\}[a-z0-9])?(:([1-9][0-9]\{0,3\}\|[1-5][0-9]\{4\}\|6[0-4][0-9]\{3\}\|65[0-4][0-9]\{2\}\|655[0-2][0-9]\|6553[0-5]))?$`
**Required**
| +| `authentication` _[EgressAuthentication](#egressauthentication)_ | Authentication replaces a placeholder header at the gateway. Requests
must send the header; its credential value is never supplied to the runtime. | | | + +#### EgressPolicy + +EgressPolicy is a reusable grant of additional network access. Workloads in its +namespace select it explicitly; changes update their existing runtime Actors. + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `api.kagent.dev/v1alpha3` | | | +| `kind` _string_ | `EgressPolicy` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[EgressPolicySpec](#egresspolicyspec)_ | | | **Required**
| + +#### EgressPolicySpec + +EgressPolicySpec grants additional HTTP(S) access to workloads in its namespace. + +_Appears in:_ +- [EgressPolicy](#egresspolicy) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `destinations` _[EgressDestination](#egressdestination) array_ | Destinations are additional origins the workload may reach. | | MaxItems: 64
MinItems: 1
**Required**
| + #### FoundryAPIFormat _Underlying type:_ _string_ @@ -631,7 +683,7 @@ _Appears in:_ | `stream` _boolean_ | Stream controls LLM response streaming for the kagent harness. Set to false
for model endpoints that do not support streaming. Defaults to true. | true | | | `apiKeySecret` _string_ | The name of the secret that contains the API key. Must be a reference to the name of a secret in the same namespace as the referencing ModelConfig.
For the SAPAICore provider, the secret must contain two keys: "client_id" and "client_secret"
(the OAuth2 client credentials for SAP AI Core). The apiKeySecretKey field is not used for SAPAICore. | | | | `apiKeySecretKey` _string_ | The key in the secret that contains the API key.
Not used for the SAPAICore provider (which always reads "client_id" and "client_secret" from the secret). | | | -| `apiKeyPassthrough` _boolean_ | APIKeyPassthrough enables forwarding the Bearer token from incoming A2A requests
directly to the LLM provider as the API key. This is useful for organizations
with federated identity that want to avoid separate secret management.
Mutually exclusive with apiKeySecret. | | | +| `tokenPassthrough` _boolean_ | TokenPassthrough enables forwarding the bearer token from incoming A2A requests
directly to the LLM provider as the API key. This is useful for organizations
with federated identity that want to avoid separate secret management.
Mutually exclusive with apiKeySecret. | | | | `defaultHeaders` _object (keys:string, values:string)_ | | | | | `provider` _[ModelProvider](#modelprovider)_ | The provider of the model | OpenAI | Enum: [Anthropic OpenAI AzureOpenAI Ollama Gemini GeminiVertexAI AnthropicVertexAI Bedrock SAPAICore Foundry Mistral]
| | `openAI` _[OpenAIConfig](#openaiconfig)_ | OpenAI-specific configuration | | AtMostOneOf: [maxTokens maxCompletionTokens]
| @@ -854,7 +906,8 @@ _Appears in:_ | `description` _string_ | | | **Required**
| | `protocol` _[RemoteMCPServerProtocol](#remotemcpserverprotocol)_ | | STREAMABLE_HTTP | Enum: [SSE STREAMABLE_HTTP]
| | `url` _string_ | | | MinLength: 1
**Required**
| -| `headersFrom` _[ValueRef](#valueref) array_ | | | ExactlyOneOf: [value valueFrom]
| +| `headersFrom` _[ValueRef](#valueref) array_ | HeadersFrom supplies request headers. Secret-backed headers are injected by
the egress gateway and cannot use routing, framing, hop-by-hop,
proxy-authentication, or Cookie headers. At most 64 headers may be supplied. | | ExactlyOneOf: [value valueFrom]
MaxItems: 64
items:XValidation: \{!has(self.valueFrom) \|\| self.valueFrom.type != 'Secret' \|\| !(self.name.lowerAscii() in ['host', 'connection', 'content-length', 'transfer-encoding', 'te', 'trailer', 'upgrade', 'keep-alive', 'proxy-authorization', 'proxy-authenticate', 'cookie']) this header cannot carry an egress credential .name <nil>\}
| +| `tokenPassthrough` _boolean_ | TokenPassthrough forwards the bearer token from the incoming A2A request
to this server as its Authorization header. Requests without a caller
token fail. Controller tool discovery has no caller token, so servers that
require one should disable discovery with the kagent.dev/discovery label. | | | | `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#duration-v1-meta)_ | | 30s | | | `sseReadTimeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#duration-v1-meta)_ | | | | | `terminateOnClose` _boolean_ | | true | | @@ -960,6 +1013,7 @@ _Appears in:_ | `workload` _[SandboxTemplateWorkload](#sandboxtemplateworkload)_ | Workload selects the immutable runtime image. | | **Required**
| | `env` _[RuntimeEnvVar](#runtimeenvvar) array_ | Env supplies runtime environment defaults.
Credential references do not grant permission to read the referenced Secret. | | MaxItems: 100
| | `substrate` _[RuntimeSubstratePolicy](#runtimesubstratepolicy)_ | Substrate configures compute placement and snapshot storage. References are
resolved in this template's namespace. | | **Required**
| +| `egressPolicyRefs` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#localobjectreference-v1-core) array_ | EgressPolicyRefs grants additional access in this template's namespace.
Changes update the access of existing sandboxes without restarting them. | | MaxItems: 16
| #### SandboxTemplateStatus @@ -971,7 +1025,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `observedGeneration` _integer_ | ObservedGeneration is the generation considered by preparation. | | | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | Conditions describe whether a prepared revision is available. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | Conditions report runtime readiness and independent egress grant admission.
EgressPoliciesApplied is False for EgressPolicyNotFound, InvalidEgressPolicy,
or EgressGrantConflict; Applied means all grants are admitted. Grant issues
do not affect Ready. | | | #### SandboxTemplateWorkload @@ -1050,7 +1104,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | | | **Required**
| +| `name` _string_ | | | MaxLength: 256
**Required**
| | `value` _string_ | | | | | `valueFrom` _[ValueSource](#valuesource)_ | | | | diff --git a/docs-site/content/kagent/1.x/reference/helm.md b/docs-site/content/kagent/1.x/reference/helm.md index c95ba1e1..94785f7d 100644 --- a/docs-site/content/kagent/1.x/reference/helm.md +++ b/docs-site/content/kagent/1.x/reference/helm.md @@ -11,7 +11,7 @@ A Helm chart for kagent, built with Google ADK | Repository | Name | Version | |------------|------|---------| -| file://../tools/grafana-mcp | grafana-mcp | 1.0.0-alpha14 | +| file://../tools/grafana-mcp | grafana-mcp | 1.0.0-alpha16 | | https://oauth2-proxy.github.io/manifests | oauth2-proxy | ~10.7.0 | | oci://ghcr.io/kagent-dev/tools/helm | kagent-tools | 0.3.0 | @@ -34,7 +34,9 @@ A Helm chart for kagent, built with Google ADK | config.push.allowHttp | bool | `false` | | | config.push.allowPrivateNetworks | bool | `false` | | | config.push.recoveryInterval | string | `"60s"` | | +| config.runtimeDefaults.cpu | string | `"1"` | | | config.runtimeDefaults.logging.level | string | `"info"` | | +| config.runtimeDefaults.memory | string | `"1Gi"` | | | config.runtimeDefaults.skillTools.bashVenvPath | string | `""` | | | config.runtimeDefaults.skillTools.disableBash | bool | `false` | | | config.runtimeDefaults.skillTools.fileSearchTools | bool | `false` | | @@ -111,6 +113,7 @@ A Helm chart for kagent, built with Google ADK | database.postgres.readWriteConnectionStringSecretRef | object | `{"key":"readWriteConnectionString","name":"kagent-postgres-readwrite"}` | Secret holding the read/write login's connection string, used for every runtime query. Required. Secret changes require restarting the controller. | | extraObjects | list | [] | Additional arbitrary Kubernetes manifests to deploy alongside the chart. Each list entry is rendered through `tpl`, so values may reference the release context (e.g. `{{ include "kagent.fullname" . }}`, `{{ .Release.Namespace }}`). Both map and multi-line string entries are supported. Use this to manage resources such as ExternalSecret, HTTPRoute, or NetworkPolicy within the same chart lifecycle without maintaining a separate chart. To use, replace the empty list below with your manifests, e.g.: extraObjects: - apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: '`{{ include "kagent.fullname" . }}`-openai' namespace: '`{{ .Release.Namespace }}`' spec: secretStoreRef: name: aws-secretsmanager kind: ClusterSecretStore target: name: kagent-openai data: - secretKey: OPENAI_API_KEY remoteRef: key: prod/kagent/openai property: api_key | | fullnameOverride | string | `""` | | +| global.clusterDomain | string | `"cluster.local"` | Kubernetes DNS domain used in runtime-facing service URLs. | | global.imagePullPolicy | string | `""` | Fallback imagePullPolicy where neither a component nor the top-level imagePullPolicy sets one. | | global.imagePullSecrets | list | `[]` | Pull secrets for every pod. The chart merges this list (union) into each pod's own imagePullSecrets, so a local secret is never removed. Coverage matches imageRegistry above. | | global.imageRegistry | string | `""` | Registry that overrides every per-image registry when set. This is the air-gap mirror knob: one value redirects all images. Repository and tag stay per-image. For per-image control, leave this unset and set the per-image registry keys. Covers this chart's own images: controller, sandbox guest, ui, grafana-mcp, substrateWorkerPool.workerImage, and built-in Harness images (both packaged defaults and config.harnessImages overrides). The vendored subcharts kagent-tools adopts it when its pinned version bumps. | From f9e58c677f0323d4ac7f7b440206013755fe8a52 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Mon, 19 Oct 2026 15:11:58 -0500 Subject: [PATCH 02/10] alpha16 init Signed-off-by: Rachael Graham --- docs-site/content/kagent/1.x/agents/agent-harness.md | 2 +- docs-site/content/kagent/1.x/operations/upgrade-from-0x.md | 7 ++++--- .../content/kagent/1.x/reference/release-notes/1.0.md | 2 +- .../1.x/setup/model-providers/about-model-providers.md | 4 ++-- .../kagent/1.x/setup/model-providers/azure-ai-foundry.md | 2 +- .../kagent/1.x/setup/model-providers/azure-openai.md | 2 +- .../content/kagent/1.x/setup/model-providers/ollama.md | 4 ++-- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs-site/content/kagent/1.x/agents/agent-harness.md b/docs-site/content/kagent/1.x/agents/agent-harness.md index 93f7ed98..4b02a063 100644 --- a/docs-site/content/kagent/1.x/agents/agent-harness.md +++ b/docs-site/content/kagent/1.x/agents/agent-harness.md @@ -292,7 +292,7 @@ Some supported combinations still carry restrictions. | `claude` with `Anthropic` | Does not accept any `anthropic` setting beyond `baseUrl`. | | `claude` with `Bedrock` | Does not accept any `bedrock` setting beyond `region`. | | `Bedrock` on any harness | The Secret must hold an `AWS_BEARER_TOKEN_BEDROCK` key. A Secret of IAM access keys is rejected with `cannot use gateway header injection`, because IAM signs each request locally. | -| `codex` and `claude` with any provider | Neither harness accepts a ModelConfig that sets `defaultHeaders`, `tls`, or `apiKeyPassthrough`. | +| `codex` and `claude` with any provider | Neither harness accepts a ModelConfig that sets `defaultHeaders`, `tls`, or `tokenPassthrough`. | ## Tool and skill support 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 c90418ff..7d5af087 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 @@ -161,13 +161,14 @@ The export splits into three groups: resources that need only an `apiVersion` ch ### Resources that carry forward -ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets. `v1alpha3` adds fields that 0.10.x did not serve, so an exported resource applies unchanged apart from its `apiVersion`. +ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets except one. The ModelConfig field `apiKeyPassthrough` is renamed to `tokenPassthrough` in `v1alpha3`, and its behavior is unchanged. `v1alpha3` also adds fields that 0.10.x did not serve, so an exported resource needs no other change. 1. Recreate the Secrets that your ModelConfigs name before you apply them, or the ModelConfigs resolve to nothing. -2. Change the `apiVersion` and apply the resources unchanged. +2. Change the `apiVersion`, rename `apiKeyPassthrough`, and apply the resources. ```bash - sed 's|^apiVersion: kagent.dev/v1alpha2$|apiVersion: api.kagent.dev/v1alpha3|' \ + sed -e 's|^apiVersion: kagent.dev/v1alpha2$|apiVersion: api.kagent.dev/v1alpha3|' \ + -e 's|^\( *\)apiKeyPassthrough:|\1tokenPassthrough:|' \ kagent-0.10-resources.yaml > kagent-1.0-resources.yaml ``` 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 1bdd3155..23c00325 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 @@ -46,7 +46,7 @@ Six resource kinds are removed or replaced and three carry forward. The distinct | `SandboxAgent` | Removed, and no replacement is needed. Every 1.0 agent runs sandboxed by default. | | `MCPServer` | Removed, because 1.0 no longer bundles kmcp or its controller. Run the MCP server outside of kagent, and set its URL on a RemoteMCPServer. The `kagent mcp` commands are removed with it. | | `AgentHarness` | Removed, with no equivalent. It provisioned OpenClaw and Hermes coding-agent sandboxes with Slack and Telegram channels. | -| `ModelConfig`, `ModelProviderConfig`, `RemoteMCPServer` | Carried forward. `v1alpha3` keeps every field that `v1alpha2` sets, so an exported resource applies unchanged apart from its `apiVersion`. | +| `ModelConfig`, `ModelProviderConfig`, `RemoteMCPServer` | Carried forward. `v1alpha3` keeps every field that `v1alpha2` sets except one, so an exported resource needs a new `apiVersion` and one field rename. The ModelConfig field `apiKeyPassthrough` is now `tokenPassthrough`, the same name that RemoteMCPServer uses for the same behavior. | > [!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. diff --git a/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md b/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md index 7f2db1e5..3f1bbd2f 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md @@ -24,7 +24,7 @@ Credentials come from a Kubernetes Secret in the same namespace as the ModelConf ### Forward the caller's token -Set `apiKeyPassthrough: true` to send the bearer token from the incoming agent-to-agent (A2A) request to the provider as the API key, rather than reading a key from a Secret. A ModelConfig cannot set both `apiKeyPassthrough` and `apiKeySecret`. The setting is not available for the `Gemini`, `GeminiVertexAI`, or `AnthropicVertexAI` providers, or on a `codex` or `claude` Harness. +Set `tokenPassthrough: true` to send the bearer token from the incoming agent-to-agent (A2A) request to the provider as the API key, rather than reading a key from a Secret. A ModelConfig cannot set both `tokenPassthrough` and `apiKeySecret`. The setting is not available for the `Gemini`, `GeminiVertexAI`, or `AnthropicVertexAI` providers, or on a `codex` or `claude` Harness. +kagent merges the two sources when it applies the policy to an Actor. The inferred destinations always apply. A policy can only add destinations, and an inferred credential takes precedence over a credential from a policy for the same origin and header. For how to grant a destination, see [Egress policies]({{< link path="agents/egress-policies" >}}). An agent also needs a route to the OpenTelemetry collector, or its traces never leave the sandbox and the kagent UI shows nothing for this Session. kagent adds the collector host for agents on every harness, `byo` included. A `byo` image that exports to a telemetry backend of its own, through a Harness `spec.env` entry, has no route to that backend, because the AgentTemplate never named it. For more information, see [Harness coverage]({{< link path="observability/tracing#harness-coverage" >}}). @@ -80,7 +80,7 @@ An agent also needs a route to the OpenTelemetry collector, or its traces never The gateway caches each Actor's policy rather than fetching it per request, so policy changes are eventually consistent. The cache holds an entry for 10 seconds by default, so a policy that is created, updated, or deleted reaches new requests within that window. Deleting a policy produces denials rather than an absence of enforcement, because the gateway caches the absence too. > [!IMPORTANT] -> A Session pins the revision it was created from, so editing an AgentTemplate does not move an existing Session onto a new allowlist. A Session keeps the destinations that it started with for however long it runs. To pick up a changed set of destinations, create a new Session. +> A Session pins the revision it was created from, so editing an AgentTemplate does not move an existing Session onto a new set of inferred destinations. To pick up a changed model or tool destination, create a new Session. EgressPolicy grants are the exception. kagent applies a change to an Agent's `egressPolicyRefs`, or to a selected EgressPolicy, to the running and suspended Actors of existing Sessions as well. ## Diagnose a denied request @@ -104,13 +104,13 @@ A `503` means that the gateway could not reach the control plane or a credential An allowed request is logged the same way, with its status, the `http.host` and `http.path` it matched on, and the `substrate.connect.authority` address the Actor originally dialed. The log is therefore the quickest way to see what an agent actually reaches. -The compiled policy is not readable through `kubectl` or `kagent`, so treat the AgentTemplate as the source of truth for what an agent is meant to reach. A destination that is missing from the log is missing from the AgentTemplate. +The compiled policy is not readable through `kubectl` or `kagent`, so treat the AgentTemplate and the Agent's selected EgressPolicy resources as the source of truth for what an agent is meant to reach. A destination that is missing from the log is missing from both. To check whether kagent admitted the Agent's EgressPolicy grants, read the Agent's `EgressPoliciesApplied` condition. For the reasons that this condition reports, see [Troubleshoot a grant]({{< link path="agents/egress-policies#troubleshoot-a-grant" >}}). ## Credential injection A model provider or an MCP server usually requires an API key or token. The agent sends a placeholder header in place of the real value, and the gateway replaces this header with the credential as the request passes through. The key never enters the agent's environment or its filesystem. -kagent sets this up from two resources. Every ModelConfig that holds an `apiKeySecret` becomes a credential on the rule for that model's host, and every RemoteMCPServer whose `headersFrom` reads a Secret becomes a credential on the rule for its host. kagent attaches the credential to the destination's own rule rather than adding a rule for it, and it refuses to compile a credential whose host is not already an allowed destination. +kagent sets this up from three resources. Every ModelConfig that holds an `apiKeySecret` becomes a credential on the rule for that model's host, and every RemoteMCPServer whose `headersFrom` reads a Secret becomes a credential on the rule for its host. Every EgressPolicy destination with an `authentication` block becomes a credential on the rule for its origin. kagent attaches the credential to the destination's own rule rather than adding a rule for it, and it refuses to compile a credential whose host is not already an allowed destination. A credential is declared on a rule as a `replace_headers` entry. The entry names the header to replace, an optional prefix such as `Bearer `, and either a reference that a registered credential provider resolves or a JWT that Agent Substrate issues to assert the Actor's identity. The gateway replaces the header only when the request already sends it. A request that omits the placeholder header is forwarded unchanged, without the credential, rather than being denied. diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md index 9da0012d..9a7977eb 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md @@ -103,6 +103,4 @@ Agent Substrate creates a Kubernetes NetworkPolicy for each WorkerPool, selectin That policy governs inbound traffic only. Outbound traffic is governed separately by an egress policy that the gateway enforces on every connection that an Actor opens. That policy is default-deny. An Actor reaches a destination only when a rule allows it, and the gateway denies the connection when no rule does. -kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations it allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. You author neither policy. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). - - +kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations it allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. To allow a destination that the AgentTemplate does not name, the Agent selects an EgressPolicy resource. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index 9b898fee..4442728a 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 @@ -83,6 +83,7 @@ A SandboxTemplate is a namespaced `api.kagent.dev/v1alpha3` resource that prepar | `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. | | `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. Without this field, a Sandbox has no 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. @@ -97,7 +98,7 @@ Editing a template prepares a new revision. A Sandbox that already exists keeps ## Run a command > [!WARNING] -> **The gateway denies every outbound connection from a Sandbox.** kagent compiles an empty [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}) as it creates a Sandbox, and the gateway rejects any destination that the policy does not name, so a command that fetches a package, clones a repository, or calls an API fails. The SandboxTemplate schema does not define a destination field, so no configuration opens one. Move what a command needs into the Sandbox with `kagent sandbox upload`. +> **By default, the gateway denies every outbound connection from a Sandbox.** kagent compiles an empty [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}) for a Sandbox whose template selects no EgressPolicy resources, so a command that fetches a package, clones a repository, or calls an API fails. To allow a destination, select an EgressPolicy in the SandboxTemplate's `spec.egressPolicyRefs` field, as described in [Grant access to a standalone sandbox]({{< link path="agents/egress-policies#grant-access-to-a-standalone-sandbox" >}}). 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. diff --git a/docs-site/content/kagent/1.x/agents/egress-policies.md b/docs-site/content/kagent/1.x/agents/egress-policies.md new file mode 100644 index 00000000..96e6216f --- /dev/null +++ b/docs-site/content/kagent/1.x/agents/egress-policies.md @@ -0,0 +1,217 @@ +--- +title: Egress policies +description: Grant an Agent or a standalone sandbox access to HTTP and HTTPS destinations beyond its model and tools, and inject a credential into requests to those destinations. +weight: 55 +author: kagent.dev +--- + +An {{< gloss "Agent" >}}Agent{{< /gloss >}} reaches only the destinations that its AgentTemplate needs, such as its model provider, its MCP servers, and its skill sources. {{< reuse "kagent-docs/snippets/name-product.md" >}} infers these destinations and compiles them into the agent's [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}). The egress gateway denies every other destination. + +An `EgressPolicy` resource grants access to more destinations. An Agent or a SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. A policy can also hold a credential, which the gateway adds to each request so that the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. + +A grant only adds access. A policy cannot remove a destination that kagent inferred from the model or the tools. An Agent that selects no policies has no access beyond the inferred destinations, and a standalone sandbox that selects no policies has no outbound access at all. + +## Before you begin + +1. [Create your first agent]({{< link path="get-started/your-first-agent" >}}), so that you have the `my-first-agent` Agent in the `kagent` namespace. + +2. Check your kagent CLI version. The steps on this page need the {{< reuse "kagent-docs/versions/kagent.md" >}} CLI. + ```bash + kagent version + ``` + +## Grant access to a destination + +Create an EgressPolicy that lists the destinations, and then select the policy on the Agent. This example grants access to the npm registry and the Node.js download site. + +1. Create the EgressPolicy in the namespace of the Agent. An Agent can select only policies in its own namespace. + ```yaml + kubectl apply -f - <}} For the complete schema, see the [API reference]({{< link path="reference/api-ref#egresspolicy" >}}). + + | Field | Description | + | ----- | ----------- | + | `destinations` | The destinations to grant, from 1 to 64 entries. | + | `destinations[].origin` | A scheme, a DNS hostname, and an optional port, such as `https://registry.npmjs.org` or `https://search.example.com:8443`. The grant covers every path on this origin. | + | `destinations[].authentication` | Optional. A credential for the gateway to add to each request. For more information, see [Inject a credential](#inject-a-credential). | + +2. Select the policy on the Agent. + ```bash + kubectl patch agent my-first-agent -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' + ``` + +3. Check that kagent admitted the grant. The `EgressPoliciesApplied` condition reports `True` with the reason `Applied` when every selected policy is admitted. + ```bash + kubectl get agent my-first-agent -n kagent \ + -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' + ``` + + Example output: + ```console + True Applied All requested egress grants are admitted + ``` + +The grant reaches existing {{< gloss "Session" >}}Sessions{{< /gloss >}} as well as new ones. kagent updates the policy of every running and suspended Actor that the Agent owns, without restarting the Actor. The update is asynchronous, so a request that the agent sends immediately after the change can still use the previous policy. + +A grant applies to the whole Actor. Subagents that share the Agent's Actor get the same access, because they run in one sandbox and kagent cannot isolate their network access from each other. The AgentTemplate and the Harness cannot select a policy, so two Agents that share an AgentTemplate can have different access. + +## Origin format + +An origin names exactly one scheme, host, and port. When the port is omitted, kagent uses `443` for `https` and `80` for `http`. The API server rejects an origin that contains a path, a query, a fragment, user information, or an IP address. Origins on protocols other than HTTP and HTTPS are not supported. + +A wildcard can replace the leftmost label of the hostname. For example, `https://*.example.com` matches `api.example.com`, but it matches neither `example.com` nor `a.b.example.com`. The origins `*` and `https://*.com` are rejected. + +A redirect to another origin needs its own grant. For example, a package registry often serves downloads from a separate host, and an install fails at that host until the policy names it as well. + +The following limits apply to each Agent or SandboxTemplate. + +| Limit | Value | +| ----- | ----- | +| Selected policies | 16 | +| Destinations in one policy | 64 | +| Rules in the compiled policy, including the inferred destinations | 256 | +| Credential headers for one origin | 16 | + +## Inject a credential + +The gateway can add a credential from a Kubernetes Secret to each request for an origin. The agent sends the header with a placeholder value, and the gateway replaces this value as the request passes through. The Secret value is not stored in the agent's revision and never reaches the sandbox. + +1. Create the Secret in the namespace of the EgressPolicy. + ```bash + kubectl create secret generic ticket-auth -n kagent \ + --from-literal=token= + ``` + +2. Create an EgressPolicy with an `authentication` block for the destination. + ```yaml + kubectl apply -f - <}} + + | Field | Description | + | ----- | ----------- | + | `origin` | The destination. An origin with a credential must be exact, so a wildcard origin is rejected. | + | `authentication.header` | The request header that carries the credential, such as `Authorization` or `X-API-Key`. Routing, framing, hop-by-hop, proxy-authentication, and `Cookie` headers are rejected. | + | `authentication.prefix` | Optional. Text that the gateway puts before the Secret value. The prefix is literal, so include the trailing space, as in `"Bearer "`. When the prefix is omitted or empty, the header carries the Secret value alone. | + | `authentication.secretKeyRef` | The Secret and key that hold the credential. Both `name` and `key` are required, and the reference cannot be optional. | + +3. Select the policy on the Agent. The `egressPolicyRefs` list replaces the existing list, so include every policy that the Agent needs. + ```bash + kubectl patch agent my-first-agent -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' + ``` + +The gateway replaces a header that the request already sends, and it does not add a header that is missing. A script or an SDK in the agent must therefore send the header with a placeholder value. + +```bash +curl -H 'Authorization: Bearer kagent-credential-injected' \ + https://tickets.example.com/tickets +``` + +Selecting a policy does not configure a tool or set an environment variable for you. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). + +To rotate the credential, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. + +A credential on a cleartext `http` origin is readable on the path between the gateway and the destination. Use an `http` origin with a credential only for an in-cluster service whose traffic a service mesh encrypts. + +## Grant access to a standalone sandbox + +A standalone Sandbox has no outbound access by default. To grant access, select the policy on the SandboxTemplate that the Sandbox is created from. + +1. Add the policy to the SandboxTemplate's `spec.egressPolicyRefs` field. This example uses the `scratch` template from [Standalone sandboxes]({{< link path="about/substrate-runtime/standalone-sandboxes#create-a-sandboxtemplate" >}}). + ```bash + kubectl patch sandboxtemplate scratch -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' + ``` + +2. Check that kagent admitted the grant. + ```bash + kubectl get sandboxtemplate scratch -n kagent \ + -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' + ``` + +Existing Sandboxes from this template receive the change without a restart. + +## Troubleshoot a grant + +The `EgressPoliciesApplied` condition on the Agent or SandboxTemplate reports whether kagent admitted the grants. This condition is independent of `Ready`, so an Agent with a missing or invalid policy still reports `Ready` and still serves Sessions with its inferred destinations. + +The following reasons can appear on the condition. The condition message names the affected policies. + +| Status | Reason | Meaning | +| ------ | ------ | ------- | +| `True` | `Applied` | Every selected policy is admitted. | +| `False` | `EgressPolicyNotFound` | A selected policy does not exist, or is being deleted. | +| `False` | `InvalidEgressPolicy` | A policy, a Secret reference, or the `egressPolicyRefs` list is invalid. For example, the Secret or its key does not exist. | +| `False` | `EgressGrantConflict` | A new grant conflicts with an existing one. kagent holds the new grants and keeps the earlier grants that are still selected and compatible. | + +A problem with one policy does not block the others. A policy that is missing or invalid contributes no access, and the remaining policies still apply. An `egressPolicyRefs` list that is itself invalid revokes every grant for that resource. For example, one name that is not a valid Kubernetes resource name, such as `Node-Deps`, revokes the grants of every other policy in the list. + +A grant conflict happens in the following cases: + +- Two policies set a different Secret or prefix for the same origin and header. +- A grant sets a credential for an origin that the model or a tool already holds a credential for. The inferred credential always takes precedence. +- A grant sets a credential for an origin that uses token passthrough. Token passthrough and a Secret credential cannot share an origin. +- The compiled policy would exceed 256 rules. + +A destination that no grant allows does not resolve inside the sandbox. A client therefore reports a name-resolution error rather than an HTTP error. For example, `curl` fails with `Could not resolve host` and exit code `6`. A request that the gateway denies appears in the gateway's log. For how to read the log, see [Diagnose a denied request]({{< link path="about/substrate-runtime/networking-and-egress#diagnose-a-denied-request" >}}). + +## Access control + +An EgressPolicy is namespace-scoped, and kagent does not check who may select it. Any user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace, and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets they name. + +To control who can grant access, give the permission to create and edit EgressPolicy resources only to administrators. The kagent controller needs only read access to EgressPolicy resources. + +The gateway keeps a credential out of the sandbox, but it cannot stop an allowed destination from returning the credential in a response. Grant a credential only to a destination that you trust with it. + +## Clean up + +1. Remove the policies from the Agent and the SandboxTemplate. + ```bash + kubectl patch agent my-first-agent -n kagent --type json \ + -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' + kubectl patch sandboxtemplate scratch -n kagent --type json \ + -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' + ``` + +2. Delete the EgressPolicy resources and the Secret. + ```bash + kubectl delete egresspolicy node-dependencies ticket-api -n kagent + kubectl delete secret ticket-auth -n kagent + ``` + +## Next steps + +{{< cards >}} + {{< card link=`{{< link path="about/substrate-runtime/networking-and-egress" >}}` title="Egress control" subtitle="Learn how the egress gateway authorizes each connection." >}} + {{< card link=`{{< link path="about/substrate-runtime/standalone-sandboxes" >}}` title="Standalone sandboxes" subtitle="Create sandboxes that agents and scripts can run commands in." >}} +{{< /cards >}} From cd92220eb5a7afeac9d4501205a78e594c3aea43 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Mon, 19 Oct 2026 15:32:02 -0500 Subject: [PATCH 04/10] pdf Signed-off-by: Rachael Graham --- docs-site/content/kagent/1.x/examples/a2a-agents.md | 2 +- .../content/kagent/1.x/observability/launch-ui.md | 12 ++++++++++-- .../kagent/1.x/setup/model-providers/azure-openai.md | 9 +++++---- 3 files changed, 16 insertions(+), 7 deletions(-) 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 6faa830e..167e6a17 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -111,7 +111,7 @@ An A2A client typically starts by reading the agent card, which tells it what th 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. > [!NOTE] -> Unlike the UI, which checks each file as you stage it, this path accepts any media type. A 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" >}}). +> Unlike the UI, which checks each file as you stage it, this path accepts any media type. A PDF, `.docx`, `.xlsx`, or `.pptx` file reaches a model whose ModelConfig sets `openAI.apiFormat: responses` 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. A part that sets `url` in place of `raw` sends a link. A `responses` model or a Gemini model fetches the file, and every other model receives the URL as text. 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" >}}). 1. Send a message to the Agent. Omitting a `contextId` starts a new Session. ```bash 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 ac59d061..385cf42b 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -119,13 +119,21 @@ Stage files in any of the following ways. - 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. -Sent files stay in the transcript as download chips, and reloading the page redraws them. Every non-image file reaches the model as text, regardless of the provider that the agent uses. 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. Images reach the providers that accept them. Amazon Bedrock, Ollama, and SAP AI Core receive a note in place of the image. +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. + +- **Text files**, such as Markdown, CSV, JSON, YAML, and HTML, reach the model as text, regardless of the provider. +- **PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files** reach the model as files when the ModelConfig sets `openAI.apiFormat: responses`, as an OpenAI or Azure OpenAI model can. The model reads the document itself. Every other model receives the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of the contents. When the files in a Session total more than 50 MB, a `responses` model receives the newest files, and a note replaces each older file. +- **Images** reach the providers that accept them. Amazon Bedrock, Ollama, and SAP AI Core receive a note in place of the image. + +A link in the message text to a file of an accepted type other than HTML, such as `https://example.com/q3-report.pdf`, is also sent as a file link. A model with the `responses` format, or a Gemini model, fetches the file itself, so the link must be reachable from the provider. The UI does not send a link to an IP address or to a local hostname, such as one ending in `.svc` or `.local`, because the provider cannot reach this host. Every other model receives the link as text, which an agent with a fetch tool can read. Only the message that sends a link passes 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. The following limits apply to attachments: | Limit | Value | Enforced by | | ----- | ----- | ----------- | -| Accepted file types | Plain text, Markdown, CSV, JSON, XML, YAML, and HTML 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, and HTML files, 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. | | 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. | diff --git a/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md b/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md index 3f68a6b0..2534b1df 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md @@ -94,18 +94,19 @@ spec: ## Send a file to the model -The `responses` format attaches a file to a prompt as a link, or as inline bytes when the file is an image. Azure fetches a link both on the turn that sends it and on every turn that replays the conversation, so a file link must be reachable from Azure for as long as the conversation lives. +The `responses` format attaches a file to a prompt as a link, or as inline bytes when the file is an image, a PDF, or an Office document. Azure fetches a link only on the turn that sends it. In later turns, the runtime replaces the link with the bare URL as text. -A link reaches Azure regardless of its media type. Inline bytes reach Azure only for an image. +A link reaches Azure regardless of its media type. Inline bytes reach Azure only for an image, a PDF, or a `.docx`, `.xlsx`, or `.pptx` file. Azure documents file input for PDF files only, so an Office file can fail on Azure even though the runtime sends it. | What the message includes | What the runtime sends | | ------------------------ | ---------------------- | | An `http://` or `https://` link with an `image/` media type | An `input_image` part with the link. | | An `http://` or `https://` link with any other media type, such as a PDF | An `input_file` part with the link. | | Inline image bytes | An `input_image` part with the bytes as a `data:` URL. | -| Inline bytes of any other type, such as a PDF or an Office document | Nothing. Azure OpenAI does not take inline bytes outside an image. | +| Inline bytes of a PDF, `.docx`, `.xlsx`, or `.pptx` file | An `input_file` part with the bytes as a `data:` URL. The bytes must match the media type. A file that does not, such as a text file labeled `application/pdf`, reaches the model as a note that it could not be read. When the files in the conversation total more than 50 MB, the newest files are sent, and a note replaces each older file. | +| Inline bytes of any other type | The file's text, or a note when the file cannot be read as text. | -The runtime refuses a link on any other scheme. When the newest user message includes such a link, the turn fails with `cannot send link "" with media type "": this provider integration can only pass http and https links`. When an older message in the replayed conversation includes one, the runtime replaces the link with a text note that keeps the URL, so a model with a fetch tool can still retrieve the file. +The runtime refuses a link on any other scheme. When the newest user message includes such a link, the turn fails with `cannot send link "" with media type "": this provider integration can only pass http and https links`. When an older message in the replayed conversation includes one, the runtime replaces the link with the bare URL as text, so a model with a fetch tool can still retrieve the file. A link attachment with a display name gets an extra text part that reads `File: ` that is placed immediately before the file part. Azure rejects a filename beside a file URL, so the name reaches the model as ordinary text. From c0515d56e8e8c1bb57d48936d320c3d8e3150d9d Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Mon, 19 Oct 2026 15:39:03 -0500 Subject: [PATCH 05/10] conventions Signed-off-by: Rachael Graham --- .../networking-and-egress.md | 18 ++-- .../1.x/about/substrate-runtime/sandboxing.md | 2 +- .../substrate-runtime/standalone-sandboxes.md | 4 +- .../kagent/1.x/agents/egress-policies.md | 99 ++++++++++--------- .../content/kagent/1.x/examples/a2a-agents.md | 4 +- .../kagent/1.x/observability/launch-ui.md | 6 +- .../kagent/1.x/operations/upgrade-from-0x.md | 9 +- .../1.x/setup/model-providers/azure-openai.md | 8 +- 8 files changed, 84 insertions(+), 66 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/networking-and-egress.md b/docs-site/content/kagent/1.x/about/substrate-runtime/networking-and-egress.md index 70c6d537..d7de0ea7 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/networking-and-egress.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/networking-and-egress.md @@ -7,7 +7,7 @@ aliases: - /kagent/1.x/substrate-runtime/networking-and-egress/ --- -An agent reaches the network only through Agent Substrate's egress gateway, and the gateway authorizes every connection against a policy attached to that {{< gloss "Actor" >}}Actor{{< /gloss >}}. The policy is default-deny. An Actor can attempt any request, and the gateway denies the ones that no rule allows. kagent derives the policy from the AgentTemplate, so a documented agent works without any egress configuration of its own. To grant destinations that the AgentTemplate does not name, an Agent selects EgressPolicy resources. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). +An agent reaches the network only through Agent Substrate's egress gateway, and the gateway authorizes every connection against a policy attached to that {{< gloss "Actor" >}}Actor{{< /gloss >}}. The policy is default-deny. An Actor can attempt any request, and the gateway denies the ones that no rule allows. kagent derives the policy from the AgentTemplate, so a documented agent works without any egress configuration of its own. An Agent can also select EgressPolicy resources, which grant destinations that the AgentTemplate does not name. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). > [!NOTE] > The gateway terminates TLS, so hostname rules constrain encrypted traffic as well as cleartext. An HTTPS request is decrypted at the gateway, matched against the policy's hostnames, and re-originated to the destination. A TLS passthrough rule is the exception, because the gateway forwards this connection without decrypting it. For details, see [Traffic types](#traffic-types). @@ -20,7 +20,7 @@ All outbound traffic leaves a Worker through the egress gateway, which gives Age 1. The gateway identifies the Actor. It reads the Actor's SPIFFE ID from the client certificate on the connection from the Actor, and it verifies this certificate rather than trusting anything in the request. -2. The gateway fetches the policy for this Actor. Each Actor has at most one Agent Substrate policy named `default`, and Agent Substrate addresses the policy by the Actor rather than by this name. This Agent Substrate policy is not the kagent `EgressPolicy` resource. kagent compiles the Actor's policy from the inferred destinations and from every kagent `EgressPolicy` that the Agent selects. The gateway denies every request from an Actor that has no policy, because only a policy allows access to a destination. +2. The gateway fetches the policy for this Actor. Each Actor has at most one Agent Substrate policy named `default`, and Agent Substrate addresses the policy by the Actor rather than by this name. This Agent Substrate policy is not the kagent EgressPolicy resource. kagent compiles the Agent Substrate policy from the destinations that the AgentTemplate needs, and from every kagent EgressPolicy that the Agent selects. The gateway denies every request from an Actor that has no policy, because only a policy allows access to a destination. 3. The gateway picks which rule applies to the request. Rules are an unordered set rather than a list, and exactly one rule applies to a given destination name and port regardless of its protocol. When more than one rule matches, the gateway applies two criteria in order, moving to the second only when the first ties. @@ -69,9 +69,9 @@ kagent derives the allowlist from the AgentTemplate rather than taking it as con kagent compiles this host set into the Actor's policy when it creates the Actor. Each destination becomes one rule that covers one hostname on one port, and the scheme decides the rule type. For example, an `https` destination becomes an `https` rule, and a cleartext destination becomes an `http` rule. Keeping exactly one rule per host and port is deliberate. An overlapping rule could otherwise decide a request and skip the credential that the narrower rule attaches. A destination that uses both HTTP and HTTPS on the same host and port is rejected rather than compiled. -The inferred host set is fixed when kagent compiles the revision. An agent also reaches the destinations of every EgressPolicy that its Agent selects, in its `spec.egressPolicyRefs` field. The gateway denies every other destination, including a web page that a model decides to fetch partway through a Session. +kagent fixes the inferred host set when it compiles the revision. An agent also reaches the destinations of every EgressPolicy that its Agent selects, in its `spec.egressPolicyRefs` field. The gateway denies every other destination, including a web page that a model decides to fetch partway through a Session. -kagent merges the two sources when it applies the policy to an Actor. The inferred destinations always apply. A policy can only add destinations, and an inferred credential takes precedence over a credential from a policy for the same origin and header. For how to grant a destination, see [Egress policies]({{< link path="agents/egress-policies" >}}). +When kagent applies the policy to an Actor, it merges the inferred destinations with the EgressPolicy grants. An EgressPolicy can only add destinations. For the same origin and header, an inferred credential takes precedence over a credential from an EgressPolicy. For how to grant a destination, see [Grant access to a destination]({{< link path="agents/egress-policies#grant-access-to-a-destination" >}}). An agent also needs a route to the OpenTelemetry collector, or its traces never leave the sandbox and the kagent UI shows nothing for this Session. kagent adds the collector host for agents on every harness, `byo` included. A `byo` image that exports to a telemetry backend of its own, through a Harness `spec.env` entry, has no route to that backend, because the AgentTemplate never named it. For more information, see [Harness coverage]({{< link path="observability/tracing#harness-coverage" >}}). @@ -104,13 +104,19 @@ A `503` means that the gateway could not reach the control plane or a credential An allowed request is logged the same way, with its status, the `http.host` and `http.path` it matched on, and the `substrate.connect.authority` address the Actor originally dialed. The log is therefore the quickest way to see what an agent actually reaches. -The compiled policy is not readable through `kubectl` or `kagent`, so treat the AgentTemplate and the Agent's selected EgressPolicy resources as the source of truth for what an agent is meant to reach. A destination that is missing from the log is missing from both. To check whether kagent admitted the Agent's EgressPolicy grants, read the Agent's `EgressPoliciesApplied` condition. For the reasons that this condition reports, see [Troubleshoot a grant]({{< link path="agents/egress-policies#troubleshoot-a-grant" >}}). +The compiled policy is not readable through `kubectl` or `kagent`. Treat the AgentTemplate and the Agent's selected EgressPolicy resources as the source of truth for what an agent is meant to reach. A destination that is missing from the log is missing from both. To check whether kagent admitted the Agent's EgressPolicy grants, read the Agent's `EgressPoliciesApplied` condition. For the reasons that this condition reports, see [Troubleshoot a grant]({{< link path="agents/egress-policies#troubleshoot-a-grant" >}}). ## Credential injection A model provider or an MCP server usually requires an API key or token. The agent sends a placeholder header in place of the real value, and the gateway replaces this header with the credential as the request passes through. The key never enters the agent's environment or its filesystem. -kagent sets this up from three resources. Every ModelConfig that holds an `apiKeySecret` becomes a credential on the rule for that model's host, and every RemoteMCPServer whose `headersFrom` reads a Secret becomes a credential on the rule for its host. Every EgressPolicy destination with an `authentication` block becomes a credential on the rule for its origin. kagent attaches the credential to the destination's own rule rather than adding a rule for it, and it refuses to compile a credential whose host is not already an allowed destination. +kagent builds these credentials from the following resources. + +- Every ModelConfig that sets an `apiKeySecret` becomes a credential on the rule for that model's host. +- Every RemoteMCPServer whose `headersFrom` reads a Secret becomes a credential on the rule for its host. +- Every EgressPolicy destination with an `authentication` block becomes a credential on the rule for its origin. + +kagent attaches the credential to the destination's own rule rather than adding a rule for it, and it refuses to compile a credential whose host is not already an allowed destination. A credential is declared on a rule as a `replace_headers` entry. The entry names the header to replace, an optional prefix such as `Bearer `, and either a reference that a registered credential provider resolves or a JWT that Agent Substrate issues to assert the Actor's identity. The gateway replaces the header only when the request already sends it. A request that omits the placeholder header is forwarded unchanged, without the credential, rather than being denied. diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md index 9a7977eb..9a92c614 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/sandboxing.md @@ -103,4 +103,4 @@ Agent Substrate creates a Kubernetes NetworkPolicy for each WorkerPool, selectin That policy governs inbound traffic only. Outbound traffic is governed separately by an egress policy that the gateway enforces on every connection that an Actor opens. That policy is default-deny. An Actor reaches a destination only when a rule allows it, and the gateway denies the connection when no rule does. -kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations it allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. To allow a destination that the AgentTemplate does not name, the Agent selects an EgressPolicy resource. For more information, see [Egress policies]({{< link path="agents/egress-policies" >}}). For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). +kagent derives the egress policy from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}, so an agent does not need egress configuration of its own. The destinations it allows come from what the AgentTemplate already names, such as its model provider and its MCP servers. Agent Substrate owns the ingress policy, and kagent regenerates the egress policy whenever the AgentTemplate changes. To allow a destination that the AgentTemplate does not name, select an EgressPolicy resource on the Agent, as described in [Grant access to a destination]({{< link path="agents/egress-policies#grant-access-to-a-destination" >}}). For what the policy holds, how a rule is matched, and how to diagnose a denied request, see [Egress control]({{< link path="about/substrate-runtime/networking-and-egress" >}}). 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 4442728a..2340da86 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 @@ -83,7 +83,7 @@ A SandboxTemplate is a namespaced `api.kagent.dev/v1alpha3` resource that prepar | `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. | | `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. Without this field, a 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 has no 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. @@ -98,7 +98,7 @@ Editing a template prepares a new revision. A Sandbox that already exists keeps ## Run a command > [!WARNING] -> **By default, the gateway denies every outbound connection from a Sandbox.** kagent compiles an empty [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}) for a Sandbox whose template selects no EgressPolicy resources, so a command that fetches a package, clones a repository, or calls an API fails. To allow a destination, select an EgressPolicy in the SandboxTemplate's `spec.egressPolicyRefs` field, as described in [Grant access to a standalone sandbox]({{< link path="agents/egress-policies#grant-access-to-a-standalone-sandbox" >}}). Otherwise, move what a command needs into the Sandbox with `kagent sandbox upload`. +> **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 standalone sandbox]({{< link path="agents/egress-policies#grant-access-to-a-standalone-sandbox" >}}). 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. diff --git a/docs-site/content/kagent/1.x/agents/egress-policies.md b/docs-site/content/kagent/1.x/agents/egress-policies.md index 96e6216f..dc083739 100644 --- a/docs-site/content/kagent/1.x/agents/egress-policies.md +++ b/docs-site/content/kagent/1.x/agents/egress-policies.md @@ -5,21 +5,28 @@ weight: 55 author: kagent.dev --- -An {{< gloss "Agent" >}}Agent{{< /gloss >}} reaches only the destinations that its AgentTemplate needs, such as its model provider, its MCP servers, and its skill sources. {{< reuse "kagent-docs/snippets/name-product.md" >}} infers these destinations and compiles them into the agent's [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}). The egress gateway denies every other destination. +An {{< gloss "Agent" >}}Agent{{< /gloss >}} reaches only the destinations that its AgentTemplate needs, such as its model provider, its Model Context Protocol (MCP) servers, and its skill sources. {{< reuse "kagent-docs/snippets/name-product.md" >}} infers these destinations and compiles them into the agent's [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}). The egress gateway denies every other destination. -An `EgressPolicy` resource grants access to more destinations. An Agent or a SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. A policy can also hold a credential, which the gateway adds to each request so that the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. +An EgressPolicy resource grants access to more destinations. An Agent or a SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. A policy can also reference a credential in a Kubernetes Secret. The gateway adds the credential to each request, so the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. -A grant only adds access. A policy cannot remove a destination that kagent inferred from the model or the tools. An Agent that selects no policies has no access beyond the inferred destinations, and a standalone sandbox that selects no policies has no outbound access at all. +A grant only adds access. A policy cannot remove a destination that kagent inferred from the model or the tools. ## Before you begin 1. [Create your first agent]({{< link path="get-started/your-first-agent" >}}), so that you have the `my-first-agent` Agent in the `kagent` namespace. -2. Check your kagent CLI version. The steps on this page need the {{< reuse "kagent-docs/versions/kagent.md" >}} CLI. +2. Optional: To grant access to a standalone sandbox, create the `scratch` SandboxTemplate, as described in [Create a SandboxTemplate]({{< link path="about/substrate-runtime/standalone-sandboxes#create-a-sandboxtemplate" >}}). + +3. Check your kagent CLI version. The steps on this page need the {{< reuse "kagent-docs/versions/kagent.md" >}} CLI. ```bash 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" >}} + ``` + ## Grant access to a destination Create an EgressPolicy that lists the destinations, and then select the policy on the Agent. This example grants access to the npm registry and the Node.js download site. @@ -39,21 +46,21 @@ Create an EgressPolicy that lists the destinations, and then select the policy o EOF ``` - {{< reuse "kagent-docs/snippets/review-table.md" >}} For the complete schema, see the [API reference]({{< link path="reference/api-ref#egresspolicy" >}}). + {{< reuse "kagent-docs/snippets/review-table.md" >}} Each destination grants one origin, and the grant covers every path on that origin. For the complete schema, see the [API reference]({{< link path="reference/api-ref#egresspolicy" >}}). | Field | Description | | ----- | ----------- | | `destinations` | The destinations to grant, from 1 to 64 entries. | - | `destinations[].origin` | A scheme, a DNS hostname, and an optional port, such as `https://registry.npmjs.org` or `https://search.example.com:8443`. The grant covers every path on this origin. | + | `destinations[].origin` | A scheme, a Domain Name System (DNS) hostname, and an optional port, such as `https://registry.npmjs.org` or `https://search.example.com:8443`. For the accepted forms, see [Origin format](#origin-format). | | `destinations[].authentication` | Optional. A credential for the gateway to add to each request. For more information, see [Inject a credential](#inject-a-credential). | -2. Select the policy on the Agent. +2. Select the policy on the Agent. Only an Agent or a SandboxTemplate can select a policy. The AgentTemplate and the Harness cannot, so two Agents that share an AgentTemplate can have different access. The grant applies to the whole {{< gloss "Actor" >}}Actor{{< /gloss >}}, including the subagents that run in it. ```bash kubectl patch agent my-first-agent -n kagent --type merge \ -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' ``` -3. Check that kagent admitted the grant. The `EgressPoliciesApplied` condition reports `True` with the reason `Applied` when every selected policy is admitted. +3. Check that kagent admitted the grant. When kagent admits every selected policy, the `EgressPoliciesApplied` condition reports `True` with the reason `Applied`. ```bash kubectl get agent my-first-agent -n kagent \ -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' @@ -64,25 +71,23 @@ Create an EgressPolicy that lists the destinations, and then select the policy o True Applied All requested egress grants are admitted ``` -The grant reaches existing {{< gloss "Session" >}}Sessions{{< /gloss >}} as well as new ones. kagent updates the policy of every running and suspended Actor that the Agent owns, without restarting the Actor. The update is asynchronous, so a request that the agent sends immediately after the change can still use the previous policy. - -A grant applies to the whole Actor. Subagents that share the Agent's Actor get the same access, because they run in one sandbox and kagent cannot isolate their network access from each other. The AgentTemplate and the Harness cannot select a policy, so two Agents that share an AgentTemplate can have different access. +The grant reaches existing {{< gloss "Session" >}}Sessions{{< /gloss >}} as well as new ones. kagent updates the policy of every running and suspended Actor that the Agent owns, without restarting the Actor. The update is asynchronous. A request that the agent sends immediately after the change might still use the previous policy. ## Origin format -An origin names exactly one scheme, host, and port. When the port is omitted, kagent uses `443` for `https` and `80` for `http`. The API server rejects an origin that contains a path, a query, a fragment, user information, or an IP address. Origins on protocols other than HTTP and HTTPS are not supported. +An origin names exactly one scheme, host, and port. The scheme must be `http` or `https`. When the port is omitted, kagent uses `443` for `https` and `80` for `http`. The API server rejects an origin that contains a path, a query, a fragment, user information, or an IP address. -A wildcard can replace the leftmost label of the hostname. For example, `https://*.example.com` matches `api.example.com`, but it matches neither `example.com` nor `a.b.example.com`. The origins `*` and `https://*.com` are rejected. +A wildcard can replace the leftmost label of the hostname. For example, `https://*.example.com` matches `api.example.com`, but it matches neither `example.com` nor `a.b.example.com`. The API server rejects `*` and `https://*.com`. -A redirect to another origin needs its own grant. For example, a package registry often serves downloads from a separate host, and an install fails at that host until the policy names it as well. +A redirect to another origin needs its own grant. For example, a package registry often serves downloads from a separate host. An install fails at that host until the policy grants it as well. -The following limits apply to each Agent or SandboxTemplate. +The API server rejects a policy or an `egressPolicyRefs` list that exceeds the first two of the following limits. The compiled policy limit also counts the destinations that kagent inferred, so an Agent with many tools reaches it sooner. | Limit | Value | | ----- | ----- | -| Selected policies | 16 | +| Policies that one Agent or SandboxTemplate selects | 16 | | Destinations in one policy | 64 | -| Rules in the compiled policy, including the inferred destinations | 256 | +| Rules in the compiled policy of one Actor | 256 | | Credential headers for one origin | 16 | ## Inject a credential @@ -115,39 +120,34 @@ The gateway can add a credential from a Kubernetes Secret to each request for an EOF ``` - {{< reuse "kagent-docs/snippets/review-table.md" >}} + {{< reuse "kagent-docs/snippets/review-table.md" >}} The gateway sets the header to the prefix followed by the Secret value. | Field | Description | | ----- | ----------- | - | `origin` | The destination. An origin with a credential must be exact, so a wildcard origin is rejected. | - | `authentication.header` | The request header that carries the credential, such as `Authorization` or `X-API-Key`. Routing, framing, hop-by-hop, proxy-authentication, and `Cookie` headers are rejected. | - | `authentication.prefix` | Optional. Text that the gateway puts before the Secret value. The prefix is literal, so include the trailing space, as in `"Bearer "`. When the prefix is omitted or empty, the header carries the Secret value alone. | - | `authentication.secretKeyRef` | The Secret and key that hold the credential. Both `name` and `key` are required, and the reference cannot be optional. | + | `origin` | The destination. An origin with a credential must be exact, so the API server rejects a wildcard origin. On a cleartext `http` origin, the credential is readable on the path between the gateway and the destination. Use an `http` origin with a credential only for an in-cluster service whose traffic a service mesh encrypts. | + | `authentication.header` | The request header that carries the credential, such as `Authorization` or `X-API-Key`. The API server rejects the headers that control routing and framing, such as `Host` and `Content-Length`, and the `Proxy-Authorization`, `Proxy-Authenticate`, and `Cookie` headers. | + | `authentication.prefix` | Optional. Text that the gateway puts before the Secret value. The prefix is literal. Include the trailing space, as in `"Bearer "`. When the prefix is omitted or empty, the header contains only the Secret value. | + | `authentication.secretKeyRef` | The Secret and key that store the credential. Both `name` and `key` are required, and the reference cannot be optional. | -3. Select the policy on the Agent. The `egressPolicyRefs` list replaces the existing list, so include every policy that the Agent needs. +3. Select the policy on the Agent. A merge patch replaces the whole `egressPolicyRefs` list. Include every policy that the Agent needs. ```bash kubectl patch agent my-first-agent -n kagent --type merge \ -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' ``` -The gateway replaces a header that the request already sends, and it does not add a header that is missing. A script or an SDK in the agent must therefore send the header with a placeholder value. - -```bash -curl -H 'Authorization: Bearer kagent-credential-injected' \ - https://tickets.example.com/tickets -``` - -Selecting a policy does not configure a tool or set an environment variable for you. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). +4. In the script or software development kit (SDK) that the agent uses, send the header with a placeholder value. The gateway replaces a header that the request already sends, and it does not add a header that is missing. Selecting a policy does not configure a tool or set an environment variable. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). + ```bash + curl -H 'Authorization: Bearer kagent-credential-injected' \ + https://tickets.example.com/tickets + ``` To rotate the credential, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. -A credential on a cleartext `http` origin is readable on the path between the gateway and the destination. Use an `http` origin with a credential only for an in-cluster service whose traffic a service mesh encrypts. - ## Grant access to a standalone sandbox -A standalone Sandbox has no outbound access by default. To grant access, select the policy on the SandboxTemplate that the Sandbox is created from. +A standalone Sandbox has no outbound access by default. To grant access, select the policy on the SandboxTemplate that the Sandbox is created from. Existing Sandboxes from this template receive the change without a restart. -1. Add the policy to the SandboxTemplate's `spec.egressPolicyRefs` field. This example uses the `scratch` template from [Standalone sandboxes]({{< link path="about/substrate-runtime/standalone-sandboxes#create-a-sandboxtemplate" >}}). +1. Add the policy to the SandboxTemplate's `spec.egressPolicyRefs` field. ```bash kubectl patch sandboxtemplate scratch -n kagent --type merge \ -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' @@ -159,35 +159,38 @@ A standalone Sandbox has no outbound access by default. To grant access, select -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' ``` -Existing Sandboxes from this template receive the change without a restart. + Example output: + ```console + True Applied All requested egress grants are admitted + ``` ## Troubleshoot a grant -The `EgressPoliciesApplied` condition on the Agent or SandboxTemplate reports whether kagent admitted the grants. This condition is independent of `Ready`, so an Agent with a missing or invalid policy still reports `Ready` and still serves Sessions with its inferred destinations. +The `EgressPoliciesApplied` condition on the Agent or SandboxTemplate reports whether kagent admitted the grants. This condition is independent of `Ready`. An Agent with a missing or invalid policy still reports `Ready`, and it still serves Sessions with its inferred destinations. -The following reasons can appear on the condition. The condition message names the affected policies. +The condition message names the affected policies, and the reason identifies the problem. | Status | Reason | Meaning | | ------ | ------ | ------- | -| `True` | `Applied` | Every selected policy is admitted. | +| `True` | `Applied` | kagent admitted every selected policy. | | `False` | `EgressPolicyNotFound` | A selected policy does not exist, or is being deleted. | | `False` | `InvalidEgressPolicy` | A policy, a Secret reference, or the `egressPolicyRefs` list is invalid. For example, the Secret or its key does not exist. | -| `False` | `EgressGrantConflict` | A new grant conflicts with an existing one. kagent holds the new grants and keeps the earlier grants that are still selected and compatible. | +| `False` | `EgressGrantConflict` | A new grant conflicts with an existing one. kagent holds the new grants, and keeps the previously admitted grants that are still selected and compatible. | -A problem with one policy does not block the others. A policy that is missing or invalid contributes no access, and the remaining policies still apply. An `egressPolicyRefs` list that is itself invalid revokes every grant for that resource. For example, one name that is not a valid Kubernetes resource name, such as `Node-Deps`, revokes the grants of every other policy in the list. +A problem with one policy does not block the others. A policy that is missing or invalid does not grant any access, and the remaining policies still apply. An `egressPolicyRefs` list that is itself invalid revokes every grant for that resource. For example, one name that is not a valid Kubernetes resource name, such as `Node-Deps`, revokes the grants of every other policy in the list. A grant conflict happens in the following cases: - Two policies set a different Secret or prefix for the same origin and header. -- A grant sets a credential for an origin that the model or a tool already holds a credential for. The inferred credential always takes precedence. +- A grant sets a credential for an origin that already has a credential from the model or a tool. The credential from the model or the tool always takes precedence. - A grant sets a credential for an origin that uses token passthrough. Token passthrough and a Secret credential cannot share an origin. - The compiled policy would exceed 256 rules. -A destination that no grant allows does not resolve inside the sandbox. A client therefore reports a name-resolution error rather than an HTTP error. For example, `curl` fails with `Could not resolve host` and exit code `6`. A request that the gateway denies appears in the gateway's log. For how to read the log, see [Diagnose a denied request]({{< link path="about/substrate-runtime/networking-and-egress#diagnose-a-denied-request" >}}). +In a standalone Sandbox, a destination that no grant allows does not resolve. A client therefore reports a name-resolution error, not an HTTP error. For example, `curl` fails with `Could not resolve host` and exit code `6`. For how to find a denied request in the gateway's log, see [Diagnose a denied request]({{< link path="about/substrate-runtime/networking-and-egress#diagnose-a-denied-request" >}}). ## Access control -An EgressPolicy is namespace-scoped, and kagent does not check who may select it. Any user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace, and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets they name. +An EgressPolicy is namespace-scoped, and kagent does not check who may select it. A user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. To control who can grant access, give the permission to create and edit EgressPolicy resources only to administrators. The kagent controller needs only read access to EgressPolicy resources. @@ -195,15 +198,19 @@ The gateway keeps a credential out of the sandbox, but it cannot stop an allowed ## Clean up -1. Remove the policies from the Agent and the SandboxTemplate. +1. Remove the policies from the Agent. ```bash kubectl patch agent my-first-agent -n kagent --type json \ -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' + ``` + +2. If you granted access to the `scratch` SandboxTemplate, remove the policy from the template. + ```bash kubectl patch sandboxtemplate scratch -n kagent --type json \ -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' ``` -2. Delete the EgressPolicy resources and the Secret. +3. Delete the EgressPolicy resources and the Secret. ```bash kubectl delete egresspolicy node-dependencies ticket-api -n kagent kubectl delete secret ticket-auth -n kagent 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 167e6a17..635ebb6d 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -108,10 +108,10 @@ An A2A client typically starts by reading the agent card, which tells it what th `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. +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`. > [!NOTE] -> Unlike the UI, which checks each file as you stage it, this path accepts any media type. A PDF, `.docx`, `.xlsx`, or `.pptx` file reaches a model whose ModelConfig sets `openAI.apiFormat: responses` 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. A part that sets `url` in place of `raw` sends a link. A `responses` model or a Gemini model fetches the file, and every other model receives the URL as text. 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" >}}). +> 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. A `responses` model or a Gemini model fetches a linked file itself. Every other model receives the URL as text. 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" >}}). 1. Send a message to the Agent. Omitting a `contextId` starts a new Session. ```bash 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 385cf42b..c12e5cac 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -122,10 +122,10 @@ Stage files in any of the following ways. 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. - **Text files**, such as Markdown, CSV, JSON, YAML, and HTML, reach the model as text, regardless of the provider. -- **PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files** reach the model as files when the ModelConfig sets `openAI.apiFormat: responses`, as an OpenAI or Azure OpenAI model can. The model reads the document itself. Every other model receives the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of the contents. When the files in a Session total more than 50 MB, a `responses` model receives the newest files, and a note replaces each older file. +- **PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files** depend on the API format of the model. When the ModelConfig sets `apiFormat: responses` in its `openAI` or `azureOpenAI` block, the model receives the file and reads the document itself. Every other model receives the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of the contents. When the files in a Session total more than 50 MB, a `responses` model receives the newest files, and a note replaces each older file. - **Images** reach the providers that accept them. Amazon Bedrock, Ollama, and SAP AI Core receive a note in place of the image. -A link in the message text to a file of an accepted type other than HTML, such as `https://example.com/q3-report.pdf`, is also sent as a file link. A model with the `responses` format, or a Gemini model, fetches the file itself, so the link must be reachable from the provider. The UI does not send a link to an IP address or to a local hostname, such as one ending in `.svc` or `.local`, because the provider cannot reach this host. Every other model receives the link as text, which an agent with a fetch tool can read. Only the message that sends a link passes it as a file. In later turns, the model receives the bare URL as text. +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. A `responses` model or a Gemini 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. @@ -133,7 +133,7 @@ The following limits apply to attachments: | Limit | Value | Enforced by | | ----- | ----- | ----------- | -| Accepted file types | Plain text, Markdown, CSV, JSON, XML, YAML, and HTML files, 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. A rejected file is reported 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. | 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 7d5af087..61a444f4 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 @@ -161,17 +161,22 @@ The export splits into three groups: resources that need only an `apiVersion` ch ### Resources that carry forward -ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets except one. The ModelConfig field `apiKeyPassthrough` is renamed to `tokenPassthrough` in `v1alpha3`, and its behavior is unchanged. `v1alpha3` also adds fields that 0.10.x did not serve, so an exported resource needs no other change. +ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets except one. `v1alpha3` renames the ModelConfig field `apiKeyPassthrough` to `tokenPassthrough`, and the field's behavior does not change. `v1alpha3` also adds fields that 0.10.x did not serve, so an exported resource needs no other change. 1. Recreate the Secrets that your ModelConfigs name before you apply them, or the ModelConfigs resolve to nothing. -2. Change the `apiVersion`, rename `apiKeyPassthrough`, and apply the resources. +2. Convert the export to `v1alpha3`. The command changes each `apiVersion` and renames the `apiKeyPassthrough` field. ```bash sed -e 's|^apiVersion: kagent.dev/v1alpha2$|apiVersion: api.kagent.dev/v1alpha3|' \ -e 's|^\( *\)apiKeyPassthrough:|\1tokenPassthrough:|' \ kagent-0.10-resources.yaml > kagent-1.0-resources.yaml ``` +3. Apply the converted resources. + ```bash + kubectl apply -f kagent-1.0-resources.yaml + ``` + ### 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. diff --git a/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md b/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md index 2534b1df..c7d46395 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/azure-openai.md @@ -94,19 +94,19 @@ spec: ## Send a file to the model -The `responses` format attaches a file to a prompt as a link, or as inline bytes when the file is an image, a PDF, or an Office document. Azure fetches a link only on the turn that sends it. In later turns, the runtime replaces the link with the bare URL as text. +The `responses` format sends a file to Azure as a link or as inline bytes. Azure fetches a link only on the turn that sends it. In later turns, the runtime replaces the link with the bare URL as text. -A link reaches Azure regardless of its media type. Inline bytes reach Azure only for an image, a PDF, or a `.docx`, `.xlsx`, or `.pptx` file. Azure documents file input for PDF files only, so an Office file can fail on Azure even though the runtime sends it. +A link reaches Azure regardless of its media type. Inline bytes reach Azure as a file only for an image, a PDF, or a `.docx`, `.xlsx`, or `.pptx` file. Azure documents file input only for PDF files. A `.docx`, `.xlsx`, or `.pptx` file might fail on Azure, although the runtime sends it. | What the message includes | What the runtime sends | | ------------------------ | ---------------------- | | An `http://` or `https://` link with an `image/` media type | An `input_image` part with the link. | | An `http://` or `https://` link with any other media type, such as a PDF | An `input_file` part with the link. | | Inline image bytes | An `input_image` part with the bytes as a `data:` URL. | -| Inline bytes of a PDF, `.docx`, `.xlsx`, or `.pptx` file | An `input_file` part with the bytes as a `data:` URL. The bytes must match the media type. A file that does not, such as a text file labeled `application/pdf`, reaches the model as a note that it could not be read. When the files in the conversation total more than 50 MB, the newest files are sent, and a note replaces each older file. | +| Inline bytes of a PDF, `.docx`, `.xlsx`, or `.pptx` file | An `input_file` part with the bytes as a `data:` URL. The bytes must match the media type. A file that does not, such as a text file labeled `application/pdf`, reaches the model as a note that it could not be read. When the files in the conversation total more than 50 MB, the runtime sends the newest files, and a note replaces each older file. | | Inline bytes of any other type | The file's text, or a note when the file cannot be read as text. | -The runtime refuses a link on any other scheme. When the newest user message includes such a link, the turn fails with `cannot send link "" with media type "": this provider integration can only pass http and https links`. When an older message in the replayed conversation includes one, the runtime replaces the link with the bare URL as text, so a model with a fetch tool can still retrieve the file. +The runtime refuses a link on any other scheme. When the newest user message includes such a link, the turn fails with `cannot send link "" with media type "": this provider integration can only pass http and https links`. A link in an older message does not fail the turn, because the runtime sends the links from older messages as bare URLs. A model with a fetch tool can still retrieve the file. A link attachment with a display name gets an extra text part that reads `File: ` that is placed immediately before the file part. Azure rejects a filename beside a file URL, so the name reaches the model as ordinary text. From 70442f8184f6d795239ceee4164f439a4bfb1506 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Mon, 19 Oct 2026 16:32:42 -0500 Subject: [PATCH 06/10] conventions Signed-off-by: Rachael Graham --- .../content/kagent/1.x/examples/a2a-agents.md | 4 ++-- .../content/kagent/1.x/observability/launch-ui.md | 4 ++-- .../kagent/1.x/operations/upgrade-from-0x.md | 14 +++++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) 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 0cb2f506..2401463d 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -114,10 +114,10 @@ An A2A client typically starts by reading the agent card, which tells it what th `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`. +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. > [!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. A `responses` model or a Gemini model fetches a linked file itself. Every other model receives the URL as text. 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" >}}). +> 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" >}}). 1. Send a message to the Agent. Omitting a `contextId` starts a new Session. ```bash 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 c12e5cac..b81d0666 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -122,10 +122,10 @@ Stage files in any of the following ways. 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. - **Text files**, such as Markdown, CSV, JSON, YAML, and HTML, reach the model as text, regardless of the provider. -- **PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files** depend on the API format of the model. When the ModelConfig sets `apiFormat: responses` in its `openAI` or `azureOpenAI` block, the model receives the file and reads the document itself. Every other model receives the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of the contents. When the files in a Session total more than 50 MB, a `responses` model receives the newest files, and a note replaces each older file. +- **PDF, Word (`.docx`), Excel (`.xlsx`), and PowerPoint (`.pptx`) files** depend on the API format of the model. When the ModelConfig sets `apiFormat: responses` in its `openAI` or `azureOpenAI` block, the model receives the file and reads the document itself. Every other model receives the note `[Uploaded file "report.pdf" (application/pdf) could not be read as text.]` instead of the contents. When the files in a Session total more than 50 MB, the model receives only the newest files, and a note replaces each older file. - **Images** reach the providers that accept them. Amazon Bedrock, Ollama, and SAP AI Core receive a note in place of the image. -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. A `responses` model or a Gemini 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. +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. 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 61a444f4..2386cf48 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 @@ -40,6 +40,8 @@ The two releases also cannot run side by side on one cluster. The group rename s --file=kagent-0.10-backup.dump ``` +5. Install [`yq`](https://github.com/mikefarah/yq#install) version 4 or later. The steps in [Resources that carry forward](#resources-that-carry-forward) use `yq` to convert the exported resources. + ## Export your 0.10.x resources Your Kubernetes resources do carry forward, so export them before anything replaces the CRDs. Conversation history does not carry forward. @@ -157,18 +159,20 @@ Before you start, complete the [Before you begin]({{< link path="setup/installat ## Recreate your resources -The export splits into three groups: resources that need only an `apiVersion` change, agents that need rewriting, and resources with no 1.0 equivalent. Work through them in that order, because an AgentTemplate refers to the ModelConfigs and RemoteMCPServers that the first group creates. +The export splits into three groups: resources that carry forward with small changes, agents that need rewriting, and resources with no 1.0 equivalent. Work through them in that order, because an AgentTemplate refers to the ModelConfigs and RemoteMCPServers that the first group creates. ### Resources that carry forward -ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets except one. `v1alpha3` renames the ModelConfig field `apiKeyPassthrough` to `tokenPassthrough`, and the field's behavior does not change. `v1alpha3` also adds fields that 0.10.x did not serve, so an exported resource needs no other change. +ModelConfig, ModelProviderConfig, and RemoteMCPServer keep every field that `v1alpha2` sets except one. `v1alpha3` renames the ModelConfig field `apiKeyPassthrough` to `tokenPassthrough`. The field's behavior does not change. `v1alpha3` also adds fields that 0.10.x did not serve, so these resources need no other change to their `spec`. 1. Recreate the Secrets that your ModelConfigs name before you apply them, or the ModelConfigs resolve to nothing. -2. Convert the export to `v1alpha3`. The command changes each `apiVersion` and renames the `apiKeyPassthrough` field. +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. ```bash - sed -e 's|^apiVersion: kagent.dev/v1alpha2$|apiVersion: api.kagent.dev/v1alpha3|' \ - -e 's|^\( *\)apiKeyPassthrough:|\1tokenPassthrough:|' \ + yq '.items |= map(select(.kind == "ModelConfig" or .kind == "ModelProviderConfig" or .kind == "RemoteMCPServer")) + | .items[].apiVersion = "api.kagent.dev/v1alpha3" + | del(.items[].status, .items[].metadata.resourceVersion, .items[].metadata.uid, .items[].metadata.creationTimestamp, .items[].metadata.generation, .items[].metadata.managedFields, .items[].metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"]) + | (.items[] | select(.kind == "ModelConfig" and has("spec") and (.spec | has("apiKeyPassthrough"))) | .spec) |= (.tokenPassthrough = .apiKeyPassthrough | del(.apiKeyPassthrough))' \ kagent-0.10-resources.yaml > kagent-1.0-resources.yaml ``` From 12b9ce9ce55ba6b4309fb5ce696d3020f7012a6c Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Tue, 20 Oct 2026 10:41:37 -0500 Subject: [PATCH 07/10] fix playwrght screenshot captures Signed-off-by: Rachael Graham --- .github/workflows/playwright-screenshots.yaml | 19 +- docs-site/playwright/README.md | 33 ++- .../playwright/provisioners/kagent-kind.sh | 211 ++++-------------- 3 files changed, 73 insertions(+), 190 deletions(-) diff --git a/.github/workflows/playwright-screenshots.yaml b/.github/workflows/playwright-screenshots.yaml index e117e22a..5276432c 100644 --- a/.github/workflows/playwright-screenshots.yaml +++ b/.github/workflows/playwright-screenshots.yaml @@ -211,27 +211,28 @@ jobs: sudo install -m 0755 /tmp/kind /usr/local/bin/kind kind --version - - name: Install kubectl-ate + - name: Install the kagent CLI working-directory: website/docs-site run: | - # The same version the install guide tells a reader to download, read from the - # same conref, so the tooling matches the charts the provisioner installs. - version="$(python3 - assets/kagent-docs/versions/agent-substrate.md <<'PY' + # `kagent install` installs the charts of its own release, so the CLI version IS + # the chart version. Read it from the same conref the install guide renders; the + # provisioner refuses to run if the two disagree. + version="$(python3 - assets/kagent-docs/versions/kagent.md <<'PY' import re, sys txt = open(sys.argv[1]).read() m = re.search(r'include-if="[^"]*\b1\.x\b[^"]*"[^>]*>}}([^<{]+)', txt) print((m.group(1) if m else txt).strip()) PY )" - curl -fsSL -o kubectl-ate \ - "https://github.com/kagent-dev/substrate/releases/download/v${version}/kubectl-ate-linux-amd64" - chmod +x kubectl-ate && sudo mv kubectl-ate /usr/local/bin/ + curl -fsSL https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent \ + | bash -s -- --version "v${version}" + kagent version - name: Stand up the cluster at the docs-pinned versions working-directory: website/${{ env.PW_DIR }} env: - # The provisioner only tests that this is non-empty before writing it into a - # ModelProviderConfig, and these captures (dashboard, agents list, substrate + # `kagent install` only tests that this is non-empty before writing it into the + # provider Secret, and these captures (dashboard, agents list, substrate # page on a fresh install) invoke no model. So a placeholder is correct here, # not a workaround -- and it keeps a missing or rotated secret from failing a # run that never needed one. (kagent's `make check-api-key` tests the same diff --git a/docs-site/playwright/README.md b/docs-site/playwright/README.md index 21acdb43..3555255c 100644 --- a/docs-site/playwright/README.md +++ b/docs-site/playwright/README.md @@ -66,20 +66,17 @@ Two constraints on the mock path: - **Node ≥24.13.0** additionally for the mock path — that is the kagent UI's own engine floor (`ui/package.json`), and its build tooling fails on Node 18 with a `styleText` import error from `node:util` that does not name the cause. -- For live-cluster captures: `kind`, `helm`, `kubectl`, `kubectl-ate`, `jq`, `openssl`, and a - model provider API key. The provisioner checks for all of these before it touches the cluster - and names whichever is missing, so you find out up front rather than half way through an - install. -- **`kubectl-ate` is not on most machines and must match the Agent Substrate version** the - capture installs, because it writes that version's identity material. It is published per - release rather than through a package manager: +- For live-cluster captures: `kind`, `kubectl`, `jq`, the kagent CLI, and a model provider API + key. The provisioner checks for all of these before it touches the cluster and names whichever + is missing, so you find out up front rather than half way through an install. +- **The kagent CLI must match the kagent version the docs pin.** The provisioner runs + `kagent install`, which installs the charts of the CLI's own release, so it refuses to run + when `kagent version` disagrees with `assets/kagent-docs/versions/kagent.md`. Install the + matching CLI the same way the install guide does: ```sh - curl -sSL -o kubectl-ate \ - "https://github.com/kagent-dev/substrate/releases/download/v/kubectl-ate-$(uname -s | tr '[:upper:]' '[:lower:]')-amd64" - chmod +x kubectl-ate && mv kubectl-ate /usr/local/bin/ # or anywhere on PATH + curl https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent \ + | bash -s -- --version v ``` - Read `` from `assets/kagent-docs/versions/agent-substrate.md`, which is the - same conref the provisioner reads. - One-time setup: ```sh cd docs-site/playwright @@ -161,9 +158,10 @@ more thing to get working before a screenshot exists. ## Task: refresh for a new kagent version -1. **Nothing here pins a version.** The provisioner reads both chart versions from the same - conrefs the install guide renders (`assets/kagent-docs/versions/{kagent,agent-substrate}.md`, - the `1.x` entry), so bumping the docs is what moves the capture. Do not hardcode a version +1. **Nothing here pins a version.** The provisioner reads the kagent version from the same + conref the install guide renders (`assets/kagent-docs/versions/kagent.md`, the `1.x` entry), + and `kagent install` brings the Agent Substrate release that version bundles, so bumping the + docs is what moves the capture. Do not hardcode a version to "get the new UI" — that decouples the screenshot from what a reader installs. 2. **Re-capture and review the diffs visually.** A diff means the live UI no longer matches the committed screenshots, which may mean the *prose* is now wrong too, not just the image. That @@ -341,7 +339,7 @@ the job falls back to a placeholder, which is correct rather than a workaround: | `tests/chat.spec.ts` | Mock-backed capture: the seeded conversation | | `docs-image-map.json` | Baseline name → `{ light, dark }` destinations under `assets/img`, with a `$note` per image | | `scripts/sync-docs-images.mjs` | Copies baselines into `assets/img` | -| `provisioners/kagent-kind.sh` | kind cluster + Agent Substrate + kagent at docs-pinned versions | +| `provisioners/kagent-kind.sh` | kind cluster + `kagent install` at the docs-pinned version | | `__screenshots__/` | Committed baselines (light + dark per capture) | ## npm scripts @@ -360,5 +358,4 @@ the job falls back to a placeholder, which is correct rather than a workaround: | `UI_BASE_URL` | `http://localhost:8082` | The running UI to attach to | | `OPENAI_API_KEY` | — | Required by the provisioner (the kagent chart needs a model provider key) | | `CLUSTER_NAME` | `kagent-shots` | kind cluster name | -| `KAGENT_VERSION` | docs conref | Override the kagent chart version for a one-off run | -| `SUBSTRATE_VERSION` | docs conref | Override the Agent Substrate chart version for a one-off run | +| `KAGENT_VERSION` | docs conref | Override the kagent version for a one-off run; the CLI on `PATH` must match it | diff --git a/docs-site/playwright/provisioners/kagent-kind.sh b/docs-site/playwright/provisioners/kagent-kind.sh index 9d397ff8..7ec498ac 100755 --- a/docs-site/playwright/provisioners/kagent-kind.sh +++ b/docs-site/playwright/provisioners/kagent-kind.sh @@ -7,9 +7,9 @@ # content/kagent/1.x/setup/installation.md # so the screenshots always match what a reader who follows the docs would see. # -# Chart versions are read from the SAME conrefs that guide renders -# (assets/kagent-docs/versions/{kagent,agent-substrate}.md, 1.x entry), not from -# "latest published". That is the invariant to preserve: a capture must show what a +# The kagent version is read from the SAME conref that guide renders +# (assets/kagent-docs/versions/kagent.md, 1.x entry), not from "latest published". +# `kagent install` then installs the Agent Substrate release that kagent version bundles. That is the invariant to preserve: a capture must show what a # reader gets, so when the docs bump a version the next capture follows automatically # and nothing here needs editing. # @@ -22,32 +22,24 @@ # ./kagent-kind.sh --delete # # Flags: -# --dry-run Print every kind/helm/kubectl command without running it. +# --dry-run Print every kind/kagent/kubectl command without running it. # --no-cluster Skip `kind create` and install into the current kube-context. # --delete Delete the kind cluster and exit. # # Env (all have sane defaults): # CLUSTER_NAME kind cluster name (default: kagent-shots) # NAMESPACE kagent install namespace (default: kagent) -# ATE_NAMESPACE Agent Substrate namespace (default: ate-system) -# KAGENT_VERSION kagent chart version (default: read from docs conref) -# SUBSTRATE_VERSION Agent Substrate chart version (default: read from docs conref) +# KAGENT_VERSION kagent release; must match the CLI (default: read from docs conref) # NODE_IMAGE kind node image (default: kindest/node:v1.37.0) # OPENAI_API_KEY model provider key (required unless --dry-run) set -euo pipefail CLUSTER_NAME="${CLUSTER_NAME:-kagent-shots}" NAMESPACE="${NAMESPACE:-kagent}" -ATE_NAMESPACE="${ATE_NAMESPACE:-ate-system}" # Kubernetes 1.37+, per the install guide: Agent Substrate depends on the -# certificates.k8s.io/v1beta1 API, which is enabled through runtimeConfig below. +# PodCertificateRequest and ClusterTrustBundle APIs, which are GA and on by default there. NODE_IMAGE="${NODE_IMAGE:-kindest/node:v1.37.0}" -SUBSTRATE_CRDS_CHART="oci://ghcr.io/kagent-dev/substrate/helm/substrate-crds" -SUBSTRATE_CHART="oci://ghcr.io/kagent-dev/substrate/helm/substrate" -KAGENT_CRDS_CHART="oci://ghcr.io/kagent-dev/kagent/helm/kagent-crds" -KAGENT_CHART="oci://ghcr.io/kagent-dev/kagent/helm/kagent" - DRY_RUN=false NO_CLUSTER=false DELETE=false @@ -90,36 +82,24 @@ PY } KAGENT_VERSION="$(resolve_version kagent.md "${KAGENT_VERSION:-}")" -SUBSTRATE_VERSION="$(resolve_version agent-substrate.md "${SUBSTRATE_VERSION:-}")" run() { echo "+ $*" $DRY_RUN || "$@" } -# For commands that need a shell (heredocs, pipelines). Printed with the key redacted. -run_sh() { - local printable="$1" script="$2" - echo "+ ${printable}" - $DRY_RUN || bash -c "$script" -} - if $DELETE; then run kind delete cluster --name "$CLUSTER_NAME" exit 0 fi # Fail on a missing tool up front, naming it. Without this the first absent tool -# surfaces mid-run as its own unhelpful error -- a missing kubectl-ate reads as -# `unknown command "ate" for "kubectl"` -- and by then Agent Substrate is installed, -# so the cluster is half-built. kubectl-ate must match SUBSTRATE_VERSION; it is -# published at https://github.com/kagent-dev/substrate/releases. +# surfaces mid-run as its own unhelpful error, after the kind cluster already exists. if ! $DRY_RUN; then missing=() - for tool in kind helm kubectl jq openssl; do + for tool in kind kubectl kagent jq; do command -v "$tool" >/dev/null 2>&1 || missing+=("$tool") done - kubectl ate --help >/dev/null 2>&1 || missing+=("kubectl-ate") if (( ${#missing[@]} )); then echo "Missing required tool(s): ${missing[*]}" >&2 echo "See the Prerequisites section of docs-site/playwright/README.md." >&2 @@ -133,14 +113,12 @@ if ! $DRY_RUN && [[ -z "${OPENAI_API_KEY:-}" ]]; then exit 1 fi -echo "kagent chart: ${KAGENT_VERSION}" -echo "Agent Substrate chart: ${SUBSTRATE_VERSION}" +echo "kagent release: ${KAGENT_VERSION}" echo # --- Cluster ----------------------------------------------------------------------- # Skip creation when the cluster is already there, so a re-run after a failed install -# picks up where it left off instead of dying on `kind create`. Every step below is a -# `helm upgrade --install` or a `kubectl create` for the same reason. +# picks up where it left off instead of dying on `kind create`. # # Reuse it only when it also ANSWERS. `kind get clusters` lists a cluster whose node # container is stopped -- which is what Docker Desktop leaves behind when it restarts -- @@ -157,144 +135,51 @@ if ! $NO_CLUSTER && ! $DRY_RUN && kind get clusters 2>/dev/null | grep -qx "$CLU fi if ! $NO_CLUSTER; then - run_sh "kind create cluster --image ${NODE_IMAGE} --config=- (name ${CLUSTER_NAME})" " -kind create cluster --image ${NODE_IMAGE} --config=- <" already exists` rather than no-opping. That breaks the re-run the -# cluster-reuse branch above promises, and it breaks it at the FIRST pool, so a -# re-run after any later failure never reaches the step that failed. Create only what -# is missing; rotating a pool means deleting its secret and re-running. -ensure_pool() { - local kind="$1" name="$2" ns="$3" - shift 3 - if ! $DRY_RUN && kubectl get secret "$name" -n "$ns" >/dev/null 2>&1; then - echo "+ pool ${ns}/${name} already exists; skipping." - return - fi - if [[ "$kind" == jwt ]]; then - run kubectl ate admin make-jwt-pool --key-id=1 --name="$name" --secret-namespace="$ns" "$@" - else - run kubectl ate admin make-ca-pool --ca-id=1 --name="$name" --secret-namespace="$ns" "$@" +# The CLI installs the charts of its own release, so KAGENT_VERSION pins the charts only +# because the CLI on PATH is that release. Refuse a mismatch rather than capture the +# wrong UI. +if ! $DRY_RUN; then + cli_version="$(kagent version | jq -r .kagent_version)" + if [[ "$cli_version" != "$KAGENT_VERSION" ]]; then + echo "kagent CLI is ${cli_version}, but the docs pin ${KAGENT_VERSION}." >&2 + echo "Install the matching CLI; see the Prerequisites section of docs-site/playwright/README.md." >&2 + exit 1 fi -} - -# The pod-certificate-controller deployment hardcodes its namespace, so these two do -# NOT follow the release namespace. -ensure_pool ca service-dns-ca-pool podcertificate-controller-system -ensure_pool ca pod-identity-ca-pool podcertificate-controller-system - -# Actor identity pools. -ensure_pool jwt actor-id-jwt-pool "$ATE_NAMESPACE" -ensure_pool ca actor-id-ca-pool "$ATE_NAMESPACE" - -# The egress gateway terminates HTTPS from actors and re-signs it with this pool, which -# Agent Substrate mounts as a REQUIRED volume from the 0.2.x line on. Omitting it does -# not fail loudly: atenet-egress sits in ContainerCreating on `secret -# "egress-mitm-ca-pool" not found`, the --wait below burns its full 10m timeout, and the -# run dies before installing kagent at all. ECDSA P-256 is what substrate's own cluster -# setup uses here, and the key type is not negotiable -- the gateway reads tls.crt and -# tls.key straight out of this secret. -ensure_pool ca egress-mitm-ca-pool "$ATE_NAMESPACE" --key-type=ECDSAP256 - -run_sh "kubectl create secret generic actor-id-ca-certs (from the actor-id-ca-pool root)" " -set -euo pipefail -actor_id_ca_root=\"\$(kubectl get secret actor-id-ca-pool -n ${ATE_NAMESPACE} \ - -o jsonpath='{.data.pool}' | base64 --decode \ - | jq -r '.CAs[0].RootCertificateDER' | base64 --decode \ - | openssl x509 -inform der -outform pem)\" -kubectl create secret generic actor-id-ca-certs -n ${ATE_NAMESPACE} \ - --from-literal=ca.crt=\"\${actor_id_ca_root}\" \ - --dry-run=client -o yaml | kubectl apply -f - -" - -run_sh "kubectl create configmap ate-api-authentication" " -set -euo pipefail -# Ask the cluster for its issuer rather than assuming one: kind 1.37 advertises -# https://kubernetes.default.svc.cluster.local, and a mismatch is accepted at install -# time, surfacing later as \`token issuer ... not trusted\` on every kubectl-ate and -# ateapi call. kagent's own setup-cluster.sh and the install guide both derive it -# (kagent#2763, fixed in #2770). -k8s_issuer=\"\$(kubectl get --raw /.well-known/openid-configuration | jq -r .issuer)\" -kubectl create configmap ate-api-authentication -n ${ATE_NAMESPACE} \ - --from-literal=authentication.yaml='actorIdentityJWTProvider: kubernetes -jwtProviders: -- name: kubernetes - issuer: '\"\${k8s_issuer}\"' - audiences: [api.${ATE_NAMESPACE}.svc] - certificateAuthorityFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - discoveryTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token -' --dry-run=client -o yaml | kubectl apply -f - -" - -# Roll out again so the pods mount the identity material, and wait this time. -run helm upgrade substrate "$SUBSTRATE_CHART" \ - --version "$SUBSTRATE_VERSION" --namespace "$ATE_NAMESPACE" \ - --reuse-values --wait --timeout 10m - -# --- kagent -------------------------------------------------------------------------- -run_sh "helm upgrade --install kagent-crds ${KAGENT_CRDS_CHART} --version ${KAGENT_VERSION}" " -helm upgrade --install kagent-crds ${KAGENT_CRDS_CHART} --version ${KAGENT_VERSION} \ - --namespace ${NAMESPACE} --create-namespace --wait -" +fi -# The key is interpolated inside the heredoc, so the printed form is the redacted one. -run_sh "helm upgrade --install kagent ${KAGENT_CHART} --version ${KAGENT_VERSION} -f - (apiKey redacted)" " -helm upgrade --install kagent ${KAGENT_CHART} \ - --version ${KAGENT_VERSION} \ - --namespace ${NAMESPACE} --create-namespace --timeout 10m \ - -f - < "${CONFIG_DIR}/kagent-values.yaml" < "${CONFIG_DIR}/kagent-config.yaml" < Date: Tue, 20 Oct 2026 10:50:57 -0500 Subject: [PATCH 08/10] resolve review comments Signed-off-by: Rachael Graham --- .../kagent/1.x/about/architecture/agent-substrate.md | 4 ++-- docs-site/content/kagent/1.x/agents/agent-memory.md | 6 +++--- .../kagent/1.x/operations/tune-agent-substrate.md | 10 +++++----- .../1.x/setup/model-providers/about-model-providers.md | 5 +---- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md index b65901ca..7cb9f16d 100644 --- a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md +++ b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md @@ -40,9 +40,9 @@ Agent Substrate checks two things before it places an Actor on a Worker: * Whether the Worker has a free Actor slot. This check always runs. * Whether the Worker has enough spare CPU and memory. This check runs only when the ActorTemplate states how much CPU and memory one Actor needs. -An ActorTemplate that the kagent controller compiles does not state per-Actor CPU or memory, so the free Actor slot check is the only one that decides where a Session's Actor lands. [Worker resource limits]({{< link path="operations/tune-agent-substrate#set-resource-requests-on-workers" >}}) still matter, because a Worker with no limits reports its whole node as its own capacity. +Every ActorTemplate that the kagent controller compiles states a CPU and memory limit for one Actor, so both checks run for a Session's Actor. The `config.runtimeDefaults.cpu` and `config.runtimeDefaults.memory` Helm values set this limit for every agent, and default to `1` CPU and `1Gi` of memory. - +The CPU and memory check compares that limit against the capacity that the Worker reports. A WorkerPool sets no resource limits on its Workers by default, and a Worker with no limits reports its whole node as its own capacity. The check then ignores every other pod on the node, so node pressure surfaces as eviction rather than as a placement failure. To make the check reflect the Worker, [set resource limits on Workers]({{< link path="operations/tune-agent-substrate#set-resource-requests-on-workers" >}}). Agent Substrate first narrows the pool to the Workers that can take the Actor. It then picks two of those Workers at random and places the Actor on the less loaded of the two. A Worker's load is the higher of two shares: the share of its Actor slots that are filled, and the share of its CPU and memory that is committed. Sampling two Workers keeps the decision cheap, and it avoids the pile-up that follows when every placement aims at the Worker that looks emptiest. diff --git a/docs-site/content/kagent/1.x/agents/agent-memory.md b/docs-site/content/kagent/1.x/agents/agent-memory.md index 34075f56..898fb5b3 100644 --- a/docs-site/content/kagent/1.x/agents/agent-memory.md +++ b/docs-site/content/kagent/1.x/agents/agent-memory.md @@ -27,10 +27,10 @@ Memories are scoped to the agent and to the user who created them, and carry the Agent memory requires your own PostgreSQL database with the [pgvector](https://github.com/pgvector/pgvector) extension installed. The default database that `kagent install` deploys runs the stock `postgres` image, which omits pgvector, so that database cannot store memories. -> [!WARNING] -> kagent requires a new, empty database. When you move an existing installation to a pgvector database, its existing Sessions do not carry over. +If your installation already uses your own PostgreSQL database, skip step 1 and install pgvector in that database. The vector migration adds only the memory tables, so existing Sessions are kept. - +> [!WARNING] +> On the default database, step 1 moves kagent to a new, empty database. Existing Sessions do not carry over. 1. Prepare the database for kagent and create its two connection Secrets. Follow steps 1 to 3 of [Use an external PostgreSQL instance]({{< link path="operations/operational-considerations#use-an-external-postgresql-instance" >}}) to create the `kagent_owner` and `kagent_readwrite` roles and the `my-postgres-owner` and `my-postgres-readwrite` Secrets. diff --git a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md index 5520a11c..05d5e9b5 100644 --- a/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md +++ b/docs-site/content/kagent/1.x/operations/tune-agent-substrate.md @@ -223,10 +223,10 @@ Because `rustfs.enabled` defaults to `true`, the Agent Substrate chart installs - For Google Cloud Storage, the client uses application default credentials. Bind the components' ServiceAccounts through [Workload Identity Federation for GKE](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity). The chart does not render a Helm value for Google credentials. Set nothing further. - For Amazon S3, the client reads the standard AWS environment and uses a role that the pod assumes. No static key is needed. - - For an S3-compatible store, pass two variables and the static credentials through `atelet.extraEnv`. `AWS_ENDPOINT_URL` names the store's API endpoint, which the client otherwise resolves to Amazon S3. `AWS_S3_USE_PATH_STYLE` puts the bucket name in the request path instead of the hostname, which most S3-compatible stores require. + - For an S3-compatible store, pass two variables and the static credentials through `objectStorePlugin.extraEnv`. `AWS_ENDPOINT_URL` names the store's API endpoint, which the client otherwise resolves to Amazon S3. `AWS_S3_USE_PATH_STYLE` puts the bucket name in the request path instead of the hostname, which most S3-compatible stores require. The chart applies this list to the storage client in both the `atelet` and `ate-api-server` pods. ```yaml - atelet: + objectStorePlugin: extraEnv: - name: AWS_ENDPOINT_URL value: https://objects.example.com @@ -234,7 +234,7 @@ Because `rustfs.enabled` defaults to `true`, the Agent Substrate chart installs value: "true" ``` - + Do not set these variables through `atelet.extraEnv`. That list reaches the `atelet` container, which no longer reads storage settings. 3. Point each Harness at the new bucket. @@ -251,9 +251,9 @@ Because `rustfs.enabled` defaults to `true`, the Agent Substrate chart installs ### How the chart reaches the components -The `atelet` DaemonSet writes a snapshot, and the `ate-api-server` Deployment manages the snapshot. The chart renders `atelet.storageBackend` as the `ATE_STORAGE_BACKEND` variable on the DaemonSet and the Deployment alike, so one Helm value keeps the two in agreement. +The `atelet` DaemonSet writes a snapshot, and the `ate-api-server` Deployment manages the snapshot. Each pod reaches the bucket through an `objectstore-plugin` sidecar container, and only that container reads the storage variables. The chart renders `atelet.storageBackend` as the `ATE_STORAGE_BACKEND` variable on both sidecars, so one Helm value keeps the two in agreement. The `atelet` and `ate-api-server` containers fail to start when `ATE_STORAGE_BACKEND` is set on them directly. -Reading these variables off a running pod helps when a snapshot fails to write. Edit the Helm values rather than the pod environment because the next Helm upgrade overwrites a direct edit. +Reading these variables off the `objectstore-plugin` container helps when a snapshot fails to write. Edit the Helm values rather than the pod environment because the next Helm upgrade overwrites a direct edit. | Variable | Purpose | | -------- | ------- | diff --git a/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md b/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md index 3f1bbd2f..295bf199 100644 --- a/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md +++ b/docs-site/content/kagent/1.x/setup/model-providers/about-model-providers.md @@ -26,10 +26,7 @@ Credentials come from a Kubernetes Secret in the same namespace as the ModelConf Set `tokenPassthrough: true` to send the bearer token from the incoming agent-to-agent (A2A) request to the provider as the API key, rather than reading a key from a Secret. A ModelConfig cannot set both `tokenPassthrough` and `apiKeySecret`. The setting is not available for the `Gemini`, `GeminiVertexAI`, or `AnthropicVertexAI` providers, or on a `codex` or `claude` Harness. - +The egress gateway injects no credential for the provider's host, so the caller's token reaches the provider unchanged. kagent also refuses to attach a static credential to that host. ### Turn off model streaming From 0d86c589282a141a90e42a89110718ff401d38e9 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Tue, 20 Oct 2026 11:39:20 -0500 Subject: [PATCH 09/10] more review comments Signed-off-by: Rachael Graham --- .../kagent/1.x/about/architecture/agent-substrate.md | 2 +- docs-site/content/kagent/1.x/about/core-concepts.md | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md index 7cb9f16d..e5eb942e 100644 --- a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md +++ b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md @@ -28,7 +28,7 @@ An operator never creates a Worker directly. Agent Substrate manages them and ke A WorkerPool exposes a Kubernetes scale subresource, so a HorizontalPodAutoscaler can size a pool from a metric instead of a fixed replica count. Agent Substrate publishes how many Workers in each pool have no free Actor slot. An autoscaler that watches this number adds Workers before the pool fills, instead of after placements fail. - + ### Worker capacity diff --git a/docs-site/content/kagent/1.x/about/core-concepts.md b/docs-site/content/kagent/1.x/about/core-concepts.md index 6f62ac60..2d724e1f 100644 --- a/docs-site/content/kagent/1.x/about/core-concepts.md +++ b/docs-site/content/kagent/1.x/about/core-concepts.md @@ -55,9 +55,7 @@ An operator applies the three custom resources directly. The kagent controller w ## Harness - - -An agent harness is the application layer that executes an agent and adds capabilities around its model. It typically assembles context, invokes the model, executes tool calls, loads skills, feeds results back into the agent loop, and manages session state. A harness can also provide memory, subagents, permissions, approvals, sandboxing, and event streaming. +An agent harness is the software around a model that turns the model into an agent. LangChain defines a harness as "every piece of code, configuration, and execution logic that isn't the model itself," in [The Anatomy of an Agent Harness](https://www.langchain.com/blog/the-anatomy-of-an-agent-harness). A harness typically assembles context, invokes the model, executes tool calls, loads skills, feeds results back into the agent loop, and manages session state. A harness can also provide memory, subagents, permissions, approvals, sandboxing, and event streaming. Examples include [Claude Code](https://code.claude.com/docs/en/how-claude-code-works), [Codex CLI](https://learn.chatgpt.com/docs/codex/cli), [Gemini CLI](https://geminicli.com/docs/), [OpenClaw](https://docs.openclaw.ai/concepts/agent-runtimes), and custom in-house applications. Each of those runs on a local machine with your own shell and files. @@ -162,8 +160,6 @@ This identity also keeps API keys out of the agent. An agent sends a placeholder Each Actor runs in its own sandbox, with its own filesystem and its own durable volume for application data. Two Sessions on the same Agent share no files and cannot see any of each other's state, even when they run on the same Worker. The sandbox also isolates the workload's system calls from the node's kernel, which matters because an agent runs commands that a model chose. For what each sandbox class separates, see [Sandboxing]({{< link path="about/substrate-runtime/sandboxing" >}}). - - ## Subagent tools An agent tool binding can point at another AgentTemplate instead of an MCP server, so a broad-scope agent can hand part of a task to a specialist. For example, a release agent delegates a database question to another agent, gets the answer, and carries on. The specialist keeps its own prompt and its own tools, and the conversation stays with the agent that the caller addressed. From a004a308383661d6adba10c358f1f0662c509e2b Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Tue, 20 Oct 2026 13:37:19 -0500 Subject: [PATCH 10/10] review egresspolicy Signed-off-by: Rachael Graham --- .../substrate-runtime/standalone-sandboxes.md | 2 +- .../kagent/1.x/agents/egress-policies.md | 101 +++++++++--------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md b/docs-site/content/kagent/1.x/about/substrate-runtime/standalone-sandboxes.md index 2340da86..91dd69d7 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 @@ -98,7 +98,7 @@ Editing a template prepares a new revision. A Sandbox that already exists keeps ## Run a command > [!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 standalone sandbox]({{< link path="agents/egress-policies#grant-access-to-a-standalone-sandbox" >}}). Otherwise, move what a command needs into the Sandbox with `kagent sandbox upload`. +> **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. diff --git a/docs-site/content/kagent/1.x/agents/egress-policies.md b/docs-site/content/kagent/1.x/agents/egress-policies.md index dc083739..e7516c09 100644 --- a/docs-site/content/kagent/1.x/agents/egress-policies.md +++ b/docs-site/content/kagent/1.x/agents/egress-policies.md @@ -7,31 +7,21 @@ author: kagent.dev An {{< gloss "Agent" >}}Agent{{< /gloss >}} reaches only the destinations that its AgentTemplate needs, such as its model provider, its Model Context Protocol (MCP) servers, and its skill sources. {{< reuse "kagent-docs/snippets/name-product.md" >}} infers these destinations and compiles them into the agent's [egress policy]({{< link path="about/substrate-runtime/networking-and-egress#policy-generation" >}}). The egress gateway denies every other destination. -An EgressPolicy resource grants access to more destinations. An Agent or a SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. A policy can also reference a credential in a Kubernetes Secret. The gateway adds the credential to each request, so the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. +To grant an Agent or SandboxTemplate access to destinations beyond the inferred set, you can create an EgressPolicy resource. The Agent or SandboxTemplate selects one or more policies by name in its `spec.egressPolicyRefs` field. An EgressPolicy only adds access, and cannot remove a destination that kagent inferred from the model or the tools. -A grant only adds access. A policy cannot remove a destination that kagent inferred from the model or the tools. +A policy can also reference a credential in a Kubernetes Secret. The gateway adds the credential to each request, so the value never enters the agent's sandbox. Typical uses include a package registry that a skill installs from, or an internal API that a tool script calls. ## Before you begin -1. [Create your first agent]({{< link path="get-started/your-first-agent" >}}), so that you have the `my-first-agent` Agent in the `kagent` namespace. - -2. Optional: To grant access to a standalone sandbox, create the `scratch` SandboxTemplate, as described in [Create a SandboxTemplate]({{< link path="about/substrate-runtime/standalone-sandboxes#create-a-sandboxtemplate" >}}). - -3. Check your kagent CLI version. The steps on this page need the {{< reuse "kagent-docs/versions/kagent.md" >}} CLI. - ```bash - 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" >}} - ``` +Create the resource that you want to grant access to. The examples on this page use the `kagent` namespace. +* **Agent**: [Create your first agent]({{< link path="get-started/your-first-agent" >}}), so that you have the `my-first-agent` Agent. Use commands in the "Agent" tabs in the following steps. +* **Standalone sandbox**: Create the `scratch` SandboxTemplate, as described in [Create a SandboxTemplate]({{< link path="about/substrate-runtime/standalone-sandboxes#create-a-sandboxtemplate" >}}). Use commands in the "Standalone sandbox" tabs in the following steps. ## Grant access to a destination -Create an EgressPolicy that lists the destinations, and then select the policy on the Agent. This example grants access to the npm registry and the Node.js download site. +Create an EgressPolicy that lists the destinations that you want to grant access to, and then select the policy on the Agent or SandboxTemplate. -1. Create the EgressPolicy in the namespace of the Agent. An Agent can select only policies in its own namespace. +1. Create the EgressPolicy in the namespace of the Agent or SandboxTemplate. A resource can select only the policies in its own namespace. This example grants access to the npm registry and the Node.js download site. ```yaml kubectl apply -f - <}}Actor{{< /gloss >}}, including the subagents that run in it. +2. Select the policy in the `spec.egressPolicyRefs` field. + {{< tabs >}} + {{% tab name="Agent" %}} + The AgentTemplate and the Harness cannot select a policy, so two Agents that share an AgentTemplate can have different access. The grant applies to the whole {{< gloss "Actor" >}}Actor{{< /gloss >}}, including the subagents that run in it. ```bash kubectl patch agent my-first-agent -n kagent --type merge \ -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' ``` + {{% /tab %}} + {{% tab name="Standalone sandbox" %}} + Every Sandbox that is created from the template receives the grant. + ```bash + kubectl patch sandboxtemplate scratch -n kagent --type merge \ + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' + ``` + {{% /tab %}} + {{< /tabs >}} 3. Check that kagent admitted the grant. When kagent admits every selected policy, the `EgressPoliciesApplied` condition reports `True` with the reason `Applied`. + {{< tabs >}} + {{% tab name="Agent" %}} ```bash kubectl get agent my-first-agent -n kagent \ -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' ``` + {{% /tab %}} + {{% tab name="Standalone sandbox" %}} + ```bash + kubectl get sandboxtemplate scratch -n kagent \ + -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' + ``` + {{% /tab %}} + {{< /tabs >}} Example output: ```console True Applied All requested egress grants are admitted ``` -The grant reaches existing {{< gloss "Session" >}}Sessions{{< /gloss >}} as well as new ones. kagent updates the policy of every running and suspended Actor that the Agent owns, without restarting the Actor. The update is asynchronous. A request that the agent sends immediately after the change might still use the previous policy. +The grant reaches existing workloads as well as new ones. kagent updates the policy of every running and suspended Actor without restarting it. For an Agent, this update covers every {{< gloss "Session" >}}Session{{< /gloss >}}. For a SandboxTemplate, the update covers every Sandbox that was created from the template. The update is asynchronous, so a request that is sent immediately after the change might still use the previous policy. ## Origin format @@ -129,40 +141,29 @@ The gateway can add a credential from a Kubernetes Secret to each request for an | `authentication.prefix` | Optional. Text that the gateway puts before the Secret value. The prefix is literal. Include the trailing space, as in `"Bearer "`. When the prefix is omitted or empty, the header contains only the Secret value. | | `authentication.secretKeyRef` | The Secret and key that store the credential. Both `name` and `key` are required, and the reference cannot be optional. | -3. Select the policy on the Agent. A merge patch replaces the whole `egressPolicyRefs` list. Include every policy that the Agent needs. +3. Select the policy. A merge patch replaces the whole `egressPolicyRefs` list, so the command also keeps `node-dependencies`. Include every policy that the resource needs. + {{< tabs >}} + {{% tab name="Agent" %}} ```bash kubectl patch agent my-first-agent -n kagent --type merge \ -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' ``` - -4. In the script or software development kit (SDK) that the agent uses, send the header with a placeholder value. The gateway replaces a header that the request already sends, and it does not add a header that is missing. Selecting a policy does not configure a tool or set an environment variable. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). - ```bash - curl -H 'Authorization: Bearer kagent-credential-injected' \ - https://tickets.example.com/tickets - ``` - -To rotate the credential, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. - -## Grant access to a standalone sandbox - -A standalone Sandbox has no outbound access by default. To grant access, select the policy on the SandboxTemplate that the Sandbox is created from. Existing Sandboxes from this template receive the change without a restart. - -1. Add the policy to the SandboxTemplate's `spec.egressPolicyRefs` field. + {{% /tab %}} + {{% tab name="Standalone sandbox" %}} ```bash kubectl patch sandboxtemplate scratch -n kagent --type merge \ - -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"}]}}' + -p '{"spec":{"egressPolicyRefs":[{"name":"node-dependencies"},{"name":"ticket-api"}]}}' ``` + {{% /tab %}} + {{< /tabs >}} -2. Check that kagent admitted the grant. +4. In the script or software development kit (SDK) that sends the request, send the header with a placeholder value. The gateway replaces a header that the request already sends, and it does not add a header that is missing. Selecting a policy does not configure a tool or set an environment variable. The client must also trust the egress gateway's certificate authority, which kagent provides to every Actor. For the variables that carry the trust bundle, see [Environment variables on an Actor]({{< link path="agents/agent-harness#environment-variables-on-an-actor" >}}). ```bash - kubectl get sandboxtemplate scratch -n kagent \ - -o jsonpath='{range .status.conditions[?(@.type=="EgressPoliciesApplied")]}{.status} {.reason} {.message}{end}' + curl -H 'Authorization: Bearer kagent-credential-injected' \ + https://tickets.example.com/tickets ``` - Example output: - ```console - True Applied All requested egress grants are admitted - ``` +To rotate the credential, update the Secret. The gateway reads the value from the Secret, so the agent's revision does not change. ## Troubleshoot a grant @@ -190,7 +191,7 @@ In a standalone Sandbox, a destination that no grant allows does not resolve. A ## Access control -An EgressPolicy is namespace-scoped, and kagent does not check who may select it. A user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. +An EgressPolicy is namespace-scoped, and kagent does not check who may select it. A user who can create or edit an Agent or a SandboxTemplate in a namespace can select every EgressPolicy in this namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. For more information about how Kubernetes role-based access control (RBAC) governs who can write these resources, see [The Kubernetes plane]({{< link path="about/substrate-runtime/identity#the-kubernetes-plane" >}}). To control who can grant access, give the permission to create and edit EgressPolicy resources only to administrators. The kagent controller needs only read access to EgressPolicy resources. @@ -198,19 +199,23 @@ The gateway keeps a credential out of the sandbox, but it cannot stop an allowed ## Clean up -1. Remove the policies from the Agent. +1. Remove the policies from the resource. + {{< tabs >}} + {{% tab name="Agent" %}} ```bash kubectl patch agent my-first-agent -n kagent --type json \ -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' ``` - -2. If you granted access to the `scratch` SandboxTemplate, remove the policy from the template. + {{% /tab %}} + {{% tab name="Standalone sandbox" %}} ```bash kubectl patch sandboxtemplate scratch -n kagent --type json \ -p '[{"op":"remove","path":"/spec/egressPolicyRefs"}]' ``` + {{% /tab %}} + {{< /tabs >}} -3. Delete the EgressPolicy resources and the Secret. +2. Delete the EgressPolicy resources and the Secret. ```bash kubectl delete egresspolicy node-dependencies ticket-api -n kagent kubectl delete secret ticket-auth -n kagent