app.discover()
Discover available agent capabilities and schemas at runtime for dynamic orchestration
app.discover()
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 based on user requests, creating dynamic tool catalogs for LLMs, or implementing intelligent health-based routing.
Basic Example
from agentfield import Agent
app = Agent(node_id="orchestrator")
@app.reasoner()
async def intelligent_router(user_query: str) -> dict:
"""Route user query to the best available capability."""
# Discover all research-related capabilities
result = app.discover(
tags=["research"],
include_input_schema=True
)
# Select best match based on available agents
for agent_cap in result.json.capabilities:
for reasoner in agent_cap.reasoners:
if "deep" in reasoner.id:
# Execute the discovered capability
result = await app.call(
reasoner.invocation_target,
query=user_query
)
return result
return {"error": "No suitable capability found"}Parameters
Prop
Type
Returns: DiscoveryResult - Wrapper object containing the response in requested format (.json, .xml, or .compact)
Discovery Response Structure
class DiscoveryResult:
format: str # Format used: "json", "xml", "compact"
raw: str # Raw response string
json: Optional[DiscoveryResponse] # Parsed JSON response (if format="json")
compact: Optional[CompactDiscoveryResponse] # Parsed compact response (if format="compact")
xml: Optional[str] # XML response string (if format="xml")
class DiscoveryResponse:
discovered_at: datetime # Discovery timestamp
total_agents: int # Total number of agents found
total_reasoners: int # Total reasoners across all agents
total_skills: int # Total skills across all agents
pagination: Pagination # Pagination metadata
capabilities: List[AgentCapability] # List of agent capabilities
class AgentCapability:
agent_id: str # Unique agent identifier
base_url: str # Agent's HTTP endpoint
version: str # Agent version
health_status: str # Health: active, inactive, degraded
deployment_type: str # Deployment: long_running, serverless
last_heartbeat: datetime # Last heartbeat timestamp
reasoners: List[ReasonerCapability] # List of reasoners
skills: List[SkillCapability] # List of skills
class ReasonerCapability:
id: str # Reasoner identifier
description: Optional[str] # Human-readable description
tags: List[str] # Array of tag strings
input_schema: Optional[dict] # JSON Schema for input
output_schema: Optional[dict] # JSON Schema for output
examples: Optional[List[dict]] # Usage examples
invocation_target: str # Target for execution (agent_id.reasoner_id)
class SkillCapability:
id: str # Skill identifier
description: Optional[str] # Human-readable description
tags: List[str] # Array of tag strings
input_schema: Optional[dict] # JSON Schema for input
invocation_target: str # Target for execution (agent_id.skill:skill_id)Common Patterns
Basic Discovery
Discover all available capabilities:
from agentfield import Agent
app = Agent(node_id="my-agent")
# Discover everything
result = app.discover()
print(f"Found {result.json.total_agents} agents")
print(f"Found {result.json.total_reasoners} reasoners")
print(f"Found {result.json.total_skills} skills")
# Iterate through agents
for agent_cap in result.json.capabilities:
print(f"Agent: {agent_cap.agent_id}")
print(f" Version: {agent_cap.version}")
print(f" Health: {agent_cap.health_status}")
for reasoner in agent_cap.reasoners:
print(f" Reasoner: {reasoner.id}")
print(f" Target: {reasoner.invocation_target}")Filter by Wildcards
Use wildcard patterns to find specific capabilities:
# Find all research-related reasoners
research_caps = app.discover(
reasoner="*research*",
include_input_schema=True
)
# Find reasoners starting with "deep_"
deep_caps = app.discover(reasoner="deep_*")
# Find web-related skills
web_skills = app.discover(
skill="web_*",
tags=["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:
# Find ML-related capabilities (with wildcard)
ml_caps = app.discover(
tags=["ml*"], # Matches: ml, mlops, ml_vision
health_status="active"
)
# Find capabilities with multiple tags
multi_tag_caps = app.discover(
tags=["research", "nlp", "ml"],
include_input_schema=True
)Schema-Based Discovery
Discover capabilities with full schemas for validation:
# Get full schemas for dynamic validation
result = app.discover(
tags=["data"],
include_input_schema=True,
include_output_schema=True
)
for agent_cap in result.json.capabilities:
for reasoner in agent_cap.reasoners:
print(f"Reasoner: {reasoner.id}")
print(f"Input Schema: {reasoner.input_schema}")
print(f"Output Schema: {reasoner.output_schema}")
# Validate user input against schema
if validates_against_schema(user_input, reasoner.input_schema):
result = await app.call(
reasoner.invocation_target,
**user_input
)AI Orchestrator Pattern
Build AI systems that select tools at runtime:
from agentfield import Agent
import openai
app = Agent(node_id="orchestrator")
@app.reasoner()
async def ai_orchestrated_task(user_request: str) -> dict:
"""AI orchestrator that discovers and selects tools dynamically."""
# Step 1: Discover available tools
result = app.discover(
include_input_schema=True,
include_descriptions=True
)
# Step 2: Build tool catalog for LLM
tools = []
for agent_cap in result.json.capabilities:
for reasoner in agent_cap.reasoners:
tools.append({
"type": "function",
"function": {
"name": reasoner.id,
"description": reasoner.description,
"parameters": reasoner.input_schema,
"invocation_target": reasoner.invocation_target
}
})
# Step 3: Let AI select the right tool
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Select appropriate tool"},
{"role": "user", "content": user_request}
],
tools=tools
)
# Step 4: Execute selected tool
if response.tool_calls:
tool_call = response.tool_calls[0]
# Find the invocation target
selected_tool = next(
t for t in tools
if t["function"]["name"] == tool_call.name
)
# Execute via Agentfield
result = await app.call(
selected_tool["function"]["invocation_target"],
**tool_call.arguments
)
return result
return {"error": "No tool selected"}Health-Based Routing
Route to healthy agents only:
@app.reasoner()
async def resilient_execution(task: str) -> dict:
"""Execute on healthy agents with automatic failover."""
# Find healthy agents with the capability
healthy_caps = app.discover(
tags=["processing"],
health_status="active"
)
if not healthy_caps.capabilities:
# No healthy agents available
return {"error": "No healthy agents available"}
# Try each healthy agent until one succeeds
for agent_cap in healthy_caps.capabilities:
for reasoner in agent_cap.reasoners:
try:
result = await app.call(
reasoner.invocation_target,
task=task
)
return {
"result": result,
"executed_by": agent_cap.agent_id
}
except Exception as e:
# Try next agent
continue
return {"error": "All agents failed"}Compact Format for Performance
Use compact format when you only need targets:
# Minimal response, just IDs and targets
compact_caps = app.discover(format="compact")
# Returns minimal structure
# {
# "discovered_at": "...",
# "reasoners": [
# {"id": "...", "agent_id": "...", "target": "...", "tags": [...]}
# ],
# "skills": [...]
# }
# Fast lookup by tag
search_targets = [
r["target"] for r in compact_caps["reasoners"]
if "search" in r["tags"]
]XML Format for LLM Context
Use XML format for LLM system prompts:
# Get capabilities in XML format
result = app.discover(
format="xml",
include_descriptions=True
)
# Inject into LLM system prompt
system_prompt = f"""
You are an AI assistant with access to these capabilities:
{result.xml}
Select and use the appropriate capability for the user's request.
"""
# Use with any LLM
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": "I need research on AI trends"}
]
)Advanced Patterns
Best Practices
Cache Discovery Results
Discovery results change infrequently—cache them:
from datetime import datetime, timedelta
class CapabilityCache:
def __init__(self, ttl_seconds=30):
self.cache = None
self.cache_time = None
self.ttl = timedelta(seconds=ttl_seconds)
def get_capabilities(self, app: Agent, **filters):
now = datetime.now()
# Return cached if still valid
if self.cache and self.cache_time:
if (now - self.cache_time) < self.ttl:
return self.cache
# Refresh cache
self.cache = app.discover(**filters)
self.cache_time = now
return self.cache
# Usage
cache = CapabilityCache(ttl_seconds=30)
@app.reasoner()
async def cached_discovery(query: str) -> dict:
capabilities = cache.get_capabilities(app, tags=["research"])
# Use capabilities...Filter Aggressively
Request only what you need to minimize response size and latency:
# Bad: Retrieve everything
all_caps = app.discover() # Large response, slow
# Good: Filter to specific needs
focused_caps = app.discover(
tags=["research"],
health_status="active",
include_input_schema=True,
format="compact"
) # Small response, fastHandle Missing Capabilities
Always handle the case where no capabilities are found:
@app.reasoner()
async def safe_discovery(capability_type: str) -> dict:
result = app.discover(tags=[capability_type])
if not result.json.capabilities:
return {
"error": "No capabilities found",
"requested_type": capability_type,
"suggestion": "Check if agents are running"
}
# Proceed with discovered capabilities
return {"found": result.json.total_agents}Use Appropriate Format
Choose format based on use case:
# JSON: Default, full structure
json_caps = app.discover(format="json")
# XML: LLM system prompts
xml_caps = app.discover(format="xml")
# Compact: Quick lookups, minimal bandwidth
compact_caps = app.discover(format="compact")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
include_input_schemaonly 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
fast_discovery = app.discover(
tags=["specific"],
format="compact",
include_descriptions=False
)
# Slower: Full response with schemas
full_discovery = app.discover(
include_input_schema=True,
include_output_schema=True,
include_examples=True
)Related
- Agent Discovery REST API - HTTP endpoint documentation
- app.call() - Execute discovered capabilities
- Cross-Agent Communication - Build AI orchestrators
- Multi-Agent Orchestration - Workflow patterns