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" anywhere
  • abc* - 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, fast

Handle 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_schema 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
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
)