Skip to content
Draft
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: 24 additions & 0 deletions go/api/config/crd/bases/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13169,12 +13169,24 @@ spec:
properties:
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
type: string
namespace:
type: string
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down Expand Up @@ -13242,6 +13254,11 @@ spec:
type: array
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
Expand All @@ -13267,6 +13284,13 @@ spec:
type: string
maxItems: 50
type: array
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down
24 changes: 24 additions & 0 deletions go/api/config/crd/bases/kagent.dev_sandboxagents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10826,12 +10826,24 @@ spec:
properties:
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
type: string
namespace:
type: string
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down Expand Up @@ -10899,6 +10911,11 @@ spec:
type: array
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
Expand All @@ -10924,6 +10941,13 @@ spec:
type: string
maxItems: 50
type: array
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down
10 changes: 10 additions & 0 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,16 @@ type TypedReference struct {
Name string `json:"name"`
// +optional
Namespace string `json:"namespace,omitempty"`
// URL, when set on an Agent tool reference, points at a remote A2A endpoint
// (e.g. an agent in another cluster reachable over an east-west gateway).
// When set, the controller does NOT resolve a local Agent CR — it uses this
// URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
// +optional
URL string `json:"url,omitempty"`
// Description is shown for a remote (URL-based) agent tool, since there is no
// local Agent CR to read the description from.
// +optional
Description string `json:"description,omitempty"`
}

func (t *TypedReference) GroupKind() schema.GroupKind {
Expand Down
30 changes: 30 additions & 0 deletions go/core/internal/controller/translator/agent/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ func (a *adkApiTranslator) validateAgent(ctx context.Context, agent v1alpha2.Age
return fmt.Errorf("tool must have an agent reference")
}

// Remote (e.g. cross-cluster) agent tool (#1853): there is no local
// Agent CR to resolve or recurse into — validation is satisfied.
if tool.Agent.URL != "" {
continue
}

toolAgent, err := a.getToolAgent(ctx, tool.Agent, agent.GetNamespace())
if err != nil {
return err
Expand Down Expand Up @@ -327,6 +333,30 @@ func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent v1alp
secretHashBytes = append(secretHashBytes, toolHashBytes...)
}
case tool.Agent != nil:
// Cross-cluster / remote A2A agent tool (#1853): when an explicit URL
// is given, use it directly instead of resolving a local Agent CR.
// Mirrors the local path's RemoteAgentConfig and honors globalProxyURL.
if tool.Agent.URL != "" {
targetURL := tool.Agent.URL
if a.globalProxyURL != "" {
targetURL, headers, err = applyProxyURL(tool.Agent.URL, a.globalProxyURL, headers)
if err != nil {
return nil, nil, nil, err
}
}
name := tool.Agent.Name
if name == "" {
name = "remote-agent"
}
cfg.RemoteAgents = append(cfg.RemoteAgents, adk.RemoteAgentConfig{
Name: utils.ConvertToPythonIdentifier(name),
Url: targetURL,
Headers: headers,
Description: tool.Agent.Description,
})
continue
}

toolAgent, err := a.getToolAgent(ctx, tool.Agent, agent.GetNamespace())
if err != nil {
return nil, nil, nil, err
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package agent_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
schemev1 "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/kagent-dev/kagent/go/api/v1alpha2"
agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent"
)

// Test_AdkApiTranslator_RemoteURLAgentTool covers cross-cluster declarative A2A
// (kagent-enterprise#1853). An Agent tool reference that sets an explicit URL must
// compile into a RemoteAgentConfig pointing at that URL WITHOUT resolving a local
// Agent CR — the referenced agent lives in another cluster and is not present in
// this cluster's API server. Without a URL, the same reference fails with
// "not found", which is the bug this feature fixes.
func Test_AdkApiTranslator_RemoteURLAgentTool(t *testing.T) {
ctx := context.Background()
scheme := schemev1.Scheme
require.NoError(t, v1alpha2.AddToScheme(scheme))

modelConfig := &v1alpha2.ModelConfig{
ObjectMeta: metav1.ObjectMeta{Name: "default-model", Namespace: "test"},
Spec: v1alpha2.ModelConfigSpec{
Provider: "OpenAI",
Model: "gpt-4o",
},
}
testNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}

declarativeSpec := func(tools ...*v1alpha2.Tool) v1alpha2.AgentSpec {
return v1alpha2.AgentSpec{
Type: v1alpha2.AgentType_Declarative,
Description: "test agent",
Declarative: &v1alpha2.DeclarativeAgentSpec{
SystemMessage: "Test",
ModelConfig: "default-model",
Tools: tools,
},
}
}

const remoteURL = "http://remote-agent.kagent.svc.cluster.local:8080"

tests := []struct {
name string
tool *v1alpha2.Tool
wantErr bool
errContains string
wantURL string
wantDescription string
}{
{
name: "URL set - resolves to the remote endpoint without a local CR (#1853 fix)",
tool: &v1alpha2.Tool{
Type: v1alpha2.ToolProviderType_Agent,
Agent: &v1alpha2.TypedReference{
Name: "remote-agent",
URL: remoteURL,
Description: "Remote netops specialist in the west cluster",
},
},
wantURL: remoteURL,
wantDescription: "Remote netops specialist in the west cluster",
},
{
name: "no URL and no local CR - fails as before (demonstrates the bug)",
tool: &v1alpha2.Tool{
Type: v1alpha2.ToolProviderType_Agent,
Agent: &v1alpha2.TypedReference{
Name: "remote-agent",
},
},
wantErr: true,
errContains: "not found",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Note: only the model config + namespace exist in this cluster. The
// referenced "remote-agent" is intentionally NOT created here — it
// lives in the peer cluster.
kubeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(modelConfig, testNamespace).
Build()

translator := agenttranslator.NewAdkApiTranslator(
kubeClient,
types.NamespacedName{Name: "default-model", Namespace: "test"},
nil,
"",
nil,
)

sourceAgent := &v1alpha2.Agent{
ObjectMeta: metav1.ObjectMeta{Name: "orchestrator", Namespace: "test"},
Spec: declarativeSpec(tt.tool),
}

inputs, err := translator.CompileAgent(ctx, sourceAgent)

if tt.wantErr {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}

require.NoError(t, err)
require.NotNil(t, inputs)
require.NotNil(t, inputs.Config)
require.Len(t, inputs.Config.RemoteAgents, 1)
assert.Equal(t, tt.wantURL, inputs.Config.RemoteAgents[0].Url)
assert.Equal(t, tt.wantDescription, inputs.Config.RemoteAgents[0].Description)
})
}
}
24 changes: 24 additions & 0 deletions helm/kagent-crds/templates/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13169,12 +13169,24 @@ spec:
properties:
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
type: string
namespace:
type: string
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down Expand Up @@ -13242,6 +13254,11 @@ spec:
type: array
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
Expand All @@ -13267,6 +13284,13 @@ spec:
type: string
maxItems: 50
type: array
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down
24 changes: 24 additions & 0 deletions helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10826,12 +10826,24 @@ spec:
properties:
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
type: string
namespace:
type: string
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down Expand Up @@ -10899,6 +10911,11 @@ spec:
type: array
apiGroup:
type: string
description:
description: |-
Description is shown for a remote (URL-based) agent tool, since there is no
local Agent CR to read the description from.
type: string
kind:
type: string
name:
Expand All @@ -10924,6 +10941,13 @@ spec:
type: string
maxItems: 50
type: array
url:
description: |-
URL, when set on an Agent tool reference, points at a remote A2A endpoint
(e.g. an agent in another cluster reachable over an east-west gateway).
When set, the controller does NOT resolve a local Agent CR — it uses this
URL directly, enabling cross-cluster declarative agent-to-agent tools (#1853).
type: string
required:
- name
type: object
Expand Down
Loading