diff --git a/hub-docs/products/insights/agent-sessions/overview.md b/hub-docs/products/insights/agent-sessions/overview.md deleted file mode 100644 index fd011534..00000000 --- a/hub-docs/products/insights/agent-sessions/overview.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: Agent sessions -sidebar_position: 1 -description: Ask questions about the resources across your fleet in a chat session backed by the agent API. -draft: true ---- - -Agent sessions add a conversational surface to Hub. The feature serves the -`agent.hub.upbound.io/v1alpha1` API group, which exposes session and message -endpoints under `/apis/agent.hub.upbound.io/v1alpha1/`. You ask about the -Crossplane resources in your fleet and the agent answers from the state Hub -already indexes. - -:::note -Agent sessions is an alpha feature. It's disabled by default, and its API may -change in incompatible ways between releases. See the [feature -lifecycle](../../../reference/feature-releases.md). Set the `AgentSessions` gate in -your Helm values to turn it on. It also needs an Anthropic API key, or -`hub-core` refuses to start. See [Feature -flags](../../../reference/feature-flags.md). -::: - -## Concepts - -| Resource | What it declares | -| --- | --- | -| `Session` | A conversation. Cluster-scoped, owned by the user who created it. | -| `sessions/messages` | The subresource you post a message to. The reply streams back on the same request. | - -A `Session` has one settable field, `spec.title`. Hub assigns the session name -itself, ignoring any name you supply on create. `status.messages` holds the -conversation history and is only populated on a get, not on a list. - -## What the agent can see - -The agent answers using two read-only tools against Hub's indexed fleet state: - -| Tool | What it returns | -| --- | --- | -| `query_resources` | A filtered list of resources across connected control planes, by kind, group, control plane, realm, space, namespace, health, or free-text search. Capped at 100 results per call. | -| `get_resource` | One resource by name, including its full Kubernetes object with `metadata.managedFields` stripped. | - -Both read what the connectors report, so the agent can't see a resource a -connector doesn't sync and can't reach a control plane directly. Neither tool -writes. See [Connect a control -plane](../../../howtos/connect-control-plane.md) for what the connector syncs by -default. - -## Access - -Sessions are private to the user who created them. Every session endpoint scopes -its lookup by the authenticated user's name, so you can't read or delete another -user's session. - -:::warning -The agent doesn't scope the resources it reads to the caller. In this release -both agent tools query with an unconstrained view, so any user who can create a -session can ask it about every resource in the hub, including control planes in -realms they can't otherwise view. Upbound plans to release per-session -authorization scoping in a future release. - -Access to the feature is the control mechanism today. Organization admins have -`session` and `sessions/messages` resources access by default. Other users have -no access to the feature. - -Don't enable this feature if that grant is wider than the fleet visibility you -intend. See [Access and authorization](../../../howtos/rbac.md). -::: - - -## The Anthropic API dependency - - -The agent calls the Anthropic API using the `claude-sonnet-4-6` model and doesn't -start without an API key. Plan for both of these: - -- Hub needs network egress to the Anthropic API from the `hub-core` namespace. -- Conversation content leaves your cluster. The Anthropic API receives resource - names, labels, and status messages as part of the conversation. - -## Limits - -| Limit | Value | -| --- | --- | -| Request body | 1 MB | -| Tool call timeout | 30 seconds | -| Conversation history loaded per session | 500 events | -| Results per `query_resources` call | 100 | - - -Because the message stream has no server write deadline, it won't cut off long -replies. The stream ends when the client disconnects, or when the Anthropic API hits -its own timeout. - - -## See also - -- [Start a troubleshooting session](troubleshooting-session.md) -- [Feature flags](../../../reference/feature-flags.md) -- [Feature lifecycle](../../../reference/feature-releases.md) diff --git a/hub-docs/products/insights/agent-sessions/troubleshooting-session.md b/hub-docs/products/insights/agent-sessions/troubleshooting-session.md deleted file mode 100644 index 5cafd9bf..00000000 --- a/hub-docs/products/insights/agent-sessions/troubleshooting-session.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Start a troubleshooting session -sidebar_position: 3 -description: Create a session, ask about a failing resource, and read the streamed reply. -draft: true ---- - -This guide walks through one use case: a composite resource somewhere in the -fleet isn't becoming `Ready`, and you want to find it and understand why without -knowing which control plane it's on. - -## Before you start - -- [Enable agent sessions](../../../reference/feature-flags.md) and confirm the API group responds. -- An account in an organization admin group. No other role is granted the - session resources. - - -The examples use two shell variables. Set `HUB_URL` to the base URL clients use -to reach `hub-core`, the same value as `hub-core.api.externalURL`: - -```shell -HUB_URL=https://api. -``` - -`TOKEN` is a **hub** token, not the token your identity provider issued. -`hub-core` rejects an IdP access or ID token presented directly with a `401`. -Exchange the IdP token for a hub token first, using the [RFC -8693](https://datatracker.ietf.org/doc/html/rfc8693) endpoint: - -```shell -TOKEN=$(curl -sS -X POST \ - "$HUB_URL/apis/tokenexchange.hub.upbound.io/v1alpha1/tokenexchangerequests" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ - -d "subject_token=$IDP_TOKEN" \ - -d "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \ - -d "scope=upbound:org:default" | jq -r .access_token) -``` - -`$IDP_TOKEN` is an access token for the identity provider you registered with -Hub, given by your provider. Hub tokens are short-lived, so -repeat the exchange when calls start returning `401`. - -`scope` names the organization. A self-hosted Hub is a single organization named -`default`. See [Access and authorization](../../../howtos/rbac.md). - -## Step 1: Create a session - -Post a `Session`. The `Session` spec contains only the `title` field and Hub assigns the name: - -```shell -SESSION=$(curl -sS "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "apiVersion": "agent.hub.upbound.io/v1alpha1", - "kind": "Session", - "spec": {"title": "Composites stuck not ready"} - }' | jq -r '.metadata.name') - -echo "$SESSION" -``` - -The name looks like `ses_2f9k...`. Hub ignores a name you set yourself, so read -it back from the response rather than choosing one. - -## Step 2: Ask a question - -Post to the `messages` subresource. The reply streams back on the same request -as server-sent events, so pass `-N` to stop curl buffering it: - -```shell -curl -sSN "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions/$SESSION/messages" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"content": "Which composite resources are not ready across the fleet, and what is the most common reason?"}' -``` - -Each frame is a `data:` line holding one JSON event, and the stream ends with a -literal `data: [DONE]`: - -```text -data: {"type":"tool_call","content":"{\"tool\":\"query_resources\",\"arguments\":{\"crossplaneType\":\"xr\",\"ready\":false}}"} - -data: {"type":"tool_result","content":"..."} - -data: {"type":"text_delta","content":"Three composites"} - -data: {"type":"text_delta","content":" are not ready."} - -data: [DONE] -``` - -| Event `type` | Meaning | -| --- | --- | -| `message` | A complete message. | -| `text_delta` | One chunk of the reply. Concatenate these in order. | -| `tool_call` | The agent called a tool. `content` holds `{"tool": ..., "arguments": ...}`. | -| `tool_result` | What the tool returned. | -| `error` | The turn failed. The reply ends here. | - -The `tool_call` events are worth reading. They show which query the agent ran, -which tells you whether it looked where you meant. - -## Step 3: Narrow the conversation - -Sessions are stateful, so the next message continues the same thread. Post again -to the same session and refer to the earlier answer: - -```shell -curl -sSN "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions/$SESSION/messages" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"content": "Show me the full object for the first one, and explain the Synced condition."}' -``` - -The agent calls `get_resource` for that resource and answers from the full -Kubernetes object. - -## Step 4: Read the history - -A get returns the conversation in `status.messages`: - -```shell -curl -sS "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions/$SESSION" \ - -H "Authorization: Bearer $TOKEN" | jq '.status.messages' -``` - -Each entry has a `role` of `user`, `assistant`, or `tool`, a `timestamp`, and -either `content` or `toolCalls`. - -Listing sessions omits the messages for performance, so use a get when you want -the transcript: - -```shell -curl -sS "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions" \ - -H "Authorization: Bearer $TOKEN" \ - | jq -r '.items[] | "\(.metadata.name)\t\(.spec.title)"' -``` - -## Step 5: Clean up - -Rename a session with a `PUT`, which updates the title and nothing else, or -delete it: - -```shell -curl -sS -X DELETE \ - "$HUB_URL/apis/agent.hub.upbound.io/v1alpha1/sessions/$SESSION" \ - -H "Authorization: Bearer $TOKEN" -``` - -## Troubleshooting - - -| Symptom | Cause | -| --- | --- | -| `404` on the messages endpoint | The session doesn't exist, or it belongs to another user. Sessions are private to their creator, and posting a message never creates one. Create the session first. | -| `400 content is required` | The body was empty or only whitespace. | -| `401` | No authenticated user on the request. | -| One `error` event, then the stream ends | The turn failed. An invalid Anthropic API key surfaces here rather than at startup. | -| `agent produced no response` | The agent returned no events at all. Check the `hub-core` logs and that the Anthropic API is reachable from the `hub-core` namespace. | -| The reply stops mid-thought after 30 seconds | A tool call hit its 30 second timeout. | - - -## See also - -- [Agent sessions overview](overview.md) -- [Filtering resource lists](../resource-exploration/filtering-resources.md), which - uses the same query surface the agent's `query_resources` tool calls. diff --git a/hub-docs/products/insights/agent-skills/overview.md b/hub-docs/products/insights/agent-skills/overview.md new file mode 100644 index 00000000..ef5e8826 --- /dev/null +++ b/hub-docs/products/insights/agent-skills/overview.md @@ -0,0 +1,149 @@ +--- +title: Agent skills +sidebar_position: 1 +description: Install the upbound-hub Agent Skill so your coding agent answers questions about your fleet from Hub's API instead of guessing. +--- + +An [Agent Skill](https://agentskills.io/) is a set of instructions a coding agent +loads when a task matches it. Upbound publishes skills for the Upbound Platform +in [upbound/skills](https://github.com/upbound/skills), where `upbound-hub` +teaches an agent to work with Hub: how to query control planes, spaces, realms, +types, packages, resources, identity providers and the image catalog, and how to +avoid the handful of quirks that turn a reasonable query into a wrong answer. + +The skill installs on your own machine. Nothing runs in `hub-core`, no feature +gate turns it on and Hub needs no configuration to support it. Once the plugin is +in place you ask your agent an ordinary question about the fleet's health, and it +signs itself in, queries Hub and answers from your fleet instead of from a guess. + +## Why an agent needs a skill + +Hub presents a Kubernetes-shaped API, so an agent treats it like any other +cluster and gets the ordinary questions right. Two places where Hub departs from +that expectation produce the answers worth worrying about, and an agent hands you +both without hedging. + +Counting is the clearest one. A resource list reports `metadata.total.count`, but +that value saturates at 1000 and then reports a `relation` of `gt` without saying +by how much. An agent that pages the list to count returns a lower bound and +presents it as a fleet total. The skill routes counting questions to the +aggregation endpoint, which returns a real number. + +Health is the subtler one. Most aggregated records carry no conditions at all, +and `Ready=Unknown` is the normal state for the whole window a managed resource +takes to come up. Divide failing resources by the fleet total and the result +looks precise while meaning nothing. The skill has the agent report three numbers +instead: how many resources are assessable, how many explicitly fail and how many +never reported. + +## Install + +Add the marketplace, then install the plugin: + +```text +/plugin marketplace add upbound/skills +/plugin install upbound@upbound +``` + +The first command registers Upbound as a plugin source and the second installs +the skills it publishes. + +To try the skill without installing it, clone the repository and point your agent +at the working copy: + +```shell +git clone https://github.com/upbound/skills upbound-skills +claude --plugin-dir upbound-skills +``` + +## Supported agents + +Claude Code is the supported agent today, and Upbound plans to add others. The +skills carry no vendor-specific front matter, so copying `skills/upbound-hub/` +into another agent's skills directory should load it. Upbound doesn't test that +path though, and the scripts assume `bash`, `curl`, `jq` and `column`. + +## Prerequisites + +The skill's scripts call `bash`, `curl`, `jq`, `column` and `shasum`, which a +developer machine tends to have already. Add `kubectl` if you plan to write. +Reads don't need it. + +Beyond that you need the base URL clients use to reach `hub-core`, the same value +as `hub-core.api.externalURL`, and a browser for the first sign-in. The skill +asks for the endpoint the first time it runs and saves the answer, so there's +nothing to set up in advance. + +## What runs on your machine + +:::warning +Skills are instructions, not sandboxed code. The scripts run with your +credentials and your network access, and anything they print reaches the agent's +transcript. Read the skill and its scripts before you install them, and see +[SECURITY.md](https://github.com/upbound/skills/blob/main/SECURITY.md) in the +repository. +::: + +On its first run the skill downloads a credential helper from +`storage.googleapis.com/upbound-hub-artifacts` and checks it against a published +SHA-256. That checksum shares an origin with the binary, so it catches a +corrupted download rather than a compromised source. + +The skill saves your endpoint to `${XDG_CONFIG_HOME:-~/.config}/upbound/hub.env` +and the credential it obtains lasts about 90 days. Set `HUB_API_URL` to override +the saved endpoint for a one-off query against another deployment, and +`HUB_CA_FILE` to a PEM bundle when your system trust store doesn't include Hub's +CA. `HUB_INSECURE=1` disables TLS verification, so don't set it. + +## What the agent can see + +The agent acts with your Hub token, so it sees the realms your permissions grant +and nothing else. Two people asking the same question get different answers when +they hold different access, which is worth remembering before you compare notes +on a fleet number. See [Access and authorization](../../../howtos/rbac.md). + +## What the agent can change + +Hub has two surfaces and only one of them accepts writes. + +| Surface | Verbs | +|---|---| +| Hub's own objects: `controlplanes`, `lenses`, `realmrolebindings` and the registration resources | Create, update, delete | +| `realms` and `spaces` | Create, delete. Neither has an `update` verb | +| The fleet resources Insights aggregates: `resources`, `resourcestats`, `resourcerelationships` and `resourcerelationshiptrees` | Read only | + +The practical consequence is that you can't change a Crossplane resource through +Hub. Insights indexes what the connectors report, so a managed or composite +resource is a read-only record there, and changing one means talking to the +control plane that owns it. Ask the agent to fix a failing resource and you get a +diagnosis rather than a fix. + +Writes that Hub does accept go through `kubectl` against the Hub API. The skill +builds its own kubeconfig pointing at your Hub endpoint, so it ignores the +contexts in your `KUBECONFIG` and you need no cluster access to any control +plane. What it does need is `kubectl` on your `PATH`, network reach to Hub and +Hub permissions for the verb. To keep a `hub` context of your own alongside it, +[Configure kubectl for the hub](../../../howtos/configure-kubectl.md) sets one up +with the same credential helper. + +Hub's differences from a cluster show up here. It serves no `patch` verb, so +`kubectl apply` against an object that already exists returns a `405` and the +skill uses `replace` instead. The missing `update` verb on realms matters more +than it looks: deleting a realm removes the namespace every control plane in it +lives in, so the skill treats realm deletion as something to agree on first +rather than a way to edit one. Before any write it asks +`selfsubjectaccessreviews` whether you're permitted, rather than attempting the +operation to find out. + +## Related resources + +**How-to guides** + +- [Query your fleet with an agent](query-with-an-agent.md) +- [Query your fleet](../resource-exploration/query.md) +- [Access and authorization](../../../howtos/rbac.md) + +**Linked concepts** + +- [Insights](../overview.md) +- [Hub architecture](../../../concepts/architecture.md) diff --git a/hub-docs/products/insights/agent-skills/query-with-an-agent.md b/hub-docs/products/insights/agent-skills/query-with-an-agent.md new file mode 100644 index 00000000..43a8488b --- /dev/null +++ b/hub-docs/products/insights/agent-skills/query-with-an-agent.md @@ -0,0 +1,164 @@ +--- +title: Query your fleet with an agent +sidebar_position: 2 +description: Install the upbound-hub skill, sign in to Hub, and ask a coding agent questions about the resources running across your fleet. +--- + +This guide walks through asking a coding agent about your fleet: installing the +`upbound-hub` skill, letting it sign you in, asking a first question and +checking that the answer came from where you think it did. + +:::note +The steps use Claude Code, the supported agent today. See [Supported +agents](overview.md#supported-agents) for the state of the others. +::: + +## Prerequisites + +- Claude Code, and the tools the skill's scripts call: `bash`, `curl`, `jq`, + `column` and `shasum` +- The base URL clients use to reach `hub-core`, the same value as + `hub-core.api.externalURL` +- An account with read access to at least one realm. See [Access and + authorization](../../../howtos/rbac.md) +- At least one [connected control plane](../../../howtos/connect-control-plane.md), + or the answers have nothing to report on + +## Step 1: Install the skill + +1. Register the marketplace and install the plugin. + + ```text + /plugin marketplace add upbound/skills + /plugin install upbound@upbound + ``` + +2. Confirm the agent picked up the skill. + + Ask it what skills it has available. `upbound-hub` should appear, described + as the skill for querying and mutating Upbound Hub. + +## Step 2: Point the skill at your Hub + +The skill sets itself up the first time you ask it a fleet question. It installs +its credential helper, checks the download against a published SHA-256 and signs +you in, so there's no setup command for you to run. All it needs from you is the +endpoint and a browser. + +1. Ask an ordinary question to trigger setup. + + > How many resources are unhealthy across the fleet? + +2. Give the agent your Hub API endpoint when it asks. + + The skill has nowhere to discover this on its own, which is why it asks. It + saves the value to `${XDG_CONFIG_HOME:-~/.config}/upbound/hub.env` and doesn't + ask again on this machine. + +3. Complete the sign-in in your browser. + + The setup script opens a browser window. Finish the sign-in there and the + agent carries on with your question. The credential lasts about 90 days. + +## Step 3: Read the first answer + +Fleet health is the question agents get wrong most often, which makes the first +answer a good test of whether the skill loaded. A correct one separates three +figures rather than collapsing them into a single percentage: + +| Figure | What it counts | +|---|---| +| Assessable | Resources reporting `Ready` explicitly, either `True` or `False` | +| Failing | Resources where `Ready` is explicitly `False` | +| Not reporting | Resources carrying no conditions, or reporting `Unknown` | + +Most records in a fleet land in that last row, which is why an "X% unhealthy" +figure divides by a denominator that doesn't mean anything. If your agent answers +with one percentage it isn't using the skill, so check that the plugin installed +and that `upbound-hub` loaded for the question you asked. + +A fleet-wide answer counts `Ready` alone, because the aggregation endpoint counts +each condition on its own axis and a resource can fail more than one, so the +counters overlap and you can't add them up. A per-control-plane answer does treat +`Synced` or `Healthy` being `False` as failing, so the two scopes report different +totals. Ask which one a number uses. + +It helps to remember that `Unknown` isn't a failure. It's the normal state for +the whole window a managed resource takes to come up, and `Synced=False` with a +reason of `ReconcilePaused` means someone chose to pause the resource. + +## Step 4: Narrow the question + +The conversation carries context, so follow up in your own words rather than +restating the whole question. Questions the skill handles well: + +> List the control planes and group them by phase. +> +> List the unhealthy resources in `prod-1`, with the reason for each. +> +> What CRDs and XRDs exist for AWS? +> +> What changed in the last hour? + +Two habits make the answers better. Name the control plane, realm or kind you +care about, because a question with no scope leaves the agent to choose one for +you. And ask for the reason alongside the count: condition messages are +filterable, so the same question that returns a count of three can tell you which +three and why. + +## Step 5: Check what the agent ran + +The agent reports what it found without showing how it got there, so an answer +you plan to act on is worth a second look. + +1. Ask the agent to show its work. + + > What endpoint did you call to get that? + + It should name a script or a Hub API path. That tells you the scope it + queried, which is where a plausible wrong answer tends to originate. + +2. Reproduce the query yourself when the answer matters. + + The same data comes straight from the API. See [Query your + fleet](../resource-exploration/query.md) for the endpoints and the CEL filter + syntax. + +3. Check the age of the data before trusting an absence. + + Hub's view lags its sources, so a resource created moments ago may not appear + yet. Each `resources` record carries `hub.lastSyncTime`, and the agent should + check it against the clock before telling you something doesn't exist. Hub's + own objects, such as control planes, spaces and realms, carry no such + freshness field. + +## Step 6: Know where querying stops + +Every step so far reads. Hub serves the fleet resources Insights aggregates as +read-only, so asking the agent to fix a failing resource gets you a diagnosis +rather than a change, and making the change means working in the control plane +that owns the resource. + +Hub's own objects are a different matter. The skill can create and delete control +planes, realms, spaces and lenses, and update control planes and lenses. Neither +`realms` nor `spaces` has an `update` verb. [What the agent can +change](overview.md#what-the-agent-can-change) covers that along with the +permissions and quirks that come with each. + +## Troubleshooting + +| Symptom | Cause | +| --- | --- | +| The agent asks for a Hub API endpoint | The skill has no endpoint for this machine yet. Give it the base URL of `hub-core`. It asks once. | +| Sign-in opens again, or calls start failing after about 90 days | The credential expired. Complete the browser sign-in and the agent continues. | +| Certificate or TLS verification errors | Your system trust store doesn't include Hub's CA. Set `HUB_CA_FILE` to a PEM bundle. Don't reach for `HUB_INSECURE=1`. | +| A resource you just created doesn't appear | Hub hasn't synced it yet. Check `hub.lastSyncTime` and ask again. | +| A count stops at 1000 | The agent paged a list to count instead of using the aggregation endpoint. Ask which endpoint it called. | +| Two questions return different totals for the same thing | Type definitions and resource stats disagree by design, partly because composition fans one logical resource out into several records. Ask for both numbers. | + +## See also + +- [Agent skills](overview.md) +- [Query your fleet](../resource-exploration/query.md) +- [Resource filtering](../resource-exploration/overview.md) +- [Insights](../overview.md) diff --git a/hub-docs/reference/feature-flags.md b/hub-docs/reference/feature-flags.md index c39553e3..9b5a20fe 100644 --- a/hub-docs/reference/feature-flags.md +++ b/hub-docs/reference/feature-flags.md @@ -54,16 +54,6 @@ logs. Every gate in this release is alpha, and most default to `false`. | `Metrics` | `false` | The metrics ingest endpoint and the `metrics.hub.upbound.io` API group. Requires `hub-core.otelGateway.enabled=true`. | | `Registry` | `false` | The `registry.hub.upbound.io` API group, providing the `Connection` resource (with its `verify` subresource) and the `Repository` resource. | - - - - - - - - - - ### Aggregated types The `AggregatedTypes` gate serves the fleet-wide `typedefinitions` and diff --git a/src/sidebars/hub.js b/src/sidebars/hub.js index 6066b398..37f1cd62 100644 --- a/src/sidebars/hub.js +++ b/src/sidebars/hub.js @@ -52,6 +52,12 @@ module.exports = { label: "Metrics", customProps: { badge: "Preview" }, }, + { + type: "category", + label: "Agent skills", + link: { type: "doc", id: "products/insights/agent-skills/overview" }, + items: ["products/insights/agent-skills/query-with-an-agent"], + }, ], }, ],