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>
_build_registry built one capabilities dict from the global default_model and
attached it to every entry, so any agent with an `agents.<name>.model` or
`model_capabilities` override was advertised under the wrong model — even
though server.py applies those overrides at startup. Resolve capabilities per
agent, mirroring _register_unknown_models.
Also stop emitting null context_window / max_output_tokens when
model_capabilities is absent: _register_one_model registers the model with
131072 / 16384, so the registry now advertises those effective values. The
defaults are hoisted to module constants in server.py so the two cannot drift.
Verified against mentor's config: capabilities go from
{"context_window": null, "max_output_tokens": null} to {131072, 16384}, model
name unchanged. No checked-in deployment uses per-agent overrides today, so no
currently-published model changes.
Adds tests/test_registry.py (9 tests, first coverage for registry.py) and
documents agents.<name>.model / model_capabilities, which the agents.yaml
field table omitted entirely.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fast-agent's agent.send() returns only the final assistant text, so
ImageContent produced by downstream tools during the agentic loop
(playwright screenshots, rommie desktop captures) reached the agent's own
vision model but never crossed the MCP boundary — Daedalus and lead agents
saw text-only results.
A per-request after_tool_call hook (pallas.image_passthrough, same
composition pattern as assistant_stream / loop_guard) collects every
ImageContent block from the turn's tool results; send_message then returns
a FastMCP ToolResult of [final text, *images]. Turns with no images return
the plain string — wire shape unchanged (the str-only output schema is
dropped so the union return passes through cleanly; no consumer read
structuredContent). Images cascade hop-by-hop up delegation chains with no
extra wiring: verified live playwright → dolores → harper → MCP client,
image intact at each hop.
New per-agent agents.yaml knob max_result_images (default 8, keeps most
recent, 0 disables) and pallas_result_images_total counter. Version 0.7.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces `pallas.loop_guard` module that detects and halts agentic loops
where the same `(tool, args) → result` repeats consecutively, preventing
wasted LLM turns when upstream MCP servers return contradictory data.
- Add per-request `ToolRunnerHooks` tracking rolling tool-call signatures
- Halt loop after `loop_repeat_threshold` consecutive repeats (default 3)
- Collapse `max_iterations` on halt to terminate without further LLM call
- Append user-facing explanation to the turn with `stop_reason=endTurn`
- Expose `pallas_agent_loop_aborted_total{agent,reason}` counter
- Add per-agent `max_iterations` and `loop_repeat_threshold` config
- Document guard behavior, metric, and alerting query
Install a per-request `after_llm_call` hook that emits each intermediate
assistant turn as an MCP `notifications/message`, so users see
substantive text from earlier loop iterations instead of only the final
`agent.send()` return value.
Add tests covering the hook's payload shape, error handling, and
lifecycle via `install_for_request`.