One identity and one source
Polyglot identifies an offering by the exact pair(driver, wire model). A profile owns the
offering’s support status, context and output limits, modalities, capabilities, source, and
catalog version. LLMConfig and connection presets deliberately do not duplicate those facts.
find() never guesses by provider or model-family pattern. A missing pair returns a
ModelProfile whose status and feature facts are unknown and whose numeric facts are null.
Custom and newly released models therefore remain executable without being falsely advertised
as supported or unsupported.
Reasoning uses the same exact record. capabilities.reasoning declares the supported selection
kinds, exact provider effort values, effective effort, mapping quality, budget range, default
behavior, and whether reasoning content and token counts are visible. Omitting that object means
reasoning support is unknown. A quality value of lossy says the provider value would produce a
different effective effort; typed requests reject that mapping by default.
Optional request-scoped resolution
Ordinary inference does not discover, load, or consult a model catalog. Compose the runtime once per service or worker and reuse it across isolated inference objects:InferenceRuntime resolves the exact profile after applying a
request-level model override. The transient profile travels with the in-process request so local
policy, translation, and telemetry describe the model actually executed. It is excluded from
request serialization.
discover() resolves model directories through the same application, monorepo, and vendor
locations as LLM presets. It creates a reusable catalog without reading any model records.
find() reads only the exact offering’s YAML file, using the first matching directory.
Application config/llm/models takes precedence over packaged records. Overlays replace whole
records; omitted facts in a replacement become unknown rather than inheriting packaged values.
Discovered catalogs are memoized by application root. discover($projectRoot) selects a project
explicitly without changing the global base path; fromPaths($projectModels, $packageModels)
creates an independent catalog with explicit ordered roots. Each catalog caches hydrated profiles
and missing keys. Reusing it across hundreds of inference objects avoids repeated file reads and
hydration. count(), iteration, forDriver(), and toArray() explicitly enumerate records.
Profiles are immutable. A catalog instance has no automatic reload; create a new instance to
change its configured scope. The shared config reader caches parsed source by path and mtime;
tests or development tools rewriting files within one timestamp tick can explicitly call
Config::flushSourceCache(). Deploy records with the application’s immutable release directory,
not by rebuilding a directory active requests are reading. Runtime performs no network lookup,
model-family matching, catalog writes, database query, or background refresh.
Before a provider body is rendered, an in-process preflight step reads only facts explicitly
attached to that request. It does not make an LLM or network request. Explicitly unsupported
streaming, tools, tool choice, JSON Object, and reasoning selections are rejected. Missing or
unknown facts make no local support assertion, so custom models remain executable.
A semantic change such as JSON Schema to JSON Object requires both an exact supported fallback
fact and LLMConfig::$allowLossyFallback === true. The default is false. Approved changes are
recorded on the effective request and included in the InferenceRequested event; adapters never
silently remove or weaken requested behavior. An adapter can still reject an operation it cannot
encode, which is a protocol limitation rather than a catalog capability decision.
Catalog files
A runtime offering is one named YAML record. For example,config/llm/models/openai-compatible/acme-1.yaml:
<encoded-driver>/<encoded-model>.yaml, with each component encoded using
rawurlencode(); the special components . and .. have their dots percent-encoded as well.
For example, model openai/gpt-oss-120b under driver openrouter is stored as
openrouter/openai%2Fgpt-oss-120b.yaml. The profile retains the exact unencoded identity,
which must match its path. ModelRecordDirectory::relativePath(new ModelKey($driver, $model))
provides the canonical relative path. Model facts are loaded literally, without environment or
secret substitution.
schemaVersion must be integer 1; version is a non-empty data revision. Parsers reject
unknown fields, invalid status types, malformed nested records, and duplicate effort mappings.
Explicit null is permitted for numeric limits and the optional maximum reasoning budget;
other present fields must have their declared type. Missing facts remain unknown. A malformed
or unreadable selected record raises an error; it does not fall back to a lower-priority record.
Every support field is tri-state: supported, unsupported, or unknown. Omitted support and
numeric fields are unknown. Catalog serialization omits unknown and null facts.
Overall status: supported means only that the exact (driver, wire model) route is intentionally
maintained and the named driver is executable. It does not imply that every modality or feature is
supported. Each nested fact stands on its own; a sparse supported record is valid. A partially
supported distinction that the current schema cannot express stays unknown. For example, Qwen’s
model record does not claim generic tool-choice support because some choices depend on whether
thinking is enabled, while drivers that cannot render any explicit choice use unsupported.
Packaged source values describe how the checked-in record was authored:
upstream-reviewedmeans selected upstream facts were reviewed before being committed;hand-authoredmeans the maintainer owns the exact assertions directly;- project records choose their own source label, commonly
project.
Generic file input is intentionally absent. A future document or PDF capability needs a concrete
message abstraction, at least one provider renderer, and final wire-payload tests before it can be
advertised.
The packaged record directory is generated offline from source and override files:
--target
names a generated record directory. Build updates its YAML records and removes obsolete generated
records; do not store unrelated hand-authored files in that output directory.
The source and override JSON catalogs remain offline maintenance inputs. ModelCatalog::fromArray()
and fromFile() support explicit bulk data loading for tools and callers supplying in-memory
catalogs; runtime discovery never loads a monolithic models.json file. Bulk envelopes accept
schemaVersion: 1 (omission also selects schema 1); generated runtime records require it explicitly.
Maintaining the packaged catalog
Editresources/catalog/models-source.json for reviewed base facts or
resources/catalog/models-overrides.json for hand-authored whole-record replacements. Review the
source diff directly in Git, then rebuild the deterministic runtime records: