diff --git a/docs/integrations/forge.md b/docs/integrations/forge.md index 546bc764..b410972e 100644 --- a/docs/integrations/forge.md +++ b/docs/integrations/forge.md @@ -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 `/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 : ` 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 ` 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 ` +subject's `created_at`. `forge_notify` labels anything later `activity on ` — 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). diff --git a/docs/integrations/github.md b/docs/integrations/github.md index 9d3a6061..bb8faa6b 100644 --- a/docs/integrations/github.md +++ b/docs/integrations/github.md @@ -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 `/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:` 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:` so a github thread id can't collide with a forge one. Two host differences worth knowing before touching this code: diff --git a/docs/integrations/knowledge.md b/docs/integrations/knowledge.md index ce9ea66a..e21c6cd6 100644 --- a/docs/integrations/knowledge.md +++ b/docs/integrations/knowledge.md @@ -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 diff --git a/docs/integrations/matrix.md b/docs/integrations/matrix.md index df30f75b..a0370ed1 100644 --- a/docs/integrations/matrix.md +++ b/docs/integrations/matrix.md @@ -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:`) 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 `.`-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: