Skip to content
Closed
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
24 changes: 21 additions & 3 deletions docs/architecture/cross-namespace-references.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Agents can use ModelConfigs and RemoteMCPServers in other namespaces. A reference
contains `name` and optionally `namespace`; omitting or leaving namespace empty
uses the namespace of the reference owner. Names never contain `namespace/name`.
A model or MCP server reference may also set `kind`, and `group` with it.
Omitted values select ModelConfig or RemoteMCPServer. An embedding controller
can register more kinds with `app.Options.ReferenceKinds`, projecting objects
to those two. The controller rejects any other kind with `ResolvedRefs=False`
and reason `InvalidKind`.

AgentTemplate owns its model and MCP references, including child templates.
Inline templates use the Agent namespace. Harness owns memory and summarizer
Expand Down Expand Up @@ -152,7 +157,20 @@ preserves namespaces when saving references.

## Rollout

Install the regenerated CRDs **before** upgrading the controller or applying
Install the regenerated CRDs **before** you upgrade the controller or apply
namespace-qualified manifests. Older schemas can prune the namespace fields.
Existing local manifests retain their JSON shape. Go callers must adopt
`v1alpha3.ObjectReference` and `corev1.TypedObjectReference` for the changed fields.

Manifests and stored objects from v1.0.x work unchanged and keep their revision
digest.

- **MCP server `kind`** defaults to `RemoteMCPServer` and is stored, so v1.0.x
controllers and clients can still update the object.
- **Other kinds** on model references need the new controller and CRDs. A v1.0.x
controller ignores them, and a CRD rollback drops them, so the reference selects
the ModelConfig with the same name. Before you roll back to v1.0.x, point those
references back to the default kind.
- **`apiGroup`** stays as a deprecated MCP server field that must be omitted. The
API server drops the `null` that v1.0.x Go clients send.

Go callers can keep the deprecated alias `v1alpha3.ObjectReference`.
`MCPToolBinding.Server` now uses `v1alpha3.MCPServerReference`.
9 changes: 7 additions & 2 deletions docs/kubernetes-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ details out of the public API unless callers need them to express intent.
Namespace and name identify an object; UID distinguishes deletion and recreation.
Validate names according to the referenced kind, not a universal DNS-label rule.
Use native structured types such as `LocalObjectReference`,
`TypedLocalObjectReference`, and `SecretKeySelector`. Start with same-namespace
references; crossing namespaces requires an explicit authorization policy.
`TypedLocalObjectReference`, and `SecretKeySelector` when their semantics match.
When they do not, own the reference type instead of patching a native one with
CEL. For example, `TypedObjectReference` treats an omitted `apiGroup` as the core
group, so model and MCP server references own `group` and `kind`
fields that default to the kagent kind. The controller rejects other kinds with
reason `InvalidKind`. Start with same-namespace references; crossing namespaces
requires an explicit authorization policy.

Do not encode references as `"namespace/name"` strings. Keep inline configuration
and references in separate, typed fields. If both forms are supported, define
Expand Down
8 changes: 2 additions & 6 deletions go/api/v1alpha3/agenttemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,8 @@ type AgentTemplatePromptSource struct {

// MCPToolBinding binds tools from an authorized MCP server.
type MCPToolBinding struct {
// +kubebuilder:validation:XValidation:rule="self.kind == 'RemoteMCPServer'",message="kind must be RemoteMCPServer"
// +kubebuilder:validation:XValidation:rule="!has(self.apiGroup)",message="apiGroup must be omitted"
// +kubebuilder:validation:XValidation:rule="size(self.name) > 0 && size(self.name) <= 253",message="name must contain between 1 and 253 characters",fieldPath=".name"
// +kubebuilder:validation:XValidation:rule="!has(self.namespace) || size(self.namespace) <= 63",message="namespace must contain at most 63 characters",fieldPath=".namespace"
// +required
Server corev1.TypedObjectReference `json:"server"`
Server MCPServerReference `json:"server"`
// Tools optionally limits which server tools are exposed. An omitted or empty
// list exposes every tool. Harnesses that cannot enforce a partial selection
// may expose the whole server and report a warning.
Expand Down Expand Up @@ -178,7 +174,7 @@ type AgentTemplateSpec struct {
// ModelConfig is required by managed harnesses and optional for BYO harnesses.
// +kubebuilder:validation:XValidation:rule="has(self.name) && self.name != ''",message="name must not be empty"
// +optional
ModelConfig *ObjectReference `json:"modelConfig,omitempty"`
ModelConfig *ModelConfigReference `json:"modelConfig,omitempty"`
// +optional
Description string `json:"description,omitempty"`
// +optional
Expand Down
98 changes: 91 additions & 7 deletions go/api/v1alpha3/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,31 @@ limitations under the License.
package v1alpha3

import (
"cmp"
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
)

// ObjectReference identifies a resource; an omitted namespace means the owner's namespace.
type ObjectReference struct {
// ModelConfigReference identifies a model; an omitted namespace means the owner's namespace.
// +kubebuilder:validation:XValidation:rule="!has(self.group) || has(self.kind)",message="group requires kind"
type ModelConfigReference struct {
// Group is the referenced API group. It defaults to api.kagent.dev and requires kind.
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
// +optional
Group *string `json:"group,omitempty"`
// Kind is the referenced kind. It defaults to ModelConfig.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`
// +optional
Kind *string `json:"kind,omitempty"`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
Expand All @@ -38,12 +53,81 @@ type ObjectReference struct {
Namespace string `json:"namespace,omitempty"`
}

// GroupKind returns the referenced kind, defaulting to ModelConfig.
func (r ModelConfigReference) GroupKind() schema.GroupKind {
return referenceGroupKind(r.Group, r.Kind, ModelConfigGroupKind)
}

// Target returns the referenced kind and key, defaulting to the owner's namespace.
func (r ModelConfigReference) Target(owner string) (schema.GroupKind, types.NamespacedName) {
return r.GroupKind(), types.NamespacedName{Namespace: cmp.Or(r.Namespace, owner), Name: r.Name}
}

// ObjectReference is the v1.0.x name of ModelConfigReference.
//
// Deprecated: use ModelConfigReference.
type ObjectReference = ModelConfigReference

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to preserve the older ref type through an alias? it's also odd for a generic ObjectReference to alias to a Model specific type


// NamespacedName resolves the reference relative to its owner.
func (r ObjectReference) NamespacedName(ownerNamespace string) types.NamespacedName {
if r.Namespace != "" {
ownerNamespace = r.Namespace
}
return types.NamespacedName{Namespace: ownerNamespace, Name: r.Name}
//
// Deprecated: it ignores Kind; use Target.
func (r ModelConfigReference) NamespacedName(owner string) types.NamespacedName {
_, key := r.Target(owner)
return key
}

// The kind default applies before CEL, so "group requires kind" would always pass.
// The second rule rejects a foreign group with the default kind instead.

// MCPServerReference identifies an MCP server; an omitted namespace means the owner's namespace.
// +kubebuilder:validation:XValidation:rule="!has(self.apiGroup)",message="use group instead of apiGroup"
// +kubebuilder:validation:XValidation:rule="!has(self.group) || self.group == 'api.kagent.dev' || (has(self.kind) && self.kind != 'RemoteMCPServer')",message="a group other than api.kagent.dev requires its own kind"
type MCPServerReference struct {
// v1.0.x Go clients encode an unset apiGroup as null. Keeping the field lets
// strict field validation accept their requests; the server drops the null.

// APIGroup is not supported.
//
// Deprecated: use Group.
// +optional
APIGroup *string `json:"apiGroup,omitempty"`
// Group is the referenced API group. It defaults to api.kagent.dev.
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
// +optional
Group *string `json:"group,omitempty"`
// v1.0.x required kind. The schema default stores it, so a v1.0.x CRD or Go client
// can still update objects written without it.

// Kind is the referenced kind. It defaults to RemoteMCPServer.
// +kubebuilder:default=RemoteMCPServer
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`
// +optional
Kind *string `json:"kind,omitempty"`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +required
Name string `json:"name"`
// +kubebuilder:validation:MaxLength=63
// +optional
Namespace string `json:"namespace,omitempty"`
}

// GroupKind returns the referenced kind, defaulting to RemoteMCPServer.
func (r MCPServerReference) GroupKind() schema.GroupKind {
return referenceGroupKind(r.Group, r.Kind, RemoteMCPServerGroupKind)
}

// Target returns the referenced kind and key, defaulting to the owner's namespace.
func (r MCPServerReference) Target(owner string) (schema.GroupKind, types.NamespacedName) {
return r.GroupKind(), types.NamespacedName{Namespace: cmp.Or(r.Namespace, owner), Name: r.Name}
}

// referenceGroupKind fills an omitted group or kind from the field's default.
func referenceGroupKind(group, kind *string, defaultKind schema.GroupKind) schema.GroupKind {
return schema.GroupKind{Group: ptr.Deref(group, defaultKind.Group), Kind: ptr.Deref(kind, defaultKind.Kind)}
}

// ConfigMapKeyReference identifies a key in a same-namespace ConfigMap.
Expand Down
Loading
Loading