openai: compose healthchecks + dependency ordering, registries, LiteLLM routing

docker-compose.yml gains healthchecks and depends_on/condition chains for the
litellm/langfuse/postgres tier so dependants wait for a genuinely ready
service instead of a started container. Also plumbs AGAP_MCP_TOKEN into the
adolf and adolf-llm containers, sourced from openai/.env (gitignored), for the
kb#180 bearer auth on the agap MCP server; shared-mcp.json consumes it via
bearerTokenEnvVar so the Kimi backbone authenticates too.

agent-registry.yaml / agent_registry.py: the version-controlled source of
truth for agent identities and trust classes -- the same ids the agap-mcp
token map resolves to (`adolf`, `claude-coder`; note `claude-code-cli` is the
runtime entry, not an agent identity).

model-registry.yaml, litellm-config.yaml, auto-router-routes.json and
provision_litellm_keys.py: model tiering, virtual-key provisioning and
auto-router routes. tei-reranker/ is the local reranker service backing
Hindsight recall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 04:41:31 +00:00
parent a5c625b9b6
commit b27d31b3ca
12 changed files with 732 additions and 40 deletions

View File

@@ -153,6 +153,76 @@ def effective_card(registry, agent_id, model_registry=None):
return card
# ---------------------------------------------------------------------------
# litellm_key_spec — kb#147 (A2A-15): turn an agent's static registry fields
# into the LiteLLM virtual-key grant provision_litellm_keys.py provisions.
# "Grants live in the agent registry, not scattered configs" (kb#147 accept-
# ance bar) means the model allow-list and budget are COMPUTED here from
# preferred_tier + trust_class, never hand-typed per agent.
# ---------------------------------------------------------------------------
# Ascending order matching model-registry.yaml's routing.tiers keys. An
# agent may use its preferred tier and anything below it (a "large"-
# preferring agent degrades to "small" gracefully; a "small"-only agent
# never gets "large" — that asymmetry IS the sandboxed/trusted split this
# key spec exists to enforce).
_TIER_ORDER = ["small", "large"]
def _reachable_tiers(preferred_tier):
if preferred_tier not in _TIER_ORDER:
return []
return _TIER_ORDER[: _TIER_ORDER.index(preferred_tier) + 1]
def litellm_key_spec(registry, agent_id, model_registry=None):
"""Return the LiteLLM virtual-key grant for `agent_id`: which
litellm_model_name values it may use and its default budget, derived
from THIS registry's data (preferred_tier, trust_class) plus
model-registry.yaml's routing.tiers/metered_opt_in — never hand-entered
per agent. Models with no litellm_model_name (e.g. `kimi`, called
directly via the adolf-llm wrapper, never through LiteLLM) are outside
LiteLLM's enforcement surface by construction and are excluded, not
silently allowed.
provision_litellm_keys.py consumes this dict's `models`/`max_budget`/
`budget_duration`/`key_alias` as the body of a LiteLLM /key/generate (or
/key/update) call. This function makes no network call itself.
"""
a = get_agent(registry, agent_id)
model_registry = model_registry if model_registry is not None else mr.load_registry()
grant = a.get("capability_grant") or {}
key_alias = grant.get("litellm_key_alias", agent_id)
opted_in = set(model_registry.get("routing", {}).get("metered_opt_in", []) or [])
opted_in_key = f"agent:{agent_id}"
pools = model_registry.get("routing", {}).get("tiers", {})
models = []
for tier in _reachable_tiers(a.get("preferred_tier")):
for model_id in pools.get(tier, []):
m = mr.get_model(model_registry, model_id)
name = m.get("litellm_model_name")
if not name:
continue # not LiteLLM-routed (e.g. kimi's adolf-llm wrapper) -- nothing to grant/deny here
if m.get("metered") and opted_in_key not in opted_in:
continue # §3a: no metered API by default, per-key opt-in only
if name not in models:
models.append(name)
classes = registry.get("trust_classes", {})
cls = classes.get(a["trust_class"], {})
return {
"agent_id": agent_id,
"key_alias": key_alias,
"trust_class": a["trust_class"],
"models": models,
"max_budget": cls.get("default_budget_usd"),
"budget_duration": cls.get("budget_duration"),
"mcp_auth_token_env": grant.get("mcp_auth_token_env"),
}
# ---------------------------------------------------------------------------
# CLI — manual verification only, not part of the library contract.
# ---------------------------------------------------------------------------
@@ -175,6 +245,9 @@ def main():
p = sub.add_parser("can-reach-vault")
p.add_argument("--id", required=True)
p = sub.add_parser("litellm-key-spec")
p.add_argument("--id", required=True)
sub.add_parser("list")
args = ap.parse_args()
@@ -192,6 +265,8 @@ def main():
ok = can_reach_vault(reg, args.id)
print(json.dumps({"id": args.id, "can_reach_vault": ok}))
sys.exit(0 if ok else 1)
elif args.cmd == "litellm-key-spec":
print(json.dumps(litellm_key_spec(reg, args.id, model_reg), indent=2))
elif args.cmd == "list":
for a in reg["agents"]:
backbone = a.get("backbone") or "-"