{"_id":"@a2a-lib/registry-server","_rev":"3-b73d2e86773abd1a862a5c96fbd5f0a2","name":"@a2a-lib/registry-server","dist-tags":{"latest":"0.3.0"},"versions":{"0.2.4":{"name":"@a2a-lib/registry-server","version":"0.2.4","keywords":["a2a","a2a-lib","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-lib/registry-server@0.2.4","maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"homepage":"https://github.com/a2a-lib/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-lib/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"54f1db4a8e5cbaf88c498942d160da09d2d4560c","tarball":"https://registry.npmjs.org/@a2a-lib/registry-server/-/registry-server-0.2.4.tgz","fileCount":52,"integrity":"sha512-IIRdpX0c3Wn9UBVueqZKFeHJqj6cBdEy6JE/2WHcx3dC/TYWEmFQBD71zvfkSQ6+YqN/6+9lQuVX92YYKpQiOA==","signatures":[{"sig":"MEUCIEiHDM1gWFaDu3nDr4uIR93CKJzjea6gsHLd4T8uAJRkAiEA+2CPAXJhSUFbB+3U06ihvm4a02xEUqc3PjweQs9RZ7g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":463979},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"cba1668ae70a8169d4b263efedd6eeea79cd9be2","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","prepack":"npm run build:all && node scripts/prepare-ui-package.mjs","build:ui":"npm --prefix ui run build","postpack":"node scripts/cleanup-ui-package.mjs","build:all":"npm run build:ui && npm run build","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"tsangwailam@gmail.com"},"repository":{"url":"git+https://github.com/a2a-lib/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"pino":"^10.3.1","@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.4_1787237483590_0.15643843996235374","host":"s3://npm-registry-packages-npm-production"}},"0.2.5":{"name":"@a2a-lib/registry-server","version":"0.2.5","keywords":["a2a","a2a-lib","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-lib/registry-server@0.2.5","maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"homepage":"https://github.com/a2a-lib/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-lib/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"9ea451f7b57bc757b1156f4690990fe6e9961095","tarball":"https://registry.npmjs.org/@a2a-lib/registry-server/-/registry-server-0.2.5.tgz","fileCount":56,"integrity":"sha512-dd4/4WL4/KtIPhqr/dYW5QgTzMl5wDc9Ni+tTjfjx/i7s/Q7f/lbru3K5hkCG0LT40yO4/1OImc56gbzW/Mt4A==","signatures":[{"sig":"MEUCIQCg/DkauZC/0sJFYZsEXOiYfOKygDDaBzmOCh+nkyNXWwIgMSH+b/6ktbHUFadsd09ZM1hKYCstukiiljKNX0tTp4M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":468779},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"616404176cf9a8418d9c6f7ad21486e69f45035f","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","prepack":"npm run build:all && node scripts/prepare-ui-package.mjs","build:ui":"npm --prefix ui run build","postpack":"node scripts/cleanup-ui-package.mjs","build:all":"npm run build:ui && npm run build","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"tsangwailam@gmail.com"},"repository":{"url":"git+https://github.com/a2a-lib/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"pino":"^10.3.1","@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.5_1787324801964_0.8039959235227181","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@a2a-lib/registry-server","version":"0.3.0","description":"A lease-based registry and discovery server for A2A agents","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"a2a-registry":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"engines":{"node":">=22"},"scripts":{"build:ui":"npm --prefix ui run build","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","build:all":"npm run build:ui && npm run build","prepack":"npm run build:all && node scripts/prepare-ui-package.mjs","postpack":"node scripts/cleanup-ui-package.mjs","check":"tsc -p tsconfig.json --noEmit","dev":"tsx watch src/cli.ts","cli":"tsx src/cli.ts","start":"node dist/cli.js","test":"node --import tsx --test test/*.test.ts","test:watch":"node --import tsx --test --watch test/*.test.ts"},"keywords":["a2a","a2a-lib","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/a2a-lib/a2a-registry-server.git"},"dependencies":{"@a2a-js/sdk":"^1.0.1","pino":"^10.3.1"},"devDependencies":{"@types/node":"^24.0.0","tsx":"^4.20.3","typescript":"^5.9.2"},"publishConfig":{"access":"public"},"gitHead":"342fd56ef1f78a26583718259bdf8e028bcc03b5","_id":"@a2a-lib/registry-server@0.3.0","bugs":{"url":"https://github.com/a2a-lib/a2a-registry-server/issues"},"homepage":"https://github.com/a2a-lib/a2a-registry-server#readme","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-FVHkVLEcZH7OpEBzE/g9ZQS+2/Y90UEXOmgclDwwry5PGPx7Xy3iAl3qpFC3jIAuE8N0nUucgKy4wZE56HqGrA==","shasum":"5f41c17f8e572f84c97137771f53b7ea0a6e6937","tarball":"https://registry.npmjs.org/@a2a-lib/registry-server/-/registry-server-0.3.0.tgz","fileCount":56,"unpackedSize":503148,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD8zgrV51sFLdUvUoWQ9ss8V6rjSFSu0w6nBtMTP+KJwwIhAM0SMzo1nwsvFc+DdpsFf/jIwcG5bYJmndGZ+7MK4Lha"}]},"_npmUser":{"name":"tsangwailam","email":"tsangwailam@gmail.com"},"directories":{},"maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/registry-server_0.3.0_1787937315547_0.6680974895688454"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T14:51:23.330Z","modified":"2026-08-28T17:15:15.898Z","0.2.4":"2026-08-20T14:51:23.761Z","0.2.5":"2026-08-21T15:06:42.206Z","0.3.0":"2026-08-28T17:15:15.668Z"},"bugs":{"url":"https://github.com/a2a-lib/a2a-registry-server/issues"},"license":"Apache-2.0","homepage":"https://github.com/a2a-lib/a2a-registry-server#readme","keywords":["a2a","a2a-lib","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"repository":{"type":"git","url":"git+https://github.com/a2a-lib/a2a-registry-server.git"},"description":"A lease-based registry and discovery server for A2A agents","maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"readme":"# A2A Registry Server\n\n[![GitHub release](https://img.shields.io/github/v/release/a2a-lib/a2a-registry-server)](https://github.com/a2a-lib/a2a-registry-server/releases/latest)\n[![GitHub stars](https://img.shields.io/github/stars/a2a-lib/a2a-registry-server)](https://github.com/a2a-lib/a2a-registry-server/stargazers)\n[![Docker image version](https://img.shields.io/docker/v/digicrafts/a2a-registry?sort=semver&logo=docker&label=Docker%20image)](https://hub.docker.com/r/digicrafts/a2a-registry/tags)\n[![Docker pulls](https://img.shields.io/docker/pulls/digicrafts/a2a-registry?logo=docker&label=Docker%20pulls)](https://hub.docker.com/r/digicrafts/a2a-registry)\n[![npm version](https://img.shields.io/npm/v/%40a2a-lib%2Fregistry-server)](https://www.npmjs.com/package/@a2a-lib/registry-server)\n[![npm downloads](https://img.shields.io/npm/dm/%40a2a-lib%2Fregistry-server)](https://www.npmjs.com/package/@a2a-lib/registry-server)\n[![CI](https://github.com/a2a-lib/a2a-registry-server/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/a2a-lib/a2a-registry-server/actions/workflows/ci.yml)\n\nA lease-based registration and discovery service for logical AI agents that publish [A2A Agent Cards](https://a2a-protocol.org/latest/specification/). A logical agent can expose multiple independently leased runtime instances that share one Agent Card. The server also provides ownership tokens, TTL heartbeats, filtering, pagination, caching, metrics, tests, and an optional distributed etcd store.\n\nThis project is a registry **for** A2A agents. Its registry REST API is intentionally separate from the A2A task/message protocol. The A2A specification standardizes Agent Cards and describes registries/catalogs as a discovery mechanism, but it does not prescribe one universal registry API.\n\n## Features\n\n- Stores current A2A 1.0 Agent Cards without stripping unknown fields\n- Accepts both v1 `supportedInterfaces[].url` and the legacy card `url`\n- Lease/heartbeat model inspired by etcd and Consul TTL checks\n- Multiple runtime instances per logical agent, with a shared Agent Card\n- Per-instance endpoint, metadata, TTL, lease token, heartbeat, and expiry\n- Optional global write bearer token controls who may create registrations\n- Discovery by skill, skill tag, capability, protocol binding, or agent name\n- Cursor pagination, ETags, readiness/liveness probes, Prometheus text metrics\n- Optional active HTTP/TCP health checks with passing, warning, and critical states\n- Revision-resumable Server-Sent Events watch API for local resolver updates\n- In-memory backend for development and an etcd v3 backend for replicated deployments\n- Compatibility aliases for the routes and `ttlMs` field in the original PoC\n- No web framework; runtime dependencies are the official A2A TypeScript SDK and Pino structured logger\n\n## Quick start\n\nRequirements: Node.js 22 or newer.\n\nInstall the package globally via npm:\n\n```bash\nnpm install -g @a2a-lib/registry-server\n```\n\nStart the registry server using the CLI:\n\n```bash\na2a-registry\n```\n\nAlternatively, run it directly without global installation using `npx`:\n\n```bash\nnpx @a2a-lib/registry-server\n```\n\nThe server starts by default at `http://localhost:3003` using the in-memory store.\n\n### Web dashboard\n\nThe React dashboard is maintained as the `ui` Git submodule and can be served on the same port as the registry API:\n\n```bash\ngit clone --recurse-submodules git@github.com:a2a-lib/a2a-registry-server.git\ncd a2a-registry-server\nnpm ci\nnpm --prefix ui ci\nnpm run build:all\nnode dist/cli.js --ui\n```\n\nWhen working from an existing clone, initialize the dashboard with `git submodule update --init --recursive`. The default build path is `ui/dist`; use `--ui-dir <path>` or `REGISTRY_UI_DIR` for a different static build. If the UI build is missing, dashboard requests return a clear `503` response while API and health endpoints remain available.\n\n### CLI options\n\nThe CLI accepts configuration flags (which take precedence over environment variables) as well as dotenv-compatible files:\n\n```bash\n# Start with explicit host, port, and store\na2a-registry --host 127.0.0.1 --port 3003 --store memory\n\n# Load configuration from a .env file\na2a-registry --env-file .env\n\n# Serve the built web dashboard with the API\na2a-registry --ui\n\n# Emit only warnings and errors\na2a-registry --log-level warn\n\n# Inspect all available options\na2a-registry --help\n```\n\nUse `--help` for all options. `SIGINT` and `SIGTERM` trigger a graceful shutdown that stops accepting connections, waits for active requests, and closes the storage backend.\n\n## Deploying with Docker\n\nThe registry server is available as the published Docker Hub image [`digicrafts/a2a-registry`](https://hub.docker.com/repository/docker/digicrafts/a2a-registry). The image includes the API, optional web UI, a non-root runtime user, and a readiness healthcheck on port `3003`.\n\n### Use the published Docker image\n\nPull a specific release tag for repeatable deployments:\n\n```bash\ndocker pull digicrafts/a2a-registry:0.3.0\n```\n\nRun the registry with the in-memory store:\n\n```bash\ndocker run -d \\\n  --name a2a-registry \\\n  --restart unless-stopped \\\n  -p 3003:3003 \\\n  -e REGISTRY_PORT=3003 \\\n  -e REGISTRY_STORE=memory \\\n  digicrafts/a2a-registry:0.3.0\n```\n\nThe `latest` tag is also available, but version tags are recommended for production:\n\n```bash\ndocker pull digicrafts/a2a-registry:latest\n```\n\nTo enable a write bearer token, pass it at runtime rather than storing it in the image:\n\n```bash\ndocker run -d \\\n  --name a2a-registry \\\n  --restart unless-stopped \\\n  -p 3003:3003 \\\n  -e REGISTRY_PORT=3003 \\\n  -e REGISTRY_STORE=memory \\\n  -e REGISTRY_WRITE_TOKEN=my-secret-token \\\n  digicrafts/a2a-registry:0.3.0\n```\n\nFor a distributed deployment, use `REGISTRY_STORE=etcd` and configure `ETCD_ENDPOINT`, `ETCD_PREFIX`, and any required etcd credentials. See [Distributed deployment with etcd](#distributed-deployment-with-etcd).\n\n### Build the Docker image locally\n\nYou can also build and deploy the registry server as a lightweight container using the included multi-stage `Dockerfile`:\n\n```bash\ndocker build -t digicrafts/a2a-registry:local .\n```\n\nRun the local image:\n\n```bash\ndocker run -d \\\n  --name a2a-registry \\\n  -p 3003:3003 \\\n  -e REGISTRY_PORT=3003 \\\n  -e REGISTRY_STORE=memory \\\n  digicrafts/a2a-registry:local\n```\n\n### Publish a Docker image\n\nLog in to Docker Hub and publish both a release tag and `latest`. The following command creates a multi-platform image for Linux AMD64 and ARM64:\n\n```bash\ndocker login\n\ndocker buildx create \\\n  --name digicrafts-builder \\\n  --driver docker-container \\\n  --use\n\ndocker buildx inspect --bootstrap\n\ndocker buildx build \\\n  --platform linux/amd64,linux/arm64 \\\n  --pull \\\n  -t digicrafts/a2a-registry:0.3.0 \\\n  -t digicrafts/a2a-registry:latest \\\n  --push \\\n  .\n```\n\nIf you only need one architecture, use `docker build` followed by `docker push` instead. The Docker daemon must be running before building or running containers.\n\n### Container health check\n\nThe container image includes a built-in healthcheck probing `http://127.0.0.1:3003/health/ready`. You can check container status and logs:\n\n```bash\ndocker ps --filter \"name=a2a-registry\"\ndocker logs a2a-registry\n```\n\n## Registering and discovering agents\n\nRegister an agent:\n\n```bash\ncurl -i http://localhost:3003/v1/agents \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"id\": \"weather-eu-1\",\n    \"ttlSeconds\": 60,\n    \"healthCheck\": { \"protocol\": \"http\", \"path\": \"/health\", \"intervalSeconds\": 10 },\n    \"metadata\": { \"region\": \"eu-west\" },\n    \"agentCard\": {\n      \"name\": \"Weather Agent\",\n      \"description\": \"Returns local forecasts\",\n      \"version\": \"1.0.0\",\n      \"supportedInterfaces\": [{\n        \"url\": \"https://weather.example/a2a\",\n        \"protocolBinding\": \"HTTP+JSON\",\n        \"protocolVersion\": \"1.0\"\n      }],\n      \"capabilities\": { \"streaming\": true },\n      \"defaultInputModes\": [\"text/plain\"],\n      \"defaultOutputModes\": [\"application/json\"],\n      \"skills\": [{\n        \"id\": \"forecast\",\n        \"name\": \"Weather forecast\",\n        \"description\": \"Forecast by location\",\n        \"tags\": [\"weather\"]\n      }]\n    }\n  }'\n```\n\nThe create response contains the server-assigned `instance.instanceId` and a `leaseToken`. Save the lease token securely: it is returned once and is required to update, renew, or remove the registration.\n\n```bash\nexport AGENT_LEASE_TOKEN='<value from registration response>'\n\ncurl -X POST http://localhost:3003/v1/agents/weather-eu-1/heartbeat \\\n  -H \"X-Registry-Lease-Token: $AGENT_LEASE_TOKEN\"\n\ncurl 'http://localhost:3003/v1/agents?skill=forecast&capability=streaming&tag=weather'\n\ncurl -X DELETE http://localhost:3003/v1/agents/weather-eu-1 \\\n  -H \"X-Registry-Lease-Token: $AGENT_LEASE_TOKEN\"\n```\n\nSend a heartbeat well before `ttlSeconds` elapses—normally every one-third of the TTL, with jitter and retry backoff. Registrations that omit `instanceId` receive a unique UUID from the server. The returned `instance.instanceId` can be used with the instance-specific routes; the legacy agent-level heartbeat and unregister routes continue to work for a sole instance or when the lease token identifies the instance.\n\n## Multiple instances\n\nRegister named instances with the same logical agent ID and exactly the same Agent Card. Put the instance-specific URL in `endpoint`; the shared card may advertise a stable load-balancer URL while discovery clients can select from `agent.instances` directly.\n\n```bash\ncurl -i http://localhost:3003/v1/agents/weather/instances \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"instanceId\": \"eu-west-1a\",\n    \"endpoint\": \"https://weather-a.example/a2a\",\n    \"ttlSeconds\": 60,\n    \"metadata\": { \"zone\": \"eu-west-1a\", \"weight\": \"100\" },\n    \"agentCard\": { \"name\": \"Weather Agent\", \"supportedInterfaces\": [{ \"url\": \"https://weather.example/a2a\" }] }\n  }'\n\ncurl -i http://localhost:3003/v1/agents/weather/instances \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"instanceId\": \"eu-west-1b\",\n    \"endpoint\": \"https://weather-b.example/a2a\",\n    \"ttlSeconds\": 60,\n    \"metadata\": { \"zone\": \"eu-west-1b\", \"weight\": \"100\" },\n    \"agentCard\": { \"name\": \"Weather Agent\", \"supportedInterfaces\": [{ \"url\": \"https://weather.example/a2a\" }] }\n  }'\n```\n\nEach response has a different `leaseToken`. Heartbeat an instance at `/v1/agents/{id}/instances/{instanceId}/heartbeat`. Discovery returns one logical agent containing both active records in `instances`; an instance disappears independently when its lease expires. While multiple instances are active, a registration with a different Agent Card is rejected with `409 agent_card_mismatch`.\n\n## API\n\n| Method | Path | Purpose |\n|---|---|---|\n| `POST` | `/v1/agents` | Register an instance (server-generated UUID when `instanceId` is omitted) |\n| `GET` | `/v1/agents` | Discover logical agents and active instances |\n| `GET` | `/v1/watch` | Stream revisioned registry snapshots as Server-Sent Events |\n| `GET` | `/v1/agents/{id}` | Fetch one logical agent and its active instances |\n| `POST` | `/v1/agents/{id}/instances` | Register a named instance |\n| `GET` | `/v1/agents/{id}/instances` | List active instances |\n| `PUT` | `/v1/agents/{id}/instances/{instanceId}` | Create or replace a named instance |\n| `GET` | `/v1/agents/{id}/instances/{instanceId}` | Fetch a named instance |\n| `POST` | `/v1/agents/{id}/instances/{instanceId}/heartbeat` | Renew a named instance lease |\n| `DELETE` | `/v1/agents/{id}/instances/{instanceId}` | Unregister a named instance |\n| `PUT` | `/v1/agents/{id}` | Register an instance, generating its ID when omitted |\n| `DELETE` | `/v1/agents/{id}` | Remove the compatibility instance, or the lease-token-owned sole instance |\n| `POST` | `/v1/agents/{id}/heartbeat` | Renew the compatibility instance, or the lease-token-owned sole instance |\n| `GET` | `/health/live` | Process liveness |\n| `GET` | `/health/ready` | Storage readiness |\n| `GET` | `/metrics` | Prometheus text metrics |\n| `GET` | `/openapi.yaml` | OpenAPI 3.1 document |\n\nDiscovery accepts `skill`, `tag`, `capability`, `protocolBinding`, `name`, `limit`, and `cursor`. Pagination and `total` count logical agents, and only logical agents with at least one unexpired instance are returned. The top-level instance fields (`endpoint`, TTL, timestamps, and metadata) remain as a compatibility projection of the first active instance, preferring an explicitly named `default` instance; new clients should use `instances`.\n\nAn optional `healthCheck` on each registration enables server-side HTTP or TCP probes. HTTP checks use the registration endpoint unless `path` is supplied; TCP checks connect to the endpoint host and port. Health results are returned as `instance.health` and do not extend the agent-driven TTL lease. The SSE watch endpoint sends an initial snapshot unless `after` (or `Last-Event-ID`) is supplied, then emits a new snapshot whenever the registry revision changes.\n\nPoC-compatible aliases remain available at `/v1/registry`, `/v1/registry/register`, `/v1/registry/agents`, and `/v1/registry/heartbeat`. They use the new ownership rules.\n\n## Configuration\n\n| Variable | Default | Meaning |\n|---|---:|---|\n| `REGISTRY_HOST` | `0.0.0.0` | Listen address |\n| `REGISTRY_PORT` | `3003` | Listen port |\n| `REGISTRY_PUBLIC_URL` | local port URL | Base URL used in service metadata |\n| `REGISTRY_STORE` | `memory` | `memory` or `etcd` |\n| `REGISTRY_LOG_LEVEL` | `info` | Minimum Pino log level: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, or `silent` |\n| `REGISTRY_DEFAULT_TTL_SECONDS` | `60` | Lease TTL if omitted |\n| `REGISTRY_MIN_TTL_SECONDS` | `10` | Lowest accepted TTL |\n| `REGISTRY_MAX_TTL_SECONDS` | `3600` | Highest accepted TTL |\n| `REGISTRY_WRITE_TOKEN` | unset | If set, registrations require `Authorization: Bearer …` |\n| `REGISTRY_CORS_ORIGIN` | `*` | CORS allow-origin value |\n| `REGISTRY_MAX_BODY_BYTES` | `1048576` | Maximum JSON body size |\n| `REGISTRY_HEALTH_CHECK_INTERVAL_MS` | `1000` | Scheduler tick for active health checks |\n| `REGISTRY_UI` / `REGISTRY_ENABLE_UI` | `false` | Serve the built web dashboard |\n| `REGISTRY_UI_DIR` | package `ui/dist` | Static dashboard build directory |\n| `ETCD_ENDPOINT` | `http://localhost:2379` | etcd v3 JSON gateway |\n| `ETCD_PREFIX` | `/a2a-registry/agents/` | etcd key prefix |\n| `ETCD_USERNAME`, `ETCD_PASSWORD` | unset | etcd authentication credentials |\n| `ETCD_BEARER_TOKEN` | unset | Pre-issued etcd auth token |\n\nOperational logs are emitted as newline-delimited JSON through Pino. The `--log-level`\nCLI option overrides `REGISTRY_LOG_LEVEL`; help and version output remain plain text.\n\n## Distributed deployment with etcd\n\n```bash\ndocker compose up --build\n```\n\nThe etcd adapter grants a lease for each runtime instance and attaches that instance's registry key to it. A heartbeat atomically reattaches the key to a new lease and revokes the previous lease. When an instance stops renewing, etcd removes only that instance key even if the registry process that accepted it has failed. All registry replicas must use the same `ETCD_PREFIX` and cluster.\n\nFor production, enable etcd authentication and TLS, use a dedicated least-privilege role restricted to the registry prefix, and run an odd-sized etcd cluster. The current adapter accepts an HTTPS endpoint but does not yet expose custom CA/client-certificate file settings.\n\n## Security model\n\n- `REGISTRY_WRITE_TOKEN` is an enrollment control; enable it outside trusted development networks.\n- `X-Registry-Lease-Token` proves ownership of one runtime instance. Only its SHA-256 hash is stored.\n- Put TLS and an identity-aware proxy/API gateway in front of the server. A shared write token is not a replacement for OAuth2, workload identity, or mTLS.\n- Active health checks intentionally fetch or connect to registered endpoints when configured; restrict registration access and network egress to trusted agents to manage SSRF risk.\n- Agent Cards are public discovery metadata. Do not place credentials or internal secrets in them.\n- Signed Agent Cards are preserved but signature verification and trust policy are deployment-specific and are not performed yet.\n\n## What to add next\n\n1. **Identity and policy:** OIDC/mTLS identities, tenant namespaces, RBAC, admission policy, and audit events. Bind the authenticated identity to the registered agent ID.\n2. **Trust:** verify A2A Agent Card JWS signatures, restrict `jku` origins, maintain trusted issuers/keys, and record verification status without modifying the signed card.\n3. **Active health checks:** add gRPC health probes and richer check policies; HTTP/TCP probes and passing/warning/critical state reporting are available now and remain separate from TTL heartbeats.\n4. **Watch API:** add a native etcd watch/gRPC stream for lower-latency cross-replica delivery; the current SSE endpoint provides revisioned snapshots and works with both memory and etcd stores.\n5. **Locality-aware resolution:** add first-class zone/region/weight fields, health-aware selection, and optional client-side round-robin helpers. Until then these values can be carried in per-instance metadata.\n6. **Consul adapter:** use Consul sessions/TTL checks and KV/catalog metadata when an organization already operates Consul.\n7. **Operations:** OpenTelemetry traces, labeled/rate metrics with bounded cardinality, rate limiting, quotas, backups, chaos tests, and SLO dashboards.\n8. **Governance:** moderation/approval workflows, metadata schemas, retention, version compatibility policy, and a documented response to compromised registrations.\n\n## Development\n\n```bash\nnpm run check\nnpm test\nnpm run build\n```\n\nThe memory store is used in unit/integration tests. Add an etcd container test before changing lease behavior.\n\n## References\n\n- [A2A 1.0 specification](https://a2a-protocol.org/latest/specification/)\n- [A2A discovery guide](https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md)\n- [Official TypeScript SDK](https://github.com/a2aproject/a2a-js)\n- [etcd leases and gRPC naming](https://etcd.io/docs/v3.8/dev-guide/grpc_naming/)\n- [Consul health checks](https://developer.hashicorp.com/consul/docs/reference/service/health-check)\n- [Consul blocking queries](https://developer.hashicorp.com/consul/api-docs/features/blocking)\n","readmeFilename":"README.md"}