Skip to content

ModelCatalog CRD

Group/Version: agents.re-cinq.com/v1beta1 · Kind: ModelCatalog · Scope: Namespaced · Short name: mc

The models the subsystem can run with the API keys it has. There is one object, named default, in the controller’s namespace (ai-agents by default). It has no spec: the controller owns it and writes only status. Callers such as an orchestrator read it to find out which model ids they can put in an AgentDefinition.

Terminal window
kubectl get mc default -n ai-agents -o yaml

kubectl get mc prints the Refreshed and Age columns.

FieldTypeNotes
refreshedAtdate-timeWhen the controller last refreshed the catalog (RFC 3339).
vendors[] objectOne entry per vendor that has at least one key to query; see below.
FieldTypeNotes
vendorstringclaude, codex or gemini: the agent CLI the subsystem routes these model ids to.
providerstringanthropic, openai or gemini: the API the models were listed from.
secretRefs[]stringThe agent-secrets keys that were queried. An AgentDefinition can run these models when its resources.secrets maps one of these keys.
errorstringWhy some keys failed on the last refresh, as <ref>: <message>; .... Empty when every key answered.
models[] object{id, displayName} for each model, deduplicated by id and sorted by id.

The controller lists the AgentDefinitions in its namespace and collects every distinct agent-secrets key they map in resources.secrets under one of these env names:

resources.secrets namevendorproviderList call
ANTHROPIC_API_KEYclaudeanthropicGET /v1/models (paginated)
OPENAI_API_KEYcodexopenaiGET /v1/models
GEMINI_API_KEYgeminigeminiGET /v1beta/models (paginated), only models that support generateContent

It reads each key from the agent-secrets Secret and calls the vendor’s list-models API with it. Only model ids the subsystem would route back to that vendor are kept, so OpenAI’s embeddings, dall-e and whisper models and Gemini’s gemma models are dropped. Models from several keys of the same vendor are merged into one entry.

A key that fails sets that vendor’s error; the other keys and vendors still publish their models. If every key of a vendor fails, its entry has the error and no models.

Only the leader replica refreshes the catalog: once right after it becomes leader, then every MODEL_CATALOG_REFRESH_SECONDS seconds (default 3600), set as an environment variable on the controller Deployment. A key mapped by a new AgentDefinition shows up at the next refresh. A refresh that fails as a whole (for example the AgentDefinition list is denied) is retried after 30 seconds.

The opencode and exec models are not listed.

The catalog is off by default. Without the deploy/components/model-catalog kustomize component the controller does nothing for it and no default object is written. The component grants the controller get on the agent-secrets Secret (that Secret only) and sets MODEL_CATALOG_ENABLED=true on the controller Deployment. Add it next to the base install as shown in Install; it can be combined with the batch component. See RBAC & network for the grant and the egress the controller then needs.

MODEL_CATALOG_ENABLED accepts only true or false, and MODEL_CATALOG_REFRESH_SECONDS only a non-negative integer. Any other value stops the controller at startup with MODEL_CATALOG_ENABLED must be "true" or "false", got: "<value>" or MODEL_CATALOG_REFRESH_SECONDS must be a non-negative integer, got: "<value>".

@re-cinq/agent-contracts exports the ModelCatalog, ModelCatalogStatus, VendorModels and ModelInfo types, and AgentContractsClient has a getModelCatalog() method that returns the default object in the client’s namespace. It throws on a 404, which is what you get when the catalog is not enabled:

const client = new AgentContractsClient(transport, "ai-agents");
const catalog = await client.getModelCatalog();
const claudeModelIds = catalog.status?.vendors
?.find((vendorModels) => vendorModels.vendor === "claude")
?.models?.map((model) => model.id);

The caller role in deploy/rbac/caller-rbac.yaml grants get, list and watch on modelcatalogs.

AgentDefinition models are not checked against the catalog. A model id that is not in it still runs, and an unknown id runs on the Claude CLI.