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
boot by `hive-c0re::forge`. The provisioning flow is idempotent:
existing accounts + tokens are reused, so container destroy/recreate
doesn't lose forge identity. The token is written to
`hive-c0re::forge` reuses existing accounts + tokens, so container destroy/recreate
doesn't lose forge identity. It writes the token to
`<state>/forge-token` (one line, no trailing newline) inside the
agent container so `hive-forge` CLI + `forge_notify` poller can
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
fault-tolerance backstop) — but
`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
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
@ -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
finds nothing stale — the delivered threads are already read on forge.
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
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
the single `limit=UNREAD_FETCH_LIMIT` (50) fetch page, so it can never
exceed that many entries (a debug assertion pins the invariant; the
fetch limit and the bound are the same constant). A failed _delivery_
is left unread and out of the map, so it resurfaces next tick.
fetch limit and the bound are the same constant). `forge_notify` leaves
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.
### 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
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):
- **Self-authored comments / reviews** — comment payload's
`user.login` matches `own_login`.
- **Self-authored creations** (an agent opening its own PR/issue) — the
already-fetched subject payload's poster `user.login` matches
`own_login`. Only _creations_ are dropped; a later state change on the
agent's own subject is driven by someone else and still surfaces.
`own_login`. `forge_notify` drops only _creations_ this way; a later state
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
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
@ -246,7 +246,7 @@ not context (mara, triage: "just the issue title should be enough").
Review labels come from the Forgejo `state` field: `APPROVED`
`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
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
`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
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
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
pre-merge last comment the subject keeps — append it as a
`comment by <author>: <excerpt>` block before the meta suffix
(`fresh_post_close_comment_tail`). The wake carries **both** `[PR merged]`
and the racing comment. The kept pre-merge comment (created before
`closed_at`) is left off, a self-authored racing comment is dropped (don't
echo the agent's own write), and a missing/unparseable `created_at`/
`closed_at` appends nothing (conservative — only surface a comment we can
and the racing comment. `forge_notify` leaves the kept pre-merge comment (created before
`closed_at`) off, drops a self-authored racing comment (don't
echo the agent's own write), and appends nothing for a missing/unparseable `created_at`/
`closed_at` (conservative — only surface a comment we can
positively place after the close). Cost: one extra comment fetch on
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`
state only earns the `new <kind>` label when the notification's event
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
activity was without an extra reviews fetch. Missing/unparseable
timestamps default to `new` (preserve prior behavior rather than mask a
genuine new item). Timestamps are parsed by a small dependency-free
RFC 3339 helper (`parse_rfc3339`).
genuine new item). A small dependency-free
RFC 3339 helper (`parse_rfc3339`) parses timestamps.
Number is extracted from `subject.html_url`'s last path segment
(strips `#anchor` first); repo slug from `repository.full_name`.
`forge_notify` extracts the number from `subject.html_url`'s last path segment
(strips `#anchor` first), and the repo slug from `repository.full_name`.
Both degrade gracefully when absent (number → blank, repo → blank)
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`,
regardless of the Forgejo `reason` field. Forgejo doesn't reliably
set `reason == "review_requested"` (often null instead), so the
fallback checks the subject payload directly. Detection is gated on
`is_new` so the label only fires once on PR creation, not on every
fallback checks the subject payload directly. `is_new` gates detection
so the label only fires once on PR creation, not on every
subsequent comment.
### Subscription management
The poller does **not** autounsubscribe from repo watches — it
delivers every unread notification it's handed. Bounding the
firehose (dropping broad repo watches an agent doesn't need) is done
explicitly via a hive-forge CLI subscription verb, not by the poller
guessing which watches to drop. See the `subscription` verb in
delivers every unread notification it's handed. A hive-forge CLI
subscription verb bounds the firehose explicitly (dropping broad repo
watches an agent doesn't need) — the poller never guesses which
watches to drop. See the `subscription` verb in
[`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
[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
injected into the agent's state dir out of band.
tab and it works. No per-agent nix declaration, no rebuild — hive-c0re
injects the token into the agent's state dir out of band.
## Enabling
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
file.
@ -81,8 +81,8 @@ When enabled, the container gets:
Both scripts read the token from `<state>/github-token` **at invocation
time**, so a PAT written (or rotated) mid-session takes effect immediately
— no container rebuild or restart. Until the file exists, `gh` / `git push`
fail unauthenticated. The token path is baked into the scripts at
build time (not read from an env var), because claude's Bash tool runs in a
fail unauthenticated. The build bakes the token path into the scripts
(rather than reading it from an env var), because claude's Bash tool runs in a
minimal environment that wouldn't carry one.
### 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
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`,
`subject {title,url,latest_comment_url}` and `updated_at` line up field
for field. The two real differences are absorbed by lenient
deserializers — GitHub sends the thread id as a _string_ where Forgejo
sends a number, and says `PullRequest` where Forgejo says `Pull`. Todo
keys are prefixed `gh:<id>` so a github thread id can't collide with a
for field. Lenient deserializers absorb the two real differences —
GitHub sends the thread id as a _string_ where Forgejo
sends a number, and says `PullRequest` where Forgejo says `Pull`.
`hive-github-notify` prefixes todo keys `gh:<id>` so a github thread id can't collide with a
forge one.
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;
only the `core` account has push access, for autoseeding.
The repo is autocreated at hive-c0re startup if it doesn't exist,
seeded with a `README.md` containing a contribution guide and a
hive-c0re autocreates the repo at startup if it doesn't exist,
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
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
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
reconnect — the periodic pull below is what closes that gap. One
A hive that's offline when the swarm controller sends the event
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
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
the same broadcast mechanism used for other hive-wide notices — inbox
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
blocks the pull itself.
broadcast" hint. hive-c0re logs a diff or per-agent send failure
without blocking the pull itself.
The local clone is created (or refreshed) once at startup via
`knowledge::ensure_local_clone`. If the repo is brand new and
`knowledge::ensure_local_clone` creates (or refreshes) the local
clone once at startup. If the repo is brand new and
empty, `ensure_local_clone` seeds it with the default README
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
required.
1. Clone the repo (credentials are injected automatically; the `-r`
flag selects the repo, the clone lands in `./knowledge`):
1. Clone the repo (`hive-forge` injects credentials automatically;
the `-r` flag selects the repo, the clone lands in `./knowledge`):
```sh
hive-forge -r internal/knowledge clone

View file

@ -49,8 +49,8 @@ homeserver.
⚠️ **they're still not interchangeable, and the difference is the
cost of changing one.** `gatewayHost` is a routing detail clients
rediscover through `.well-known`, so it's safe to move on a running
deployment. `serverName` is baked into every user and room id, so
adopting a new one does **not** rename the existing users and rooms —
deployment. The matrix id format bakes `serverName` into every user and
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
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
immediately after writing the token so the daemon picks up the
new credential without waiting for a full container restart. If
the restart fails (for example daemon not yet running on first boot) the
error is logged as a warning and the `.path`-trigger sibling
the restart fails (for example daemon not yet running on first boot)
hive-c0re logs the error as a warning and the `.path`-trigger sibling
(`hive-matrix-daemon.path` watching for `matrix-token` appearance)
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
wired up later by extending `trustedServers` without a homeserver
restart. `trusted_servers = []` keeps it effectively closed
until peers are listed.
until you list peers.
- `allow_registration = true` (required for the token flow to
engage). The absent
`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
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
this homeserver doesn't permit room encryption. Crypto keys are stored
in the per-agent sqlite store under the state dir and persist across
this homeserver doesn't permit room encryption. matrix-sdk stores
crypto keys in the per-agent sqlite store under the state dir; they persist across
restarts (lost on `--purge`). `read_room` decrypts via
`room.messages()` — UTD events surface as `event_type =
"m.room.encrypted"` with `body = "[unable to decrypt]"`.
@ -159,7 +159,7 @@ Initial rollout settings:
## 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
account (`@hive:<server_name>`) and invites every provisioned agent
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
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):
- `space-room-id` — the Space itself
- `chat-room-id` — the `hive-chat` room
These paths are **outside** every agent state dir and are NOT deleted by
`nixos-container destroy --purge` — both survive full agent purges and
are reused on re-provision.
`nixos-container destroy --purge` — both survive full agent purges, and
hive-c0re reuses them on re-provision.
**Idempotent**: if the files exist and are non-empty, the Space and
room are considered already created. Delete the files to force
**Idempotent**: if the files exist and are non-empty, hive-c0re considers the Space and
room already created. Delete the files to force
re-creation (for example after a homeserver wipe).
## Configuration tuning
@ -219,7 +219,7 @@ surprising behaviour:
empty string renders `.<hive>`-shaped garbage in both nginx
`server_name` (treated as wildcard catch-all, surprising) and
`/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
homeserver at all rather than of a setting: