feat: the orchestrating agent
Entry point for the estate: understands a request, consults the specialists that can settle it, composes one answer. LangGraph 1.2.11, a2a-sdk 1.1.2, both current as of August 2026. It holds no data, and the realm is what enforces that rather than the prompt. classic-models-agent-assistant is granted two optional scopes — one per specialist, plus the inference endpoint — and every domain audience it could request comes back Invalid scopes. An injection that succeeds here finds no path to a domain because there is none. "No scopes at all" was too absolute: the agent has to reason, and inference is not a domain. The two-hop token chain was the heaviest unproven thing in the design and it holds: sub stays the end user through all four hops, so their own roles apply at the far end and the MCP server filters tool discovery for the right human. azp names the last agent, not the first — Keycloak accepts actor_token and emits no act claim, so an orchestrator's identity survives only in the traces. Verified end to end: a routed question answered from the specialist's data; a question outside every remit refused rather than answered from memory; and a write proposed through the orchestrator, held by the specialist in input-required, relayed here unchanged, approved by the user through the same conversation, and written. The orchestrator never owns that decision. The graph is plan → delegate → plan, tools described by what a specialist decides rather than what it holds, delegations in one step run concurrently and capped at eight counted in calls. Rebuilt per turn, because its tools carry the user's token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""Runtime configuration, entirely from the environment.
|
||||
|
||||
Nothing here has a value that would be wrong to commit, so the defaults are the
|
||||
lab's real endpoints. The one secret — this agent's Keycloak client secret — has
|
||||
no default and fails loudly if unset.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="ASSISTANT_", env_file=".env")
|
||||
|
||||
# --- identity -----------------------------------------------------------
|
||||
keycloak_realm_url: str = "https://keycloak.sttlab.local/realms/classic-models"
|
||||
client_id: str = "classic-models-agent-assistant"
|
||||
client_secret: str = Field(..., description="Keycloak client secret; no default on purpose")
|
||||
|
||||
# --- inference ----------------------------------------------------------
|
||||
# This agent has no data of its own. The only thing it may reach besides
|
||||
# other agents is the inference endpoint, and the realm enforces exactly
|
||||
# that: `classic-models-agent-assistant` is granted two optional scopes, one
|
||||
# per specialist plus this one. Every domain audience it could ask for comes
|
||||
# back `Invalid scopes`.
|
||||
llm_base_url: str = "https://llm-dev.sttlab.local/llm/v1"
|
||||
llm_model: str = "google/gemini-3.7-flash"
|
||||
llm_scope: str = "llm-proxy-audience"
|
||||
|
||||
# --- transport ----------------------------------------------------------
|
||||
# The lab uses a private CA. Point this at the bundle rather than disabling
|
||||
# verification. See the note on `.local` names in the README: macOS resolves
|
||||
# them through mDNS and takes ~5s per host, which is why the timeout is not
|
||||
# the usual 5 or 10.
|
||||
ca_bundle: str | None = None
|
||||
http_timeout: float = 60.0
|
||||
|
||||
# A turn that fans out and loops has to stop somewhere. Counted in
|
||||
# delegations, not in model calls — one specialist consulted twice for the
|
||||
# same question is the loop worth catching.
|
||||
max_delegations: int = 8
|
||||
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8080
|
||||
# Advertised in the Agent Card, so it must be the URL other callers reach,
|
||||
# not the bind address.
|
||||
public_url: str = "http://localhost:8080/"
|
||||
|
||||
@property
|
||||
def token_endpoint(self) -> str:
|
||||
return f"{self.keycloak_realm_url}/protocol/openid-connect/token"
|
||||
|
||||
@property
|
||||
def verify(self) -> str | bool:
|
||||
return self.ca_bundle if self.ca_bundle else True
|
||||
Reference in New Issue
Block a user