feat: forward tool-result images to the MCP caller in send_message results #5
Reference in New Issue
Block a user
Delete Branch "feat/tool-result-image-passthrough"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Pallas multimodal support was inbound-only.
agent.send()returns only the final assistant text, soImageContentproduced 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 any lead agent driving a sub-agent) saw a text-onlyCallToolResult; the live stream reduced result images to a"+N images"preview. Screenshots never reached Robert.Fix
pallas.image_passthroughmodule: a per-requestafter_tool_callhook (same merge/restore composition asassistant_stream/loop_guard) collects everyImageContentblock from the turn's tool results.send_messagenow returns a FastMCPToolResultof[final text, *images]when images were collected; turns with no images return the plain string as before.-> strreturn annotation becomes-> str | ToolResult, which drops FastMCP's auto-generated{"result": string}output schema. Verified no consumer readsstructuredContent(Daedalus readsresult.contentonly, and already renders image blocks —chat.pyextractstype == "image").Config & observability
agents.yamlknobmax_result_images(default 8, keeps the most recent,0disables passthrough) — plumbed throughrequest_limitslike the other per-agent limits.pallas_result_images_total{agent}counter; drops are logged.Verification
tests/test_image_passthrough.py).ToolResultpasses through verbatim, union annotation yieldsoutputSchema: None.PYTHONPATH, temp topology): direct Dolores call returned[text, image/png]; Harper → Dolores delegation returned the same image intact through both hops;pallas_result_images_totalcounted dolores=2, harper=1.Found along the way (fixed in the open Dolores PRs, not here)
playwright's
browser_take_screenshotomits the image from its result whenever afilenameargument is passed — Dolores was helpfully naming files, so no image ever existed to forward. Also, a screenshot taken immediately after a cold browser launch can fail withUnable to capture screenshot(surface-readiness race; retry succeeds). Both addressed via prompt guidance in kottos#2 / mentor#3 / iolaus#4.Release train
v0.6.0tagged on pre-change main as the rollback point. Post-merge: tagv0.7.0, then pinpallas_relin ouranos/virgo inventories when ready (currentlymain). Version bumped to 0.7.0 in this PR.🤖 Generated with Claude Code