Architecture¶
Anvil Serving is one umbrella product with six explicit authority domains: Model Serving, Capability Gateway, Evaluation & Evidence, Anvil Voice, Anvil Media, and Control Plane & Fleet. The gateway is an explicit capability meta-router implemented as a thin request path; it is not the whole product and does not perform semantic model selection.
Product-family architecture¶
| Family | Runtime authority | Handoff |
|---|---|---|
| Model Serving | Artifacts, recipes, manifests, lifecycle, and reservations | Presents a concrete endpoint for qualification. |
| Capability Gateway | Auth, exact route selection, protocol translation, readiness, admission, and relay | Exposes only a configured, reviewed capability. |
| Evaluation & Evidence | Functional gates, benchmarks, and durable evidence | Supports human review; never mutates serving state. |
| Anvil Voice | STT/TTS and realtime proxy lifecycle plus voice qualification | Uses declared owners and explicit audio routes. |
| Anvil Media | Named workflows, durable jobs, cancellation, qualification, and artifacts | Uses one declared worker and typed controller operations. |
| Control Plane & Fleet | Topology resolution, controller/MCP dispatch, host utilities, integrations, and fleet state | Preserves the selected resource owner's authority. |
The code-owned catalog and ordered journeys are documented in Product families. Each operational root command belongs to one family, while a reviewed user journey can cross family boundaries without transferring authority.
Request boundary¶
The router accepts Anthropic Messages, OpenAI Chat Completions, and the supported stateless Responses subset. It authenticates the caller, translates the request and tools for the selected upstream dialect, enforces the selected tier's context/tool/readiness/admission constraints, then relays ordinary or SSE responses. There is exactly one selected tier per accepted chat request.
Qualified same-host replicas. A declared 2–16-member equivalent set adds one internal member-admission/selection step only after the singular alias-to-tier decision. Members share one declared host and compatible served model, declared revision, engine/image/configuration provenance, dialect, and context/output/tool/media contract; each member has independent readiness. Declared provenance is not runtime attestation, and readiness is distinct from qualification and promotion.
round_robin is the default member strategy. Opt-in capacity scheduling
orders eligible members by local reservations divided by their declared
ceiling, conservative normalized upstream pressure, and rotating stable ID.
Tier admission owns the atomic selection and compound lease; aggregate tier
capacity is an explicit tier ceiling or the sum of member ceilings, never a
per-member multiplier. The pressure cache is bounded to configured members and
two workers, runs outside admission locks, and treats absent, stale, failed, or
malformed observations as unknown for ranking. It is scheduling evidence, not
qualification or a live deployment measurement.
One selected member receives one dispatch attempt. No member failure, changed readiness, metric change, or stream terminal path can reselect a peer. The same compound lease remains held until buffered or SSE terminal closure and is then released once. Current capacity projections do not start a refresh; historical DecisionLog scheduler values are pre-reservation selection evidence, not live gauges. See Configuration, ADR-0039, and ADR-0034.
The front door also has deterministic purpose-model endpoints for embeddings and reranking, plus normalized audio endpoints. They have separate operator-configured route tables and never join the chat route vocabulary. The same authenticated origin may additionally expose MCP, A2A, and opaque artifact routes. Those routes adapt to a durable operation service; they do not enter the inference relay or gain authority over lifecycle or placement.
Meta-router authority planes¶
The architecture separates identity, topology, served configuration, and evidence so each mutable fact has one authority:
| Plane | Authority | Runtime effect |
|---|---|---|
| Capability | Caller alias plus [router.model_routes] |
Selects exactly one tier or returns 404 |
| Topology and policy | Operator-owned router configuration | Fixes endpoint, dialect, auth reference, readiness, and safety rules |
| Private network | Tailscale user/node identity, grants, MagicDNS, and Serve | Makes only approved device paths reachable; does not replace Anvil application auth or resource ownership |
| Served configuration | Router config or the selected inference service | Supplies model identity, context, and allowlisted runtime facts for admission and metadata |
| Request | Router | Authenticates, validates, admits, translates, streams, and relays to the selected endpoint |
| Media operation | Durable media service | Validates named workflows and owns idempotent jobs, cancellation, reconciliation, and artifact metadata |
| Protocol projection | MCP and A2A adapters | Authorizes and projects the operation service; never owns execution state |
| Remote operation | Controller | Dispatches allowlisted typed operations and records confirmation/lifecycle receipts |
| Resource execution | Declared resource-owning host | Owns managed ComfyUI lifecycle, GPU reservations, and backend execution |
| Evidence and promotion | Evaluation artifacts and guarded operator commands | Determines whether configuration should change; never selects per request |
In upstream-owned mode, the router asks only the endpoint selected by the configured route. Metadata resolution is therefore downstream of route selection. It cannot introduce a second candidate, fallback, or endpoint choice. See Capability meta-router.
For media work, operator configuration likewise maps one named workflow version to one media service and resource owner. The gateway never accepts a raw ComfyUI graph, proxies backend routes, or substitutes another workflow or host. See ADR-0040.
Capability topology¶
A representative primary inference node exposes two equivalent GPUs as stable
UUID-backed Compute A and Compute B resources. Split mode admits compatible
workloads independently on either role. dual-gpu-exclusive mode drains and
stops every GPU inference competitor, then grants both roles to one explicitly
configured TP=2 serve. The router and controller may remain online, but an
alias whose backing serve is offline returns unavailable and never substitutes
the TP=2 model.
The broader multi-device example keeps a lightweight harness node model-free, places the voice agent and STT/TTS on a voice/audio node, and places ComfyUI plus an optional fast LLM on a media/burst node. Phones, tablets, and operator computers join only as approved clients. These are roles, not public machine identities or a claim about live operator state. See Private networking with Tailscale and Device topologies.
serves manages compose-backed model lifecycle and GPU reservations.
eval preflight and benchmark commands qualify a concrete endpoint. The
gateway can only expose a configured capability. It neither proves model
quality nor promotes a new recipe.
Split-host controller¶
The resource-owning inference node owns the execution plane. Its dedicated
Linux controller image
contains Anvil Serving, the pinned Docker CLI and Compose plugin, and the
NVIDIA runtime view. It receives only the Docker socket, declared serving
manifests, and a durable operation-state volume. Host loopback URLs in those
manifests are rewritten to the explicit host.docker.internal alias inside
the container; ordinary native and router-container behavior is unchanged.
The harness node owns the client plane. anvil-serving mcp serve is the
model-free stdio bridge that authenticates to the resource owner's controller
through host-owned Tailscale Serve. The packaged bridge uses the official
TypeScript MCP SDK:
its client-facing side negotiates either the initialize era through
2025-11-25 or stateless 2026-07-28, while its downstream client is pinned
to 2026-07-28. OpenClaw can therefore launch it with its initialize-based
SDK without adding a legacy listener to the resource owner. The controller is
published on the owning host's loopback only, so neither the container port
nor Docker socket
is directly reachable from the tailnet.
The controller runs non-root with a read-only root filesystem, dropped Linux capabilities, and an explicit operation allowlist. Docker-socket membership is still host-equivalent authority over Docker, so the token, loopback publish, tailnet ACL, restricted tool catalog, and absence of home/SSH/GitHub mounts are the actual security boundary. Git and SSH credentials are deliberately not available in the image.
Planned node-runtime bootstrap boundary¶
The managed bootstrap design extends the
control plane at one host-owned coupling point; it is not yet a shipped
installation workflow. Planning resolves one declared host:<id> and binds
its topology fingerprint, expected node, software/receiver digests and
platform adapters. Reachable nodes use the controller. Only explicit recovery
of a declared absent/unavailable controller may use the pinned forced-command
SSH receiver; denial or wrong identity never authorizes fallback. No remote
Docker socket, general shell, undeclared discovery or caller-supplied endpoint
is added.
Every bootstrap read requires node-admin:bootstrap; mutation also requires
local bootstrap policy, confirmation and immediate plan revalidation. The
artifact bundle contains no credentials or operator configuration. v1 uses
wheel-installed immutable venv generations with an already provisioned Windows
scheduled task or Linux systemd user supervisor, receiver and stable launcher.
It does not install/upgrade the Docker controller described above or a macOS
runtime, create supervisors, elevate privileges or install machine prerequisites.
An atomic generation pointer and flushed operation journal bracket activation. Fresh authenticated controller acceptance must prove exact node, package and immutable build identity, compatible protocol, expected per-node catalog and health. Journal state is reconciled against inspected runtime/supervisor state. Failure ends in verified prior-generation rollback or explicit manual recovery, not silent success or automated repair. Only validated operation staging may be cleaned; evidence and prior generations remain. Enrollment changes no model, route, GPU ownership, topology, client profile or promotion approval. See ADR-0034 and ADR-0035 for the state and authority boundaries.
Durability model¶
Every class of runtime state has exactly one authoritative home (ADR-0033). Secrets are
file-backed references in the operator home; the process environment overrides but is never
the only copy. Desired state is git-tracked operator config, with the anvil-router-cfg
volume holding the installed copy mutated only through the promote pipeline. The controller's
operation ledger lives on its durable volume and is reconciled at boot: an operation
interrupted by a crash is marked failed with a typed error, never silently re-executed.
Operator intent (tier quiescence) may be persisted opt-in on a router state volume;
readmission always re-passes the fail-closed health and identity gate. Docker itself is the
supervision ledger — no pidfiles, no registry service, no self-healing daemon. Everything
else — availability results, admission counters, in-flight requests — is process memory, and
restart is the reset.
Both long-lived servers drain gracefully on SIGTERM within a bounded budget before Docker's
stop_grace_period expires, which is what makes restart-as-reload a legitimate mechanism.
Operator command architecture¶
The public CLI is assembled from explicit command families under
anvil_serving/commands/. Each family module owns the paths, dispatch target,
safety class, topology metadata, and controller-operation mapping for one
cohesive area such as serves, models, or voice.
The @command_family decorator attaches the root-help category to a
module-local factory. commands/registry.py imports an explicit family list,
validates it, and orders the public roots deterministically. It does not scan
the filesystem or import operational handlers. Handlers remain lazy and are
imported only when dispatch or explicit validation resolves them.
The registry joins machine-relevant command facts to the code-owned product
catalog. Leaf argparse parsers own detailed argument help, while the family documentation owns
workflows, examples, configuration precedence, and behavioral guidance. The
v6 manifest intentionally omits prose copies of that documentation while
carrying stable family ids, promises, boundaries, commands, and docs anchors.
Evaluation and control-plane composition¶
The benchmark and control-plane public modules are stable compatibility facades. Responsibilities live in directed internal packages so callers keep their supported imports while persistence, security, protocol, and tool-family code remain independently reviewable.
anvil_serving/benchmark.py, anvil_serving/controller.py, and
anvil_serving/mcp.py preserve their documented imports, command entrypoints,
and compatibility trampolines. Internal modules do not scan the filesystem,
load entry points, or dynamically discover handlers. The MCP catalog is built
once from an explicit family tuple, rejects duplicate family and tool names,
and dispatches through one dictionary lookup.
Benchmark artifact validation is owned by benchmarking/artifacts.py. MCP
adapts its domain errors to the MCP error contract rather than carrying a
second path-validation implementation. Controller internals consume the public
MCP catalog/call surface, while MCP foundations and tool families do not import
controller internals.
The resource-owner controller uses the 2026-07-28 stateless request contract
exclusively. server/discover, tools/list, and tools/call are the only
JSON-RPC methods served at /mcp; initialize is intentionally absent.
Request metadata and the matching MCP-Protocol-Version, Mcp-Method, and
conditional Mcp-Name HTTP headers are validated before dispatch. The
harness-side bridge is the only dual-era boundary. The SDK pins one era per stdio
connection, converts both client eras to a modern authenticated controller
client, validates the controller identity and dynamic tool schemas, and
returns the result in the caller's negotiated wire format.
Deliberate non-components¶
The direct inference relay has no workload classifier, intent presets, quality-profile router, policy engine, residency selector, verification chain, circuit-breaker fallback, cloud route, or routing calibration loop. A selected tier that cannot serve returns an error rather than changing the request's capability. Named media workflows may expose explicit caller-selected quality profiles, but each profile is only a locked parameter set inside the same workflow; it cannot select another route, model, host, or backend. There is likewise no fleet registry service and no background reconciler daemon: the topology file is the registry, and Docker restart policy is the supervisor.
Evidence and observability¶
The read-only workload visibility schema projects bounded active and recent metadata from router, controller, benchmark, media, recipe, and manifest owners without becoming a second lifecycle authority. Node and fleet completeness, source timestamps, observation quality, and unknown omissions remain explicit across CLI, scoped HTTP/MCP, and dashboard surfaces.
DecisionLog records routing metadata without request/response content:
normalized alias, selected tier, timing, token counters where available, and
terminal outcome. Records carry a creation timestamp and can be persisted
opt-in to a bounded, append-only JSONL sink on the router state volume;
aggregate views remain snapshots of the in-memory buffer, not historical
windows. The controller's audit stream can likewise tee to a bounded file on
its state volume. All of it is audit evidence, not a feedback signal for
future routing. Benchmark results and preflight artifacts remain the source of
truth for changes to the configured mapping.