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
15 changes: 15 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ const config: Config = {
colorMode: {
respectPrefersColorScheme: true,
},
// Release banner, above the navbar on every page. "documentation" links to
// /docs/, which serves the newest release (lastVersion), so the link keeps
// pointing at the current docs without an edit when the next one is cut.
// The `id` is the key Docusaurus stores when a reader dismisses the bar:
// change it for the next release so the new bar shows to everyone, and
// leave it alone otherwise or the bar reappears for people who closed it.
// Colours are deliberately NOT set here — `backgroundColor`/`textColor`
// render as inline styles, which would override the theme-aware rules in
// src/css/custom.css.
announcementBar: {
id: 'rossoctl-0-8-release',
content:
'\u{1F389} rossoctl 0.8 is here! Explore the updated <a href="/docs/">documentation \u2192</a>',
isCloseable: true,
},
navbar: {
title: 'rossoctl',
logo: {
Expand Down
39 changes: 39 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -701,3 +701,42 @@ h6 {
.plugin-id-default .table-of-contents__link--active {
font-weight: 500;
}

/* ===== RELEASE ANNOUNCEMENT BAR =====
Brand-red bar above the navbar announcing a release. `--ifm-color-primary`
is the same red in light and dark (see the [data-theme='dark'] block), so a
single rule covers both themes.
theme-classic's own .announcementBar class sets a white background and a
bottom border with the same specificity as this one, so these declarations
are marked !important rather than relying on stylesheet order. */
.theme-announcement-bar {
background-color: var(--ifm-color-primary) !important;
color: #ffffff !important;
border-bottom: none !important;
font-size: 0.95rem;
font-weight: 500;
}

/* The default link colour is the same brand red as the bar, which would be
invisible. */
.theme-announcement-bar a {
color: #ffffff;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 2px;
}

.theme-announcement-bar a:hover {
color: #ffffff;
text-decoration-thickness: 2px;
}

/* The close button draws with currentColor. */
.theme-announcement-bar button {
color: #ffffff;
opacity: 0.85;
}

.theme-announcement-bar button:hover {
opacity: 1;
}
5 changes: 5 additions & 0 deletions versioned_docs/version-0.8/concepts/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"label": "Concepts",
"position": 2,
"link": { "type": "doc", "id": "concepts/index" }
}
5 changes: 5 additions & 0 deletions versioned_docs/version-0.8/concepts/core/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"label": "Core",
"position": 1,
"collapsed": false
}
78 changes: 78 additions & 0 deletions versioned_docs/version-0.8/concepts/core/agents-and-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Agents and tools
description: What Rossoctl accepts as an agent and as a tool.
sidebar_position: 2
---

Rossoctl runs two types of workload. For each type, Rossoctl requires only a network interface. It
does not require a specific programming language or a specific agent framework.

## An agent

An agent is a container that uses the A2A protocol. The container must serve three HTTP endpoints.

| Endpoint | Method | Function |
| --- | --- | --- |
| `/.well-known/agent-card.json` | `GET` | Returns the agent card |
| `/` | `POST` | Receives a message or a task |
| `/tasks/{id}` | `GET` | Returns the status of a task |

These three endpoints are the complete requirement. Streaming is optional. If your agent supports
streaming, declare it in the agent card.

Rossoctl is neutral about frameworks because the requirement is a network interface, not a library.
Agents that use LangGraph, CrewAI, AG2, Llama Stack or BeeAI all run on Rossoctl. Agent programs such
as Claude Code also run on Rossoctl. To adapt your own agent, read
[Bring your own agent](../../workloads/bring-your-own-agent.md).

## The agent card

The agent card is a JSON document. It gives the name, the version, the address, the capabilities and
the skills of the agent. Rossoctl reads the card and stores it in an `AgentCard` resource. Rossoctl
reads the card again at a fixed interval, so the record stays correct.

You can sign the agent card. Rossoctl then validates the signature against the trust bundle of SPIRE.
Rossoctl can also read the identity of the signer from the certificate chain. This confirms that the
card comes from the workload that the card describes. If the card fails this check, the operator can
isolate the workload with a network policy. See
[identity binding](control-plane.md#identity-binding).

## A tool

A tool is a container that uses the MCP protocol. The container must serve one HTTP endpoint:

| Endpoint | Method | Function |
| --- | --- | --- |
| `/mcp` | `POST` | Receives MCP messages |

A tool makes functions, files and prompt templates available to an agent. The agent calls `tools/list`
to find what the tool offers, and then calls the function that it needs.

## How an agent finds a tool

An agent reads the address of a tool from an environment variable.

| Variable | Use it when |
| --- | --- |
| `MCP_URL` | The agent uses one tool, or the agent uses the MCP Gateway. |
| `MCP_URLS` | The agent uses more than one tool. Separate the addresses with commas. |

There are two possible arrangements.

**Direct.** Each agent holds the address of each tool. This arrangement is simple. It is correct for
one or two tools. If you add a tool, you must change each agent that needs it.

**Through the gateway.** Each agent holds one address, and that address is the MCP Gateway. Each tool
registers with the gateway one time. The gateway then gives all agents one list of tools. See
[MCP Gateway](../experiments/mcp-gateway.md).

## Why the two types are different

Rossoctl adds an agent and a tool to the platform in the same way. Both receive the same sidecar. But
an agent and a tool are at opposite ends of a chain of delegation. A user delegates work to an agent.
The agent delegates work to a tool.

The security model protects this chain. The token that an agent gives to a tool is valid only for that
tool, and it names the user. A tool can therefore identify the user, and can refuse the request if the
user does not have the necessary permission. An agent never holds a credential that belongs to a tool.
See [Identity and trust](identity.md).
79 changes: 79 additions & 0 deletions versioned_docs/version-0.8/concepts/core/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: Architecture
description: The components of a Rossoctl cluster, and the path of a request.
sidebar_position: 1
---

A Rossoctl cluster has four layers. This page describes each layer, and then follows one request
through all of them.

![The Rossoctl components, and the namespace of each component](../../images/architecture.svg)

## Layer 1: your workloads

An **agent** is a container that uses the A2A protocol. A **tool** is a container that uses the MCP
protocol. See [Agents and tools](agents-and-tools.md).

Both are standard Kubernetes workloads. A workload becomes part of the platform when you create an
`AgentRuntime` resource that points to it.

## Layer 2: the data plane

Each workload in the platform receives a RossoCortex sidecar. The sidecar is an Envoy proxy and a Go
processor. It controls the traffic in both directions.

- For traffic that arrives, the sidecar validates the token of the caller. An invalid token receives
the HTTP status 401. The request does not reach your agent.
- For traffic that leaves, the sidecar exchanges the token of the agent for a token that is valid
only for the target. It then applies the plugins that you enabled.

The sidecar also keeps the identity documents of the workload current, and registers the workload
with Keycloak. See [RossoCortex](cortex.md).

## Layer 3: the control plane

The **operator** watches the `AgentRuntime` and `AgentCard` resources. It labels the workloads,
injects the sidecars, registers the OAuth clients, reads the agent cards and configures the traces.
See [Control plane](control-plane.md).

The **backend and the web console** give you a REST interface and a browser interface. You use them
to deploy agents and tools, to send messages to an agent, and to read traces. The
[CLI](../../reference/cli.md) uses the same REST interface.

## Layer 4: the infrastructure

| Component | Function | When you get it |
| --- | --- | --- |
| cert-manager | Certificates for the webhooks and the gateways | Always |
| Gateway API and the Istio gateway controller | Network entry for the console, Keycloak and the agents | Always |
| Keycloak | Identity provider, OAuth2 tokens and token exchange | Always |
| SPIRE | Gives an identity to each workload | `--with-spire` |
| Istio ambient mesh | mTLS between the workloads | `--with-istio` |
| Shipwright and Tekton | Builds agents and tools from source | `--with-builds` |
| OpenTelemetry collector | Collects the traces | `--with-otel` |
| MLflow | Stores the traces | `--with-mlflow` |
| Kiali and Prometheus | Network diagram and metrics | `--with-kiali` |

:::warning
The Istio gateway controller and the Istio ambient mesh are different components. The gateway
controller is always installed. It serves all network entry on `*.localtest.me:8080`. The ambient
mesh is optional. It adds mTLS between the workloads. The `--with-istio` option installs the ambient
mesh only.
:::

## The path of one request

A user asks an agent to do work, and the agent calls a tool.

1. The user signs in to the console. Keycloak issues an access token.
2. The console sends an A2A message to the agent. The message contains the token.
3. The sidecar of the agent validates the token. If the token is not valid, the request stops here.
4. The agent decides to call a tool.
5. The sidecar of the agent exchanges the token of the user for a token that is valid only for that
tool. It then applies the plugins that you enabled. A plugin can stop the call.
6. The sidecar of the tool validates the new token, and confirms that the token names the tool.
7. The tool does the work and replies. The reply returns through both sidecars.

The identity of the user is present at each step. No step has more permissions than the user and the
agent both have. For the diagrams of each step, see
[Authentication flows](../../security/flows.md).
138 changes: 138 additions & 0 deletions versioned_docs/version-0.8/concepts/core/control-plane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: Control plane
description: What the operator does, and the two custom resources that it manages.
sidebar_position: 5
---

The operator makes a standard Kubernetes workload into a platform workload. You deploy a normal
Deployment. The operator adds the identity, the sidecars, the OAuth registration, the discovery and
the traces.

For each field of each resource, see [Custom resources](../../reference/custom-resources.md).

## How a workload joins the platform

You do not add labels to your Deployment. You create one resource that points to it:

```yaml
apiVersion: agent.rossoctl.dev/v1alpha1
kind: AgentRuntime
metadata:
name: weather-agent
spec:
type: agent # or "tool"
targetRef:
apiVersion: apps/v1
kind: Deployment
name: weather-agent
```

The operator then does five things:

1. It adds the label `rossoctl.io/type: agent` or `rossoctl.io/type: tool` to the workload.
2. It adds a label to the pod template. A webhook then injects the RossoCortex sidecars when
Kubernetes next creates a pod.
3. It registers the workload as an OAuth client in Keycloak. The client identifier is the SPIFFE
identity of the workload. The operator writes the credentials to a Secret in the namespace of the
workload.
4. It writes a hash of the resolved configuration to the pod template. When the configuration changes,
the hash changes, and Kubernetes starts a rolling update.
5. For an agent, it creates an `AgentCard` resource, and discovery starts.

If you delete the `AgentRuntime` resource, the operator removes the label and the workload leaves the
platform.

The web console and the CLI create this resource for you.

## Discovery

An `AgentCard` resource records what an agent says about itself:

```yaml
apiVersion: agent.rossoctl.dev/v1alpha1
kind: AgentCard
metadata:
name: weather-agent-deployment-card
spec:
syncPeriod: 30s
targetRef:
apiVersion: apps/v1
kind: Deployment
name: weather-agent
```

The operator reads `/.well-known/agent-card.json` from the agent at each `syncPeriod` interval. It
stores the result in the status of the resource. The status contains the name, the description, the
version, the address, the capabilities and the skills.

You can therefore query the data of an agent with `kubectl`. The record also stays correct after you
deploy the agent again.

The operator names a card that it creates automatically with this pattern: `{name}-{kind}-card`.

### Signature validation

An agent card can contain JWS signatures. The operator validates them against the X.509 trust bundle
of SPIRE, and writes the result to the status:

| Field | Meaning |
| --- | --- |
| `validSignature` | The signature is valid for the trust bundle. |
| `signatureSpiffeId` | The SPIFFE identity from the certificate chain of the signature. |
| `signatureIdentityMatch` | The signature is valid **and** the identity binding passed. |
| `cardId` | A hash of the content of the card. Use it to detect a change. |

### Identity binding

A valid signature proves that a holder of a valid certificate signed the card. Identity binding proves
that the signer is the workload that the card describes. The operator compares the identity from the
certificate with the expected identity of the workload.

There are two modes:

```yaml
spec:
identityBinding:
trustDomain: localtest.me # replaces the default of the operator
strict: false # audit only
```

- **`strict: false` is the default.** The operator writes the result to the status. It takes no other
action.
- **`strict: true`** makes the operator create a network policy that isolates the workload when the
binding fails.

Start with the audit mode. Change to the strict mode after you confirm that your agents sign their
cards correctly.

## Configuration layers

The operator resolves the configuration from two ConfigMaps. The more specific layer wins.

1. **Namespace defaults.** A ConfigMap with the label `rossoctl.io/defaults=true`, in the namespace of
the workload.
2. **Cluster defaults.** The `rossoctl-platform-config` ConfigMap, in the `rossoctl-system` namespace.

Feature gates are separate. The `rossoctl-feature-gates` ConfigMap applies to the whole cluster. A
namespace cannot replace it, and an `AgentRuntime` resource cannot replace it. It controls which
RossoCortex components run, and whether skill discovery is active. This design is deliberate: the
owner of a namespace must not be able to disable the security components of the platform.

## Deployment types

A workload in the platform can be a `Deployment`, a `StatefulSet` or a `Sandbox`.

| Type | Use it for |
| --- | --- |
| `Deployment` | Agents and tools that keep no state. This type is the default. |
| `StatefulSet` | An agent that needs durable storage. |
| `Sandbox` | An agent that needs stronger isolation. See [Sandboxes](../experiments/sandboxes.md). |

## Other operator functions

- **Traces.** The operator finds an MLflow instance, creates an experiment for each agent, and
configures the workload to send traces there. See [Observability](../../operate/observability.md).
- **Skill discovery.** When the `skillDiscovery` feature gate is on, the operator reads the
`rossoctl.io/skills` annotation and resolves the names into the status of the resource. See
[Skills](../experiments/skills.md).
- **Network policy.** The operator isolates a workload that fails identity binding in the strict mode.
Loading