Data Flow Architecture
Data transformations in the four-stage workflow.
Overview
Data flows through four stages:
Each stage transforms input data into a different format:
- Snapshot: Captures raw system state (OS, GPU, Kubernetes, SystemD)
- Recipe: Generates configuration recommendations by matching query parameters against overlay rules
- Validate: Checks recipe constraints against actual system measurements
- Bundle: Produces deployment artifacts (Helm values, manifests, scripts)
Stage 1: Snapshot (Data Capture)
Input Sources
SystemD Services:
- Source:
systemctl show containerd.service - Data: Service configuration, resource limits, cgroup delegates
- Format: Key-value pairs from SystemD properties
OS Configuration:
- grub:
/proc/cmdline- Boot parameters - kmod:
/proc/modules- Loaded kernel modules - sysctl:
/proc/sys/**/*- Kernel runtime parameters - release:
/etc/os-release- OS identification
Kubernetes Cluster:
- Source: Kubernetes API via
client-go - server: Version info from
/versionendpoint - image: Container images from all pods across namespaces
- policy: GPU Operator ClusterPolicy custom resource
- slinky-slurm: Controller presence plus a secret-safe, single-Controller projection of associated NodeSet, LoginSet, RestApi, and Accounting CRs
- mariadb-operator: Official
k8s.mariadb.com/mariadbsAPI conflict evidence (not database availability or health) - aks-gpu-pools: Orchestration-layer projection, not a collector —
produced from the explicit operator-supplied pool dump passed to
aicr snapshot --aks-gpu-pools <file>(per-poolgpu-driverinstall modes). The pool file is parsed and validated up front — before any collector runs (local mode,pkg/snapshotter/snapshot.go) or before the agent Job is deployed (Job mode,pkg/snapshotter/agent.go) — so a malformed file fails loud. The resulting K8s subtype is then attached to the snapshot after the parallel collectors finish (local mode) or merged into the snapshot the Job returns after retrieval (Job mode)
GPU Hardware:
- Source: NFD/PCI enumeration via sysfs (driver-free; no
nvidia-smi) - Data: GPU presence, count, kernel-module state, and the accelerator SKU resolved from the PCI device ID
Snapshot Data Structure
Output Destinations:
- File:
aicr snapshot --output system.yaml - Stdout:
aicr snapshot(default, pipe to other commands) - ConfigMap:
aicr snapshot --output cm://namespace/name(Kubernetes-native)
ConfigMap Storage Pattern:
Agent Deployment:
Kubernetes Job writes snapshots directly to ConfigMap without volumes:
Reading Interface:
Collection Process
Parallel Collection
Context Propagation:
- All collectors respect context cancellation
- Collectors degrade gracefully: a collector that errors is logged and
skipped, and its measurement is omitted — a partial snapshot is the
intended outcome, not a hard failure of the whole snapshot. (The
orchestrator runs under
errgroup.WithContextso a future cancel-on-error collector is supported, but today per-collector errors are swallowed.) - Provider projections follow the opposite failure policy: the
aks-gpu-poolsprojection is explicit operator input, so a malformed pool file ABORTS the snapshot before any collector runs (pkg/snapshotter/snapshot.go) — it must never ride the degrade-to-warning path and masquerade as a snapshot whose reading is merely unavailable. - Each collector sets its own timeout rather than sharing a universal one — e.g. 10s (OS, systemd), 60s (Kubernetes), 90s (node topology), 5s (NFD GPU detection), up to 10m (network discovery)
Stage 2: Recipe (Data Optimization)
Recipe Input Options
Query Mode - Direct generation from parameters:
Snapshot Mode (File) - Analyze captured snapshot:
Snapshot Mode (ConfigMap) - Read from Kubernetes:
Query Extraction (Snapshot Mode)
When a snapshot is provided, the recipe builder extracts query parameters:
Extraction mapping
Fingerprint.ToCriteria projects only service, accelerator, os, and node
count. Intent and platform are recipe-author choices the cluster cannot
reveal, so they always resolve to any and must be supplied via CLI flags.
Other snapshot fields (K8s server version, OS version, kernel) are captured as
measurements and become constraint targets, not recipe criteria.
K8s.slinky-slurm records whether a Slinky Controller declaration was
observed, but does not infer platform: slurm. Slinky can coexist with other
training or inference stacks, and its resource projection does not reconstruct
Helm values. K8s.mariadb-operator likewise records only official API/CR
conflict evidence and never infers accounting intent or database source.
Hybrid resolution therefore combines snapshot-derived infrastructure criteria
with explicit intent and platform, as in the Slurm command above. Older
snapshots without either subtype remain valid inputs.
Recipe Generation
Inheritance and Overlay Merging
When a query matches a leaf recipe with a spec.base reference, the builder:
- Matches overlays by criteria. An overlay matches when every field it
specifies equals the query; omitted fields act as wildcards (e.g. an overlay
that omits
osmatches any OS). Theanysentinel is its own value — a queryanyonly matches a recipeany. - Resolves the inheritance chain for each match by following
spec.baseto the implicitbase, producing a root-to-leaf ordering such asbase → eks → eks-training → gb200-eks-training → gb200-eks-ubuntu-training. - Merges in order, later overlays overriding earlier ones.
- Applies mixins (
spec.mixins): appends their constraints andcomponentRefs, evaluating mixin constraints when a snapshot is provided. - Applies the selected profile after overlay and mixin composition. An
explicit
name=valueselection wins; otherwise the declaration’s default is used. Its overrides merge before finalization and registry defaults. - Strips context maps from subtypes unless context output is requested.
For the resolver internals (specificity scoring, deep-merge semantics) see Recipe architecture.
Recipe Data Structure
Unprofiled recipe results retain aicr.run/v1alpha2. Selecting a configuration
profile produces aicr.run/v1alpha3 and records the selected profile and its
owned value paths in result metadata.
Applied Overlays Example (with inheritance):
Stage 3: Validate (Constraint Checking)
Validation Process
The validate stage compares recipe constraints against actual measurements from a cluster snapshot.
Constraint Path Format
Constraints use fully qualified paths ({Type}.{Subtype}.{Key}) and a set of
comparison operators. The full path and operator reference — including the
per-validator operator narrowings and the inference-perf input entries
(model, concurrency, routing mode) — lives in the CLI reference; see
Constraint paths and operators.
Input Sources
File-based:
ConfigMap-based:
HTTP/HTTPS:
Validation Output
Results are emitted in CTRF (Common Test Report Format)
JSON: a top-level summary (test counts and start/stop timestamps) plus a
tests array where each entry carries a name, status
(passed/failed/skipped/other), suite (the phase — deployment, performance,
conformance), and stdout lines with the per-check evidence. For a worked
example of the full report and how performance checks such as inference-perf
surface their measured values, see
Emitting recipe evidence for a PR.
The readiness pre-flight is not a CTRF suite. It runs before any phase or report
is constructed, so a readiness failure exits 2 (see below) and produces no
CTRF output. Once phases run, the report carries an entry for every requested
phase — including phases reported skipped (for example, all of them under
--no-cluster, or phases after the first failure under --fail-fast).
CI/CD Integration
By default, the command exits with non-zero status on validation failures (ideal for CI/CD):
Stage 4: Bundle (Data Packaging)
Bundler Framework
pkg/bundler/registry exists but is not used by the production path: the
default flow constructs a single DefaultBundler, extracts values for every
component in one DefaultBundler invocation, builds one deployer
(helm/argocd/argocd-helm/flux/helmfile), and invokes it once. Static values land in values.yaml; dynamic, per-cluster values land in
cluster-values.yaml. Every component is handled in that single invocation, not by separate
per-component bundlers. The per-component file layout above is the Helm
deployer’s; argocd/argocd-helm/flux/helmfile emit their own layouts.
Finalization treats each deployer output as a closed-world inventory:
checksums.txt lists every regular payload file, including recipe.yaml when
present, and verification rejects additional files, directories, symlinks, or
other non-regular objects outside the exact allowed metadata paths.
Configuration Extraction
RecipeResult Pattern
Bundlers receive RecipeResult with component references and values maps:
Template usage: per-component values.yaml is the component’s values map
marshaled to YAML directly (not a Go-templated file). README.md and
deploy.sh are rendered from readmeTemplateData / deployTemplateData,
which expose fields like RecipeVersion, Components, and Constraints:
Template data
Scripts and READMEs are rendered from embedded templates using per-output
structs defined in pkg/bundler/deployer/helm/helm.go — readmeTemplateData
(recipe/bundler version, components, constraints) for README.md, and
deployTemplateData (bundler version, components, readiness timeout) for
deploy.sh. There is no ScriptData type.
Bundle Structure
The deployer generates the final output structure. See Deployer-Specific Output for details per deployer type.
Stage 5: Deployment (GitOps Integration)
Deployer Framework
After bundlers generate artifacts, the deployer framework transforms them into deployment-specific formats based on the --deployer flag.
Deployment Order Flow
Ordering follows each component’s declared dependencyRefs, not its linear position. The flat deploymentOrder (one topological serialization) numbers the NNN-<name>/ folders and drives the serial helm deploy.sh. The concurrent deployers derive ordering from the dependency graph so independent components roll out together, but differ in how faithfully: argocd, argocd-helm, and helmfile approximate it as depth tiers (a component waits for its whole prior tier), while flux projects the exact dependencyRefs DAG onto dependsOn (a component waits for only its actual dependencies). Each deployer expresses this differently:
The orderComponentsByDeployment() sort shown above produces the flat order used for folder numbering and the helm deploy.sh. The Argo CD sync-waves are not taken from that linear order: they are assigned by dependency tier (wave = tier*4 + phase), so components that share a tier share a wave and sync together. The 1, 5, 9 values above reflect the specific cert-manager → gpu-operator → network-operator dependency chain (one component per tier); a recipe with independent components would place them at the same wave. See Deployment ordering for the full model.
Deployer-Specific Output
Helm Deployer (default):
Folder names carry the NNN-<component>/ prefix (the number encodes
deployment order). Local-chart components instead ship Chart.yaml +
templates/ in place of upstream.env.
Argo CD Deployer:
Component folders use the same numbered NNN-<name>/ prefix as the Helm
deployer (the number encodes deployment order), and each folder holds its
application.yaml directly — there is no nested argocd/ subdirectory.
Argo CD Application with multi-source:
Deployer Data Flow
Data Serialization
Formats Supported
JSON:
YAML:
Table (Human-readable):
Serialization Pipeline
API Server Data Flow
Request Processing
The bundle response includes recipe.yaml when the deployer emits it and
excludes every unverified entry. X-Bundle-Files is the number of verified
regular files, and X-Bundle-Size is their aggregate uncompressed byte size.
The server rejects an additional file or directory, symlink, or other
non-regular object before the ZIP response is published.
Response Headers
Data Storage
Embedded Data
Recipe Data:
- Location:
recipes/overlays/*.yaml(includingbase.yaml),recipes/mixins/*.yaml - Embedded at compile time via
//go:embeddirectives - Loaded once per process, cached in memory
- The per-
Clientmetadata-store and component-registry caches persist for the lifetime of theClient(keyed onDataProvideridentity) and are released only byClient.Close()— they do not expire on a timer - The HTTP recipe/query response
Cache-Control: max-ageis600seconds (defaults.RecipeCacheTTL, 10 minutes) — a downstream/browser caching hint, distinct from the in-process caches above
Bundle Templates:
- Location:
pkg/bundler/*/templates/*.tmpl - Embedded at compile time:
//go:embed templates/*.tmpl - Parsed once per bundler initialization
No External Dependencies:
- No database
- No configuration files
- No network calls (except Kubernetes API for snapshots)
- Fully self-contained binaries
See Also
- Data Architecture - Recipe data architecture
- API Reference - API endpoint details
- Automation - CI/CD integration patterns
- CONTRIBUTING.md - Developer guide