diff --git a/hive-agent/src/boot_announce.rs b/hive-agent/src/boot_announce.rs index 43c9a509..d7b8081f 100644 --- a/hive-agent/src/boot_announce.rs +++ b/hive-agent/src/boot_announce.rs @@ -46,8 +46,8 @@ pub fn announce(label: &str, todos: &Todos, wake: &Notify) { let summary = format!( "You were just (re)started. If you were mid-task, check \ `/agents/{label}/state/` for your notes and pick up where you left \ - off — claude's `--continue` session is intact, so prior context is \ - still in your window." + off — your claude session (resumed by title) is intact, so prior \ + context is still in your window." ); match todos.upsert(SUBSYSTEM, Some(KEY), &summary, None, true) { Ok((_, true)) => wake.notify_one(), diff --git a/hive-agent/src/events.rs b/hive-agent/src/events.rs index 7b25eb0f..c43a7bf9 100644 --- a/hive-agent/src/events.rs +++ b/hive-agent/src/events.rs @@ -302,10 +302,9 @@ pub struct Bus { last_cost_usage: Arc>>, /// True while the harness is parked after a rate-limit response. /// Set by `emit_status("rate_limited")`, cleared by - /// `emit_status("online")`. Also mirrored to a sentinel file at - /// `{state_dir}/hyperhive-rate-limited` so the host-side - /// `container_view` can surface the status on the dashboard without - /// a live socket call. + /// `emit_status("online")`. `emit_status` also persists it in + /// `hyperhive-harness.json` (see `harness_state`), so the host can + /// surface the status on the dashboard without a live socket call. rate_limited: Arc, /// One-shot: operator asked to reset the claude session (`POST /// /api/new-session`). Consumed at the *next* turn boundary by diff --git a/hive-agent/src/identity.rs b/hive-agent/src/identity.rs index 21d7d702..e9ee2ed0 100644 --- a/hive-agent/src/identity.rs +++ b/hive-agent/src/identity.rs @@ -30,10 +30,10 @@ pub fn hive_domain() -> Option { /// Human display name of this hive (e.g. `pr1ma`). Distinct from /// [`hive_domain`] — the domain is the machine-readable DNS address; -/// this is the prose label humans use in conversation. Returns None -/// when `/etc/hyperhive-hive-facts` carries no name (the host's -/// `services.hyperhive.hiveName` is unset), in which case callers fall -/// back to the domain or the short label at their discretion. +/// this is the prose label humans use in conversation. Reads the +/// `HYPERHIVE_HIVE_NAME` env var; returns None when it is unset or empty +/// (the host's `services.hyperhive.hiveName` is unset), in which case +/// callers fall back to the domain or the short label at their discretion. #[must_use] pub fn hive_name() -> Option { non_empty_env("HYPERHIVE_HIVE_NAME") diff --git a/hive-agent/src/login.rs b/hive-agent/src/login.rs index 2a953977..73cdd122 100644 --- a/hive-agent/src/login.rs +++ b/hive-agent/src/login.rs @@ -75,7 +75,7 @@ pub struct ClearedSession { /// Delete the credential files (see [`CRED_FILE_NAMES`]) from `dir`, forcing a /// re-login on the next turn, while preserving the session-history files -/// alongside them so `claude --continue` keeps working after a fresh login. +/// alongside them so the session resumed by title survives a fresh login. /// Idempotent: an already-absent file is skipped, not reported. This is the /// write-side counterpart to [`has_session`]; `/logout` (`web_ui::auth`) drives /// it. @@ -165,13 +165,12 @@ pub async fn wait_for_login( claude_dir = %claude_dir.display(), "no claude session — staying in partial-run mode (web UI only)" ); - // Announce `needs_login_idle` to the bus so the sentinel file - // (`{state_dir}/hyperhive-needs-login`) gets written on every entry - // path — cold-boot, 401-mid-turn, and `/api/logout`. The host's - // `auth_failed_sentinel` reads that file to surface `needs_login` - // on the dashboard. Idempotent — `emit_status` is a `write` on a - // small empty file, so re-entering this function after a transient - // operator action is a no-op for the on-disk state. + // Announce `needs_login_idle` to the bus so + // `hyperhive-harness.json` records it on every entry path — + // cold-boot, 401-mid-turn, and `/api/logout`. The host reads that + // file to surface `needs_login` on the dashboard. Idempotent — + // re-entering this function after a transient operator action + // rewrites the same on-disk state. bus.emit_status("needs_login_idle"); // Baseline for the unreadable-mtime fallback in `has_fresh_credentials` // — captured once, here, not recomputed per poll. See that fn's doc diff --git a/hive-agent/src/main.rs b/hive-agent/src/main.rs index d8b6a53b..bc646c62 100644 --- a/hive-agent/src/main.rs +++ b/hive-agent/src/main.rs @@ -614,9 +614,8 @@ async fn serve_main(socket: &Path, poll_ms: u64) -> Result<()> { ) .await; } else { - // Clear any stale `hyperhive-needs-login` sentinel left over - // from a prior boot — `online` status writes the sentinel - // cleanup in `Bus::emit_status`. + // Record `online` in `hyperhive-harness.json`, which clears a + // `needs_login` left over from a prior boot (`Bus::emit_status`). bus.emit_status("online"); } serve_loop::( diff --git a/hive-agent/src/otel_turn_metrics.rs b/hive-agent/src/otel_turn_metrics.rs index a78e6737..dcc9fd99 100644 --- a/hive-agent/src/otel_turn_metrics.rs +++ b/hive-agent/src/otel_turn_metrics.rs @@ -6,7 +6,7 @@ //! only the harness-only concepts: wall-clock turn duration as *this harness* //! measures it (not Claude's own per-request latency), what woke the turn, //! the harness's own outcome classification, the loose-ends backlog at turn -//! end, session boundaries (fresh vs. `--continue`'d), context-window fill, +//! end, session boundaries (fresh vs. resumed by title), context-window fill, //! and the cost an ACP agent reports — claude's runtime reports none. //! //! Synchronous instruments, not observable ones: unlike hive-c0re's diff --git a/hive-agent/src/serve_common.rs b/hive-agent/src/serve_common.rs index d56e3ccc..7e869d39 100644 --- a/hive-agent/src/serve_common.rs +++ b/hive-agent/src/serve_common.rs @@ -1,5 +1,5 @@ //! Pure helpers factored out of the harness serve loop -//! (`bin/hive-agent.rs`). +//! (`serve_main` in `main.rs`). //! Only functions with no wire-type dependency live here; //! request/response-flavored helpers (`requeue_inflight`, `ack_turn`, etc.) //! stay in the binary because they touch the request enum variants directly. diff --git a/hive-agent/src/stats.rs b/hive-agent/src/stats.rs index e9891903..cf8e1df5 100644 --- a/hive-agent/src/stats.rs +++ b/hive-agent/src/stats.rs @@ -151,9 +151,9 @@ pub struct Snapshot { pub first_turn_ctx: Option, /// Number of fresh claude sessions (i.e. rows in the `sessions` table) /// that started within the window. A new session is minted whenever the - /// harness runs a turn without `--continue` (manual `/new-session`, - /// auto-compaction fallback, or first-ever turn). `None` when the - /// `sessions` table doesn't exist yet (older db) — same inert-until-data + /// harness creates a session instead of resuming it by title (manual + /// `/new-session`, auto-compaction fallback, or first-ever turn). `None` when + /// the `sessions` table doesn't exist yet (older db) — same inert-until-data /// pattern as `first_turn_ctx`. #[serde(default, skip_serializing_if = "Option::is_none")] pub session_count: Option, @@ -380,7 +380,7 @@ fn snapshot(path: &Path, window: Window) -> Result { /// First-turn input tokens of the most recent fresh claude session that /// started in `[from, now]`. Tracks system-prompt + AGENTS.md sprawl: -/// the first turn of a fresh session (`--continue` suppressed) pays the +/// the first turn of a fresh session (created, not resumed by title) pays the /// full static prefix as uncached input, so watching this over time /// surfaces creep. Uses the agreed per-session derive — the first turn /// (`ORDER BY started_at LIMIT 1`) of the latest session row. @@ -404,8 +404,9 @@ fn read_first_turn_ctx(conn: &Connection, from: i64) -> rusqlite::Result { /// Count of fresh claude sessions that started within `[from, now]`. /// /// Each row in the `sessions` table represents one fresh `claude --print` -/// invocation (without `--continue`): a manual `/new-session`, an -/// auto-compaction fallback, or the very first turn after spawning. +/// invocation that created a session rather than resuming by title: a manual +/// `/new-session`, an auto-compaction fallback, or the very first turn after +/// spawning. /// /// Returns `Err` when the `sessions` table doesn't exist (older db) so the /// caller can map to `None` — same inert-until-data pattern as diff --git a/hive-agent/src/turn.rs b/hive-agent/src/turn.rs index c111b388..c9b33165 100644 --- a/hive-agent/src/turn.rs +++ b/hive-agent/src/turn.rs @@ -717,8 +717,9 @@ fn claude_config(bus: &Bus, files: &TurnFiles) -> Config { /// Map a `hive_claude::Error` onto the harness's `TurnOutcome`. The recognized /// sentinels become their matching outcomes; a residual `SessionNotFound` -/// (create path itself missed — shouldn't happen) settles as `Ok`; genuine -/// failures become `Failed` (converting the typed lib error into `anyhow`). +/// (create path itself missed — shouldn't happen) stays `Err(SessionNotFound)` +/// for `handle_turn_error_recovery` to requeue; genuine failures become +/// `Failed` (converting the typed lib error into `anyhow`). fn error_to_turn(err: hive_claude::Error) -> TurnOutcome { use hive_claude::Error; match err { diff --git a/hive-agent/src/turn_stats.rs b/hive-agent/src/turn_stats.rs index 513d190e..39b0ace4 100644 --- a/hive-agent/src/turn_stats.rs +++ b/hive-agent/src/turn_stats.rs @@ -10,8 +10,9 @@ //! `open_reminders_count` (planned: a loose-ends-over-time trend), and //! `note` (failure detail for `result_kind = "failed"`). //! -//! Lives next to `hyperhive-events.sqlite` in the agent's state dir -//! so the host-side state vacuum sweep can reach both. Schema is +//! Lives in the harness dir (`HYPERHIVE_HARNESS_DIR`) as +//! `hyperhive-turn-stats.sqlite`; the dashboard reads it read-only and no +//! vacuum sweeps it. Schema is //! intentionally append-only — every column has a default so future //! additions don't break old readers; new columns land via //! `ALTER TABLE ... ADD COLUMN ... DEFAULT ...` in the migration @@ -62,7 +63,8 @@ CREATE INDEX IF NOT EXISTS idx_turn_stats_started -- (the old table has no session_id column), so indexing session_id in this -- batch would fail with `no such column` and abort the whole SCHEMA apply — -- which disables the stats sink. The column is added by MIGRATIONS first. --- One row per fresh claude session (minted when --continue is suppressed). +-- One row per fresh claude session (minted when a session is created rather +-- than resumed by title). -- turn_stats.session_id FKs here so per-session stats (first-turn tokens, -- per-session totals, turn count, duration) are one GROUP BY away. CREATE TABLE IF NOT EXISTS sessions ( diff --git a/hive-agent/src/web_ui/mod.rs b/hive-agent/src/web_ui/mod.rs index 113b3ac2..39126a70 100644 --- a/hive-agent/src/web_ui/mod.rs +++ b/hive-agent/src/web_ui/mod.rs @@ -169,11 +169,12 @@ pub async fn serve( Ok(()) } -/// Bind a `UnixListener` at `path` and drop a `.bound` marker next -/// to it so c0re's gateway-map writer knows the socket is live. +/// Bind a `UnixListener` at `path` and drop a `hyperhive-socket-bound` marker +/// next to it so c0re's gateway-map writer knows the socket is live. /// Best-effort unlinks any stale socket left from a crashed previous -/// harness (clean exit removes it, but `bind(2)` refuses to overwrite -/// an existing file) and `mkdir -p`s the parent for first-boot. Mode +/// harness (nothing removes the socket or marker on exit, and `bind(2)` +/// refuses to overwrite an existing file) and `mkdir -p`s the parent for +/// first-boot. Mode /// `0o666` — world-accessible so the gateway's nginx process can /// `connect(2)` without sharing a group with the agent user. What bounds /// that is the per-agent subdir (`/run/hive-agent//`): the socket