🐾 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:
2026-08-03 13:14:45 -04:00
parent 0ae0d55a8d
commit 455d3eef3d
3 changed files with 113 additions and 1 deletions

View File

@@ -191,7 +191,7 @@ agents:
| `agents.<name>.module` | yes | Importable Python module path containing a `fast` instance |
| `agents.<name>.port` | yes | Port for this agent's StreamableHTTP MCP server |
| `agents.<name>.title` | no | Display name in registry. Default: `name.title()` |
| `agents.<name>.description` | no | Description in registry |
| `agents.<name>.description` | no | Description in registry. Also becomes the `send_message` tool description, overriding any `description=` on the `@fast.agent` decorator |
| `agents.<name>.model` | no | `provider.model-name` override for this agent. Overrides `default_model`, is applied to every agent in the module at startup, and is what the registry advertises for this entry |
| `agents.<name>.model_capabilities` | no | Per-agent `{vision, context_window, max_output_tokens}` block. Overrides the top-level `model_capabilities`; the same defaults apply to omitted fields |
| `agents.<name>.depends_on` | no | List of agent names that must start and become ready before this agent |
@@ -452,6 +452,16 @@ Each agent's MCP tool accepts:
When `images` is provided, the message is sent as a `PromptMessageExtended` containing both `TextContent` and `ImageContent` parts — the agent's underlying model must support vision.
#### Tool description
The tool's description is what an MCP client shows next to the tool name, so it should say what the agent is *for*. Pallas resolves it in this order:
1. `agents.<name>.description` from `agents.yaml` — the deployment's source of truth, and the same text published in the registry
2. `description=` on the `@fast.agent` decorator
3. `Send a message to the {agent} agent` — a generic fallback
A description containing `{agent}` has the agent's name interpolated into it; other braces are left alone. Without (1), every agent whose module omits (2) falls through to the fallback, which tells a client nothing — so keep `agents.yaml` descriptions meaningful.
### Tool-Result Image Passthrough
Images work in both directions. fast-agent's `agent.send()` returns only the final assistant text, so images produced by downstream tools during the agentic loop (playwright screenshots, rommie desktop captures) would otherwise reach the agent's own vision model but never the MCP caller. A per-request `after_tool_call` hook (`pallas.image_passthrough`) collects every `ImageContent` block from the turn's tool results; at end of turn `send_message` returns a `CallToolResult` whose content is the assistant's text block followed by the collected images. Turns that produce no images return the plain string, unchanged from previous releases.