Capability Matching
The
capabilityMatchbehavior rule condition enables config-driven agent discovery. An orchestrating agent can dynamically find other agents that declare a specific skill, without hardcoding agent IDs. This is the foundation of EDDI's A2A (Agent-to-Agent) soft routing.
How It Works
┌───────────────────────────┐
│ Agent A (Orchestrator) │
│ behavior.json: │
│ condition: │
│ capabilityMatch │
│ skill: "translation" │
│ │
│ If SUCCESS → action: │
│ "delegate_to_translator"│
└──────────┬────────────────┘
│ queries registry
▼
┌───────────────────────────┐
│ CapabilityRegistryService │
│ │
│ Index: │
│ "translation" → │
│ Agent B (confidence:high)│
│ Agent C (confidence:med) │
└──────────┬────────────────┘
│ matched agent IDs
▼
┌───────────────────────────┐
│ Conversation Memory: │
│ capabilityMatch.results = │
│ ["agent-b-id", "agent-c"] │
└───────────────────────────┘Agent B and C declare capabilities in their
AgentConfiguration:Agent A (the orchestrator) uses
capabilityMatchin its behavior rulesWhen the condition evaluates, it queries the
CapabilityRegistryServiceMatching agent IDs are stored in memory as
capabilityMatch.resultsDownstream tasks (group orchestration, httpCalls, LLM tools) can consume the results
Configuration
behavior.json
Config Keys
skill
Yes
—
Skill name to search for (case-insensitive)
strategy
No
highest_confidence
Selection strategy: highest_confidence, round_robin, random, or all
minResults
No
1
Minimum number of matching agents for SUCCESS
Selection Strategies
highest_confidence
Sort matches by confidence (high → medium → low)
round_robin
Deterministic rotation — a per-skill counter advances one position on each query (for load distribution)
random
Shuffle matches randomly
all
Return all matches in natural order
Template Variables
Known limitation — template expressions do not currently resolve here.
CapabilityMatchCondition.resolveTemplatehands a config value to the templating engine only when the value contains the double-brace marker{{, so a single-brace Qute expression such as{properties.requiredSkill}never reaches the engine at all — and a double-brace one that does reach it is left literal, because Qute does not resolve{{ … }}(pinned byPlaceholderSyntaxContractTest). Either way the unresolved string is used as the skill name, matches nothing, and the condition silently returns FAIL. Giveskillandstrategyliteral values until this is fixed.
The intent is that config values are Qute template expressions, resolved against the conversation memory at evaluation time, enabling dynamic routing:
The skill and strategy values would be resolved using IMemoryItemConverter.convert(memory) — the same data map available to system prompts and httpCalls templates.
Agent Capability Declaration
Agents declare capabilities in their AgentConfiguration:
Capability Fields
skill
Yes
—
Unique skill identifier (lowercased for indexing)
confidence
No
medium
Self-declared confidence level: high, medium, low
attributes
No
{}
Key-value metadata for fine-grained filtering
Consuming Results
When capabilityMatch evaluates to SUCCESS, the matching agent IDs are stored in conversation memory:
Example 1: Action Delegation
The simplest pattern — match a skill, then fire an action that another task (e.g., LLM, httpCalls) reacts to:
The LLM task or httpCalls task listens for the call_translation_agent action and can access the discovered agents via memory.
Example 2: Dynamic Group Composition
Use the discovered agents to dynamically compose a group conversation:
behavior.json:
System prompt (LLM task triggered by create_expert_group):
The matched agent IDs are written with
storeDatainto the current step's data store under the keycapabilityMatch.results, so Java tasks and tools can read them. They are not reachable from a template:{memory.current.*}resolves against the step'sconversationOutput, which only theaddConversationOutput*methods populate — and the key itself contains a dot, so it would not be addressable as.capabilityMatch.resultseven if it were there.
Example 3: Template-Based Routing with Properties
Use PropertySetter to capture the user's intent, then route dynamically — subject to the limitation in Template Variables: the {properties.requiredSkill} below is not resolved by capabilityMatch today, so this example does not yet work.
property.json (PropertySetterTask):
behavior.json:
Attribute Filtering
The CapabilityRegistryService also supports fine-grained attribute matching via the findBySkillAndAttributes API. This is available to in-process Java callers only — no REST endpoint, MCP tool or behavior rule config currently exposes attribute filtering (/capabilities, the A2A /.well-known/capabilities endpoint and the findAgentsByCapability tool all call the skill-only overload). Example:
Comma-separated attribute values are matched with contains — "en,de,fr" matches "de".
Metrics
The CapabilityRegistryService exposes metrics at /q/metrics:
eddi.capability.query.count
Total number of capability queries
eddi.capability.query.time
Query execution time distribution
ContentTypeMatcher — Attachment Routing
A companion condition for routing based on attachment MIME types:
mimeType
—
MIME type pattern (supports */*, image/*, application/pdf)
minCount
1
Minimum number of matching attachments
This condition reads from the attachments memory key populated by the attachment pipeline (see docs/attachments-guide.md).
Last updated
Was this helpful?