AgentFieldbuild

Service discovery

Find agents, reasoners, and skills at runtime through the control plane discovery API, and publish selected capabilities through ARD when they need to be discoverable outside the deployment.

Runtime service discovery — agents find each other

Find every agent, reasoner, and skill in your fleet -- at runtime, not at deploy time.

Hard-coded service addresses break the moment you scale past a handful of agents. AgentField's discovery API lets any agent query the control plane for live capabilities, filtered by tags, health status, or name patterns. Build orchestrators that discover what is available, introspect input schemas, and dynamically decide which agents to call -- all without a single hardcoded address.

For capabilities that should be discoverable outside this control plane, AgentField also supports external agent discovery through Agentic Resource Discovery (ARD). Treat it as the outside-network extension of this model: AgentField-to-AgentField communication stays simple through app.call("node.function"); ARD decides what selected capabilities other systems can find, import, and possibly call after explicit approval.

@app.reasoner(tags=["orchestrator"])
async def dynamic_router(question: str) -> dict:
    # 1. Discover active agents with input schemas — zero hardcoded addresses
    caps = app.discover(
        tags=["public"],
        health_status="active",
        include_input_schema=True,
        format="json",
    )
    print(f"{caps.json.total_agents} agents online, {len(caps.json.capabilities)} capabilities")

    # 2. Feed discovered capabilities + schemas into an LLM — it picks the best agent
    result = await app.ai(
        system=(
            "You are a routing agent. Pick the best tool for the user's question.\n"
            "Available capabilities:\n" + caps.raw
        ),
        user=question,
        tools="discover",  # built-in: turns discovered capabilities into callable tools
    )

    # 3. The LLM chose an agent and called it — no static routing table needed
    return result

# New agents register at startup → the router discovers them automatically.
# No config changes, no redeployment, no service mesh.

What just happened

  • The agent queried the live control plane registry instead of relying on hardcoded targets
  • Discovery results included enough metadata to drive dynamic routing decisions
  • New active agents could appear in routing immediately after registration

Internal discovery vs ARD

SurfaceScopePrimary use
app.call("node.function")Inside one AgentField control planeMake inter-agent communication feel like a native backend call, with routing and execution context handled by AgentField.
app.discover() / /api/v1/discovery/capabilitiesInside one AgentField control planeRoute to healthy local agents, reasoners, and skills.
/.well-known/ai-catalog.jsonPublic catalog for selected entriesLet external agents, registries, and partner systems discover published capabilities.
/api/v1/ard/searchPublic ARD registry search when enabledLet an outside client search your published catalog.
Discovery -> ImportsYour control planeRecord external ARD entries without making them callable yet.
external.* callable bindingYour control planeRoute app.call() to an imported external resource after approval.

The important product boundary: global ARD enablement does not publish or invoke anything by itself. Publishing is per reasoner or skill; external invocation is per imported binding.

Example discovery summary:

{
  "discovered_at": "2026-03-23T12:00:00Z",
  "total_agents": 2,
  "total_reasoners": 2,
  "total_skills": 0,
  "pagination": { "limit": 100, "offset": 0, "has_more": false },
  "capabilities": [
    {
      "agent_id": "weather-agent",
      "base_url": "http://weather-agent:9000",
      "version": "1.0.0",
      "health_status": "active",
      "deployment_type": "sidecar",
      "last_heartbeat": "2026-03-23T12:00:00Z",
      "reasoners": [
        { "id": "forecast", "tags": ["public", "weather"], "invocation_target": "weather-agent:forecast" }
      ],
      "skills": []
    },
    {
      "agent_id": "translator",
      "base_url": "http://translator:9000",
      "version": "1.0.0",
      "health_status": "active",
      "deployment_type": "sidecar",
      "last_heartbeat": "2026-03-23T12:00:00Z",
      "reasoners": [
        { "id": "translate", "tags": ["public", "translation"], "invocation_target": "translator:translate" }
      ],
      "skills": []
    }
  ]
}
What you get
  • Runtime registry -- every agent auto-registers its reasoners and skills on startup.
  • Tag-based filtering -- find agents by domain ("insurance", "nlp") instead of memorizing node IDs.
  • Health-aware routing -- filter to only active agents so you never call a dead endpoint.
  • Schema introspection -- request input/output schemas to build dynamic tool calling at runtime.
  • Three output formats -- json for programmatic use, compact for LLM context windows, xml for legacy integrations.
Full SDK examples
from agentfield import Agent

app = Agent(node_id="orchestrator")

# Discover all registered capabilities
result = app.discover()
print(f"Found {result.json.total_agents} agents")

# Filter by tags
result = app.discover(tags=["insurance", "claims"])
for cap in result.json.capabilities:
    print(f"{cap.agent_id}: {[r.id for r in cap.reasoners]}")

# Only active agents, with input schemas
result = app.discover(
    health_status="active",
    include_input_schema=True,
    include_output_schema=True,
)

# Compact format for feeding into LLM context
result = app.discover(format="compact")
print(result.raw)  # raw string for LLM context
# result.compact is a CompactDiscoveryResponse with .reasoners and .skills
Auto-registration

Agents register themselves with the control plane automatically. When an agent starts, it sends a heartbeat containing its node ID, version, and the full list of reasoners and skills it exposes. The control plane updates the registry, and other agents can immediately discover the new capabilities.

af server          # Terminal 1 -- control plane at http://localhost:8080
python app.py      # Terminal 2 -- agent registers on startup

The agent exposes a /discover endpoint that the control plane polls. No manual registration step is required.

Patterns

Dynamic Tool Calling

Use discovery to build tool definitions for LLM calls at runtime. When agents come and go, the tool list updates automatically.

@app.reasoner()
async def smart_router(question: str) -> dict:
    # Discover available capabilities
    caps = app.discover(
        tags=["public"],
        include_input_schema=True,
        health_status="active",
    )

    # Feed discovered tools into an LLM call
    result = await app.ai(
        system="Route the question to the best available agent.",
        user=question,
        tools="discover",  # built-in shorthand
    )
    return result

Health-Gated Failover

Before calling a specific agent, verify it is active. If not, discover an alternative with the same tags.

async def call_with_failover(target: str, tags: list[str], **kwargs) -> dict:
    try:
        return await app.call(target, **kwargs)
    except Exception:
        # Find an active alternative
        fallback = app.discover(tags=tags, health_status="active")
        if not fallback.json.capabilities:
            raise RuntimeError(f"No active agents with tags {tags}")

        alt_cap = fallback.json.capabilities[0]
        alt_target = f"{alt_cap.agent_id}.{alt_cap.reasoners[0].id}"
        return await app.call(alt_target, **kwargs)
Publishing through ARD

Use ARD when a selected reasoner or skill should be discoverable outside the deployment.

  1. Enable ARD deployment guardrails in agentfield.yaml or environment variables.
  2. Open Discovery -> Overview and confirm the catalog URL and DID/trust status.
  3. Open Discovery -> Public Catalog or expand an agent in Agent nodes.
  4. Turn on Expose through ARD for one reasoner or skill.
  5. Add a display name, description, tags, representative queries, and artifact type.

Published entries are served at:

curl https://control-plane.example.com/.well-known/ai-catalog.json

See Expose agents to external discovery for the full external search, import, and callability workflow.

SDK reference

Python -- app.discover()

ParameterTypeDefaultDescription
agentstr | NoneNoneFilter by single agent ID
node_idstr | NoneNoneAlias for agent
agent_idslist[str] | NoneNoneFilter by multiple agent IDs
node_idslist[str] | NoneNoneAlias for agent_ids
reasonerstr | NoneNoneFilter by reasoner name pattern
skillstr | NoneNoneFilter by skill name pattern
tagslist[str] | NoneNoneFilter by tags
include_input_schemaboolFalseInclude input schemas in response
include_output_schemaboolFalseInclude output schemas in response
include_descriptionsboolTrueInclude descriptions in response
include_examplesboolFalseInclude usage examples
formatstr"json"Response format: "json", "compact", "xml"
health_statusstr | NoneNoneFilter by health: "active", "inactive", "degraded", "unknown"
limitint | NoneNonePagination limit
offsetint | NoneNonePagination offset

Returns: DiscoveryResult with .json, .compact, .xml, .raw, and .format fields.

TypeScript -- agent.discover(options?)

PropertyTypeDefaultDescription
agentstring--Filter by single agent ID
nodeIdstring--Alias for agent
agentIdsstring[]--Filter by multiple agent IDs
nodeIdsstring[]--Alias for agentIds
reasonerstring--Filter by reasoner name pattern
skillstring--Filter by skill name pattern
tagsstring[]--Filter by tags
includeInputSchemaboolean--Include input schemas
includeOutputSchemaboolean--Include output schemas
includeDescriptionsboolean--Include descriptions
includeExamplesboolean--Include usage examples
formatDiscoveryFormat"json""json", "compact", "xml"
healthStatusstring--Filter by health status
limitnumber--Pagination limit
offsetnumber--Pagination offset

Returns: Promise<DiscoveryResult> with json?, compact?, xml?, raw, and format fields.

Go -- agent.Discover(ctx, ...DiscoveryOption)

Option FunctionDescription
WithAgent(id)Filter by single agent ID
WithNodeID(id)Alias for WithAgent
WithAgentIDs(ids)Filter by multiple agent IDs
WithNodeIDs(ids)Alias for WithAgentIDs
WithReasonerPattern(pattern)Wildcard pattern for reasoner IDs
WithSkillPattern(pattern)Wildcard pattern for skill IDs
WithTags(tags)Filter by tags (supports wildcards)
WithDiscoveryInputSchema(bool)Include input schemas
WithDiscoveryOutputSchema(bool)Include output schemas
WithDiscoveryDescriptions(bool)Include descriptions
WithDiscoveryExamples(bool)Include usage examples
WithFormat(format)"json", "compact", "xml"
WithHealthStatus(status)Filter by health status
WithLimit(n)Pagination limit
WithOffset(n)Pagination offset

Returns: (*types.DiscoveryResult, error) with JSON, Compact, XML, Raw, and Format fields.