From 9add99838c1f2ecfeb4f41537ce242d076374f36 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 18 Oct 2026 03:37:27 +0200 Subject: [PATCH] document reading and editing an agent's agents.md through the swarm docs/swarm/README.md gets the routes, the revision, the apply semantics and the stopped-agent behaviour; the persistence doc points there from the AGENTS.md entry. The swarm-controller, swarm-controller-api and swarmctl READMEs and the repo map in AGENTS.md list the new bodies and routes. docs/tools/swarmctl-cli.md is regenerated for `swarmctl agent agents-md`, whose help text loses two passive phrasings vale flags. --- AGENTS.md | 8 ++-- docs/agent-lifecycle/persistence.md | 5 ++- docs/swarm/README.md | 47 +++++++++++++++++++++-- docs/tools/swarmctl-cli.md | 59 +++++++++++++++++++++++++++++ swarm-controller-api/README.md | 3 +- swarm-controller/README.md | 6 +++ swarmctl/README.md | 5 ++- swarmctl/src/main.rs | 4 +- 8 files changed, 124 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 931dbe9d..496242a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -118,11 +118,11 @@ Swarm-level: **the socket's directory is its access control**; the crate README has the constraint and a unit test pins the path. - **`swarm-controller-api/`** — serde-only request and response bodies of - the controller's HTTP API, shared with `swarmctl` (forge, schedules and - the agent-create and forge-token responses). + the controller's HTTP API, shared with `swarmctl` (forge, schedules, + `AGENTS.md` edits, and the agent-create and forge-token responses). - **`swarmctl/`** — swarm operator CLI; runs as **root and acts directly** - (README says why). Doesn't link `swarm-controller`: the forge and - schedule bodies and the agent responses come from + (README says why). Doesn't link `swarm-controller`: the forge, schedule + and `AGENTS.md` bodies and the agent responses come from `swarm-controller-api`; the agent-create request, inference and permissions ones are a local mirror. ⚠️ Edits authelia's **`users.yml` in place**, shared with `swarm-authelia-bridge`; that diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index feaf1601..5ad13f56 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -276,7 +276,10 @@ Under `/var/lib/hyperhive/agents//`: `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. + one exists in its cwd or above. The harness also publishes the file + to the swarm, which reads that copy, and applies the git diffs the + swarm sends to edit it. This file stays the only copy that counts: + [`docs/swarm/README.md`](../swarm/README.md#an-agents-agentsmd). - `harness/` — harness-internal ephemeral state; not intended for agent consumption. Bind-mounted to `/agents//harness` inside the container (`$HYPERHIVE_HARNESS_DIR`). Contents: diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 808431d7..d39ba838 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -278,13 +278,19 @@ The third thing an agent publishes is its **icon**, the same SVG its own `GET /icon` serves. It goes into the `agent-icons` KV bucket under the key ``, with no hive in it, so the swarm can show the icon of an agent that's stopped or has moved hives. The queue grants the agent's own credential -`$KV.agent-icons.` and no other key. The harness writes once per start, -because a config change reaches an agent by restarting its container. An agent -with no icon deletes its key. Swarm-side, `GET /api/agents//icon` serves +`$KV.agent-icons.` and no other key of that bucket. The harness writes +once per start, because a config change reaches an agent by restarting its +container. An agent with no icon deletes its key. Swarm-side, +`GET /api/agents//icon` serves the stored bytes, and 404 means the agent has no icon. swarm-ui's agent cards load it as an `` and show the dimmed hyperhive mark for an agent without one, as the hive dashboard does. +The fourth is its **`AGENTS.md`**, which goes into the `agents-md` bucket under +the key ``, writable by the agent's own credential alone +(`$KV.agents-md.`). The swarm reads that copy and edits the file through +the agent; [An agent's AGENTS.md](#an-agents-agentsmd) has the details. + The queue also limits what an agent may **subscribe** to, because agents are the least trusted clients on the queue. An agent's own queue credential, which its subagent daemon also presents, may subscribe to its reply inbox @@ -309,6 +315,41 @@ no replies, so every request it makes times out. +### An agent's AGENTS.md + +The file in the agent's state dir, `state/AGENTS.md`, is the only copy that +counts, and only the agent writes it: itself, or its harness applying an edit +from the swarm. No hive is in either path. + +**Reading.** The harness publishes the file's text and its revision, the git +blob id that `git hash-object` prints for it, at start, within ten seconds of +any change, and straight after an edit it applied. An agent with no `AGENTS.md` +publishes `""`. `GET /api/agents//agents-md` answers `{content, revision}` +from that copy, so it works while the agent isn't running. A 404 means the agent +hasn't published one: its harness hasn't run since it began to. + +**Editing.** `PUT /api/agents//agents-md` takes `{base_revision, diff}`. +The diff is a unified diff that names `a/AGENTS.md` and `b/AGENTS.md`, made +against the text at `base_revision`. swarm-controller sends it as a request on +the agent's command subject `$SWARM.control.`, as it sends a cancel. The +harness refuses it when the file isn't at `base_revision`. Otherwise it runs +`git apply --check` and then `git apply` on a copy of the file, and writes the +result over the file only when both pass: + +| Status | Meaning | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| 200 | applied; the body is `{revision}`, the file's new revision | +| 409 | the file isn't at `base_revision` any more; nothing applied, and `revision` in the body is the one it's at | +| 422 | `git apply` refused the diff, or it touches a file other than `AGENTS.md`; `detail` carries git's message, and the file stays as it was | +| 503 | the agent isn't running or isn't connected to the queue | +| 504 | the harness didn't answer within ten seconds, so whether the edit applied is unknown | +| 500 | the harness couldn't read or write the file or run git, or the queue failed the request | + +An edit needs the agent running, and a read of a stopped agent shows the text +it last published. A harness older than this route doesn't know the command and +drops it unanswered, which reads as a 504. `swarmctl agent agents-md get` and +`apply` call the two routes. + ### Swarm-wide forge objects The controller also keeps the forge objects that are one per swarm, not diff --git a/docs/tools/swarmctl-cli.md b/docs/tools/swarmctl-cli.md index 3d0c89de..6ec9956c 100644 --- a/docs/tools/swarmctl-cli.md +++ b/docs/tools/swarmctl-cli.md @@ -16,6 +16,9 @@ This document contains the help content for the `swarmctl` command-line program. * [`swarmctl agent permissions get`↴](#swarmctl-agent-permissions-get) * [`swarmctl agent permissions set`↴](#swarmctl-agent-permissions-set) * [`swarmctl agent permissions clear`↴](#swarmctl-agent-permissions-clear) +* [`swarmctl agent agents-md`↴](#swarmctl-agent-agents-md) +* [`swarmctl agent agents-md get`↴](#swarmctl-agent-agents-md-get) +* [`swarmctl agent agents-md apply`↴](#swarmctl-agent-agents-md-apply) * [`swarmctl schedule`↴](#swarmctl-schedule) * [`swarmctl schedule add`↴](#swarmctl-schedule-add) * [`swarmctl schedule list`↴](#swarmctl-schedule-list) @@ -71,6 +74,7 @@ Manage agents across the swarm * `mint-forge-token` — Check one agent's forge token, and mint it if it's missing or stale * `inference` — Show the model, effort, subagent models and subagent cap the swarm sets for an agent, or change the model, effort and subagent models * `permissions` — Show or change the permissions the swarm grants an agent +* `agents-md` — Show an agent's AGENTS.md, or edit it with a diff @@ -274,6 +278,61 @@ Clear the agent's permissions, so the default applies again +## `swarmctl agent agents-md` + +Show an agent's AGENTS.md, or edit it with a diff. + +`get` shows the copy the agent last published, so it works while the agent isn't running. `apply` hands the diff to the agent, which applies it to the file with `git apply`; it needs the agent running. + +**Usage:** `swarmctl agent agents-md ` + +###### **Subcommands:** + +* `get` — Print an agent's AGENTS.md on stdout, and its revision on stderr +* `apply` — Apply a unified diff to an agent's AGENTS.md + + + +## `swarmctl agent agents-md get` + +Print an agent's AGENTS.md on stdout, and its revision on stderr + +**Usage:** `swarmctl agent agents-md get [OPTIONS] ` + +###### **Arguments:** + +* `` — Agent name + +###### **Options:** + +* `--controller-socket ` — swarm-controller's unix socket. + + Supplied by the nix module that installs this binary, from the same `socketPath` option the daemon binds; falls back to `SWARM_CONTROLLER_SOCKET`. + + + +## `swarmctl agent agents-md apply` + +Apply a unified diff to an agent's AGENTS.md. + +The diff names `a/AGENTS.md` and `b/AGENTS.md`, as `diff -u a/AGENTS.md b/AGENTS.md` writes it. The agent refuses it when the file is no longer at `--base`, or when it doesn't apply, and leaves the file as it was. + +**Usage:** `swarmctl agent agents-md apply [OPTIONS] --base ` + +###### **Arguments:** + +* `` — Agent name +* `` — The file holding the diff + +###### **Options:** + +* `--base ` — The revision you made the diff against, as `get` printed it. It's the git blob id of that text, so `git hash-object` of the unedited file prints it too +* `--controller-socket ` — swarm-controller's unix socket. + + Supplied by the nix module that installs this binary, from the same `socketPath` option the daemon binds; falls back to `SWARM_CONTROLLER_SOCKET`. + + + ## `swarmctl schedule` Manage scheduled prompts. diff --git a/swarm-controller-api/README.md b/swarm-controller-api/README.md index b8800a64..1ae12c8d 100644 --- a/swarm-controller-api/README.md +++ b/swarm-controller-api/README.md @@ -17,7 +17,8 @@ controller's `OpenAPI` spec. `swarmctl` leaves it off. ## What's here One module per endpoint family: `agents` (the responses of `POST /api/agents` -and `POST /api/agents/{name}/forge-token`), `forge` +and `POST /api/agents/{name}/forge-token`), `agents_md` +(`/api/agents/{name}/agents-md`), `forge` (`POST /api/forge/users/{name}/admin`) and `schedules` (`/api/schedules`). The agent-create request, inference and permissions bodies are still mirrored by hand in `swarmctl`. diff --git a/swarm-controller/README.md b/swarm-controller/README.md index 6ac96d37..346b0071 100644 --- a/swarm-controller/README.md +++ b/swarm-controller/README.md @@ -58,6 +58,12 @@ The swarm's control plane. The swarm UI and `swarmctl` are its clients. (`GET`/`PUT`/`DELETE /api/agents/{name}/limits`); the hive running the agent reads it, and each cap replaces the hive's own answer for that field → [`docs/scheduler/coordinator.md`](../docs/scheduler/coordinator.md#container-resource-limits). +- **Agent instructions** — each agent's `AGENTS.md` as its harness last + published it into the `agents-md` KV bucket + (`GET /api/agents/{name}/agents-md`), and edits to it as git diffs, which + this daemon hands to the agent's harness to apply + (`PUT /api/agents/{name}/agents-md`) → + [`docs/swarm/README.md`](../docs/swarm/README.md#an-agents-agentsmd). - **Review classifier** — try-outs from the swarm UI and reviews on review requests, and the newest 100 finished runs, one document in the `review-classifier-runs` KV bucket, which this daemon alone writes diff --git a/swarmctl/README.md b/swarmctl/README.md index b495a41c..eba5badc 100644 --- a/swarmctl/README.md +++ b/swarmctl/README.md @@ -7,8 +7,9 @@ that talk to the controller, from anywhere as your own SSO account (see Distinct from `hivectl`, which drives one hive's `hive-c0re` over its admin socket. This crate doesn't link `swarm-controller`, for the same -reason `hivectl` doesn't link `hive-c0re`; the forge and schedule bodies -and the agent responses both sides speak come from `swarm-controller-api`, +reason `hivectl` doesn't link `hive-c0re`; the forge, schedule and +`AGENTS.md` bodies and the agent responses both sides speak come from +`swarm-controller-api`, as `hivectl`'s come from `hive-host-sock`. ## Why root, and why no socket diff --git a/swarmctl/src/main.rs b/swarmctl/src/main.rs index d2dd9967..35352ca2 100644 --- a/swarmctl/src/main.rs +++ b/swarmctl/src/main.rs @@ -306,7 +306,7 @@ enum AgentVerb { /// Show an agent's AGENTS.md, or edit it with a diff. /// /// `get` shows the copy the agent last published, so it works while the - /// agent is stopped. `apply` hands the diff to the agent, which applies + /// agent isn't running. `apply` hands the diff to the agent, which applies /// it to the file with `git apply`; it needs the agent running. AgentsMd { #[command(subcommand)] @@ -333,7 +333,7 @@ struct AgentsMdApplyArgs { agent: String, /// The file holding the diff. diff_file: PathBuf, - /// The revision the diff was made against, as `get` printed it. It is + /// The revision you made the diff against, as `get` printed it. It's /// the git blob id of that text, so `git hash-object` of the unedited /// file prints it too. #[arg(long, value_name = "REVISION")]