From 05c193859fe7198a455dd8072d557e385b74ab8b Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 15 Nov 2026 14:10:50 +0100 Subject: [PATCH] hive-agent: stop writing active_model to the harness state file, nothing reads it --- .../agent-state/persistence.md | 3 +-- hive-agent/src/events.rs | 23 +++++------------ hive-agent/src/harness_state.rs | 25 +++++-------------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/docs/4-implementation/agent-state/persistence.md b/docs/4-implementation/agent-state/persistence.md index 600202f03..648c4ca81 100644 --- a/docs/4-implementation/agent-state/persistence.md +++ b/docs/4-implementation/agent-state/persistence.md @@ -118,7 +118,7 @@ Consolidated harness state file written atomically (`.tmp` + rename) by Shape: ```json -{ "rate_limited": false, "needs_login": false, "active_model": "…" } +{ "rate_limited": false, "needs_login": false } ``` - `rate_limited` — set when the harness detects a 429 from the Claude @@ -127,7 +127,6 @@ Shape: - `needs_login` — set when a turn hits 401 (expired OAuth credentials); cleared by `"online"` status (re-auth completed). Drives the `needs_login` flag alongside the `claude_has_session` check. -- `active_model` — the resolved Claude model. The turn loop is the only writer today, but it still goes read-modify-write under a shared in-process lock and merges into the diff --git a/hive-agent/src/events.rs b/hive-agent/src/events.rs index c32ba8980..86e6f7adf 100644 --- a/hive-agent/src/events.rs +++ b/hive-agent/src/events.rs @@ -393,15 +393,10 @@ impl Bus { let (tx, _) = broadcast::channel(CHANNEL_CAPACITY); let (initial_model, model_source) = local_model(configured_model()); let (initial_effort, effort_source) = local_effort(configured_effort()); - // Restore rate_limited (and needs_login) from the consolidated - // harness state file so the dashboard shows the correct status - // on cold load if the harness crashed while parked. - let (was_rate_limited, was_needs_login, _) = read_harness_state(); - // Write the resolved active model to hyperhive-harness.json on - // startup so hive-c0re can surface the model badge without reading - // hyperhive-model directly. Written once here; updated on every - // apply_model() call and emit_status() call. - write_harness_state(was_rate_limited, was_needs_login, Some(&initial_model)); + // Restore rate_limited from the consolidated harness state file so + // the dashboard shows the correct status on cold load if the harness + // crashed while parked. + let (was_rate_limited, _) = read_harness_state(); Self { tx: Arc::new(tx), event_seq: Arc::new(AtomicU64::new(0)), @@ -575,11 +570,6 @@ impl Bus { pub fn apply_model(&self, value: String, source: Source) { self.model.lock().unwrap().clone_from(&value); *self.model_source.lock().unwrap() = source; - // Mirror the resolved model into hyperhive-harness.json so - // hive-c0re can surface it on the dashboard badge without reading - // the hyperhive-model override file directly. - let (rate_limited, needs_login, _) = read_harness_state(); - write_harness_state(rate_limited, needs_login, Some(&value)); self.emit(LiveEvent::ModelChanged { model: value }); } @@ -905,7 +895,7 @@ impl Bus { // turns, so the two callers never overlap. Documented rather than // locked because adding a Mutex here would be overkill for the // actual call pattern. - let (_, current_needs_login, _) = read_harness_state(); + let (_, current_needs_login) = read_harness_state(); let new_needs_login = if status == "needs_login_idle" { true } else if status == "online" { @@ -913,8 +903,7 @@ impl Bus { } else { current_needs_login }; - let current_model = self.model.lock().unwrap().clone(); - write_harness_state(new_rate_limited, new_needs_login, Some(¤t_model)); + write_harness_state(new_rate_limited, new_needs_login); self.emit(LiveEvent::StatusChanged { status }); } diff --git a/hive-agent/src/harness_state.rs b/hive-agent/src/harness_state.rs index 7a99df4f5..6f7273686 100644 --- a/hive-agent/src/harness_state.rs +++ b/hive-agent/src/harness_state.rs @@ -131,7 +131,7 @@ fn write_harness_json(v: &serde_json::Value) { } } -pub(crate) fn read_harness_state() -> (bool, bool, Option) { +pub(crate) fn read_harness_state() -> (bool, bool) { // Try the new consolidated file first. if let Ok(raw) = std::fs::read_to_string(harness_json_path()) && let Ok(v) = serde_json::from_str::(&raw) @@ -144,38 +144,25 @@ pub(crate) fn read_harness_state() -> (bool, bool, Option) { .get("needs_login") .and_then(serde_json::Value::as_bool) .unwrap_or(false); - let active_model = v - .get("active_model") - .and_then(serde_json::Value::as_str) - .filter(|s| !s.is_empty()) - .map(str::to_owned); - return (rate_limited, needs_login, active_model); + return (rate_limited, needs_login); } // Fall back to legacy sentinel files written by older harness builds. let state_dir = crate::paths::state_dir(); let rate_limited = state_dir.join("hyperhive-rate-limited").exists(); let needs_login = state_dir.join("hyperhive-needs-login").exists(); - (rate_limited, needs_login, None) + (rate_limited, needs_login) } /// Write the turn-loop's harness state fields via a read-modify-write so -/// any other writer's fields in the consolidated state file survive. -/// Pass `active_model: Some(s)` to update the resolved model (surfaced in -/// the dashboard badge); `None` leaves the stored value untouched. -pub(crate) fn write_harness_state( - rate_limited: bool, - needs_login: bool, - active_model: Option<&str>, -) { +/// any other field in the consolidated state file survives, including an +/// `active_model` an older harness left behind (nothing reads it). +pub(crate) fn write_harness_state(rate_limited: bool, needs_login: bool) { let _guard = HARNESS_JSON_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let mut v = read_harness_json(); v["rate_limited"] = rate_limited.into(); v["needs_login"] = needs_login.into(); - if let Some(model) = active_model { - v["active_model"] = model.into(); - } write_harness_json(&v); }