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.
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.agent.reasoner('dynamicRouter', async (ctx) => {
// 1. Discover active agents with input schemas — zero hardcoded addresses
const caps = await agent.discover({
tags: ['public'],
healthStatus: 'active',
includeInputSchema: true,
format: 'json',
});
console.log(`${caps.json?.totalAgents} agents online`);
// 2. Feed discovered capabilities + schemas into an LLM — it picks the best agent
const result = await ctx.ai(ctx.input.question, {
system: `You are a routing agent. Pick the best tool.\nAvailable:\n${caps.raw}`,
});
// 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.a.RegisterReasoner("dynamic_router", func(ctx context.Context, input map[string]any) (any, error) {
// 1. Discover active agents with input schemas — zero hardcoded addresses
caps, err := a.Discover(ctx,
agent.WithTags([]string{"public"}),
agent.WithHealthStatus("active"),
agent.WithDiscoveryInputSchema(true),
agent.WithFormat("json"),
)
if err != nil {
return nil, err
}
fmt.Printf("%d agents online, %d capabilities\n",
caps.JSON.TotalAgents, len(caps.JSON.Capabilities))
// 2. Use compact format in LLM context for dynamic tool selection
// 3. Route to the best agent based on discovered capabilities
return map[string]any{
"available_agents": caps.JSON.TotalAgents,
"capabilities": caps.JSON.Capabilities,
}, nil
})
// New agents register at startup → the router discovers them automatically.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
| Surface | Scope | Primary use |
|---|---|---|
app.call("node.function") | Inside one AgentField control plane | Make inter-agent communication feel like a native backend call, with routing and execution context handled by AgentField. |
app.discover() / /api/v1/discovery/capabilities | Inside one AgentField control plane | Route to healthy local agents, reasoners, and skills. |
/.well-known/ai-catalog.json | Public catalog for selected entries | Let external agents, registries, and partner systems discover published capabilities. |
/api/v1/ard/search | Public ARD registry search when enabled | Let an outside client search your published catalog. |
| Discovery -> Imports | Your control plane | Record external ARD entries without making them callable yet. |
external.* callable binding | Your control plane | Route 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": []
}
]
}