|
5 | 5 | [](./LICENSE.md) |
6 | 6 | [](CODE_OF_CONDUCT.md) |
7 | 7 |
|
8 | | -The Directory Runtime is a discovery service that watches runtimes (Docker, Kubernetes) for workloads and provides a gRPC API for querying them. It resolves workloads using various resolvers (A2A, OASF) to extract and provide details about the workload's capabilities, integrating with the [Directory](https://github.com/agntcy/dir) for capability-based discovery. |
| 8 | +The Directory Runtime watches container runtimes (Docker, Kubernetes) for labeled workloads and exposes them through a gRPC API. Resolvers enrich workloads with capability metadata (A2A agent cards, OASF records from the [Directory](https://github.com/agntcy/dir)) for discovery-based routing. |
9 | 9 |
|
10 | | -## Architecture |
11 | | - |
12 | | -The system is split into two independent components: |
13 | | - |
14 | | -``` |
15 | | -┌──────────────────────────────────────────────────────────────────────────────┐ |
16 | | -│ Runtime Discovery System │ |
17 | | -└──────────────────────────────────────────────────────────────────────────────┘ |
18 | | -
|
19 | | -┌─────────────────────────────┐ ┌──────────────────────────────────────┐ |
20 | | -│ Discovery │ │ Server │ |
21 | | -│ (runtime/discovery) │ │ (runtime/server) │ |
22 | | -│ │ │ │ |
23 | | -│ ┌─────────────────────┐ │ │ ┌─────────────────────────┐ │ |
24 | | -│ │ Runtime Adapters │ │ │ │ gRPC API │ │ |
25 | | -│ │ - Docker │ │ │ │ /ListWorkloads │ │ |
26 | | -│ │ - Kubernetes │ │ │ └─────────────────────────┘ │ |
27 | | -│ └─────────────────────┘ │ │ ▲ │ |
28 | | -│ │ │ │ │ │ |
29 | | -│ ▼ │ └──────────────────────────────────────┘ |
30 | | -│ ┌─────────────────────┐ │ │ |
31 | | -│ │ Resolvers │ │ │ |
32 | | -│ │ - A2A │ │ │ |
33 | | -│ │ - OASF │ │ │ |
34 | | -│ └─────────────────────┘ │ │ |
35 | | -│ │ │ │ |
36 | | -│ ▼ │ │ |
37 | | -│ ┌─────────────────────┐ │ │ |
38 | | -│ │ Store Writer │ │ │ |
39 | | -│ └─────────────────────┘ │ │ |
40 | | -└────────────│────────────────┘ │ |
41 | | - │ write/manage │ |
42 | | - ▼ │ |
43 | | - ┌──────────────────────────────┐ │ |
44 | | - │ Storage Backend │ read/watch │ |
45 | | - │ - etcd (distributed) │──────────────────────┘ |
46 | | - │ - Kubernetes CRDs (native) │ |
47 | | - └──────────────────────────────┘ |
48 | | -``` |
49 | | - |
50 | | -## Components |
51 | | - |
52 | | -### Discovery (`discovery/`) |
53 | | - |
54 | | -The discovery component is responsible for: |
55 | | -- **Watching runtimes** for workloads with the `org.agntcy/discover=true` label. Supported runtimes: |
56 | | - - **Docker**: Watches Docker daemon for labeled containers |
57 | | - - **Kubernetes**: Watches Kubernetes API for labeled pods/services |
58 | | - - Extensible architecture allows adding more runtimes in the future |
59 | | -- **Resolving workload metadata** using configurable resolvers: |
60 | | - - **A2A resolver**: Extracts A2A agent card from workloads with `org.agntcy/agent-type=a2a` label |
61 | | - - **OASF resolver**: Resolves OASF records from Directory for workloads with `org.agntcy/agent-record=<fqdn>` label |
62 | | - - Extensible architecture allows adding more resolvers in the future |
63 | | -- **Writing workloads** to the storage backend (etcd or CRDs) |
64 | | - |
65 | | -The storage backend can be used to expose discovered workloads to other components (e.g., clients/servers) without coupling them directly and to reduce attack surface. |
66 | | - |
67 | | -In non-Kubernetes environments, etcd is recommended as the storage backend for better portability. |
68 | | -In Kubernetes environments, CRDs can be used for a more native experience to ensure clients can access workloads via the Kubernetes API. |
69 | | - |
70 | | -``` |
71 | | -Runtime Discovery --------> Runtime Server (for querying workloads via gRPC) |
72 | | - │ |
73 | | - └-------------------> CRD (for querying workloads via Kubernetes API) |
74 | | -``` |
75 | | - |
76 | | -### Server (`server/`) |
77 | | - |
78 | | -The server component provides a **gRPC API** for querying discovered workloads |
79 | | - |
80 | | -## Example Setup |
81 | | - |
82 | | -### Build Container Images |
| 10 | +## Quick Start |
83 | 11 |
|
84 | 12 | ```bash |
| 13 | +# Build images |
85 | 14 | IMAGE_TAG=latest task build |
86 | | -``` |
87 | | - |
88 | | -### Docker Compose |
89 | 15 |
|
90 | | -```bash |
91 | | -# Deploy the stack |
| 16 | +# Deploy discovery + server |
92 | 17 | docker compose -f install/docker/docker-compose.yml up -d |
93 | 18 |
|
94 | | -# Deploy example workloads |
95 | | -docker compose -f install/examples/docker-compose.yml up -d |
96 | | - |
97 | | -# Add discovery to all networks (required for resolvers to work) |
98 | | -docker network connect examples_team-a runtime-discovery |
99 | | -docker network connect examples_team-b runtime-discovery |
100 | | -docker restart runtime-discovery |
101 | | - |
102 | | -# Query the API |
103 | | -grpcurl -plaintext localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/ListWorkloads |
104 | | - |
105 | | -# Cleanup |
106 | | -docker compose -f install/docker/docker-compose.yml down |
107 | | -docker compose -f install/examples/docker-compose.yml down |
108 | | -``` |
109 | | - |
110 | | -### Kubernetes |
111 | | - |
112 | | -The Helm chart supports both CRD and etcd storage backends. |
113 | | - |
114 | | -#### Setup KIND Cluster |
115 | | - |
116 | | -```bash |
117 | | -# Create cluster |
118 | | -kind create cluster --name runtime |
119 | | - |
120 | | -# Load images into KIND |
121 | | -kind load docker-image ghcr.io/agntcy/dir-runtime-discovery:latest --name runtime |
122 | | -kind load docker-image ghcr.io/agntcy/dir-runtime-server:latest --name runtime |
123 | | -``` |
124 | | - |
125 | | -#### Deploy Example Workloads |
126 | | - |
127 | | -```bash |
128 | | -kubectl apply -f install/examples/k8s.workloads.yaml |
129 | | -``` |
130 | | - |
131 | | -#### Deploy with CRD Storage |
132 | | - |
133 | | -```bash |
134 | | -# Install the chart with CRD storage (default) |
135 | | -helm install runtime install/chart/ |
136 | | - |
137 | | -# Wait for pods |
138 | | -kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=discovery --timeout=60s |
139 | | -kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=server --timeout=60s |
140 | | - |
141 | | -# Query the gRPC API |
142 | | -kubectl port-forward svc/runtime-server 8080:8080 & |
| 19 | +# Query discovered workloads |
143 | 20 | grpcurl -plaintext localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/ListWorkloads |
144 | | - |
145 | | -# Query the Kubernetes API to see discovered workloads |
146 | | -kubectl get dw |
147 | 21 | ``` |
148 | 22 |
|
149 | | -#### Deploy with etcd Storage |
150 | | - |
151 | | -```bash |
152 | | -# Install the chart with etcd storage |
153 | | -helm install runtime install/chart/ \ |
154 | | - --set etcd.enabled=true \ |
155 | | - --set discovery.config.store.type=etcd \ |
156 | | - --set server.config.store.type=etcd |
157 | | - |
158 | | -# Wait for pods |
159 | | -kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=etcd --timeout=60s |
160 | | -kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=discovery --timeout=60s |
161 | | -kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=server --timeout=60s |
162 | | - |
163 | | -# Query the gRPC API |
164 | | -kubectl port-forward svc/runtime-server 8080:8080 & |
165 | | -grpcurl -plaintext localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/ListWorkloads |
166 | | -``` |
167 | | - |
168 | | -#### Cleanup |
169 | | - |
170 | | -```bash |
171 | | -kind delete cluster --name runtime |
172 | | -``` |
| 23 | +See the [documentation](docs/dir-component-runtime-discovery.md) for example workloads, Kubernetes deployment, workload labels, configuration, and the full gRPC API reference. |
173 | 24 |
|
174 | | -## Workload Labels |
| 25 | +## Documentation |
175 | 26 |
|
176 | | -Workloads are discovered based on labels. The discovery component watches for workloads with specific labels and processes their metadata. |
177 | | - |
178 | | -### Discovery Labels |
179 | | - |
180 | | -| Label | Runtime | Description | |
181 | | -|-------|---------|-------------| |
182 | | -| `org.agntcy/discover=true` | Kubernetes | Marks a pod/service for discovery | |
183 | | -| `org.agntcy/discover=true` | Docker | Marks a container for discovery | |
184 | | - |
185 | | -### Resolver Labels |
186 | | - |
187 | | -Resolvers extract metadata from discovered workloads based on their labels. |
188 | | -They provide additional information about the workload's capabilities. |
189 | | - |
190 | | -| Label/Annotation | Description | |
191 | | -|------------------|-------------| |
192 | | -| `org.agntcy/agent-type=a2a` | Enables A2A resolver - fetches A2A agent card from workload | |
193 | | -| `org.agntcy/agent-record=<fqdn>` | Enables OASF resolver - resolves record from Directory (e.g., `my-agent:v1.0.0`) | |
194 | | - |
195 | | -To configure OASF resolver, the Directory client must be set up using environment variables (e.g., `DIRECTORY_CLIENT_SERVER_ADDRESS`, `DIRECTORY_CLIENT_AUTH_MODE`). |
196 | | - |
197 | | -### Workload Services |
198 | | - |
199 | | -Discovered workloads have a `services` field that holds metadata extracted by resolvers: |
200 | | - |
201 | | -```json |
202 | | -{ |
203 | | - "id": "4467371c-84fd-4683-ab30-93895d78bab7", |
204 | | - "name": "service-a2a", |
205 | | - "hostname": "service-a2a", |
206 | | - "runtime": "kubernetes", |
207 | | - "type": "pod", |
208 | | - "labels": { |
209 | | - "app": "service-a2a", |
210 | | - "org.agntcy/discover": "true", |
211 | | - "org.agntcy/agent-type": "a2a", |
212 | | - "org.agntcy/agent-record": "my-agent:1.0.0" |
213 | | - }, |
214 | | - "addresses": [ |
215 | | - "10-244-0-9.team-a.pod" |
216 | | - ], |
217 | | - "ports": [ |
218 | | - "8080", |
219 | | - "9999" |
220 | | - ], |
221 | | - "isolationGroups": [ |
222 | | - "team-a" |
223 | | - ], |
224 | | - "services": { |
225 | | - "a2a": { |
226 | | - "name": "My Agent", |
227 | | - "description": "...", |
228 | | - "capabilities": [...] |
229 | | - }, |
230 | | - "oasf": { |
231 | | - "cid": "baf123", |
232 | | - "name": "my-agent:1.0.0", |
233 | | - "record": { |
234 | | - "name": "my-agent", |
235 | | - "version": "1.0.0", |
236 | | - "skills": [...] |
237 | | - } |
238 | | - } |
239 | | - } |
240 | | -} |
241 | | -``` |
242 | | - |
243 | | -## Configuration |
244 | | - |
245 | | -### Discovery Component |
246 | | - |
247 | | -| Environment Variable | Description | Default | |
248 | | -|---------------------|-------------|---------| |
249 | | -| `DISCOVERY_WORKERS` | Number of resolver workers | `16` | |
250 | | -| `DISCOVERY_STORE_TYPE` | Storage type (`etcd`, `crd`) | `etcd` | |
251 | | -| `DISCOVERY_STORE_ETCD_HOST` | etcd server hostname | `localhost` | |
252 | | -| `DISCOVERY_STORE_ETCD_PORT` | etcd server port | `2379` | |
253 | | -| `DISCOVERY_STORE_ETCD_USERNAME` | etcd username for authentication | `` | |
254 | | -| `DISCOVERY_STORE_ETCD_PASSWORD` | etcd password for authentication | `` | |
255 | | -| `DISCOVERY_STORE_ETCD_DIAL_TIMEOUT` | Timeout for connecting to etcd | `5s` | |
256 | | -| `DISCOVERY_STORE_ETCD_WORKLOADS_PREFIX` | etcd key prefix for workloads | `/discovery/workloads/` | |
257 | | -| `DISCOVERY_STORE_CRD_NAMESPACE` | Namespace to store workloads in | `default` | |
258 | | -| `DISCOVERY_STORE_CRD_KUBECONFIG` | Path to kubeconfig file (empty for in-cluster) | `` | |
259 | | -| `DISCOVERY_STORE_CRD_RESYNC_PERIOD` | How often to resync the cache from the API server | `30s` | |
260 | | -| `DISCOVERY_RUNTIME_TYPE` | Runtime type (`docker`, `kubernetes`) | `docker` | |
261 | | -| `DISCOVERY_RUNTIME_DOCKER_HOST` | Docker daemon socket path | `unix:///var/run/docker.sock` | |
262 | | -| `DISCOVERY_RUNTIME_DOCKER_LABEL_KEY` | Label key to filter containers | `org.agntcy/discover` | |
263 | | -| `DISCOVERY_RUNTIME_DOCKER_LABEL_VALUE` | Label value to filter containers | `true` | |
264 | | -| `DISCOVERY_RUNTIME_KUBERNETES_KUBECONFIG` | Path to kubeconfig file (empty for in-cluster) | `` | |
265 | | -| `DISCOVERY_RUNTIME_KUBERNETES_NAMESPACE` | Namespace to watch (empty for all namespaces) | `` | |
266 | | -| `DISCOVERY_RUNTIME_KUBERNETES_LABEL_KEY` | Label key to filter pods | `org.agntcy/discover` | |
267 | | -| `DISCOVERY_RUNTIME_KUBERNETES_LABEL_VALUE` | Label value to filter pods | `true` | |
268 | | -| `DISCOVERY_RESOLVER_A2A_ENABLED` | Enable A2A resolver | `true` | |
269 | | -| `DISCOVERY_RESOLVER_A2A_TIMEOUT` | Timeout for A2A discovery | `5s` | |
270 | | -| `DISCOVERY_RESOLVER_A2A_PATHS` | Comma-separated list of paths to probe for A2A discovery | `/.well-known/agent-card.json,/.well-known/card.json` | |
271 | | -| `DISCOVERY_RESOLVER_A2A_LABEL_KEY` | Label key to identify A2A workloads | `org.agntcy/agent-type` | |
272 | | -| `DISCOVERY_RESOLVER_A2A_LABEL_VALUE` | Label value to identify A2A workloads | `a2a` | |
273 | | -| `DISCOVERY_RESOLVER_OASF_ENABLED` | Enable OASF resolver | `true` | |
274 | | -| `DISCOVERY_RESOLVER_OASF_TIMEOUT` | Timeout for OASF resolution | `5s` | |
275 | | -| `DISCOVERY_RESOLVER_OASF_LABEL_KEY` | Label key to identify OASF workloads | `org.agntcy/agent-record` | |
276 | | - |
277 | | -The OASF resolver requires Directory client configuration via environment variables. |
278 | | -These are the same as those used by the Directory client library, e.g. `DIRECTORY_CLIENT_SERVER_ADDRESS`. |
279 | | -Refer to the [Directory Client documentation](https://github.com/agntcy/dir/tree/main/client) for all available options. |
280 | | - |
281 | | -When a workload has the configured OASF resolver label, the resolver will attempt to fetch the corresponding record from Directory and validate its signature before attaching it to the workload. |
282 | | - |
283 | | -### Server Component |
284 | | - |
285 | | -| Environment Variable | Description | Default | |
286 | | -|---------------------|-------------|---------| |
287 | | -| `SERVER_HOST` | Server bind address | `0.0.0.0` | |
288 | | -| `SERVER_PORT` | Server listen port | `8080` | |
289 | | -| `SERVER_STORE_TYPE` | Storage type (`etcd`, `crd`) | `etcd` | |
290 | | -| `SERVER_STORE_ETCD_HOST` | etcd server hostname | `localhost` | |
291 | | -| `SERVER_STORE_ETCD_PORT` | etcd server port | `2379` | |
292 | | -| `SERVER_STORE_ETCD_USERNAME` | etcd username for authentication | `` | |
293 | | -| `SERVER_STORE_ETCD_PASSWORD` | etcd password for authentication | `` | |
294 | | -| `SERVER_STORE_ETCD_DIAL_TIMEOUT` | Timeout for connecting to etcd | `5s` | |
295 | | -| `SERVER_STORE_ETCD_WORKLOADS_PREFIX` | etcd key prefix for workloads | `/discovery/workloads/` | |
296 | | -| `SERVER_STORE_CRD_NAMESPACE` | Namespace to read workloads from | `default` | |
297 | | -| `SERVER_STORE_CRD_KUBECONFIG` | Path to kubeconfig file (empty for in-cluster) | `` | |
298 | | -| `SERVER_STORE_CRD_RESYNC_PERIOD` | How often to resync the cache from the API server | `30s` | |
299 | | - |
300 | | -## gRPC API |
301 | | - |
302 | | -The server exposes a gRPC API defined in `proto/agntcy/dir/runtime/v1/discovery_service.proto`. |
303 | | - |
304 | | -### GetWorkload |
305 | | - |
306 | | -Get a specific workload by ID, name, or hostname. |
307 | | - |
308 | | -```bash |
309 | | -grpcurl -plaintext -d '{"id": "my-service"}' \ |
310 | | - localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/GetWorkload |
311 | | -``` |
312 | | - |
313 | | -### ListWorkloads |
314 | | - |
315 | | -Stream all workloads with optional label filters. Labels support regex patterns. |
316 | | - |
317 | | -```bash |
318 | | -# List all workloads |
319 | | -grpcurl -plaintext -d '{}' \ |
320 | | - localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/ListWorkloads |
321 | | - |
322 | | -# Filter by labels (supports regex) |
323 | | -grpcurl -plaintext -d '{"labels": {"org.agntcy/agent-type": "a2a"}}' \ |
324 | | - localhost:8080 agntcy.dir.runtime.v1.DiscoveryService/ListWorkloads |
325 | | -``` |
| 27 | +- [Runtime Discovery](docs/dir-component-runtime-discovery.md) — architecture, setup, labels, configuration, and API |
326 | 28 |
|
327 | 29 | ## Contributing |
328 | 30 |
|
|
0 commit comments