Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarmctl/src/agents_md.rs
atlas 88674eb63a read an agent's agents.md through the swarm and edit it with git diffs
The agent's state/AGENTS.md stays the truth and its harness the only
writer. The harness mirrors the file into a new `agents-md` KV bucket,
and swarm-controller serves that copy, so a read works while the agent is
stopped. An edit is a unified diff plus the revision it was made against,
sent over the agent's existing control subject; the harness refuses a
stale base, applies the diff with `git apply --check` then `git apply` to
a temp copy, and writes the file only when both pass. Fixes #5519.

Files:
- swarm-queue-client/src/agents_md.rs, lib.rs: the bucket name and
  subject, the mirror value, the `ApplyAgentsMd` control command and its
  reply. `AgentControl` loses `Copy` because the new variant carries text.
- hive-agent/src/swarm_agents_md.rs, main.rs: the mirror task (start,
  10 s re-read, immediate republish after an edit) and the apply.
- hive-agent/src/swarm_input.rs: the control loop answers both commands.
- hive-agent/src/claude_md_watch.rs: its AGENTS.md path is shared.
- hive-agent/Cargo.toml, Cargo.lock: sha1 for the git blob id (already in
  the lock), tempfile moves from dev to runtime for the apply directory.
- swarm-controller/src/agents_md.rs, main.rs: the bucket reader and
  GET/PUT /api/agents/{name}/agents-md; test AppState literals get the
  new field.
- swarm-controller/src/agent_control.rs: the sink hands back raw reply
  bytes, so each command decodes its own reply type.
- swarm-controller-api/src/agents_md.rs: the route bodies, shared with
  swarmctl.
- swarmctl/src/agents_md.rs, main.rs: `swarmctl agent agents-md get|apply`.
- nix/host-modules/swarm-nats.nix, nix/module-eval/nats-authelia.nix: the
  agent's publish grant on its own `$KV.agents-md.<agent>` key.

git needs no new package: agent containers enable programs.git, and the
harness unit's PATH includes /run/current-system/sw.
2026-10-18 12:35:11 +02:00

68 lines
2.2 KiB
Rust

//! `swarmctl agent agents-md` — read an agent's `AGENTS.md` and edit it with
//! a diff, through swarm-controller's `/api/agents/{name}/agents-md` routes.
//!
//! The read is the copy the agent's harness last published, so it works
//! while the agent is stopped. An edit is applied by the harness to the file
//! itself, so it needs the agent running.
use std::path::Path;
use anyhow::{Context as _, Result};
use swarm_controller_api::agents_md::{AgentsMdEdit, AgentsMdSaved, AgentsMdView};
use crate::agent::{parse_ident, send};
use crate::remote::Target;
fn runtime() -> Result<tokio::runtime::Runtime> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("starting a tokio runtime for the controller request")
}
/// The agent's `AGENTS.md` path. The name is checked first, so it cannot
/// reshape the URI.
fn member(agent: &str) -> Result<String> {
let agent = parse_ident(agent, "agent name")?;
Ok(format!("/api/agents/{agent}/agents-md"))
}
/// Run `swarmctl agent agents-md get`: the text on stdout, unchanged, and its
/// revision on stderr, so stdout redirected to a file is the file.
pub(crate) fn get(target: &Target, agent: &str) -> Result<()> {
let uri = member(agent)?;
let view: AgentsMdView = runtime()?.block_on(send(
target,
hyper::Method::GET,
&uri,
None::<&()>,
"agents-md-get",
))?;
eprintln!("revision {}", view.revision);
print!("{}", view.content);
Ok(())
}
/// Run `swarmctl agent agents-md apply`: send the diff in `diff_file`, made
/// against `base`, and print the file's new revision.
pub(crate) fn apply(target: &Target, agent: &str, base: &str, diff_file: &Path) -> Result<()> {
let uri = member(agent)?;
let diff = std::fs::read_to_string(diff_file)
.with_context(|| format!("reading the diff {}", diff_file.display()))?;
let edit = AgentsMdEdit {
base_revision: base.to_owned(),
diff,
};
let saved: AgentsMdSaved = runtime()?.block_on(send(
target,
hyper::Method::PUT,
&uri,
Some(&edit),
"agents-md-apply",
))?;
println!(
"applied; {agent}'s AGENTS.md is at revision {}",
saved.revision
);
Ok(())
}