model_registry¶
Model registry: scan, validate, rank, filter, and activate model bundles.
Provides a pure, testable API for discovering promoted model bundles, checking compatibility with the current schema and label set, ranking candidates by the selection policy, and managing the active model pointer.
BundleMetrics Fields¶
| Field | Type | Description |
|---|---|---|
macro_f1 |
float | Macro-averaged F1 across all classes |
weighted_f1 |
float | Support-weighted F1 across all classes |
confusion_matrix |
list[list[int]] | NxN confusion matrix |
label_names |
list[str] | Label ordering for confusion matrix rows/cols |
ModelBundle Fields¶
| Field | Type | Description |
|---|---|---|
model_id |
str | Bundle directory name |
path |
Path | Absolute path to the bundle directory |
valid |
bool | False if parsing or validation failed |
invalid_reason |
str or None | Why the bundle is invalid (if applicable) |
metadata |
ModelMetadata or None | Parsed metadata.json |
metrics |
BundleMetrics or None | Parsed metrics.json |
created_at |
datetime or None | Parsed metadata.created_at timestamp |
SelectionPolicy Fields¶
| Field | Type | Description |
|---|---|---|
version |
int | Policy version (default 1) |
min_improvement |
float | Hysteresis threshold: candidate must exceed current active macro_f1 by this amount to trigger a switch (default 0.0 — disabled) |
ExclusionRecord Fields¶
| Field | Type | Description |
|---|---|---|
model_id |
str | Bundle directory name |
path |
Path | Path to the excluded bundle directory |
reason |
str | Human-readable exclusion reason (e.g. "invalid: missing metrics.json", "incompatible: schema_hash mismatch") |
SelectionReport Fields¶
| Field | Type | Description |
|---|---|---|
best |
ModelBundle or None | Highest-ranked eligible bundle, or None if no bundle qualifies |
ranked |
list[ModelBundle] | Eligible bundles in score-descending order (best first) |
excluded |
list[ExclusionRecord] | Every bundle that was filtered out, with reason |
policy |
SelectionPolicy | Policy used for this selection |
required_schema_hash |
str | Schema hash that bundles were required to match |
ActivePointer Fields¶
| Field | Type | Description |
|---|---|---|
model_dir |
str | Relative path to the bundle directory (e.g. "models/best_bundle") |
selected_at |
str | ISO8601 UTC timestamp of when the pointer was written |
policy_version |
int | Selection policy version used to choose this bundle |
model_id |
str or None | Optional stable model identifier |
reason |
dict or None | Optional structured reason including ranking metrics |
ActiveHistoryEntry Fields¶
| Field | Type | Description |
|---|---|---|
at |
str | ISO8601 timestamp of the transition |
old |
ActivePointer or None | Previous pointer (None on first activation) |
new |
ActivePointer | New pointer |
IndexCacheBundleSummary Fields¶
| Field | Type | Description |
|---|---|---|
model_id |
str | Bundle directory name |
path |
str | Path to the bundle directory |
macro_f1 |
float or None | Macro-averaged F1 |
weighted_f1 |
float or None | Support-weighted F1 |
created_at |
str or None | ISO8601 creation timestamp |
eligible |
bool | Whether the bundle was eligible for selection |
IndexCache Fields¶
| Field | Type | Description |
|---|---|---|
generated_at |
str | ISO8601 timestamp when the cache was written |
schema_hash |
str | Runtime schema hash used during the scan |
policy_version |
int | Selection policy version |
ranked |
list[IndexCacheBundleSummary] | Eligible bundles in score-descending order |
excluded |
list[ExclusionRecord] | Every bundle that was filtered out, with reason |
best_model_id |
str or None | Model ID of the highest-ranked bundle |
Functions¶
list_bundles(models_dir)— scan a directory for bundle subdirectories; returns valid and invalid bundles sorted bymodel_id.is_compatible(bundle, required_schema_hash, required_label_set)— check schema hash + label set match.passes_constraints(bundle, policy)— hard constraint gate (policy v1: valid bundle with metrics).score(bundle, policy)— sortable ranking tuple(macro_f1, weighted_f1, created_at).find_best_model(models_dir, policy, required_schema_hash, required_label_set)— scan, filter, rank, and select the best bundle; returns aSelectionReport.read_active(models_dir)— readactive.jsonpointer; returnsActivePointerorNoneif missing/invalid.write_active_atomic(models_dir, bundle, policy, reason)— atomically writeactive.jsonand append toactive_history.jsonl; returns the newActivePointer.append_active_history(models_dir, old, new)— append a transition record toactive_history.jsonl.resolve_active_model(models_dir, policy, required_schema_hash, required_label_set)— resolve the active bundle: uses the pointer if valid, falls back tofind_best_modeland self-heals the pointer; returns(ModelBundle | None, SelectionReport | None).write_index_cache(models_dir, report)— atomically writemodels/index.jsonfrom aSelectionReport; returns theIndexCache.read_index_cache(models_dir)— read cachedindex.json; returnsIndexCacheorNoneif missing/invalid.should_switch_active(current, candidate, policy)— hysteresis check: returnsTrueif the candidate'smacro_f1exceeds the current active model'smacro_f1by at leastpolicy.min_improvement.
taskclf.model_registry
¶
Model registry: scan, validate, rank, filter, and activate model bundles.
Provides a pure, testable API for discovering promoted model bundles
under models/, checking compatibility with the current schema and
label set, ranking candidates by the selection policy, and managing
the active model pointer.
Public surface:
- :class:
BundleMetrics— parsedmetrics.json - :class:
ModelBundle— one scanned bundle (valid or invalid) - :class:
SelectionPolicy— ranking / constraint configuration - :class:
ExclusionRecord— why a bundle was excluded from selection - :class:
SelectionReport— full result of :func:find_best_model - :class:
ActivePointer— persistedactive.jsonpointer - :class:
ActiveHistoryEntry— one line inactive_history.jsonl - :class:
IndexCacheBundleSummary— one bundle row inindex.json - :class:
IndexCache— cached scan/ranking snapshot - :func:
list_bundles— scan a directory for bundles - :func:
is_compatible— schema hash + label set gate - :func:
passes_constraints— hard constraint gate (policy v1: no-op) - :func:
score— sortable ranking tuple - :func:
find_best_model— scan, filter, rank, and select the best bundle - :func:
read_active— readactive.jsonpointer - :func:
write_active_atomic— atomically updateactive.json - :func:
append_active_history— append toactive_history.jsonl - :func:
resolve_active_model— resolve active bundle with fallback - :func:
write_index_cache— writeindex.jsonfrom a selection report - :func:
read_index_cache— read cachedindex.json - :func:
should_switch_active— hysteresis check before switching active
BundleMetrics
¶
Bases: BaseModel
Metrics stored in a bundle's metrics.json.
See docs/guide/metrics_contract.md for the stable contract.
Source code in src/taskclf/model_registry.py
ModelBundle
¶
Bases: BaseModel
A scanned model bundle directory.
Both valid and invalid bundles are represented; check :attr:valid
before using :attr:metadata or :attr:metrics.
Source code in src/taskclf/model_registry.py
SelectionPolicy
¶
Bases: BaseModel
Selection policy configuration.
Policy v1 ranks by macro_f1 desc, weighted_f1 desc,
created_at desc and applies no additional hard constraints
(acceptance gates are enforced at promotion time by retrain).
min_improvement controls hysteresis: the candidate must exceed
the current active model's macro_f1 by at least this amount
before the active pointer is switched. Set to 0.0 (default)
to disable hysteresis.
Source code in src/taskclf/model_registry.py
ExclusionRecord
¶
Bases: BaseModel
Why a single bundle was excluded during :func:find_best_model.
Source code in src/taskclf/model_registry.py
SelectionReport
¶
Bases: BaseModel
Full result of :func:find_best_model.
ranked contains eligible bundles in score-descending order.
best is ranked[0] when the list is non-empty, else None.
excluded lists every bundle that was filtered out, with a
human-readable reason.
Source code in src/taskclf/model_registry.py
ActivePointer
¶
Bases: BaseModel
Persisted pointer to the currently active model bundle.
Stored as models/active.json. See
docs/guide/model_selection.md for the schema contract.
Source code in src/taskclf/model_registry.py
ActiveHistoryEntry
¶
Bases: BaseModel
One line in models/active_history.jsonl.
Records every change to active.json for auditability and
rollback.
Source code in src/taskclf/model_registry.py
IndexCacheBundleSummary
¶
Bases: BaseModel
Summary of one bundle stored inside :class:IndexCache.
Source code in src/taskclf/model_registry.py
IndexCache
¶
Bases: BaseModel
Cached scan/ranking snapshot written to models/index.json.
This is an informational cache — selection never reads it.
Operators and taskclf train list may consume it for fast
inspection without a full rescan.
Source code in src/taskclf/model_registry.py
list_bundles(models_dir)
¶
Scan models_dir for model bundle subdirectories.
Each subdirectory is parsed independently; failures are captured as invalid bundles rather than aborting the scan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
Parent directory containing bundle subdirectories
(e.g. |
required |
Returns:
| Type | Description |
|---|---|
list[ModelBundle]
|
A list of :class: |
list[ModelBundle]
|
for deterministic ordering. |
Source code in src/taskclf/model_registry.py
is_compatible(bundle, required_schema_hash=get_feature_schema('v1').SCHEMA_HASH, required_label_set=LABEL_SET_V1)
¶
Check whether bundle is compatible with the current runtime.
A bundle is compatible when both hold:
metadata.schema_hashexactly matches required_schema_hash.sorted(metadata.label_set)exactly matchessorted(required_label_set).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
ModelBundle
|
A scanned model bundle. |
required |
required_schema_hash
|
str
|
Expected schema hash (defaults to
|
SCHEMA_HASH
|
required_label_set
|
frozenset[str]
|
Expected label vocabulary (defaults to
|
LABEL_SET_V1
|
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/taskclf/model_registry.py
passes_constraints(bundle, policy)
¶
Check whether bundle passes the hard constraints of policy.
Policy v1 applies no additional constraints beyond validity: all acceptance gates are enforced at promotion time by retrain, so any promoted bundle that parsed successfully is eligible for ranking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
ModelBundle
|
A scanned model bundle (must be valid). |
required |
policy
|
SelectionPolicy
|
Selection policy configuration. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/taskclf/model_registry.py
score(bundle, policy)
¶
Compute a sortable ranking key for bundle.
The tuple sorts descending on all three components:
macro_f1(higher is better)weighted_f1(tie-break; higher is better)created_at(tie-break; newer is better — ISO8601 strings with the same UTC offset sort lexicographically)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
ModelBundle
|
A valid model bundle with non-null metrics and metadata. |
required |
policy
|
SelectionPolicy
|
Selection policy configuration. |
required |
Returns:
| Type | Description |
|---|---|
float
|
A 3-tuple |
float
|
for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the bundle is invalid or missing metrics/metadata. |
Source code in src/taskclf/model_registry.py
find_best_model(models_dir, policy=None, required_schema_hash=None, required_label_set=None)
¶
Scan, filter, rank, and select the best model bundle.
This is the main entry-point for non-mutating model selection.
It composes :func:list_bundles, :func:is_compatible,
:func:passes_constraints, and :func:score into a single call
that returns a structured :class:SelectionReport.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
Directory containing promoted model bundle
subdirectories (e.g. |
required |
policy
|
SelectionPolicy | None
|
Selection policy configuration. Defaults to
|
None
|
required_schema_hash
|
str | None
|
Schema hash that bundles must match.
Defaults to |
None
|
required_label_set
|
frozenset[str] | None
|
Label vocabulary that bundles must match.
Defaults to |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
SelectionReport
|
class: |
SelectionReport
|
the full ranked list of eligible bundles, and exclusion |
|
SelectionReport
|
records for every bundle that was filtered out. |
Source code in src/taskclf/model_registry.py
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 | |
read_active(models_dir)
¶
Read the active model pointer from models_dir/active.json.
Returns None (without raising) when the file is missing,
contains invalid JSON, or fails :class:ActivePointer validation.
A warning is logged on parse/validation failures so operators can
notice stale pointer files.
Source code in src/taskclf/model_registry.py
append_active_history(models_dir, old, new)
¶
Append a transition record to models_dir/active_history.jsonl.
Creates the file if it does not exist. Each line is a
self-contained JSON object matching :class:ActiveHistoryEntry.
Source code in src/taskclf/model_registry.py
write_active_atomic(models_dir, bundle, policy, reason=None)
¶
Atomically write models_dir/active.json for bundle.
The pointer is written to a temporary file first, then moved into
place with :func:os.replace to guarantee readers never see a
partial write. The previous pointer (if any) is read before the
overwrite and both old and new are appended to the audit log via
:func:append_active_history.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
The |
required |
bundle
|
ModelBundle
|
The bundle to activate (must be valid with metrics). |
required |
policy
|
SelectionPolicy
|
The selection policy used to choose this bundle. |
required |
reason
|
str | None
|
Optional human-readable reason string. |
None
|
Returns:
| Type | Description |
|---|---|
ActivePointer
|
The newly written :class: |
Source code in src/taskclf/model_registry.py
resolve_active_model(models_dir, policy=None, required_schema_hash=None, required_label_set=None)
¶
Resolve the active model bundle, falling back to selection.
Resolution order:
- Read
active.json. If valid and the pointed-to bundle exists, is parseable, and is compatible — return it immediately (no full scan). - Otherwise fall back to :func:
find_best_model. If a best bundle is found, atomically updateactive.jsonto self-heal the pointer.
Returns:
| Type | Description |
|---|---|
ModelBundle | None
|
A 2-tuple |
SelectionReport | None
|
pointer was valid and no scan was needed. |
Source code in src/taskclf/model_registry.py
write_index_cache(models_dir, report)
¶
Write models_dir/index.json from a :class:SelectionReport.
The cache is written atomically (temp + :func:os.replace). It is
informational only — :func:find_best_model never reads it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
The |
required |
report
|
SelectionReport
|
A completed selection report. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
The |
IndexCache
|
class: |
Source code in src/taskclf/model_registry.py
read_index_cache(models_dir)
¶
Read the cached index from models_dir/index.json.
Returns None (without raising) when the file is missing,
contains invalid JSON, or fails :class:IndexCache validation.
Source code in src/taskclf/model_registry.py
should_switch_active(current, candidate, policy)
¶
Decide whether candidate should replace current as active.
When policy.min_improvement is positive, the candidate's
macro_f1 must exceed the current active model's macro_f1
by at least that amount. If current is None or has no
recorded macro_f1, the switch is always allowed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current
|
ActivePointer | None
|
The current active pointer (may be |
required |
candidate
|
ModelBundle
|
The best-ranked bundle from selection. |
required |
policy
|
SelectionPolicy
|
Selection policy with hysteresis threshold. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|