Agent Discovery
Discover available agent capabilities and schemas at runtime for dynamic orchestration
Agent Discovery
Discover available agent capabilities and schemas at runtime for dynamic orchestration
Discover available agents, reasoners, and skills at runtime with their input/output schemas. Perfect for building AI orchestrators that select tools dynamically, generating LLM tool catalogs, or implementing intelligent health-based routing.
Basic Example
package main
import (
"context"
"fmt"
"github.com/Agent-Field/agentfield/sdk/go/agent"
)
func main() {
app, _ := agent.New(agent.Config{
NodeID: "orchestrator",
AgentFieldURL: "http://localhost:8080",
})
ctx := context.Background()
// Discover all research-related capabilities
capabilities, err := app.Discover(
ctx,
agent.WithTags([]string{"research"}),
agent.WithInputSchema(true),
)
if err != nil {
panic(err)
}
fmt.Printf("Found %d agents with %d reasoners\n",
capabilities.TotalAgents,
capabilities.TotalReasoners)
// Use discovered capabilities
for _, agentCap := range capabilities.Capabilities {
for _, reasoner := range agentCap.Reasoners {
fmt.Printf("Found: %s\n", reasoner.InvocationTarget)
}
}
}Discovery Options
Use functional options to filter and configure discovery:
Prop
Type
Discovery Response Types
type DiscoveryResponse struct {
DiscoveredAt time.Time `json:"discovered_at"`
TotalAgents int `json:"total_agents"`
TotalReasoners int `json:"total_reasoners"`
TotalSkills int `json:"total_skills"`
Pagination Pagination `json:"pagination"`
Capabilities []AgentCapability `json:"capabilities"`
}
type AgentCapability struct {
AgentID string `json:"agent_id"`
BaseURL string `json:"base_url"`
Version string `json:"version"`
HealthStatus string `json:"health_status"`
DeploymentType string `json:"deployment_type"`
LastHeartbeat time.Time `json:"last_heartbeat"`
Reasoners []ReasonerCapability `json:"reasoners"`
Skills []SkillCapability `json:"skills"`
}
type ReasonerCapability struct {
ID string `json:"id"`
Description *string `json:"description,omitempty"`
Tags []string `json:"tags"`
InputSchema map[string]interface{} `json:"input_schema,omitempty"`
OutputSchema map[string]interface{} `json:"output_schema,omitempty"`
Examples []map[string]interface{} `json:"examples,omitempty"`
InvocationTarget string `json:"invocation_target"`
}
type SkillCapability struct {
ID string `json:"id"`
Description *string `json:"description,omitempty"`
Tags []string `json:"tags"`
InputSchema map[string]interface{} `json:"input_schema,omitempty"`
InvocationTarget string `json:"invocation_target"`
}
type Pagination struct {
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"has_more"`
}Common Patterns
Basic Discovery
Discover all available capabilities:
package main
import (
"context"
"fmt"
"github.com/Agent-Field/agentfield/sdk/go/agent"
)
func main() {
app, _ := agent.New(agent.Config{
NodeID: "my-agent",
AgentFieldURL: "http://localhost:8080",
})
ctx := context.Background()
// Discover everything
capabilities, err := app.Discover(ctx)
if err != nil {
panic(err)
}
fmt.Printf("Found %d agents\n", capabilities.TotalAgents)
fmt.Printf("Found %d reasoners\n", capabilities.TotalReasoners)
fmt.Printf("Found %d skills\n", capabilities.TotalSkills)
// Iterate through agents
for _, agentCap := range capabilities.Capabilities {
fmt.Printf("Agent: %s (v%s)\n", agentCap.AgentID, agentCap.Version)
fmt.Printf(" Health: %s\n", agentCap.HealthStatus)
for _, reasoner := range agentCap.Reasoners {
fmt.Printf(" Reasoner: %s\n", reasoner.ID)
fmt.Printf(" Target: %s\n", reasoner.InvocationTarget)
fmt.Printf(" Tags: %v\n", reasoner.Tags)
}
}
}Filter by Wildcards
Use wildcard patterns to find specific capabilities:
// Find all research-related reasoners
researchCaps, err := app.Discover(
ctx,
agent.WithReasonerPattern("*research*"),
agent.WithInputSchema(true),
)
// Find reasoners starting with "deep_"
deepCaps, err := app.Discover(
ctx,
agent.WithReasonerPattern("deep_*"),
)
// Find web-related skills
webSkills, err := app.Discover(
ctx,
agent.WithSkillPattern("web_*"),
agent.WithTags([]string{"web", "scraping"}),
)Wildcard Patterns:
*abc*- Contains "abc" anywhereabc*- Starts with "abc"*abc- Ends with "abc"abc- Exact match
Filter by Tags
Find capabilities using tags with wildcard support:
// Find ML-related capabilities (with wildcard)
mlCaps, err := app.Discover(
ctx,
agent.WithTags([]string{"ml*"}), // Matches: ml, mlops, ml_vision
agent.WithHealthStatus("active"),
)
// Find capabilities with multiple tags
multiTagCaps, err := app.Discover(
ctx,
agent.WithTags([]string{"research", "nlp", "ml"}),
agent.WithInputSchema(true),
)Schema-Based Discovery
Discover capabilities with full schemas for validation:
// Get full schemas for dynamic validation
capabilities, err := app.Discover(
ctx,
agent.WithTags([]string{"data"}),
agent.WithInputSchema(true),
agent.WithOutputSchema(true),
)
if err != nil {
panic(err)
}
for _, agentCap := range capabilities.Capabilities {
for _, reasoner := range agentCap.Reasoners {
fmt.Printf("Reasoner: %s\n", reasoner.ID)
fmt.Printf("Input Schema: %+v\n", reasoner.InputSchema)
fmt.Printf("Output Schema: %+v\n", reasoner.OutputSchema)
// Validate user input against schema
if ValidatesAgainstSchema(userInput, reasoner.InputSchema) {
result, _ := app.Execute(ctx, agent.ExecuteRequest{
Target: reasoner.InvocationTarget,
Input: userInput,
})
fmt.Printf("Result: %+v\n", result)
}
}
}AI Orchestrator Pattern
Build AI systems that select tools at runtime:
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/Agent-Field/agentfield/sdk/go/agent"
)
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
InvocationTarget string `json:"-"`
}
func AIOrchestrator(ctx context.Context, app *agent.Agent, userRequest string) (map[string]interface{}, error) {
// Step 1: Discover available tools
capabilities, err := app.Discover(
ctx,
agent.WithInputSchema(true),
agent.WithDescriptions(true),
)
if err != nil {
return nil, err
}
// Step 2: Build tool catalog for AI
tools := []Tool{}
for _, agentCap := range capabilities.Capabilities {
for _, reasoner := range agentCap.Reasoners {
tool := Tool{
Name: reasoner.ID,
Description: *reasoner.Description,
Parameters: reasoner.InputSchema,
InvocationTarget: reasoner.InvocationTarget,
}
tools = append(tools, tool)
}
}
// Step 3: Let AI select the right tool
selectedTool, args := AISelectTool(userRequest, tools) // Your AI logic
// Step 4: Execute selected tool
result, err := app.Execute(ctx, agent.ExecuteRequest{
Target: selectedTool.InvocationTarget,
Input: args,
})
return result, err
}
func main() {
app, _ := agent.New(agent.Config{
NodeID: "orchestrator",
AgentFieldURL: "http://localhost:8080",
})
ctx := context.Background()
result, err := AIOrchestrator(ctx, app, "Research AI trends in 2025")
if err != nil {
panic(err)
}
fmt.Printf("Result: %+v\n", result)
}Health-Based Routing
Route to healthy agents with automatic failover:
func ResilientExecution(ctx context.Context, app *agent.Agent, task string) (map[string]interface{}, error) {
// Find healthy agents with the capability
healthyCaps, err := app.Discover(
ctx,
agent.WithTags([]string{"processing"}),
agent.WithHealthStatus("active"),
)
if err != nil {
return nil, err
}
if len(healthyCaps.Capabilities) == 0 {
return nil, fmt.Errorf("no healthy agents available")
}
// Try each healthy agent until one succeeds
var lastErr error
for _, agentCap := range healthyCaps.Capabilities {
for _, reasoner := range agentCap.Reasoners {
result, err := app.Execute(ctx, agent.ExecuteRequest{
Target: reasoner.InvocationTarget,
Input: map[string]interface{}{"task": task},
})
if err == nil {
return map[string]interface{}{
"result": result,
"executed_by": agentCap.AgentID,
}, nil
}
lastErr = err
// Continue to next agent
}
}
return nil, fmt.Errorf("all agents failed: %w", lastErr)
}Compact Format for Performance
Use compact format when you only need targets:
// Minimal response, just IDs and targets
compactCaps, err := app.Discover(
ctx,
agent.WithFormat("compact"),
)
if err != nil {
panic(err)
}
// Compact format returns minimal structure
// Access via type assertion
compactData := compactCaps.(*CompactDiscoveryResponse)
// Fast lookup by tag
for _, reasoner := range compactData.Reasoners {
for _, tag := range reasoner.Tags {
if tag == "search" {
fmt.Printf("Search target: %s\n", reasoner.Target)
}
}
}XML Format for LLM Context
Use XML format for LLM system prompts:
// Get capabilities in XML format
xmlCaps, err := app.Discover(
ctx,
agent.WithFormat("xml"),
agent.WithDescriptions(true),
)
if err != nil {
panic(err)
}
// XML response is returned as string in XMLDiscoveryResponse
xmlData := xmlCaps.(*XMLDiscoveryResponse)
// Inject into LLM system prompt
systemPrompt := fmt.Sprintf(`
You are an AI assistant with access to these capabilities:
%s
Select and use the appropriate capability for the user's request.
`, xmlData.XML)
// Use with any LLM
response := CallLLM(systemPrompt, userQuery)Advanced Patterns
Best Practices
Cache Discovery Results
Discovery results change infrequently—cache them:
import (
"sync"
"time"
)
type CapabilityCache struct {
cache *DiscoveryResponse
cacheTime time.Time
ttl time.Duration
mu sync.RWMutex
}
func NewCapabilityCache(ttl time.Duration) *CapabilityCache {
return &CapabilityCache{
ttl: ttl,
}
}
func (c *CapabilityCache) GetCapabilities(
ctx context.Context,
app *agent.Agent,
options ...agent.DiscoverOption,
) (*DiscoveryResponse, error) {
c.mu.RLock()
if c.cache != nil && time.Since(c.cacheTime) < c.ttl {
defer c.mu.RUnlock()
return c.cache, nil
}
c.mu.RUnlock()
// Refresh cache
capabilities, err := app.Discover(ctx, options...)
if err != nil {
return nil, err
}
c.mu.Lock()
c.cache = capabilities
c.cacheTime = time.Now()
c.mu.Unlock()
return capabilities, nil
}
// Usage
cache := NewCapabilityCache(30 * time.Second)
func MyReasoner(ctx context.Context, app *agent.Agent) {
capabilities, _ := cache.GetCapabilities(
ctx,
app,
agent.WithTags([]string{"research"}),
)
// Use capabilities...
}Filter Aggressively
Request only what you need to minimize response size and latency:
// Bad: Retrieve everything
allCaps, _ := app.Discover(ctx) // Large response, slow
// Good: Filter to specific needs
focusedCaps, _ := app.Discover(
ctx,
agent.WithTags([]string{"research"}),
agent.WithHealthStatus("active"),
agent.WithInputSchema(true),
agent.WithFormat("compact"),
) // Small response, fastHandle Errors Gracefully
Always handle the case where no capabilities are found:
func SafeDiscovery(ctx context.Context, app *agent.Agent, capabilityType string) error {
capabilities, err := app.Discover(
ctx,
agent.WithTags([]string{capabilityType}),
)
if err != nil {
return fmt.Errorf("discovery failed: %w", err)
}
if len(capabilities.Capabilities) == 0 {
return fmt.Errorf(
"no capabilities found for type: %s. Check if agents are running",
capabilityType,
)
}
fmt.Printf("Found %d agents\n", len(capabilities.Capabilities))
return nil
}Performance Considerations
Caching:
- Server caches results for 30 seconds
- Typical cache hit rate: >95%
- Response time: <50ms (p50), <100ms (p95)
Schema Inclusion:
- Without schemas: ~10-20KB response
- With input/output schemas: ~50-100KB response
- Use
WithInputSchema(true)only when needed
Filtering:
- All filtering happens server-side in-memory
- Wildcard matching adds ~5-10ms overhead
- Tag filtering is highly optimized
Best Performance:
// Fast: Minimal response
fastDiscovery, _ := app.Discover(
ctx,
agent.WithTags([]string{"specific"}),
agent.WithFormat("compact"),
agent.WithDescriptions(false),
)
// Slower: Full response with schemas
fullDiscovery, _ := app.Discover(
ctx,
agent.WithInputSchema(true),
agent.WithOutputSchema(true),
agent.WithExamples(true),
)Related
- Agent Discovery REST API - HTTP endpoint documentation
- Agent Package - Core Agent type and configuration
- Cross-Agent Communication - Build AI orchestrators
- Multi-Agent Orchestration - Workflow patterns