Logical services and local resolvers

The optional top-level service_directory is the fleet routing contract. authority names the target and absolute Stado binary that serve canonical versioned snapshots and commit placement changes; other hosts use their local registry only to bootstrap that SSH path. generation is monotonic. Each logical service declares its active host, host-relative loopback origin on every eligible host, optional placement profile, and the exact consumers and capabilities allowed to resolve it.

Per-target service_resolver policy declares the loopback resolution API and stable compatibility adapters for workloads on that host:

{
  "service_directory": {
    "authority": {
      "target": "control-host",
      "command": "/opt/stado/bin/stado"
    },
    "generation": 7,
    "services": {
      "model-router": {
        "placement_profile": "router-with-credentials",
        "active_host": "control-host",
        "endpoints": {
          "control-host": {"url": "http://127.0.0.1:8080"},
          "worker-host": {"url": "http://127.0.0.1:8080"}
        },
        "consumers": {
          "application": {"capabilities": ["model-routing"]}
        }
      }
    }
  },
  "targets": [{
    "name": "worker-host",
    "kind": "local",
    "service_resolver": {
      "api_bind": "127.0.0.1:17600",
      "refresh_seconds": 5,
      "max_stale_seconds": 15,
      "adapters": [{
        "service": "model-router",
        "bind": "127.0.0.1:17601",
        "consumer": "application"
      }]
    }
  }]
}

All resolver and adapter binds must be loopback. The authority target must have registry SSH transport, and the authority command must be an absolute, component-normal path. Remote host-relative endpoints require targets[].ssh; they are never rewritten into client configuration. The resolver refuses an unknown consumer, an active placement transaction, a rolled-back directory generation, or a cache older than the configured limit.

Each adapter declares idle_seconds — two minutes when it is not written down — and that window is measured against the last byte that moved in either direction of a proxied connection, not against each direction separately. A request/response connection is silent towards the service for exactly as long as the service works, so a per-direction window would cut answers that are still arriving — a slow object API under load then ends uploads with error sending request ... connection closed before message completed. A service that keeps writing keeps its connection, and the bound the window exists for is unchanged — a connection nobody is using is still closed, which is what keeps the resolver inside its descriptor budget.

When the window does close a connection, the resolver prints one line naming the service, the consumer, the endpoint, the window and the bytes it forwarded each way, and a client that spoke HTTP and received nothing yet reads 504 Gateway Timeout with service <name> did not answer within the <n>s idle window this adapter declares instead of a bare close. A service that legitimately thinks for longer than two minutes declares a larger idle_seconds on its own adapter; raising it for every adapter is what exhausted the resolver's file descriptors and took the local data plane down with Too many open files.

Source: this website