This repository was archived by the owner on Jun 25, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
102 lines (80 loc) · 3.55 KB
/
Copy path__init__.py
File metadata and controls
102 lines (80 loc) · 3.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""Roster plugin — a ``list_agents`` tool over the delegate registry.
Ports Ava's habit of reading her live roster before assuming who's available. The
roster is whatever is configured for the always-on ``delegates`` plugin (the
top-level ``delegates:`` list that ``delegate_to`` dispatches to), annotated with
the delegates plugin's cached health when its prober is running.
Decoupled + degrades gracefully: it reads the delegates config via the delegates
plugin when present, else straight from the live config doc, and treats missing
health as "unknown". Storage/lookup live in plain functions so the tool is
unit-testable without the graph.
"""
from __future__ import annotations
import logging
from langchain_core.tools import tool
log = logging.getLogger("protoagent.plugins.roster")
def _read_delegates() -> list:
"""The configured delegates (raw dicts, no secrets). Prefer the delegates
plugin's reader; fall back to the live config doc directly."""
try:
from plugins.delegates.store import read_delegates_raw
return read_delegates_raw()
except Exception: # noqa: BLE001 — delegates plugin absent or moved
try:
from graph.config_io import load_yaml_doc
doc = load_yaml_doc() or {}
val = doc.get("delegates")
return list(val) if isinstance(val, list) else []
except Exception: # noqa: BLE001 — config read is best-effort
log.exception("[roster] reading delegates config failed")
return []
def _health() -> dict:
"""Cached per-delegate health from the delegates plugin's prober (or empty)."""
try:
from plugins.delegates.health import health_snapshot
return health_snapshot() or {}
except Exception: # noqa: BLE001 — prober not running / plugin absent
return {}
def list_agents_impl() -> list[dict]:
"""Structured roster: one dict per configured delegate. Pure (no graph)."""
health = _health()
out: list[dict] = []
for raw in _read_delegates():
if not isinstance(raw, dict):
continue
name = str(raw.get("name", "")).strip()
if not name:
continue
h = health.get(name) or {}
out.append(
{
"name": name,
"type": str(raw.get("type", "")).strip(),
"description": str(raw.get("description", "")).strip(),
"url": str(raw.get("url", "")).strip(),
"reachable": h.get("ok"), # True / False / None (unknown)
}
)
return out
def _fmt(rec: dict) -> str:
if rec["reachable"] is True:
badge = "🟢"
elif rec["reachable"] is False:
badge = "🔴"
else:
badge = "⚪"
desc = f" — {rec['description']}" if rec["description"] else ""
typ = f" ({rec['type']})" if rec["type"] else ""
return f"{badge} {rec['name']}{typ}{desc}"
@tool
def list_agents() -> str:
"""List the agents/delegates you can reach with `delegate_to`, with each one's
type, description, and current reachability (🟢 reachable · 🔴 down · ⚪ unknown).
Read this before assuming who's available — the roster is configuration, not a
fixed set, and it changes as delegates are added or removed."""
roster = list_agents_impl()
if not roster:
return "No agents configured. Add delegates (Console → Delegates) to reach other agents."
return "\n".join(_fmt(r) for r in roster)
def register(registry) -> None:
"""Entry point — called once per graph build."""
registry.register_tool(list_agents)