diff --git a/ui/src/app/actions/agents.ts b/ui/src/app/actions/agents.ts
index 96c4936b0..f011092af 100644
--- a/ui/src/app/actions/agents.ts
+++ b/ui/src/app/actions/agents.ts
@@ -116,6 +116,12 @@ function fromAgentFormDataToAgent(agentFormData: AgentFormData): Agent {
tools: convertTools(agentFormData.tools || []),
};
+ if (agentFormData.a2aSkills && agentFormData.a2aSkills.length > 0) {
+ base.spec!.declarative!.a2aConfig = {
+ skills: agentFormData.a2aSkills,
+ };
+ }
+
if (agentFormData.skillRefs && agentFormData.skillRefs.length > 0) {
base.spec!.skills = {
refs: agentFormData.skillRefs,
diff --git a/ui/src/app/agents/new/page.tsx b/ui/src/app/agents/new/page.tsx
index 4d15e6195..6bcfb97b0 100644
--- a/ui/src/app/agents/new/page.tsx
+++ b/ui/src/app/agents/new/page.tsx
@@ -4,11 +4,12 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Loader2, Settings2, PlusCircle, Trash2 } from "lucide-react";
-import { ModelConfig, AgentType } from "@/types";
+import { Loader2, Settings2, PlusCircle, Trash2, Globe } from "lucide-react";
+import { ModelConfig, AgentType, AgentSkill } from "@/types";
import { SystemPromptSection } from "@/components/create/SystemPromptSection";
import { ModelSelectionSection } from "@/components/create/ModelSelectionSection";
import { ToolsSection } from "@/components/create/ToolsSection";
+import { A2ASkillsSection } from "@/components/create/A2ASkillsSection";
import { useRouter, useSearchParams } from "next/navigation";
import { useAgents } from "@/components/AgentsProvider";
import { LoadingState } from "@/components/LoadingState";
@@ -32,6 +33,7 @@ interface ValidationErrors {
knowledgeSources?: string;
tools?: string;
skills?: string;
+ a2aSkills?: string;
}
interface AgentPageContentProps {
@@ -69,6 +71,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
selectedModel: SelectedModelType | null;
selectedTools: Tool[];
skillRefs: string[];
+ a2aSkills: AgentSkill[];
byoImage: string;
byoCmd: string;
byoArgs: string;
@@ -91,6 +94,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
selectedModel: null,
selectedTools: [],
skillRefs: [""],
+ a2aSkills: [],
byoImage: "",
byoCmd: "",
byoArgs: "",
@@ -136,6 +140,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
selectedTools: (agent.spec?.declarative?.tools && agentResponse.tools) ? agentResponse.tools : [],
selectedModel: agentResponse.modelConfigRef ? { model: agentResponse.model || "default-model-config", ref: agentResponse.modelConfigRef } : null,
skillRefs: (agent.spec?.skills?.refs && agent.spec.skills.refs.length > 0) ? agent.spec.skills.refs : [""],
+ a2aSkills: agent.spec?.declarative?.a2aConfig?.skills || [],
stream: agent.spec?.declarative?.stream ?? false,
byoImage: "",
byoCmd: "",
@@ -226,6 +231,26 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
// If all refs are empty/whitespace, that's fine - no skills will be included
}
+ if (state.agentType === "Declarative" && state.a2aSkills && state.a2aSkills.length > 0) {
+ for (const skill of state.a2aSkills) {
+ if (!skill.id.trim()) {
+ newErrors.a2aSkills = "All A2A skills must have an ID";
+ break;
+ }
+ if (!skill.name.trim()) {
+ newErrors.a2aSkills = "All A2A skills must have a name";
+ break;
+ }
+ }
+ if (!newErrors.a2aSkills) {
+ const ids = state.a2aSkills.map(s => s.id.trim().toLowerCase());
+ const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
+ if (duplicateIds.length > 0) {
+ newErrors.a2aSkills = `Duplicate skill ID: ${duplicateIds[0]}`;
+ }
+ }
+ }
+
setState(prev => ({ ...prev, errors: newErrors }));
return Object.keys(newErrors).length === 0;
};
@@ -280,6 +305,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
stream: state.stream,
tools: state.selectedTools,
skillRefs: state.agentType === "Declarative" ? (state.skillRefs || []).filter(ref => ref.trim()) : undefined,
+ a2aSkills: state.agentType === "Declarative" ? (state.a2aSkills || []).filter(s => s.id.trim() && s.name.trim()) : undefined,
// BYO
byoImage: state.byoImage,
byoCmd: state.byoCmd || undefined,
@@ -706,6 +732,23 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
+
+
{agent.spec.description}
-+ Define A2A (Agent-to-Agent) skills that this agent exposes. Each skill describes a capability + that other agents can discover and invoke via the A2A protocol. +
+No A2A skills configured. Click "Add A2A Skill" to get started.
+ )} + +{error}
+ )} +