🐾 fix: use the agents.yaml description as the send_message tool description
Every agent's MCP tool advertised the generic "Send a message to the {agent}
agent" fallback, because register_agent_tools only had the @fast.agent
decorator's description to fall back on — and no agent in the estate sets one
(0 of 34 agent modules across kottos, iolaus, mentor and dodona).
Meanwhile the description an operator actually wrote already sits in
agents.yaml and is published in the registry; _start_agent had it in scope
and simply never passed it through. Wire it to tool_description.
Fixes every agent in every deployment at once, with no per-repo edits:
scotty's tool description becomes "Systems administration expert —
infrastructure diagnostics, security hardening, and keeping everything
running" instead of "Send a message to the scotty agent".
Adds tests/test_tool_description.py pinning the resolution order
(agents.yaml > decorator > fallback) and the {agent} templating, including
that prose containing other braces is not passed through .format().
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
tests/test_tool_description.py
Normal file
88
tests/test_tool_description.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Tests for send_message tool-description resolution.
|
||||
|
||||
``server._start_agent`` passes the agents.yaml ``description`` through as
|
||||
``tool_description``, so MCP clients see the description an operator actually
|
||||
wrote instead of the generic "Send a message to the {agent} agent" fallback.
|
||||
|
||||
These pin the precedence implemented in
|
||||
``MultimodalAgentMCPServer.register_agent_tools``:
|
||||
|
||||
tool_description (agents.yaml) > @fast.agent description > fallback
|
||||
|
||||
Constructing a real ``MultimodalAgentMCPServer`` needs a live FastAgent
|
||||
instance, so these exercise the resolution expression directly — it is the
|
||||
part that carries the logic, and the part that would silently regress.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _resolve(
|
||||
tool_description: str | None,
|
||||
agent_description: str | None = None,
|
||||
agent_name: str = "scotty",
|
||||
) -> str:
|
||||
"""Mirror register_agent_tools' description resolution."""
|
||||
resolved = (
|
||||
tool_description.format(agent=agent_name)
|
||||
if tool_description and "{agent}" in tool_description
|
||||
else tool_description
|
||||
)
|
||||
return (
|
||||
resolved
|
||||
or agent_description
|
||||
or f"Send a message to the {agent_name} agent"
|
||||
)
|
||||
|
||||
|
||||
# ── Precedence ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_agents_yaml_description_is_used() -> None:
|
||||
"""The agents.yaml description reaches the tool, not the fallback."""
|
||||
assert _resolve("Systems administration expert", None) == (
|
||||
"Systems administration expert"
|
||||
)
|
||||
|
||||
|
||||
def test_agents_yaml_wins_over_decorator() -> None:
|
||||
"""agents.yaml is the deployment's source of truth for agent metadata."""
|
||||
assert _resolve("From agents.yaml", "From the decorator") == "From agents.yaml"
|
||||
|
||||
|
||||
def test_decorator_used_when_no_yaml_description() -> None:
|
||||
"""An agent module's own description still beats the generic fallback."""
|
||||
assert _resolve(None, "From the decorator") == "From the decorator"
|
||||
|
||||
|
||||
def test_falls_back_when_nothing_configured() -> None:
|
||||
"""With neither source set, the generic fallback stands."""
|
||||
assert _resolve(None, None) == "Send a message to the scotty agent"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("empty", ["", None])
|
||||
def test_empty_description_falls_through(empty: str | None) -> None:
|
||||
"""An empty agents.yaml description must not shadow the other sources."""
|
||||
assert _resolve(empty, "From the decorator") == "From the decorator"
|
||||
|
||||
|
||||
# ── Templating ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_agent_placeholder_is_interpolated() -> None:
|
||||
"""``{agent}`` in a description is substituted with the agent name."""
|
||||
assert _resolve("Talk to {agent} about ops") == "Talk to scotty about ops"
|
||||
|
||||
|
||||
def test_other_braces_are_left_alone() -> None:
|
||||
"""Prose containing braces is only formatted when it holds ``{agent}``.
|
||||
|
||||
Guards the ``"{agent}" in ...`` check — an unconditional ``.format()``
|
||||
would raise KeyError on a description mentioning JSON.
|
||||
"""
|
||||
description = 'Handles JSON like {"a": 1} safely'
|
||||
assert _resolve(description, None) == description
|
||||
Reference in New Issue
Block a user