hermes-hub/docs/UI_STATE_CONTRACT.md

14 KiB
Raw Blame History

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.
  • generation and seq are 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.0100.0 or None if unmeasured.
remaining_percent Optional[float] 0.0100.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.