flock-spawn-test / src /flock /worker /identity.py
cmpatino's picture
cmpatino HF Staff
deploy controller (2e85dec)
3ed7ad0 verified
Raw History Blame Contribute Delete
2.86 kB
"""msgboard identity: redeem the type's agent invite by name, inject the token.
`POST {MSGBOARD_URL}/v1/join {code, name}` is unauthenticated; re-joining with the same
name returns the same principal with a rotated token, so the worker joins once per agent per
worker process (first start on this worker) and reuses the token for later bursts. Swapping
to an admin "create-or-get agent by name" endpoint later means replacing `join` only.
"""
from __future__ import annotations
import logging
import os
from dataclasses import dataclass
import httpx
from flock.models import TypeSpec
log = logging.getLogger(__name__)
@dataclass
class MsgboardIdentity:
url: str
token: str
principal: str # external_id (the agent name) or the numeric id
async def join(url: str, code: str, name: str, *, client: httpx.AsyncClient | None = None) -> MsgboardIdentity:
own = client is None
client = client or httpx.AsyncClient(timeout=20)
try:
r = await client.post(url.rstrip("/") + "/v1/join", json={"code": code, "name": name})
r.raise_for_status()
body = r.json()
finally:
if own:
await client.aclose()
p = body.get("principal") or {}
return MsgboardIdentity(url=url, token=body["token"],
principal=str(p.get("external_id") or p.get("id") or name))
class Identities:
"""Per-worker cache of msgboard identities, one per agent."""
def __init__(self, env: dict[str, str] | None = None, client: httpx.AsyncClient | None = None) -> None:
self._env = env if env is not None else os.environ
self._client = client
self._ids: dict[str, MsgboardIdentity] = {}
def principal(self, agent: str) -> str | None:
i = self._ids.get(agent)
return i.principal if i else None
async def env_for(self, agent: str, spec: TypeSpec) -> dict[str, str]:
"""MSGBOARD_URL/MSGBOARD_TOKEN for the harness process, or {} when not configured
or the join fails (the agent still runs, just without a board identity)."""
if not spec.msgboard_invite:
return {}
if agent not in self._ids:
url = spec.env.get("MSGBOARD_URL") or self._env.get("MSGBOARD_URL")
code = self._env.get(spec.msgboard_invite)
if not url or not code:
log.warning("%s: msgboard_invite set but MSGBOARD_URL or secret %s missing",
agent, spec.msgboard_invite)
return {}
try:
self._ids[agent] = await join(url, code, agent, client=self._client)
except (httpx.HTTPError, KeyError, ValueError) as e:
log.warning("%s: msgboard join failed: %s", agent, e)
return {}
i = self._ids[agent]
return {"MSGBOARD_URL": i.url, "MSGBOARD_TOKEN": i.token}