Skip to main content

hank-model-authority

The model authority every product repository reads: which models exist (registry entries with their execution identity and per-family request facts), which model each capability uses (selection policy), and what each model costs (rate tables). The distribution depends on PyYAML and pydantic only, so Risk, Investment and Edgar take it without the gateway.

Mechanism — one artifact, admission, the host directory, lifecycle, skew — is docs/reference/model-authority.md; the data deploy is docs/runbooks/model-authority-deploy.md.

Layout

Module Owns
authority/model-authority.yaml, authority/rates/*.json the packaged artifact: one model-authority/2.x document (registry models, selection.capabilities, one revision) and per-provider prices
schema.py the version rule (SCHEMA_MAJOR/SCHEMA_MINOR), ModelAuthorityError and its message, protocol families and each family's compat: key set
loader.py HANK_MODEL_AUTHORITY_DIR resolution, the strict load, the directory digest
current.py the process's one import-time load: INITIAL_MODEL_REGISTRY, INITIAL_MODEL_SELECTION_POLICY, RATE_TABLES, HEALTH_FACT, health_fact()
registry.py, selection.py registry entries and selection policy
capabilities.py capability IDs and the process that executes each (a release, not data)
binding.py, bind.py the single resolver and CapabilityBind
rates.py, thinking.py rate tables; effort vocabulary
__main__.py python -m model_authority check <dir>: the ordinary load of a candidate directory

The package root imports types and the loader, never current, so python -m model_authority check <dir> can check a candidate directory while the host directory is broken.

Which directory a process loads

HANK_MODEL_AUTHORITY_DIR set: that directory, resolved once at start; missing or unreadable fails the start. Unset: the packaged authority/ directory. Every loading unit sets it — /etc/hank/model-authority in production, ~/.config/hank/model-authority on this Mac — so the packaged copy is what tests and unwired tools load. Every process reports what it admitted through health_fact(): {"revision", "schema", "sha256", "source", "loader", "admits"}; source is the resolved release path (…/model-authority.d/<revision>-<sha12>/…) or packaged:<path>.

Schema

This loader implements model-authority/2.2 and admits 2.0 through 2.2 artifacts (health_fact()["admits"] is model-authority/2.0-2.2): same major, minor at most its own, every other field and literal strict; each refusal is one ModelAuthorityError naming artifact, revision, schema, loader and admitted range.

Each entry's compat: key set is its protocol family's (schema.py): the profile maps to anthropic.messages, openai.chat_completions, openai.responses or google.generate_content (PROTOCOL_FAMILY_BY_PROFILE), and COMPAT_MODEL_BY_FAMILY holds each family's frozen key set. 2.1 adds two anthropic.messages keys, each derived when absent so 2.0 artifacts load unchanged, and each refused when it contradicts the 2.0 key it refines:

Key Values Absent Must agree with
thinking.off_value disabled, between_tools, null (cannot be turned off) disabled if can_disable, else null can_disable is true exactly when off_value is disabled
tool_choice_forcing any, thinking_off_only, never thinking_off_only if forced_tool_choice_requires_thinking_off, else any that flag is false exactly when forcing is any

The 2.0 keys stay required (a minor removes nothing), so an adapter that still reads can_disable or forced_tool_choice_requires_thinking_off keeps working; the gateway's Anthropic adapter reads both new keys. Sonnet 5.5 uses can_disable: false, off_value: between_tools: the legacy bool still means whether the literal thinking.type: disabled is legal.

Tier selection (2.2)

selection.tiers maps arbitrary non-empty role names to a default assignment and an optional allowed_model_keys ceiling. Assignments are one of:

  • {models: [key, ...], effort: high}: ordered, non-empty execution candidates; this tier must declare a ceiling containing every candidate.
  • {alias: deep, effort?: high}: use another tier's assignment, optionally replacing its effort. An absent ceiling inherits the target's ceiling over the base policy alias graph, before any override. Aliases must be acyclic and target an existing tier.
  • {kind: inherit_parent}: copy the existing exact parent bind; no role ceiling.

Capability and channel defaults may be {kind: tier, tier: default, effort?: high}. The static project_tier owner is shared by artifact admission and resolution: first candidate qualified for the capability, in both the slot and referring tier ceilings, and eligible for new selection. Effort is requested effort, then slot-local effort, then resolved tier effort (outer alias effort wins). The chosen candidate must support that exact effort; no clamp or next-candidate retry occurs. Existing entitlement and credential checks follow selection, and their failure never falls through to another candidate.

resolve_capability_model adds keyword-only requested_role, role_map, role_map_source and requested_role_source. A map's entries replace tier defaults only, omitted roles use policy, and aliases resolve within that effective map. Redirecting an alias cannot widen the referring role's fixed base ceiling. Map values may be TierModelsDefault, TierAliasDefault, TierInheritParentDefault, or their literal mappings.

Existing explicit, saved and authenticated concrete-model inputs win first. Then requested role wins over channel/default tier selection. Selection source is, in order: inherited assignment → parent_binding; explicit-user requested role → explicit_user; an overridden assignment anywhere in its alias chain → role_map_source (saved_preference or internal_policy, required for a supplied map); other requested role → internal_policy; channel tier → channel_default; otherwise the existing capability-default source (capability_default for the driver, internal_policy for other slots). Role/map provenance does not extend concrete-choice permission flags or tools. CapabilityBind stays 1.0 with its existing source literals. Inherited role assignments still enforce both the requested role's fixed ceiling and the slot ceiling against the parent's model. Without a snapshot, concrete selections without their own effort use applicable base-policy tier effort, never map effort.

Frozen selection snapshots

capture_selection_snapshot(selection_policy, role_map=..., role_map_source=...) returns an immutable SelectionSnapshot containing every capability's CapabilityDefaults (default plus by_channel, each a CapabilityDefault) and every capture-time tier's alias-resolved {models, effort} or {kind: inherit_parent} assignment. overridden records roles whose effective alias chain used the winning map, even when that map value equals the policy value. source is the winning map's saved_preference or internal_policy literal exactly when overridden is non-empty; otherwise it is null. Independent captures compare by value.

snapshot.to_json() returns this closed, versioned form:

{"version": 1, "policy_revision": "revision", "assignments": {"default": {"models": ["model-key"], "effort": "high"}}, "capabilities": {"session.driver": {"default": {"kind": "tier", "tier": "default"}, "by_channel": {}}}, "overridden": ["default"], "source": "saved_preference"}

The example shows one slot; a snapshot holds every capability that existed at capture. Top-level keys have the order shown; assignments, capabilities, channels and overridden names are sorted, while model arrays preserve candidate order. SelectionSnapshot.from_json round-trips the value; missing/unknown keys, wrong types, unresolved aliases, unknown efforts and empty candidate arrays raise TierProjectionError with capability_selection_invalid.

Pass selection_snapshot=snapshot to resolve_capability_model or project_tier instead of role_map (supplying both raises ValueError). Slot defaults, channel defaults, slot-local effort and tier assignments are frozen; a later slot repoint cannot select a newly published tier or concrete model. The referring tier's ceiling, slot allowance, permission flags, capability exposure, lifecycle, entitlement and credentials remain current. A tier absent from the snapshot or removed from current policy is capability_role_unknown; no currently eligible candidate is capability_role_unavailable. A snapshot is validated on its own contents, not against today's capability set, so one journaled before a release that adds or retires a capability still loads and serves its captured slots; a requested slot it did not capture is capability_policy_missing, and a frozen model key the current registry no longer has is capability_model_unavailable. The exact effort rules and source precedence above still apply: only overridden roles use snapshot.source, while the others retain today's selection-source literal. Concrete explicit, saved or run-override selections without effort use the frozen applicable slot default's effort when a snapshot is supplied. Omitting the snapshot (or passing None) leaves resolution unchanged.

Artifacts declaring 2.0 or 2.1 cannot contain tiers or kind: tier. The packaged artifact intentionally stays 2.1 until consumer loaders upgrade in W2; publishing 2.2 data before those upgrades would prevent those consumers from starting.

Changing it

  • A data change (a model, a default, a price, a compat value) edits authority/ and ships by scripts/deploy_model_authority.sh, not by a release (runbook). Declare the lowest schema minor that covers the fields the artifact uses.
  • A schema change is a release: a new optional field or literal is a minor (SCHEMA_MINOR), a removal or redefinition a major. Deploy loaders before data that uses a new minor: a loader refuses an artifact whose minor is newer than its own, naming both.
  • A capability or its executing process is a release (capabilities.py).

Check a candidate directory with the ordinary load:

python -m model_authority check <dir>   # prints the health fact, or exits 1 with the refusal

Tests: .venv/bin/python3 -m pytest packages/model-authority/tests -q (make test-area AREA=model_authority).

Metadata

Release files for hank-model-authority 1.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hank-model-authority 1.4.0
File Size Uploaded
hank_model_authority-1.4.0.tar.gz 69.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hank-model-authority 1.4.0
File Interpreter ABI Platform
hank_model_authority-1.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 119.7 kB

Release files / hank_model_authority-1.4.0.tar.gz

Download URL hank_model_authority-1.4.0.tar.gz
Size 69.8 kB
Tags Source
SHA-256 checksum
How to use checksums
76194772dce52b44923b90154da1b10404f111c586ae59680ca1265f7e05ebf6
BLAKE2b-256 checksum
How to use checksums
bf0f793a8c96d70b6845c524f42ddb7d347c6c0f04a2faea6d7e5dd9bdbaf92a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / hank_model_authority-1.4.0-py3-none-any.whl

Download URL hank_model_authority-1.4.0-py3-none-any.whl
Size 49.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9851c00c19d11d60b20b7fa25fc21f4f3913aa4f2a2050c79e2bb7e28a455860
BLAKE2b-256 checksum
How to use checksums
22f5dc6b708b5f23039161fb27cbb45a6151942c086db22a9a33520b3d81ad93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release history Release notifications | RSS feed

1.6.1

2 release files

1.6.0

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

This release

1.4.0 This release

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page