rename claude.md to agents.md for agents and the repo
Claude Code (>= 2.1.277) and opencode both read AGENTS.md, but Claude
Code reads it only when no CLAUDE.md exists in its cwd or above. So
this is a plain rename with no CLAUDE.md stub left behind.
- hive-agent/src/agents_md_migrate.rs: new. At harness start, before
the first turn, renames state/CLAUDE.md to state/AGENTS.md when only
the former exists; logs an error naming both paths when both exist
and touches neither; does nothing otherwise. Idempotent.
- hive-agent/src/main.rs: calls it in serve_main; checkpoint prompt
and a comment name AGENTS.md.
- hive-agent/src/claude_md_watch.rs: the size watch reads AGENTS.md;
todo text, doc comments and tests follow. Module name, todo
subsystem key ("claude_md") and metric name
(hyperhive.agent.claude_md.lines) are unchanged so existing todo
rows and dashboard history carry over.
- hive-agent/src/turn.rs, hive-agent/prompts/system.md: prompts tell
the agent its file is AGENTS.md.
- hive-agent/src/{otel_turn_metrics,stats}.rs,
frontend/packages/agent/src/stats.js, hive-subagent-mcp/src/*.rs:
comments and a tool parameter description naming the agent's file.
- nix/host-modules/swarm-grafana/dashboards/agents.json: panel title
and description.
- claude-plugins skills claude-subagents, state-hygiene: they mean the
agent's own state file.
- CLAUDE.md -> AGENTS.md at the repo root; docs/crates/README.md link
(checked by check-doc-refs.sh), hive-c0re/README.md,
.prettierignore and the nix/sources.nix comment follow.
- docs/: every prose mention of the agent's file; persistence.md
documents the boot-time rename, claude-invocation.md the shadowing.
Fixes #5509
This commit is contained in:
parent
484a9fd4c1
commit
a429b30448
25 changed files with 217 additions and 45 deletions
|
|
@ -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<Todos>, wake: Arc<Notify>) {
|
||||
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<u64> {
|
||||
let contents = std::fs::read_to_string(path).ok()?;
|
||||
|
|
@ -108,9 +112,9 @@ fn summary_for(lines: u64) -> Option<String> {
|
|||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue