feat(agents): manifest plumbing + GET /agents/registry (ADR-0014 step 3)

Each agent now exports a module-level MANIFEST declaring id, version,
pref_schema, required_consents, ttl_sec, and silenced_in_contexts. The
registry surfaces both the agent and its manifest, and rejects on
mismatch so the two cannot drift.

ml/serving exposes GET /agents/registry; services/api proxies it as
GET /api/agents/registry with a 60s in-process cache so admin pageviews
don't hammer upstream. Failures aren't cached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 10:55:54 +00:00
parent 5d43339616
commit 305eeae38b
13 changed files with 511 additions and 33 deletions

View File

@@ -38,7 +38,7 @@ if _repo_root not in sys.path:
sys.path.insert(0, _repo_root)
from ml.agents.base import AgentInput # noqa: E402
from ml.agents.registry import get_agent, all_agents # noqa: E402
from ml.agents.registry import get_agent, all_agents, all_manifests # noqa: E402
logging_config.configure()
@@ -177,6 +177,16 @@ def health():
}
@app.get("/agents/registry")
def agents_registry():
"""Manifest list for every registered agent (ADR-0014).
Consumers: TS recommender (eligibility filter), admin UI (auto-rendered
pref forms), inference framework (#111). Static at process boot.
"""
return {"agents": [m.to_dict() for m in all_manifests()]}
_RETRY_SUFFIX = (
"\n\nYour previous response was not valid JSON. "
"Reply ONLY with the JSON array — no prose, no markdown fences."