docs: fix genuine passive-voice hits in docs/integrations

Sixth batch of the ongoing write-good.Passive pass (hyperhive#4042):
read all 52 hits across knowledge.md/github.md/matrix.md/forge.md in
context and rewrote 39 with a clearly nameable actor -- mostly
hive-c0re, forge_notify, or a specific fn named right there or a
sentence or two earlier. forge.md's notification poller is the
densest yet (19/20 hits rewritten): forge_notify is established as
the section's sole actor early and reused throughout, the shape
that's produced the highest catch rates all along.

Left 13 alone: the "no X is needed" negative-capability idiom (x2),
a container-lifecycle state descriptor ("when container is stopped"),
a false-positive tokenization ("read-only" split across a line wrap,
vale matches "is read" inside it -- not a real passive at all), the
"X can't be Yed" idiom, a generic "before the ids are minted" timing
clause with no natural actor to name, a room-join policy-state
descriptor, an "is enabled"/"is trusted" pair describing a config/
trust state (predicate-adjective-copula bucket, same family as
"is privileged" from an earlier batch), three "**X is required**"
bolded requirement-list labels (structural convention, not really
mid-sentence passives), and a contrastive "are shared" clause
mirrored against an active sibling clause exactly like
claude-invocation.md's "everything else is shared" from the
turn-loop batch -- left alone there for the same reason.

One sibling-inconsistency catch worth flagging: forge.md's merge-
racing-comment paragraph had two passive clauses ("is left off",
"is dropped") sitting next to a third, already-active clause
("appends nothing") in the same three-item parallel list -- rewrote
all three under one active subject (forge_notify) for consistency.

Verified via vale before/after: 52 -> 13 write-good.Passive hits,
exactly the 13 left alone above; error count and other warning
categories unchanged (still on TooWordy since #4097 hasn't merged to
this branch yet). Re-read every changed line in full surrounding
context after editing, matching the diff to intent before running
the final vale check.
This commit is contained in:
iris 2026-09-08 12:46:08 +02:00 committed by mara
commit 060f325716
4 changed files with 62 additions and 62 deletions

View file

@ -48,8 +48,8 @@ hive-c0re to re-mint with the new scopes.
Each agent gets its own Forgejo user + access token, provisioned at Each agent gets its own Forgejo user + access token, provisioned at
boot by `hive-c0re::forge`. The provisioning flow is idempotent: boot by `hive-c0re::forge`. The provisioning flow is idempotent:
existing accounts + tokens are reused, so container destroy/recreate `hive-c0re::forge` reuses existing accounts + tokens, so container destroy/recreate
doesn't lose forge identity. The token is written to doesn't lose forge identity. It writes the token to
`<state>/forge-token` (one line, no trailing newline) inside the `<state>/forge-token` (one line, no trailing newline) inside the
agent container so `hive-forge` CLI + `forge_notify` poller can agent container so `hive-forge` CLI + `forge_notify` poller can
read it without touching c0re's host-side credential store. read it without touching c0re's host-side credential store.
@ -63,7 +63,7 @@ Two things live in the `agent-configs` Forgejo organization:
`hive-c0re/src/forge/config_pr_poll.rs` re-scans every 5 minutes as a `hive-c0re/src/forge/config_pr_poll.rs` re-scans every 5 minutes as a
fault-tolerance backstop) — but fault-tolerance backstop) — but
`main` is branch-protected core-only: only hive-c0re's verify-and-ff-push `main` is branch-protected core-only: only hive-c0re's verify-and-ff-push
merge handler lands on `main`, an operator-team approval is required, and merge handler lands on `main`, the operator team must approve first, and
the agent can neither push `main` directly nor self-merge. `main` is the agent can neither push `main` directly nor self-merge. `main` is
fast-forward-only — hive-c0re never force-pushes (the merge handler's ff fast-forward-only — hive-c0re never force-pushes (the merge handler's ff
push lands fine; the `push_config` mirror pushes `main` + the add-only push lands fine; the `push_config` mirror pushes `main` + the add-only
@ -126,7 +126,7 @@ That size property is the whole point. A container rebuild starts the
poller with no memory of what it delivered, re-scans `?all=false`, and poller with no memory of what it delivered, re-scans `?all=false`, and
finds nothing stale — the delivered threads are already read on forge. finds nothing stale — the delivered threads are already read on forge.
Forge's own read-state is thus the durable, cross-rebuild record of Forge's own read-state is thus the durable, cross-rebuild record of
what's been delivered; there is **no persisted cursor**. what `forge_notify` has delivered; there is **no persisted cursor**.
**Gotcha:** don't reintroduce a persisted dedup cursor here. A design **Gotcha:** don't reintroduce a persisted dedup cursor here. A design
that leaves threads unread and tracks delivery via a separately persisted that leaves threads unread and tracks delivery via a separately persisted
@ -149,10 +149,10 @@ re-fire the wake. it's **not persisted** and resets on restart (forge
read-state covers the durable case). Each poll prunes it to the ids in read-state covers the durable case). Each poll prunes it to the ids in
the single `limit=UNREAD_FETCH_LIMIT` (50) fetch page, so it can never the single `limit=UNREAD_FETCH_LIMIT` (50) fetch page, so it can never
exceed that many entries (a debug assertion pins the invariant; the exceed that many entries (a debug assertion pins the invariant; the
fetch limit and the bound are the same constant). A failed _delivery_ fetch limit and the bound are the same constant). `forge_notify` leaves
is left unread and out of the map, so it resurfaces next tick. a failed _delivery_ unread and out of the map, so it resurfaces next tick.
Self-echo notifications (the agent's own writes, see below) are marked `forge_notify` marks self-echo notifications (the agent's own writes, see below)
read directly without a delivery — same `mark_read` call, no wake. read directly without a delivery — same `mark_read` call, no wake.
### Activation gates (graceful no-ops) ### Activation gates (graceful no-ops)
@ -176,17 +176,17 @@ best-effort: logged at debug/warn and retried next tick.
Forgejo fires notifications for the agent's own actions (it opened a Forgejo fires notifications for the agent's own actions (it opened a
PR, posted a comment, submitted a review). Surfacing those would PR, posted a comment, submitted a review). Surfacing those would
loop claude on its own writes. The comment/review case is dropped loop claude on its own writes. `forge_notify` drops the comment/review case
silently (mark-read without delivery): silently (mark-read without delivery):
- **Self-authored comments / reviews** — comment payload's - **Self-authored comments / reviews** — comment payload's
`user.login` matches `own_login`. `user.login` matches `own_login`.
- **Self-authored creations** (an agent opening its own PR/issue) — the - **Self-authored creations** (an agent opening its own PR/issue) — the
already-fetched subject payload's poster `user.login` matches already-fetched subject payload's poster `user.login` matches
`own_login`. Only _creations_ are dropped; a later state change on the `own_login`. `forge_notify` drops only _creations_ this way; a later state
agent's own subject is driven by someone else and still surfaces. change on the agent's own subject comes from someone else, so it still surfaces.
`own_login` is fetched at startup via `GET /api/v1/user`. On fetch `forge_notify` fetches `own_login` at startup via `GET /api/v1/user`. On fetch
failure the filter degrades open (no filtering) rather than crashing failure the filter degrades open (no filtering) rather than crashing
the task — a noisy inbox beats a silently stuck poller — but the fetch the task — a noisy inbox beats a silently stuck poller — but the fetch
is **re-attempted on each poll tick** until it succeeds, so a boot-time is **re-attempted on each poll tick** until it succeeds, so a boot-time
@ -246,7 +246,7 @@ not context (mara, triage: "just the issue title should be enough").
Review labels come from the Forgejo `state` field: `APPROVED` Review labels come from the Forgejo `state` field: `APPROVED`
`approved`, `REQUEST_CHANGES``changes requested`, `COMMENT` `approved`, `REQUEST_CHANGES``changes requested`, `COMMENT`
`review comment`. `PENDING` is dropped (review saved but not `review comment`. `forge_notify` drops `PENDING` (review saved but not
submitted yet — no peer-visible event). Unknown states fall back to submitted yet — no peer-visible event). Unknown states fall back to
the generic comment wrapper. the generic comment wrapper.
@ -276,17 +276,17 @@ hidden behind a stale comment.
The one gap the timestamp cut leaves: a genuine comment posted **within The one gap the timestamp cut leaves: a genuine comment posted **within
`NEW_ITEM_TOLERANCE_SECS` of the merge** bumps `updated_at` close enough `NEW_ITEM_TOLERANCE_SECS` of the merge** bumps `updated_at` close enough
to `closed_at` that `state_change_is_current` returns `true` — so it takes to `closed_at` that `state_change_is_current` returns `true` — so it takes
the state-change path and its body would be dropped. Best of both worlds: the state-change path, dropping its body. Best of both worlds:
on the merge/close path we fetch the `latest_comment_url` comment and, when on the merge/close path we fetch the `latest_comment_url` comment and, when
its `created_at` is strictly **after** the subject's `closed_at` its `created_at` is strictly **after** the subject's `closed_at`
(`comment_is_after_close`) — that is it raced the merge rather than being the (`comment_is_after_close`) — that is it raced the merge rather than being the
pre-merge last comment the subject keeps — append it as a pre-merge last comment the subject keeps — append it as a
`comment by <author>: <excerpt>` block before the meta suffix `comment by <author>: <excerpt>` block before the meta suffix
(`fresh_post_close_comment_tail`). The wake carries **both** `[PR merged]` (`fresh_post_close_comment_tail`). The wake carries **both** `[PR merged]`
and the racing comment. The kept pre-merge comment (created before and the racing comment. `forge_notify` leaves the kept pre-merge comment (created before
`closed_at`) is left off, a self-authored racing comment is dropped (don't `closed_at`) off, drops a self-authored racing comment (don't
echo the agent's own write), and a missing/unparseable `created_at`/ echo the agent's own write), and appends nothing for a missing/unparseable `created_at`/
`closed_at` appends nothing (conservative — only surface a comment we can `closed_at` (conservative — only surface a comment we can
positively place after the close). Cost: one extra comment fetch on positively place after the close). Cost: one extra comment fetch on
merge/close notifications, acceptable given how rare they are. merge/close notifications, acceptable given how rare they are.
@ -299,15 +299,15 @@ so it misses the comment path and lands on the state-change path with
original open notification and miss the review. The `open` original open notification and miss the review. The `open`
state only earns the `new <kind>` label when the notification's event state only earns the `new <kind>` label when the notification's event
time (`updated_at`) is within `NEW_ITEM_TOLERANCE_SECS` (120s) of the time (`updated_at`) is within `NEW_ITEM_TOLERANCE_SECS` (120s) of the
subject's `created_at`. Anything later is labeled `activity on <kind>` subject's `created_at`. `forge_notify` labels anything later `activity on <kind>`
— neutral and non-misleading, since we can't cheaply say _what_ the — neutral and non-misleading, since we can't cheaply say _what_ the
activity was without an extra reviews fetch. Missing/unparseable activity was without an extra reviews fetch. Missing/unparseable
timestamps default to `new` (preserve prior behavior rather than mask a timestamps default to `new` (preserve prior behavior rather than mask a
genuine new item). Timestamps are parsed by a small dependency-free genuine new item). A small dependency-free
RFC 3339 helper (`parse_rfc3339`). RFC 3339 helper (`parse_rfc3339`) parses timestamps.
Number is extracted from `subject.html_url`'s last path segment `forge_notify` extracts the number from `subject.html_url`'s last path segment
(strips `#anchor` first); repo slug from `repository.full_name`. (strips `#anchor` first), and the repo slug from `repository.full_name`.
Both degrade gracefully when absent (number → blank, repo → blank) Both degrade gracefully when absent (number → blank, repo → blank)
so unexpected Forgejo shapes don't crash the formatter. so unexpected Forgejo shapes don't crash the formatter.
@ -326,15 +326,15 @@ For new PRs, the kind label flips to `[review requested #N
owner/repo]` when `own_login` appears in `requested_reviewers`, owner/repo]` when `own_login` appears in `requested_reviewers`,
regardless of the Forgejo `reason` field. Forgejo doesn't reliably regardless of the Forgejo `reason` field. Forgejo doesn't reliably
set `reason == "review_requested"` (often null instead), so the set `reason == "review_requested"` (often null instead), so the
fallback checks the subject payload directly. Detection is gated on fallback checks the subject payload directly. `is_new` gates detection
`is_new` so the label only fires once on PR creation, not on every so the label only fires once on PR creation, not on every
subsequent comment. subsequent comment.
### Subscription management ### Subscription management
The poller does **not** autounsubscribe from repo watches — it The poller does **not** autounsubscribe from repo watches — it
delivers every unread notification it's handed. Bounding the delivers every unread notification it's handed. A hive-forge CLI
firehose (dropping broad repo watches an agent doesn't need) is done subscription verb bounds the firehose explicitly (dropping broad repo
explicitly via a hive-forge CLI subscription verb, not by the poller watches an agent doesn't need) — the poller never guesses which
guessing which watches to drop. See the `subscription` verb in watches to drop. See the `subscription` verb in
[`docs/tools/forge.md`](../tools/forge.md). [`docs/tools/forge.md`](../tools/forge.md).

View file

@ -7,13 +7,13 @@ HTTPS, both authenticated by an operator-supplied personal access token
Provisioning is UI-driven, mirroring the dashboard side of the Provisioning is UI-driven, mirroring the dashboard side of the
[matrix account](matrix.md) flow: paste a PAT into the agent's credentials [matrix account](matrix.md) flow: paste a PAT into the agent's credentials
tab and it works. No per-agent nix declaration, no rebuild — the token is tab and it works. No per-agent nix declaration, no rebuild — hive-c0re
injected into the agent's state dir out of band. injects the token into the agent's state dir out of band.
## Enabling ## Enabling
The integration is **on by default** for every agent (`hyperhive.github.enable The integration is **on by default** for every agent (`hyperhive.github.enable
= true`), inert until a PAT is provisioned. No per-agent declaration is = true`), inert until the operator provisions a PAT. No per-agent declaration is
needed — an agent gains GitHub by having a PAT written to its token needed — an agent gains GitHub by having a PAT written to its token
file. file.
@ -81,8 +81,8 @@ When enabled, the container gets:
Both scripts read the token from `<state>/github-token` **at invocation Both scripts read the token from `<state>/github-token` **at invocation
time**, so a PAT written (or rotated) mid-session takes effect immediately time**, so a PAT written (or rotated) mid-session takes effect immediately
— no container rebuild or restart. Until the file exists, `gh` / `git push` — no container rebuild or restart. Until the file exists, `gh` / `git push`
fail unauthenticated. The token path is baked into the scripts at fail unauthenticated. The build bakes the token path into the scripts
build time (not read from an env var), because claude's Bash tool runs in a (rather than reading it from an env var), because claude's Bash tool runs in a
minimal environment that wouldn't carry one. minimal environment that wouldn't carry one.
### Notifications ### Notifications
@ -108,13 +108,13 @@ logs why and exits 0, which is why it's `Restart = on-failure` and
never `always` — a clean exit on a PAT-less agent must not become a never `always` — a clean exit on a PAT-less agent must not become a
restart loop. restart loop.
Forgejo's notifications API is modelled on GitHub's, so one tolerant Forgejo models its notifications API on GitHub's, so one tolerant
parse serves both: `id`, `repository.full_name`, parse serves both: `id`, `repository.full_name`,
`subject {title,url,latest_comment_url}` and `updated_at` line up field `subject {title,url,latest_comment_url}` and `updated_at` line up field
for field. The two real differences are absorbed by lenient for field. Lenient deserializers absorb the two real differences —
deserializers — GitHub sends the thread id as a _string_ where Forgejo GitHub sends the thread id as a _string_ where Forgejo
sends a number, and says `PullRequest` where Forgejo says `Pull`. Todo sends a number, and says `PullRequest` where Forgejo says `Pull`.
keys are prefixed `gh:<id>` so a github thread id can't collide with a `hive-github-notify` prefixes todo keys `gh:<id>` so a github thread id can't collide with a
forge one. forge one.
Two host differences worth knowing before touching this code: Two host differences worth knowing before touching this code:

View file

@ -28,8 +28,8 @@ repo `knowledge`). The repo is public, so every agent's forge account
has read access without an explicit per-agent collaborator grant; has read access without an explicit per-agent collaborator grant;
only the `core` account has push access, for autoseeding. only the `core` account has push access, for autoseeding.
The repo is autocreated at hive-c0re startup if it doesn't exist, hive-c0re autocreates the repo at startup if it doesn't exist,
seeded with a `README.md` containing a contribution guide and a seeding it with a `README.md` containing a contribution guide and a
blank table of contents. Add an entry to that ToC each time you blank table of contents. Add an entry to that ToC each time you
create a new document. create a new document.
@ -44,8 +44,8 @@ hive-c0re maintains the local clone at
event to every hive over the swarm queue and each hive runs `git event to every hive over the swarm queue and each hive runs `git
pull`, so agents see the new content on their next turn. pull`, so agents see the new content on their next turn.
A hive that's offline when the event is sent doesn't get it on A hive that's offline when the swarm controller sends the event
reconnect — the periodic pull below is what closes that gap. One doesn't get it on reconnect — the periodic pull below is what closes that gap. One
hive briefly showing older `/knowledge` content than another is hive briefly showing older `/knowledge` content than another is
expected, and resolves by itself within the fallback interval. expected, and resolves by itself within the fallback interval.
@ -76,11 +76,11 @@ sender `system`, body `[system] /knowledge updated:` followed by a
"see the repo" fallback if computing the diff itself fails). This is "see the repo" fallback if computing the diff itself fails). This is
the same broadcast mechanism used for other hive-wide notices — inbox the same broadcast mechanism used for other hive-wide notices — inbox
message only, no forced wake, and it carries the standard "this was a message only, no forced wake, and it carries the standard "this was a
broadcast" hint. A diff or per-agent send failure is logged but never broadcast" hint. hive-c0re logs a diff or per-agent send failure
blocks the pull itself. without blocking the pull itself.
The local clone is created (or refreshed) once at startup via `knowledge::ensure_local_clone` creates (or refreshes) the local
`knowledge::ensure_local_clone`. If the repo is brand new and clone once at startup. If the repo is brand new and
empty, `ensure_local_clone` seeds it with the default README empty, `ensure_local_clone` seeds it with the default README
before returning. before returning.
@ -105,8 +105,8 @@ write access, so they can't push a branch directly. The supported path
is Forgejo's **AGit flow** through the `hive-forge` CLI — no fork is Forgejo's **AGit flow** through the `hive-forge` CLI — no fork
required. required.
1. Clone the repo (credentials are injected automatically; the `-r` 1. Clone the repo (`hive-forge` injects credentials automatically;
flag selects the repo, the clone lands in `./knowledge`): the `-r` flag selects the repo, the clone lands in `./knowledge`):
```sh ```sh
hive-forge -r internal/knowledge clone hive-forge -r internal/knowledge clone

View file

@ -49,8 +49,8 @@ homeserver.
⚠️ **they're still not interchangeable, and the difference is the ⚠️ **they're still not interchangeable, and the difference is the
cost of changing one.** `gatewayHost` is a routing detail clients cost of changing one.** `gatewayHost` is a routing detail clients
rediscover through `.well-known`, so it's safe to move on a running rediscover through `.well-known`, so it's safe to move on a running
deployment. `serverName` is baked into every user and room id, so deployment. The matrix id format bakes `serverName` into every user and
adopting a new one does **not** rename the existing users and rooms — room id, so adopting a new one does **not** rename the existing users and rooms —
it strands them, because their ids still name a homeserver that no it strands them, because their ids still name a homeserver that no
longer answers. longer answers.
@ -125,8 +125,8 @@ see it. The agent only receives the resulting `access_token`.
6. **hive-c0re restarts `hive-matrix-daemon`** for the agent 6. **hive-c0re restarts `hive-matrix-daemon`** for the agent
immediately after writing the token so the daemon picks up the immediately after writing the token so the daemon picks up the
new credential without waiting for a full container restart. If new credential without waiting for a full container restart. If
the restart fails (for example daemon not yet running on first boot) the the restart fails (for example daemon not yet running on first boot)
error is logged as a warning and the `.path`-trigger sibling hive-c0re logs the error as a warning and the `.path`-trigger sibling
(`hive-matrix-daemon.path` watching for `matrix-token` appearance) (`hive-matrix-daemon.path` watching for `matrix-token` appearance)
brings the daemon up on the same boot cycle anyway. brings the daemon up on the same boot cycle anyway.
@ -135,7 +135,7 @@ Initial rollout settings:
- `allow_federation = true` at the protocol level so swarms can be - `allow_federation = true` at the protocol level so swarms can be
wired up later by extending `trustedServers` without a homeserver wired up later by extending `trustedServers` without a homeserver
restart. `trusted_servers = []` keeps it effectively closed restart. `trusted_servers = []` keeps it effectively closed
until peers are listed. until you list peers.
- `allow_registration = true` (required for the token flow to - `allow_registration = true` (required for the token flow to
engage). The absent engage). The absent
`yes_i_am_very_very_sure_…_open_registration_…` flag keeps the `yes_i_am_very_very_sure_…_open_registration_…` flag keeps the
@ -148,8 +148,8 @@ Initial rollout settings:
homeserver admin. **The agent matrix client always supports decryption homeserver admin. **The agent matrix client always supports decryption
regardless of this flag** — it uses the `e2e-encryption` feature of regardless of this flag** — it uses the `e2e-encryption` feature of
`matrix-sdk` so it can read encrypted rooms it's invited to even when `matrix-sdk` so it can read encrypted rooms it's invited to even when
this homeserver doesn't permit room encryption. Crypto keys are stored this homeserver doesn't permit room encryption. matrix-sdk stores
in the per-agent sqlite store under the state dir and persist across crypto keys in the per-agent sqlite store under the state dir; they persist across
restarts (lost on `--purge`). `read_room` decrypts via restarts (lost on `--purge`). `read_room` decrypts via
`room.messages()` — UTD events surface as `event_type = `room.messages()` — UTD events surface as `event_type =
"m.room.encrypted"` with `body = "[unable to decrypt]"`. "m.room.encrypted"` with `body = "[unable to decrypt]"`.
@ -159,7 +159,7 @@ Initial rollout settings:
## Hive Matrix Space ## Hive Matrix Space
On first boot (after all agent accounts are provisioned), hive-c0re On first boot, after hive-c0re provisions all agent accounts, it
creates a private **Matrix Space** named `"hive"` using the admin creates a private **Matrix Space** named `"hive"` using the admin
account (`@hive:<server_name>`) and invites every provisioned agent account (`@hive:<server_name>`) and invites every provisioned agent
into it. This gives the operator a single Space in FluffyChat or any into it. This gives the operator a single Space in FluffyChat or any
@ -174,17 +174,17 @@ restricted (any Space member including the operator can join; agents
are explicitly invited). Room version pinned to 10 for the restricted are explicitly invited). Room version pinned to 10 for the restricted
join floor. join floor.
**State**: both room IDs are persisted to `/var/lib/hyperhive/matrix/` **State**: hive-c0re persists both room IDs to `/var/lib/hyperhive/matrix/`
(mode `0600`, owned by the hive-c0re service user): (mode `0600`, owned by the hive-c0re service user):
- `space-room-id` — the Space itself - `space-room-id` — the Space itself
- `chat-room-id` — the `hive-chat` room - `chat-room-id` — the `hive-chat` room
These paths are **outside** every agent state dir and are NOT deleted by These paths are **outside** every agent state dir and are NOT deleted by
`nixos-container destroy --purge` — both survive full agent purges and `nixos-container destroy --purge` — both survive full agent purges, and
are reused on re-provision. hive-c0re reuses them on re-provision.
**Idempotent**: if the files exist and are non-empty, the Space and **Idempotent**: if the files exist and are non-empty, hive-c0re considers the Space and
room are considered already created. Delete the files to force room already created. Delete the files to force
re-creation (for example after a homeserver wipe). re-creation (for example after a homeserver wipe).
## Configuration tuning ## Configuration tuning
@ -219,7 +219,7 @@ surprising behaviour:
empty string renders `.<hive>`-shaped garbage in both nginx empty string renders `.<hive>`-shaped garbage in both nginx
`server_name` (treated as wildcard catch-all, surprising) and `server_name` (treated as wildcard catch-all, surprising) and
`/etc/hosts` (invalid entry). `null` is the right opt-out shape; `/etc/hosts` (invalid entry). `null` is the right opt-out shape;
empty string is rejected explicitly. a config assertion rejects empty string explicitly.
SSO is unconditional, so the three below are requirements of running a SSO is unconditional, so the three below are requirements of running a
homeserver at all rather than of a setting: homeserver at all rather than of a setting: