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:
@@ -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."
|
||||
|
||||
21
ml/serving/tests/test_registry_endpoint.py
Normal file
21
ml/serving/tests/test_registry_endpoint.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""GET /agents/registry — manifests are exposed in JSON-serialisable form."""
|
||||
import pytest
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
|
||||
from main import app
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_registry_returns_all_agents():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
resp = await client.get("/agents/registry")
|
||||
|
||||
assert resp.status_code == 200
|
||||
payload = resp.json()
|
||||
ids = {a["id"] for a in payload["agents"]}
|
||||
assert ids == {"overdue-task", "momentum", "time-of-day", "recent-patterns", "focus-area"}
|
||||
|
||||
sample = payload["agents"][0]
|
||||
for key in ("id", "version", "description", "pref_schema", "required_consents", "ttl_sec"):
|
||||
assert key in sample
|
||||
Reference in New Issue
Block a user