hyperhive/hive-agent/src/paths.rs
iris 07b62612b0 docs: restructure into topic subdirectories, collapse duplicated index
Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):

Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
  getting-started/  setup.md
  agent-lifecycle/  agent-hierarchy.md, approvals.md, persistence.md
  trust-boundary/   boundary.md, security.md
  integrations/     forge.md, matrix.md, github.md, knowledge.md
  networking/       gateway.md, network.md, snapshot-store.md
  scheduler/        jobq.md, coordinator.md, ci.md, observability.md
  process/          conventions.md, gotchas.md, pr-review-gate.md
  web-ui/           terminal-rendering.md (moved into the EXISTING dir,
                    per mara's correction to the original getting-started
                    guess -- it's UI implementation detail, not onboarding)

The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).

Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).

Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).

Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.

nix fmt clean, both pre-push lints clean.
2026-09-02 01:55:37 +02:00

133 lines
6 KiB
Rust

//! Per-agent path resolution for state, harness, and credential directories.
//!
//! All agents (including the manager `root`) use `/agents/{label}/state`
//! for agent-owned durable notes, and `/agents/{label}/harness` for
//! harness-internal files (`hyperhive-events.sqlite`, `hyperhive-model`, etc.)
//! that should not clutter what claude sees as "my notes dir".
//! Claude credentials live at `$HOME/.claude` (resolves to
//! `/home/<agent-name>/.claude` because the harness service runs as a
//! non-root unix user matching the agent label — see
//! `docs/agent-lifecycle/persistence.md::First-boot agent-user migration`).
//!
//! All three paths can be overridden via env vars (`HYPERHIVE_STATE_DIR`,
//! `HYPERHIVE_HARNESS_DIR`, `HYPERHIVE_CLAUDE_DIR`) for dev / test scenarios.
use std::path::PathBuf;
/// Durable state directory for the current agent. Reads `HYPERHIVE_STATE_DIR`
/// first (always set by the meta flake to `/agents/{label}/state`); falls back
/// to the same pattern derived from `HIVE_LABEL` for dev/test environments
/// where the env var may not be set.
#[must_use]
pub fn state_dir() -> PathBuf {
if let Some(p) = std::env::var_os("HYPERHIVE_STATE_DIR") {
return PathBuf::from(p);
}
let label = std::env::var("HIVE_LABEL").unwrap_or_default();
PathBuf::from(format!("/agents/{label}/state"))
}
/// Harness-internal state directory. Holds files the harness owns
/// (`hyperhive-events.sqlite`, `hyperhive-turn-stats.sqlite`,
/// `hyperhive-model`) so they do not appear inside the agent-visible
/// `/agents/{label}/state` tree. Delegates to the shared canonical
/// resolver in `hive_agent_sock::paths` so the harness + every
/// out-of-process MCP daemon resolve this identically (reads
/// `HYPERHIVE_HARNESS_DIR`, always injected by the meta flake — panics
/// if it's unset rather than silently deriving a fallback path).
#[must_use]
pub fn harness_dir() -> PathBuf {
hive_agent_sock::paths::harness_dir()
}
/// Consolidated harness-local state db — todos + reminders, one table
/// each — mutable per-agent state the harness owns, kept out of the
/// append-only `hyperhive-events.sqlite` sink. Each store's `open()` only
/// applies its own `CREATE TABLE IF NOT EXISTS` against this same path
/// (see their call sites), so distinct table names keep them from
/// colliding without needing a per-store path wrapper here.
///
/// ⚠️ A pre-existing harness may still carry an inert `questions` table
/// here from the now-removed ask/answer mechanism — nothing opens or
/// writes it, and any rows in it are harmless leftovers, not a store to
/// migrate or clean up.
///
/// A one-time boot migration (`db_migrate::run`) folds the older
/// `hyperhive-todos.sqlite` / `hyperhive-reminders.sqlite` files into this
/// path on first boot after an upgrade.
#[must_use]
pub fn state_db() -> PathBuf {
harness_dir().join("hyperhive-state.sqlite")
}
/// Legacy pre-consolidation todos db path, consulted only by
/// [`crate::db_migrate`] on the first boot after the upgrade.
#[must_use]
pub fn legacy_todos_db() -> PathBuf {
harness_dir().join("hyperhive-todos.sqlite")
}
/// Legacy pre-consolidation reminders db path, consulted only by
/// [`crate::db_migrate`] on the first boot after the upgrade.
#[must_use]
pub fn legacy_reminders_db() -> PathBuf {
harness_dir().join("hyperhive-reminders.sqlite")
}
/// Per-turn config dir for the regenerated claude-{mcp-config,settings,
/// system-prompt} files the harness drops before each turn. Set by
/// systemd via `RuntimeDirectory = "hive-config"`: a per-service runtime
/// dir owned by the agent unix user, auto-cleared on stop. Kept separate
/// from `/run/hive` (the host-owned mcp.sock bind) so the harness owns
/// its own write surface and we don't have to chown a bind-mounted dir.
/// Overridable via `HYPERHIVE_CONFIG_DIR` for dev / test scenarios.
#[must_use]
pub fn config_dir() -> PathBuf {
if let Some(p) = std::env::var_os("HYPERHIVE_CONFIG_DIR") {
return PathBuf::from(p);
}
PathBuf::from("/run/hive-config")
}
/// Marker file whose presence means "this agent is paused": the harness
/// keeps serving its web UI and MCP daemons but drives no turns, so
/// inbox messages queue up unacked until it's removed.
///
/// It lives in the harness dir rather than `state/` because `state/` is
/// the agent's own scratch space — this is harness control state. The
/// harness dir is bind-mounted from the host, so the marker is the
/// single source of truth for both sides: the harness stats it to gate
/// the turn loop, and hive-c0re stats it to render the paused
/// indicator and creates/removes it for `hivectl pause|resume`. Being a
/// plain file, it survives container restarts — pause is sticky by
/// construction, and works even when the harness isn't running.
/// The filename constant is shared via `hive_sh4re::paths::PAUSED_MARKER_FILE`
/// (itself re-exported from `hive-priv-sock`, the actual writer on the host
/// side) so this resolver and hive-c0re's host-side one
/// (`Coordinator::agent_paused_marker`) cannot drift apart — only the
/// call-site-specific composition lived in the shared crate for no reason,
/// since hive-agent is the only in-container caller.
#[must_use]
pub fn paused_marker() -> PathBuf {
harness_dir().join(hive_sh4re::paths::PAUSED_MARKER_FILE)
}
/// Claude credentials directory for the current agent. `$HOME/.claude`
/// matches what the `claude` CLI reads at runtime — the harness sees
/// the same `$HOME` set by the per-service systemd `environment`
/// declaration (`/home/<agent>`). Falls back to `/root/.claude` for
/// dev / test environments where `HOME` isn't set so the previous
/// root-by-default shape keeps working without env wiring.
/// Overridable via `HYPERHIVE_CLAUDE_DIR` for dev / test scenarios.
#[must_use]
pub fn claude_dir() -> PathBuf {
if let Some(p) = std::env::var_os("HYPERHIVE_CLAUDE_DIR") {
return PathBuf::from(p);
}
if let Some(home) = std::env::var_os("HOME") {
let mut path = PathBuf::from(home);
path.push(".claude");
return path;
}
PathBuf::from("/root/.claude")
}