14 KiB
Hermes Hub UI state contract
- Contract version: 1.1
- Published against:
e8a404be035fa04b5f76e3e572c6539fba0e83e4 - Contract owner:
antigravity/contract-gaps - Consumer:
codex/ui-redesign
This document describes the backend state that the native UI may render. It is descriptive of the code at the published commit. Fields marked real are backed by persisted configuration, authentication metadata, runtime health or provider responses. Fields marked derived are computed from real fields. Fields marked estimated or placeholder must be labelled as such or hidden by the UI.
1. Snapshot boundary
HubSnapshot
Defined in router/state_store.py as a frozen dataclass.
| Field | Type | Reality and meaning |
|---|---|---|
generation |
int |
Real local sequence. Monotonically increases for every accepted rebuild within one process. Starts at 1; an empty bootstrap snapshot uses 0. |
seq |
int |
Real request sequence token. Monotonically increasing counter of the latest completed refresh request. Guaranteed to equal or exceed generation. |
timestamp |
float |
Real local time (time.time()) when the snapshot was built. |
profiles_by_provider |
dict[str, list[ProfileViewModel]] |
Derived normalized profiles grouped by provider. |
all_profiles |
dict[str, ProfileViewModel] |
Derived map keyed by profile_id. Profile IDs are unique by this map. |
readiness |
SystemReadiness |
Derived readiness summary. |
agents |
list[AgentViewModel] |
Derived current role assignments with active quota and session tracking. |
providers |
list[ProviderSummary] |
Derived provider summaries. |
routing |
dict[str, RolePipeline] |
Derived routing pipelines keyed by role ID. |
quotas |
dict[str, QuotaSnapshot] |
Keyed by profile_id; see provider truth matrix below. |
metrics |
dict[str, Any] |
Real local diagnostics: generation, sequence, build duration, profile counts and refresh counters. |
is_stale |
bool |
True for uninitialized bootstrap snapshots or when background refresh is overdue (> 300s). |
Consistency guarantees:
- The store publishes one snapshot reference after building it under an
RLock; readers never observe a partially-built snapshot. generationandseqare public comparison keys for the UI.- Stale background worker responses (
seq < _latest_applied_seq) are strictly rejected and discarded. get_snapshot()returns the cached snapshot without blocking disk scans.
2. Account and health models
ProfileViewModel
| Field | Type | Optional | Reality and meaning |
|---|---|---|---|
profile_id |
str |
no | Real configuration slot/profile ID. |
display_name |
str |
no | Real configured name when present; otherwise localized slot fallback. |
account_identity |
str |
no | Best available identifier: email → display name → provider account ID → profile ID. |
provider |
str |
no | Real normalized provider ID. |
provider_display_name |
str |
no | Derived localized/display label. |
assigned_roles |
list[str] |
no | Derived from router config. |
primary_role |
str |
yes | Derived/configured. May be absent for spare slots. |
is_main_account |
bool |
no | Real local profile preference. |
is_main_orchestrator |
bool |
no | Derived from orchestrator chain. |
auth_state |
str |
no | Normalized auth state (AUTHENTICATED, AUTH_REQUIRED, AUTH_EXPIRED, NOT_CONFIGURED). |
health_state |
str |
no | Normalized health state (healthy, quota_exhausted, rate_limited, cooldown, disabled, cold_spare, not_configured, unhealthy). |
health_label_ru |
str |
no | Derived presentation label. |
model_states |
dict[str, ModelFamilyHealth] |
no | Derived from local health tracker/runtime observations. |
cooldown_remaining_sec |
int |
no | Derived local runtime state. Zero when healthy. |
last_checked_at |
str |
yes | Real local check time string (%H:%M:%S). |
enabled |
bool |
no | Real config state. |
is_cold_spare |
bool |
no | Derived/configured. |
is_empty_slot |
bool |
no | Derived placeholder slot with no configured auth. |
email |
str |
no | Extracted from saved auth/JWT claims; empty string if unavailable. |
plan |
str |
no | Display text (e.g. "Тариф: MAX", "Тариф: PRO"). |
plan_code |
str |
no | Normalized code (PRO, PLUS, MAX, SUPERGROK, UNKNOWN). |
plan_source |
str |
no | Real provenance: "provider_api", "jwt_claim", "provider_auth", "inferred", "unknown". UI uses this to display PlanBadge only when trustworthy (!= "unknown"). |
quota_snapshot |
QuotaSnapshot |
yes | Associated quota snapshot object. |
preferred_models |
list[str] |
no | Real config/model-discovery values. |
3. Quota models
QuotaSnapshot
| Field | Type | Reality and meaning |
|---|---|---|
account_id |
str |
Real local profile/account key. |
provider |
str |
Real normalized provider ID. |
buckets |
list[QuotaBucket] |
Separate capacity pools; never combine them into one percent. |
fetched_at |
timezone-aware datetime |
Real local collection time. |
stale_after_seconds |
int |
Local cache TTL, default 300 seconds. |
source |
str |
Provenance: "runtime_event", "jwt_claim", "provider_auth", "baseline", "unconfigured". |
unavailable_reason |
Optional[str] |
Human-readable reason when data cannot be collected. |
is_estimated |
property | True for baseline/unconfigured; False for verified runtime events and provider claims. |
QuotaBucket
| Field | Type | Notes |
|---|---|---|
id |
str |
Stable bucket key (antigravity.claude.5h, antigravity.gemini.5h, codex.primary.weekly, claude.session.5h, grok.frequent_tasks). |
display_name |
str |
User-facing label ("Claude 5h", "Gemini 5h", "Codex Weekly"). |
model_family |
Optional[str] |
Family selector ("claude", "gemini", "gpt", "grok", "opencode"). |
used_percent |
Optional[float] |
0.0–100.0 or None if unmeasured. |
remaining_percent |
Optional[float] |
0.0–100.0 or None if unmeasured. |
used_absolute |
Optional[int] |
Absolute units used if reported. |
remaining_absolute |
Optional[int] |
Absolute units remaining. |
limit_absolute |
Optional[int] |
Absolute maximum limit. |
reset_at |
Optional[datetime] |
UTC reset timestamp. |
reset_in_seconds |
Optional[int] |
Seconds until quota reset. |
period |
Optional[str] |
"5h", "7d", "30d", "sliding". |
status |
str |
"healthy", "warning", "exhausted", "unknown". |
Provider Truth Matrix at v1.1
| Provider | Buckets emitted | Values | Reset | Source / UI treatment |
|---|---|---|---|---|
| Antigravity | antigravity.claude.5h, antigravity.gemini.5h |
Baseline: values None. On runtime 429: exact 0% remaining. |
On 429: extracted from server response. Baseline: None. |
Baseline: baseline, is_estimated=True. 429 event: runtime_event, is_estimated=False. |
| OpenAI Codex | codex.primary.weekly |
Baseline: values None. On 429: 0% remaining. |
On 429: derived. Baseline: None. |
Baseline: baseline, is_estimated=True. |
| Claude | claude.session.5h |
Baseline: values None. On 429: 0% remaining. |
On 429: derived. Baseline: None. |
Baseline: baseline, is_estimated=True. |
| Grok | grok.frequent_tasks |
Baseline: values None. On 429: 0% remaining. |
On 429: derived. Baseline: None. |
Baseline: baseline, is_estimated=True. |
| OpenCode Go | opencode.tasks |
Baseline: values None. |
None. |
Baseline: baseline, is_estimated=True. |
4. Team and routing models
AgentViewModel
| Field | Type | Description |
|---|---|---|
role_id |
str |
Logical role ("orchestrator", "coder-primary", "reviewer", etc.). |
role_name_ru |
str |
Localized role title ("Главный оркестратор", "Кодер 1"). |
role_description_ru |
str |
Localized role description. |
assigned_profile_id |
Optional[str] |
Active profile ID assigned to this role. |
assigned_display_name |
Optional[str] |
Display name of assigned profile. |
provider |
str |
Active provider ID. |
provider_display_name |
str |
Localized provider name. |
model |
str |
Selected active model. |
account_identity |
str |
Masked identity of the active account. |
routing_position |
str |
"Primary", "Fallback 1", "Fallback 2". |
status |
str |
"healthy", "quota_exhausted", "auth_required", etc. |
status_label_ru |
str |
Localized status text ("Работает", "Исчерпан"). |
is_active |
bool |
True if healthy and receiving requests. |
is_main_orchestrator |
bool |
True if role is orchestrator. |
cooldown_remaining_sec |
int |
Active cooldown in seconds. |
session_id |
Optional[str] |
Active affinity session bound to this agent. |
active_quota_status |
str |
Status of governing quota bucket ("healthy", "warning", "exhausted"). |
active_quota_label |
str |
Human-readable quota state (e.g. "Осталось 85%", "Доступна"). |
PipelineNode & RolePipeline
Each PipelineNode represents one failover step in a role's route:
| Field | Type | Description |
|---|---|---|
profile_id |
str |
Profile ID for this step. |
display_name |
str |
Slot display name. |
provider |
str |
Localized provider name. |
model |
str |
Model configured for this step. |
account_identity |
str |
Masked account identity for this node. |
status |
str |
Health status of this node ("healthy", "quota_exhausted"). |
status_label_ru |
str |
Localized status text. |
quota_status |
str |
Quota health status ("healthy", "exhausted"). |
is_active |
bool |
True if this node is currently handling traffic. |
cooldown_remaining_sec |
int |
Cooldown in seconds. |
failover_reason |
Optional[str] |
Real reason why traffic switched from this node (e.g. "Primary исчерпал квоту (429)", "Требуется авторизация"). None for active node or standby reserve. |
RolePipeline: role_id, role_name_ru, default_model, max_failover, session_affinity, active_profile_id, nodes: List[PipelineNode].
5. Event bus contract
Callbacks receive (event_name: str, payload: Any). All events carry active generation and seq tokens.
| Event Constant | Name String | Payload Contract (v1.1) | Canonical Publisher Site |
|---|---|---|---|
EVENT_ACCOUNT_UPDATED |
"ACCOUNT_UPDATED" |
{"profile_id": str, "profile": ProfileViewModel, "generation": int, "seq": int} |
HubStateStore.apply_delta_account_updated |
EVENT_ACCOUNT_ADDED |
"ACCOUNT_ADDED" |
{"provider": str, "profile_id": str, "profile": ProfileViewModel, "generation": int, "seq": int} |
HubStateStore.apply_delta_account_added |
EVENT_ACCOUNT_REMOVED |
"ACCOUNT_REMOVED" |
{"provider": str, "profile_id": str, "generation": int, "seq": int} |
HubStateStore.apply_delta_account_removed |
EVENT_ACCOUNT_AUTH_CHANGED |
"ACCOUNT_AUTH_CHANGED" |
{"provider": str, "profile_id": str, "auth_state": str, "generation": int, "seq": int} |
HubStateStore.apply_delta_account_auth_changed |
EVENT_QUOTA_UPDATED |
"QUOTA_UPDATED" |
{"provider": str, "profile_id": str, "snapshot": QuotaSnapshot, "quota_snapshot": QuotaSnapshot, "generation": int, "seq": int} |
HubStateStore.apply_delta_quota_updated |
EVENT_ROUTING_UPDATED |
"ROUTING_UPDATED" |
{"role_id": str, "active_profile_id": str, "pipeline": RolePipeline, "generation": int, "seq": int} |
HubStateStore.apply_delta_route_changed, RouterEngine.route_request |
EVENT_AGENT_UPDATED |
"AGENT_UPDATED" |
{"role_id": str, "agent": AgentViewModel, "generation": int, "seq": int} |
HubStateStore.apply_delta_route_changed |
EVENT_SYSTEM_READINESS_CHANGED |
"SYSTEM_READINESS_CHANGED" |
{"readiness": SystemReadiness, "generation": int, "seq": int} |
HubStateStore.refresh |
EVENT_REFRESH_STARTED |
"REFRESH_STARTED" |
{"key": str, "seq": int} |
HermesRefreshScheduler._execute_task |
EVENT_REFRESH_COMPLETED |
"REFRESH_COMPLETED" |
{"generation": int, "seq": int, "duration_ms": float} |
HubStateStore.refresh |
EVENT_REFRESH_FAILED |
"REFRESH_FAILED" |
{"key": str, "error": str, "seq": int} |
HermesRefreshScheduler._execute_task |
6. Closed Gaps & Audit Status (v1.1)
| Gap ID | Description | Status in v1.1 | Solution / Commit |
|---|---|---|---|
| Gap 1 & 2 | Antigravity Claude vs Gemini bucket isolation & live 429 parsing | Closed | Structured multi-buckets with model-family isolation in quota_collector.py and truthful reset timestamp parsing on runtime 429 events. |
| Gap 3 | Public seq in HubSnapshot |
Closed | HubSnapshot.seq exposed to UI; matches accepted refresh token. |
| Gap 6 | Plan provenance for PlanBadge |
Closed | ProfileViewModel.plan_source added ("provider_api", "jwt_claim", "provider_auth", "inferred", "unknown"). |
| Gap 7 | AgentViewModel active session and quota |
Closed | session_id, active_quota_status, and active_quota_label added. |
| Gap 8 | PipelineNode identity, quota, and failover reason |
Closed | account_identity, quota_status, and real failover_reason added. |
| Gap 9 | Canonical publishers for all declared events | Closed | Every declared event constant now has a dedicated, verified publisher in state_store.py / router_engine.py. Dead event constants removed. |
| Gap 10 | Scheduler async quota race | Closed | Scheduler triggers complete quota collection before invoking snapshot rebuild. |
| Gap 11 | Stale response protection verification | Closed | seq recorded only on completion; late responses strictly dropped with test proof. |