diff --git a/.prettierignore b/.prettierignore index a49094a1..8aab0453 100644 --- a/.prettierignore +++ b/.prettierignore @@ -14,7 +14,7 @@ docs/tools/swarm-metrics-cli.md # prettier's `proseWrap: "preserve"` prevents prose reflow but not list-item # indentation normalisation, so these files need manual re-wrap before # adding them back to the formatter scope. -CLAUDE.md +AGENTS.md docs/agent-lifecycle/config-changes.md docs/process/conventions.md docs/networking/gateway.md diff --git a/CLAUDE.md b/AGENTS.md similarity index 100% rename from CLAUDE.md rename to AGENTS.md diff --git a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md index f3b6b04b..352ebf9f 100644 --- a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md +++ b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md @@ -37,7 +37,7 @@ actually attached, so a `continue` naming a session that isn't there fails the tool call outright instead of looking like it worked; the error says which directory it searched, which is usually the fix (pass `dir`). `dir` defaults to your work dir, `/agents//work`, which has no -`CLAUDE.md` above it, so a subagent started there does not load yours +`AGENTS.md` above it, so a subagent started there does not load yours (until that directory exists, the daemon's cwd is used). Use `status` for a zero-cost "is it still going" check — for a running subagent it also diff --git a/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md b/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md index 276f1f49..9c0223d8 100644 --- a/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md +++ b/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md @@ -5,7 +5,7 @@ description: Enforces safe handling of an agent's own durable state/notes files # State Hygiene -Durable notes/state files (your `CLAUDE.md`, `state/notes.md`, `state/TODO.md`, +Durable notes/state files (your `AGENTS.md`, `state/notes.md`, `state/TODO.md`, and anything else under `state/`) are your memory across container resets and context compactions. They only work if you actually read them before you change them - writing blind is how history gets silently destroyed or diff --git a/docs/agent-lifecycle/config-changes.md b/docs/agent-lifecycle/config-changes.md index 85c971b7..56208d5f 100644 --- a/docs/agent-lifecycle/config-changes.md +++ b/docs/agent-lifecycle/config-changes.md @@ -360,7 +360,7 @@ The system prompt (`hive-agent/prompts/system.md`, rendered by varies is which MCP tools it surfaces (gated by the agent's permissions, which the swarm sets). No `role:manager` block renders only for the root agent. The root agent's role-specific -behaviour comes from its CLAUDE.md / agent-specific instructions, not +behaviour comes from its AGENTS.md / agent-specific instructions, not the system prompt template. ## Helper events diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index ee46d818..feaf1601 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -270,6 +270,13 @@ Under `/var/lib/hyperhive/agents//`: - `hyperhive-harness.json` — rate-limited / needs-login flags read by the dashboard's async container-state fetch. See `docs/web-ui/dashboard.md::Container row`. + + The agent writes its own instructions to `AGENTS.md` here; claude and + opencode load it automatically from their cwd, `state/`. At boot, + `agents_md_migrate::run` renames a `state/CLAUDE.md` to `AGENTS.md` + when no `AGENTS.md` exists. When both exist it renames neither and + logs an error naming both paths: claude loads only `CLAUDE.md` while + one exists in its cwd or above. - `harness/` — harness-internal ephemeral state; not intended for agent consumption. Bind-mounted to `/agents//harness` inside the container (`$HYPERHIVE_HARNESS_DIR`). Contents: @@ -321,7 +328,7 @@ Under `/var/lib/hyperhive/agents//`: and other large working data. Bind-mounted RW to `/agents//work` inside the container and owned by the agent user. It sits outside `state/` so a claude or subagent started under - `work/` doesn't walk up into `state/CLAUDE.md` and load the agent's + `work/` doesn't walk up into `state/AGENTS.md` and load the agent's own identity. Survives `destroy`/recreate, gone on `--purge`. Counted in the per-agent disk metric (`measure_agent_disk`) together with `state/` and the container rootfs. The dashboard doesn't serve it: diff --git a/docs/crates/README.md b/docs/crates/README.md index d841e95f..e2caa230 100644 --- a/docs/crates/README.md +++ b/docs/crates/README.md @@ -9,5 +9,5 @@ drift out of sync the way a hand-copied mirror would — edit the crate's own README to change what shows up here. For the one-line-per-crate index (which crate does what, at a glance) -see the main [`CLAUDE.md`](../../CLAUDE.md#repo-map) repo map instead; +see the main [`AGENTS.md`](../../AGENTS.md#repo-map) repo map instead; this section is each crate's own, longer word on itself. diff --git a/docs/process/gotchas.md b/docs/process/gotchas.md index 6f55d2d1..343b7a61 100644 --- a/docs/process/gotchas.md +++ b/docs/process/gotchas.md @@ -210,7 +210,7 @@ alongside the claude dir. `/var/lib/hyperhive/agents//work/` bind-mounts to `/agents//work` (RW, owned by the agent user). It's the place for git worktrees and build dirs: a claude started under `state/` walks up -into `state/CLAUDE.md` and takes on the agent's identity, and one +into `state/AGENTS.md` and takes on the agent's identity, and one started under `work/` doesn't. `/home/` lives in the container rootfs, which `destroy` deletes; `work/` survives `destroy`/recreate and goes on `--purge`. The per-agent disk metric counts it; the diff --git a/docs/scheduler/observability.md b/docs/scheduler/observability.md index f50eb853..d5cadcea 100644 --- a/docs/scheduler/observability.md +++ b/docs/scheduler/observability.md @@ -383,7 +383,7 @@ of their own — see [ACP-reported cost](#acp-reported-cost). | `hyperhive.agent.session.count` | — | counter | `model` (incremented once per fresh, non-`--continue`'d session) | | `hyperhive.agent.loose_ends.threads` | — | gauge | none | | `hyperhive.agent.loose_ends.reminders` | — | gauge | none | -| `hyperhive.agent.claude_md.lines` | — | gauge | none — recorded from the `CLAUDE.md`-size watch's own ~15-minute tick, **not** per turn like the rows above | +| `hyperhive.agent.claude_md.lines` | — | gauge | none — recorded from the `AGENTS.md`-size watch's own ~15-minute tick, **not** per turn like the rows above | | `hyperhive.agent.subagent_role.lines` | — | gauge | `role` (the file stem) — one series per role file in `subagent_roles/`, on the same ~15-minute tick | | `hyperhive.agent.claude_usage.percent` | `%` | gauge | `window` (`five_hour`, `seven_day`, … as the usage endpoint names them) — polled every 5 minutes, **not** per turn | | `hyperhive.agent.claude_usage.resets_at` | `s` | gauge | `window` — unix seconds at which that window resets; same 5-minute poll | diff --git a/docs/tools/hivectl.md b/docs/tools/hivectl.md index 70551e49..a31057ee 100644 --- a/docs/tools/hivectl.md +++ b/docs/tools/hivectl.md @@ -162,7 +162,7 @@ lands in a faithful copy of the agent's environment: agent's `/home//.claude` where its OAuth credentials live. choom prefixes the machine with `@` (the meta-flake sets the agent's unix user name to its label). -- **from `/agents//state`**: the session and `CLAUDE.md` (the +- **from `/agents//state`**: the session and `AGENTS.md` (the persona) resolve against the right project dir. - **with the harness flags**: `--settings`, `--mcp-config`, and `--system-prompt-file` from `/run/hive-config/` — the same files the diff --git a/docs/turn-loop/claude-invocation.md b/docs/turn-loop/claude-invocation.md index 22b1ba15..21539961 100644 --- a/docs/turn-loop/claude-invocation.md +++ b/docs/turn-loop/claude-invocation.md @@ -210,9 +210,12 @@ still applies. The child runs with `cwd = /state` (when the bind exists; falls back to the parent's cwd in dev), so any relative path in a tool call (`Read foo.md`, `Bash ls`, `Write notes.md`) lands in the -agent's durable bind-mounted dir. CLAUDE.md autoload walks -upward from `/state` — drop a per-agent CLAUDE.md there if you -want long-term hints that survive destroy/recreate. +agent's durable bind-mounted dir. AGENTS.md autoload walks +upward from `/state` — drop a per-agent AGENTS.md there if you +want long-term hints that survive destroy/recreate. A `CLAUDE.md` +in `/state` or above it shadows `AGENTS.md`: claude then loads only +the `CLAUDE.md` (see `docs/agent-lifecycle/persistence.md` for the +boot-time rename). The wake prompt is intentionally minimal: the popped message's `from`/`body`, prefixed with a `[msg #]` broker-row-id marker diff --git a/docs/web-ui/agent.md b/docs/web-ui/agent.md index 1fe199f9..de91478f 100644 --- a/docs/web-ui/agent.md +++ b/docs/web-ui/agent.md @@ -427,7 +427,7 @@ hit-rate** (`cache_read` over all input-side tokens) and scheduled / delivered / pending** for the window. When the per-session capture has data, a **first-turn ctx** chip shows the input tokens of the most recent fresh claude session's first turn — -a proxy for system-prompt + CLAUDE.md sprawl (a fresh session's first +a proxy for system-prompt + AGENTS.md sprawl (a fresh session's first turn pays the full static prefix uncached). It's derived in `stats.rs` (`first_turn_ctx`: the first turn, `ORDER BY started_at LIMIT 1`, of the latest `sessions` row in the window) and stays out of the JSON diff --git a/frontend/packages/agent/src/stats.js b/frontend/packages/agent/src/stats.js index 15468083..38b37612 100644 --- a/frontend/packages/agent/src/stats.js +++ b/frontend/packages/agent/src/stats.js @@ -147,7 +147,7 @@ window.Chart = Chart; chips.push(["sessions", fmtInt(s.session_count)]); } // First-turn ctx: input tokens of the most recent fresh session's - // first turn — the cold system-prompt + CLAUDE.md cost, a sprawl + // first turn — the cold system-prompt + AGENTS.md cost, a sprawl // proxy. Omitted from the JSON (and so absent here) until the // per-session capture has data. if (typeof s.first_turn_ctx === "number") { diff --git a/hive-agent/prompts/system.md b/hive-agent/prompts/system.md index c3dd9e95..2f389439 100644 --- a/hive-agent/prompts/system.md +++ b/hive-agent/prompts/system.md @@ -33,7 +33,7 @@ Durable knowledge: - write to `/agents/{label}/state/notes.md` (free-form) or any other path under `/agents/{label}/state/`. That directory is bind-mounted from the host and persists across container destroy/recreate — claude's `--continue` session only carries short-term context, but `/agents/{label}/state/` is forever. Read it back at the start of relevant turns to remember things across resets. -**Work dir**: `/agents/{label}/work/` is the place for git worktrees, clones, build trees and other bulky scratch. It is bind-mounted from the host, persists across container destroy/recreate and is backed up. Keep `state/` for notes and small durable files. Don't keep work under `/home`, `/tmp` or other container paths — they are outside what's backed up. Start subagents with their `dir` under `work/` (that is also the default): a claude started under `state/` walks up into `state/CLAUDE.md` and takes on your identity, one under `work/` doesn't. +**Work dir**: `/agents/{label}/work/` is the place for git worktrees, clones, build trees and other bulky scratch. It is bind-mounted from the host, persists across container destroy/recreate and is backed up. Keep `state/` for notes and small durable files. Don't keep work under `/home`, `/tmp` or other container paths — they are outside what's backed up. Start subagents with their `dir` under `work/` (that is also the default): a claude started under `state/` walks up into `state/AGENTS.md` and takes on your identity, one under `work/` doesn't. Claude session (OAuth credentials) lives at `/root/.claude/` and persists across restarts. diff --git a/hive-agent/src/agents_md_migrate.rs b/hive-agent/src/agents_md_migrate.rs new file mode 100644 index 00000000..5aad8ed8 --- /dev/null +++ b/hive-agent/src/agents_md_migrate.rs @@ -0,0 +1,143 @@ +//! One-time on-boot rename of the agent's instructions file from +//! `state/CLAUDE.md` to `state/AGENTS.md`. Runs in `main.rs` before the +//! first turn. Idempotent: once the file is renamed, later boots find no +//! `CLAUDE.md` and do nothing. +//! +//! Claude Code reads `AGENTS.md` only when no `CLAUDE.md` exists in its cwd +//! or above, so a `CLAUDE.md` left next to an `AGENTS.md` shadows it. When +//! both exist this module refuses to choose: it logs an error naming both +//! paths and leaves the files alone for the agent or operator to merge. + +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; + +/// What [`rename_claude_md`] found and did. +#[derive(Debug, PartialEq, Eq)] +pub enum Outcome { + /// `CLAUDE.md` existed without an `AGENTS.md`, and is now `AGENTS.md`. + Renamed, + /// Neither file, or only `AGENTS.md`: nothing to do. + NothingToDo, + /// Both files exist; neither was touched. + BothExist { + claude_md: PathBuf, + agents_md: PathBuf, + }, +} + +/// Rename `/CLAUDE.md` to `/AGENTS.md` if only the +/// former exists. Logs the outcome; never fatal to boot. +pub fn run(state_dir: &Path) { + match rename_claude_md(state_dir) { + Ok(Outcome::Renamed) => { + tracing::info!(dir = %state_dir.display(), "renamed CLAUDE.md to AGENTS.md"); + } + Ok(Outcome::NothingToDo) => {} + Ok(Outcome::BothExist { + claude_md, + agents_md, + }) => tracing::error!( + claude_md = %claude_md.display(), + agents_md = %agents_md.display(), + "both CLAUDE.md and AGENTS.md exist; claude loads only CLAUDE.md while it exists. \ + Merge them into AGENTS.md and delete CLAUDE.md" + ), + Err(e) => tracing::error!(error = ?e, "CLAUDE.md to AGENTS.md rename failed"), + } +} + +/// Decide and apply the rename. +/// +/// # Errors +/// +/// Propagates a failed `rename`; both files are left as they were. +pub fn rename_claude_md(state_dir: &Path) -> Result { + let claude_md = state_dir.join("CLAUDE.md"); + let agents_md = state_dir.join("AGENTS.md"); + match (claude_md.exists(), agents_md.exists()) { + (true, false) => { + std::fs::rename(&claude_md, &agents_md).with_context(|| { + format!("rename {} -> {}", claude_md.display(), agents_md.display()) + })?; + Ok(Outcome::Renamed) + } + (true, true) => Ok(Outcome::BothExist { + claude_md, + agents_md, + }), + (false, _) => Ok(Outcome::NothingToDo), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn claude_md_alone_is_renamed_once() { + let dir = tempfile::tempdir().expect("tempdir"); + std::fs::write(dir.path().join("CLAUDE.md"), "me\n").expect("write"); + + assert_eq!(rename_claude_md(dir.path()).expect("run"), Outcome::Renamed); + assert!(!dir.path().join("CLAUDE.md").exists()); + assert_eq!( + std::fs::read_to_string(dir.path().join("AGENTS.md")).expect("read"), + "me\n" + ); + + assert_eq!( + rename_claude_md(dir.path()).expect("second run"), + Outcome::NothingToDo + ); + assert_eq!( + std::fs::read_to_string(dir.path().join("AGENTS.md")).expect("read"), + "me\n" + ); + } + + #[test] + fn both_present_touches_neither() { + let dir = tempfile::tempdir().expect("tempdir"); + let claude_md = dir.path().join("CLAUDE.md"); + let agents_md = dir.path().join("AGENTS.md"); + std::fs::write(&claude_md, "old\n").expect("write"); + std::fs::write(&agents_md, "new\n").expect("write"); + + assert_eq!( + rename_claude_md(dir.path()).expect("run"), + Outcome::BothExist { + claude_md: claude_md.clone(), + agents_md: agents_md.clone(), + } + ); + assert_eq!(std::fs::read_to_string(&claude_md).expect("read"), "old\n"); + assert_eq!(std::fs::read_to_string(&agents_md).expect("read"), "new\n"); + } + + #[test] + fn neither_present_creates_nothing() { + let dir = tempfile::tempdir().expect("tempdir"); + + assert_eq!( + rename_claude_md(dir.path()).expect("run"), + Outcome::NothingToDo + ); + assert_eq!(std::fs::read_dir(dir.path()).expect("ls").count(), 0); + } + + #[test] + fn agents_md_alone_is_left_alone() { + let dir = tempfile::tempdir().expect("tempdir"); + std::fs::write(dir.path().join("AGENTS.md"), "me\n").expect("write"); + + assert_eq!( + rename_claude_md(dir.path()).expect("run"), + Outcome::NothingToDo + ); + assert_eq!( + std::fs::read_to_string(dir.path().join("AGENTS.md")).expect("read"), + "me\n" + ); + } +} diff --git a/hive-agent/src/claude_md_watch.rs b/hive-agent/src/claude_md_watch.rs index efd21e79..8f88958c 100644 --- a/hive-agent/src/claude_md_watch.rs +++ b/hive-agent/src/claude_md_watch.rs @@ -1,5 +1,5 @@ -//! `CLAUDE.md` size watch: raises a loose-ends-v2 todo when the agent's -//! own `CLAUDE.md` grows past 500 lines, and feeds the same line count +//! `AGENTS.md` size watch: raises a loose-ends-v2 todo when the agent's +//! own `AGENTS.md` grows past 500 lines, and feeds the same line count //! into an OTEL gauge every tick regardless of threshold. Per the //! operator's own framing of the ask — "claude md is personality and //! how you organize yourself, not a work log" — the prompt is asked to @@ -11,7 +11,7 @@ //! summary) — a third instance of the same anti-nag pattern rather than //! a new one. //! -//! `CLAUDE.md` lives at the top of the agent's own state dir — the same +//! `AGENTS.md` lives at the top of the agent's own state dir — the same //! directory `claude --print` runs with as its cwd (`turn::session_cwd`), //! which is how the CLI's own auto-load discovers it. //! @@ -40,20 +40,24 @@ const WARN_LINES: u64 = 500; const BUCKET: u64 = 50; /// Producing subsystem marker for the todo row. const SUBSYSTEM: &str = "claude_md"; -/// Fixed dedup key — there is only ever one `CLAUDE.md`-size todo per +/// Fixed dedup key — there is only ever one `AGENTS.md`-size todo per /// agent. const TODO_KEY: &str = "size"; -/// Background loop: periodically reconcile the `CLAUDE.md`-size todo and +/// Background loop: periodically reconcile the `AGENTS.md`-size todo and /// feed the OTEL gauge. Detached task — runs for the harness's lifetime; /// errors are logged, never fatal. pub async fn run(todos: Arc, wake: Arc) { loop { tokio::time::sleep(CHECK_INTERVAL).await; - let lines = match tokio::task::spawn_blocking(|| line_count(&claude_md_path())).await { + let lines = match tokio::task::spawn_blocking(|| { + line_count(&agents_md_path(&crate::paths::state_dir())) + }) + .await + { Ok(l) => l, Err(e) => { - tracing::warn!(error = %e, "claude.md watch probe panicked"); + tracing::warn!(error = %e, "agents.md watch probe panicked"); continue; } }; @@ -72,25 +76,25 @@ fn reconcile(todos: &Todos, wake: &Notify, summary: Option<&str>) { Some(summary) => match todos.upsert(SUBSYSTEM, Some(TODO_KEY), summary, None, false) { Ok((_, true)) => wake.notify_one(), Ok((_, false)) => {} - Err(e) => tracing::warn!(error = ?e, "claude.md todo upsert failed"), + Err(e) => tracing::warn!(error = ?e, "agents.md todo upsert failed"), }, None => { if let Err(e) = todos.clear(SUBSYSTEM, Some(TODO_KEY)) { - tracing::warn!(error = ?e, "claude.md todo clear failed"); + tracing::warn!(error = ?e, "agents.md todo clear failed"); } } } } -/// `CLAUDE.md`'s path: the top of the agent's own state dir, matching +/// `AGENTS.md`'s path: the top of the agent's own state dir, matching /// where `claude --print` runs (`turn::session_cwd`) and therefore where /// its own auto-load discovers the file. -fn claude_md_path() -> std::path::PathBuf { - crate::paths::state_dir().join("CLAUDE.md") +fn agents_md_path(state_dir: &Path) -> std::path::PathBuf { + state_dir.join("AGENTS.md") } /// Line count of the file at `path`. `None` when it can't be read — no -/// `CLAUDE.md` yet is not a warning, and a transient read failure isn't +/// `AGENTS.md` yet is not a warning, and a transient read failure isn't /// either. fn line_count(path: &Path) -> Option { let contents = std::fs::read_to_string(path).ok()?; @@ -108,9 +112,9 @@ fn summary_for(lines: u64) -> Option { } let bucket = lines / BUCKET * BUCKET; Some(format!( - "your CLAUDE.md is {bucket}+ lines. CLAUDE.md is personality and how you organize \ + "your AGENTS.md is {bucket}+ lines. AGENTS.md is personality and how you organize \ yourself, not a work log — if it's grown past that, moving detail into separate files \ - (linked from CLAUDE.md) or an archive is what actually shrinks this count. It \ + (linked from AGENTS.md) or an archive is what actually shrinks this count. It \ auto-loads into every turn, so a smaller file is a fixed cost saved on every turn. \ Nothing here is a hard limit, and deleting stale content outright is fine too if \ that's genuinely what it is — no obligation either way. No need to act on this now — \ @@ -175,16 +179,28 @@ mod tests { #[test] fn line_count_matches_editor_expectations() { let dir = tempfile::tempdir().expect("tempdir"); - let path = dir.path().join("CLAUDE.md"); + let path = dir.path().join("AGENTS.md"); // Three lines, trailing newline — an editor shows 3 lines, not 4. std::fs::write(&path, "one\ntwo\nthree\n").expect("write"); assert_eq!(line_count(&path), Some(3)); } + /// The watch counts `AGENTS.md`, and a leftover `CLAUDE.md` beside it + /// is not what it reads. + #[test] + fn watch_reads_agents_md_not_claude_md() { + let dir = tempfile::tempdir().expect("tempdir"); + std::fs::write(dir.path().join("CLAUDE.md"), "one\n").expect("write"); + assert_eq!(line_count(&agents_md_path(dir.path())), None); + + std::fs::write(dir.path().join("AGENTS.md"), "one\ntwo\n").expect("write"); + assert_eq!(line_count(&agents_md_path(dir.path())), Some(2)); + } + #[test] fn missing_file_is_silent_not_an_error() { assert_eq!( - line_count(Path::new("/definitely/does/not/exist/CLAUDE.md")), + line_count(Path::new("/definitely/does/not/exist/AGENTS.md")), None ); } diff --git a/hive-agent/src/main.rs b/hive-agent/src/main.rs index 81108225..05d4cc94 100644 --- a/hive-agent/src/main.rs +++ b/hive-agent/src/main.rs @@ -9,6 +9,7 @@ //! Single bin crate: the module tree below (formerly this crate's `lib.rs`, //! before lib + bin were collapsed into one) plus the serve loop. +mod agents_md_migrate; mod boot_announce; mod claude_credentials; mod claude_md_watch; @@ -215,7 +216,7 @@ fn graceful_stop_message() -> hive_sh4re::inbox::DeliveredMessage { body: "You are being gracefully stopped — the container will shut down after this turn, \ and new inbound messages are already fenced. This is your one checkpoint turn: \ flush anything worth keeping into your durable /state files now — update your \ - notes / CLAUDE.md / TODO.md with in-flight task state, decisions made, important \ + notes / AGENTS.md / TODO.md with in-flight task state, decisions made, important \ file paths, and whatever you'd need to resume cleanly later with only a summary \ of this conversation to go on. Do not start new work or reply to anyone; just \ write your notes and end your turn. The session may be compacted after this turn \ @@ -450,7 +451,7 @@ fn spawn_todo_socket( // top level instead of disk pressure. tokio::spawn(state_entry_watch::run(store.clone(), todo_wake.clone())); // Same shape again: nudge (and feed an OTEL gauge) on a - // grown CLAUDE.md instead of a grown state dir. + // grown AGENTS.md instead of a grown state dir. tokio::spawn(claude_md_watch::run(store.clone(), todo_wake.clone())); // Swarm-scheduled prompts fire as todos too, from the list the // swarm keeps for this agent. @@ -553,6 +554,8 @@ async fn serve_main(socket: &Path, poll_ms: u64) -> Result<()> { tracing::error!(error = %e, "web_ui::serve exited with error"); } }); + // Before the first turn, so claude's own auto-load finds `AGENTS.md`. + agents_md_migrate::run(&paths::state_dir()); // Harness-local reminders: a due reminder fires // straight into an mpsc channel the serve loop races against the // broker long-poll (unlike todos, a fire carries real per-row data, diff --git a/hive-agent/src/otel_turn_metrics.rs b/hive-agent/src/otel_turn_metrics.rs index 18ef9246..a78e6737 100644 --- a/hive-agent/src/otel_turn_metrics.rs +++ b/hive-agent/src/otel_turn_metrics.rs @@ -53,7 +53,7 @@ struct Instruments { open_threads: Gauge, open_reminders: Gauge, /// Recorded from [`crate::claude_md_watch`]'s own tick, not from - /// [`record`] — `CLAUDE.md` size is a continuously-live value (like + /// [`record`] — `AGENTS.md` size is a continuously-live value (like /// hive-c0re's container gauges), not a once-per-turn one, so it has /// its own entry point rather than riding `record`'s per-turn call. claude_md_lines: Gauge, @@ -110,7 +110,7 @@ pub fn record(row: &TurnStatRow, fresh_session: bool) { } } -/// Record the current `CLAUDE.md` line count. Called from +/// Record the current `AGENTS.md` line count. Called from /// [`crate::claude_md_watch`]'s periodic tick (every ~15 minutes, not /// per turn) — see the field doc on [`Instruments::claude_md_lines`] for /// why this doesn't ride [`record`]. No-op when OTEL isn't configured, diff --git a/hive-agent/src/stats.rs b/hive-agent/src/stats.rs index e761753c..e9891903 100644 --- a/hive-agent/src/stats.rs +++ b/hive-agent/src/stats.rs @@ -142,7 +142,7 @@ pub struct Snapshot { #[serde(default, skip_serializing_if = "Option::is_none")] pub reminder_stats: Option, /// First-turn input tokens of the most recent fresh claude session - /// that started in the window — a proxy for system-prompt + CLAUDE.md + /// that started in the window — a proxy for system-prompt + AGENTS.md /// sprawl (a fresh session's first turn pays the full static prefix /// uncached, so this is the current "cold context" cost). `None` until /// the sessions capture (per-session `session_id`) has data; every @@ -379,7 +379,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 + CLAUDE.md sprawl: +/// started in `[from, now]`. Tracks system-prompt + AGENTS.md sprawl: /// the first turn of a fresh session (`--continue` suppressed) pays the /// full static prefix as uncached input, so watching this over time /// surfaces creep. Uses the agreed per-session derive — the first turn diff --git a/hive-agent/src/turn.rs b/hive-agent/src/turn.rs index 026f716c..f0dff1f9 100644 --- a/hive-agent/src/turn.rs +++ b/hive-agent/src/turn.rs @@ -81,7 +81,7 @@ Your conversation context has grown large and the harness is about to run `/comp which collapses the detailed turn-by-turn history into a short summary. Anything you \ do not persist now is effectively lost after the next turn.\n\n\ Use THIS turn to flush anything worth keeping into your durable `/state` files: update \ -your notes / CLAUDE.md / TODO.md with in-flight task state, decisions made, important \ +your notes / AGENTS.md / TODO.md with in-flight task state, decisions made, important \ file paths, and whatever you would need to resume cleanly with only a summary of this \ conversation to go on. Do not start new work or reply to anyone — just write your notes \ and end the turn."; diff --git a/hive-c0re/README.md b/hive-c0re/README.md index fb8c3ed7..fd485fb8 100644 --- a/hive-c0re/README.md +++ b/hive-c0re/README.md @@ -47,5 +47,5 @@ to track it: matrix provisioning, gateway vhosts, and the `hive-priv` client respectively. -See the top-level `CLAUDE.md`/`docs/` index for the full reading-path +See the top-level `AGENTS.md`/`docs/` index for the full reading-path map. diff --git a/hive-subagent-mcp/src/mcp.rs b/hive-subagent-mcp/src/mcp.rs index e9430b02..cbe70b18 100644 --- a/hive-subagent-mcp/src/mcp.rs +++ b/hive-subagent-mcp/src/mcp.rs @@ -80,7 +80,7 @@ struct StartArgs { /// Working directory for the subagent's session — e.g. a git worktree /// you've already prepared for it, so a parallel batch of subagents /// never race on the same working tree. Must exist. Omit to use your - /// work dir, `/agents//work` (it has no `CLAUDE.md` above it, so the + /// work dir, `/agents//work` (it has no `AGENTS.md` above it, so the /// subagent does not load yours); until that directory exists (a container /// that predates its bind) the daemon's cwd is used. The daemon /// remembers whichever `dir` you give here against `name`, so a later diff --git a/hive-subagent-mcp/src/paths.rs b/hive-subagent-mcp/src/paths.rs index 558ed6fd..24726629 100644 --- a/hive-subagent-mcp/src/paths.rs +++ b/hive-subagent-mcp/src/paths.rs @@ -31,7 +31,7 @@ pub fn state_dir() -> PathBuf { } /// The agent's persistent work dir, a sibling of [`state_dir`] -/// (`/agents//work`). It has no `CLAUDE.md` above it, so a subagent +/// (`/agents//work`). It has no `AGENTS.md` above it, so a subagent /// started here does not load the agent's own. #[must_use] pub fn work_dir() -> PathBuf { diff --git a/nix/host-modules/swarm-grafana/dashboards/agents.json b/nix/host-modules/swarm-grafana/dashboards/agents.json index 6e0047d6..0defab47 100644 --- a/nix/host-modules/swarm-grafana/dashboards/agents.json +++ b/nix/host-modules/swarm-grafana/dashboards/agents.json @@ -853,8 +853,8 @@ "kind": "Panel", "spec": { "id": 33, - "title": "CLAUDE.md size by agent", - "description": "Lines in each agent's CLAUDE.md file over time.", + "title": "AGENTS.md size by agent", + "description": "Lines in each agent's AGENTS.md file over time.", "links": [], "data": { "kind": "QueryGroup", diff --git a/nix/sources.nix b/nix/sources.nix index 2fd4ad14..79992135 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -10,7 +10,7 @@ # does NOT change the store path and therefore does NOT force a # rebuild of every agent container. # - # Dropped: scripts/, docs/, root-level *.md (README/CLAUDE/TODO/…). + # Dropped: scripts/, docs/, root-level *.md (README/AGENTS/TODO/…). # Kept (build needs them): .nix, .rs, Cargo.*, branding/, # frontend/, prompts/, flake.lock. docs/ is deliberately dropped: # it is shipped to agent containers as its OWN narrow meta-flake