Skip to content

[Feature]: Stable embeddable Go library surface for in-process recipe resolution #1071

Description

@hkii

Prerequisites

  • I searched existing issues

Feature Summary

A small, semver-tracked Go API that lets external programs use AICR's
recipe resolution, bundling, snapshotting, and validation logic
in-process — without importing AICR's internal packages directly.

Problem/Use Case

AICR ships as a CLI binary and a REST server today. Both work well
for one-shot invocations from a user or a CI pipeline.

A new class of consumer wants to call AICR's logic from inside a
long-running Kubernetes controller — for example, a Crossplane
provider that reconciles a GPUCluster custom resource and needs to
re-resolve recipes whenever the source data or the cluster's
declared intent changes. These consumers need three things AICR does
not offer today:

A stable Go API contract. Today, external programs have to
import several internal AICR packages and stitch them together
with knowledge of how AICR initializes its data provider,
metadata store, and component registry. Every refactor of those
internals breaks external builds, and consumers have nothing to
pin against except a Git SHA.
Safe concurrent use within a single process. A controller
that reconciles two GPU clusters whose recipe sources differ
needs both resolutions to run side-by-side without
cross-contaminating each other's results. AICR's current caches
are process-global and keyed implicitly to the most recently
configured data source, so two sources active in one process
silently corrupt each other.
A way to release the caches. Long-running processes that ever
touch N distinct recipe sources accumulate N caches with no
eviction path, so memory grows monotonically.

Proposed Solution

Introduce a top-level package that external consumers can import
directly. The package exposes a single Client type with methods
that mirror AICR's existing workflow stages:

Resolve a recipe from criteria
Bundle the components of a resolved recipe into deployable
Helm values and rendered manifests
Collect a snapshot of live cluster state
Validate a resolved recipe against an observed snapshot
Release the client's caches when done
Each Client owns its own data source and its own caches.
Constructing two clients against different sources is the supported
pattern for the multi-source case; the clients do not share state.

The package becomes the project's semver-tracked surface. Internal
packages remain importable, but the documentation states explicitly
that they sit below the semver line — the same pattern Go's standard
database/sql uses for driver internals.

Success Criteria

  • A new package, importable as github.com/NVIDIA/aicr, ships and is
    documented under the integrator guide.
  • The CLI and REST server external behavior is unchanged; their
    existing tests pass without modification. Migrating their internal
    implementation to consume the facade is tracked separately and is
    not in scope for this issue (see "Out of Scope" below).
  • Two clients constructed from different recipe sources in the same
    process resolve concurrently and return results scoped to their own
    source. A test exercises this directly.
  • Releasing a client frees its caches; a test confirms memory does
    not grow when N clients are created and released in a loop.
  • A migration note documents how external callers move from the
    internal packages to the new client.
  • make qualify is green.

Alternatives Considered

  • Leaving the internal packages as the public surface and adding
    more documentation.
    Rejected — documentation does not fix the
    concurrency bug, and pinning to internal packages forces every
    external consumer to absorb internal refactors.
  • Forking AICR's logic into the downstream consumer. Rejected —
    duplicates recipe and bundler logic, diverges from AICR's recipe
    catalog over time, and forfeits the value of a curated runtime.
  • A REST-only integration where the controller spawns an aicrd
    sidecar.
    Rejected — adds a network hop and an extra process to
    every cluster, and complicates the failure modes (sidecar
    liveness, connection pool, retries) for a problem that an
    in-process library call solves directly.

Component

New component

Priority

Critical (blocking adoption or major use case)

Compatibility / Breaking Changes

Strictly additive at the API level. The new package is net-new;
nothing previously importable is removed. The CLI and REST surfaces are unchanged.

We'd welcome the maintainers' guidance on whether the addition
warrants a minor-version bump (e.g., v0.14.0) or whether it can
ship inside an existing planned release.

Operational Considerations

  • Observability: the new package exposes the same structured-log
    signals AICR already emits; no new log surface.
  • Upgrades: downstream consumers track AICR via go.mod, so a
    semver-tracked surface lets them adopt patch releases without
    re-validating their own integration.
  • Config drift: the per-client design eliminates a class of latent
    drift bug (one client's source bleeding into another) that exists
    today in any process running two sources.
  • Multi-tenancy: the per-client isolation is what makes
    multi-tenant controllers (one client per tenant configuration)
    safe.
  • Blast radius: zero blast radius on the CLI and REST. The change
    is contained to the new package plus the internal call sites the
    CLI and REST already own.

Are you willing to contribute?

Yes, I can open a PR

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions