Watch
0
0
Fork
You've already forked hyperhive
0

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.
This commit is contained in:
atlas 2026-10-18 03:37:27 +02:00 • committed by mara
commit 9add99838c
8 changed files with 124 additions and 13 deletions

View file

@ -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

View file

@ -276,7 +276,10 @@ Under `/var/lib/hyperhive/agents/<name>/`:
`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/<name>/harness`
inside the container (`$HYPERHIVE_HARNESS_DIR`). Contents:

View file

@ -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
`<agent>`, 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.<agent>` 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/<name>/icon` serves
`$KV.agent-icons.<agent>` 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/<name>/icon` serves
the stored bytes, and 404 means the agent has no icon. swarm-ui's agent cards
load it as an `<img>` 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 `<agent>`, writable by the agent's own credential alone
(`$KV.agents-md.<agent>`). 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.
</details>
### 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/<name>/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/<name>/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.<name>`, 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

View file

@ -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 <COMMAND>`
###### **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] <AGENT>`
###### **Arguments:**
* `<AGENT>` — Agent name
###### **Options:**
* `--controller-socket <PATH>` — 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 <REVISION> <AGENT> <DIFF_FILE>`
###### **Arguments:**
* `<AGENT>` — Agent name
* `<DIFF_FILE>` — The file holding the diff
###### **Options:**
* `--base <REVISION>` — 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 <PATH>` — 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.

View file

@ -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`.

View file

@ -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

View file

@ -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

View file

@ -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")]