Files
agent-harness-spec/docs/registration.md
Jonathan Boniface e6f10ba8c9 feat: initial harness spec v1.0.0 + reference template
Adds the normative contract every platform agent must conform to, plus a
working reference template under template/ that ticks every box out of
the box.

Spec is derived from the production agents shipped in
cjot-backstage-az/agents/ on branch sandbox/jonathan:
  - agent-registry, agent-factory, decomposer, discovery-agent,
    golden-path, modernization-factory, modernization-factory-v2,
    policy-transformer, scaffold-agent, support-intake-agent, the-watcher

Contents:
  - SPEC.md          normative contract (13 sections + conformance checklist)
  - CHANGELOG.md     spec versioning (1.0.0)
  - docs/
      registration.md   self-registration with the agent-gateway
      observability.md  OTel logs/metrics/traces wiring
      manifest.md       /.well-known/agent.json schema + AgentSkill
                        serialisation (pydantic vs protobuf)
      kubernetes.md     k8s deployment shape, mandatory cross-refs,
                        per-namespace agents, resource sizing
      deployment.md     .image-version, ACR build, deploy scripts, rollback
      deviations.md     tracked debts against the spec
  - template/
      .env.local.example, .gitignore, .image-version (0.1.0),
      Dockerfile (python:3.12-slim, non-root UID 1001, HEALTHCHECK),
      requirements.txt (harness floor + optional LLM stack),
      app/agent.py (Starlette entry, lifespan + self-registration,
                    defensive _skill_dict for pydantic vs protobuf),
      app/logging_setup.py (canonical OTel log bridge — copy verbatim),
      app/metrics.py (meter + example counter/histogram),
      app/skills.py (AGENT_CONFIG + AgentSkill list),
      k8s/configmap.yaml + deployment.yaml (Deployment + Service,
                                            OTel annotations + 6-var env block,
                                            agents-sa + agents-kv-spc bindings),
      scripts/deploy.sh (auto-bump + az acr build + apply + rollout),
      scripts/full-deploy.sh (preflight + deploy + post-deploy smoke),
      scripts/deploy-local.sh (docker/podman + .env.local)
2026-06-09 15:54:18 +01:00

3.8 KiB

Self-registration with the agent-gateway

Normative requirements: SPEC.md §5

Every agent self-registers with the agent-gateway on startup. The gateway is the single Service in front of the registry catalogue and the proxy through which other agents (and Backstage) reach this agent's endpoints.

Topology

sequenceDiagram
    participant A as Agent pod
    participant G as agent-gateway
    Note over A: lifespan startup
    A->>A: asyncio.sleep(2s)<br/>(let uvicorn bind)
    A->>G: POST /agents/register-url<br/>{"endpoint": AGENT_SELF_URL}
    G->>A: GET {AGENT_SELF_URL}/.well-known/agent.json
    A-->>G: 200 OK + manifest
    G-->>A: 201 Created (registration row)

Configuration

Two environment variables drive the flow. Both MUST be set in the agent's ConfigMap.

Var Example Purpose
REGISTRY_URL http://agent-gateway.agents.svc.cluster.local The gateway's base URL.
AGENT_SELF_URL http://policy-transformer.agents.svc.cluster.local The URL the gateway should call back on.

Notes:

  • The registry validates AGENT_SELF_URL against an SSRF allow-list. The host MUST end in .svc.cluster.local or .kyndemo.live and use http or https. Anything else is rejected with 400 Bad Request.
  • Cross-namespace AGENT_SELF_URL values are fine as long as the URL is resolvable in cluster DNS.
  • The trailing path on AGENT_SELF_URL MUST be / (or empty); the gateway appends /.well-known/agent.json itself.

The registration call

The canonical implementation lives in template/app/agent.py. Key invariants:

  • Initial sleep of 2 seconds before the first attempt — uvicorn needs time to bind the listen socket and the manifest route.
  • 3 attempts total, 5 seconds between attempts, 10-second timeout per request.
  • HTTP 200 or 201 are both treated as success.
  • Logging:
    • INFO once on success.
    • WARNING per failed attempt with HTTP <code> + body excerpt.
    • ERROR once on final failure (after the third attempt).
  • The task is created via asyncio.create_task inside the Starlette lifespan, so it does not block uvicorn from accepting traffic. A failed registration MUST NOT crash the agent.

What the gateway does next

After the agent's POST /agents/register-url succeeds, the gateway performs:

  1. GET {endpoint}/.well-known/agent.json (hardcoded path).
  2. Parses the manifest into an AgentRegistration row keyed by the manifest's name, normalised to lower-hyphen-case.
  3. Registers the agent in the catalogue.
  4. Routes inbound traffic from /proxy/agent-gateway/{agent_name}/{path} to {endpoint}/{path}.

Therefore the manifest at AGENT_SELF_URL MUST be reachable and MUST match the schema in docs/manifest.md.

Failure modes & resolution

Symptom Cause Fix
HTTP 400 — Failed to fetch agent card from .../.well-known/agent.json: HTTP 404 The agent has no manifest route. Add Route("/.well-known/agent.json", agent_manifest, methods=["GET"]).
HTTP 400 — ... HTTP 500 The manifest route raises (e.g. AgentSkill.model_dump AttributeError). Use the defensive _skill_dict() helper from the template.
HTTP 400 — endpoint URL not allowed AGENT_SELF_URL host does not end in .svc.cluster.local or .kyndemo.live. Fix the ConfigMap.
Three WARNINGs + final ERROR but agent serves traffic fine Gateway pod was restarting during agent startup. Restart the agent pod once the gateway is Ready. Re-registration on next pod restart will recover.

Local development

When running outside the cluster, leave REGISTRY_URL unset (or set it to "") — the agent will log REGISTRY_URL not set — skipping self-registration and continue.