Merge pull request #2208 from Molecule-AI/fix/peer-discovery-fall-back-to-db-fields

fix(workspace): keep peers visible when agent_card is null
This commit is contained in:
hongmingwang-moleculeai 2026-04-28 21:23:22 +00:00 committed by GitHub
commit 5990f7a876
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 16 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import json
from typing import Any
from a2a.server.agent_execution import RequestContext
@ -89,33 +90,46 @@ def append_peer_guidance(
def summarize_peer_cards(peers: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Return compact peer metadata for prompt rendering."""
"""Return compact peer metadata for prompt rendering.
Falls back to the registry row's `name` and `role` when `agent_card` is
null or unparseable so peers stay visible to delegators even before
their A2A discovery roundtrip has populated a card. Without this
fallback a coordinator-tier workspace with N freshly-created worker
peers would render an empty `## Your Peers` section and refuse to
delegate (the regression behind the 2026-04-27 Design Director
discovery bug).
"""
summaries: list[dict[str, Any]] = []
for peer in peers:
agent_card = peer.get("agent_card")
if not agent_card:
continue
if isinstance(agent_card, str):
try:
import json
agent_card = json.loads(agent_card)
except Exception:
continue
agent_card = None
if not isinstance(agent_card, dict):
continue
agent_card = None
if agent_card:
skills_raw = agent_card.get("skills") or []
skills = [
s.get("name", s.get("id", ""))
for s in skills_raw
if isinstance(s, dict)
]
name = agent_card.get("name") or peer.get("name") or "Unknown"
else:
skills = []
name = peer.get("name") or "Unknown"
skills = agent_card.get("skills", [])
summaries.append(
{
"id": peer.get("id", "unknown"),
"name": agent_card.get("name", peer.get("name", "Unknown")),
"name": name,
"role": peer.get("role") or "",
"status": peer.get("status", "unknown"),
"skills": [
s.get("name", s.get("id", ""))
for s in skills
if isinstance(s, dict)
],
"skills": skills,
}
)
return summaries
@ -140,6 +154,8 @@ def build_peer_section(
parts.append(f"- **{peer['name']}** (id: `{peer['id']}`, status: {peer['status']})")
if peer["skills"]:
parts.append(f" Skills: {', '.join(peer['skills'])}")
elif peer.get("role"):
parts.append(f" Role: {peer['role']}")
parts.append("")
parts.append(instruction)
return "\n".join(parts)

View File

@ -203,8 +203,11 @@ def test_peer_capabilities_format(tmp_path):
assert "**Echo Agent** (id: `peer-1`, status: online)" in result
assert "Skills: echo, repeat" in result
assert "delegate_to_workspace" in result
# peer-2 has no agent_card so it's skipped
assert "Silent Agent" not in result
# peer-2 has no agent_card but DOES have a DB name + status — must
# still render so coordinators can delegate to freshly-created peers
# whose A2A discovery hasn't populated a card yet (regression of the
# 2026-04-27 Design Director discovery bug).
assert "**Silent Agent** (id: `peer-2`, status: offline)" in result
def test_peer_with_json_string_agent_card(tmp_path):

View File

@ -0,0 +1,111 @@
"""Pin peer-summary fallback when agent_card is missing.
Regresses the 2026-04-27 Design Director discovery bug:
`summarize_peer_cards()` previously skipped any peer whose `agent_card`
was null or unparseable, so a coordinator with freshly-created workers
saw an empty `## Your Peers` section in its system prompt and refused
to delegate. The registry endpoint already returns DB `name` + `role`
on every row regardless of agent_card state falling back to those
keeps peers visible while A2A discovery catches up.
"""
from __future__ import annotations
from shared_runtime import build_peer_section, summarize_peer_cards
def _peer(**overrides):
base = {
"id": "ws-1",
"name": "DB Name",
"role": "DB Role",
"status": "active",
"agent_card": None,
}
base.update(overrides)
return base
def test_summarize_includes_peer_with_null_agent_card_using_db_fields():
summaries = summarize_peer_cards([_peer()])
assert len(summaries) == 1
assert summaries[0]["id"] == "ws-1"
assert summaries[0]["name"] == "DB Name"
assert summaries[0]["role"] == "DB Role"
assert summaries[0]["status"] == "active"
assert summaries[0]["skills"] == []
def test_summarize_prefers_agent_card_name_over_db_name():
peer = _peer(
agent_card={"name": "Card Name", "skills": [{"name": "draft-spec"}]}
)
summaries = summarize_peer_cards([peer])
assert summaries[0]["name"] == "Card Name"
assert summaries[0]["skills"] == ["draft-spec"]
assert summaries[0]["role"] == "DB Role"
def test_summarize_handles_string_agent_card_json():
peer = _peer(agent_card='{"name": "JSON Name", "skills": []}')
summaries = summarize_peer_cards([peer])
assert summaries[0]["name"] == "JSON Name"
def test_summarize_falls_back_when_agent_card_string_is_malformed():
peer = _peer(agent_card="not-valid-json")
summaries = summarize_peer_cards([peer])
assert len(summaries) == 1
assert summaries[0]["name"] == "DB Name"
assert summaries[0]["role"] == "DB Role"
assert summaries[0]["skills"] == []
def test_summarize_falls_back_when_agent_card_is_wrong_type():
peer = _peer(agent_card=42)
summaries = summarize_peer_cards([peer])
assert len(summaries) == 1
assert summaries[0]["name"] == "DB Name"
def test_summarize_handles_missing_role_and_name_with_unknown_default():
peer = {"id": "ws-2", "status": "active", "agent_card": None}
summaries = summarize_peer_cards([peer])
assert summaries[0]["name"] == "Unknown"
assert summaries[0]["role"] == ""
def test_build_peer_section_renders_role_when_skills_empty():
section = build_peer_section([_peer()])
assert "## Your Peers" in section
assert "**DB Name**" in section
assert "Role: DB Role" in section
assert "Skills:" not in section
def test_build_peer_section_prefers_skills_over_role_when_card_present():
peer = _peer(
agent_card={"name": "Worker", "skills": [{"name": "design"}, {"name": "review"}]}
)
section = build_peer_section([peer])
assert "Skills: design, review" in section
assert "Role: DB Role" not in section
def test_build_peer_section_mixed_peers():
peers = [
_peer(id="ws-a"),
_peer(
id="ws-b",
agent_card={"name": "Card B", "skills": [{"name": "build"}]},
),
]
section = build_peer_section(peers)
assert "id: `ws-a`" in section
assert "id: `ws-b`" in section
assert "Role: DB Role" in section
assert "Skills: build" in section
def test_build_peer_section_empty_when_no_peers():
assert build_peer_section([]) == ""