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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 94 additions & 20 deletions docs-site/content/kagent/latest/agents/egress-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,46 @@ Create the resource that you want to grant access to. The examples on this page

## Grant access to a destination

Create an EgressPolicy that lists the destinations that you want to grant access to, and then select the policy on the Agent or SandboxTemplate.
Create an EgressPolicy that lists the destinations that you want to grant access to, and then select the policy on the Agent or SandboxTemplate. The following example shows the steps for an Agent.

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.
1. Check that the destination is blocked before you grant access.
{{< tabs >}}
{{% tab name="Agent" %}}
Create a Session and ask the Agent to open a connection with its `bash` tool. (The kagent runtime image does not include `curl`.)
```bash
export SESSION_ID=$(kagent agent session create --agent my-first-agent -o json | jq -r '.session.id')
kagent agent invoke --session $SESSION_ID \
--task "Use your bash tool to run exactly this command, then reply with only its output: bash -c 'exec 3<>/dev/tcp/registry.npmjs.org/443 && echo reachable || echo blocked'"
```

Example output:
```console
blocked
```
{{% /tab %}}
{{% tab name="Standalone sandbox" %}}
Create a Sandbox from the template, and run the check in it. The check uses `bash`, so the tools image of the template must include `bash`.
```bash
export SANDBOX_ID=$(kagent sandbox create scratch --request-id egress-check -o json | jq -r '.id')
kagent sandbox exec $SANDBOX_ID -- bash -c 'exec 3<>/dev/tcp/registry.npmjs.org/443 && echo reachable || echo blocked'
```

Example output:
```console
Process proc-5182872b055b6f61a9dc86cc in sandbox 0198c3f1-2a44-7c90-b5e1-9d8f3a7b2c04: started (stdout offset 0, stderr offset 0)
blocked
bash: line 1: registry.npmjs.org: Name or service not known
bash: line 1: /dev/tcp/registry.npmjs.org/443: Invalid argument
Process proc-5182872b055b6f61a9dc86cc: PROCESS_STATE_EXITED, exit_code=0
```

The process exits with code `0` in either case, so read the word that the command prints and not the exit code.
{{% /tab %}}
{{< /tabs >}}

A destination that no grant allows does not resolve, so the connection fails.

2. 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 - <<EOF
apiVersion: api.kagent.dev/v1alpha3
Expand All @@ -41,10 +78,10 @@ Create an EgressPolicy that lists the destinations that you want to grant access
| 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`. For the accepted forms, see [Origin format](#origin-format). |
| `destinations[].origin` | A scheme, a DNS hostname, and an optional port, such as `https://registry.npmjs.org` or `https://search.example.com:8443`. An origin has no path, and a wildcard can replace only the leftmost label of the hostname, such as `https://*.example.com`. For all of the accepted forms and the limits, see [Origin format and limits](#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 in the `spec.egressPolicyRefs` field.
3. Update the Agent or SandboxTemplate to reference 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.
Expand All @@ -62,7 +99,7 @@ Create an EgressPolicy that lists the destinations that you want to grant access
{{% /tab %}}
{{< /tabs >}}

3. Check that kagent admitted the grant. When kagent admits every selected policy, the `EgressPoliciesApplied` condition reports `True` with the reason `Applied`.
4. Check that kagent accepted the policy. The `EgressPoliciesApplied` condition reports `True` with the reason `Applied` when kagent has validated every selected policy and sent its rules to the egress gateway. The condition does not test a request, so it can report `True` for a moment before running Sessions pick up the change.
{{< tabs >}}
{{% tab name="Agent" %}}
```bash
Expand All @@ -83,24 +120,38 @@ Create an EgressPolicy that lists the destinations that you want to grant access
True Applied All requested egress grants are admitted
```

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

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.
5. Send the same request again. The condition from the previous step does not test a request, so this result shows whether the Agent or Sandbox reaches the destination now.
{{< tabs >}}
{{% tab name="Agent" %}}
Use the same Session.
```bash
kagent agent invoke --session $SESSION_ID \
--task "Use your bash tool to run exactly this command, then reply with only its output: bash -c 'exec 3<>/dev/tcp/registry.npmjs.org/443 && echo reachable || echo blocked'"
```

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`.
Example output:
```console
reachable
```
{{% /tab %}}
{{% tab name="Standalone sandbox" %}}
Use the same Sandbox. It picks up the grant without being created again.
```bash
kagent sandbox exec $SANDBOX_ID -- bash -c 'exec 3<>/dev/tcp/registry.npmjs.org/443 && echo reachable || echo blocked'
```

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.
Example output:
```console
Process proc-360e1e86c15990081d976630 in sandbox 0198c3f1-2a44-7c90-b5e1-9d8f3a7b2c04: started (stdout offset 0, stderr offset 0)
reachable
Process proc-360e1e86c15990081d976630: PROCESS_STATE_EXITED, exit_code=0
```
{{% /tab %}}
{{< /tabs >}}

The API server rejects an `egressPolicyRefs` list that selects more than 16 policies and a policy that lists more than 64 destinations. The compiled policy limit also counts the destinations that kagent inferred, so an Agent with many tools reaches it sooner.
The update is asynchronous, so if the result still says `blocked`, wait a few seconds and send the request again.

| Limit | Value |
| ----- | ----- |
| Policies that one Agent or SandboxTemplate selects | 16 |
| Destinations in one policy | 64 |
| Rules in the compiled policy of one Actor | 256 |
| Credential headers for one origin | 16 |
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.

## Inject a credential

Expand Down Expand Up @@ -220,7 +271,7 @@ The gateway mints, injects, and refreshes the token, so the policy needs no Secr

## Troubleshoot a grant

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 `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 UI follows the same rule, so an Agent whose condition is `False` does not appear as failed.

The condition message names the affected policies, and the reason identifies the problem.

Expand All @@ -242,6 +293,29 @@ A grant conflict happens in the following cases:

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" >}}).

A destination that redirects to another origin needs a grant for that origin as well. For example, a package registry often serves downloads from a separate host, so an install fails at that host until the policy grants it too.

## Origin format and limits {#origin-format}

An origin can have only one value for each 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 API server rejects `*` and `https://*.com`.

The API server rejects an `egressPolicyRefs` list that selects more than 16 policies and a policy that lists more than 64 destinations. The compiled policy limit also counts the destinations that kagent inferred, so an Agent with many tools reaches it sooner.

| Limit | Value |
| ----- | ----- |
| Policies that one Agent or SandboxTemplate selects | 16 |
| Destinations in one policy | 64 |
| Rules in the compiled policy of one Actor | 256 |
| Credential headers for one origin | 16 |

## Manage policies in the UI {#manage-policies-in-the-ui}

You can create, edit, and delete EgressPolicy resources in the **Policies** page of the kagent UI without `kubectl`. The **Egress Policies** tab on the page lists the policies that you can read, with each policy's name, namespace, origins, and authentication type, which can be **None**, **Secret key**, or **Actor JWT**. You can filter the list by name, origins, or namespaces by using the search box. For how to open the UI, see [Launch the UI]({{< link path="ui/launch-ui" >}}).

For who can reach this path, see [Access control](#access-control).

## 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 that namespace and use its credentials. Treat the namespace as the boundary for sharing policies and the Secrets that they reference. To give each team a namespace of its own, see [Run agents in multiple namespaces]({{< link path="operations/multiple-namespaces" >}}).{{% upstream %}} 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" >}}).{{% /upstream %}}
Expand Down
58 changes: 58 additions & 0 deletions docs-site/content/kagent/latest/examples/a2a-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The following table covers the methods that this example uses. The `TaskPushNoti
| `SendMessage` | Sends a message and returns the task after the agent either finishes the turn or pauses for a person. |
| `SendStreamingMessage` | Sends a message and streams events as the agent works. |
| `GetTask` | Reads a task that a previous call created. |
| `ListTasks` | Lists the tasks of one Session, or of every Session that the Agent has and the caller can see. |
| `CancelTask` | Stops a task that is still running. |
| `CreateTaskPushNotificationConfig` | Adds or replaces a callback for a task that is still active. |
| `ListTaskPushNotificationConfigs` | Lists the active callbacks for one task. |
Expand Down Expand Up @@ -217,6 +218,34 @@ A task outlives the call that created it, so a caller that lost its connection c

A task that is still running reports `TASK_STATE_WORKING` and does not have artifacts yet. To stop a task that is still running, call `CancelTask` with the same `id`.

## List tasks {#list-tasks}

`ListTasks` finds tasks without a task ID. Use it to recover a task ID that a caller lost, or to see what an Agent has run.

Setting `contextId` lists the tasks of that one Session. Leaving it out lists the tasks of every Session that the Agent has and the caller can see.

1. List the completed tasks of a Session, with a page size of 5.
```bash
grpcurl -plaintext \
-H 'A2A-Version: 1.0' \
-d '{"tenant":"'"$AGENT"'","contextId":"'"$SESSION_ID"'","status":"TASK_STATE_COMPLETED","pageSize":5}' \
localhost:8083 lf.a2a.v1.A2AService/ListTasks
```

2. Narrow or shape the list with the following request fields. Every field is optional.

| Field | Meaning |
| ----- | ------- |
| `contextId` | Limits the list to one Session. |
| `status` | Returns only the tasks in this state, such as `TASK_STATE_COMPLETED`. |
| `statusTimestampAfter` | Returns only the tasks whose status changed after this timestamp. |
| `pageSize` | Sets the number of tasks in a page. The default is `50`. A value above `100` fails with an invalid request error instead of being capped. |
| `pageToken` | Continues a list from the `nextPageToken` of the previous response. |
| `historyLength` | Keeps only the most recent messages of each task. Setting `0` returns none, and omitting the field returns every message. |
| `includeArtifacts` | Returns each task's artifacts. The default is `false`, so a list omits artifacts unless you ask for them. |

3. Read the response. It holds the page of `tasks`, the `pageSize` that applied, and `totalSize`, which counts every task that matches the filters across all pages. The `nextPageToken` value is empty on the last page, so a caller keeps requesting pages until `nextPageToken` comes back empty. To read the artifacts of a task that a list returned, set `includeArtifacts` or call `GetTask` with the task's `id`.

## Read token usage {#read-token-usage}

The `kagent` harness reports the model tokens that a task uses through the optional usage extension, `https://kagent.dev/extensions/usage/v1`. The agent card of an Agent on this harness advertises the extension. A `byo` image that is built with the Go or Python helper also reports usage, but the agent card does not advertise the extension. The `codex` and `claude` harnesses do not report usage.
Expand Down Expand Up @@ -301,6 +330,35 @@ The kagent CLI wraps the push notification configuration methods, so this sectio
kagent agent session push delete $SESSION_ID $PUSH_TASK_ID audit-webhook
```

### Handle deliveries

A delivery is an A2A `StreamResponse` that holds a `statusUpdate` with the task ID, the context ID, and the latest published status, including any status message. Ordinary progress updates do not trigger a callback. For example, the receiver gets this body when a task completes:

```json
{
"statusUpdate": {
"taskId": "task-123",
"contextId": "context-456",
"status": { "state": "TASK_STATE_COMPLETED" }
}
}
```

Build the receiver around these rules:

* Any `2xx` response acknowledges a delivery. Every other response counts as a failed attempt, and a redirect is one of them, so register the final callback URL.
* A delivery can arrive more than once, or out of order. Treat `GetTask` as the source of truth for the task's current state.
* kagent makes up to ten attempts for each state that a task publishes, and the count carries across controller restarts. After the last attempt, kagent drops that delivery but keeps the callback registered, so the next state still reaches the receiver.
* kagent does not expose delivery status, so nothing reports whether a delivery failed.

To tune delivery, configure the following Helm values under `config.push`.

| Value | Default | Effect |
| ----- | ------- | ------ |
| `maxConcurrency` | `10` | The number of deliveries that each controller replica sends at once. Setting `1` sends them one at a time. |
| `sendTimeout` | `5s` | How long one HTTP attempt can take before it counts as failed. |
| `recoveryInterval` | `60s` | How often an idle controller scans for deliveries that a missed wake-up left pending. |

### Secure callback deliveries

A callback URL is a public endpoint that receives task state from kagent. Before you register a callback in production, decide how the receiver establishes that a delivery came from kagent.
Expand Down
Loading
Loading