diff --git a/docs/content/crux/readme.md b/docs/content/crux/readme.md index 69b2f03b..f5535c64 100644 --- a/docs/content/crux/readme.md +++ b/docs/content/crux/readme.md @@ -56,16 +56,30 @@ Or download a release archive from the [releases page](https://github.com/hivecommons/crux/releases). There is one for darwin and linux, each for amd64 and arm64. The checksums are signed with cosign. cosign is a tool that proves a file was published by -this repo's release workflow and not changed since. To check: +this repo's release workflow and not changed since. That proof only holds +when you tell cosign which signer to expect, so the identity and issuer +flags below are required: cosign refuses to run without them, and a +wildcard would accept a signature from anyone. To check: ```sh # from the v0.1.0 release page, download these three files first -cosign verify-blob --bundle checksums.txt.sigstore.json checksums.txt +cosign verify-blob checksums.txt \ + --bundle checksums.txt.sigstore.json \ + --certificate-identity-regexp '^https://github\.com/hivecommons/crux/\.github/workflows/release\.yml@' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com sha256sum -c checksums.txt --ignore-missing tar xzf crux_0.1.0_linux_amd64.tar.gz ./crux --version ``` +The container image is signed the same way: + +```sh +cosign verify ghcr.io/hivecommons/crux:0.1.0 \ + --certificate-identity-regexp '^https://github\.com/hivecommons/crux/\.github/workflows/release\.yml@' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + The agent CLIs themselves (for example `copilot` or `claude`) are not included. Install and log in to the one you want to run first, and make sure `tmux` is on your PATH. @@ -77,7 +91,7 @@ Make a folder with one definition file: ```sh mkdir -p defs cat > defs/scout.yaml <<'YAML' -apiVersion: hive.kubestellar.io/v1 +apiVersion: hive.hivecommons.dev/v1 kind: AgentDefinition metadata: name: scout @@ -186,6 +200,196 @@ Progress is tracked in The design record is [`src/docs/design/crux.md`](https://github.com/hivecommons/hive/blob/v5/src/docs/design/crux.md). +## What crux exposes + +Crux has three layers you can build on. Each one is a Go package in this +repository. The first is the Go interface. The second is the same thing +over HTTP. The third is what crux asks the host for in return. + +### 1. `crux.Runtime`, the Go interface + +`crux.Runtime` lives in [`runtime.go`](https://github.com/hivecommons/crux/blob/main/runtime.go). Today it runs in the +same process as the host. After stage 5 of the sidecar work it can also be +backed by the API in layer 2, with no change to the caller. It is made of +eleven smaller interfaces, one per job: + +| Role | Methods | +|---|---| +| `Lifecycle` | `Start`, `Stop`, `Restart`, `RestartWithBootstrap`, `Pause`, `PauseBy`, `Resume`, `IsPaused`, `ResetRestartCount` | +| `Observer` | `GetStatus`, `GetStatusFast`, `AllStatuses`, `ResolveAgent`, `GetOutput`, `GetBufferOutput`, `CaptureFullLog`, `FullLogHeader` | +| `Kicker` | `SendKick`, `SendKickAsync`, `ValidateKick`, `KickDispatchState`, `ListKickLogs`, `ReadKickLog`, `SetKickObserver` | +| `Definitions` | `AddAgent`, `RemoveAgent`, `UpdateConfig`, `ReconcileAgents`, `SetModelOverride`, `SetBackendOverride`, `PinCLI`, `PinModel`, `UnpinCLI`, `UnpinModel`, `ClearModeOverrides`, `SyncModeFiles`, `RefreshInferenceRoutes` | +| `Fleet` | `BreakerState`, `EngageBreaker`, `ReleaseBreaker`, `SetACMMLevel`, `GetACMMLevel` (the fleet trust level), `RelaunchBobAgentsAwaitingKey`, `SharedDevUID` | +| `Credentials` | `SubmitLoginCode`, `CopilotToken`, `CopilotTokenSource`, `SetCopilotToken`, `ActivateCopilotToken`, `ReloadClaudeToken`, `ClaudeCredentialCandidatePaths` | +| `Resumer` | `ResumeHandle`, `SendResumeKick`, `SessionID`, `SessionMissing` | +| `Supervisor` | `GovernorMayStart`, `MarkStartupLaunchQueued`, `CheckAndRestartCrashedAgents`, `ConsentWedgedAgents`, `RefreshAgentTokens`, `RefreshAgentTokenFor`, `AgentCredentialRefreshable`, `AgentHasValidCredential`, `AgentCapabilities`, `EffectiveBackend`, `InvocationMetadata`, `RestartTelemetry`, `StartFailureState`, `ProviderErrorBackoffRemaining`, `PauseByCause`, `ArchiveAllKickLogs`, `CountAgentsWithModel`, `CrashOOMSuspected`, `BackendAuthAvailable`, `AgentAuthAvailable`, `SendGitHubAPIBudgetNudge`, `RecordAudit`, `TrajectoryAgents` | +| `Restorer` | `SeedPauseState`, `SeedRestartTelemetry`, `SeedTurnLoss`, `SeedLastKick`, `SeedKickHistory`, `RestoreBreaker` | +| `Background` | `StartAgentTokenRefresh`, `StartCredentialWatchdog`, `StartCopilotSessionRefresh`, `StartTranscriptActivityRefresh` | +| `Authorizer` | `AuthorizePROpen`, `AuthorizePushBranch`, `AuthorizeIssueOpen`, `AuthorizeMerge`, `AuthorizeReviewRequest` | + +In short: `Lifecycle` starts and stops agents. `Observer` reads their state +and screen. `Kicker` sends them work. `Definitions` edits what they are. +`Fleet` acts on all of them at once. `Credentials` handles logins. +`Resumer` picks a past session back up. `Supervisor` is the crash, OOM and +token-refresh loop, plus the telemetry and audit the host reads. +`Restorer` reloads saved state after a restart. `Background` starts the +long-running refresh loops. `Authorizer` says yes or no before an agent +opens a PR, pushes, opens an issue, merges, or asks for review. + +You get one from `agent.AsRuntime(agent.NewManager(...))`. See +[`docs/MODULE.md`](https://github.com/hivecommons/crux/blob/main/docs/MODULE.md) for a full example. + +### 2. The wire API + +`crux serve` puts the same runtime behind HTTP+JSON under `/v1`, on a unix +socket by default. The contract is in [`api/api.go`](https://github.com/hivecommons/crux/blob/main/api/api.go) and +[`api/runtime.go`](https://github.com/hivecommons/crux/blob/main/api/runtime.go). [`api/wire.go`](https://github.com/hivecommons/crux/blob/main/api/wire.go) holds the +address forms. [`api/runtime.go`](https://github.com/hivecommons/crux/blob/main/api/runtime.go) also has +`api.RuntimeRoutes`, a table that maps every `crux.Runtime` method to its +route. The server itself is in [`serve/`](https://github.com/hivecommons/crux/blob/main/serve/). + +Routes that mirror a CLI verb: + +| Route | Verb | What it does | +|---|---|---| +| `/v1/health` | GET | Server is up. Agent count, uptime, hive callback state. | +| `/v1/agents` | GET | List agents (`crux ps`). | +| `/v1/agents` | POST | Add a definition and start it (`crux run`). | +| `/v1/agents/{name}` | GET | One agent's status. | +| `/v1/agents/{name}` | DELETE | Forget an agent's definition. | +| `/v1/agents/{name}/logs` | GET | Recent output. `?source=pane\|buffer\|full`, `?n=`, `?follow=1` streams. | +| `/v1/agents/{name}/kick` | POST | Send work (`crux kick`). | +| `/v1/agents/{name}/kick` | GET | State of the last async kick. | +| `/v1/agents/{name}/stop` | POST | Stop it (`crux stop`). | +| `/v1/events` | GET | Runtime events as a stream. | + +Per-agent routes for the rest of the runtime: + +| Route | Verb | What it does | +|---|---|---| +| `/v1/agents/{name}/start` | POST | Start a defined agent. | +| `/v1/agents/{name}/restart` | POST | Restart, with an optional bootstrap prompt. | +| `/v1/agents/{name}/pause` | POST | Pause, with a trigger, reason, actor and cause. | +| `/v1/agents/{name}/resume` | POST | Lift a pause. | +| `/v1/agents/{name}/restart-count` | DELETE | Reset the crash-loop counter. | +| `/v1/agents/{name}/snapshot` | GET | Full status snapshot. | +| `/v1/agents/{name}/kick/validate` | POST | Say why a kick would be refused, without sending one. | +| `/v1/agents/{name}/kicklogs` | GET | List archived kick logs. | +| `/v1/agents/{name}/kicklogs/{id}` | GET | Read one kick log. | +| `/v1/agents/{name}/definition` | PUT | Replace the agent's config. | +| `/v1/agents/{name}/overrides` | PUT | Set the model or backend override. | +| `/v1/agents/{name}/pins/{cli\|model}` | PUT, DELETE | Pin or unpin the CLI version or model. | +| `/v1/agents/{name}/login-code` | POST | Type a device-flow login code. | +| `/v1/agents/{name}/session` | GET | Session id and resume handle. | +| `/v1/agents/{name}/session/kick` | POST | Kick a named past session. | +| `/v1/agents/{name}/supervision` | GET | Supervisor reads: capabilities, backend, restart and failure state, auth. | +| `/v1/agents/{name}/tokens/refresh` | POST | Re-mint this agent's GitHub token. | +| `/v1/agents/{name}/nudge` | POST | Send a nudge that does not count as a kick. | +| `/v1/agents/{name}/seed` | POST | Reload saved pause, restart, kick and turn-loss state. | +| `/v1/agents/{name}/watchdog` | GET | Watchdog observation and last production. | +| `/v1/agents/{name}/watchdog/conditions` | PUT | Set the agent's watchdog conditions. | + +Fleet-wide routes: + +| Route | Verb | What it does | +|---|---|---| +| `/v1/snapshots` | GET | Snapshot of every agent. | +| `/v1/definitions` | POST | Add a definition without starting it. | +| `/v1/definitions/reconcile` | POST | Add any definitions the runtime does not know yet. | +| `/v1/fleet` | GET | Trust level, shared uid, consent-wedged agents, agent counts. | +| `/v1/fleet/breaker` | GET, PUT | Read, or restore, the fleet breaker. | +| `/v1/fleet/breaker/engage` | POST | Pause every running agent. | +| `/v1/fleet/breaker/release` | POST | Resume the agents the breaker paused. | +| `/v1/fleet/acmm` | PUT | Set the fleet trust level. | +| `/v1/fleet/modes/sync` | POST | Rewrite agent mode files for a trust level. | +| `/v1/fleet/modes/clear` | POST | Drop mode overrides for named agents. | +| `/v1/fleet/inference/refresh` | POST | Re-resolve routes for a gateway backend. | +| `/v1/fleet/relaunch-bob` | POST | Relaunch Bob agents parked on a missing key. | +| `/v1/fleet/startup-queued` | POST | Record the host's staggered boot queue. | +| `/v1/fleet/restart-crashed` | POST | Relaunch crashed agents. | +| `/v1/fleet/tokens/refresh` | POST | Re-mint every agent's GitHub token. | +| `/v1/fleet/kicklogs/archive` | POST | Rotate every agent's kick log. | +| `/v1/credentials/copilot` | GET, PUT | Read or set the shared Copilot token. | +| `/v1/credentials/claude` | GET | Where crux looks for Claude credentials. | +| `/v1/credentials/claude/reload` | POST | Re-read the Claude credential store. | +| `/v1/backends/{backend}/auth` | GET | Whether a backend has credentials. | +| `/v1/audit` | POST | Append a host action to the audit trail. | +| `/v1/trajectory` | GET | Running agents with intent and transcript. | +| `/v1/authorize` | POST | Ask before a PR open, push, issue open, merge or review request. | +| `/v1/watchdog/agents` | GET | Agent names the watchdog should observe. | + +`api.Client` in [`api/client.go`](https://github.com/hivecommons/crux/blob/main/api/client.go) wraps the CLI-shaped +routes: `Health`, `List`, `Get`, `Run`, `Logs`, `FollowLogs`, `Kick`, +`Stop`, `Events`, `WaitReady`, `Status` and `Address`. `Do` reaches any +other route. [`api/client`](https://github.com/hivecommons/crux/blob/main/api/client) goes one step further: +`client.Runtime` is a full `crux.Runtime` over the API, and is what +`runtime.New` returns when `CRUX_MODE=sidecar`. + +### 3. The hive callback, what crux asks the host for + +This layer runs the other way. Crux never holds a long-lived GitHub key, a +canary list, or any other secret the host owns. It asks the host for each +one when it needs it. The contract is in +[`api/callback/callback.go`](https://github.com/hivecommons/crux/blob/main/api/callback/callback.go). The host serves +it; crux calls it with `crux serve --hive-callback`. The core set is +`callback.Capabilities`. The rest are optional halves the host may add. +Every call fails closed: if the host is down or says no, crux treats it as +a refusal. + +| Method | What the host provides | +|---|---| +| `MintToken` | A short-lived, scoped GitHub token. | +| `MintAgentToken` | A token for one agent at its trust tier. | +| `BobCredential` | The IBM Bob API key and where it came from. | +| `LinearCredential` | The Linear credential to inject. | +| `CreatePR` | Opens a pull request as the host's GitHub App. | +| `RecordREST` | Records one relayed GitHub REST response for accounting. | +| `RateLimit` | The shared GitHub rate-limit bucket snapshot. | +| `RecordUsage` | Records inference token usage. | +| `ScanCanary` | Scans a GitHub write body for planted canaries. | +| `ScanText` | Scans tool payload text. | +| `ReportCondition` | Takes a runtime condition, raised or cleared. | +| `Policy` | One snapshot of the host's policy settings. | +| `RepoAutoMergeEnabled` | Whether one repository allows auto-merge. | +| `SetInferenceRoute` | Tells the host's inference proxy which upstream to use for one agent. | +| `ClearInferenceRoute` | Removes that route. | +| `BeginEffect` | Admits or denies one external write before crux performs it. | +| `EndEffect` | Records how that write turned out. | +| `Configured` | Says whether the host has a given capability at all. | + +### Packages shipped with it + +- `agent`: the manager. tmux sessions, launch, restarts, kicks. It knows + nine engines by name in `agent/manager_launch.go`: claude, copilot, + gemini, agy, pi, goose, bob, codex and omp. `config.CLIBackends` lists + fifteen; the other six (aider, opencode, kilo, muse, openhands, + omnigent) launch as a bare binary. +- `proxy`: the GitHub and inference proxy. Policy enforcement at the tool + boundary: every agent write to GitHub and every model call goes through + it, with budgets and deny rules. +- `sandbox`: the opt-in per-task rootless Podman container, with no + credentials inside. +- `kubejob`: the same sandbox as a Kubernetes Job. +- `toolapprove`: the allow list of what an agent may call. It sorts tool + calls into read-only, GitHub write, PR open and PR merge, and scans + their text. +- `skillreg`: a local registry of named, versioned agent skills. +- `inference`: works out where an agent's model calls go and how they log + in. +- `openrouter`: the OpenRouter scan-to-fund flow that gets a scoped key. +- `watsonx`: swaps an IBM Cloud API key for a short-lived IAM token. +- `watchdog`: the per-agent liveness and readiness loop that heals stalls. +- `trajectory`: a second-model check that an agent is still on task. +- `logscrub`: a log handler that masks tokens and other secrets. +- `pushbroker`: the trusted step that pushes a sandboxed agent's work with + credentials the sandbox never sees. +- `gitidentity`: the git identity each agent commits as. +- `agentmode`: the ladder of GitHub permission tiers, one per trust level. +- `agentparse`: parsers for the tables and lists agents print. +- `agentsmd`: reads a repository's `AGENTS.md` and its inline skills. +- `hivecap`: the runtime-side adapters for the hive callback. +- `serve`, `cli`, `cmd`: the HTTP server, the `crux` command, and the binary. + ## Contributing The Go code in this repository is not edited here. It is a copy of @@ -198,6 +402,13 @@ touch Go files. Only the scaffolding is maintained in this repository: `.github/`, `.goreleaser.yaml`, `Dockerfile`, this README and `LICENSE`. The module's own README is synced to [`docs/MODULE.md`](https://github.com/hivecommons/crux/blob/main/docs/MODULE.md). +CI here applies the same gates Hive applies to this code: build, vet, +`go test -short -race`, Hive's per-package coverage floors +([`.github/scripts/coverage-floors.sh`](https://github.com/hivecommons/crux/blob/main/.github/scripts/coverage-floors.sh)), +golangci-lint, govulncheck, gosec (advisory, as in Hive), action-pin +resolution, a `crux serve` smoke and a goreleaser snapshot. The floors are +copied from Hive's `v2-tests.yml`; change them there first. + So: file issues and code changes in [hivecommons/hive](https://github.com/hivecommons/hive). Releases are cut by tagging Hive `crux/vX.Y.Z`. How the publish works is in diff --git a/docs/content/dibs/credit-wall-api.md b/docs/content/dibs/credit-wall-api.md new file mode 100644 index 00000000..fb8872c0 --- /dev/null +++ b/docs/content/dibs/credit-wall-api.md @@ -0,0 +1,305 @@ +> **Synced from dibs.** This page is pulled from [hivecommons/dibs@main](https://github.com/hivecommons/dibs/blob/main/docs/credit-wall-api.md) during the docs build. Edit the canonical source in the dibs repository. + +# Credit-wall consumer API + +This reference describes the existing HTTP API for dashboards and agents that +need credit-wall eligibility, settlement state, and rewards. It documents the +implementation, not a new eligibility service or a payments API. Rewards are +non-monetary points, levels, and badges; settlement means a GitHub issue was +recorded, **not** that the idea was implemented or its issue closed. + +## Stability and transport + +- **Experimental:** implemented, but unversioned; fields and behavior may change. + Consumers should pin a deployment revision, tolerate additional fields, and + recheck this reference when upgrading. +- **Stable:** reserved for a deliberately supported compatibility contract. No + endpoint in this reference is designated stable yet. Promotion should include + an agreed compatibility/deprecation policy and contract tests; documenting the + current schema alone does not make that promise. + +| Endpoint | Access | Stability | Purpose | +|---|---|---|---| +| `GET /api/credits` | Public | Experimental | Eligible credit-wall entries | +| `GET /api/leaderboard` | Public | Experimental | Ranked aggregate rewards | +| `GET /api/me` | Session | Experimental | Caller identity | +| `GET /api/me/stats` | Session | Experimental | Caller's lifecycle counts and rewards | +| `GET /api/ideas?scope=mine` | Session | Experimental | Discover caller's idea IDs and states | +| `GET /api/ideas/{id}` | Session + read permission | Experimental | Individual settlement state | +| `POST /api/ideas/{id}/offer` | Session + author | Experimental | Offer to a target repository | +| `POST /api/repos/{org}/{repo}/decide` | Session + repo owner | Experimental | Accept, decline, or pass | +| `POST /api/ideas/{id}/launch` | Session + author | Experimental | Prepare the GitHub issue form | +| `POST /api/ideas/{id}/confirm-issue` | Session + author | Experimental | Record the filed issue URL | + +Prepend the configured `DIBS_BASE_PATH` to **every** route (for example, +`/dibs/api/credits` when the prefix is `/dibs`). An unset prefix means `/api/...`. +Successful responses below are HTTP 200 with `Content-Type: application/json`. +Times are JSON strings in Go's RFC 3339 format, potentially with fractional +seconds. `?` in the schema notation below means a field may be omitted, not that +it is JSON `null`. Arrays without entries are `[]` unless noted otherwise. + +Authenticated HTTP routes use the hub's `hive_hub_user` session cookie. The +`Authorization: Bearer` flow described for [MCP](/docs/dibs/readme#submit-ideas-from-your-agent) +is **not** authentication for these REST routes. Browser writes must be +same-origin: the CSRF guard rejects cross-origin requests, including sibling +subdomains. Never embed session credentials in a public dashboard. + +API errors have shape `{"error":"message"}`. Relevant statuses are: + +| Status | Meaning | +|---|---| +| 400 | Invalid JSON/input, invalid URL, or disallowed lifecycle operation | +| 401 | Missing/invalid session on a protected route | +| 403 | Not the author/repo owner, or CSRF rejection | +| 404 | Missing idea/repo, or a private idea hidden from this caller | +| 413 | Request body exceeds the input limit (128 KiB) | +| 500 | Store/internal failure | +| 502 | Hub authentication backend unavailable | + +Treat status codes as the primary error signal; message strings are not error +identifiers. Routing errors such as 405 need not use the JSON error envelope. +There is no pagination, cursor, webhook, or snapshot-consistency guarantee for +these endpoints. Poll reads as needed; do not infer a deletion or loss of credit +from a failed request. + +## Eligibility and the public wall + +### `GET /api/credits` + +Response schema: + +```text +{ credits: CreditEntry[] } +CreditEntry = { + author: string, authorDisplay: string, title: string, + symbol?: string, tldr?: string, + repoID: string, issueURL: string, settledAt: timestamp +} +``` + +Example: + +```json +{"credits":[{"author":"octocat","authorDisplay":"Octocat","title":"Improve onboarding","symbol":"$ONBOARD","repoID":"example/project","issueURL":"https://github.com/example/project/issues/42","settledAt":"2026-10-01T12:00:00Z"}]} +``` + +An idea is eligible exactly when its stored `status` is `settled`, regardless of +`visibility`. Entries are newest `updatedAt` first; ties have no defined order. +`settledAt` is the idea's settlement time, stamped once when it settles; ideas +settled before that field existed fall back to their last update time. A +settled idea's title and body can no longer be edited. There is no idea ID in +this response: do not +assume title, symbol, or issue URL is a durable primary key. + +The wall intentionally exposes only attribution, title/TLDR, display symbol, +target repository, issue URL, and time. It does not expose idea bodies, offers, +or unsettled private ideas. Settling a private idea makes these credit facts +public; it does not change its visibility or make its detail endpoint public. +Use this endpoint, not authenticated idea listings, to build a public wall. + +There is no standalone `/eligibility` endpoint. Before settlement, use an +authorized idea read to determine which action is currently available. Local +eligibility checks are hints; the write handlers remain authoritative. + +## Identity and settlement-state reads + +### `GET /api/me` + +```text +{ + username: string, display_name?: string, email?: string, + avatar_url?: string, admin?: boolean +} +``` + +Compare `username` with an idea's `author` to identify author-only actions. +Identity keys are GitHub logins or hub `provider:sub` keys; do not assume every +`author` can be linked to `github.com/{author}`. Display names are not identity +keys. Do not republish the caller's email. + +### `GET /api/ideas?scope=mine` and `GET /api/ideas/{id}` + +The list returns `Idea[]` (the default scope is also `mine`); the +detail route returns an `Idea` directly, not an envelope. Detail reads require +a session even for public ideas. A private idea is readable only by its author +or an owner of a repository that holds a standing (pending or accepted) offer +for it; a declined offer no longer grants access, and everyone else gets 404. +Writes below remain author-only even when a repo owner can read. + +The settlement-relevant projection of `Idea` is: + +```text +{ + id: string, author: string, authorDisplay: string, + title: string, body: string, visibility: "public" | "private", + status: "draft" | "offered" | "accepted" | "declined" | + "issue_launched" | "settled", + createdAt: timestamp, updatedAt: timestamp, settledAt?: timestamp, + targetRepo?: string, issueURL?: string, offers?: Offer[] +} +Offer = { + repoID: string, status: "pending" | "accepted" | "declined", + createdAt: timestamp, decidedAt?: timestamp, external?: boolean +} +``` + +This is a projection, not the entire idea schema: responses also include +matching and display data. The full JSON model is in +[`store.Idea`](https://github.com/hivecommons/dibs/blob/main/pkg/store/store.go). A missing `external` means false. + +| Idea state | Meaning / next step | +|---|---| +| `draft` | Author can offer; repo owner can directly accept a public candidate | +| `offered` | Registered target needs acceptance; external target can launch directly | +| `declined` | Author may re-offer | +| `accepted` | Target selected; author can launch or directly confirm an issue | +| `issue_launched` | Prefilled form prepared; author must file the issue and confirm | +| `settled` | Issue URL recorded; eligible for the wall, terminal lifecycle state | + +Launch requires a nonempty `targetRepo`. An `offered` idea may launch only if +that target is outside the current registry. `accepted` and `issue_launched` +may launch; confirmation requires `accepted` or `issue_launched`. Neither +acceptance nor launch alone qualifies an idea for the public wall. + +## Settlement writes + +### `POST /api/ideas/{id}/offer` + +Request: `{ "repoID": "org/repo" }`. Response: the updated `Idea`. +A registered repo must be accepting ideas; the offer becomes `pending`. For a +private idea this explicitly reveals it to that repo's owner for as long as the +offer is pending or accepted; declining ends the reveal. An unregistered +GitHub-shaped target records `external: true` and sets `targetRepo` immediately, +without repo-owner acceptance. This validates the target's syntax, not its +existence. Duplicate non-declined offers and illegal transitions return 400. + +### `POST /api/repos/{org}/{repo}/decide` + +Request: `{ "ideaID": "idea-id", "decision": "accept" }`, where `decision` +is `accept`, `decline`, or `pass`. Only the registered repo's owner may decide. +Private ideas require an offer to that repository; decline requires a pending +offer. Pass applies only to non-offered public candidates: when the idea has a +pending offer to this repository, `pass` returns 400 and the offer must be +declined instead. Accept sets `targetRepo` and moves an available idea to +`accepted`. + +Response shapes: + +```text +accept: { result: "accepted", idea: Idea, warning?: string } +decline: { result: "declined", idea: Idea } +pass: { result: "passed" } +legacy successful accept: { result: "settled", idea: Idea, issueURL: string } +``` + +In optional legacy mode (`DIBS_GITHUB_TOKEN` configured), acceptance attempts to +open the issue server-side and settle immediately. Consumers must inspect the +returned result/status rather than assume every accept is already settled. +A legacy issue-opening failure returns 200 with `result: "accepted"` and a +`warning` string, leaving the author able to use the normal settlement flow. + +### `POST /api/ideas/{id}/launch` + +Request: `{ "title": "optional override", "body": "optional override" }`. +Send `{}` to use the idea's defaults; empty strings also select defaults. +Response: + +```text +{ + url: string, repoID: string, title: string, + fullBody: string, truncated: boolean +} +``` + +`url` is a prefilled GitHub **new-issue form**, not a filed issue URL. The author +files it under their own GitHub account. `fullBody` contains the untruncated +issue text, ending with the Dibs footer and a hidden `` +marker line that confirm-issue checks when the author did not sign in with +GitHub; if `truncated` is true, the URL's body was shortened to fit its +length budget (the footer and marker are preserved). Launch records `issue_launched`; re-launching from that state +rebuilds the URL without awarding another milestone. Overrides do not edit the +stored idea. Do not publish the returned body for a private idea. + +### `POST /api/ideas/{id}/confirm-issue` + +Request: `{ "issueURL": "https://github.com/org/repo/issues/42" }`. +Response: `{ result: "settled", idea: Idea }`. + +The validator requires HTTPS, a GitHub host (`github.com` or `www.github.com`), +the target repo (case-insensitive), and a positive numeric issue number. The +issue is then looked up on GitHub and must exist, be an issue (not a pull +request), be no older than the idea, and be tied to the author: opened by the +same login when they signed in with GitHub, or otherwise carrying the +`` marker from the launch body; otherwise the request +fails with 400 and the idea stays unsettled. An issue URL already credited to +another idea (compared case-insensitively, ignoring a `www.` host, trailing +slash, query, or fragment) is also rejected with 400: one issue settles at +most one idea. A GitHub lookup failure returns 502 +(retry later). Implementation or merge state is still not checked. +A repeated confirmation after settlement returns 400; after an ambiguous +network failure, read the idea before retrying. Do not replay all writes +blindly: offer/accept/confirm are not generally idempotent. + +## Reward reads + +### `GET /api/me/stats` + +Returns the caller's counts and derived progress directly: + +```text +{ + posted: integer, offered: integer, accepted: integer, settled: integer, + score: integer, level: Level, nextLevel?: Level, + toNext: integer, pct: integer, badges: Badge[] +} +Level = { name: string, emoji: string, min: integer } +Badge = { id: string, name: string, emoji: string, desc: string } +``` + +Counts and rewards include **all of the caller's ideas**, including private +ones. `posted` counts existing ideas; `offered` counts ideas with offers or a +non-draft state; `accepted` counts `accepted`, `issue_launched`, and `settled`; +`settled` counts only `settled`. These are derived current-state counts, not an +immutable event ledger. External launches also count as accepted for rewards. +`nextLevel` is omitted at the top level, where `toNext` is 0 and `pct` is 100. + +### `GET /api/leaderboard` + +```text +{ leaderboard: LeaderboardEntry[] } +LeaderboardEntry = { + rank: integer, author: string, authorDisplay: string, + score: integer, level: Level, settled: integer, badges: Badge[] +} +``` + +Only authors with at least one settled idea are listed. Rank is one-based; +ordering is score descending, then settled count descending, then author +ascending. Scores and badges derive from **all** that author's ideas, not just +settled/public ones. This exposes aggregates, never private titles or bodies. +It does not expose `nextLevel`, `toNext`, or `pct`. + +Current reward rules (experimental, not financial entitlements): + +- Each idea earns 10 points for existing, plus 25 for offers/non-draft state, + plus 50 for accepted/launched/settled state, plus 100 for settled: 185 total + for a settled idea. Repeated reads do not award additional points. +- Levels: Bronze at 0, Silver at 100, Gold at 300, Platinum at 750. +- Badge IDs: `first-dibs` (at least one idea), `pollinator` (at least five + distinct idea/repo pairings), `hivemind` (at least three distinct accepting + target repos), `rainmaker` (at least one settled idea). + +Render the returned level/badge metadata instead of hard-coding labels or +thresholds. Rules and derivation are implemented in [`pkg/game`](https://github.com/hivecommons/dibs/blob/main/pkg/game/game.go). + +## Implementation and regression references + +- Public wall/rewards: [`pkg/api/credits.go`](https://github.com/hivecommons/dibs/blob/main/pkg/api/credits.go), + [`pkg/server/credits_test.go`](https://github.com/hivecommons/dibs/blob/main/pkg/server/credits_test.go). +- Settlement writes: [`pkg/api/settlement.go`](https://github.com/hivecommons/dibs/blob/main/pkg/api/settlement.go), + [`pkg/api/offers.go`](https://github.com/hivecommons/dibs/blob/main/pkg/api/offers.go), + [`pkg/server/settlement_test.go`](https://github.com/hivecommons/dibs/blob/main/pkg/server/settlement_test.go). +- Access and prefix routing: [`pkg/api/api.go`](https://github.com/hivecommons/dibs/blob/main/pkg/api/api.go), + [`pkg/server/server.go`](https://github.com/hivecommons/dibs/blob/main/pkg/server/server.go). +- Reward derivation: [`pkg/game/game_test.go`](https://github.com/hivecommons/dibs/blob/main/pkg/game/game_test.go), + [`pkg/server/gamification_test.go`](https://github.com/hivecommons/dibs/blob/main/pkg/server/gamification_test.go). diff --git a/docs/content/dibs/mcp-registry.md b/docs/content/dibs/mcp-registry.md new file mode 100644 index 00000000..aa18e21f --- /dev/null +++ b/docs/content/dibs/mcp-registry.md @@ -0,0 +1,114 @@ +> **Synced from dibs.** This page is pulled from [hivecommons/dibs@main](https://github.com/hivecommons/dibs/blob/main/docs/mcp-registry.md) during the docs build. Edit the canonical source in the dibs repository. + +# Publishing Dibs to the MCP Registry + +The root [`server.json`](https://github.com/hivecommons/dibs/blob/main/server.json) prepares the remote-only listing +`dev.hivecommons/dibs`, using the namespace approved in +[issue #375](https://github.com/hivecommons/dibs/issues/375#issuecomment-6215095421). +Committing this file does not publish it; publication happens through the +`MCP Registry` workflow described below. + +The listing is titled **Dibs (Hive Commons)** to distinguish it from the +unrelated 1stDibs marketplace. Version `0.1.0` corresponds to the MCP +implementation's `v0.1.0` in `pkg/mcpserver/server.go`. The endpoint uses +Streamable HTTP; no installable package is needed. Its optional, secret +`Authorization` header takes the complete `Bearer ` value described +in the [README](/docs/dibs/readme#token). Public ideas and repo venues can be read +without it; submitting ideas, listing your ideas, and reading your private +ideas require authentication. + +## Publishing (automated) + +The [`MCP Registry` workflow](https://github.com/hivecommons/dibs/blob/main/.github/workflows/mcp-registry-publish.yml) +publishes `server.json` on every **published GitHub release** and on manual +`workflow_dispatch`. Merging to `main` never publishes. The workflow: + +1. Refuses to run unless `https://dibs.hivecommons.dev/readyz` reports the + same sha as the checked-out ref (so a listing never points clients at a + stale build — see [`deploy/README.md`](https://github.com/hivecommons/dibs/blob/main/deploy/README.md) to roll out). +2. Installs a pinned, checksum-verified `mcp-publisher`, runs + `mcp-publisher validate server.json`, then + `mcp-publisher login dns --domain hivecommons.dev` using the + `MCP_REGISTRY_PRIVATE_KEY` repository secret, and `mcp-publisher publish`. +3. Reads the entry back by name and version (best effort; a slow registry + does not fail the run). + +To publish a new listing version: bump `version` in `server.json` (and the +MCP `serverVersion` in `pkg/mcpserver/server.go` if it changed), merge, roll +out, then publish a GitHub release or dispatch the workflow: + +```sh +gh workflow run mcp-registry-publish.yml --repo hivecommons/dibs +``` + +### DNS authentication key + +Authentication is DNS-based: the apex TXT record on `hivecommons.dev` +(`v=MCPv1; k=ed25519; p=`) must match the Ed25519 private key +(64 hex chars, as produced by the registry's +[DNS authentication instructions](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/authentication.mdx#dns-authentication)) +stored in the `MCP_REGISTRY_PRIVATE_KEY` secret. To rotate, generate a new +key pair, replace the TXT record (remove the old one — a stale record is tried +first and breaks verification), then update the secret. + +## Operator checklist (manual fallback) + +Use this only if the workflow is unavailable. + +1. Confirm production runs current `main`. The listing sends clients straight + to the endpoint, and the published image only reaches it after a rollout + (see [`deploy/README.md`](https://github.com/hivecommons/dibs/blob/main/deploy/README.md)). From an up-to-date + checkout, the version `/healthz` and `/readyz` report must match `main`, + and `/readyz` must answer 200: + + ```sh + git fetch origin main + test "$(curl --fail --silent --show-error https://dibs.hivecommons.dev/readyz | jq -r .version)" \ + = "$(git rev-parse origin/main)" && echo "production is current" + ``` + + If the request fails or the versions differ, roll out the current image + first and repeat this check. Do not publish against an older build. + +2. Install the official `mcp-publisher` CLI using the + [registry quickstart](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx#step-3-install-mcp-publisher). + From the repository root, validate the committed manifest: + + ```sh + mcp-publisher validate server.json + ``` + +3. Follow the registry's + [DNS authentication instructions](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/authentication.mdx#dns-authentication) + for **hivecommons.dev**. The operator generates and retains the signing + key, adds the public-key TXT record at the domain apex, and waits for DNS + propagation. Keep the private key outside this checkout. With the private + key in the format required by those instructions: + + ```sh + mcp-publisher login dns --domain hivecommons.dev --private-key "$MCP_REGISTRY_PRIVATE_KEY" + ``` + +4. From the repository root, publish the reviewed manifest: + + ```sh + mcp-publisher publish server.json + ``` + +5. Verify the published name, version, and remote URL through the registry: + + ```sh + curl --fail --silent --show-error \ + 'https://registry.modelcontextprotocol.io/v0.1/servers?search=dev.hivecommons%2Fdibs' \ + | jq -e '.servers[] + | select(.server.name == "dev.hivecommons/dibs" and .server.version == "0.1.0") + | select(any(.server.remotes[]; + .type == "streamable-http" and .url == "https://dibs.hivecommons.dev/mcp"))' + ``` + + When the listing changes, update its version and this verification + command, and repeat step 1, before publishing again. (The original + listing was verified and tracked in #375, now closed.) + +See also the registry's +[remote server guidance](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/remote-servers.mdx). diff --git a/docs/content/dibs/readme.md b/docs/content/dibs/readme.md index 5574f343..18aecaca 100644 --- a/docs/content/dibs/readme.md +++ b/docs/content/dibs/readme.md @@ -98,7 +98,7 @@ fallbacks for every `DIBS_*` variable. ### Roadmap -See [ROADMAP.md](https://github.com/hivecommons/dibs/blob/main/ROADMAP.md) for what is planned next. Delivery history: +See [ROADMAP.md](/docs/dibs/roadmap) for what is planned next. Delivery history: - **Wave 1 (done):** scaffold, hub auth bridge, idea CRUD, repo registry. - **Wave 2 (done):** LLM matching + TLDRs, swipe UX ("offer" / "pass"), issue settlement (credited GitHub issues), notifications. @@ -135,13 +135,19 @@ Everything else requires a hive hub session; signed-in ideators additionally get `GET /api/me/stats` (ideas posted / offered / accepted / settled). For response schemas, eligibility/privacy rules, settlement actions, rewards, -and stability levels, see the [credit-wall consumer API](https://github.com/hivecommons/dibs/blob/main/docs/credit-wall-api.md). +and stability levels, see the [credit-wall consumer API](/docs/dibs/credit-wall-api). ## Submit ideas from your agent Dibs exposes a stateless Streamable HTTP MCP endpoint at -`https://dibs.hivecommons.dev/mcp`, so Claude Code, Copilot CLI, Cursor, and -other MCP clients can submit ideas without leaving the agent. +`https://dibs.hivecommons.dev/mcp`, so Claude Code, Copilot CLI, Cursor, +Gemini CLI, Codex, and other MCP clients can submit ideas without leaving the agent. + +Dibs is listed in the official MCP Registry as `dev.hivecommons/dibs` +([registry entry](https://registry.modelcontextprotocol.io/v0.1/servers?search=dev.hivecommons%2Fdibs)). +Search for that full name: a plain `dibs` search also returns the unrelated +1stDibs marketplace. How the listing is published is covered in +[MCP Registry publishing](/docs/dibs/mcp-registry). Available tools: @@ -218,26 +224,32 @@ Cursor reads MCP configuration from `~/.cursor/mcp.json` (global) or `.cursor/mc Cursor supports `${env:NAME}` interpolation in headers, so you can set `DIBS_TOKEN` as an environment variable rather than storing it in the file. For details, see the [Cursor MCP documentation](https://cursor.com/docs/mcp). -## Recall ideas you never acted on +### Gemini CLI -Dibs ships an MCP prompt, `recall_ideas`, for the ideas that never made it to -a form: the ones mentioned in passing and forgotten. Dibs cannot find those -itself - only the assistant holding your history can - so the prompt asks it -to. +```sh +gemini mcp add --scope user --transport http \ + --header "Authorization: Bearer " \ + dibs https://dibs.hivecommons.dev/mcp +``` -It instructs the assistant to search its memory and your past conversations -for product ideas, feature wishes, requirements and specs you described but -never acted on, report each with enough context to act on, and then ask which -ones to list. It submits only what you pick. +This adds the server to `~/.gemini/settings.json`; drop `--scope user` to add +it to the project's `.gemini/settings.json` instead. For details, see the +[Gemini CLI MCP documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md). -| Argument | Purpose | -|---|---| -| `scope` | Optional focus - a project, a repo, a period like "the last year". Omit to search everything available. | -| `limit` | Maximum candidates to report. Defaults to 10, capped at 50. | +### Codex -In Claude Code and Claude Desktop the prompt appears as a slash command once -the connector is added. Clients that do not surface prompts in their UI can -still call `prompts/get` directly. +Codex (CLI and IDE extension) reads MCP servers from `~/.codex/config.toml`. +`bearer_token_env_var` keeps the token out of the file and sends it as +`Authorization: Bearer `: + +```toml +[mcp_servers.dibs] +url = "https://dibs.hivecommons.dev/mcp" +bearer_token_env_var = "DIBS_TOKEN" +``` + +Then `export DIBS_TOKEN=` before +starting Codex. For details, see the [Codex MCP documentation](https://developers.openai.com/codex/mcp). ### Generic MCP JSON @@ -255,9 +267,30 @@ still call `prompts/get` directly. } ``` +## Recall ideas you never acted on + +Dibs ships an MCP prompt, `recall_ideas`, for the ideas that never made it to +a form: the ones mentioned in passing and forgotten. Dibs cannot find those +itself - only the assistant holding your history can - so the prompt asks it +to. + +It instructs the assistant to search its memory and your past conversations +for product ideas, feature wishes, requirements and specs you described but +never acted on, report each with enough context to act on, and then ask which +ones to list. It submits only what you pick. + +| Argument | Purpose | +|---|---| +| `scope` | Optional focus - a project, a repo, a period like "the last year". Omit to search everything available. | +| `limit` | Maximum candidates to report. Defaults to 10, capped at 50. | + +In Claude Code and Claude Desktop the prompt appears as a slash command once +the connector is added. Clients that do not surface prompts in their UI can +still call `prompts/get` directly. + ## Operations and further reading -- [MCP Registry publishing](https://github.com/hivecommons/dibs/blob/main/docs/mcp-registry.md): listing manifest and operator steps for DNS authentication, publication, and verification. +- [MCP Registry publishing](/docs/dibs/mcp-registry): listing manifest and operator steps for DNS authentication, publication, and verification. - [Runbooks](https://github.com/hivecommons/dibs/blob/main/runbooks/): [incident response](https://github.com/hivecommons/dibs/blob/main/runbooks/incident-response.md), [release rollback](https://github.com/hivecommons/dibs/blob/main/runbooks/release-rollback.md), [data recovery](https://github.com/hivecommons/dibs/blob/main/runbooks/data-recovery.md), [SLOs](https://github.com/hivecommons/dibs/blob/main/runbooks/slo.md), and a [postmortem template](https://github.com/hivecommons/dibs/blob/main/runbooks/postmortem-template.md). - [CONTRIBUTING.md](https://github.com/hivecommons/dibs/blob/main/CONTRIBUTING.md): DCO sign-off, CI requirements, and code conventions. - [Idea intake trust and abuse controls](https://github.com/hivecommons/dibs/blob/main/docs/idea-intake-trust.md): a proposed design, not yet implemented. @@ -305,6 +338,16 @@ docker build --build-arg GIT_HASH="$(git rev-parse HEAD)" -t dibs:smoke . hack/smoke.sh dibs:smoke "$(git rev-parse HEAD)" ``` +## Contributing & Community + +Dibs is part of [Hive Commons](https://hivecommons.dev), a family of tools for working with AI agents. + +- Want to contribute? See [CONTRIBUTING.md](https://github.com/hivecommons/dibs/blob/main/CONTRIBUTING.md) for DCO sign-off, the CI and coverage gates, and code conventions. +- Found a bug? Open a [bug report](https://github.com/hivecommons/dibs/issues/new?template=bug_report.yml). Have an idea for Dibs itself? Open a [feature request](https://github.com/hivecommons/dibs/issues/new?template=feature_request.yml). +- Reporting a security vulnerability? Follow [SECURITY.md](https://github.com/hivecommons/dibs/blob/main/SECURITY.md) to report it privately, not through public issues. +- Questions? Join the [Hive Commons Discord](https://hivecommons.dev/discord). +- Please follow the [Hive Commons Code of Conduct](https://github.com/hivecommons/.github/blob/main/CODE_OF_CONDUCT.md). + ## License Apache-2.0 — see [LICENSE](https://github.com/hivecommons/dibs/blob/main/LICENSE). diff --git a/docs/content/dibs/roadmap.md b/docs/content/dibs/roadmap.md new file mode 100644 index 00000000..eee1ed6d --- /dev/null +++ b/docs/content/dibs/roadmap.md @@ -0,0 +1,35 @@ +> **Synced from dibs.** This page is pulled from [hivecommons/dibs@main](https://github.com/hivecommons/dibs/blob/main/ROADMAP.md) during the docs build. Edit the canonical source in the dibs repository. + +# Dibs Roadmap + +Dibs is a marketplace for turning public ideas into claimable, settleable work. +This roadmap gives maintainers, ideators, and adopters a shared view of where +the project is headed next. + +## Recently shipped + +- End-to-end ideation, matching, and settlement flows for GitHub issue-backed + work. +- Intake support for common document formats with bounded PDF extraction and + malformed-input recovery. +- Repository registry, credit-wall mechanics, notifications, and guided server + flows covered by automated tests. + +## Near term + +1. **Trust and abuse controls:** improve spam resistance for public idea intake, + including clearer moderation states and rate/quality signals for ideators. + See the [proposed idea-intake trust design](https://github.com/hivecommons/dibs/blob/main/docs/idea-intake-trust.md); + implementation follows in separate issues. +2. **Forge and repo coverage:** define what it takes to support non-GitHub issue + targets while growing the repository registry beyond hive-managed projects. +3. **Credit-wall integrations:** the [consumer API reference](/docs/dibs/credit-wall-api) + documents eligibility, settlement state, rewards, and current experimental + stability levels. Next, define compatibility guarantees and stabilize the + downstream contract. + +## Deferred / non-goals + +- Custodial payments or token issuance. +- Replacing project maintainers' review and merge authority. +- Accepting arbitrary unauthenticated uploads or anonymous settlement claims. diff --git a/docs/content/hive/acmm-policy-matrix.md b/docs/content/hive/acmm-policy-matrix.md index 5720078f..169683e3 100644 --- a/docs/content/hive/acmm-policy-matrix.md +++ b/docs/content/hive/acmm-policy-matrix.md @@ -103,7 +103,7 @@ Agents open issues AND pull requests. Agent PRs get literal `hold` from the leve | operations (paused) | holdgated | `operations-holdgated.md` | | brainstorm | advisory | `brainstorm-advisory.md` | -### L6 — Fully Autonomous (15 agents) +### L6 — Autonomous (15 agents) Existing autonomous lanes can open issues, create PRs, and auto-merge on green CI. Auto-merge is off below L6; switching to L6 turns it on for every active repository, after which owners can toggle repositories individually. L6 PRs do not get the level hold, except outreach PRs into a repository the hive does not manage, which are still held for human review; outreach PRs into the hive's own repos auto-merge like any other lane's. Contributor PRs can merge through the contributor lane when the author has an operator-trusted tier or the conservative `auto_merge.contributor_tier.auto_promote_after_merged` clean-history ratchet admits them; reviewer verdict, CI, diff fences, soak and branch protection still apply. Hive never needs App Actions write for `action_required` workflow runs: when trusted same-repo bot/App/registry-trusted clanker PRs hit the workflow-approval banner, Hive posts `/ok-to-test` once per head and the repository's self-contained `hive-ok-to-test` workflow approves those runs with its `GITHUB_TOKEN`; if the workflow is absent or a `sentinel-alert` is present, Hive surfaces `workflow-approval-required:` / stage 085 for a human decision. Outreach handles community engagement. Reviewer stays advisory unless the self-authored review gate is enforcing: `review.require_approval: true` (or `governor.review_gate.mode: enforce`) makes the App self-authored sweep require same-head aggregate approval, while `requires_human`/incomplete verdicts can be cleared by a trusted human's normal GitHub Approve review on that exact head. For Hive-authored PRs, `review.self_authored_decision_policy: resolve` (the default) lets the reviewer resolve every decision question itself except those in a named risk class (permissions/credentials, security, sensitive paths, data loss/irreversible, merge/trust policy, external side effects, licensing/legal, unverifiable) or marked `hive-decision-risk: high`; set `escalate` to park every decision question at 085. Swarm `changes_requested`/`reject` still requires a fix or direct human merge. Use actual holds or GitHub branch rules for a universal merge stop. Adjudicator is the reviewer lane: it repairs, de-escalates, or recommends closing (and may close) escalated `needs-human` hive PRs, and never merges. Dedupe posts the same daily consolidation checklist as at L5 and stays recommendation-only. Telemetry and operations remain paused and use `ISSUES_AND_PRS`, so they never merge their own PRs. diff --git a/docs/content/hive/adr/0009-trajectory-review.md b/docs/content/hive/adr/0009-trajectory-review.md index 1fb17fa1..51e94e5a 100644 --- a/docs/content/hive/adr/0009-trajectory-review.md +++ b/docs/content/hive/adr/0009-trajectory-review.md @@ -19,8 +19,8 @@ Run a periodic trajectory-review lane from the governor tick. The lane snapshots running, non-exempt agents with their assigned intent and recent transcript, sends a bounded transcript tail to a second model through the existing OpenAI-compatible/LiteLLM endpoint, and asks for a compact JSON verdict -([reviewer](https://github.com/hivecommons/hive/blob/v5/src/pkg/trajectory/trajectory.go), -[lane](https://github.com/hivecommons/hive/blob/v5/src/pkg/trajectory/lane.go)). On divergence, the configured response is +([reviewer](https://github.com/hivecommons/hive/blob/v5/src/crux/trajectory/trajectory.go), +[lane](https://github.com/hivecommons/hive/blob/v5/src/crux/trajectory/lane.go)). On divergence, the configured response is to pause the agent and alert by default, or to alert only. The reviewer prompt instructs the model to judge direction, not single commands, and to answer non-divergent when unsure. @@ -51,7 +51,7 @@ still in progress can itself be the warning signal, so **"transcript bytes identical" is never a skip condition**. The lane now skips a review only when **all** of the following hold, as decided -by `skipDecision` in [lane.go](https://github.com/hivecommons/hive/blob/v5/src/pkg/trajectory/lane.go): +by `skipDecision` in [lane.go](https://github.com/hivecommons/hive/blob/v5/src/crux/trajectory/lane.go): 1. the agent has been reviewed at least once before; 2. the review is **not overdue** — fewer than `MaxSkipIntervals` (default 6) @@ -59,7 +59,7 @@ by `skipDecision` in [lane.go](https://github.com/hivecommons/hive/blob/v5/src/p 3. the agent manager reports the agent **idle** from its own state — the last kicked turn has ended (`KickOutcome` settled against `LastKick`), the agent is parked at the prompt awaiting CI, or it has never been kicked - (`trajectoryIdle` in [trajectory_view.go](https://github.com/hivecommons/hive/blob/v5/src/pkg/agent/trajectory_view.go)); + (`trajectoryIdle` in [trajectory_view.go](https://github.com/hivecommons/hive/blob/v5/src/crux/agent/trajectory_view.go)); 4. the manager has a known last-output time and it precedes the last review. If any condition fails the review runs. An unknown last-output time is treated diff --git a/docs/content/hive/adr/0012-skill-registry.md b/docs/content/hive/adr/0012-skill-registry.md index ffe3f2cc..1f80ae92 100644 --- a/docs/content/hive/adr/0012-skill-registry.md +++ b/docs/content/hive/adr/0012-skill-registry.md @@ -16,7 +16,7 @@ Hive a shareable catalog or version metadata. ## Decision Introduce `skillreg` as a concurrency-safe registry of named, versioned skill -files ([skill registry](https://github.com/hivecommons/hive/blob/v5/src/pkg/skillreg/skillreg.go)). A skill is a Markdown +files ([skill registry](https://github.com/hivecommons/hive/blob/v5/src/crux/skillreg/skillreg.go)). A skill is a Markdown body with optional YAML front matter for name, version, description, and tags. Missing skill directories, unreadable files, and malformed front matter are skipped with logs so one bad catalog entry does not prevent Hive from loading. diff --git a/docs/content/hive/adr/0017-podman-quadlet-lifecycle.md b/docs/content/hive/adr/0017-podman-quadlet-lifecycle.md index f9ff33a1..1274b191 100644 --- a/docs/content/hive/adr/0017-podman-quadlet-lifecycle.md +++ b/docs/content/hive/adr/0017-podman-quadlet-lifecycle.md @@ -126,7 +126,7 @@ readiness gate is not expressible below 5.0.0. The 5.6.0 fix is worth stating precisely rather than treating as a general "upgrade" argument. Podman -[#26105](https://github.com/containers/podman/issues/26105) generated +[#26105](https://github.com/podman-container-tools/podman/issues/26105) generated `--pod systemd-` instead of `systemd--pod`, so the container could not start — and by the reporter's description it bites only when the `.pod` unit does **not** set a custom `PodName=`. #4202's probe units set @@ -209,4 +209,4 @@ silently assume any of them. - [Podman Quadlet `.kube` compatibility spike](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-quadlet-kube-spike.md) — #4203. - [Podman support matrix](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-support-matrix.md) — which rootful/rootless × enforcing/advisory combinations this lifecycle may be installed into. - [`podman-systemd.unit(5)`](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) -- [Podman release notes](https://github.com/containers/podman/blob/main/RELEASE_NOTES.md) — the source for every version in the table above. +- [Podman release notes](https://github.com/podman-container-tools/podman/blob/main/RELEASE_NOTES.md) — the source for every version in the table above. diff --git a/docs/content/hive/adr/0025-central-stage-engine.md b/docs/content/hive/adr/0025-central-stage-engine.md index 742f506d..00d31f08 100644 --- a/docs/content/hive/adr/0025-central-stage-engine.md +++ b/docs/content/hive/adr/0025-central-stage-engine.md @@ -23,22 +23,23 @@ re-read-then-write `Enforce` that heals duplicate stage pills records transitions, regressions and history hops (`src/pkg/stagelabels/store.go:397`, `IsRegression` at `src/pkg/stagelabels/store.go:540`). The dashboard activity tracker and the -hub read that store and nothing else (`src/pkg/dashboard/stage_tracker.go:584-588`). +hub read that store and nothing else (`src/pkg/dashboard/stage_tracker.go:957-962`). **What is not central — three gaps.** 1. *Two derivation engines.* Issues are staged by `IssueStageFor` / - `SyncIssueStageLabels` (`src/pkg/github/issue_stage_labels.go:145`, - `src/pkg/github/issue_stage_labels.go:233`) from the enumeration bucket. PRs + `SyncIssueStageLabels` (`src/pkg/github/issue_stage_labels.go:205`, + `src/pkg/github/issue_stage_labels.go:337`) from the enumeration bucket. PRs are staged by the automerge engine: each lane calls `recordStage` - (`src/pkg/github/automerge/automerge_stage_labels.go:397`), observations are + (`src/pkg/github/automerge/automerge_stage_labels.go:365`), observations are sorted by `stageLanePrecedence` (self 3 > trusted 2 > contributor 1, - `src/pkg/github/automerge/automerge_stage_labels.go:167`), each is classified - by `classifyStage` (`src/pkg/github/automerge/automerge_stage_labels.go:806`) - into a ranked opinion (`stageRankNone..stageRankOwned`, - `src/pkg/github/automerge/automerge_stage_labels.go:619-624`), and - `resolveStage` picks the winner - (`src/pkg/github/automerge/automerge_stage_labels.go:946`). Shared concepts — + `src/pkg/github/automerge/automerge_stage_labels.go:177`), each is classified + by `ClassifyStage` (`src/pkg/stageengine/derive_pr_classify.go:261`) + into a ranked opinion (`prstage.RankNone..prstage.RankOwned`, + `src/pkg/stageengine/prstage/types.go:145-148`), and + `ResolveStage` picks the winner + (`src/pkg/stageengine/derive_pr_classify.go:454`). These PR helpers now + live in the engine (#13779); automerge keeps only gathering-side previews. Shared concepts — parking (045/085, `needs-human`, hold labels), author kind (070/071/072), adoption (074), terminal states, `origin/*` — are modelled twice with different field names, so every taxonomy change lands in both: @@ -49,11 +50,11 @@ hub read that store and nothing else (`src/pkg/dashboard/stage_tracker.go:584-58 [#12737](https://github.com/hivecommons/hive/pull/12737) (8), [#12856](https://github.com/hivecommons/hive/pull/12856) (44). 2. *Not one writer.* Automerge writes through its own - `engineStageLabeler` (`src/pkg/github/automerge/automerge_stage_labels.go:1428`); + `engineStageLabeler` (now retired in favour of `pkg/github/stage_writer.go`); the issue path uses `Client.StageLabeler` + `Enforce` (`src/pkg/github/stage_labeler.go:63`); the claim relay and ledger hooks write `stage/020` / `stage/030` directly as a side effect of a claim - (`src/cmd/hive/claimswire.go:196-201`, `src/cmd/hive/claimswire.go:158-171`, + (`src/cmd/hive/claimswire.go:236-238`, `src/cmd/hive/claimswire.go:176-178`, `src/pkg/github/issue_inprogress.go:126-141`, `src/pkg/github/issue_inprogress.go:186`, `src/pkg/github/stage_labeler.go:114`); the PR-claim sync writes 040 (`src/pkg/github/prclaims.go:1777`, `src/pkg/github/prclaims.go:2037`) and so diff --git a/docs/content/hive/adr/readme.md b/docs/content/hive/adr/readme.md index 780957a6..e7fa1a99 100644 --- a/docs/content/hive/adr/readme.md +++ b/docs/content/hive/adr/readme.md @@ -57,7 +57,9 @@ What becomes easier, harder, safer, or riskier because of this decision? - [ADR-0017: Quadlet `.container`/`.pod` units as the Podman persistent lifecycle](/docs/hive/adr/0017-podman-quadlet-lifecycle) - [ADR-0018: Shared dashboard design tokens and component layer](/docs/hive/adr/0018-dashboard-design-tokens) - [ADR-0019: Escalate to direction, spec, signal, or meta-issue instead of stalling](/docs/hive/adr/0019-escalation-over-stalling) +- [ADR-0020: Spek continuous convergence](/docs/hive/adr/0020-spek-continuous-convergence) - [ADR-0022: Opt-in redemption of earned Codex resets](/docs/hive/adr/0022-opt-in-earned-codex-reset-redemption) - [ADR-0023: Hive may merge PRs whose author could merge them](/docs/hive/adr/0023-trusted-author-automerge) - [ADR-0024: Provider credential lifecycle](/docs/hive/adr/0024-provider-credential-lifecycle) - [ADR-0025: One stage engine derives and writes every `stage/*` label](/docs/hive/adr/0025-central-stage-engine) +- [ADR-0026: Sandbox runtime selection](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0026-sandbox-runtime-selection.md) (Proposed) diff --git a/docs/content/hive/agent-configuration.md b/docs/content/hive/agent-configuration.md index 6f10caa3..4d85f496 100644 --- a/docs/content/hive/agent-configuration.md +++ b/docs/content/hive/agent-configuration.md @@ -108,7 +108,7 @@ older overlays, but new configs should use `bob.session_label`. ```yaml backend: copilot # the method: claude | copilot | goose | codex | pi | - # bob | aider | omp, or an inference backend: + # bob | aider | omp | openhands | omnigent, or an inference backend: # vllm | llm-d | litellm | watsonx | named gateway model: claude-sonnet-4-6 # model id for that method reasoning_effort: high # reasoning effort, for methods that have one: @@ -138,6 +138,10 @@ The dashboard shows a reasoning-effort dropdown next to the model picker for eve `backend: omp` launches the OMP CLI as a first-class dashboard method. Hive gives each agent its own durable HOME under `/data/home/agents/`, so `~/.omp/agent/agent.db` can hold several provider sign-ins for that one agent (for example `openai-codex` for the primary model and `anthropic` for an advisor) without sharing refresh-token writers across agents. Select models with their provider prefix (`provider/model`). The dashboard model probe runs `omp models --json` and uses each model's `thinking` list for the effort dropdown. At launch Hive passes `--approval-mode ${HIVE_OMP_APPROVAL_MODE:-yolo}`, `--model `, and `--thinking ` when set. +#### Omnigent (`omnigent`) agents + +`backend: omnigent` launches the Omnigent CLI as an experimental T3 backend. Omnigent provides an open-source meta-harness layer orchestrating underlying agent engines (Claude Code, Codex, Cursor, Pi, and custom harnesses). On contributor local launches, Omnigent has no OS sandbox wired by Hive, so local mode refuses by default without `HIVE_OMNIGENT_DANGEROUSLY_RUN_UNCONFINED=1`. Container mode is the default and recommended boundary. Every launch is `omnigent run --server local [--model ]`: `run` opens the agent REPL (bare `omnigent` only starts a background server) and `--server local` pins a local Omnigent server, so remote-server mode is unsupported. Omnigent's internal policy engine and sandbox evaluate additively, while Hive's proxy remains authoritative on all repository writes. See [`backend-omnigent-cli-contract.md`](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-omnigent-cli-contract.md). + > The dashboard also offers **gemini** as a live method (with live model discovery); as a persisted `backend:` value in `hive.yaml`, stick to the validated list above. ### Reviewer independence @@ -368,7 +372,7 @@ Presets map onto modes (`advisory` denies issue and PR creation, `issues-only` d ### Replicated agents -Set `replicas: N` on a declared agent to run a small pool with the same prompt, backend, model, mode, channel, tool, and metadata settings. `N` defaults to `1` and is capped at `5`; config load fails if it is outside `1..5`. Hive materializes derived names as `-2`, `-3`, ... (`scanner`, `scanner-2`, `scanner-3`). Do not declare those derived names yourself: a real `scanner-2` entry collides with `scanner: { replicas: 2 }` and is rejected. Derived replicas get their own IDs and bead directories (`/data/beads/scanner-2`) but inherit the base agent's kick template/prompt selection. Runtime-derived replicas are stripped before saving and recreated on the next load. +Set `replicas: N` on a declared agent to run a small pool with the same prompt, backend, model, mode, channel, tool, and metadata settings. `N` defaults to `1` and is capped at `5`; config load fails if it is outside `1..5`. Hive materializes derived names as `-2`, `-3`, ... (`scanner`, `scanner-2`, `scanner-3`). Do not declare those derived names yourself: a real `scanner-2` entry collides with `scanner: { replicas: 2 }` and is rejected. Derived replicas get their own IDs and bead directories (`/data/beads/scanner-2`) but inherit the base agent's kick template/prompt selection. Runtime-derived replicas are stripped before saving and recreated on the next load. For the reviewer lane, `review.reviewers.autoscale` can raise and lower the materialised count at runtime between `min` and `max`; the declared `replicas` then only seeds the initial size. See [review-swarm.md](https://github.com/hivecommons/hive/blob/v5/src/docs/review-swarm.md#reviewer-autoscale-reviewreviewersautoscale). ### Trigger channels @@ -554,7 +558,9 @@ Config validation accepts only `off`, `assist`, or empty; the dashboard and ## Methods: subscription CLIs vs self-hosted inference -`backend:` picks one of two families. They differ in how you authenticate and where model lists come from: +`backend:` picks one of the CLI or inference families below. Hosted execution +is a separate `runs.external.` binding and is never a `backend:` value: +it has no local binary, CLI login, or live model discovery. | Method | Family | Auth | Model discovery | |---|---|---|---| @@ -566,6 +572,7 @@ Config validation accepts only `off`, `assist`, or empty; the dashboard and | `llm-d` | inference | endpoint + optional key — no login | **live** — `/v1/models` | | `litellm` | inference | endpoint + API key — no login | **live** — `/v1/models`, entitlement-filtered per key | | `openrouter` (gateway name) | inference | Model Gateway key or scan-to-fund flow — no CLI login | **live** — OpenRouter `/v1/models`, plus curated fallback | +| `claude-managed-agents` | hosted | API key reference (`api_key_env` or `/data/secrets` `api_key_file`) — no CLI login | pinned agent/model version; no discovery | Two rules of thumb: @@ -576,6 +583,50 @@ Two rules of thumb: Every Model Gateway (and the bob backend) also accepts an optional `key_name` — a human-chosen LABEL for the configured key, e.g. `key_name: openrouter-prod-key`. It is safe-to-show metadata, not a secret: the dashboard's gateway row displays it as "Using key: ``", or "(unnamed)" when no label is set, so operators can tell keys apart without ever seeing the value. For gateway keys, the row and settings/status APIs also expose `keySHA256`, the lowercase SHA-256 of the exact effective key string Hive will inject through the inference proxy; this lets operators compare Hive with LiteLLM/OpenRouter key-hash displays without revealing the secret. Saving a replacement key refreshes live inference proxy routes for running agents, so the next kick uses the new key without an agent restart. See [`inference-backends.md`](https://github.com/hivecommons/hive/blob/v5/docs/inference-backends.md) for a full YAML example. +### Claude Managed Agents hosted execution + +Claude Managed Agents is an external hosted engine, not a `backend:` value. +The prototype is linked only with `extwork_claude_managed_agents` and is +selected under `runs.external.claude_managed_agents`. It requires a Claude +`self_hosted` environment in the contributor-private spoke with +`forced-egress` or `proxy-only` policy. The worker receives its environment key +and per-session work secret; it never receives `ANTHROPIC_API_KEY`. + +```yaml +runs: + external: + claude_managed_agents: + enabled: false + mode: shadow # report-only is the dispatch posture + endpoint: https://api.anthropic.com + workflow_version: claude-managed/2026-04-01 + agent_id: agt_private + agent_version: 1 + toolset_version: hive-tools/1 + environment_id: env_private + environment_type: self_hosted + network_policy: forced-egress + api_key_env: ANTHROPIC_API_KEY # reference only; never the value + model: claude-sonnet-4-6 + state_dir: /data/extwork/claude-managed-agents + daily_budget_cents: 2500 # hard USD 25/day maximum + github_write_path: none +``` + +The adapter performs an authenticated `GET /v1/environments/{id}` before +creating a session, pins the agent/toolset versions, sends the bounded prompt +and repository workspace, and records `span.model_request_end` token events in +`data.metrics_dir`. Event IDs are deduplicated across restarts. Session +`list_cost`, active seconds, and web-search counts are kept in the durable +managed-agents ledger; the session budget is the remaining daily cents. Set +`github_write_path` to `none` or `proxy` for a report-only spoke. Any other +value (a direct GitHub resource, vaulted secret, GitHub MCP path, or +`direct_github_write`) is rejected when the adapter is built. `endpoint` must +be `https` unless its host is loopback, because the vendor API key is sent on +every request. The hosted worker has no GitHub path in this prototype; the +`POST /pulls` and `PUT /pulls/{n}/merge` proxy denial tests from #14124 are not +yet covered. + Kubernetes manifests for deploying inference backends (vllm Deployment, EPP RBAC, kustomization) are in [`deploy/inference/`](https://github.com/hivecommons/hive/blob/v5/src/deploy/inference/). Agents can inspect peer panes with `hive-panes [lines]` when the deployment includes `src/deploy/hive-panes.sh`. It reads pluk JSONL logs from `/var/run/pluk/logs`, skips the calling agent named by `HIVE_PROXY_AGENT`, strips terminal escapes, and prints the last N raw-output lines for every other agent. Use it for situational awareness; it is read-only and does not attach to another agent's tmux session. See [Agent peer-awareness logging](https://github.com/hivecommons/hive/blob/v5/src/docs/agent-logging.md) for the pluk log format, when it's available, and its retention behavior. @@ -726,7 +777,7 @@ You don't have to design a roster. Hive ships six **ACMM packs** (`level-1.yaml` | L3 | Quality-Gated (Measured) | quality opens issues and test PRs gated by literal `hold`; the rest stay advisory | | L4 | Security-Aware (Adaptive) | scanner/guide file issues; quality, ci-maintainer, and sec-check can open PRs gated by literal `hold` | | L5 | Semi-Autonomous (Semi-Automated) | issues **and** PRs; PRs are gated by literal `hold`; humans batch-approve | -| L6 | Fully Autonomous | auto-merge on green CI; no level hold, except cross-repo outreach PRs (target repo not managed by this hive) remain held | +| L6 | Autonomous | auto-merge on green CI; no level hold, except cross-repo outreach PRs (target repo not managed by this hive) remain held | Applying a level **reconciles the whole roster**, not just the diff: missing agents are created (as overlay files in `/data/agent-configs/`), existing agents are merged — pack values fill blanks, but your explicit `backend:`, `model:`, and `enabled: false` always win — and the level's `kick_template`, `mode` and `on_demand` are updated so the agent's *policy* matches the level (an `on_demand` you toggled yourself in the agent's settings dialog is operator-owned and left alone; leaving on-demand starts the agent, entering it stops it). A failed agent doesn't abort the rest; the level is only recorded as cleanly applied when every agent reconciled. diff --git a/docs/content/hive/architecture.md b/docs/content/hive/architecture.md index 6cf54a77..10fd53bc 100644 --- a/docs/content/hive/architecture.md +++ b/docs/content/hive/architecture.md @@ -295,7 +295,7 @@ flowchart LR L3["L3 Quality-Gated
quality opens PRs"] L4["L4 Security-Aware
more agents file/PR"] L5["L5 Semi-Autonomous
all PRs, hold gated"] - L6["L6 Fully Autonomous
auto-merge switches on"] + L6["L6 Autonomous
auto-merge switches on"] L1 --> L2 --> L3 --> L4 --> L5 --> L6 ``` @@ -306,7 +306,7 @@ flowchart LR | L3 | Quality-Gated (Measured) | `quality` opens PRs gated by literal `hold` about testing gaps, coverage, and CI health; others advisory. This measurement foundation is what earns automation at higher levels | | L4 | Security-Aware (Adaptive) | All agents file issues; quality, ci-maintainer, and sec-check can open PRs gated by literal `hold` | | L5 | Semi-Autonomous (Semi-Automated) | All agents open PRs — every PR carries literal `hold` for human review | -| L6 | Fully Autonomous | Agents open PRs and **auto-merge on green CI**; switching to L6 enables auto-merge for every active repo, owners may toggle repos afterward, no level hold, except cross-repo outreach PRs (target repo not managed by this hive) remain held | +| L6 | Autonomous | Agents open PRs and **auto-merge on green CI**; switching to L6 enables auto-merge for every active repo, owners may toggle repos afterward, no level hold, except cross-repo outreach PRs (target repo not managed by this hive) remain held | `supervisor` is always advisory. The full matrix is in [`acmm-policy-matrix.md`](/docs/hive/acmm-policy-matrix). diff --git a/docs/content/hive/contributor-relay.md b/docs/content/hive/contributor-relay.md index 4c4a3490..67210479 100644 --- a/docs/content/hive/contributor-relay.md +++ b/docs/content/hive/contributor-relay.md @@ -194,7 +194,7 @@ Important environment variables: | `HIVE_COMMONS_STRATEGY` | `ranked` | Multi-hive routing strategy for The Commons. `ranked` solicits the first subscribed hive and falls through only when it has no work; `spread` rotates with rank weights and periodic mixing; `neediest` scores each hub's `/api/contribute/status` by `actionable_items` with a small boost for idle contributor capacity (`total_registered - active_contributors`) when those fields are present. The relay evaluates this only when it is ready for another task, never while a lease is in flight. | | `HIVE_COMMONS_SPREAD_MIX_EVERY` | `7` | In `spread`, every N completed/failed tasks the next hive is chosen randomly from the weighted rank cycle instead of by cursor. Set `0` to disable mixing. | | `HIVE_COMMONS_NEEDIEST_REFRESH_MS` | `60000` | In `neediest`, how often to refresh each subscribed hub's `/api/contribute/status` cache. Set `0` to rely on auth-time/manual refreshes only. | -| `AGENT_BACKEND` | `claude` | CLI/backend to run (`claude`, `copilot`, `goose`, `bob`, `codex`, `pi`, `aider`, `litellm`, `agy`, `opencode`, `kilo`, `muse`, `omp`, `openhands`, depending on image support and credentials). `omp` is interactive-only: Hive starts normal `omp --model ` in the prepared tmux cwd and passes no fabricated permission flags. It has no verified local confinement mechanism, so local mode refuses it without `HIVE_OMP_DANGEROUSLY_RUN_UNCONFINED=1`; container mode is the supported boundary. `agy` has the same confinement limit. `opencode`, `kilo`, `muse`, and `openhands` only run headless (`CONTRIBUTOR_MODE=headless`) — hive has no interactive-tmux wiring for them. | +| `AGENT_BACKEND` | `claude` | CLI/backend to run (`claude`, `copilot`, `goose`, `bob`, `codex`, `pi`, `aider`, `litellm`, `agy`, `opencode`, `kilo`, `muse`, `omp`, `openhands`, `omnigent`, depending on image support and credentials). `omp` is interactive-only: Hive starts normal `omp --model ` in the prepared tmux cwd and passes no fabricated permission flags. It has no verified local confinement mechanism, so local mode refuses it without `HIVE_OMP_DANGEROUSLY_RUN_UNCONFINED=1`; container mode is the supported boundary. `agy` and `omnigent` have the same confinement limit; `omnigent` is launched as `omnigent run --server local [--model ]` (`run` opens the agent REPL — bare `omnigent` only starts a background server — and `--server local` keeps the session off any remote server configured by `omnigent login`). `opencode`, `kilo`, `muse`, and `openhands` only run headless (`CONTRIBUTOR_MODE=headless`) — hive has no interactive-tmux wiring for them. | | `AGENT_MODEL` | unset (backend default) | Optional model override passed to the contributor agent (e.g. `claude-sonnet-4-6`, `gpt-4o`, `gemini-2.5-pro`). Declared to the hive when the relay connects. | | `AGENT_REASONING_EFFORT` | unset | Reasoning effort override. Consumed by `codex` (`-c model_reasoning_effort`), by `agy` (`--effort low\|medium\|high`, required whenever a model is set, else agy ignores the model), by `muse` (`--reasoning-effort none\|minimal\|low\|medium\|high\|xhigh\|max\|ultra`, applied with or without a model; a value outside that set is dropped rather than passed, because muse exits 2 on it), and by `claude` (`--effort low\|medium\|high\|xhigh\|max`, applied with or without a model; a value outside that set is dropped the same way, and unset leaves Claude Code at its own default - [#8377](https://github.com/hivecommons/hive/issues/8377)). Ignored by other backends, including inference routes such as `litellm` that drive the claude binary. | | `HIVE_CONTRIBUTOR_KNOWLEDGE_EXPORT_MAX_FACTS` | `80` | Maximum entries in the `/api/knowledge/export` response when it is fetched with a contributor registration token. Owner/API-token exports remain full; contributor startup context receives a bounded summary with a truncation marker and should fetch more specific entries on demand with `hive knowledge` or the Hive MCP knowledge tool. | @@ -231,6 +231,7 @@ Important environment variables: | `HIVE_KILO_DANGEROUSLY_RUN_UNCONFINED` | unset | **Required** for `just contribute-hive kilo local` to launch at all. kilo's `--auto` is an unattended auto-approve flag, not a boundary; kilo has no verified sandbox, filesystem allowlist, or command deny-list hive can wire. | | `HIVE_OPENHANDS_DANGEROUSLY_RUN_UNCONFINED` | unset | **Required** for `just contribute-hive openhands local` to launch at all. The bare `openhands` CLI runs the agent on the host with no sandbox; `--always-approve` is approval, not a boundary, and its Docker sandbox is reachable only through `openhands serve`, not the headless one-shot path. | | `HIVE_OMP_DANGEROUSLY_RUN_UNCONFINED` | unset | **Required** for `just contribute-hive omp local` to launch at all. OMP has no sandbox, filesystem allowlist, or command deny-list Hive can wire; local mode refuses to launch without this. | +| `HIVE_OMNIGENT_DANGEROUSLY_RUN_UNCONFINED` | unset | **Required** for `just contribute-hive omnigent local` to launch at all. Omnigent has no OS sandbox wired by Hive on the local CLI path; local mode refuses to launch without this. | ### Where each backend reads its instructions @@ -254,6 +255,7 @@ mode fixed for Goose in [#2393](https://github.com/hivecommons/hive/issues/2393) | `openhands` | `AGENTS.md`, `CLAUDE.md` | | `muse` | `AGENTS.md`, `CLAUDE.md` | | `omp` | `AGENTS.md`, `CLAUDE.md` | +| `omnigent` | `AGENTS.md`, `CLAUDE.md` | | anything else | `CLAUDE.md` only — the `*` fallback | A backend that reads neither `CLAUDE.md` nor one of the names above falls into @@ -302,7 +304,7 @@ OS-enforced — Seatbelt/bubblewrap/ProcessContainer depending on platform), gated on the installed CLI actually supporting the flag; opencode gets a command-name deny-list via its own `permission.bash` config (a floor, not a filesystem boundary — opencode has no OS sandbox); goose, agy, bob, pi, aider, -kilo, omp, and openhands have no confinement mechanism this repo can wire at all, and local +kilo, omp, openhands, and omnigent have no confinement mechanism this repo can wire at all, and local mode for them **refuses to launch** unless the operator sets that backend's own `HIVE__DANGEROUSLY_RUN_UNCONFINED=1`. See [sandbox-isolation.md](https://github.com/hivecommons/hive/blob/v5/src/docs/sandbox-isolation.md)'s per-backend confinement matrix @@ -359,6 +361,7 @@ The relay speaks to whatever backend you set up — pass it to `contribute-setup | `muse` | Muse Code (`curl -fsSL https://dev.meta.ai/install.sh | bash`). Headless-only: `muse exec ""` is its documented non-interactive sub-command. Auth is `META_API_KEY` (which muse says always takes priority) or `~/.config/muse/auth.json` written by `muse login` / `muse auth set --api-key-stdin`. Set `AGENT_MODEL` to a catalog id from `GET https://api.meta.ai/v1/models`, queried **from the machine that will run muse** — the catalog is caller-dependent (a workstation and an AWS container saw different model sets for the same key on 2026-09-08), and an id the caller cannot see fails at task time. **muse brings its own OS sandbox** (bubblewrap/seccomp on Linux, seatbelt on macOS), on by default — hive narrows it rather than refusing local launch. Installed in both images, pinned by version and per-arch SHA-256 from muse's release manifest. | | `omp` | Oh My Pi — **interactive-only**: Hive starts ordinary `omp --model ` in the prepared tmux cwd, with no fabricated permission flags; there is no headless wiring for it. Sign in once on the host (run `omp`, complete its provider setup, quit): container mode stages an allowlist of `~/.omp/agent` — only the selected provider's credential rows — into the container ([#7678](https://github.com/hivecommons/hive/issues/7678)), and the contributor image ships a pinned, checksummed `omp` ([#7661](https://github.com/hivecommons/hive/issues/7661)) so `just contribute-hive omp` runs without an `omp` on the host. No verified local confinement mechanism, so local mode refuses without `HIVE_OMP_DANGEROUSLY_RUN_UNCONFINED=1`; container mode is the supported boundary. Full setup notes: [docs/backend-setup.md](https://github.com/hivecommons/hive/blob/v5/docs/backend-setup.md) | | `openhands` | OpenHands CLI (`uv tool install openhands --python 3.12`; PyPI `openhands`, MIT). Headless-only: `openhands --headless -t "" --always-approve --override-with-envs`. Auth is `LLM_API_KEY` (plus `LLM_BASE_URL` for an OpenAI-compatible gateway) or the `~/.openhands/settings.json` an interactive first run writes; `AGENT_MODEL=provider/model` (any litellm id) is forwarded as `LLM_MODEL` — the CLI has no `--model` flag and ignores `LLM_*` without `--override-with-envs`. No sandbox on this path, so local mode refuses without `HIVE_OPENHANDS_DANGEROUSLY_RUN_UNCONFINED=1`; not shipped in the contributor image (needs Python 3.12). Upstream has marked the CLI repo as no longer actively maintained in favour of Agent Canvas — T3 experimental. Full setup notes: [docs/backend-setup.md](https://github.com/hivecommons/hive/blob/v5/docs/backend-setup.md) | +| `omnigent` | Omnigent CLI ([github.com/omnigent-ai/omnigent](https://github.com/omnigent-ai/omnigent), Apache-2.0). Meta-harness over Claude Code, Codex, Cursor, Pi, and custom agents. Auth uses ambient provider API keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`), Databricks profiles (`~/.databrickscfg`), or session tokens in `~/.omnigent/auth_tokens.json` / `~/.omnigent/config.yaml`. Launched as `omnigent run --server local`; remote-server mode is unsupported. No OS sandbox wired by Hive on the local CLI path, so local mode refuses without `HIVE_OMNIGENT_DANGEROUSLY_RUN_UNCONFINED=1`; container mode is the recommended boundary and stages only `auth_tokens.json` and `config.yaml` from `~/.omnigent`. Omnigent's internal policy engine and sandboxing compose additively, while Hive egress/API proxy remains strictly authoritative on all GitHub writes — T3 experimental. See [backend-omnigent-cli-contract.md](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-omnigent-cli-contract.md). | ## Running multiple backends under one account @@ -995,6 +998,7 @@ Two admission behaviors are worth knowing when your relay seems idle: - **An issue somebody has *claimed* is not offered — and a clanker already on it is pulled off when a higher rank claims it.** The PR-claim ledger above only knows about work that has reached a pull request. Since [#8380](https://github.com/hivecommons/hive/issues/8380) the hub also keeps a **worker-claim ledger**: who is on an issue *right now*, ranked **human > agent > contributor > external**. A person claims from the terminal (`hivectl claim owner/repo#N`) or the API (`POST /api/claims/{owner}/{repo}/{number}`); a governor kick *lists* each issue it hands one of the hive's own agents — no GitHub comment, no claim, just a 30-minute hold that keeps your session and other agents off it while the kick is live — and the hub records the **agent** claim on the agent's first start signal on that issue (its own comment, label or claim request through `hive-open-issue`, or a `hive-open-pr` request naming it; [#10527](https://github.com/hivecommons/hive/issues/10527) — a comment that is an explicit *deferral* (see the [recognized forms](https://github.com/hivecommons/hive/blob/v5/src/docs/hive-open-issue.md#comment)) is posted but records no claim, so a parked issue is not shown in progress for two hours after its blocker lands; [#12387](https://github.com/hivecommons/hive/issues/12387)); and the relay records a **contributor** claim for your session the moment it leases you a task, releasing it when the task completes, is abandoned, is yanked or its lease expires. `selectTask` excludes every issue another holder claims exactly as it excludes leased items, and the scheduler withholds them from kicks under *In Flight*. Precedence is enforced on write: a higher rank **takes over** and the displaced holder is told to stop — for a relay session that means the hub yanks that one task (`task_revoke`, generation bumped so a late completion is fenced) and hands the session its next admissible item in the same call, leaving your other sessions alone; for an agent or an external bot it means a `🔁` comment addressed to it plus a `preempted:` label on the issue. Same rank warns unless forced; a lower rank is refused. Claims lapse on a per-kind TTL (`claims.human_ttl_s` 4h, `agent_ttl_s` 2h, `contributor_ttl_s` 30m — the relay lease length — capped by `max_ttl_s` 24h) and `claims.enabled: false` turns the whole mechanism off. Every transition is mirrored onto the issue as a comment carrying a `` marker (or `` on takeover) and a `claimed` label, so a relay that only reads GitHub can still see it was preempted. - **Claims expire.** Ledger entries live 72 hours — refreshed while the PR stays open, or anchored at the merge for a merged claim, so a settled issue is suppressed for 72h after its fix lands and nothing is stranded forever. An **open** claiming PR that goes red on a required check and stale releases the issue back to the queue; merged claims never take that valve — a fix already on `main` cannot go red. - **A repository the App cannot mint a token for is excluded, not fatal.** Since C4 the `task_assign` token is scoped to the chosen item's repository, so GitHub's `422 There is at least one repository that does not exist or is not accessible to the parent installation` means *that repo* — renamed, deleted, or removed from the installation — and nothing about the tier or the App key. It used to fail the whole selection with `token_mint_failed`, and because selection is deterministic the same doomed item was chosen on every 30 s retry: one stale entry in the project repo list (a renamed repo) wedged a hive for every contributor while `actionable_items` sat at 19 ([#7869](https://github.com/hivecommons/hive/issues/7869)). Now the claim is rolled back, the repo is excluded from selection for `mintFailureRepoCooldown` (10 min, so the fleet is not re-discovering it every retry, but the operator's fix takes effect without a restart), and the *same* call selects again from the remaining candidates (bounded at three such skips). If nothing else is admissible the reason is `repo_unmintable` rather than `no_matching_work` or `token_mint_failed`, and the hub log names the repo with a pointer at the repo list. Non-repo mint failures (5xx, bad key, an unaccepted permission) keep the `token_mint_failed` contract and exclude nothing — they would recur for any candidate. +- **An App with no installation says so.** When `github.installation_id` is `0` — the App is not installed on the hive's org, or discovery never found it — no token can be minted for any repository. The hub answers `task_unavailable` with reason `app_not_installed` and a `message` naming the org and the App install link, without calling GitHub and without excluding any repo ([#14002](https://github.com/hivecommons/hive/issues/14002)). Before this, GitHub's 404 for installation `0` was read as a repo-scope failure and contributors saw `repo_unmintable`, which points at the repo list rather than the missing installation. The relay prints the message under the reason line; the fix is the operator's (install the App on the org), never the contributor's. ## Contributor repository scope @@ -1219,6 +1223,8 @@ The one place a declaration DOES shape dispatch is the stage-072 contributor nud The hub records the mode it used in the nudge comment's marker (`mode=spawn|preempt|queued|comment`) and exposes queue depth / in-flight as `fix_queue` on the contributor profile API. +**When the relay fails the task.** `task_failed` for a fix's `task_id` does not settle it as done: its PRs go back in the hub's queue (same place by age) and are not interrupted back in for 5 minutes, though the relay's next `ready` serves them at once. After three failed dispatches a PR leaves the fix queue and the stage engine's re-nudge / adoption path takes over. A dispatch the relay fails within one second of the nudge is not reported as delivered: the nudge comment reads `mode=queued`, says the relay could not take the task (quoting the relay's `message`/`reason`), and the stage log carries `relay_failed=true` (hivecommons/hive#14668). + #### What the operator sees (Operations → Connected contributor agents) Each row of the "Connected contributor agents (ClankeR)" list on `/contribute/operations` reads the same declaration back (hivecommons/hive#12502), so an operator can check whether the rework the stage labels promise is actually reaching the agent. The row is fed by `/api/contribute/fleet`; each `clankers[]` entry carries: @@ -1639,17 +1645,26 @@ there. A relay that is offered staged run work must declare `run-stage` in `capabilities.relay_capabilities`; otherwise the hub refuses that staged item instead of silently downgrading it to an unstaged task. -External-execution items add engine-scoped tokens: `ext-exec/flue` and -`ext-exec/omp` ([#8361](https://github.com/hivecommons/hive/issues/8361)). A -run-stage item that names an external engine (`ext_exec: flue` or -`ext_exec: omp`) is offered only while the operator has enabled that engine's -binding (`runs.external.flue`, `runs.external.omp`) AND the relay declares -that engine's token in `capabilities.relay_capabilities`. A relay without the -token, or a hub with the binding off, refuses that item; it is never handed -out as ordinary local work, and declaring one token grants nothing for the -other engine. The token grants nothing by itself: the engine still receives -only a bounded context bundle, and the hub still decides acceptance of its -receipt. +External-execution items add engine-scoped tokens: `ext-exec/flue`, +`ext-exec/omp`, and `ext-exec/claude-managed-agents` +([#8361](https://github.com/hivecommons/hive/issues/8361), +[#14124](https://github.com/hivecommons/hive/issues/14124)). A run-stage item +that names an external engine (`ext_exec: flue`, `ext_exec: omp`, or +`ext_exec: claude-managed-agents`) is offered only while the operator has +enabled that engine's binding AND the relay declares that engine's token in +`capabilities.relay_capabilities`. A relay without the token, or a hub with +the binding off, refuses that item; it is never handed out as ordinary local +work, and declaring one token grants nothing for another engine. The token +grants nothing by itself: each engine receives only a bounded context bundle, +and the hub still decides acceptance of its receipt. + +**Claude Managed Agents self-hosted host.** The +`ext-exec/claude-managed-agents` token is an opt-in for the private-spoke +hosted adapter. The relay sends no vendor key, GitHub token, or dashboard +credential; Hive sends the bounded prompt/repository context only after the +normal lease authority is durable. The adapter keeps the session in advisory +`shadow` or `report-only` mode, refuses direct GitHub write paths, and records +token and list-cost metering in the spoke ledger with a hard USD 25 daily cap. **OMP workbench as a contributor host** ([#6899](https://github.com/hivecommons/hive/issues/6899)). An already-running OMP workbench does not embed the relay; it registers as a @@ -1921,6 +1936,25 @@ If the session cannot be recreated (tmux binary gone, server socket unwritable), Note that this is different from the poisoned-server case above: a recreated session inherits the server's working directory, so if `pane_current_path` shows `(deleted)` you still need `tmux kill-server`. +## Troubleshooting: `Hostname/IP does not match certificate's altnames` + +Symptom: every connect attempt logs + +```text +Connecting to wss://hosted-.hive.hivecommons.dev/api/contribute/ws... +WebSocket error: Hostname/IP does not match certificate's altnames: Host: hosted-.hive.hivecommons.dev. is not in the cert's altnames: DNS:*.hive.hivecommons.dev, … +Connection closed (code=1006 …) Reconnecting in 16000ms... +``` + +Cause: the hosted fleet moved apex from `*.hive.hivecommons.dev` to `*.hive.hivecommons.dev` on 2026-09-04. A `~/.config/hive/contributor.env` written by `just contribute-setup` before that date still carries the old host on its `HIVE_HUB` line. The old apex answers HTTP with a 301, but a WebSocket handshake does not follow redirects, and the hosted subdomains are outside the current certificate, so the TLS handshake fails before any redirect could be seen. + +What the relay does now: + +- **Known retired apex** — `contribute-hive` (and the relay itself, including on a `SIGUSR1` reload) rewrites `*.hive.hivecommons.dev` to `*.hive.hivecommons.dev` in memory, keeping the per-hive label, and warns once naming the file to edit. Nothing is written back to `contributor.env`; update the `HIVE_HUB` line there (or re-run `just contribute-setup`) to silence the warning. +- **Any other hostname mismatch** — the relay stops redialing that URL (no more `Reconnecting in …` loop), prints one remediation line naming `contributor.env`, and, for a hosted hive, asks the hub's `/api/registry` for the hive's current `dashboardUrl` and retries there **once**. `relay-status.json` reports the parked hub as `readiness: hive-moved`. A standalone hive (no hub to ask) is parked immediately. + +Fix: `export HIVE_HUB=wss:///contribute` for this run, and change the `HIVE_HUB` line in `~/.config/hive/contributor.env` to match; or `just contribute-setup ` to pick the hive again from the hub's listing. Your registration token is unchanged by the move. + ## Protocol compatibility Both sides state a contributor-protocol version: the hub advertises its own on `auth_ok`, and the relay declares `relay_protocol_version` in `auth_response`. Since [#2547](https://github.com/hivecommons/hive/issues/2547) both sides also **compare** them, so an old relay against a new hub is something you are told about rather than something you infer from misbehaviour: diff --git a/docs/content/hive/dashboard-sections.md b/docs/content/hive/dashboard-sections.md index 396d6248..b5abbcce 100644 --- a/docs/content/hive/dashboard-sections.md +++ b/docs/content/hive/dashboard-sections.md @@ -99,6 +99,7 @@ How busy Hive thinks your projects are right now, and how often it wakes its wor - The Governor picks a mode by comparing the amount of actionable work with three thresholds. Actionable work is work Hive could do now, with nothing waiting on a person. - The **actionable issues** and **actionable PRs** tiles use the same count as the Overview's Actionable now tile. The two always agree. - The **hold** list names every held item. Held means someone parked it with a hold label. +- The compact strip at the top of the Agents view also shows **running** — how many workers are mid-round right now, and the estimated spend the whole fleet has accrued in the current clock hour (`3 · $1.20 this hour`). The hour figure is the rise in the cumulative estimate since the top of the hour; `$–` means the hive has not yet taken a reading this hour. - The **cadence table** shows, for each worker, how often it runs in each mode. Continuous means it runs again as soon as it finishes. - The **budget** shows how much of the AI spending allowance for the current period has been used. - The **next kick** estimate follows the eval tick, which is longer while GitHub webhooks are trusted (`/api/status.webhooks.mode` reads `webhook`) and drops back to `governor.eval_interval_s` when they degrade (`polling`). How that decision is made is in [webhooks.md](https://github.com/hivecommons/hive/blob/v5/src/docs/webhooks.md#fallback-when-webhooks-degrade). @@ -338,6 +339,7 @@ One card per repository Hive looks after, listing its open issues and pull reque **How the numbers are worked out.** - The cards use the same bands as the Overview charts. See the Overview entry for each band's meaning. +- The small label above each repository name is its code hosting service. On GitHub it is the host, such as `github.com`, with `· Enterprise` on GitHub Enterprise. On GitLab or Gitea/Forgejo (`project.forge`) it names the service and its host, such as `🦊 GitLab · gitlab.example.com`. The card's links then go to that host. The badge in the section header names the same forge when every card is on it, and shows `N forges` when a `project.repo_policies[].forge` override puts cards on different forges. Each repository in `/api/status` carries this as `forge` (`github`, `gitlab` or `gitea`) and, for GitLab and Gitea, `forgeUrl`. See [Forges](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md). - Small pills on each item show signals, such as a hold, a failing check or no recent activity. - A `🔗` pill on an issue means Hive has verified an open or merged pull request covers it. Hover it: "closes via PR #N" means merging the PR closes the issue; "covered by PR #N (partial)" means the PR says `Refs #N` and merging it does not close the issue. The pill comes from Hive's own PR-claim evidence, so it appears on every issue labelled `🐝 covered by PR` — including ones Hive has parked in the "covered by PR" group because an outside PR already fixes them — and never depends on GitHub having a closing link. - A red pull-request pill means the sentinel flagged it for a possible security override, privilege escalation or codebase damage. Hive will not merge it until a maintainer reviews it and removes the sentinel label. Pull requests from trusted authors usually get a sentinel comment instead of the label, so they are usually not red. @@ -355,6 +357,7 @@ One card per repository Hive looks after, listing its open issues and pull reque **Settings that change it.** - The repositories Hive watches are set in **Settings → Projects**. +- `project.forge` (or a repo's `project.repo_policies[].forge` / `forge_url` override) and the `gitlab:` or `gitea:` instance URL set the host label and links. See [Forges](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md). - The `dashboard.issue_bands` settings change the band rules. - See [Repository card legend, issue bands, and PR bands](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard.md#repository-card-legend-issue-bands-and-pr-bands). @@ -615,6 +618,14 @@ More about the line: - **Updates.** The line updates with each worker update, every 10 seconds by default, so a change shows within about 15 seconds. - **Privacy.** Titles come only from the issue and pull request lists the dashboard already shows you. If it shows you no repositories' issues, the line shows no item numbers either. +**The run line.** While a worker is working, a second line under **Now:** reads like `$0.42 · 38.1K tok · 4m 12s · tests ✓ · 3 files`. The same line is pinned in the worker's detail panel header. It is built only from figures Hive already keeps: + +- **`$` and `tok`** are the estimated cost and tokens of *this* round of work: the worker's cumulative token count × public list price (the same basis as the Cost section), minus what it was when Hive started the round (the last kick). Hive remembers the starting point, so a page reload does not reset the figure. `$–` means the token collector has no figure for this worker yet. +- **Elapsed** counts from the start of the round. +- **`tests ✓` / `tests ✗` / `tests –`** is the last test-runner summary visible in the worker's terminal (`go test` `ok`/`FAIL`, pytest `N passed`/`N failed`, jest/vitest `Tests:` lines). `–` means no result line is visible. +- **`N files`** is the last `N files changed` git summary visible in the terminal; `–` means none is. +- It updates with each worker update and disappears when the round ends. The **spend (range)** row further down the card is unchanged: that is the worker's spend over the Cost section's timeframe, not this round's. + **What it is good for.** Use it to pause a worker, restart a stuck one, or change its settings. Fix any card that says it needs a login. Read the **Now:** lines to see what each worker is on without opening its log. **Example.** A card says "needs login". You click **Login** and sign in again. Another card says `Now: working — no issue or pull request yet · started 50 min ago`. That worker has changed nothing through the hive for 50 minutes, so you open its log to see what it is doing. @@ -631,7 +642,7 @@ More about the line: A progress strip per open issue and pull request showing which step of the pipeline it is on and who must act next. -Fed by `GET /api/stage-tracker`. Every issue and pull request follows the same pipeline, named once in the column header above the list: `Open → Actionable (010) → Claimed (020) → PR in progress (030/040) → CI / Review (050/060) → Merge gate (080/100) → ✓ merged`. Each entry is a title line over a thin chevron strip. Only the current step is coloured: it is filled amber and carries the step name in words; once the PR is merged the final ✓ chevron is the lit one, in green. Motion is reserved for what is running or needs a hand — the lit chevron falls into one of three buckets (`stageTrackerMotion()` in `index.html`): **static** (steady): Open, Open · on hold (any hold label wins over every other signal), Actionable (010), Claimed (020), PR in progress / Covered by PR (030/040) by a human author, Merged, and any unrecognised stage; **active** (gentle pulse): the hive is acting — 030/040 by a hive or clanker author, 050 review pending, 060 reviewed, 070 fixing, 074 adopted by hive, 080 merge gate, 095 pre-merge soak, 100 merge queue; **needs-human** (slower amber→red breathe): a person must move — 015 triage needed, 045/085 needs decision, 071 needs author, 072 returned to contributor, 090 ready for a human merge, or a `stalled` / `blocked` / `needs-human` / `needs-decision` / `needs-triage` modifier on any stage. Under `prefers-reduced-motion` nothing animates; a needs-human step keeps a steady red tint so it still stands out. Every other step is a low-contrast shape with a small grey glyph (circle, bolt, lock, branch, eye, shield, check) — passed steps a subtle neutral fill, steps ahead a faint outline. Side stages are not extra steps: `070 Fixing` (the author's move; the `needs-rebase` chip next to it says what is being fixed — the stage labeller always parks a needs-rebase PR at 070/071/072, never at CI / Review), `071 Needs author` (human-authored), `072 Returned to contributor` (clanker-authored) and `074 Adopted by hive` light *CI / Review* with their own name (an adopted PR stays on 074 through the hive's pushes going pending, green or red again, and moves on only when *Merge gate* takes it); `085 Needs human decision` (PR-only) and `090 Ready for human merge` light *Merge gate* with their own name (the 085 tooltip says what must be decided); an issue parked on a person wears `045 Needs human decision` and lights *Actionable* (the issue side) with that name — a pre-045 issue still wearing 085 is drawn the same way until the reconciler re-stages it; an actionable issue no maintainer has looked at yet wears `015 Triage needed` (carrying `needs-triage`, or held in the reporter-trust gate's `reporter_triage` bucket — never derived from who filed it) and lights *Actionable* as "Triage needed" (a human's move) — so the Actionable column reads "Actionable", "Triage needed" or "Needs human decision", and the Open column is untouched. An issue carrying a hold-class label (`hold`, `do-not-merge*`, `hive-pause/`) is never parked: it sits at *Open* as "Open · on hold" even beside `needs-human`, and the reconciler removes any 045 the hold caught it with ([#13012](https://github.com/hivecommons/hive/issues/13012)). The lit chevron ends with an ink-coloured icon of whose move it is — person (human), robot (clanker) or hexagon (hive): the claim holder on Claimed, the PR author on PR in progress and Returned to contributor, hive on CI / Review, Adopted by hive and Ready for auto merge, a human on Needs human decision and Ready for human merge. `040 Covered by PR` shares the *PR in progress* column with 030; `060 Reviewed`, `080 Awaiting merge admission` and `100 Merge queue` all light *Merge gate* as "Ready for auto merge" (the chevron tooltip says "not yet admitted — " for 060/080 and "admitted, merging" for 100); `095 Pre-merge soak` lights the same column as "Soaking" (hive's move; tooltip "admitted, soaking — Xm left", headline "Pre-merge soak — Xm left" from the payload's `soak_remaining_seconds`, aged from the lane's last sweep), so an admitted PR inside its soak window is no longer drawn as merging; the chevron's own tooltip names the exact stage. Covering pull requests nest under their issue. An issue with no PR yet draws the whole strip like any other row, lit at Open, Actionable or Claimed with the steps ahead faint. Once exactly one PR covers it, issue and PR are a single entry: the issue's `#N title` on the left, `[author icon] #PR title` under the *PR in progress* header on the same title line, and one continuous strip — Open, Actionable and Claimed passed, the PR's stage lighting the chevron from PR in progress through CI / Review, Merge gate and ✓; the chip, tooltip and `since` are the PR's. With two or more covering PRs the issue's own strip stops at Claimed — all passed, nothing on the issue line lit. Directly beneath, tighter than the gap between entries, sits one indented sub-row per open covering PR, furthest along first: `[author icon] #N title` at the *PR in progress* column, then that PR's own strip from there on — `PR in progress → CI / Review → Merge gate → ✓` — with its own lit chevron, chip, tooltip and `since`. So an issue with two PRs, one at 030, shows "PR in progress" lit on that sub-row, not on the issue. A PR linked to an open issue in the list has no top-level row of its own; a PR with no linked issue keeps a full-width row and shows the first steps as passed. A PR whose only linked issue is already closed — a superseded PR whose body still says `Fixes #N` after an earlier PR fixed it — keeps its own row with the closed issue in the issue slot: `#N title` as a link, muted and struck through, followed by a small `closed` chip, so the pairing reads at a glance; when the title cannot be resolved the slot says `#N (closed issue)` rather than a bare number (the title comes from the snapshot, or from one cached issue read the cycle makes once per issue — see [Linked issues](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard.md#pr-review-and-link-signals)). The *Pull requests* filter flattens the list — every PR as a top-level row; *Issues* and *All* show the nested view. Each row is named once on the left; a covering PR appears under the *PR in progress* header as `[author icon] #N title`. A pull request with no linked issue is named once: `[PR icon] [author icon] #N title` on the left over one continuous strip (first steps passed, its own step lit) and nothing in the *PR in progress* column, which is reserved for covering PRs — so a title never appears twice in a row. Pull requests are never issues here: when a number arrives through both the issues feed and the pulls feed the PR record wins and the issue copy is dropped on the server (pairing is keyed by repository, kind and number), and the *Issues* filter never lists a PR. The author icon is the one coloured legend: hive hexagon in blue, clanker robot in purple, human silhouette in teal (the `author/*` kind, inferred from the snapshot when the label is absent; the login is in the `#N` link's tooltip). The right-hand `since` column, on the title line, is how long the item has been on its step (red when `stalled`). At most one muted chip sits to its left on the title line: the most important health modifier (`stalled`, `regressed`, `hold`, `needs-human`, `needs-decision` — shown as "needs human decision" —, `needs-triage` — shown as "triage needed" (amber) on an issue still carrying the `needs-triage` label at a stage other than 015, whose own chevron already says so —, `needs-rebase`, `blocked`, `draft`) with a `+N` for the rest. Everything else — the reason sentence, who must act, nudges sent, the repository, all flags and the last stage hops — is in the hover tooltip on the item's `#N` link (on a nested sub-row, on its `#N` PR link). Hover text in a row lives only on the `#N` link, the covering-PR link, the title and the lit chevron (its stage, and whose move it is); passed and upcoming chevrons, the kind and author icons, chips and the `since` column have none. Title lines never wrap or push the strip out of alignment: the issue title is truncated with an ellipsis before the *PR in progress* column (a clear gap always separates it from the PR entry, which starts at the column edge) and the PR title before the chips and `since`; hovering a truncated title shows the full text. Rows are sorted furthest-along first by default — a covered issue ranks by its furthest-along PR — then longest on the step first; *Sort* also offers longest waiting, newest and number, and the arrow beside it reverses the order. Every open issue and pull request of the watched repositories is listed — actionable, held, claimed, in progress, covered by a PR, filtered by a `needs-*` label, reporter-triage, draft and exempt-labelled alike; an item without a stage label or journal entry sits at *Open*, and one in a scanner bucket with no label yet is placed from the bucket (claimed → 020, in progress → 030, covered → 040). Filters work on the cached payload in the browser and persist across reloads (the page number does not): kind chips, *Repo*, a search box (`#number` / number / title substring, also matching a covering PR), *Since* (any, over 1 hour / 1 day / 1 week on the current step, or past the stage's configured dwell — stalled items always qualify), *Author* kind, *Needs attention* (anything with a health modifier or on 071 / 072 / 085 / 090), and label chips built from every label present, each with its count: a **🙋 Needs human** facet first (attention-coloured; it matches any item carrying a label of the needs-human set — the server's `needs_human_labels`, the same list as the Projects *Needs human* preset: `stage/045-needs-decision`, `stage/085-needs-decision`, `needs-decision`, `needs-direction`, `needs-spec`, `needs-signal`, `needs-human` and `needs-human/*`, the hold labels, `stage/071-needs-author`, `sentinel-alert`, `needs-triage`; its count is the number of distinct items, not the sum of label counts), then every `stage/NNN-*` label in numerical order, then all other labels alphabetically (case-insensitive). When the row would overflow, the **+N more** button opens an in-app *All labels* dialog listing every label in the same order with counts, a type-to-filter box, the same toggles the chips use and a *Clear* button; a label selected there is promoted into the visible row. Chips of one family OR, families AND; *Needs human* ANDs with the label chips. *Clear filters* shows while anything is narrowed. The list is paged — 25 / 50 / 100 rows, default 50 — with Prev / Next and "showing X–Y of N (M filtered out)" above and below; the header count "N items · M need attention" is for the filtered set, the sidebar badge for everythingthe chevron's own tooltip names the exact stage. Covering pull requests nest under their issue. An issue with no PR yet draws the whole strip like any other row, lit at Open, Actionable or Claimed with the steps ahead faint. Once exactly one PR covers it, issue and PR are a single entry: the issue's `#N title` on the left, `[author icon] #PR title` under the *PR in progress* header on the same title line, and one continuous strip — Open, Actionable and Claimed passed, the PR's stage lighting the chevron from PR in progress through CI / Review, Merge gate and ✓; the chip, tooltip and `since` are the PR's. With two or more covering PRs the issue's own strip stops at Claimed — all passed, nothing on the issue line lit. Directly beneath, tighter than the gap between entries, sits one indented sub-row per open covering PR, furthest along first: `[author icon] #N title` at the *PR in progress* column, then that PR's own strip from there on — `PR in progress → CI / Review → Merge gate → ✓` — with its own lit chevron, chip, tooltip and `since`. So an issue with two PRs, one at 030, shows "PR in progress" lit on that sub-row, not on the issue. A PR linked to an open issue in the list has no top-level row of its own; a PR with no linked issue keeps a full-width row and shows the first steps as passed. The *Pull requests* filter flattens the list — every PR as a top-level row; *Issues* and *All* show the nested view. Each row is named once on the left; a covering PR appears under the *PR in progress* header as `[author icon] #N title`. A pull request with no linked issue is named once: `[PR icon] [author icon] #N title` on the left over one continuous strip (first steps passed, its own step lit) and nothing in the *PR in progress* column, which is reserved for covering PRs — so a title never appears twice in a row. Pull requests are never issues here: when a number arrives through both the issues feed and the pulls feed the PR record wins and the issue copy is dropped on the server (pairing is keyed by repository, kind and number), and the *Issues* filter never lists a PR. The author icon is the one coloured legend: hive hexagon in blue, clanker robot in purple, human silhouette in teal (the `author/*` kind, inferred from the snapshot when the label is absent; the login is in the `#N` link's tooltip). The right-hand `since` column, on the title line, is how long the item has been on its step (red when `stalled`). At most one muted chip sits to its left on the title line: the most important health modifier (`stalled`, `regressed`, `hold`, `needs-human`, `needs-decision` — shown as "needs human decision" —, `needs-rebase`, `blocked`, `draft`) with a `+N` for the rest. Everything else — the reason sentence, who must act, nudges sent, the repository, all flags and the last stage hops — is in the hover tooltip on the item's `#N` link (on a nested sub-row, on its `#N` PR link). Hover text in a row lives only on the `#N` link, the covering-PR link, the title and the lit chevron (its stage, and whose move it is); passed and upcoming chevrons, the kind and author icons, chips and the `since` column have none. Title lines never wrap or push the strip out of alignment: the issue title is truncated with an ellipsis before the *PR in progress* column (a clear gap always separates it from the PR entry, which starts at the column edge) and the PR title before the chips and `since`; hovering a truncated title shows the full text. Rows are sorted furthest-along first by default — a covered issue ranks by its furthest-along PR — then longest on the step first; *Sort* also offers longest waiting, newest and number, and the arrow beside it reverses the order. Every open issue and pull request of the watched repositories is listed — actionable, held, claimed, in progress, covered by a PR, filtered by a `needs-*` label, reporter-triage, draft and exempt-labelled alike; an item without a stage label or journal entry sits at *Open*, and one in a scanner bucket with no label yet is placed from the bucket (claimed → 020, in progress → 030, covered → 040). Filters work on the cached payload in the browser and persist across reloads (the page number does not): kind chips, *Repo*, a search box (`#number` / number / title substring, also matching a covering PR), *Since* (any, over 1 hour / 1 day / 1 week on the current step, or past the stage's configured dwell — stalled items always qualify), *Author* kind, *Needs attention* (anything with a health modifier or on 071 / 072 / 085 / 090), and label chips built from every label present, each with its count: a **🙋 Needs human** facet first (attention-coloured; it matches any item carrying a label of the needs-human set — the server's `needs_human_labels`, the same list as the Projects *Needs human* preset: `stage/045-needs-decision`, `stage/085-needs-decision`, `needs-decision`, `needs-direction`, `needs-spec`, `needs-signal`, `needs-human` and `needs-human/*`, the hold labels, `stage/071-needs-author`, `sentinel-alert`, `needs-triage`; its count is the number of distinct items, not the sum of label counts), then every `stage/NNN-*` label in numerical order, then all other labels alphabetically (case-insensitive). When the row would overflow, the **+N more** button opens an in-app *All labels* dialog listing every label in the same order with counts, a type-to-filter box, the same toggles the chips use and a *Clear* button; a label selected there is promoted into the visible row. Chips of one family OR, families AND; *Needs human* ANDs with the label chips. *Clear filters* shows while anything is narrowed. The list is paged — 25 / 50 / 100 rows, default 50 — with Prev / Next and "showing X–Y of N (M filtered out)" above and below; the header reads "N items (I issues / P PRs) · M need attention" — N, I and P are the whole list, the same `openItems` set the Overview tile and its left-menu counter print, so N always equals the tile's **Total open issues + Total open PRs**; a "· X match filters" note follows while a filter is on. The sidebar counter beside **Hive activity monitor** is that same N — total open items, written in the same render pass as the header, so the two cannot drift apart; how many need attention is the warn-coloured header suffix and the counter's hover tooltip (counted over every item, filters or not). When covering pull requests are nested under their issue the "showing X–Y of R" line counts rows and adds "(P nested under their issues)" so R never reads as a different total. The small monospace strip beside the count — `engine enforce · N anomalies` — is the central stage engine ([ADR-0025](/docs/hive/adr/0025-central-stage-engine)): `engine` names the current `governor.labels.stage.engine.mode` (green `enforce`, neutral `shadow` = paused, nothing written; hover for what the mode does and the derivation count), `anomalies` counts stage moves the engine's admission table did not expect (red when non-zero). The same numbers are `/api/status.stageEngine`; the mode is changed under **Settings → Labels → Central stage engine mode** and the operator notes are in [operator-reference.md](https://github.com/hivecommons/hive/blob/v5/src/docs/operator-reference.md#central-stage-engine-rollout). "updated Ns ago · cache" says how old the server-built payload is: the spoke rebuilds it at most every `dashboard.stage_tracker.cache_ttl_s` (default `governor.eval_interval_s`) from its existing snapshot and serves it with an `ETag`, so the browser's 30-second poll is a 304 and no request ever reaches GitHub (`/api/gh-rate-limits` lists the caller `dashboard:stage-tracker` with nothing charged). See [Stage labels](/docs/hive/labels-and-control-signals#stage-labels-stagennn--the-pipeline-state-machine). +Fed by `GET /api/stage-tracker`. Every issue and pull request follows the same pipeline, named once in the column header above the list: `Open → Actionable (010) → Claimed (020) → PR in progress (030/040) → CI / Review (050/060) → Merge gate (080/100) → ✓ merged`. Each entry is a title line over a thin chevron strip. Only the current step is coloured: it is filled amber and carries the step name in words; once the PR is merged the final ✓ chevron is the lit one, in green. Motion is reserved for what is running or needs a hand — the lit chevron falls into one of three buckets (`stageTrackerMotion()` in `index.html`): **static** (steady): Open, Open · on hold (any hold label wins over every other signal), Actionable (010), Claimed (020), PR in progress / Covered by PR (030/040) by a human author, Merged, and any unrecognised stage; **active** (gentle pulse): the hive is acting — 030/040 by a hive or clanker author, 050 review pending, 060 reviewed, 070 fixing, 074 adopted by hive, 080 merge gate, 095 pre-merge soak, 100 merge queue; **needs-human** (slower amber→red breathe): a person must move — 015 triage needed, 045/085 needs decision, 071 needs author, 072 returned to contributor, 090 ready for a human merge, or a `stalled` / `blocked` / `needs-human` / `needs-decision` / `needs-triage` modifier on any stage. Under `prefers-reduced-motion` nothing animates; a needs-human step keeps a steady red tint so it still stands out. Every other step is a low-contrast shape with a small grey glyph (circle, bolt, lock, branch, eye, shield, check) — passed steps a subtle neutral fill, steps ahead a faint outline. Side stages are not extra steps: `070 Fixing` (the author's move; the `needs-rebase` chip next to it says what is being fixed — the stage labeller always parks a needs-rebase PR at 070/071/072, never at CI / Review), `071 Needs author` (human-authored), `072 Returned to contributor` (clanker-authored) and `074 Adopted by hive` light *CI / Review* with their own name (an adopted PR stays on 074 through the hive's pushes going pending, green or red again, and moves on only when *Merge gate* takes it); `085 Needs human decision` (PR-only) and `090 Ready for human merge` light *Merge gate* with their own name (the 085 tooltip says what must be decided); an issue parked on a person wears `045 Needs human decision` and lights *Actionable* (the issue side) with that name — a pre-045 issue still wearing 085 is drawn the same way until the reconciler re-stages it; an actionable issue no maintainer has looked at yet wears `015 Triage needed` (carrying `needs-triage`, or held in the reporter-trust gate's `reporter_triage` bucket — never derived from who filed it) and lights *Actionable* as "Triage needed" (a human's move) — so the Actionable column reads "Actionable", "Triage needed" or "Needs human decision", and the Open column is untouched. An issue carrying a hold-class label (`hold`, `do-not-merge*`, `hive-pause/`) is never parked: it sits at *Open* as "Open · on hold" even beside `needs-human`, and the reconciler removes any 045 the hold caught it with ([#13012](https://github.com/hivecommons/hive/issues/13012)). The lit chevron ends with an ink-coloured icon of whose move it is — person (human), robot (clanker) or hexagon (hive): the claim holder on Claimed, the PR author on PR in progress and Returned to contributor, hive on CI / Review, Adopted by hive and Ready for auto merge, a human on Needs human decision and Ready for human merge. A row at `090 Ready for human merge` always says *why* a person and not the hive merges it (#14658): the lit word ends `· workflow files`, `· tests removed`, `· manual-merge policy`, `· branch protection`, `· merge method`, `· auto-merge off`, `· untrusted author`, `· unauthorized`, `· reviewer deferred` or `· policy` (payload `human_merge_reason`, a `prstage.HumanMergeReason*` code derived from the stage record's lane reason — no second store), the chevron tooltip leads with the one-sentence `human_merge_reason_text` ("Changes .github/workflows/** — the hive never merges workflow changes …; click Merge on GitHub"), the manual-merge word reads `Awaiting human merge · workflow files · approved by @login`, the search / needs-attention headline carries the same tail, and a PR nested under its issue mirrors `human_approver`, `manual_merge`, `human_merge_reason` and `human_merge_reason_text` onto its `linked_prs` entry so the sub-row says the same. `040 Covered by PR` shares the *PR in progress* column with 030; `060 Reviewed`, `080 Awaiting merge admission` and `100 Merge queue` all light *Merge gate* as "Ready for auto merge" (the chevron tooltip says "not yet admitted — " for 060/080 and "admitted, merging" for 100); `095 Pre-merge soak` lights the same column as "Soaking" (hive's move; tooltip "admitted, soaking — Xm left", headline "Pre-merge soak — Xm left" from the payload's `soak_remaining_seconds`, aged from the lane's last sweep), so an admitted PR inside its soak window is no longer drawn as merging; the chevron's own tooltip names the exact stage. Covering pull requests nest under their issue. An issue with no PR yet draws the whole strip like any other row, lit at Open, Actionable or Claimed with the steps ahead faint. Once exactly one PR covers it, issue and PR are a single entry: the issue's `#N title` on the left, `↳ [author icon] #PR title` under the *PR in progress* header on the same title line (the ↳ marks a PR drawn on its issue's row; the PR link's tooltip says so — "linked PR, shown on its issue's row" — and, when two open issues list the same PR, every row but the lowest-numbered issue adds "also under #N"), and one continuous strip — Open, Actionable and Claimed passed, the PR's stage lighting the chevron from PR in progress through CI / Review, Merge gate and ✓; the chip, tooltip and `since` are the PR's. With two or more covering PRs the issue's own strip stops at Claimed — all passed, nothing on the issue line lit. Directly beneath, tighter than the gap between entries, sits one indented sub-row per open covering PR, furthest along first: `[author icon] #N title` at the *PR in progress* column, then that PR's own strip from there on — `PR in progress → CI / Review → Merge gate → ✓` — with its own lit chevron, chip, tooltip and `since`. So an issue with two PRs, one at 030, shows "PR in progress" lit on that sub-row, not on the issue. A PR linked to an open issue in the list has no top-level row of its own; a PR with no linked issue keeps a full-width row and shows the first steps as passed. A PR whose only linked issue is already closed — a superseded PR whose body still says `Fixes #N` after an earlier PR fixed it — keeps its own row with the closed issue in the issue slot: `#N title` as a link, muted and struck through, followed by a small `closed` chip, so the pairing reads at a glance; when the title cannot be resolved the slot says `#N (closed issue)` rather than a bare number (the title comes from the snapshot, or from one cached issue read the cycle makes once per issue — see [Linked issues](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard.md#pr-review-and-link-signals)). The *Pull requests* filter flattens the list — every PR as a top-level row; *Issues* and *All* show the nested view. Each row is named once on the left; a covering PR appears under the *PR in progress* header as `[author icon] #N title`. A pull request with no linked issue is named once: `[PR icon] [author icon] #N title` on the left over one continuous strip (first steps passed, its own step lit) and nothing in the *PR in progress* column, which is reserved for covering PRs — so a title never appears twice in a row. Pull requests are never issues here: when a number arrives through both the issues feed and the pulls feed the PR record wins and the issue copy is dropped on the server (pairing is keyed by repository, kind and number), and the *Issues* filter never lists a PR. The author icon is the one coloured legend: hive hexagon in blue, clanker robot in purple, human silhouette in teal (the `author/*` kind, inferred from the snapshot when the label is absent; the login is in the `#N` link's tooltip). The right-hand `since` column, in line with the chevron strip, is how long the item has been on its step (red when `stalled`). A plain *Open* step — no stage label yet and not on hold — also carries that age in its own words, "Open · since 3d" ("since 14h" under two days), taken from the same `stage_since` instant the column draws (falling back to the time the item was opened, which the chevron tooltip then marks "(opened)"); past `STAGE_TRACKER_OPEN_STALE_DAYS` (2) the chevron takes a subtle red tint. *Open · hold* never shows the age. An Open item carrying the `blocked` label is drawn the same way as a hold — "Open · blocked" with a ⛔ glyph in the hold's yellow — but keeps the age ("Open · blocked · since 7h"; a stale blocked item is exactly what should stand out, [#14194](https://github.com/hivecommons/hive/issues/14194)) and its chevron tooltip says why: "blocked on owner/repo#N" when the payload names the blocker (`blocked_on`), else "carries the blocked label"; when an item is both held and blocked, hold wins — it is a human instruction. At most one muted chip sits at the right end of the title line, directly over the strip's ✓ cap (on the last line when the title wraps; a hairline above each entry and a hover tint over the whole entry keep chip and `since` unambiguously with their own row — [#14640](https://github.com/hivecommons/hive/issues/14640)): the most important health modifier (`stalled`, `regressed`, `hold`, `needs-human`, `needs-decision` — shown as "needs human decision" —, `needs-triage` — shown as "triage needed" (amber) on an issue still carrying the `needs-triage` label before the claim (010, 017, 045; at 015 the lit chevron already says so); a `needs-triage` left on a claimed or later issue (020+) is stale, never shown as a chip or stage text, and reported instead as `stale_labels` in the payload with a muted *Stale label* line in the hover tooltip until the stage sweep removes it (hivecommons/hive#14586) —, `needs-rebase`, `blocked`, `draft`) with a `+N` for the rest. Everything else — the reason sentence, who must act, nudges sent, the repository, all flags and the last stage hops — is in the hover tooltip on the item's `#N` link (on a nested sub-row, on its `#N` PR link). Hover text in a row lives only on the `#N` link, the covering-PR link, the title and the lit chevron (its stage, and whose move it is); passed and upcoming chevrons, the kind and author icons, chips and the `since` column have none. Title lines wrap rather than push the strip out of alignment: the issue title stays before the *PR in progress* column (a clear gap always separates it from the PR entry, which starts at the column edge) and the PR title before the chip; hovering a title shows the full text. Rows are sorted furthest-along first by default — a covered issue ranks by its furthest-along PR — then, within a column, attention-first: rows a person must act on now (any shown pill — `stalled`, `unauthorized`, `needs rebase`, `needs human`, … — or a human-gated stage: 045, 071, 072, 085, 090) come first, plain rows next, and rows nobody should act on — `hold` (incl. `do-not-merge/*`), `draft`, and a PR standing in for a closed issue — sink to the bottom of the column; within a tier the longest on the step comes first, then repo and number. Pills the server holds back until the merge gate (`deferred_modifiers`) never promote a row. The same tiering orders the PR sub-rows under an issue, and the server pre-sorts `GET /api/stage-tracker` the same way (stage, tier, wait, repo, number) so chat `/prs` and the hub read the same order. *Sort* also offers longest waiting (tier first, then wait), newest and number (no tiering); the arrow beside it flips the column / age / repo / number keys but never the tier — attention stays on top in both directions, as its tooltip says ("Attention-first within each column; arrow flips age order"). Every open issue and pull request of the watched repositories is listed — actionable, held, claimed, in progress, covered by a PR, filtered by a `needs-*` label, reporter-triage, draft and exempt-labelled alike; an item without a stage label or journal entry sits at *Open*, and one in a scanner bucket with no label yet is placed from the bucket (claimed → 020, in progress → 030, covered → 040). Filters work on the cached payload in the browser and persist across reloads (the page number does not): kind chips, *Repo*, a search box (`#number` / number / title substring, also matching a covering PR), *Since* (any, over 1 hour / 1 day / 1 week on the current step, or past the stage's configured dwell — stalled items always qualify), *Author* kind, *Needs attention* (anything with a health modifier or on 071 / 072 / 085 / 090), and label chips built from every label present, each with its count: a **🙋 Needs human** facet first (attention-coloured; it matches any item carrying a label of the needs-human set — the server's `needs_human_labels`, the same list as the Projects *Needs human* preset: `stage/045-needs-decision`, `stage/085-needs-decision`, `needs-decision`, `needs-direction`, `needs-spec`, `needs-signal`, `needs-human` and `needs-human/*`, the hold labels, `stage/071-needs-author`, `sentinel-alert`, `needs-triage`; its count is the number of distinct items, not the sum of label counts), then every `stage/NNN-*` label in numerical order, then all other labels alphabetically (case-insensitive). When the row would overflow, the **+N more** button opens an in-app *All labels* dialog listing every label in the same order with counts, a type-to-filter box, the same toggles the chips use and a *Clear* button; a label selected there is promoted into the visible row. Chips of one family OR, families AND; *Needs human* ANDs with the label chips. *Clear filters* shows while anything is narrowed. The list is paged — 25 / 50 / 100 rows, default 50 — with Prev / Next and "showing X–Y of N (M filtered out)" above and below; the header count "N rows · M need attention" is for the filtered set, the **sidebar badge is the row count of the default view** (owner: the tracker counts rows, not items — a covering PR drawn on its issue's row is not a row of its own, and an issue with several PRs already has several), and both come from the same `stageTrackerCountsText()` call so they cannot disagree. When rows sit below the payload's open issues + PRs, the "showing" line's tooltip and the badge's footnote say why — "2 PRs shown on their issue's row" — rather than printing a second total.the chevron's own tooltip names the exact stage. Covering pull requests nest under their issue. An issue with no PR yet draws the whole strip like any other row, lit at Open, Actionable or Claimed with the steps ahead faint. Once exactly one PR covers it, issue and PR are a single entry: the issue's `#N title` on the left, `[author icon] #PR title` under the *PR in progress* header on the same title line, and one continuous strip — Open, Actionable and Claimed passed, the PR's stage lighting the chevron from PR in progress through CI / Review, Merge gate and ✓; the chip, tooltip and `since` are the PR's. With two or more covering PRs the issue's own strip stops at Claimed — all passed, nothing on the issue line lit. Directly beneath, tighter than the gap between entries, sits one indented sub-row per open covering PR, furthest along first: `[author icon] #N title` at the *PR in progress* column, then that PR's own strip from there on — `PR in progress → CI / Review → Merge gate → ✓` — with its own lit chevron, chip, tooltip and `since`. So an issue with two PRs, one at 030, shows "PR in progress" lit on that sub-row, not on the issue. A PR linked to an open issue in the list has no top-level row of its own; a PR with no linked issue keeps a full-width row and shows the first steps as passed. The *Pull requests* filter flattens the list — every PR as a top-level row; *Issues* and *All* show the nested view. Each row is named once on the left; a covering PR appears under the *PR in progress* header as `[author icon] #N title`. A pull request with no linked issue is named once: `[PR icon] [author icon] #N title` on the left over one continuous strip (first steps passed, its own step lit) and nothing in the *PR in progress* column, which is reserved for covering PRs — so a title never appears twice in a row. Pull requests are never issues here: when a number arrives through both the issues feed and the pulls feed the PR record wins and the issue copy is dropped on the server (pairing is keyed by repository, kind and number), and the *Issues* filter never lists a PR. The author icon is the one coloured legend: hive hexagon in blue, clanker robot in purple, human silhouette in teal (the `author/*` kind, inferred from the snapshot when the label is absent; the login is in the `#N` link's tooltip). The right-hand `since` column, in line with the chevron strip, is how long the item has been on its step (red when `stalled`). At most one muted chip sits at the right end of the title line, directly over the strip's ✓ cap (on the last line when the title wraps; a hairline above each entry and a hover tint over the whole entry keep chip and `since` unambiguously with their own row — [#14640](https://github.com/hivecommons/hive/issues/14640)): the most important health modifier (`stalled`, `regressed`, `hold`, `needs-human`, `needs-decision` — shown as "needs human decision" —, `needs-rebase`, `blocked`, `draft`) with a `+N` for the rest. A `regressed` chip is red only when the row is parked on a person (a human-gate modifier or stage 045 / 071 / 072 / 085 / 090); on a hive-driven stage with nothing waiting on a human it draws as a muted dashed `stage ↓` pill, yields the lead slot to any other modifier, keeps the row out of **Needs attention**, and its tooltip ends "the hive is handling it — no action needed; clears on the next forward step". The marker is set by a genuine backward hop (a lapsed claim 020 → 010, a 030 ↔ 020 flicker and a 040 → 010 after a partially-addressing merge are not regressions) and clears on the first forward hop; see "The regressed marker" in `labels-and-control-signals.md`. Everything else — the reason sentence, who must act, nudges sent, the repository, all flags and the last stage hops — is in the hover tooltip on the item's `#N` link (on a nested sub-row, on its `#N` PR link). Hover text in a row lives only on the `#N` link, the covering-PR link, the title and the lit chevron (its stage, and whose move it is); passed and upcoming chevrons, the kind and author icons, chips and the `since` column have none. Title lines wrap rather than push the strip out of alignment: the issue title stays before the *PR in progress* column (a clear gap always separates it from the PR entry, which starts at the column edge) and the PR title before the chip; hovering a title shows the full text. Rows are sorted furthest-along first by default — a covered issue ranks by its furthest-along PR — then, within a column, attention-first: rows a person must act on now (any shown pill — `stalled`, `unauthorized`, `needs rebase`, `needs human`, … — or a human-gated stage: 045, 071, 072, 085, 090) come first, plain rows next, and rows nobody should act on — `hold` (incl. `do-not-merge/*`), `draft`, and a PR standing in for a closed issue — sink to the bottom of the column; within a tier the longest on the step comes first, then repo and number. Pills the server holds back until the merge gate (`deferred_modifiers`) never promote a row. The same tiering orders the PR sub-rows under an issue, and the server pre-sorts `GET /api/stage-tracker` the same way (stage, tier, wait, repo, number) so chat `/prs` and the hub read the same order. *Sort* also offers longest waiting (tier first, then wait), newest and number (no tiering); the arrow beside it flips the column / age / repo / number keys but never the tier — attention stays on top in both directions, as its tooltip says ("Attention-first within each column; arrow flips age order"). Every open issue and pull request of the watched repositories is listed — actionable, held, claimed, in progress, covered by a PR, filtered by a `needs-*` label, reporter-triage, draft and exempt-labelled alike; an item without a stage label or journal entry sits at *Open*, and one in a scanner bucket with no label yet is placed from the bucket (claimed → 020, in progress → 030, covered → 040). Filters work on the cached payload in the browser and persist across reloads (the page number does not): kind chips, *Repo*, a search box (`#number` / number / title substring, also matching a covering PR), *Since* (any, over 1 hour / 1 day / 1 week on the current step, or past the stage's configured dwell — stalled items always qualify), *Author* kind, *Needs attention* (anything with a health modifier or on 071 / 072 / 085 / 090), and label chips built from every label present, each with its count: a **🙋 Needs human** facet first (attention-coloured; it matches any item carrying a label of the needs-human set — the server's `needs_human_labels`, the same list as the Projects *Needs human* preset: `stage/045-needs-decision`, `stage/085-needs-decision`, `needs-decision`, `needs-direction`, `needs-spec`, `needs-signal`, `needs-human` and `needs-human/*`, the hold labels, `stage/071-needs-author`, `sentinel-alert`, `needs-triage`; its count is the number of distinct items, not the sum of label counts), then every `stage/NNN-*` label in numerical order, then all other labels alphabetically (case-insensitive). When the row would overflow, the **+N more** button opens an in-app *All labels* dialog listing every label in the same order with counts, a type-to-filter box, the same toggles the chips use and a *Clear* button; a label selected there is promoted into the visible row. Chips of one family OR, families AND; *Needs human* ANDs with the label chips. *Clear filters* shows while anything is narrowed. The list is paged — 25 / 50 / 100 rows, default 50 — with Prev / Next and "showing X–Y of N (M filtered out)" above and below; the header reads "N items (I issues / P PRs) · M need attention" — N, I and P are the whole list, the same `openItems` set the Overview tile and its left-menu counter print, so N always equals the tile's **Total open issues + Total open PRs**; a "· X match filters" note follows while a filter is on. The sidebar counter beside **Hive activity monitor** is that same N — total open items, written in the same render pass as the header, so the two cannot drift apart; how many need attention is the warn-coloured header suffix and the counter's hover tooltip (counted over every item, filters or not). When covering pull requests are nested under their issue the "showing X–Y of R" line counts rows and adds "(P nested under their issues)" so R never reads as a different total. The small monospace strip beside the count — `engine enforce · N anomalies` — is the central stage engine ([ADR-0025](/docs/hive/adr/0025-central-stage-engine)): `engine` names the current `governor.labels.stage.engine.mode` (green `enforce`, neutral `shadow` = paused, nothing written; hover for what the mode does and the derivation count), `anomalies` counts stage moves the engine's admission table did not expect (red when non-zero). The same numbers are `/api/status.stageEngine`; the mode is changed under **Settings → Labels → Central stage engine mode** and the operator notes are in [operator-reference.md](https://github.com/hivecommons/hive/blob/v5/src/docs/operator-reference.md#central-stage-engine-rollout). "updated Ns ago · cache" says how old the server-built payload is: the spoke rebuilds it at most every `dashboard.stage_tracker.cache_ttl_s` (default `governor.eval_interval_s`) from its existing snapshot and serves it with an `ETag`, so the browser's 30-second poll is a 304 and no request ever reaches GitHub (`/api/gh-rate-limits` lists the caller `dashboard:stage-tracker` with nothing charged). See [Stage labels](/docs/hive/labels-and-control-signals#stage-labels-stagennn--the-pipeline-state-machine). **Repo chip.** Each row carries a small monospace chip before the number naming the repository it belongs to (`dibs #456`, `hive #13524`); the chip is tinted per repository from a fixed palette so one repo always looks the same, the configured primary repo is shown muted, and the full `owner/repo` is in the tooltip. Linked PR sub-rows carry the same chip. Clicking a chip sets the **Repo** filter to that repository (click it again, or pick "All repos", to clear). By default the chip appears only when the list spans more than one repository; the ⚙ settings modal's **Repositories** section offers **Show repo chip: always / only when multiple repos / never** (stored under `hive.tracker.repochip`). @@ -645,11 +656,33 @@ Fed by `GET /api/stage-tracker`. Every issue and pull request follows the same p **Sentinel alert.** A pull request the suspicious-activity sentinel flagged — it wears the `sentinel-alert` label (`sentinel.label`), or the review evidence bundle for its current head records sentinel findings — carries `sentinel_alert: true` in the tracker payload (with `sentinel_findings` and a short `sentinel_reason` when the bundle has them); a webhook label change flips it live. In the **Needs human decision** lane the lit chevron's own words read **Sentinel alert · needs human** (danger-tinted, the same wording in the cell's `title` and `aria-label`, and in the compact caption), so a maintainer reads the security implication before the stage mechanics; in every other lane the row shows a compact danger-tinted `sentinel` chip ahead of `needs rebase` / `regressed` / `stalled`. The tooltip reads "Sentinel flagged security-relevant changes at ``: ``" (a generic description stands in when the bundle has no reason). An issue carried by one flagged PR shows the lane text too, and a nested PR sub-row shows its own. The *Decide…* dialogs for a flagged PR open with a ⚠ line per flagged PR naming the sentinel reason, and *Approve* is styled as a dangerous action. Lane text and chip update in place on refresh (keyed reconcile, no flash). +**Unauthorized pill.** A pull request the hive cannot move because its GitHub App lacks a permission for the next step — it wears `hive:unauthorized` ([#14589](https://github.com/hivecommons/hive/issues/14589)); first case: approving held `action_required` workflow runs needs `actions: write` — carries the `unauthorized` modifier in the tracker payload, drawn in the far-right pill column exactly like `stalled` / `hold` / `draft` but muted (dashed, `--text-muted`), never red: it is nothing wrong, just a step only a person may take. It counts under **Needs attention**. Its tooltip (payload `unauthorized`) names the missing permission and the human step; on the **Needs human decision** lane the lit chevron and the caption read **Awaiting human: approve workflow runs** (payload `unauthorized_step`, from the `unauthorized:` stage reason). The pill, label and payload fields clear themselves in the tick the step is no longer needed. +**Closed issue, open PR.** A pull request whose `Fixes #N` issue has already closed (auto-closed by a bot, or closed by hand) while the PR is still open is live work, not a stale line: the row is anchored on the PR ([#14655](https://github.com/hivecommons/hive/issues/14655)). The PR's own icon, number and title lead the row at full width, its stage drives the strip and its timestamps sort it — closing the issue is not a regression of the PR — and the closed issue shrinks to a muted `closes #N · closed` chip linking to it. An issue closed as *not planned* adds an `issue closed (not planned)` chip so the reader can tell the PR may be moot. At 090 such a row still counts under *Needs attention*: the human has a merge to click. When the PR closes or merges too, the row leaves the list as before (a merged PR shows in *Recently merged* for its retention window). + + **Row gutter.** Every issue and PR row (never a column or repo-group header) carries a left gutter with a select checkbox and a ⋯ actions button that is always present and always visible: dimmed at rest (`--ham-tool-rest-opacity`), at full emphasis on the row under the pointer or holding focus, on a selected row, and on every row while anything is selected or the context menu is open — the only animation is the hover fade (`--ham-tool-fade-ms`, ≤ 120 ms), and nothing about it depends on scrolling, row position or which rows the live refresh re-created. -**Row actions (right-click, ⋯, Shift+F10).** Right-clicking a row — or a nested PR sub-row — opens an in-app context menu of the actions that fit that item's current state; the ⋯ button in the row's left gutter opens the same menu for pointer-free use, and so do **Shift+F10** and the context-menu key while the row or anything in it has focus (every row is a tab stop). The menu is positioned at the pointer, clamped to the viewport, uses `menu` / `menuitem` roles, moves focus with ↑ ↓ Home End, opens the *Decide…* group with → and closes it with ←, and closes on Esc, Tab, click-away, scroll or resize, returning focus to the row. Shift+right-click keeps the browser's own menu. Entries, each shown only when it applies: **Decide…** on a row waiting on a person — a PR wearing the *Needs human decision* pill at 085 on the Merge gate column (the primary case: e.g. two competing `dibs` PRs), an issue row whose nested PR is parked there (the menu reads *Decide PR #458…* and acts on that PR, since the issue's own stage is only mirrored from it), or an issue parked at 045. For a PR: *Approve (/lgtm + /approve)* posts those two commands as comments attributed to you — the merge gate takes it from there exactly as if you had typed them; the dashboard reviews, labels and merges nothing — *Close as superseded by…* (a picker listing the other open PRs in the repo, or type `#458` / `owner/repo#458` / a URL; then an optional comment, then the sweep's labelled close), *Won't fix…* (a comment naming you, then a not-planned close) and *Give direction…* (your text is recorded as `/hive decision ` against the head, so the author agent pushes a new head; only on PRs the hive opened). For an issue: *Approve direction* (records the same `/hive approve` a maintainer would comment: the parking labels come off, `approved-direction` goes on, the stage engine logs the un-park and a reply comment names you; nothing is merged), *Send back to the hive…* (`/hive decision `), *Close as superseded by…* and *Close as won't-fix…*. A selection mixing parked issues and parked PRs has Decide… greyed; **Hold** or **Release hold** depending on the `hold` chip (the same `…/items/{number}/hold` toggle as the repository cards, and the same permission — owner, or a signed-in user with write access to the repo); **Re-evaluate now** (the hive-wide read-only rescan behind the Overview's refresh — a rescan already running is reported, not duplicated); **Accept triage** (only on an issue the chevron calls *Triage needed* — stage 015, or the `needs-triage` chip on another stage — never on a PR; the one-click form of a maintainer accepting triage on GitHub: adds the reporter-trust triage label, `triage/accepted` by default or the first of `issue_filter.reporter_trust.untrusted_require_labels`, removes the awaiting label `needs-triage` / `reporter_trust.awaiting_label` and a Hive-written `stage/015-triage-needed`; the row re-reads *Actionable* with the 👤 gone at once, the reporter-trust gate admits the issue on its next poll and the stage engine records 015 → 010; same permission as Hold, audited as `repo_item_accept_triage`, a no-op when already accepted); **Mark needs-human…** (hidden once the chip is on; merger or owner role, applies `needs-human` and a marker comment through the review pipeline's send-to-human endpoint); **Open on GitHub**; **Copy link**. Entries you may not use are greyed with the reason in their tooltip, never hidden. Confirmation and free-text input use the dashboard's own dialogs — no browser prompts. Every action is an existing audited dashboard endpoint (the decision endpoint is `POST /api/repos/{owner}/{repo}/items/{number}/decision` and triage acceptance is `POST /api/repos/{owner}/{repo}/items/{number}/accept-triage`, see [api-reference.md](https://github.com/hivecommons/hive/blob/v5/src/docs/api-reference.md)); none of them bypasses the hive's governance or merges anything. +**Row actions (right-click, ⋯, Shift+F10).** Right-clicking a row — or a nested PR sub-row — opens an in-app context menu of the actions that fit that item's current state; the ⋯ button in the row's left gutter opens the same menu for pointer-free use, and so do **Shift+F10** and the context-menu key while the row or anything in it has focus (every row is a tab stop). The menu is positioned at the pointer, clamped to the viewport, uses `menu` / `menuitem` roles, moves focus with ↑ ↓ Home End, opens the *Decide…* group with → and closes it with ←, and closes on Esc, Tab, click-away, scroll or resize, returning focus to the row. Shift+right-click keeps the browser's own menu. Entries, each shown only when it applies: **Decide…** on a row waiting on a person — a PR wearing the *Needs human decision* pill at 085 on the Merge gate column (the primary case: e.g. two competing `dibs` PRs), an issue row whose nested PR is parked there (the menu reads *Decide PR #458…* and acts on that PR, since the issue's own stage is only mirrored from it), or an issue parked at 045. The first entry is always *Let hive decide* (see [Let hive decide](#let-hive-decide) below): the hive decides as the owner's delegate and records it the way a person's decision is recorded. For a PR: *Approve (/lgtm + /approve)* posts those two commands as comments attributed to you — the merge gate takes it from there exactly as if you had typed them; the dashboard reviews, labels and merges nothing — *Close as superseded by…* (a picker listing the other open PRs in the repo, or type `#458` / `owner/repo#458` / a URL; then an optional comment, then the sweep's labelled close), *Won't fix…* (a comment naming you, then a not-planned close) and *Give direction…* (your text is recorded as `/hive decision ` against the head, so the author agent pushes a new head; only on PRs the hive opened). For an issue: *Approve direction* (records the same `/hive approve` a maintainer would comment: the parking labels come off, `approved-direction` goes on, the stage engine logs the un-park and a reply comment names you; nothing is merged), *Send back to the hive…* (`/hive decision `), *Close as superseded by…* and *Close as won't-fix…*. A selection mixing parked issues and parked PRs has Decide… greyed; **Hold** or **Release hold** depending on the `hold` chip (the same `…/items/{number}/hold` toggle as the repository cards, and the same permission — owner, or a signed-in user with write access to the repo); **Re-evaluate now** (the hive-wide read-only rescan behind the Overview's refresh — a rescan already running is reported, not duplicated); **Accept triage** (only on an issue the chevron calls *Triage needed* — stage 015, or the `needs-triage` chip on another stage — never on a PR; the one-click form of a maintainer accepting triage on GitHub: adds the reporter-trust triage label, `triage/accepted` by default or the first of `issue_filter.reporter_trust.untrusted_require_labels`, removes the awaiting label `needs-triage` / `reporter_trust.awaiting_label` and a Hive-written `stage/015-triage-needed`; the row re-reads *Actionable* with the 👤 gone at once, the reporter-trust gate admits the issue on its next poll and the stage engine records 015 → 010; same permission as Hold, audited as `repo_item_accept_triage`, a no-op when already accepted); **Mark needs-human…** (hidden once the chip is on; merger or owner role, applies `needs-human` and a marker comment through the review pipeline's send-to-human endpoint); **Open on GitHub**; **Copy link**. Entries you may not use are greyed with the reason in their tooltip, never hidden. Confirmation and free-text input use the dashboard's own dialogs — no browser prompts. Every action is an existing audited dashboard endpoint (the decision endpoint is `POST /api/repos/{owner}/{repo}/items/{number}/decision` and triage acceptance is `POST /api/repos/{owner}/{repo}/items/{number}/accept-triage`, see [api-reference.md](https://github.com/hivecommons/hive/blob/v5/src/docs/api-reference.md)); none of them bypasses the hive's governance or merges anything. + +**Multi-select.** The checkbox in a row's left gutter selects the row; **Shift+click** selects the range from the last clicked row, **Cmd/Ctrl+click** toggles, **Cmd/Ctrl+Shift+click** adds a range to what is selected; with a row focused, **Space** toggles it (Shift+Space extends). Cmd/Ctrl- or Shift-clicking the row itself, away from its links, selects it too. A selection bar appears above the list with the count, **Clear**, and the same actions for the whole selection — *Decide…* (opens the group as a menu, *Let hive decide* first — each selected row gets its own delegated-decision job, see below), *Hold*, *Release hold*, *Re-evaluate now*, *Mark needs-human…*, *Copy links* — each enabled only when it is valid for every selected row and otherwise greyed with the first reason ("Not applicable to acme/console#12", "Sign in with write access…"). Right-clicking a selected row acts on the whole selection. Bulk actions run one request per row, in list order, under a progress toast ("Hold 2/5 — acme/console#12…"); the result toast lists each failure by row and the successes stand, after which the list and the status snapshot refresh (a hive-wide rescan is sent once per batch). Selection is keyed on the row's item (repository, kind, number), not its node, so the live re-render keeps it; a row that leaves the list — merged, closed, filtered or paged out — leaves the selection. + +### Let hive decide + +**What it is.** *Let hive decide* is the first entry under *Decide…* on a row parked at *Needs human decision* (an issue at 045, a PR at 085, or an issue whose nested PR is parked there), and under *Decide…* in the selection bar for a bulk run. It asks the hive to make the decision itself, as the owner's delegate, under the owner's standing operating rules — and to record it exactly as a human decision is recorded: a `/hive decision ` comment on the item (plus the queue approval on a PR head when the decision is to release the PR), the parking labels lifted, `triage/accepted` added where it applies, and the stage engine's un-park logged with the requesting operator named. The item leaves 085/045 and proceeds without the owner. The action is audited as `repo_item_decision_delegated` (or `repo_item_decision_delegated_failed`) and the **Decisions log** shows those rows with a *delegated* badge so delegated and human decisions can be told apart and counted. + +**How it runs.** Clicking it posts `POST /api/repos/{owner}/{repo}/items/{number}/decide-delegated` (body `{"type": "issue"|"pr", "reason", "caption", "sentinel_reason"}` — the row's park words, so the decider reads why the hive stopped) which enqueues one delegated-decision job per item (a second click while a job is running answers 409 with the running job). The page shows *Hive is deciding acme/console#12…*, polls the same URL (`GET`) until the job reports `done` or `failed`, then the result toast reads what was decided — "Hive decided: acme/console#12 approve (small-low-risk)" — and the row refreshes. A job gathers the item (title, body, labels, author, state), the PR's mergeable state and changed files, whether the author is a hive account, the author's repository permission (trusted = write or above), the sentinel label, and the row's park reason, then runs the policy and executes the resulting actions through the same GitHub helpers the manual *Decide…* entries use. Permission is the one *Hold* uses (owner, or a signed-in user with write access to the repository); the requesting user is named in the recorded decision as the person who delegated it. + +**The policy.** The rules live in one place — `src/pkg/delegatedecision/policy.go` (`PolicyText`, the owner's words verbatim, including the contributor-adequacy rail adopted in #13998; the Go rules beside it implement them) — so the dashboard, docs and any future LLM-backed decider share the same text. In short: be decisive and, except for the safety rails, never ask the owner a question back; pick the option that keeps work moving and is reversible. **PRs:** prefer an adequate contributor PR over a hive duplicate (a mergeable, current competitor prompts a human comparison of issue coverage, checks and review approval; keep the hive PR open and parked until adequacy is confirmed, rather than closing it solely on an issue reference; an unmergeable contributor PR is superseded with credit); approve small, necessary, low-risk changes (CI pins, read-only RBAC adds, warning text, workflow hygiene) once the sentinel finding is checked and explained; a sentinel alert on a trusted author's PR is approved with a one-line "reviewed, intended" rationale; a reporter-trust hold is cleared when the fixing PR is sound; a contributor who has gone quiet is superseded now, not after a deadline (#13975). **Issues:** dedupe aggressively (`Duplicate of #N`, cross-link overlaps); secrets / PAT / org-permission items are owner-only — `needs-owner` + `hold` with one comment stating the exact owner action, and no more failure issues for that root cause; flaky tests are quarantined with a retry and a deflake task rather than blocking; CI hygiene gate proposals (actionlint, duplication ratchets, link-checker allowlists) are approved as ratchets (baseline; fail only on increases; warning-only briefly, then enforce); design proposals with concrete acceptance criteria are accepted as proposed with explicit defaults; a "review backlog" item is never auto-closed when its PR closes unmerged (drop the relation, comment, re-triage). Each decision names the rule that fired (`dedupe`, `owner-only`, `flaky-test`, `ci-ratchet`, `design-proposal`, `review-backlog`, `default-approve`, `prefer-contributor`, `contributor-offline`, `sentinel-trusted`, `small-low-risk`, `reporter-trust`, `fix-then-merge`, `default-release`, `hive-policy`, `sentinel-untrusted`, `rail-hold`) in the comment and in the job's `decision.rule`. + +**Safety rails** (`ApplyRails`, applied after the policy and before anything is written): the delegate never merges a PR — it only records the approval the auto-merge engine already acts on per repository settings (a merger/owner requester's approval is written as the queue line the sweep parses; anyone else's as `/lgtm` + `/approve` comments attributed to them); it does not touch an item wearing `hold` (refused, nothing written — release the hold first if you want it decided); it never grants org membership, secrets or permissions (those items get `needs-owner` + `hold` and the one owner-action comment, and keep their park); and a question that is a policy or scope change to the hive itself (settings, governance docs, security posture — the `governance` label or those words in the park reason) is left parked: the delegate posts "hive recommends: …; owner to confirm" and keeps 085. A sentinel-flagged PR whose author lacks write access is likewise a recommendation only. Because the sentinel sweep re-applies its label when a hive account removes it, a trusted-author sentinel approval posts the "reviewed, intended" comment and leaves the final queueing to the person who clears the label — the job's `notes` say so. + +**Audit marker.** Every comment the delegate writes ends with `` (and a visible "Decided by … as the owner's delegate under the owner-delegate policy (rule `…`)" line), so a delegated decision can always be told from a human one on GitHub, in the Decisions log, and in any later count. + +**Setting.** `decisions.delegate.enabled` (default **true** — the same default as the other "let the hive do it" switches); `decisions.delegate.repos..enabled` (or the bare repository name) overrides per repository. `decisions.delegate.llm.enabled` (default **false**) adds the LLM step described below. When it is off the menu entry stays visible but the endpoint answers 403 naming the setting. See [operator-reference.md](https://github.com/hivecommons/hive/blob/v5/src/docs/operator-reference.md). + +**What this release decides mechanically.** The decider is rules-first: the cases above that are mechanically decidable (dedupe, owner-only, flaky, CI ratchet, design proposal, review backlog, contributor-offline, sentinel-trusted, small-low-risk, reporter-trust, hive-policy) are decided from the item itself; anything else falls to the reversible default (approve direction on an issue; release a green hive PR; ask for the fix first when the head is red or conflicting). Competing-PR detection and a real adopt-supersede trigger are tracked in a follow-up issue linked from the PR that added this action. -**Multi-select.** The checkbox in a row's left gutter selects the row; **Shift+click** selects the range from the last clicked row, **Cmd/Ctrl+click** toggles, **Cmd/Ctrl+Shift+click** adds a range to what is selected; with a row focused, **Space** toggles it (Shift+Space extends). Cmd/Ctrl- or Shift-clicking the row itself, away from its links, selects it too. A selection bar appears above the list with the count, **Clear**, and the same actions for the whole selection — *Decide…* (opens the group as a menu), *Hold*, *Release hold*, *Re-evaluate now*, *Mark needs-human…*, *Copy links* — each enabled only when it is valid for every selected row and otherwise greyed with the first reason ("Not applicable to acme/console#12", "Sign in with write access…"). Right-clicking a selected row acts on the whole selection. Bulk actions run one request per row, in list order, under a progress toast ("Hold 2/5 — acme/console#12…"); the result toast lists each failure by row and the successes stand, after which the list and the status snapshot refresh (a hive-wide rescan is sent once per batch). Selection is keyed on the row's item (repository, kind, number), not its node, so the live re-render keeps it; a row that leaves the list — merged, closed, filtered or paged out — leaves the selection. +**LLM step for the non-mechanical cases.** With `decisions.delegate.llm.enabled: true` (default **false**), an item the rules leave at the reversible default (rule `default-approve` or `default-release`) is shown to one *owner-delegate decider* turn: the item, its comment thread, sentinel findings, linked issues/PRs, the repository's auto-merge setting, the author's association and reporter-trust state, its labels, and `PolicyText` verbatim. The model answers with a structured `{verdict, text, rationale, actions[]}`; the reply is rejected unless every verdict, action kind and label is in the delegate's closed set and fits the item (no `approve-review` on an issue, no action on a park-keeping verdict, …), and unless the actions carry the verdict out: an acting verdict must include its recording action (`comment-decision`, or `close-as-duplicate` for the close verdicts, plus `approve-review` / `adopt-supersede` where the verdict names them), `labels-remove` can never name `stage/085-needs-decision` (only the recording action lifts the park, with the comment and the marker), and `close-duplicate` closes the newer item in favour of an older one. An accepted reply is recorded under rule `llm-delegate` and goes through the same safety rails and executor as a rule decision; a model error or rejected reply keeps the reversible default and the job's `notes` say why. Rules-first stays authoritative: every case a rule decides, and every item wearing `hold`, never reaches the model. The call uses the governor reviewer endpoint (`governor.trajectory`, falling back to `governor.litellm`); `decisions.delegate.llm.model` overrides the model. ## Agent activity @@ -734,4 +767,4 @@ This section exists on the v6 line only. On v5 the same facts are in the Diagnos **When it appears.** On v6 only, when the hive reports platform data. -**Settings that change it.** Your code hosting settings and optional service settings change it. +**Settings that change it.** Your code hosting settings and optional service settings change it. The code hosting service is `project.forge`; a self-managed GitLab or a Gitea/Forgejo instance also shows its URL. See [Forges](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md). diff --git a/docs/content/hive/documentation-map.md b/docs/content/hive/documentation-map.md index fd3d9b6e..1e41e661 100644 --- a/docs/content/hive/documentation-map.md +++ b/docs/content/hive/documentation-map.md @@ -10,6 +10,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Manual provisioning](/docs/hive/manual-provisioning) — heartbeat-only cluster provisioning, hub access roles, and common gotchas. - [DiskPressure recovery runbook](https://github.com/hivecommons/hive/blob/v5/src/docs/diskpressure-recovery-runbook.md) — spoke down with `/data` crash-looping on kubelet eviction: diagnosis, safe deletes, volume expansion/migration, and the health-check/governor-pause/self-janitor that degrade gracefully before it gets there. +- [Agent backend-auth failure recovery runbook](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-auth-recovery-runbook.md) — the response path when an agent's model backend stops authenticating: sizing the impact from the `backend_auth` canary and `auth_health` roll-up, re-authenticating, and clearing the `inference_auth_failed` fleet report. - [Hosted Hive Hub onboarding](https://github.com/hivecommons/hive/blob/v5/src/docs/hosted-hub.md) — signing in at `https://hive.hivecommons.dev`, requesting a hosted hive, installing the GitHub App, configuring model gateways and Copilot login, reading `/fleet`, and fixing common setup problems. - [Self-hosted hub deployment](https://github.com/hivecommons/hive/blob/v5/src/docs/hub-deployment.md) — `HIVE_MODE=hub`, hub storage, heartbeat secrets, and SaaS spoke registration. - [`CAP_NET_ADMIN` and self-hosted spokes](/docs/hive/net-admin-requirement) — the container runs with or without `NET_ADMIN`; granting it (`--cap-add NET_ADMIN` / `securityContext.capabilities.add`) enables the full forced-proxy-egress gate, and what the degraded best-effort mode means without it. @@ -22,6 +23,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Work-source terminology](https://github.com/hivecommons/hive/blob/v5/src/docs/work-source-terminology.md) — glossary and audit notes for keeping user-facing copy neutral across GitHub, GitLab, Gitea, Linear, Jira, and future work sources. - [Release channels](/docs/hive/release-channels) — `stable`/`candidate`/`edge` moving image tags, per-line channel ownership (`v5` → `candidate`/`latest`, `v6` → `edge`, `v4` → none), switching a hive to a channel, the version pill, and the timeline for when a hive follows `stable` after the tag moves. - [Digest-verifiable rollback](https://github.com/hivecommons/hive/blob/v5/src/docs/release-rollback.md) - the operator runbook for pinning a hive back to a prior immutable short-SHA build for all three images (`hive`, `hive-contributor`, `hive-hub`), stopping the hub automation that would undo it, and verifying by digest rather than by tag that the pin landed on the running spoke. +- [Outcome benchmarks](https://github.com/hivecommons/hive/blob/v5/src/docs/benchmarks/README.md) — reproducible mediated-vs-unmediated comparison (revert rate, change-failure, time-to-merge, CI at merged head, changes requested pre-merge, cost per merged PR) from stored review evidence, with the monthly reports, generated charts, the plain-language README cards (minimum-base and vocabulary rules) and the README outcomes block it keeps in sync. - [v5 GA readiness bar](https://github.com/hivecommons/hive/blob/v5/src/docs/v5-ga.md) — measurable release-train, migration, safety, and governance criteria that must be evidenced before v5 can be promoted beyond the active-development `edge` channel. - [v5 GA candidate week sequencing](https://github.com/hivecommons/hive/blob/v5/src/docs/v5-candidate-week.md) — dependency-ordered plan for the remaining human-gated GA-bar steps: candidate-SHA designation, candidate-pinned measurements, the two bound exercises, and the v4 freeze evaluation. - [Stable soak and promotion policy (v5 line)](/docs/hive/operations/stable-soak-and-promotion-policy) — the CI-enforced gate between `candidate` and `stable`: soak conditions, immutable short-SHA rollback tags, the emergency exception path (what it waives, and the post-hoc evidence that closes its follow-up issue), and the ledger of exceptions taken with their per-image rollback digests. @@ -36,6 +38,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Dashboard-triggered standalone upgrades](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard-standalone-upgrades.md) — the dashboard upgrade button for standalone (Compose/Podman-Quadlet) hives: the `HIVE_DEPLOYMENT_RUNTIME`/`HIVE_DEPLOYMENT_PODMAN_MODE` runtime contract, the closed host-side helper (`hive-dashboard-upgrade-helper.sh`), and why an unproven runtime deliberately hides the button. - [Work sources](/docs/hive/work-sources) — GitHub Issues, GitHub Projects, Linear, Jira Cloud, Jira Data Center / Server, run-stage, and Wavefront work enumeration. - [Work source providers](/docs/hive/integrations/work-source-providers) — implement and contribute a provider that maps source-native work items onto `pkg/worksource.WorkSource`. +- [Forges: GitLab, Gitea and Forgejo](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md) — `project.forge`, the `gitlab:`/`gitea:` keys, which `pkg/forge` operations each adapter supports, which governor writes already use them, and how the dashboard shows the forge. - [Hive Labels and Control Signals](/docs/hive/labels-and-control-signals) — authoritative operator reference for labels, holds, approvals, contributor skip labels, planning labels, and dashboard bands. The old [Hive issue labels](https://github.com/hivecommons/hive/blob/v5/src/docs/issue-labels.md) page redirects here. - [GitHub webhooks: cadence, patch and reconcile](https://github.com/hivecommons/hive/blob/v5/src/docs/webhooks.md) — the three writers of the GitHub snapshot (reconcile / webhook patch / write-through), the accuracy guarantees of #13373, `eval_interval_s` vs `eval_interval_webhook_s` and the other cadence keys with defaults, and the automatic fallback to the polling cadence when webhooks degrade (`/api/status.webhooks.mode`). - [Spoke dashboard](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard.md) — the static dashboard FAQ panel contract: not ACMM-gated, no JS/fetch, grouped L1-L6/runs/contributors/claims/cost help, and guarded config-key references. @@ -65,6 +68,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Fleet health: the verdict and remediation hints](https://github.com/hivecommons/hive/blob/v5/src/docs/fleet-health.md) — the per-hive green/amber/red/unknown verdict on `/fleet`: what each state means, ACMM-banded output expectations, precedence (App broken beats provider limit beats budget beats generic no-output), the cause → remediation table, detector semantics (error streaks, consent wedge, no cadence, channel lag) with the carry-forward rule that lets a recovered hive clear its own alarm, and a symptom → fix troubleshooting table. - [Fleet drift signals](https://github.com/hivecommons/hive/blob/v5/src/docs/fleet-drift-signals.md) — the per-hive deviation badges on My Hives: all signal kinds (`heartbeat-stale`, `duplicate-spoke`, `identity-split`, `version-absent`, `pinned-image`, …) with severities and who fixes each (owner vs hub operator), the derived fleet norm behind `branch-mismatch`/`version-behind`, the deliberate suppression rules (placeholders, actively-upgrading hives, status-flipping yielding to duplicate-spoke), and the in-memory first-seen semantics. - [Fleet self-reporting (`governor.fleet_report`)](https://github.com/hivecommons/hive/blob/v5/src/docs/fleet-report.md) — how a hive reports its own hive-attributable failures upstream to `hivecommons/hive`: the `file_upstream` opt-in (default off = dry-run preview on the dashboard), the triggers (`acmm-shortfall` after two unmet weekly epochs with attributable evidence — rolled up onto an open self-report about the same component when there is one — and `runtime-error` self-reports from an allow-list of classes including agent stalls and crash loops; the standalone `hive-code-defect` trigger is retired), exactly which fields leave the hive and which never do (the raw hive ID is replaced by a truncated SHA-256), fingerprint deduplication via comment + 👍 reaction instead of duplicate issues, and recovery comments that close only issues the hive itself opened. +- [Push-reported cluster node health](https://github.com/hivecommons/hive/blob/v5/src/docs/push-reported-node-health.md) — how spokes on clusters the hub cannot `kubectl` push node health in their heartbeat (`hub.cluster_id` / `HIVE_CLUSTER_ID`, the heartbeat node-health block, and how the hub merges it into cluster views). - [Agent self-healing watchdog](https://github.com/hivecommons/hive/blob/v5/src/docs/agent-watchdog.md) — liveness and readiness reconciliation for launched agents: liveness classification, restart backoff, crash-loop escalation, the auth probe that refuses to restart into dead credentials, and the `conditions` array on `/api/agents`. Ships in `mode: observe`, which audits the restarts it would have made without making them. - [Audit log format](https://github.com/hivecommons/hive/blob/v5/src/docs/audit-log.md) — the JSONL schema of `/data/audit.jsonl`: its fields, how to parse the flat `detail` string (and the typed `repo`/`target` fields on GitHub write entries), the pseudo-users, and why size-triggered rotation means the effective lookback varies per hive rather than being 90 days. - [Per-repo agent pause](https://github.com/hivecommons/hive/blob/v5/src/docs/repo-pause.md) — quieting ONE repository without stopping the hive: `project.paused_repos`, the dashboard toggle and `POST /api/repos/pause`, the provenance every pause carries (who/when/why), and exactly which layers enforce it — the MITM proxy, the `hive-open-pr`/`hive-merge` relays, work enumeration and the auto-merge sweeps — plus what a pause deliberately does *not* stop (reads, the hive's own control plane, and your own pushes). @@ -94,6 +98,8 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und [the design record](https://github.com/hivecommons/hive/blob/v5/src/docs/design/tui.md) for the reasoning behind it. - [`bd` beads CLI](https://github.com/hivecommons/hive/blob/v5/src/docs/beads-cli.md) — work-ledger and knowledge command reference for operators and contributors. - [`crux` CLI](https://github.com/hivecommons/hive/blob/v5/src/docs/crux-cli.md) — run, list, tail, kick and stop agents through `crux.Runtime` with no governor or dashboard; definition formats, the control socket served by `crux run`, and why the runtime being in-process shapes it. +- [`crux serve`: the runtime over a local API](https://github.com/hivecommons/hive/blob/v5/src/docs/crux-serve.md) — the Stage 3 process boundary: HTTP+JSON on a unix socket (TCP with a bearer token), the `/v1` routes and event stream, fail-closed rules, and how to drive it with curl, the `crux` CLI's `--server` mode and the Go client in `crux/api`. +- [Publishing Crux](https://github.com/hivecommons/hive/blob/v5/src/docs/crux-publish.md) — how `hivecommons/crux` is produced from `src/crux` (plain copy, module path rewritten, fast-forward commits), what is preserved there, the `CRUX_SYNC_TOKEN` credential, manual sync and how a `crux/vX.Y.Z` tag becomes a release. - [Backup and restore](/docs/hive/backup-dr) — `hive-backup`, Kubernetes CronJob, spoke backup scope, and setting the backup encryption key from Governor Config (hosted flow). Host-level backup, restore, and `docker compose down -v` are given per runtime: Docker Compose, and Podman/Quadlet with the executed backup → wipe → restore cycle in both root modes, the rootless mapped-UID trap that makes a host-shell `tar` skip the GitHub App key, and the Docker→Podman migration (the two volume stores are never shared). Also covers restoring a `pkg/spokebackup` archive: the archive-path → container-path mapping, and the executed `hive-backup restore` path that applies it — including the identity guard that refuses to splice one hive's config and GitHub App keys onto another's ([#6529](https://github.com/hivecommons/hive/issues/6529)). - [Hub disaster recovery](https://github.com/hivecommons/hive/blob/v5/docs/HUB_DISASTER_RECOVERY.md) — the hub-level runbook that goes beyond per-hive backup: hub backup and key escrow, spoke fleet recovery, Slack blast, and the full rebuild-from-zero procedure after a catastrophic loss. - [Deployment helper scripts](https://github.com/hivecommons/hive/blob/v5/src/docs/deployment-scripts.md) — the all-in-one LXC setup, Proxmox LXC, and blue-green Compose helpers. All are Docker-only; the page states each script's runtime scope and where a Podman operator should go instead. @@ -136,6 +142,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Knowledge curator](https://github.com/hivecommons/hive/blob/v5/src/docs/knowledge-curator.md) — automatic fact extraction and promotion knobs, plus the other `knowledge:` sub-sections: `git_sources` (indexing a remote repo, layer semantics, private-repo auth (unsupported), diagnosing a failed source), `vaults` (local Obsidian vaults, git-sync), `documents` (PDF/URL import), and `bead_synthesizer` (on-by-default bead→wiki synthesis and retention policy, and how to turn it off). See also [knowledge confidence scoring](https://github.com/hivecommons/hive/blob/v5/src/docs/knowledge-confidence.md) for dashboard confidence badges and score explanations. [Knowledge lifecycle states](https://github.com/hivecommons/hive/blob/v5/src/docs/knowledge-lifecycle.md) covers `draft`/`approved`/`deprecated`/`superseded` facts and how stale knowledge is kept out of agent prompts. [Scoped knowledge table of contents](https://github.com/hivecommons/hive/blob/v5/src/docs/knowledge-toc.md) covers the capped `GET /api/knowledge/toc` listing, the full-read `GET /api/knowledge/entry/{id}` endpoint, and the `knowledge_toc` MCP tool. Generated repository code maps (`knowledge.code_maps`) are covered in the knowledge curator page. - [Public knowledge MCP endpoint](/docs/hive/public-knowledge-mcp) — the owner-switched, read-only `POST /mcp/knowledge` surface that lets any external agent (Goose, Claude, Copilot) read a hive's operational knowledge without a session; what is and is not exposed, and client configuration. - [Skill registry](https://github.com/hivecommons/hive/blob/v5/src/docs/skills.md) — the `/data/skills/` file format and front-matter fields. **Delivered to agents today**: skills are opt-in per agent via `skills: [name, ...]`, resolved registry-first with an `AGENTS.md` repo-local fallback, and the rendered block is prepended to the kick's `${KNOWLEDGE}`. Both sources reload on every kick. An 8 KiB whole-skill cap applies; a skill that would exceed it is dropped whole, never truncated. +- [House style](https://github.com/hivecommons/hive/blob/v5/src/docs/house-style.md) — the short how-to-change section that opens every kick; repo `HIVE_STYLE.md` > `project.style` > built-in, size-capped and ioscan-checked. - [AGENTS.md repo instructions](https://github.com/hivecommons/hive/blob/v5/src/docs/agents-md.md) — the per-repo `AGENTS.md` file format Hive's parser (`pkg/agentsmd`) understands, including front-matter `skills:` and inline `## Skill:` sections. **Wired into kicks, but needs a checkout**: Hive agents keep no clones, so set `project.checkouts_dir` to a directory holding one checkout per repo. Without it there is no root to read and injection stays a no-op, which is the default. - [Agent peer-awareness logging (pluk)](https://github.com/hivecommons/hive/blob/v5/src/docs/agent-logging.md) — pluk log format, `hive-panes`, availability, and retention. - [Experiment Lab (Nous)](https://github.com/hivecommons/hive/blob/v5/src/docs/strategy-lab.md) — experiment lifecycle, dashboard/API configuration, fast-fail bounds, and the gate-decision flow. No `nous:` block in `hive.yaml`. @@ -150,6 +157,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Inception](https://github.com/hivecommons/hive/blob/v5/src/docs/inception.md) — operator guide to the L1 brainstorm/inception workflow: phases, API, and template variables. - [Planning intelligence](https://github.com/hivecommons/hive/blob/v5/src/docs/planning-intelligence.md) — how a large GitHub issue becomes an epic the architect lane decomposes into child beads, the human plan-review gate that withholds those children until approved, and stall-replan. - [Review swarm](https://github.com/hivecommons/hive/blob/v5/src/docs/review-swarm.md) — the five review perspectives, the verdict collector and its report contract, and the opt-in merge-gate integration and bounded auto-fix cycle for review findings. +- [Swarm mode](https://github.com/hivecommons/hive/blob/v5/src/docs/swarm.md) — focusing the hive on one repository for a bounded window: Discord announcements, theming (`swarm:` config, `HIVE_SWARM_EVENT_NAME` / `HIVE_SWARM_CALL_TO_ARMS`), the `/api/leaderboard/swarm` endpoint and scoring. - [Review evidence bundle](https://github.com/hivecommons/hive/blob/v5/src/docs/review-evidence.md) — the per-PR evidence bundle schema (v1), canonical hash, Ed25519 signing, offline verification, and retention design. - [Evidence reporting](https://github.com/hivecommons/hive/blob/v5/src/docs/evidence.md) — the guarded control-arm (`control_window: on|off`) evidence report: per-repository outcome comparison, the minimum-denominator rule, and why it is reporting groundwork rather than an enabled experiment. - [SOC 2 control mapping (not a compliance claim)](https://github.com/hivecommons/hive/blob/v5/src/docs/soc2-control-mapping.md) — maps common auditor change-management questions to bundle fields, with sample policy language and the gaps Hive does not cover. @@ -170,6 +178,9 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Podman preflight: subordinate IDs, graphroot, and networking](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-preflight-ids.md) — read-only diagnostics for rootless subordinate UID/GID delegation, unsupported (NFS and other distributed) container storage, and the rootless network backend/helper, with remediation that never edits `/etc/subuid` or `/etc/subgid`. - [CLI backend setup](https://github.com/hivecommons/hive/blob/v5/docs/backend-setup.md) — setup notes for Claude, Copilot, Goose, Bob, Pi, Codex, and Aider. - [Backend support tiers and acceptance bar](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-support-tiers.md) — the policy for adding a CLI backend: the three tiers (core/headless-pod, supported/confined, experimental/unconfined), the criteria each requires with the exact file or test a PR must touch (list parity, declared posture, flag-honoring proof, credential detection, pinned install, refusal gate, confinement floor, headless entry point, unattended-credential verification, allowlist touchpoints), and how a backend moves between tiers. +- [Gap analysis: Claude Managed Agents](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-gap-claude-managed-agents.md) — how Anthropic's hosted agent runtime maps onto the backend acceptance bar: task prompt and repo delivery, which session setups give the hosted sandbox a GitHub write path Hive's proxy cannot see (and so cap it at advisory mode), the usage and cost fields available for metering, and a recommendation for a hosted-execution tier. +- [Gap analysis: OpenAI Agents API](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-gap-openai-agents-api.md) — how OpenAI's Responses API, hosted tools and Agents SDK map onto the backend acceptance bar: prompt and repo delivery, which hosted-container setups egress GitHub directly (and so cap the backend at advisory mode), the usage fields available for metering, and a recommendation for a hosted-execution tier. +- [Omnigent CLI contract and policy composition](https://github.com/hivecommons/hive/blob/v5/src/docs/backend-omnigent-cli-contract.md) — the contract of the Omnigent CLI against the acceptance bar: what Hive actually launches (`omnigent run --server local`, and why both words matter), the six contract facts (upstream's non-interactive `run -p`, not wired by Hive; how the prompt is passed, exit codes on success/failure/denial, where the transcript lands, model and cost reporting, the tool-denial list), which safety flags it honours, `detect_cli` credential detection, the confinement posture and refusal gate that place it at T3 (experimental/unconfined), and how its policy layer composes with Hive's. - [Inference backends](https://github.com/hivecommons/hive/blob/v5/docs/inference-backends.md) — vLLM, llm-d, LiteLLM, and Model Gateway troubleshooting. - [apiproxy](https://github.com/hivecommons/hive/blob/v5/src/docs/apiproxy.md) — Anthropic-compatible proxy logging and deployment notes. - [Outreach anti-spam ruleset](https://github.com/hivecommons/hive/blob/v5/docs/outreach-antispam.md) — the deduplication and anti-spam rules the outreach agent operates under across awesome lists, project issues, directories, and community threads. @@ -177,10 +188,12 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und ## Architecture and design +- [What Hive is](https://github.com/hivecommons/hive/blob/v5/src/docs/what-is-hive.md) — the project overview that used to open the README: how Hive differs from a terminal agent, hub and spoke, the contributor relay, project vs hosted service, "does Hive fit my project", run-it paths, the ACMM level table, works-with, related projects, and where adopters/contributing/security live. - [Architecture](/docs/hive/architecture) — process model, governor loop, guardrails, hub/spoke, and walkthrough. - [Automatic repo ACMM onboarding reconciler](https://github.com/hivecommons/hive/blob/v5/src/docs/design/repo-acmm-onboarding.md) — proposed design for per-repo ACMM targets, gap-to-work reconciliation, human hand-off, waivers, and anti-gaming controls (RFC #6235). - [Hive federation design](https://github.com/hivecommons/hive/blob/v5/docs/federation-design.md) — the multi-hive registry: live `/api/hives` endpoints, project onboarding, contributor flow across hubs, and what remains future design work. -- [Public roadmap](/docs/hive/roadmap) — the v4 direction as Now / Next / Later, with the tracking issue behind each item. Directional rather than a promise, maintained by pull request; check the date in its header before relying on the ordering. +- [Public roadmap](/docs/hive/roadmap) — the v5 direction as Now / Next / Later, with the tracking issue behind each item. Directional rather than a promise, maintained by pull request; check the date in its header before relying on the ordering. +- [Integrations](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations.md) — one page of logos and status (Supported / Opt-in / Planned) for agent backends, work sources, forges, sandboxes and upstream projects. - [Landscape and positioning](/docs/hive/landscape) — how Hive's operations-plane design compares to nearby agentic orchestration tools, with public references per project. Explicitly time-sensitive; check the conducted date in its header before quoting product details. - [CNCF sandbox onboarding status](https://github.com/hivecommons/hive/blob/v5/src/docs/cncf-sandbox-onboarding.md) — repo-local map of the onboarding tracker and repository evidence (LICENSE, governance, security, OWNERS, DCO). - [CNCF reference architecture](https://github.com/hivecommons/hive/blob/v5/src/docs/cncf-reference-architecture.md) — CNCF submission/reference template. @@ -194,6 +207,7 @@ Documentation for the current Hive line (branch `v5`; the code and docs live und - [Podman Compose-provider selection spike](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-compose-provider-spike.md) — why `podman compose` must name its provider explicitly, and which provider needs no Docker tooling. - [Sourcing token metering from ccusage behind a tokens.Source seam](https://github.com/hivecommons/hive/blob/v5/src/docs/design/token-metering-ccusage.md) — proposed design for ccusage-backed token metering with attribution preservation, Bob fallback, and cutover diagnostics (RFC #6234). - [Trajectory review](https://github.com/hivecommons/hive/blob/v5/src/docs/trajectory-review.md) — trajectory safety lane and review signals. +- [Central stage engine implementation plan](https://github.com/hivecommons/hive/blob/v5/src/docs/stage-engine-plan.md) — companion to [ADR-0025](/docs/hive/adr/0025-central-stage-engine): the PR-by-PR plan that landed `pkg/stageengine` as the single writer of `stage/*` labels. - [Podman Quadlet `.container`/`.pod` spike](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-quadlet-container-pod-spike.md) — feasibility result for explicit Quadlet units: readiness via `Notify=healthy`, the startup-timeout trap, and what the generator does not validate. ## Historical/design notes @@ -209,7 +223,7 @@ Some documents describe planned or design-only work rather than live features. T - [OSS Scanner threat model](https://github.com/hivecommons/hive/blob/v5/.oss-scanner/threat_model.md) — the scanner-facing companion kept at the repository root with [its Dockerfile](https://github.com/hivecommons/hive/blob/v5/.oss-scanner/Dockerfile): where untrusted input enters, which packages matter most, how to exercise a hive offline, and the severity rubric used to triage findings from [Anthropic's OSS Scanner](https://red.anthropic.com/oss-scanner/). - [Security response process](https://github.com/hivecommons/hive/blob/v5/src/docs/security-response.md) — who responds to a vulnerability report (the Maintainer Committee, rostered in `OWNERS`), the end-to-end handling flow and the 60-day fix commitment, how membership is added and rotated, the escalation path if a reporter gets no response, and the project's known limits stated plainly. - [CNCF TAG-Security self-assessment](https://github.com/hivecommons/hive/blob/v5/src/docs/security-self-assessment.md) — the CNCF Incubation self-assessment artifact: metadata, actors/actions/goals, critical security components with file/line citations, project compliance, secure development practices, vulnerability response process, and the three most significant known weaknesses stated plainly. -- [ioscan red-team evaluation](https://github.com/hivecommons/hive/blob/v5/src/docs/ioscan-red-team.md) — measured, not asserted: a 43-payload adversarial corpus run through the production input path. 42% of attacks withheld from the agent, 58% reached it verbatim, with the per-family breakdown, the two Unicode table gaps a single character exploited (both now closed), the benign-text false positives, and the proxy deny rules that contain what gets through — verified across all four ACMM modes on every test run. +- [ioscan red-team evaluation](https://github.com/hivecommons/hive/blob/v5/src/docs/ioscan-red-team.md) — measured, not asserted: a 63-payload adversarial corpus plus 12 benign controls run through the production input path. 97% of attacks withheld from the agent, 3% reached it verbatim, one benign false positive, with the per-family breakdown, the two Unicode table gaps a single character exploited (both now closed), and the proxy deny rules that contain what gets through — verified across all four ACMM modes on every test run. - [CNCF General Technical Review](https://github.com/hivecommons/hive/blob/v5/src/docs/general-technical-review.md) — the full Day 0/1/2 GTR questionnaire answered against this repository, cited file-by-file, with every currently-unanswerable question marked `[NEEDS OPERATOR INPUT]` rather than guessed at. - [Heartbeat bearer cutover](https://github.com/hivecommons/hive/blob/v5/src/docs/heartbeat-bearer-cutover.md) — retiring the fleet-wide heartbeat bearer, whose possession proves only "some provisioned spoke" and lets any spoke heartbeat as any hive, in favour of the per-hive key — without re-provisioning the fleet, and the precondition that gates the removal. - [Rootless Podman startup and exit-77 behavior](https://github.com/hivecommons/hive/blob/v5/src/docs/podman-rootless-startup-spike.md) — measured rootless matrix: fail-closed exit 77, gate installation under `--cap-add NET_ADMIN`, proven interception, and what is still unproven. diff --git a/docs/content/hive/env-vars.md b/docs/content/hive/env-vars.md index 0860eb61..78ffa77e 100644 --- a/docs/content/hive/env-vars.md +++ b/docs/content/hive/env-vars.md @@ -19,7 +19,7 @@ This reference is compiled by hand from the Go source under `src/`, the deployme | `HIVE_DASHBOARD_COOKIE` | No | none | **Client-side only** - read by `hivectl tui`, never by the server. Cookie header value (e.g. `hive_session=...`) carrying a per-user session, for hives that do not accept the shared token: hub-hosted ones, and spokes with an `authorized_users` allowlist. See [hivectl.md, Credentials](/docs/hive/hivectl#credentials). | | `HIVE_DASHBOARD_BIND` | No | `127.0.0.1` for the legacy `dashboard/server.js` | Legacy Node dashboard listen address. Leave unset for loopback-only binding; set `HIVE_DASHBOARD_BIND=0.0.0.0` only when an authenticated reverse proxy or equivalent network control protects the unauthenticated legacy control endpoints. | | `HIVE_AUTHORIZED_USERS` | No | none | Comma-separated direct-route dashboard allowlist, with optional `user:role` entries. Used when `dashboard.authorized_users` is empty. | -| `HIVE_SELF_AUTHORIZATION_HOLD` | No | `github.self_authorization_hold` / `project.repo_policies[].self_authorization_hold`; when all are unset, default `true` through ACMM L5 and `false` at ACMM L6 Fully Autonomous | Process-level override for the #5117 self-authorization hold. Set `false` to let this hive skip and release self-authorization holds for every repo while preserving unrelated human holds; set `true` to keep the hold enabled even at L6. | +| `HIVE_SELF_AUTHORIZATION_HOLD` | No | `github.self_authorization_hold` / `project.repo_policies[].self_authorization_hold`; when all are unset, default `true` through ACMM L5 and `false` at ACMM L6 (Autonomous) | Process-level override for the #5117 self-authorization hold. Set `false` to let this hive skip and release self-authorization holds for every repo while preserving unrelated human holds; set `true` to keep the hold enabled even at L6. | | `HIVE_SELF_EXPERIMENTS_OPT_IN` | No | unset (off) | Opts this hive in to receiving hub-delivered Hive-self feature-flag assignments over heartbeat (`pkg/hub/spoke/self_experiment_flags.go`). Only the exact string `true` enables it; `1`, `yes`, `on` and any other value leave it off. Consent is local: the hub cannot opt a spoke in. Unsetting it withdraws the flags on the next heartbeat. Separate from the Experiment Lab/governor settings. | | `HIVE_REPORTER_TRUST_HOLD` | No | `github.reporter_trust_hold` / `project.repo_policies[].reporter_trust_hold`; when all are unset, follows `project.issue_filter.reporter_trust.enabled` | Process-level override for the #9665 reporter-trust hold (a PR whose rationale traces to an issue from an untrusted reporter is held at every ACMM level). When set, the dashboard toggle and per-repo overrides are locked and say so. | | `HIVE_REPO` | No | none | Bootstrap shortcut in `owner/repo` form; fills `project.org`, `project.repos`, and `project.primary_repo` if missing. | @@ -34,8 +34,8 @@ This reference is compiled by hand from the Go source under `src/`, the deployme | `HIVE_NPS_RELAY_URL` | No | `hub.nps_relay_url`; empty (relay disabled) | Base URL of the NPS relay for standalone hives (the hivecommons relay is `https://docs.hivecommons.dev/api/nps`). A spoke with NPS enabled and no hub link self-registers a generated Ed25519 key at `/register` and POSTs signed responses here, with no token to configure; a hub pulls from `/pending` and acks at `/ack`. Must be `https` (plain `http` only to a loopback host). See [NPS feedback prompt](https://github.com/hivecommons/hive/blob/v5/src/docs/nps.md#standalone-hives-the-nps-relay). | | `HIVE_NPS_RELAY_PULL_SECRET` | No | `hub.nps_relay_pull_secret`; empty | Secret, hub only. Authenticates the hub's periodic pull from the NPS relay. Without it (or without a relay URL) the hub does not pull. Never logged. | | `HIVE_HUB_TASK_STATUS_PUSH` | No | `hub.task_status_push`, default `true` | Enable the separate spoke task-status push loop. Accepts `1/true/yes/on` or `0/false/no/off` (case-insensitive); empty/invalid values fall back to config. Read at startup; restart after changing. Core heartbeat and hub-managed upgrade/config delivery remain enabled. | -| `HIVE_HUB_PUBLIC_URL` | No | compiled fallback `https://hive.hivecommons.dev`; hosted service sets `https://hive.hivecommons.dev` | Hub canonical public origin used to derive OAuth/OIDC callback URLs, hub Open Graph/notification links, same-origin checks, and the registrable-domain scope for hub SSO cookies. Public hub operators should set this to `https://hive.hivecommons.dev` for the cutover; leave the legacy value only while deliberately serving the redirect hostname. **Changing the registrable domain here signs users out of every first-party sibling product left on the old one** — the `hive_hub_user` cookie is scoped to this URL's registrable domain and a browser will not send it anywhere else. See [Moving a public hostname](https://github.com/hivecommons/hive/blob/v5/src/docs/hivecommons-migration.md#moving-a-public-hostname). | -| `HIVE_HUB_SPOKE_DOMAIN` | No | compiled fallback `hive.hivecommons.dev`; hosted service sets `hive.hivecommons.dev` | Parent domain used for hub-provisioned spoke ingress hostnames (for example `.`). Kept separate from `HIVE_HUB_PUBLIC_URL` because the wildcard spoke domain may differ from the hub's own hostname. | +| `HIVE_HUB_PUBLIC_URL` | No | compiled fallback `https://hive.hivecommons.dev` (`defaultHubPublicURL`, `src/pkg/hub/saas_auth.go`) | Hub canonical public origin used to derive OAuth/OIDC callback URLs, hub Open Graph/notification links, same-origin checks, and the registrable-domain scope for hub SSO cookies. Public hub operators should set this to `https://hive.hivecommons.dev` for the cutover; leave the legacy value only while deliberately serving the redirect hostname. **Changing the registrable domain here signs users out of every first-party sibling product left on the old one** — the `hive_hub_user` cookie is scoped to this URL's registrable domain and a browser will not send it anywhere else. See [Moving a public hostname](https://github.com/hivecommons/hive/blob/v5/src/docs/hivecommons-migration.md#moving-a-public-hostname). | +| `HIVE_HUB_SPOKE_DOMAIN` | No | compiled fallback `hive.hivecommons.dev` (`defaultHubSpokeDomain`, `src/pkg/hub/saas_auth.go`) | Parent domain used for hub-provisioned spoke ingress hostnames (for example `.`). Kept separate from `HIVE_HUB_PUBLIC_URL` because the wildcard spoke domain may differ from the hub's own hostname. | | `HIVE_HUB_LEGACY_COOKIE_DOMAIN` | No | none | Optional transition-only hub SSO cookie domain to expire alongside the active cookie domain while accepting any carried `hive_hub_user` cookie value during a host migration. Writes still target the domain derived from `HIVE_HUB_PUBLIC_URL`; unset disables the extra legacy-domain cleanup. | | `HIVE_HUB_SECRET` | Required for spokes registered to a protected hub; optional for a standalone hub with `/data/saas/hub-secret.key` | `/data/saas/hub-secret.key` on the hub when present; no fallback for spoke heartbeat auth | Bearer secret for spoke heartbeats and hub/spoke SaaS APIs. | | `HIVE_HUB_AGENT_RESTART_PROBLEM_THRESHOLD` | No | `5` | Hosted hub threshold for surfacing agent restart storms on `/fleet`. When an agent reports at least this many restarts in the last 24 hours (or since the hub-side reset marker), the hive gets a restart problem chip and drift signal. | @@ -47,7 +47,7 @@ This reference is compiled by hand from the Go source under `src/`, the deployme | `HIVE_SHA` | No | build SHA | Passed to launched agents and used in hub upgrade/status paths. | | `HIVE_ADVISORY_ISSUE` | No | none | Passed to launched agents so advisory findings can target a configured issue. | | `HIVE_TTYD_PORT` | No | `7681` | Web terminal port used by the entrypoint and terminal proxy. | -| `HIVE_TTYD_CREDENTIAL` | No | `hive:` when a token is set, else none | ttyd basic-auth credential (`user:pass`) the entrypoint starts the web terminal with. Also read by `hivectl tui`'s remote attach ([#5644](https://github.com/hivecommons/hive/issues/5644)), which must present the same credential through the terminal proxy and derives the same default from `HIVE_DASHBOARD_TOKEN` — set it on the client only if the deployment overrode it on the server. | +| `HIVE_TTYD_CREDENTIAL` | No | `hive:` when a token is set, else none | ttyd basic-auth credential (`user:pass`) the entrypoint starts the web terminal with. The dashboard's `/terminal` proxies (Go and Node) present it to ttyd on every forwarded request, so a browser the dashboard has already authenticated is never shown ttyd's own user/password dialog. Also read by `hivectl tui`'s remote attach ([#5644](https://github.com/hivecommons/hive/issues/5644)), which must present the same credential through the terminal proxy and derives the same default from `HIVE_DASHBOARD_TOKEN` — set it on the client only if the deployment overrode it on the server. | | `HIVE_METRICS_ENABLED` | No | disabled | Registers Prometheus `/metrics` when set to `1`, `true`, `yes`, or `on`. Requires `HIVE_METRICS_TOKEN` — enabled-but-tokenless returns 403 ([#3804](https://github.com/hivecommons/hive/pull/3804)). | | `HIVE_METRICS_TOKEN` | Yes when metrics enabled | none | Bearer token read by `pkg/dashboard/metrics_prometheus.go` for `/metrics` (`Authorization: Bearer ...`; Prometheus `bearer_token`). `/metrics` bypasses dashboard session auth, so this token is its only guard; enabled-but-tokenless fails closed and the cost/agent series are never served without it. | | `HIVE_METRICS_FILE` | No | `/var/run/hive-metrics/contribute.json` | Contributor metrics JSON file override. | @@ -156,6 +156,7 @@ new value at the same time. | `HIVE_STATIC_DIR` | No | `/opt/hive/proxy/public` | Static asset directory for the Node proxy. | | `HIVE_PROXY_EGRESS_MARK` | No | `0x1112` | Packet mark exempted from the MITM egress redirect. | | `HIVE_PROXY_ADVISORY_OK` | No | `false` | Allows the spoke to start when the forced-proxy egress redirect cannot be installed (no `CAP_NET_ADMIN`/iptables). Enforcement becomes advisory-only — agents can bypass the proxy. Also gates whether the Go proxy trusts a self-asserted `Proxy-Authorization` header as agent identity when its UID map is unavailable (N7, #3841) — off by default, an unidentified caller is treated as `ADVISORY` (writes blocked) rather than whatever name it claims. See [security-model.md](/docs/hive/security-model#5-network-and-credentials). | +| `HIVE_PROXY_ALLOW_PRIVATE_FORWARD` | No | `false` | Lifts the plain-HTTP forward destination guard ([#14082](https://github.com/hivecommons/hive/issues/14082)). Off by default: a non-CONNECT request through the proxy is dialled only when the resolved address is globally routable, so an agent cannot use the proxy to reach loopback services beside it, the link-local cloud metadata endpoint (`169.254.169.254`), RFC 1918/CGNAT cluster addresses or multicast, and the refusal is a `403` naming the class. Only the exact string `true` turns it on, for deployments that genuinely egress to a private network over plain HTTP (a LAN mirror on a native install). Loopback refusals stay at `DEBUG` and are not counted as violations, because agents are already handed `NO_PROXY` for loopback (#14076). | | `HIVE_PROXY_INJECT_GH_AUTH` | No | unset (off; opt-in only on every hive, hosted or not) | Proxy-side GitHub credential injection ([#1861](https://github.com/hivecommons/hive/issues/1861)). Opt-in only: on exclusively when set to `true`; unset, `false` and any other value are off, and the hub renders no value onto newly provisioned spokes (a brief default-on for hosted App spokes from #9597/#9625 was reverted, [#9586](https://github.com/hivecommons/hive/issues/9586)). `true`: the hive keeps each agent's tier-scoped App token in memory, the MITM proxy strips any agent-supplied `Authorization` on GitHub hosts and attaches the UID-identified agent's real token, and the agent's readable token cache (and the `GH_TOKEN`/`GITHUB_TOKEN` derived from it) holds only the inert placeholder `hive-proxy-injected-`. `false` is the explicit opt-out (same as unset, but recorded on the pod spec). The resolved state is logged once at boot as `proxy GitHub auth injection: on|off ()` - unset reads `off (opt-in: set HIVE_PROXY_INJECT_GH_AUTH=true)` - and shown in the dashboard Security tab. Under injection the Copilot CLI's `/copilot_internal/` auth exchange keeps the agent's own credential. **`true` together with `HIVE_PROXY_ADVISORY_OK=true` makes the spoke refuse to start** (exit code 19): advisory mode lets a caller self-assert another agent's identity and receive its injected token. Any other value (`1`, `TRUE`, `yes`, ...) is treated as off, as before, but is logged at ERROR on boot and shown in the dashboard Security tab's coherence warnings, because it leaves the real token in the agent's cache. Requires GitHub App auth; on a PAT-only hive there is nothing to inject. See [security-model.md](/docs/hive/security-model#5-network-and-credentials). | | `HIVE_DEPLOYMENT_RUNTIME` | No | none (config `deployment.runtime` fallback; hub-registered Kubernetes hives are auto-detected) | Declares the deployment runtime (`kubernetes`, `podman-quadlet`, or `docker-compose`) so the dashboard can offer standalone upgrades. Unset/unrecognized means `unknown` and the upgrade button stays hidden. See [dashboard-standalone-upgrades.md](https://github.com/hivecommons/hive/blob/v5/src/docs/dashboard-standalone-upgrades.md). | | `HIVE_DEPLOYMENT_PODMAN_MODE` | Only when runtime is `podman-quadlet` | none (config `deployment.podman_mode` fallback) | `rootless` (`systemctl --user`) or `rootful` (system manager). Without an explicit mode the Podman/Quadlet upgrade path stays disabled. | @@ -267,7 +268,8 @@ Always set: | `HIVE_ACMM_LEVEL` | The project's ACMM level as a decimal integer. | | `HIVE_AGENT_MODE` | The agent's effective operating mode (from `tools.mode` when set, otherwise the resolved mode). | | `HIVE_EXPLAIN_MODE` | The **resolved** explain mode (`off`, `brief`, `full`) after hive-wide default inheritance - always exported, `off` included, so scripts can branch on it without re-deriving precedence. | -| `HTTP_PROXY`, `HTTPS_PROXY` | The local hive proxy (`http://127.0.0.1:`) all agent HTTP(S) traffic must traverse. | +| `HTTP_PROXY`, `HTTPS_PROXY` | The local hive proxy (`http://127.0.0.1:`) all non-loopback agent HTTP(S) traffic must traverse. | +| `NO_PROXY` | `127.0.0.1,localhost,::1` — loopback never traverses the hive proxy, on every backend. | | `HIVE_PROXY_AGENT` | The agent's own name, so tooling (e.g. `hive-panes`) can identify and skip the calling agent. | | `GIT_TERMINAL_PROMPT` | `0` - git never prompts for credentials. | | `NODE_EXTRA_CA_CERTS`, `GIT_SSL_CAINFO` | Path of the proxy CA certificate. `SSL_CERT_FILE` is deliberately **not** set (it breaks Copilot API TLS). | @@ -279,7 +281,7 @@ Set conditionally: | `HIVE_ID`, `HIVE_SHA`, `HIVE_ADVISORY_ISSUE` | When set in the hive's own environment | Passed through unchanged. | | `HIVE_REPO`, `HIVE_REPOS` | When the project has an org and at least one repo | `org/primary-repo`, and the full comma-separated `org/repo` list. Policy templates target `gh issue create --repo "$HIVE_REPO"`. | | `GH_HOST` | GHE spokes with a configured forge host | Forge hostname for the `gh` CLI; the gh wrapper pairs it with `GH_ENTERPRISE_TOKEN`. | -| `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `NO_PROXY`, `CLAUDE_CODE_MAX_OUTPUT_TOKENS`, `DISABLE_TELEMETRY`, `DISABLE_ERROR_REPORTING`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `CLAUDE_CODE_AUTO_MODE_SERVER=0` | Inference-routed backends only | Local inference-translate endpoint, a **synthetic** per-agent key (`sk-hive-` - not a real credential), loopback proxy bypass, an output-token cap every fronted model accepts, telemetry switched off at the source, and auto mode's server-side classifier check turned off (the OpenAI-format gateway behind the translator cannot return the `safeguard_results` verdict, and without the variable Claude Code stops on an interactive "this session isn't eligible" notice no unattended pane can answer). | +| `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `CLAUDE_CODE_MAX_OUTPUT_TOKENS`, `DISABLE_TELEMETRY`, `DISABLE_ERROR_REPORTING`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `CLAUDE_CODE_AUTO_MODE_SERVER=0` | Inference-routed backends only | Local inference-translate endpoint, a **synthetic** per-agent key (`sk-hive-` - not a real credential), an output-token cap every fronted model accepts, telemetry switched off at the source, and auto mode's server-side classifier check turned off (the OpenAI-format gateway behind the translator cannot return the `safeguard_results` verdict, and without the variable Claude Code stops on an interactive "this session isn't eligible" notice no unattended pane can answer). | | `COPILOT_GITHUB_TOKEN` | When the hive holds a Copilot auth token | Copilot OAuth token (authenticates the AI model, not GitHub writes). **Secret.** | | `GITHUB_TOKEN` | Only when App-authored PRs are enabled (`AppAuthoredPRs`), an App is configured, and the agent's mode can push | The per-agent tier-scoped App installation token, so the built-in GitHub MCP server writes as the App bot. Advisory agents have `GITHUB_TOKEN` deliberately stripped. **Secret.** | | `LINEAR_ACCESS_TOKEN` / `LINEAR_API_KEY` | ISSUES_ONLY+ agents on Linear-connected hives | See the [Linear agent integration](#linear-agent-integration) note above. | diff --git a/docs/content/hive/getting-started.md b/docs/content/hive/getting-started.md index 308a0757..338c4cfe 100644 --- a/docs/content/hive/getting-started.md +++ b/docs/content/hive/getting-started.md @@ -12,7 +12,7 @@ The biggest mistake new users make: seeing agent output and either (a) panicking ## Trust > Level. Always. -> You do not need to reach L6. Ever. L6 is full automation — agents merging code without human review. Some teams run at L4 or L5 indefinitely and that is completely fine. The number doesn't matter. What matters is whether you trust what the agents are producing. A team that runs at L3 with high confidence is in a better place than a team that jumped to L6 and is now drowning in agent change requests they don't understand. +> You do not need to reach L6. Ever. L6 is Autonomous — agents merge code on green CI without per-PR human review. Some teams run at L4 or L5 indefinitely and that is completely fine. The number doesn't matter. What matters is whether you trust what the agents are producing. A team that runs at L3 with high confidence is in a better place than a team that jumped to L6 and is now drowning in agent change requests they don't understand. > > **The goal is trust, not level.** @@ -297,7 +297,7 @@ See [sandbox-isolation.md](https://github.com/hivecommons/hive/blob/v5/src/docs/ **When to move up:** Only when you **genuinely trust the agents' judgment** — meaning you've reviewed enough of their PRs to know they're consistently doing the right thing, and your test suite is strong enough that passing checks genuinely means "safe to ship." There's no calendar for this one. -## L6 — Full Automation +## L6 — Autonomous **The level:** Full trust. Auto-merge is off below L6; when you switch to L6, Hive enables auto-merge for every active repo, then agents open PRs and merge them automatically when CI goes green. Owners can toggle individual repos off or back on afterward. No level hold, except outreach PRs into repositories the hive does not manage, which remain held for human review; outreach PRs into the hive's own repos auto-merge like any other. @@ -316,7 +316,7 @@ Telemetry and operations don't auto-enable just because you reached L6 — they **Be patient:** Trust the loop. If a bad PR merges, that's a signal to add a test, not to panic and drop levels. -> 💡 **Tip: L6 doesn't mean you're done reviewing.** Even at full automation, read the merged PRs weekly. Not to intervene — to calibrate. If you see a pattern you don't like (agents always touching the same file, tests that are too shallow), update the policy template and the next run corrects course. +> 💡 **Tip: L6 doesn't mean you're done reviewing.** Even at L6, read the merged PRs weekly. Not to intervene — to calibrate. If you see a pattern you don't like (agents always touching the same file, tests that are too shallow), update the policy template and the next run corrects course. **When to move up:** There is no up. You made it. 🐝 diff --git a/docs/content/hive/hivectl.md b/docs/content/hive/hivectl.md index ea759af6..e64986a7 100644 --- a/docs/content/hive/hivectl.md +++ b/docs/content/hive/hivectl.md @@ -290,6 +290,29 @@ Columns: `repo`, `pr`, `head_sha`, `bundle_id`, `author_login`, `author_kind`, ` Reporting only: nothing here reads into or alters merge eligibility. See [Review evidence bundle](https://github.com/hivecommons/hive/blob/v5/src/docs/review-evidence.md#exporting). +### evidence — benchmark + +```bash +hivectl evidence benchmark --month 2026-10 --out-dir src/docs/benchmarks --readme README.md +hivectl evidence benchmark --root ./evidence --month 2026-10 --spend cost.json --hub hub.json --source "hive-oke snapshot" +``` + +`evidence benchmark` folds the schema v1 bundles under `--root` per PR and compares the pull requests Hive's review +swarm touched (**mediated**: at least one verdict or posted review on any head) with those merged without it +(**unmediated**): revert rate and change-failure rate over merges observed ≥ 14 days, median time-to-merge, CI failure +at merged head, the share of mediated merges that carried a `changes_requested` verdict before merging, and — when +`--spend` names a saved `GET /api/cost` response — cost per merged PR. Every rate carries its n and a 95 % Wilson +interval; n < 10 is `not reported`. `--month YYYY-MM` bounds the merge time (default all), `--now` fixes the +observation time for maturation, `--hub` adds a saved `GET /api/hub/evidence` response as fleet context, `--source` +names the data in the methodology section. It writes `outcomes-YYYY-MM.{md,json}`, +`assets/outcomes-YYYY-MM-{bars,trend}.svg` and the plain-language README cards +`assets/outcomes-YYYY-MM-card--{dark,light}.svg` under `--out-dir`, and `--readme FILE` rewrites that file's +`…` block from the same report. The README block carries only the cards +and flat sentences, no intervals: `--readme-min-base N` (default 200) is the smallest base a production-count card +may rest on, separate from the n ≥ 10 report threshold; `--stories FILE` supplies curated caught → fixed → merged +examples and `--red-team FILE` the prompt-injection evaluation counts (see [benchmarks/](https://github.com/hivecommons/hive/blob/v5/src/docs/benchmarks/README.md) for +both formats). Local filesystem only; no hive session, no telemetry. `src/scripts/outcome-benchmark.sh` wraps it; see [benchmarks/](https://github.com/hivecommons/hive/blob/v5/src/docs/benchmarks/README.md). + ### evidence — backfill bundles from GitHub history ```bash @@ -807,6 +830,15 @@ the heartbeat project-config callback; otherwise the hub provisions a hosted lite spoke. Lite mode is advisory-only and capped at ACMM L2. It writes no repository PAT or long-lived secret. +### contribute — registration token recovery and operator controls + +`hivectl contribute login [profile]` rotates and saves a registration token using +GitHub identity proof; omitting the profile uses the active saved hive. Operator +commands `hivectl contribute tokens list`, `tokens revoke `, and +`tokens revoke --all --yes` inspect ages and invalidate credentials without changing +trust tiers. See [token expiry and incident response](https://github.com/hivecommons/hive/blob/v5/src/docs/contributor-token-policy.md) +for policy settings, API equivalents, migration and relay restart instructions. + ### hives — named profiles for the hives you contribute to ```bash @@ -1059,11 +1091,16 @@ Notes: same Hub → Hive → Repository tree as `hivectl hives web`, with the same staged edits, diff and Saved / Relay reload / Acknowledged receipt. `space` selects, `x` excludes or clears an exclusion, `f` follows, `e` enables, `R` reviews and - restricts a legacy profile, `/` searches (keeping ancestry), `A` reviews and - applies, `D` discards and `ctrl+r` refreshes without losing staged edits. Hub - and Hive actions show affected counts before staging. Nothing is written until - Apply; a review whose profiles changed elsewhere must be discarded and reviewed - again, and leaving or quitting with unapplied edits asks first. + restricts a legacy profile, `/` searches (keeping ancestry), `A` opens the bounded + change review, `D` discards and `ctrl+r` refreshes without losing staged edits. Hub + and Hive actions show affected counts before staging. In review, `↑`/`↓` or + `j`/`k` scroll wrapped rows; `PageUp`/`PageDown` page and `Home`/`End` jump. + Full target identities wrap without truncation. `Enter` explicitly applies; + `Esc` returns with edits kept. The review pins the title, scope and key hints, + scrolls only the diff, adapts to resize and pauses Apply + below 40 columns or 12 rows until its confirmation context fits again. + Nothing is written until Apply; a review whose profiles changed elsewhere must + be discarded and reviewed again, and leaving or quitting with unapplied edits asks first. - **Disable instead of removing a hive to pause it.** `hivectl hives disable ` (or `just contribute-hives disable `) preserves its URL, registration token, contributor ID and rank. `enable ` restores eligibility without registration diff --git a/docs/content/hive/integrations/work-source-providers.md b/docs/content/hive/integrations/work-source-providers.md index d675b36e..9afccee0 100644 --- a/docs/content/hive/integrations/work-source-providers.md +++ b/docs/content/hive/integrations/work-source-providers.md @@ -23,7 +23,7 @@ Implement this contract: Writes are not part of the `WorkSource` interface. Existing write paths are source-specific helpers: Linear exposes `CreateIssue` for ACMM gap filing (`src/pkg/worksource/linear.go:610`); Jira has internal `addComment` and `transitionIssue` helpers (`src/pkg/worksource/jira.go:403`, `src/pkg/worksource/jira.go:414`). The review backlog sinks (`src/pkg/worksource/review_backlog_sink.go`) add `CreateIssueInState`/`CommentOnIssue` for Linear (`src/pkg/worksource/linear.go:686`, `src/pkg/worksource/linear.go:751`), `createIssue`/`transitionToStatus` for Jira (`src/pkg/worksource/jira.go:434`, `src/pkg/worksource/jira.go:471`), and `AddIssueToColumn` for GitHub Projects. Claiming, comments, state transitions, and PR/MR equivalents are therefore not pluggable provider methods today. -Hive's change-request execution still assumes a Git forge target repository. The work source tells Hive what work exists; agents still clone `Issue.Repo` and use Hive's existing pull-request relays for code changes. +Hive's change-request execution still assumes a Git forge target repository. The work source tells Hive what work exists; agents still clone `Issue.Repo` and use Hive's existing pull-request relays for code changes. The forge is a separate setting (`project.forge`); see [Forges: GitLab, Gitea and Forgejo](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md). ## Step-by-step: contribute a primary adapter diff --git a/docs/content/hive/labels-and-control-signals.md b/docs/content/hive/labels-and-control-signals.md index 0dc3d603..a0716e54 100644 --- a/docs/content/hive/labels-and-control-signals.md +++ b/docs/content/hive/labels-and-control-signals.md @@ -65,8 +65,8 @@ Some hive-authored labels look like provenance but are read as signals, so they | GitHub "blocked by" dependency | Issues | GitHub enumeration, kick-list assembly | An open issue whose GitHub dependency list names an open blocker is enumerated with `DependsOn` edges and kept out of the ACTIONABLE ISSUES an agent picks from; the kick names it once in a `Blocked by open dependencies` footer with its blockers. A blocker that is closed resolves the edge; a mutual pair (A↔B) is dropped with a warning so neither hides the other. The blocker list failing to load fails open (issue offered). | Soft hold | Human (GitHub UI) or agent via `hive-open-issue --blocked-by` when splitting ordered children | Enumeration | Close the blocker, or remove the dependency on GitHub | None (`src/pkg/github/issue_dependencies.go`, `src/pkg/scheduler/kickmessage.go` `partitionBlockedIssues`/`formatBlockedIssuesNote`) | | `hive/` (**retired**, #12539) | Issues, PRs | None | Formerly marked which hive filed or touched an item. Replaced by the `` marker `bin/gh-wrapper.sh` appends after the identity footer (`**Instance:** \`\``) on every body it injects; `github.OriginHive` reads the marker and falls back to a leftover label. Fleet reports name the hive in ``, the seen-marker comment and the `Affected hives` block; claim markers carry `hive=`. Never a hold (#9371). | None | Nobody (was: `gh-wrapper.sh`, self-report joiner, hub relay) | — | `Client.RetireHiveIDLabels` runs at every boot until clean: strips the label from open items, deletes the definition once nothing open carries it, writes `/data/hive-id-label-retire-.json` and a `.done` marker | Hive id (`src/pkg/github/origin_marker.go`, `src/pkg/github/hive_id_label_retire.go`) | | `hold` from ACMM level gate | PRs | PR-request watcher, self-authored auto-merge release | Agent PRs at L3-L5 get literal `hold`; `outreach` PRs whose target repo is not one the hive manages (cross-repo outreach) are held at every level, while same-repo outreach PRs follow the level. The watcher, not the policy prompt, applies it from the PR's target repo (`config.AgentOwnsRepo`). | Hard merge gate | Hive App | PR creation, later self-authored auto-merge release | Auto-release only when current policy no longer requires the level hold, latest hold event was by the App, and the release path runs; otherwise a human removes it | Hive-wide ACMM and agent (`src/pkg/github/pr_request_watcher.go:452-524`, `src/pkg/github/pr_level_hold.go:16-124`) | -| `hold` from #5117 self-authorization | PRs | PR-request watcher and self-authorization release | A PR whose only rationale is unacknowledged hive-filed issues gets literal `hold`. Acknowledging the issue later does not remove an existing PR hold by itself. At ACMM L6 Fully Autonomous, the unset default is off; lower levels default on. | Hard merge gate | Hive App | PR creation; evaluated only when no level hold applies | Acknowledge the issue and remove the PR `hold`; disabled policy release can remove Hive's own hold. At L6, that release path also clears existing Hive-applied #5117 holds when no explicit config keeps the policy on. | `github.self_authorization_hold`, per-repo `project.repo_policies[].self_authorization_hold`, `HIVE_SELF_AUTHORIZATION_HOLD` (`src/pkg/github/pr_self_authorization.go:14-226`, `src/pkg/github/pr_request_watcher.go:452-524`) | -| `triage/accepted` (or `project.issue_filter.reporter_trust.untrusted_require_labels`); `needs-triage` while waiting | Human-filed issues from untrusted reporters | GitHub enumeration | With `reporter_trust.enabled: true`, an issue whose reporter's GitHub `author_association` is outside the trusted set (default `OWNER`, `MEMBER`, `COLLABORATOR`) and whose login is not in `trusted_logins` is not actionable until it carries one of these labels. On the first excluded scan, core `github.Client.fetchIssues` posts one marked explanation comment and applies `needs-triage` (or `awaiting_label`); later scans do not repeat the comment. This is a poller-side write, not an agent prompt, and is capped at 10 new notices per poll. When the triage label appears, the same poll loop removes `needs-triage` only if the marker records that Hive added it, then the issue enters the backlog. Trusted reporters' issues pass untouched. Runs after holds/exempts and before `require_labels`, so the ordinary allow-list still applies afterwards. Hive- and bot-filed issues are not judged here (#5117 owns them). Counted as "needs triage" on the repo card, not as a generic filter refusal. | Hard admission gate (off by default) | Human; Hive App for the visibility comment/label | Enumeration | Add the label, trust the login or association under **Settings → Labels → Reporter trust**, or disable the gate | `project.issue_filter.reporter_trust.{enabled,trusted_associations,trusted_logins,untrusted_require_labels,awaiting_label,comment}` (`src/pkg/config/reporter_trust.go`, `src/pkg/github/client.go` `fetchIssues`) | +| `hold` from #5117 self-authorization | PRs | PR-request watcher and self-authorization release | A PR whose only rationale is unacknowledged hive-filed issues gets literal `hold`. Acknowledging the issue later does not remove an existing PR hold by itself. At ACMM L6 (Autonomous), the unset default is off; lower levels default on. | Hard merge gate | Hive App | PR creation; evaluated only when no level hold applies | Acknowledge the issue and remove the PR `hold`; disabled policy release can remove Hive's own hold. At L6, that release path also clears existing Hive-applied #5117 holds when no explicit config keeps the policy on. | `github.self_authorization_hold`, per-repo `project.repo_policies[].self_authorization_hold`, `HIVE_SELF_AUTHORIZATION_HOLD` (`src/pkg/github/pr_self_authorization.go:14-226`, `src/pkg/github/pr_request_watcher.go:452-524`) | +| `triage/accepted` (or `project.issue_filter.reporter_trust.untrusted_require_labels`); `needs-triage` while waiting | Human-filed issues from untrusted reporters | GitHub enumeration | With `reporter_trust.enabled: true`, an issue whose reporter's GitHub `author_association` is outside the trusted set (default `OWNER`, `MEMBER`, `COLLABORATOR`) and whose login is not in `trusted_logins` is not actionable until it carries one of these labels. On the first excluded scan, core `github.Client.fetchIssues` posts one marked explanation comment and applies `needs-triage` (or `awaiting_label`); later scans do not repeat the comment. This is a poller-side write, not an agent prompt, and is capped at 10 new notices per poll. When the triage label appears, the same poll loop removes `needs-triage` only if the marker records that Hive added it, then the issue enters the backlog. Independently of the marker, the stage sweep removes `needs-triage` once the issue reaches 020 or later (claiming settles triage; see `stage/015-triage-needed` below) — this is what retires the `needs-triage` that `fleetreport` stamps on hive-filed self-reports at filing. Trusted reporters' issues pass untouched. Runs after holds/exempts and before `require_labels`, so the ordinary allow-list still applies afterwards. Hive- and bot-filed issues are not judged here (#5117 owns them). Counted as "needs triage" on the repo card, not as a generic filter refusal. | Hard admission gate (off by default) | Human; Hive App for the visibility comment/label | Enumeration | Add the label, trust the login or association under **Settings → Labels → Reporter trust**, or disable the gate | `project.issue_filter.reporter_trust.{enabled,trusted_associations,trusted_logins,untrusted_require_labels,awaiting_label,comment}` (`src/pkg/config/reporter_trust.go`, `src/pkg/github/client.go` `fetchIssues`) | | `hold` from #9665 reporter trust | PRs | PR-request watcher, level-hold release | A PR whose rationale (closing or referencing links, or the request's declared issues) traces to an issue filed by an untrusted reporter gets literal `hold` at **every** ACMM level, including L6, plus `needs-human` (so it surfaces as waiting on a person, #10773) and a marked notice naming the untrusted reporter and stating the PR itself was authored by the hive. Any one untrusted citation holds. Evaluated even at hold-gated levels, because the notice is what stops promotion to L6 from releasing the label. | Hard merge gate | Hive App | PR creation; re-checked by the level-hold release path | A human removes `hold`. Hive never auto-releases it: the release sweep sees the notice (or re-evaluates and re-posts it) and leaves the label. Holdguard re-holds on a new head SHA. | `github.reporter_trust_hold` (nil follows `reporter_trust.enabled`), per-repo `project.repo_policies[].reporter_trust_hold`, `HIVE_REPORTER_TRUST_HOLD` (`src/pkg/github/pr_reporter_trust.go`, `src/pkg/github/pr_level_hold.go`) | | `project.repo_policies[].auto_merge: false` | PRs | `hive-merge` relay, App self-authored auto-merge sweep, proxy | Below L6 this resolves off for every repo; at L6, PRs can still be opened, reviewed and held when the per-repo switch is off, but Hive refuses every merge path for that repo. Existing `hold` / `hive-pause/` labels stay put because the disabled repo is skipped before hold release or merge. | Hard merge gate | Operator/dashboard | Merge relay request, self-merge sweep tick, proxy direct merge attempt | Dashboard repo-card switch or config edit sets `auto_merge: true`/removes the override at L6; enabling is rejected below L6 | `project.repo_policies[].auto_merge` (`src/pkg/config/repo_policy.go`, `src/pkg/agent/manager_modes.go`, `src/pkg/github/automerge/automerge_sweep.go`, `src/pkg/proxy/rules.go`) | | `hold` from holdguard | PRs | Holdguard ledger | If the head SHA changes while held, lifting the hold causes Hive to comment and re-apply literal `hold`; the next human removal is the fresh approval. Exception (#13603): when a human (not a Hive account, not a bot) removed the gate label after the current head arrived on the branch, they reviewed the current head, so the lift clears without a comment or re-hold. The head is accepted only on evidence a contributor cannot forge: the server-stamped `head_ref_force_pushed` timeline event that produced it (dated before the lift), or the hive itself having observed that exact head on the PR while the gate was still on (`observed_held_head_sha` in the holdguard ledger). Commit committer dates and SHA-keyed `check_suite` / `status` times are never used; a push after the lift, or a head the hive never saw while held, still re-holds. | Hard merge gate | Hive | Governor holdguard pass | Human removes the re-applied hold | Built-in `holdguard.ReHoldLabel` (`src/pkg/holdguard/holdguard.go:1-43`) | @@ -79,6 +79,7 @@ Some hive-authored labels look like provenance but are read as signals, so they | `needs-spec` from the clarity gate | Issues | Scheduler kick assembly (`runs.triage.clarity_gate`) | With the gate on (default off), a human-filed, unclaimed issue whose clarity score (goal, acceptance criteria, scope, detail, open choices) is under `runs.triage.clarity_gate.threshold` gets one `` comment with concise questions for its author and the `needs-spec` hard-suppress label instead of a claim; it derives `stage/045-needs-decision` and leaves the agent queue ([#13577](https://github.com/hivecommons/hive/issues/13577)). | Hard suppress | Hive | Before kicks are built | The issue author, or a non-hive commenter with write/maintain/admin, replies after the marker and the resume sweep removes the hive's own `needs-spec` and records the un-park so the issue re-derives `stage/010-actionable` ([#13588](https://github.com/hivecommons/hive/issues/13588)); a `needs-spec` a person added is never lifted by the sweep. Or remove `needs-spec` by hand. The marker keeps the gate from asking again | `runs.triage.clarity_gate` (`src/pkg/scheduler/clarity_gate.go`, `src/pkg/classify/clarity.go`, `src/pkg/github/clarity_gate_resume.go`) | | `stage/017-possible-existing` from the prior-art gate | Issues | Scheduler kick assembly (`runs.triage.prior_art`) | With the gate on (default on), an unclaimed feature / RFC / epic issue whose key terms match an existing knowledge bead, setting, merged PR, closed issue, doc or code identifier at `runs.triage.prior_art.threshold` (default 70) gets one `` comment quoting the findings and the `stage/017-possible-existing` label instead of a claim; hive-filed issues get a `` note and are never parked ([#13575](https://github.com/hivecommons/hive/issues/13575)). | Hard suppress | Hive | Before kicks are built | The author or a write+ commenter replies `/hive decision proceed — ` (label lifted, a *Scope vs existing* section appended to the body, re-derives 010) or `/hive decision exists` (closed `not_planned` with `already-exists`); a plain reply lifts the label too. A clanker-filed park with no reply is closed `already-exists` after `runs.triage.prior_art.bot_close_ttl` (default 72h); reopen to continue. A 017 a person added is never lifted by the sweep | `runs.triage.prior_art` (`src/pkg/priorart`, `src/pkg/scheduler/prior_art_gate.go`, `src/pkg/github/prior_art_gate_resume.go`) | | `hive/handoff` | Issues | Task-list sweep | Soft hand-off marker. When a merged PR's `Refs #N` remainder section says in prose that the rest needs a person or another lane ("needs a human", "fix lane", "quality lane", "production change", …) without the explicit `needs-human:` marker, the sweep adds this label and notes "handed off to the next lane" in its remainder comment instead of parking the issue. It is not a hold, suppress, or enumeration filter: the issue stays actionable, so the next lane can still claim it ([#12002](https://github.com/hivecommons/hive/issues/12002)). Not added when the same sweep is applying `needs-human`. | Informational | Hive (task-list sweep) | Task-list sweep | Human or the claiming lane removes it; Hive never removes it | Fixed label (`src/pkg/github/task_list_sweep.go:85`, `src/pkg/github/task_list_sweep.go:576-596`, `src/pkg/github/task_list_sweep.go:635-665`) | +| `hive:unauthorized` | PRs (issues when a step on one is blocked) | Contributor lane (`markUnauthorized`), dashboard activity monitor | The hive's GitHub App was refused (`403 Resource not accessible by integration`) on an API call the lane needs to advance, so only a person can take the next step. First wired call site: approving held `action_required` workflow runs for a registry-trusted author needs `actions: write`, which an operator may decline to grant ([#14589](https://github.com/hivecommons/hive/issues/14589)). The lane reports `unauthorized:` (`unauthorized:approve-workflow-runs`), the stage engine parks the PR at 085, one `` comment names the missing permission, the exact human step (**Approve and run** on the checks banner or Actions tab, with the run ids) and the alternative of granting the permission, and the hive logs `lane unauthorized` once per head at INFO instead of a WARN per tick. The activity monitor draws a muted `unauthorized` pill (counted under Needs attention) whose tooltip repeats the permission and the step, and the 085 cell reads **Awaiting human: approve workflow runs**. | Human-gated hold | Hive | Every lane tick that hits the refused call | Removed by the hive in the tick the step is no longer needed (the required checks for the head report, the lane advances); a new head re-posts the comment once. No human label edit required; removing it by hand is harmless — it is re-applied if the step is still refused | Not configurable; outside `stage/`, so the reconciler never strips it (`src/pkg/github/automerge/automerge_unauthorized.go`) | | `needs-human` | PRs | Escalation ledger, reviewer lane, self-authored auto-merge sweep, queued auto-merge sweep, supersession sweep | Applied when red-CI fix budget is exhausted or a workflow needs a person; stops automated fix dispatch, sends the PR to the human/reviewer lane, and blocks auto-merge sweeps while present. It is not a PR enumeration filter. The stage engine reads it as a park (`label:needs-human` → `stage/085-needs-decision`), so a writer that adds it must also submit a `Parked` fact (`FactSink.Parked(ref, "needs-human")`) in the same pass; the label alone leaves the previous lane stage standing until the next auto-merge tick. The supersession sweep does this for a contributor PR that touches files the merged closer PR did not, and for a hive PR below ACMM L6; it never adds the label to a hive PR at L6, which it resolves itself (see [Supersession](#supersession-closed-issue-another-merged-pr)). | Hard fix-dispatch and merge gate | Hive | Escalation pass/kick building/auto-merge sweeps/supersession sweep | Human removes after root cause or reviewer un-escalates; budget resets after grace; auto-merge reconsiders on the next tick because no persisted hold state is kept; the supersession sweep removes only the `needs-human` it applied itself (label timeline actor is a hive account) when it takes a hive PR's resolution over | Fixed label (`src/pkg/escalation/escalation.go:400-470`, `src/pkg/escalation/escalation.go:1185-1205`) | | `hive/advisory` | Issues | Standing meta classifier | Hive advisory report is never actionable and never appears on the Hold list. | Hard exclusion | Hive | Before holds/exempts | Remove label, but exact advisory title still excludes | Fixed label/name (`src/pkg/github/standing_issues.go:1-57`, `src/pkg/github/client.go:983-1001`) | | `triage/advisory` | Issues | Standing meta classifier | Advisory-only dedupe checklist (`Decision required: no`, #11864) is never actionable and never appears on the Hold list; action-required checklists are parked with `needs-decision` instead. The close-on-merge backfill closes it once every `#N` it references is closed or merged, or after 7 days, and swaps it for `needs-decision` plus the "What to reply" notice if line two turns to `Decision required: yes` (#11871). | Hard exclusion | Dedupe agent | Before holds/exempts | Remove label | Fixed label/name (`src/pkg/github/standing_issues.go`) | @@ -118,7 +119,7 @@ Some hive-authored labels look like provenance but are read as signals, so they | `1-triage`, `2-discussing`, stage labels | Issues | Dashboard repo-card bands/legend | Stage vocabulary is display-only unless another consumer names the same label. `2-discussing` ships in the default waiting band; `1-triage` is just a visible repo taxonomy label unless configured elsewhere. | Display-only | Human/repo automation | Dashboard render | Remove label or change band config | `dashboard.issue_bands` (`src/docs/dashboard.md:105-140`) | | `claimed` (**retired → `stage/020-claimed`**), `preempted:`, `hive/claimed-by-human` / `hive/claimed-by-` (the per-login `hive/claimed-by-` is **retired**, read-only during migration) | Issues | Claims/dashboard | A claim is a comment; the `claimed` label is a mirror Hive keeps in sync. Go claim gates use comments/assignees/ledger, not these labels. | Display-only | Hive/claim flows | Dashboard render | Cleared by claim flow or human | `governor.claims.*` (`src/docs/dashboard.md:105-140`) | | `hive/in-progress` (**retired → `stage/020-claimed` / `stage/030-pr-in-progress`**; the claim marker comment below remains the detection signal) | Issues | Agent claim relay, ledger mirror, enumeration sweep and dashboard in-progress band | States that a hive agent (normally the scanner) has started work on a PR for this issue ([#11841](https://github.com/hivecommons/hive/issues/11841)). Applied by `hive-open-issue claim` next to `hive/claimed-by-` in one label call, together with a bot comment carrying `` (the ledger's 🔒 comment carries the same `at=` when `governor.claims` is on). While the marker is live (`until` in the future) every hive scanning the repo withholds the issue from kicks, so two spokes never start the same issue; the `at=` instant is the start timestamp for per-repo SLA spans (`kicked → started → pr_opened`). | Kick withhold while marker live; display | Hive | Claim relay, ledger `OnClaimed`, enumeration sweep | Hive removes when the agent's PR opens (`hive-open-pr --issues`), when the ledger releases the agent claim, or at enumeration once PR evidence exists (`hive/covered-by-pr`, `hive/likely-done`, linked PRs) or the marker lapsed (default 2h `agent_ttl_s`); `governor.claims.label: false` disables it | Fixed label (`src/pkg/github/issue_inprogress.go`, `src/cmd/hive/claimswire.go`) | -| `origin/` (non-lane only) / `agent/` (agent lanes) | Issues | Every hive issue-create path, GitHub proxy, origin-label reconciler | Marks which hive component created an issue (see [Origin sources](#origin-sources)). Agent filings use exactly one lane/provenance label, `agent/`; the redundant `origin/` and bare `` aliases are stripped. Non-lane hive filers use `origin/selfreport`, `origin/governor`, `origin/dashboard`, `origin/forge`, or `origin/convergence`. Humans get none. No code path uses `origin/*` as a gate. At startup, open items carrying retired reviewer spellings (`from-review`, `origin/review`, `origin/reviewer`) are swapped to `agent/reviewer`, redundant lane-origin labels are removed, and older `hive/selfreport` issues are backfilled with `origin/selfreport`. | Informational | Hive | Issue creation/display; one-time migration at startup (`/data/origin-label-migration-v4.done`) | Remove label | `review.backlog.labels` (default `[agent/reviewer]`; `from-review`, `origin/review`, and `origin/reviewer` accepted and mapped) (`src/pkg/config/origin_labels.go`, `src/pkg/github/origin_labels.go`, `src/pkg/github/review_backlog.go`) | +| `origin/` (non-lane only) / `agent/` (agent lanes) | Issues | Every hive issue-create path, GitHub proxy, origin-label reconciler | Marks which hive component created an issue (see [Origin sources](#origin-sources)). Agent filings use exactly one lane/provenance label, `agent/`; the redundant `origin/` and bare `` aliases are stripped. Non-lane hive filers use `origin/selfreport`, `origin/governor`, `origin/dashboard`, `origin/forge`, or `origin/convergence`. Humans get none. No code path uses `origin/*` as a gate. At startup, open items carrying retired reviewer spellings (`from-review`, `origin/review`, `origin/reviewer`) are swapped to `agent/reviewer`, redundant lane-origin labels are removed, and older `hive/selfreport` issues are backfilled with `origin/selfreport`. | Informational | Hive | Issue creation/display; one-time migration at startup (`/data/origin-label-migration-v4.done`) | Remove label | `review.backlog.labels` (default `[agent/reviewer]`; `from-review`, `origin/review`, and `origin/reviewer` accepted and mapped) (`src/crux/config/origin_labels.go`, `src/pkg/github/origin_labels.go`, `src/pkg/github/review_backlog.go`) | | `hive: churn-triaged` | Issues | Contributor churn guard | Marks churn triage acknowledgement for contributor flow. | Workflow signal | Human/Hive | Contributor/churn checks | Remove label to re-triage | Fixed phrase in contributor flow docs/config (`src/pkg/dashboard/contribute_admission.go:302-338`) | | `good first issue`, `help wanted`, `bug`, `enhancement` | Issues | Contributor opportunistic ordering | Small ordering boost/visibility in contributor panels; `good first issue`, `help wanted`, and `bug` also appear in default actionable priority labels for human-filed issues. | Soft ordering | Human/Hive | Contributor queue/ranking | Remove label | Contributor/ranking config (`src/pkg/github/client.go:2745-2837`) | | `good first issue` + `help wanted` (hive-curated, `` comment) | Issues | Contributor advertising | On by default (`good_first_issue.enabled: false` opts out), hive applies both labels to a few qualifying open, unclaimed issues and leaves one welcome comment linking its relay. Not a hold — the issue stays in the hive's queue; a hive agent starting it removes the label and marks the comment superseded. Never in the hard-suppress set. See [good-first-issue.md](https://github.com/hivecommons/hive/blob/v5/src/docs/good-first-issue.md). | Soft (advertising) | Hive | Sweep (`src/pkg/gfi`) | Hive agent start, or remove label | `good_first_issue.*` | @@ -159,6 +160,9 @@ fresh label set, so a stage another writer placed since the cycle's snapshot `stage/NNN-*` labels is healed on the spot (`healed` in the reconcile log lines). The claim hooks always re-read: they know the stage a claim implies but not what the lanes think of the author tier or health, which they keep. +Per issue the hooks also run in ledger order, so a release's re-read always +follows its own claim's label writes rather than racing them +(hivecommons/hive#14201). The hub's label writes are also journaled against the item's `updated_at` from that read: the mutation journal otherwise treats "remove `stage/020` from #N" as applied forever, so a label another writer put back could never @@ -212,7 +216,7 @@ referenced it) a lone `stage/050-review-pending` | Label | Applies to | Meaning | Expected dwell (then `stage/stalled`) | |---|---|---|---| | `stage/010-actionable` | issue | triaged; eligible for the hive to pick up. A human-filed issue without `needs-triage` counts as triaged by its trusted reporter | 24h | -| `stage/015-triage-needed` | issue | actionable, but no maintainer has looked at it yet. Derived instead of 010 only when the reporter-trust gate is holding the issue: it carries the awaiting label `needs-triage` (`reporter_trust.awaiting_label` default) or sits in the #9665 gate's `reporter_triage` bucket. Who filed it is never consulted — issues from hive agents and from trusted humans are triaged by construction and stay 010. A **display and tracking state, not a gate**: the scanner and relay claim a 015 issue exactly as a 010 one (the reporter-trust gate withholds `needs-triage` issues separately, and that is unchanged), and 015 lights the activity monitor's *Actionable* column as "Triage needed". `015 → 010` (a maintainer triages) is never a regression; `010 → 015` is a reclassification. The Open column is untouched: an issue with no stage stays "Open" | 24h | +| `stage/015-triage-needed` | issue | actionable, but no maintainer has looked at it yet. Derived instead of 010 only when the reporter-trust gate is holding the issue: it carries the awaiting label `needs-triage` (`reporter_trust.awaiting_label` default) or sits in the #9665 gate's `reporter_triage` bucket. Who filed it is never consulted — issues from hive agents and from trusted humans are triaged by construction and stay 010. A **display and tracking state, not a gate**: the scanner and relay claim a 015 issue exactly as a 010 one (the reporter-trust gate withholds `needs-triage` issues separately, and that is unchanged), and 015 lights the activity monitor's *Actionable* column as "Triage needed". `015 → 010` (a maintainer triages) is never a regression; `010 → 015` is a reclassification. Because 015 is not a gate, a claim settles triage: whenever the sweep (or an out-of-cycle claim fact) writes a stage of 020 or later — 020, 030, 040 or a PR-mirrored stage — it removes `needs-triage` in the same label write, and an open issue already at 020+ still wearing it is reconciled on the next sweep; the label survives at 010, 017 and 045, which the table lets fall back to 015 (hivecommons/hive#14586). The Open column is untouched: an issue with no stage stays "Open" | 24h | | `stage/017-possible-existing` | issue | **Possibly exists · needs author.** The prior-art gate found something that looks like the request already (knowledge bead, setting, merged PR, closed issue, doc or code) and parked the issue with the findings; the author confirms the gap (`/hive decision proceed — `) or closes it (`/hive decision exists`, label `already-exists`). The label itself is the park signal and an issue-level hard suppress, so no agent claims it; clanker-filed parks auto-close after `runs.triage.prior_art.bot_close_ttl`; hive-filed issues never wear it ([#13575](https://github.com/hivecommons/hive/issues/13575)) | none | | `stage/020-claimed` | issue | a live claim by anyone — a human, a relay contributor or a hive agent's `hive-open-issue claim` / start mark — with no PR evidence yet (replaces `claimed`, `hive/in-progress`) | 2h | | `stage/030-pr-in-progress` | issue | the claimant has a PR under way: its `hive-open-pr --issues` request named the issue or its start marker named a branch / PR (`pr_started=true` on the `hive:claim` marker). A claim or start mark without that evidence — the scanner's bare `hive-open-issue claim` included — stays at 020 ([#13069](https://github.com/hivecommons/hive/issues/13069)). If the in-progress evidence includes an open Hive-authored PR, the issue-stage lane applies the same PR-link safeguard described for 040: default-branch PRs that fully address the issue get a `Fixes #N` trailer when missing, while partial or non-default-branch PRs get an issue stage-entry comment linking the PR and explaining GitHub's icon limitation. If the claiming PR has already merged, the issue leaves 030: full-fix claims close it with one `` comment (checked before posting, so a close that fails after the comment is retried without a duplicate), while `Refs` / `Part of` claims move back to 010 with a partial-landed note. | 90m | @@ -223,7 +227,7 @@ referenced it) a lone `stage/050-review-pending` | `stage/070-fixing` | PR | hive-owned repair: App/taken-over PR red, changes requested (by the swarm, or by a trusted human's **changes requested** review / `/hive decision ` on the current head — reason `human-changes-requested`, which also kicks the author agent with the text verbatim, [#13041](https://github.com/hivecommons/hive/issues/13041)) or needs-rebase, **or** a non-App PR whose red head was caused by a hive-pushed commit that must be removed/re-homed; a hive agent is reworking it | 2h | | `stage/071-needs-author` | PR | human-authored PR (`author/human`): CI red / changes requested / conflicting; the hive posted its diagnosis, the author must act. No nudge, no adoption — the person pushes or comments | 24h | | `stage/072-returned-to-contributor` | PR | clanker PR (`author/clanker`, relay or external hive): CI red / changes requested / conflicting; the hive posted its diagnosis, the ball is in the contributor hive's court. (Briefly spelled `stage/072-needs-author` by [#12713](https://github.com/hivecommons/hive/pull/12713); the engine renames that label back in place) | 24h | -| `stage/074-adopted-by-hive` | PR | the author (a clanker) was unavailable — relay offline, never registered, or silent past the grace (see below); the hive took the PR over: in place on the PR's own branch when it may push there (the scanner's red-PR fix kick rebases and fixes it), otherwise via `hive/adopt-` and a superseding App PR that keeps the author's commits (the original closes automatically when the superseder merges). Adoption is an **ownership** state, not a CI state: the PR reads 074 for as long as the hive owns it — through its fixes going pending, green, reviewed or red again (074 outranks 050/060/070/071/072) — and leaves 074 only at the merge gate (080/085/090/100), when the adoption is released, or to 085 `adoption-stalled:` when the head sat unchanged past the dwell with CI idle and a real blocker the hive cannot push past — a red optional check alone never counts, and the escalation releases back to 074 as soon as the reason clears (an in-place adoption with a blocking review finding re-homes to `hive/adopt-` first instead; one with a squash-unsafe hive commit elects a merge commit on a `role/trusted` PR — `merge-method/merge` label, lane continues — and otherwise goes to 085 `adoption-stalled:hive-signoff-attribution` at once, without a supersede, naming why the election was refused). For in-place adoption, a changed head must be linked to the App bot by GitHub commit metadata (the stage store only remembers heads that already passed that check); otherwise the hive treats it as a contributor push and hands the PR back (072 if red, else the normal path). Forgeable git author/committer names and emails never keep 074. | 3h | +| `stage/074-adopted-by-hive` | PR | the author (a clanker) was unavailable — relay offline, never registered, or silent past the grace (see below); the hive took the PR over: in place on the PR's own branch when it may push there (the scanner's red-PR fix kick rebases and fixes it), otherwise via `hive/adopt-` and a superseding App PR that keeps the author's commits (the original closes automatically when the superseder merges). Adoption is an **ownership** state, not a CI state: the PR reads 074 for as long as the hive owns it — through its fixes going pending, green, reviewed or red again (074 outranks 050/060/070/071/072) — and leaves 074 only at the merge gate (080/085/090/100), when the adoption is released, or to 085 `adoption-stalled:` when the head sat unchanged past the dwell with CI idle and a real blocker the hive cannot push past — a red optional check alone never counts, and the escalation releases back to 074 as soon as the reason clears (an in-place adoption with a blocking review finding re-homes to `hive/adopt-` first instead; one with a squash-unsafe hive commit elects a merge commit on a `role/trusted` PR — `merge-method/merge` label, lane continues — and otherwise immediately re-homes to an App-authored superseder (`signoff-rehome`), preserving commits and sign-offs while changing squash attribution; loss of push access re-homes immediately, and idle rebase work re-homes after the dwell). For in-place adoption, a changed head must be linked to the App bot by GitHub commit metadata (the stage store only remembers heads that already passed that check); otherwise the hive treats it as a contributor push and hands the PR back (072 if red, else the normal path). Forgeable git author/committer names and emails never keep 074. A PR whose author holds **admin** or **maintain** on the repository is never adopted or re-homed (one cached permission read per repo and author per 10 minutes; a failed read is not cached and defers: no adoption, and an existing adoption is neither released nor re-homed until the read succeeds), and an adoption of such an author is released on the next pass, closing a still-open hive superseder with a note first. | 3h | | `stage/080-awaiting-merge-admission` | PR | nothing left for the author: CI green (no red check on the head, required or not), verdict approve, no modifiers — and the automerge lane has not admitted it yet (prefilter not evaluated for this head, labels not synced, lane tripped). A stall here means a lane problem, so the hive also posts one `` comment with the lane's last skip reason | 30m | | `stage/085-needs-decision` | PR | **PR-only** since the owner decision of 2026-10-17 (issues park at `stage/045-needs-decision`; before that this stage carried both, [#12683](https://github.com/hivecommons/hive/issues/12683)). A person owes a **decision**, not a merge: a `requires_human` swarm verdict (reason `reviewer-verdict:requires_human`), `sentinel-alert`, `hold` / `do-not-merge*` / `needs-decision` modifier, no reviewer configured, GitHub workflow approval is required for a trusted same-repo bot/App/clanker PR (`workflow-approval-required:`); Hive has posted `/ok-to-test` and is waiting for the repo-local `hive-ok-to-test` workflow or a maintainer, or a human-only lane rule unrelated to trust (workflow-path edits, tests removed, human-merge path, reporter-trust hold). A trusted human's normal GitHub **Approve** review on the exact current head clears a `requires_human` verdict for any author kind/lane and records `reviewer-verdict:requires_human:human-approved@`; a new head invalidates it. The same approval also clears the merge-lane review gate over any non-approved swarm outcome, a perspective's `changes_requested` included (the `review_evaluated` audit line records `human_approver` and `overrode`); without it, `changes_requested` / `reject` still require fixes or manual merge. Under `review.self_authored_decision_policy: resolve`, a Hive-authored PR's reviewer decision question parks here only when it matches a named risk class (`review.DecisionRiskClasses`: permissions/credentials, security, sensitive paths, data loss/irreversible, merge/trust policy, external side effects, licensing/legal, unverifiable) or carries ``; any other question is resolved by policy and recorded as an advisory reason on the approving aggregate. Hive-authored non-risk incomplete-fix reviews with concrete remediation are normalized to `changes_requested` at verdict load/aggregation time and by the stage-label detail selector, even when the finding was marked high severity; duplicate summary/changelog intent-alignment cleanup with a concrete trim/reword fix is included. Those PRs go to `stage/070-fixing`, not here. On a hive-authored PR the trusted human can also answer with a **changes requested** review on the head, or `/hive decision `, which moves the PR to `stage/070-fixing` and kicks the author agent with the text verbatim ([#13041](https://github.com/hivecommons/hive/issues/13041)); `/hive decision` on a PR the hive did not open gets one reply naming the review-side equivalents. One `` comment on entry names what is owed, and for a hive-authored `requires_human` park names the two actions: approve the head to merge as-is, or request changes and the hive pushes the fix | none | | `stage/090-ready-for-human-merge` | PR | the hive would merge this itself but trust/role blocks it: reviewer verdict approve at the **current** head, required checks green (tide excluded), DCO yes, no sentinel alert, no conflicts, soak elapsed, no hold — and the only remaining lane reason is the author's tier/role. A maintainer's `/lgtm` `/approve` is the only missing step. One `` comment on entry carries the evidence. Always read with the `role/*` and `author/*` labels. (Was `stage/090-needs-human` before [#12528](https://github.com/hivecommons/hive/issues/12528); the engine renames the GitHub label in place) | none | @@ -261,22 +265,22 @@ stateDiagram-v2 s010 --> s045: needs-human / park label (agent escalates) s010 --> s015: needs-triage added (reclassification) s015 --> s010: maintainer triages: triage/accepted, hive/verified-open or approved-direction added, or needs-triage removed (not a regression) - s015 --> s020: scanner / relay claim (015 is not a gate) + s015 --> s020: scanner / relay claim (015 is not a gate; needs-triage removed in the same write) s015 --> s045: needs-human / park label (agent escalates) s020 --> s045: claim lapses on a parked issue s045 --> s010: /hive approve or /hive decision (approved-direction; a person's needs-human keeps it at 045, the hive's own lifts) s045 --> s015: park label removed by hand on an untriaged issue (approved-direction itself counts as triage, so /hive approve lands on 010) s020 --> s030: PR under way (hive-open-pr request or start marker naming a branch) s020 --> s040: PR opened straight from the claim - s020 --> s010: claim released or lapsed with no PR - s020 --> s015: claim released or lapsed with no PR on an untriaged issue + s020 --> s010: claim released or lapsed with no PR (not a regression: 020 is a reservation, nothing was built) + s020 --> s015: claim released or lapsed with no PR on an untriaged issue (not a regression) s030 --> s040: PR opened s030 --> s010: PR request failed / start mark lapsed with no PR s030 --> s015: PR request failed / start mark lapsed with no PR on an untriaged issue s030 --> s020: PR evidence lapsed while the claim is still held (backward hop, commented) - s040 --> s010: covering PR closed unmerged with no live claim or start marker (backward hop, narrated on the PR) + s040 --> s010: covering PR closed unmerged with no live claim or start marker (backward hop, narrated on the PR; not a regression when the PR merged and left hive/partially-addressed) s040 --> s015: covering PR closed unmerged on an untriaged issue (backward hop, narrated on the PR) - s040 --> s030: covering PR closed while a PR request / start marker is still live (backward hop, narrated on the PR) + s040 --> s030: the agent is back on the covering PR (live claim with the PR under way; not a regression) s040 --> s020: covering PR closed while the claim is still held (backward hop, narrated on the PR) s040 --> s050: the PR takes over (issue stays 040) s050 --> s060: swarm approve @@ -317,6 +321,45 @@ stateDiagram-v2 s100 --> [*]: merged (labels removed) ``` +#### The regressed marker: when it is set, when it clears + +The dashboard's `regressed` chip and the `` +stage comment both come from one per-item flag in the stage store +(`Record.RegressedFrom`, `src/pkg/stagelabels/store.go`), never from a GitHub +label (`stage/regressed` is retired and stripped on sight). Its lifecycle: + +- **Set** by `Store.Observe` when a hop is a regression under + `IsRegression(from, to)`: a numerically backward hop *except* the drawn + exceptions above. Leaving a reservation is never a regression (020 → 010 / + 015 on a lapsed or released claim — nothing was built); 030 → 020 is the + claim's PR evidence flickering (the issue is still claimed), not a fall; + 040 → 030 is the agent back on its PR; leaving 015, 045, 085, 090 and + 100 → 095 are reclassifications. A fall that is **progress** — 040 → 010 + because the covering PR merged and left `hive/partially-addressed` — is + never booked either (`Decision.Progress`, `Store.ObserveProgress`). +- **Kept** across a same-stage re-observation and across a backward hop that + is itself not a regression (a 040 → 020 fall followed by 020 → 010 stays + regressed: the item has not moved ahead yet). +- **Cleared** by the first forward hop of any size (not only by climbing back + to the stage it fell from), by a progress fall, or when the rule changes so + the recorded fall no longer counts (old records heal on their next + observation). The clear happens in the same tick that applies the new + stage label, and the dashboard stops drawing the chip on its next + `GET /api/stage-tracker` (`regressed_from` is absent, the modifier gone). + +How the chip renders depends on whether a person is in the loop. On a +hive-driven stage with no human-gate modifier (`hold`, `needs-human`, +`needs-decision`, `needs-rebase`, `blocked`, `stalled`) and off the +human-gated stages (045 / 071 / 072 / 085 / 090), it draws as a muted, +dashed `stage ↓` pill whose hover text names the fall and ends "the hive is +handling it — no action needed; clears on the next forward step", and the +row stays out of **Needs attention** and keeps its ordinary headline. Only a +regression on a human-gated row keeps the red `regressed` pill and the +"Sent back" headline — red means someone has to act +([#14570](https://github.com/hivecommons/hive/issues/14570): freshly +filed issues whose claim lapsed wore a red `regressed` that read as a call to +action). + ### How an issue's stage is decided The enumeration cycle already sorts every open issue into a work bucket @@ -415,15 +458,18 @@ used to admit the PR: | `contributor-lane:no-verdict`, `review-gate:no-review-bundle`, `review-gate:awaiting-review-approval`, CI pending with no verdict | `050-review-pending` | | CI pending, verdict approve | `060-reviewed` | | CI green, verdict approve, no modifier, yet the lane stopped on a reason no stage claims or tripped on a transient error (`review-state-check`, …) | `080-awaiting-merge-admission` (weak opinion: any lane's definite stage wins) | +| a posted reviewer decision question (`decision_requested`) on a conflicting / not-mergeable / changes-requested head | `085-needs-decision`, reason `contributor-lane:decision-requested` — no fixer is kicked for such a head, and a conflict is moot if the answer is to close it ([#14607](https://github.com/hivecommons/hive/issues/14607)) | | CI red (`check-failing:*`, `required-check-failing:*`, `status-*`), `conflicting`, `not-mergeable`, `changes-requested`, verdict changes_requested/reject, `needs-rebase` present | `070-fixing` (self-authored lane or `author/hive`) / `071-needs-author` (`author/human`) / `072-returned-to-contributor` (`author/clanker`) — see [Stage 07x by author kind](#stage-07x-by-author-kind). A head whose App-linked commits carry a `Signed-off-by` the squash would misattribute to the PR author (`hive-signoff-attribution`) follows the same split: the branch owner is the only one who can rewrite them. | | `contributor-lane:soaking(...)` — and no red check on the head | `095-pre-merge-soak` | -| `head-changed-since-eval`, `merge-failed`, `required-checks-expected`, `behind`, `updated-branch`, `behind-update-budget-exhausted` (a behind self-authored PR the sweep did not update only because this tick's branch-update budget was spent; retried next tick, waits on the hive, [#12607](https://github.com/hivecommons/hive/issues/12607)), `release-hold*` — and no red check on the head | `100-merge-queue` | +| `head-changed-since-eval`, `merge-failed`, `merge-queue-required` (the base branch's ruleset only accepts merges through GitHub's merge queue, so the direct REST merge is refused; logged as a skip rather than a merge error, [#14018](https://github.com/hivecommons/hive/issues/14018); the self-authored sweep then skips the direct merge on that base for 30 minutes and logs the skip once per PR head, [#14068](https://github.com/hivecommons/hive/issues/14068); the trusted-author, queued and contributor-lane sweeps skip it the same way), `required-checks-expected`, `behind`, `updated-branch`, `behind-update-budget-exhausted` (a behind self-authored PR the sweep did not update only because this tick's branch-update budget was spent; retried next tick, waits on the hive, [#12607](https://github.com/hivecommons/hive/issues/12607)), `release-hold*` — and no red check on the head | `100-merge-queue` | | CI red (required or optional check) while the PR is deferred to an open `[shared-ci]` incident (``, the governor's `deferred_incident`) for the same head | `050-review-pending` / `060-reviewed`, reason `shared-ci-incident:`: the base or a shared check is what is red, so the PR is never sent to 070/071/072, never nudged and never stalled; once the incident closes a still-red head is handed back as usual. Conflicts and requested changes stay author-facing ([#12606](https://github.com/hivecommons/hive/issues/12606)) | | CI red (required or optional) while **every** failing check on the head is also red on the base branch's tip — the governor's `base_red_checks` / `base_red`, set during CI enrichment when a same-named latest check run on the base ref (meta checks excluded) concluded failure / timed_out / cancelled / action_required / startup_failure / stale **and** the PR's changed files include none of `.github/workflows/*`, `Dockerfile` / `Dockerfile.*` / `*.Dockerfile`, `go.mod` / `go.sum` (a PR touching those plausibly owns the check and stays author-facing; a lookup error or a diff beyond the file-list page budget also stays author-facing) | `050-review-pending` / `060-reviewed`, reason `base-branch-red:,` ("waiting for the base branch's CI to recover (…) — not this PR's fault"): never 070/071/072, never nudged, never stalled, still not merged (the lane's skip reason is the same token). The fix loop counts no attempt, kicks no fixer, and posts one `` hold comment per head; an escalation the hive itself raised (ledger `label_applied`) over such a head has its `needs-human` removed with the hold wording. The next evaluation after the base goes green re-evaluates the head: a still-red PR is handed back as usual. Conflicts and requested changes stay author-facing (seen on [#13270](https://github.com/hivecommons/hive/pull/13270) and [#13258](https://github.com/hivecommons/hive/pull/13258), 2026-10-20) | +| CI red (required or optional) on a **fork** PR while **every** failing check's annotation says the check refuses forks (case-insensitive `fork pull request(s) cannot`, `maintainer must push this branch`, or `cannot publish` together with `fork` — a fork-publication / fork-secrets guard) | `085-needs-decision`, reason `fork-guard:,`: never 070/071/072, never nudged, never adopted (074) — neither the author nor the hive can turn the check green by pushing to the fork. The entry comment names the check and says a maintainer must push the branch to the repository (or approve the run as the repository requires). A same-repo PR with the same message, or a fork PR with any other red check, is handed back as usual ([#14273](https://github.com/hivecommons/hive/issues/14273)) | | any **non-required** check on the current head is red, whatever the lane's reason (the lane gates on required checks only, so it still soaks and merges; a reader of the rollup sees ✗). Parking modifiers (`hold`, `needs-human`, admission reasons) still win | `070-fixing` / `071-needs-author` / `072-returned-to-contributor` (by author kind), reason `optional-check-failing:` (the nudge lists every red check); never 080 or 100. A red *required* check is the lane's own `required-check-failing:*` → 070/071/072 ([#12538](https://github.com/hivecommons/hive/issues/12538), seen on [#12457](https://github.com/hivecommons/hive/pull/12457)) | -| `label:hold`, `held`, `label:needs-human`, `exempt-label`, `excluded-label:*`, `sentinel`, `human-merge-path*`, `intent-*`, `contributor-lane:workflow-path` / `tests-removed` / `no-reviewer` / `reporter-trust-hold` | human hand-off, **decision** cause → routed by the head's evidence (below); lands at `085-needs-decision` when the head is otherwise clean | +| `label:hold`, `held`, `label:needs-human`, `exempt-label`, `excluded-label:*`, `sentinel`, `human-merge-path*`, `intent-*`, `contributor-lane:workflow-path` / `tests-removed` / `no-reviewer` / `reporter-trust-hold` | human hand-off, **decision** cause → routed by the head's evidence (below); lands at `085-needs-decision` when the head is otherwise clean. `contributor-lane:workflow-path` reads "awaiting owner approval": a trusted merger's `APPROVED` review on the current head (or a dashboard `/hive decision approve`) releases the fence and the lane merges the PR itself (`owner_approval_releases_workflow_fence`, [#14664](https://github.com/hivecommons/hive/issues/14664)); only with the toggle off, or when the App lacks `workflows: write` for the merge (`hive:unauthorized`, `merge-workflow-path`), does an approved head park at `090-ready-for-human-merge` for a person's click | +| `merge-refused:review-required` (GitHub answered the merge with 405 "At least N approving review(s) is/are required by reviewers with write access"), `merge-refused:method-not-allowed` (405 "Merge commits / Squash merges / Rebase merges are not allowed on this repository") | human hand-off, **decision** cause, as above: lands at `085-needs-decision` when the head is otherwise clean, and the entry comment names the rule GitHub refused the merge on. Every sweep (self-authored, trusted-author, queued, contributor lane) logs these as skips, not `merge-failed` errors, and stops calling merge: a review refusal is remembered per PR head and re-armed when the head moves, when GitHub's `mergeable_state` for the PR changes (an approving review turns `blocked` into `clean`), or after 30 minutes; a method refusal is remembered per repository and method for 30 minutes. The hive never switches the merge method on its own (a forward merge or a `merge-method/merge` PR must not be squashed), so `method-not-allowed` is a manual-merge park. Any other 405, 409 or 422 is still `merge-failed` | | `contributor-lane:no-claim` / `claim-*` / `author-association` / `no-linked-issue`, `untrusted-author-role`, `author-permission-missing` | human hand-off, **trust** cause → routed by the head's evidence (below); lands at `090-ready-for-human-merge` when the head is otherwise clean | -| hand-offs (`not-app-authored`, `trusted-bot-lane`, `contributor-lane:app-authored`), transient fetch errors | no opinion — the owning lane decides; nothing is written | +| hand-offs (`not-app-authored`, `trusted-bot-lane`, `trusted-author:app-authored`, `contributor-lane:app-authored`), transient fetch errors | no opinion — the owning lane decides; nothing is written. An App-authored PR is handed off by the trusted-author and contributor-lane tiers under their own reason; the self-authored lane owns it, and its `review-gate:no-review-bundle` is the stage's reason while the swarm has not reviewed the head ([#14482](https://github.com/hivecommons/hive/issues/14482)) | When several lanes see the same PR the self-authored lane outranks the trusted-author lane, which outranks the contributor lane; the trusted-author @@ -644,13 +690,11 @@ that no longer mixes authorship drops it, and only the hive writes it (labels need triage). An already-parked PR — at `085` as `adoption-stalled:hive-signoff-attribution` from before this rule — is re-evaluated on the next sweep with no new head and no human un-park: the -election clears the escalation back to `074` and the lane carries on. Only -when the election is unavailable does an adopted head in this state go to -`085` as `adoption-stalled:hive-signoff-attribution`, whose one note says -why: a squash-only repo (lift `auto_merge.squash_only` or re-author the -head), a non-trusted role (a maintainer `--merge` or a re-authored head, -hivecommons/hive#13623), an unsigned commit, or a head with no contributor -commit left. A self-authored PR stays `070`. +election clears the escalation back to `074` and the lane carries on. When +the election is unavailable, the adopted head instead re-homes to an +App-authored superseder (`signoff-rehome`), keeping all commits and sign-offs +unchanged without lifting squash-only or role policy. Unsigned commits use +the separate DCO repair path. A self-authored PR stays `070`. Only a clanker PR has a hive on the other end, so only 072 gets the nudge comment, the relay interrupt and adoption; a human PR at 071 just sits there @@ -741,7 +785,9 @@ does not bump itself. in-place `stage/074-adopted-by-hive` contributor branch — is not waiting for a maintainer `/lgtm` or `/approve`. A review verdict of `changes_requested` keeps an adopted PR in the fixer's work list every cycle until a new head - lands. Existing pending fix rows are replaced with the next bounded attempt; + lands. Existing pending fix rows are replaced with the next bounded attempt + (adopted PRs at once; any other hive-owned PR once the row is older than + `review.pending_fix_ttl`, default 2h, with the head unchanged); once the attempt cap is exhausted the PR is visibly parked for a human at 085 with reason `fix-attempts-exhausted`, not left silent. - **Cleared at once on any activity.** The next cycle that sees a new head, @@ -858,23 +904,22 @@ When a clanker-authored contributor PR goes red the hive does not just relabel i DCO-red with unsigned commits the hive cannot rewrite on the contributor branch (see the DCO paragraph below). The stage record's `adopt_reason` moves to `dco-rehome` as well. - - `reason=signoff-rehome` (always `mode=supersede`) — legacy second - takeover, no longer opened: it re-homed an in-place-adopted head - carrying a hive commit whose `Signed-off-by` names the App while the - squash would land as the contributor (`check-squash-signoff-attribution.sh`, - lane reason `hive-signoff-attribution`), but the superseding PR carried - the same head and failed the same gate. Such a head on a `role/trusted` - PR now gets a **merge commit** from the lane itself (`merge-method/merge` - label, one `` note, merge sweeps use - `merge_method=merge`; see the attribution paragraph under [Stage 07x by - author kind](#stage-07x-by-author-kind)), and a PR parked at 085 on - this reason by an earlier build is released on the next sweep once - the election applies (hivecommons/hive#13777). Only where the election - is refused — `auto_merge.squash_only` repo, non-trusted role, an - unsigned commit — does the head go to `stage/085-needs-decision` as - `adoption-stalled:hive-signoff-attribution`, with one note naming the - refusal and proposing a maintainer `--merge` or a re-authored head - (hivecommons/hive#13623). + - `reason=signoff-rehome` (always `mode=supersede`) — an adopted head + carries an App-signed fix that cannot survive a squash attributed to + the contributor (`hive-signoff-attribution`). The lane still prefers + a **merge commit** when its existing trusted-role and policy gates + permit one. Otherwise it immediately opens an App-authored superseder: + the same commits and sign-offs survive unchanged, but the squash is + now attributed to the App. DCO and merge checks still apply. This also + recovers heads parked by older builds (#13975). + - `reason=push-rehome` — push access to an adopted contributor branch is + no longer available; supersede immediately rather than wait on the + offline contributor. + - `reason=rebase-rehome` — `needs-rebase` / conflict work on a pushable + adopted branch first reaches the normal fixer, which rebases onto the + PR base and pushes to the actual fork with a head-bound lease, + preserving authorship and sign-offs. If the head stays idle past the + 074 dwell, supersede in that sweep rather than escalate to 085. - `reason=in-place-stalled` (always `mode=supersede`) — second takeover: an in-place-adopted head sat unchanged for the 074 dwell (3h) with CI idle (not pending) and the review swarm's latest verdict on it @@ -884,6 +929,11 @@ When a clanker-authored contributor PR goes red the hive does not just relabel i not done. The sweep re-evaluates existing 074 records every pass, so a record adopted before this rule existed converts too (hivecommons/hive#12858). + - Stale-red recovery (no re-home): an in-place head red only from a + since-fixed base/shared-CI flake — its failing check is green on the + base tip — is updated from the base when behind, or has its failed + jobs re-run when current, once per (head, base tip), before the dwell + escalates it to 085 (hivecommons/hive#14500). A PR carrying `hold`, `do-not-merge` or `do-not-merge/hold` is never adopted (a human parked it; one log line, the stage stays 072). A PR whose diff trips the contributor lane's diff fences — it touches @@ -904,13 +954,15 @@ When a clanker-authored contributor PR goes red the hive does not just relabel i lane re-reads its diff on every pass, so the fix lane rebases and repairs CI on the PR's branch (074) and the green head lands at `stage/085-needs-decision` with "a maintainer must review and merge - this PR manually" — the lane never merges it, and the 085 note says - the hive adopted it to repair CI and why it still will not merge it. + this PR manually" — the lane does not merge it unattended, and the 085 + note says the hive adopted it to repair CI and why it still will not + merge it (for `workflow-path`, a trusted merger's approval on the + current head releases the fence, [#14664](https://github.com/hivecommons/hive/issues/14664)). - **supersede** (and the `dco-rehome`, `signoff-rehome` and `in-place-stalled` second takeovers): the App PR's body opens with `` and a **Merge stays manual** paragraph. Every App merge lane - (self-authored, trusted-author, `/hive merge`-queued) refuses a PR + (self-authored, trusted-author, `hive-merge`-queued) refuses a PR carrying that marker once it is green, with the carried `contributor-lane:*` reason, so it routes to 085 like a contributor PR with the same diff; `/hive approve` says so too. The hive's own @@ -939,7 +991,12 @@ When a clanker-authored contributor PR goes red the hive does not just relabel i pushes keep the adoption and the stage stays 074 whatever CI or the review swarm say of them — pending, green, approved or red again — until the merge gate (080/085/090/100) takes - the head (hivecommons/hive#12736). A later head keeps that ownership + the head (hivecommons/hive#12736). The hive owns that head: when it is + green, reviewer-approved, DCO-clean and soaked, the contributor lane + admits it under the hive's own authority + (`contributor-lane:adopted-by-hive`, `governor.labels.stage.adoption.auto_merge`, + default `true`) without the original author's trust tier, so 074 moves + to 095/097 and merges instead of parking on a maintainer `/lgtm`. A later head keeps that ownership only when the commit's GitHub-linked author/committer is the App bot (the stage store remembers SHAs that already passed that check; it is not a separate evidence source) @@ -1077,9 +1134,11 @@ When a clanker-authored contributor PR goes red the hive does not just relabel i focus-window rule; this ordering applies at task-boundary time. 7. Capacity ≥ 2: fixes are dispatched immediately as parallel sub-sessions, up to the advertised count so the primary session keeps going and is - never preempted; further failures queue, and each finished fix session - (`task_complete`/`task_failed` for the fix's `task_id`) frees a slot that - is refilled automatically with the next queued `fix-pr`. + never preempted; further failures queue, and each completed fix session + (`task_complete` for the fix's `task_id`) frees a slot that is refilled + automatically with the next queued `fix-pr`. A `task_failed` requeues the + fix with backoff instead (three attempts, then the re-nudge / adoption + path; see [contributor-relay.md § fix-pr tasks](/docs/hive/contributor-relay#fix-pr-tasks)). All seven steps ship with the stage engine and the contribute hub (hivecommons/hive#12436). @@ -1095,14 +1154,21 @@ depends on the author facet and on whether the PR adds anything #M did not |---|---|---| | `author/hive` at ACMM L6 | closed with `hive/superseded` | **self-resolved**: head behind base → GitHub update-branch requested, measured next pass; head current → body `Closes #N` rewritten to `Refs #N (closed by #M; this PR adds )` with the `` marker, one notice, PR continues in its lane; nothing meaningful left → closed with `hive/superseded` | | `author/hive` below L6 | `needs-human` + `Parked` fact → `stage/085-needs-decision` | same | -| `author/human` / `author/clanker` | notice, Review Queue decision, optional grace-window close | `needs-human` + `Parked` fact → `stage/085-needs-decision`, notice lists the files | +| `author/human` / `author/clanker` | notice, Review Queue decision, optional grace-window close | closing claim: `needs-human` + `Parked` fact → `stage/085-needs-decision`, notice lists the files; reference only (`Refs #N`, `Part of #N`, branch name): notice lists the files, no park | The sweep never writes `needs-human` without the `Parked` fact, so the chevron and the label agree in the same pass; and it never parks a hive PR -at L6 for a decision the hive can make. A `needs-human` a person applied -to a hive PR is an instruction: the sweep leaves that PR alone. The hive's -own `needs-human` from an earlier escalation is released when the hive takes -the resolution over, and the stage engine re-derives the lane stage on the +at L6 for a decision the hive can make. A contributor PR that only +*references* the closed issue never claimed to deliver it, so its closer did +not supersede the PR: it gets the notice, not the park (#13959 and #14090, +`Part of` / `Refs` a closed tracker, sat at 085 for this — +hivecommons/hive#14155). A `needs-human` a person applied is an +instruction: the sweep leaves that PR alone. The hive's own `needs-human` +clears with its cause: it is released when the hive takes a hive PR's +resolution over, and on any PR when the supersession the notice announced no +longer holds (claimed issue reopened, claim rewritten, link downgraded to a +reference) — the sweep's "Decision needed" notice plus a hive-applied label +is the sweep's park; the stage engine re-derives the lane stage on the next auto-merge pass (leaving 085 is never a regression). The body marker is the idempotence key: a resolved PR still references a closed issue, and without the marker the next sweep would treat it as a fresh supersession. @@ -1111,7 +1177,8 @@ without the marker the next sweep would treat it as a fresh supersession. These existing labels qualify a stage and may coexist with any stage label; the reconciler never touches them: `hold` / `hold/*`, `needs-human`, -`blocked`, `needs-rebase`, +`blocked`, `needs-rebase`, `hive:unauthorized` (the App lacks a permission +for the next step; see the reference table), `hive/likely-done`, `hive/partially-addressed`, `hive/verified-open`, `hive/claimed-by-` (the grey "who" modifier a stage cannot carry), `lgtm`, `role/*`, `author/*`, `dco` / `no-dco`, and the agent provenance @@ -1157,7 +1224,9 @@ writers all go through the helper — the claim relay (`hive-open-issue claim`, (`src/cmd/hive/claimswire.go`, `Client.ApplyClaimedByLabel`), which add the claim's own label and strip only backend-named `hive/claimed-by-*` labels and the claim holder's own retired per-login label when a claim lands -(another party's label is kept), and remove the holder's labels — only the +(another party's label is kept unless a new agent claim finds trusted App +comments proving the latest human-principal claim was explicitly released; +then its stale `hive/claimed-by-human` label is cleared), and remove the holder's labels — only the holder's, generic plus legacy per-login — when the ledger releases or hands the claim over. The ledger maintains at most one live claim per (repo, issue); thus, co-party `hive/claimed-by-*` labels on the same issue arise @@ -1225,7 +1294,7 @@ is a Prow `plugins.yaml` (hivecommons/infra) change, not a hive write. ## Config that changes label behavior -- `github.self_authorization_hold`, `project.repo_policies[].self_authorization_hold`, and `HIVE_SELF_AUTHORIZATION_HOLD` change #5117 holds. When all are unset, the default follows the live ACMM level: on through L5, off at L6 Fully Autonomous. +- `github.self_authorization_hold`, `project.repo_policies[].self_authorization_hold`, and `HIVE_SELF_AUTHORIZATION_HOLD` change #5117 holds. When all are unset, the default follows the live ACMM level: on through L5, off at L6 Autonomous. - Hive-wide ACMM level controls level holds; per-repo ACMM overrides do not make PRs skip the level hold. - `governor.labels.exempt`, `governor.labels.automerge`, and `project.issue_filter.require_labels` decide exempt/admit/queue behavior. - `project.issue_filter.reporter_trust.*` (admission by reporter), `github.reporter_trust_hold`, `project.repo_policies[].reporter_trust_hold`, and `HIVE_REPORTER_TRUST_HOLD` (the matching merge hold) are the #9665 reporter-trust gate. Off unless `reporter_trust.enabled: true`; the hold follows that switch unless set on its own. Both are edited under **Settings → Labels** (who is trusted, which triage label) and **Settings → Repos** (the hold). diff --git a/docs/content/hive/landscape.md b/docs/content/hive/landscape.md index ffddd975..8fea961f 100644 --- a/docs/content/hive/landscape.md +++ b/docs/content/hive/landscape.md @@ -82,7 +82,7 @@ Symphony's `WORKFLOW.md` or Codex app-server contracts. | Dimension | Symphony | Hive | | --- | --- | --- | -| Work source | Linear in the demo; the spec defines a tracker-neutral adapter. | GitHub Issues, GitHub Projects, Linear and Jira (see Work sources below). | +| Work source | Linear in the demo; the spec defines a tracker-neutral adapter. | Trackers: GitHub Issues, GitHub Projects, Linear and Jira. Forge: GitHub; GitLab and Gitea/Forgejo adapters are not yet end-to-end (see Work sources below). | | Pre-agent gating | Config preflight, state checks, labels, claims and concurrency limits. | Shell-level listing, classification and merge checks before any kick, plus ACMM and proxy write checks. | | Agents | Codex app-server is the runner protocol. | Many CLI backends; isolation depends on backend and deployment. | | Scale model | One workspace per issue, bounded runs, retry. | Queue-depth cadence, long-lived agents, hub/spoke, convergence audits. | @@ -144,8 +144,9 @@ Actions, like gh-aw; Jules itself is a possible backend. **[Omnigent](https://github.com/omnigent-ai/omnigent)** (Apache-2.0) is an open-source "meta-harness" over Claude Code, Codex, Cursor, Pi and others -with policy and sandboxing. Both: a competitor at single-team scale and a -backend candidate. +with policy and sandboxing. Registered as a T3 experimental CLI backend (#14094); +Omnigent's own policies and sandboxing are additive, with Hive's egress proxy +deciding all GitHub writes. **[Zeron](https://github.com/zeronsh/zeron)** (MIT) is a local-first control plane for coding agents. Orchestrator for one developer or VPS; no @@ -157,7 +158,7 @@ work-source adapters. | Work source | GitHub issues and PRs | None built in | Chat and terminal sessions | | Policy | Enterprise controls, agentic review | Scoped permissions, vaults | Omnigent policies; Zeron local | | Isolation | Vendor-run | Managed or self-hosted sandbox | Omnigent sandboxing; Zeron none stated | -| Role for Hive | Competitor; Copilot backend | Backend / hosting | Both / competitor | +| Role for Hive | Competitor; Copilot backend | Backend / hosting | Backend (T3) / competitor | ## Agent isolation @@ -172,7 +173,9 @@ The project is evaluating stronger isolation runtimes. One candidate is "safe, private runtime for fleets of autonomous AI agents" with kernel-level file, syscall and network policy per sandbox. AAIF's [OpenSandbox](https://github.com/opensandbox-group/OpenSandbox) is another. -Nothing is decided and no integration exists today. +[ADR-0026](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0026-sandbox-runtime-selection.md) (Proposed) compares both +against the sandbox contract and recommends OpenShell, pending a prototype on +a hive spoke. No integration exists today. | Project | Isolation unit | Checked | | --- | --- | --- | @@ -193,6 +196,15 @@ itself runs on it. Symphony is Linear-first; Fullsend, gh-aw and Agent HQ are forge-first. Hive states this as a differentiator: the same fleet, policy and dashboard apply whichever tracker holds the work. +That claim covers **trackers**, not **forges**. The code still has to live on +GitHub.com or GitHub Enterprise. Hive has GitLab and Gitea/Forgejo adapters +(`project.forge`) that read, comment, label and hold, and GitLab's can merge, +but the governor, merge gate and agent `gh` path are not yet forge-neutral +([#14096](https://github.com/hivecommons/hive/issues/14096)). Fullsend +already ships GitHub, GitLab and Forgejo. See [Forges](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md). +The [integrations page](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations.md) lists every tracker, forge and backend +with its status. + ## Related projects and vendors **GitHub Copilot coding agent** hands one issue to one hosted agent inside @@ -217,7 +229,8 @@ three: open-source, self-hosted, fleet-scale, with explicit policy. - **Research harnesses**: evaluation and one-off repair, not operations. - **Hive**: operating a live agent fleet across any work source, with many runtimes, ACMM gates, fixed merge policy, hub/spoke provisioning, - contributor compute, cost visibility and proxy-level write checks. + contributor compute, cost visibility and proxy-level write checks. See + [integrations.md](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations.md) for what Hive works with today. ## Hive claims checked against this repo diff --git a/docs/content/hive/manual-provisioning.md b/docs/content/hive/manual-provisioning.md index 5221c181..3a9ea923 100644 --- a/docs/content/hive/manual-provisioning.md +++ b/docs/content/hive/manual-provisioning.md @@ -122,7 +122,7 @@ you're doing a *real* deployment. GitHub — no clone: ```bash -kubectl apply -k "https://github.com/hivecommons/hive//src/deploy/kustomize/overlays/standalone?ref=v4" +kubectl apply -k "https://github.com/hivecommons/hive//src/deploy/kustomize/overlays/standalone?ref=v5" ``` (Note the `//` between the repo and the sub-path — kustomize needs it to find @@ -320,9 +320,9 @@ resources: # ── Public hive standalone overlay, pinned to a reviewed git ref ────────── # kubectl/kustomize fetches this from GitHub at apply time — no local clone. # Bump the ref= value in a git commit when you want to upgrade; review the - # diff of what changed upstream before you do. A git TAG (v4.0.0) or a full - # commit SHA is immutable; a branch (v4) moves under you. - - https://github.com/hivecommons/hive//src/deploy/kustomize/overlays/standalone?ref=v4.0.0 + # diff of what changed upstream before you do. A git TAG (v5.186.4) or a full + # commit SHA is immutable; a branch (v5) moves under you. + - https://github.com/hivecommons/hive//src/deploy/kustomize/overlays/standalone?ref=v5.186.4 # # OpenShift only: add your Route here alongside the remote base. # On plain Kubernetes, use an Ingress resource instead. @@ -347,8 +347,8 @@ patches: # - path: patch-advisory-mode.yaml ``` -> **`ref=` branch vs tag:** `?ref=v4` follows the branch — every `apply` may -> get a different manifest from the last one. `?ref=v4.0.0` (a git tag) or +> **`ref=` branch vs tag:** `?ref=v5` follows the branch — every `apply` may +> get a different manifest from the last one. `?ref=v5.186.4` (a git tag) or > `?ref=` is immutable — the same apply produces the same result > every time. For a production cluster, always pin a tag or SHA; bump it > deliberately in a git commit so you have a record of every upgrade. Git tags @@ -384,7 +384,7 @@ data: ai_author: your-bot-login # GitHub login agents use when authoring PRs primary_repo: your-repo # must appear in repos: above - acmm_level: 2 # 2 = plan+approve gating; 3 = fully autonomous + acmm_level: 2 # 2 = plan+approve gating; 3 = autonomous hub: enabled: false # standalone; no hub connection @@ -507,11 +507,11 @@ To update a key later: `kubectl patch secret hive-secrets -n hive --type=merge - **Option 2 — Sealed Secrets (safe to commit, GitOps-native)** -If you want everything in git (including secrets), [Bitnami Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) encrypts Secrets with the cluster's public key — only that cluster can decrypt. The encrypted `SealedSecret` object is safe to commit: +If you want everything in git (including secrets), [Bitnami Sealed Secrets](https://github.com/bitnami/sealed-secrets) encrypts Secrets with the cluster's public key — only that cluster can decrypt. The encrypted `SealedSecret` object is safe to commit: ```bash # Install controller once (cluster-admin): -kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml +kubectl apply -f https://github.com/bitnami/sealed-secrets/releases/latest/download/controller.yaml # Seal your secret: kubectl create secret generic hive-secrets -n hive \ @@ -1408,11 +1408,13 @@ spec: - { name: HIVE_HUB_URL, value: "https://hive.hivecommons.dev" } # startupProbe keeps the liveness clock from starting until boot # finishes, so a slow start can't race liveness into a restart loop. + # Match spoke.HostedStartupBudget: 10s + 118 x 5s = 600s; keep + # these timings in sync with its hosted startup probe constants. startupProbe: httpGet: { path: /api/health, port: 3002 } initialDelaySeconds: 10 periodSeconds: 5 - failureThreshold: 30 + failureThreshold: 118 livenessProbe: # /api/livez (NOT /api/health) — it also fails when the heartbeat # loop stops *attempting* sends, so a wedged/dead heartbeat diff --git a/docs/content/hive/readme.md b/docs/content/hive/readme.md index c62437a6..204de76a 100644 --- a/docs/content/hive/readme.md +++ b/docs/content/hive/readme.md @@ -40,6 +40,7 @@ Start with [Zero to Automation: Getting Started with Hive](/docs/hive/getting-st ## Where to go next - [Architecture](/docs/hive/architecture) +- [Integrations](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations.md) — backends, trackers, forges and sandboxes Hive works with, at a glance. - [Getting started](/docs/hive/getting-started) - [Operator reference](https://github.com/hivecommons/hive/blob/v5/src/docs/operator-reference.md) - [GitHub webhooks: cadence, patch and reconcile](https://github.com/hivecommons/hive/blob/v5/src/docs/webhooks.md) — polling fallback, webhook patch and reconcile, and the state-accuracy guarantees. @@ -52,6 +53,7 @@ Start with [Zero to Automation: Getting Started with Hive](/docs/hive/getting-st - [Release channels](/docs/hive/release-channels) — what `stable`, `candidate`, and `edge` each mean and what to expect from them. - [Security model](/docs/hive/security-model) — agent safety control inventory, threats, enforcement modes, residual gaps and operator audit checklist. - Security docs rule: `SECURITY.md` and every `src/docs/security*.md` / `ioscan*.md` page, plus `landscape.md`, must score Flesch-Kincaid grade 11 or lower (CI: `src/scripts/check-security-docs-readability.sh`). +- Security docs rot rule: the same pages carry a `Last reviewed: YYYY-MM-DD` line in their first 15 lines, no older than 120 days, and may not say an item is "tracked in" / "in progress" / "planned" against an issue that is already closed (CI: `src/scripts/check-security-docs-rot.sh`). - [Data collection and telemetry](https://github.com/hivecommons/hive/blob/v5/src/docs/telemetry.md) - [Securing your hive: a first-time operator's guide](/docs/hive/securing-your-hive) - [Community and support](https://github.com/hivecommons/hive/blob/v5/src/docs/community.md) diff --git a/docs/content/hive/release-channels.md b/docs/content/hive/release-channels.md index 29728d44..9b0730a1 100644 --- a/docs/content/hive/release-channels.md +++ b/docs/content/hive/release-channels.md @@ -50,11 +50,11 @@ The hive's image is set to `ghcr.io/hivecommons/hive:stable` (via kubectl for re Because a channel tag is a moving (mutable) tag, a channel-tracking hive gets the same floating-tag auto-upgrade treatment as a `-latest` branch tag: upgrades roll the pod but keep the `:stable` image string rather than pinning a SHA ([#3757](https://github.com/hivecommons/hive/pull/3757)). -## The version pill: `stable (v4)` +## The version pill: `stable (v5)` A channel is a moving pointer, so the dashboard shows what it currently points at. The pill on a channel-tracking hive reads, for example: -- `stable (v4)` — the channel currently resolves to a build of branch `v4`; +- `stable (v5)` — the channel currently resolves to a build of branch `v5`; - `stable (49e53e6)` — the channel resolves to a digest the hub could not attribute to a tracked branch (short digest shown); - `stable (?)` — the channel tag could not be resolved on GHCR at all. @@ -89,7 +89,7 @@ The hive's tracked channel is stored hub-side in the per-hive metadata record (` ## Spoke navbar badge -A spoke running a channel image shows both delivery dimensions in its own dashboard version badge: for example, `a1b2c3d · stable (v4) · floating`. The linked short SHA identifies the running build, `stable (v4)` identifies the release channel and built-from branch, and `floating` confirms that the Deployment follows a mutable tag. `candidate` and `edge` are displayed the same way when reported by the backend. A branch tag such as `v5-latest` renders as `a1b2c3d · v5 · floating`, while a SHA tag or digest pin renders as `a1b2c3d · v5 · pinned`. +A spoke running a channel image shows both delivery dimensions in its own dashboard version badge: for example, `a1b2c3d · stable (v5) · floating`. The linked short SHA identifies the running build, `stable (v5)` identifies the release channel and built-from branch, and `floating` confirms that the Deployment follows a mutable tag. `candidate` and `edge` are displayed the same way when reported by the backend. A branch tag such as `v5-latest` renders as `a1b2c3d · v5 · floating`, while a SHA tag or digest pin renders as `a1b2c3d · v5 · pinned`. The tooltip includes the running SHA, channel when present, built-from branch, authoritative Deployment image ref, and tracking mode. The spoke derives both channel and tracking mode server-side from its cached in-cluster Deployment image lookup; browser code does not infer mutability from tag strings. If the Deployment cannot be read (for example, during a plain `docker run`) or its image ref is malformed, the badge says `tracking unknown` and the API does not expose the untrusted ref. This preserves the distinction between unknown provenance and an intentional pin. The channel portion was introduced by [#3762](https://github.com/hivecommons/hive/pull/3762); the tracking detail is specified by [#6321](https://github.com/hivecommons/hive/issues/6321). diff --git a/docs/content/hive/roadmap.md b/docs/content/hive/roadmap.md index ef4f91f0..247625f1 100644 --- a/docs/content/hive/roadmap.md +++ b/docs/content/hive/roadmap.md @@ -36,7 +36,7 @@ order, not priority rank. | Retrospective learning lane | Build from deterministic post-completion advisory beads toward LLM-assisted retro summaries and knowledge extraction. | [#2809](https://github.com/hivecommons/hive/issues/2809), [retro lane](https://github.com/hivecommons/hive/blob/v5/src/docs/retro-lane.md) | | Large-spoke scale envelope | State how a **single** spoke behaves as its backlog grows, rather than discovering it one bug at a time. The [scale envelope](https://github.com/hivecommons/hive/blob/v5/src/docs/scale-envelope.md) page now records the design rule (overflow must paginate, prioritize, or shed load — never fail closed on the work that would drain the backlog), the fact that caps are render-side while issue/PR enumeration is uncapped, and an inventory of every cap with its overflow behaviour and tunability. Evidence base: the 2026-09-17 projectbluefin cluster (~304 open PRs across 16 repos) — uncapped kick PR list (#7368, fixed by #7379/#7395), restart/kick loop on a long delivery (#7363, fixed by #7378), stand-down on render-cap overflow (#7385, scoped per repo by #7391), fork-unaware repair queue (#7386). Remaining: decide one policy for which caps are operator-tunable vs hardcoded-and-narrowly-scoped, and measure enumeration cost against API rate limits at this shape. | [#7392](https://github.com/hivecommons/hive/issues/7392), [scale envelope](https://github.com/hivecommons/hive/blob/v5/src/docs/scale-envelope.md), [#7385](https://github.com/hivecommons/hive/issues/7385) | | ADR back-fill for remaining subsystems | **Done.** Back-filled accepted ADRs capture the knowledge system, skill registry, CEL/channel triggers, and hub/spoke mechanics, so architecture decisions stay auditable. | [ADR-0011](/docs/hive/adr/0011-knowledge-system), [ADR-0012](/docs/hive/adr/0012-skill-registry), [ADR-0013](/docs/hive/adr/0013-cel-triggers), [ADR-0014](/docs/hive/adr/0014-hub-spoke) | -| HiveCommons org migration | Make the hivecommons-to-hivecommons migration auditable before package or repository defaults change, including image mirror phases, docs sweeps, v5 edge coverage, and operator communications. | [migration tracker](https://github.com/hivecommons/hive/blob/v5/src/docs/hivecommons-migration.md), [#5686](https://github.com/hivecommons/hive/issues/5686) | +| HiveCommons org migration | **Tracker closed 2026-09-03 (#5686).** The hivecommons→hivecommons move is auditable in the [migration tracker](https://github.com/hivecommons/hive/blob/v5/src/docs/hivecommons-migration.md): repo transfer, Go module rename, GHCR namespace and the `hive.hivecommons.dev` hub cutover are done; the `ghcr.io/kubestellar/*` mirror tags and `hive.hivecommons.dev` redirect stay as compatibility surfaces until the dated package deprecation (P6) is announced. | [migration tracker](https://github.com/hivecommons/hive/blob/v5/src/docs/hivecommons-migration.md), [#5686](https://github.com/hivecommons/hive/issues/5686) | | v4 stable soak gate | **Done.** CI now keeps `candidate` moving on every green v4 image build and promotes `stable` later by digest only after the soak, evidence, blocker, smoke, and monotonic guards pass. | [stable soak policy](/docs/hive/operations/stable-soak-and-promotion-policy), [#5974](https://github.com/hivecommons/hive/issues/5974) | ## Next @@ -48,7 +48,7 @@ order, not priority rank. | Per-repo policy scoping | **Decided — #6208 closed 2026-09-09.** Repo-scoped policy is one theme: `project.paused_repos` is the shipped pause precedent, and the accepted slices are per-repo pause ([#6211](https://github.com/hivecommons/hive/pull/6211), merged) and per-repo custom agents ([#6215](https://github.com/hivecommons/hive/pull/6215), merged). Per-repo ACMM levels are **not accepted**: [#6111](https://github.com/hivecommons/hive/issues/6111) is on hold and the onboarding-reconciler RFC [#6235](https://github.com/hivecommons/hive/issues/6235) was declined (design record: [repo ACMM onboarding reconciler — declined](https://github.com/hivecommons/hive/blob/v5/src/docs/design/repo-acmm-onboarding.md)). Any future ACMM scoping goes back through #6111, keeping hive-wide ACMM the ceiling with repo overrides only narrower. | [#6208](https://github.com/hivecommons/hive/issues/6208), [#6111](https://github.com/hivecommons/hive/issues/6111), [#6203](https://github.com/hivecommons/hive/issues/6203), [#6204](https://github.com/hivecommons/hive/issues/6204), [#6211](https://github.com/hivecommons/hive/issues/6211), [#6215](https://github.com/hivecommons/hive/issues/6215), [#6235](https://github.com/hivecommons/hive/issues/6235), [#6263](https://github.com/hivecommons/hive/issues/6263), [#6264](https://github.com/hivecommons/hive/issues/6264) | | Docs site publication | **Live at [docs.hivecommons.dev](https://docs.hivecommons.dev).** The org docs site — [hivecommons/docs](https://github.com/hivecommons/docs) (Next.js + Nextra on Netlify) — pulls `src/docs/` from this repo at build time via `scripts/sync-hive-docs.ts` (default branch `v5`; overridable with `HIVE_DOCS_OWNER`/`HIVE_DOCS_REPO`/`HIVE_DOCS_REF`), with links rewritten to site routes and committed copies as a fallback when the source is unreachable. The former [hivecommons/docs](https://github.com/hivecommons/docs) pipeline is retired: `hivecommons.dev/docs/hive` is now a redirect stub pointing here. There is deliberately no second site generator in this tree (a per-repo MkDocs/Docusaurus config would duplicate that pipeline); `hive.hivecommons.dev` serves the product/dashboard landing page, not docs. This repo's job is keeping `src/docs/` a correct source for that sync — see [Docs Link Check](https://github.com/hivecommons/hive/blob/v5/.github/workflows/docs-link-check.yml), which gates relative links and heading anchors on every PR touching `src/docs/`. **Migration dependency — resolved ([#7814](https://github.com/hivecommons/hive/issues/7814), closed):** after the 2026-09-21 cut-over ([#7721](https://github.com/hivecommons/hive/issues/7721) Phase 3), the sync's default ref moved `v4` → `v5` in `hivecommons/docs` (hivecommons/docs#13), so the published site renders the v5 line. | [docs index](/docs/hive/readme), origin: [#2811](https://github.com/hivecommons/hive/issues/2811) (closed), tracker: [#7814](https://github.com/hivecommons/hive/issues/7814) (closed) | | Gitea/Forgejo forge program sequencing | **Decided — #6177/#6167 closed 2026-09-09.** The accepted sequencing: Wave 0 docs-only ADRs land now; Wave 1 may land on `v5` behind the existing `pkg/forge` seam; Wave 2 T5 enumeration-policy extraction is post-GA-cut, or v5-first only if it does not reset the required-gate soak; the enumeration-engine ADR must address the #6090 hardcoded-org portability findings (that cluster, #6081–#6087, closed with the row bound in v5-ga.md) before the seam is widened. Follow-ups go on [#6167](https://github.com/hivecommons/hive/issues/6167). | [#6177](https://github.com/hivecommons/hive/issues/6177), [#6167](https://github.com/hivecommons/hive/issues/6167), [#6169](https://github.com/hivecommons/hive/issues/6169), [#6170](https://github.com/hivecommons/hive/issues/6170), [#6171](https://github.com/hivecommons/hive/issues/6171), [#6112](https://github.com/hivecommons/hive/issues/6112), [#6090](https://github.com/hivecommons/hive/issues/6090), [#6081](https://github.com/hivecommons/hive/issues/6081), [#6082](https://github.com/hivecommons/hive/issues/6082), [#6083](https://github.com/hivecommons/hive/issues/6083), [#6084](https://github.com/hivecommons/hive/issues/6084), [#6085](https://github.com/hivecommons/hive/issues/6085), [#6086](https://github.com/hivecommons/hive/issues/6086), [#6087](https://github.com/hivecommons/hive/issues/6087) | -| One-command contribute install | Collapse the four-step ClankeR on-ramp into `brew install …` via an apptainer build of the published `hive-contributor` image, phased as distroless image → tap-repo conversion action → agent-harness integration. Ownership split and per-phase acceptance criteria are in the [contribute distribution roadmap](https://github.com/hivecommons/hive/blob/v5/docs/contribute-distribution-roadmap.md); implementation is owned by the proposer on the tracker. | [#6635](https://github.com/hivecommons/hive/issues/6635), [#6641](https://github.com/hivecommons/hive/issues/6641) | +| One-command contribute install | **Shipped — #6635/#6641 closed 2026-09-11.** `brew install hivecommons/hive/contribute` from the [`hivecommons/homebrew-hive`](https://github.com/hivecommons/homebrew-hive) tap wraps the published `hive-contributor` image; see [Homebrew](/docs/hive/contributor-relay#homebrew). The phased plan (distroless image → tap-repo conversion action → agent-harness integration) and its ownership split remain in the [contribute distribution roadmap](https://github.com/hivecommons/hive/blob/v5/docs/contribute-distribution-roadmap.md) for the follow-on phases. | [#6635](https://github.com/hivecommons/hive/issues/6635), [#6641](https://github.com/hivecommons/hive/issues/6641) | | GitLab through `pkg/forge` | `pkg/forge` ships GitHub, GitLab, and Gitea/Forgejo adapters with the read path and core write path implemented and tested; `Merge` is left an explicit interface TODO because merge semantics diverge across forges. **First production caller landed:** the governor's escalation writes (evidence comment + `needs-human` label) are now typed against the `forge.IssueWriter` seam, with the adapter selected from `project.forge` — so that key is no longer display-only. A GitHub hive is unchanged, still on `*github.Client`. Those writes are not yet *reached* on a non-GitHub hive, because the read path is still GitHub-shaped: `EnumerateActionable` feeds the whole governor cycle and owns hold-label filtering, issue filters and SLA tracking inside `pkg/github`. Neutralizing enumeration — lifting that policy above the forge boundary, and adding a bulk list method so an N-repo hive does not enumerate N times — is what remains. | [ADR-0005](/docs/hive/adr/0005-forge-abstraction), [#5259](https://github.com/hivecommons/hive/issues/5259), origin: [#2812](https://github.com/hivecommons/hive/issues/2812) (closed) | ## Later diff --git a/docs/content/hive/running-at-level-6.md b/docs/content/hive/running-at-level-6.md index 66475e84..0fbfa6ad 100644 --- a/docs/content/hive/running-at-level-6.md +++ b/docs/content/hive/running-at-level-6.md @@ -12,7 +12,7 @@ This guide walks through preparation, a weekly routine, and recovery. Start with ## Read this before enabling unattended merges -**ACMM** is Hive's AI-native Capability Maturity Model: levels determine permitted automation. Level 6 (L6) is Fully Autonomous, not a release-branch name. An **agent** is a configured AI worker; a **pack** supplies a level's roster, modes and defaults. The **governor** schedules workers according to workload modes; a **cadence** is an agent's wake interval in one mode. These definitions and the shipped roster are grounded in the [Level 6 pack](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml). Only `scanner` has `ISSUES_PRS_MERGE`; other delivery agents create PRs but the App can merge them independently. +**ACMM** is Hive's AI-native Capability Maturity Model: levels determine permitted automation. Level 6 (L6) is Autonomous, not a release-branch name. An **agent** is a configured AI worker; a **pack** supplies a level's roster, modes and defaults. The **governor** schedules workers according to workload modes; a **cadence** is an agent's wake interval in one mode. These definitions and the shipped roster are grounded in the [Level 6 pack](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml). Only `scanner` has `ISSUES_PRS_MERGE`; other delivery agents create PRs but the App can merge them independently. **Auto-merge** here means Hive initiating a merge without a person doing it, not GitHub's similarly named feature. A **hold** is a label-based exclusion from work/merging, including literal `hold` and the dashboard's `hive-pause/` item hold. See [labels and control signals](/docs/hive/labels-and-control-signals); the enforcing predicates are `HasHoldLabel`, `IsHeldLabels` and `HasExemptLabel` in [GitHub client](https://github.com/hivecommons/hive/blob/v5/src/pkg/github/client.go). @@ -190,7 +190,7 @@ Each row is a separate practice. Except where marked, the recommendations addres | **H10. Keep input scanning fail-closed.** Governor Config → Security → Input Defense → ioscan fail mode, `closed` (`ioscan.fail_mode`); retain the L6 inherited default. **ioscan** screens incoming/outgoing text for injection. | Critical suspect input blocks a kick rather than only being redacted; false positives need investigation. Verify in **Audit Log** that a blocked item shows an `ioscan_fail_closed` entry and received no kick. Hive enforces the fail mode, not perfect attack detection. See [kick input enforcement](https://github.com/hivecommons/hive/blob/v5/src/pkg/scheduler/ioscan_enforce.go), [pack](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml). | | **H11. Begin with shipped cadences, not continuous issue generation.** Agent settings → Cadences; keep the [pack's per-mode matrix](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml), slowing work creators when backlog grows. | The pack records the 2026-09 tuning lesson: 119 issues opened versus 31 closed in 24 hours. Verify creations versus merges and queue age fall after slowing. Hive enforces intervals; no throughput balance is enforced. See the pack comment and [governor](https://github.com/hivecommons/hive/blob/v5/src/pkg/governor/governor.go). | | **H12. Set a funded token budget.** **JUDGMENT CALL:** Governor Config → Budget, `governor.budget.total_tokens`, choose from measured recent use for the displayed `period_days` window; separately set a monetary cap at your inference provider. | Zero disables budgeting; tiny limits can suppress essentially every kick. Compare tokens used/remaining and provider dollars against the intended window. Hive enforces token kick suppression (subject to exemptions/ignore settings), not a dollar ceiling or a merge stop. See [governor](https://github.com/hivecommons/hive/blob/v5/src/pkg/governor/governor.go). | -| **H13. Keep optional lanes paused until their output is wanted.** Agent controls/Cadences: retain shipped pauses for supervisor, strategist, outreach, telemetry and operations in all modes. | More issue-filers can worsen saturation; L6 does not mean every lane is active. Verify actual pause/cadence state in each mode. Hive enforces pauses; deciding which work is wanted remains yours. See [pack](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml), [agent pause](https://github.com/hivecommons/hive/blob/v5/src/pkg/agent/manager_pause.go). | +| **H13. Keep optional lanes paused until their output is wanted.** Agent controls/Cadences: retain shipped pauses for supervisor, strategist, outreach, telemetry and operations in all modes. | More issue-filers can worsen saturation; L6 does not mean every lane is active. Verify actual pause/cadence state in each mode. Hive enforces pauses; deciding which work is wanted remains yours. See [pack](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/packs/level-6.yaml), [agent pause](https://github.com/hivecommons/hive/blob/v5/src/crux/agent/manager_pause.go). | | **H14. Deliver alerts to someone who can act.** **JUDGMENT CALL:** Settings → Notifications: configure a documented ntfy/Slack/Discord destination and a responsible operator. | A dashboard-only warning may go unseen while merges continue. Send the documented test notification and verify receipt; Hive sends configured notifications, not an acknowledgement/SLA. See [notifications](https://github.com/hivecommons/hive/blob/v5/src/docs/notifications.md), [notifier](https://github.com/hivecommons/hive/blob/v5/src/pkg/notify/notify.go). | | **H15. Do not mistake review approval for a universal merge gate.** **JUDGMENT CALL:** if choosing `review.require_approval: true`, set it in Governor Config → Security → PR Review (Require review approval) and use GitHub rules or literal holds for changes that require human review. | The setting gates the scanner, not the App sweep. Verify both routes with test PRs; Hive enforces only the path-specific approval check. See [eligibility](https://github.com/hivecommons/hive/blob/v5/src/cmd/hive/merge_eligibility.go), [sweep](https://github.com/hivecommons/hive/blob/v5/src/pkg/github/automerge/automerge_sweep.go). | | **H16. Turn issue claims on so workers do not collide.** Governor Config → Features → **Issue claims** (`governor.claims.enabled: true`); set `governor.claims.{human_ttl_s,agent_ttl_s,contributor_ttl_s}` to match how long work actually takes, in YAML or from the claim-lifetime inputs under the same toggle (owner only, applied without a restart). Claim an item yourself with `hivectl claim o/r#N` before you start on it. | **Off by default.** With claims off, two of your own agents, a relay contributor, an outside bot and you can all start the same issue and open competing PRs; the duplicate-PR sweep only sees the collision after the PRs exist. With claims on, the hub records who is working an issue and ranks holders human > agent > contributor > external, so a lower rank backs off and a higher rank takes over with a `preempted:` label. The record is a marker comment (``) or the assignee on the GitHub issue itself, so it needs no organization: a single user's personal repos work, and any other hive or script reading the issue sees the hold. Claims lapse on their own (4h person, 2h agent kick, 30m relay task unless configured). Verify: claim a test issue, confirm `claimed` appears and that an agent kick on that issue is withheld; let the claim lapse or `hivectl unclaim`, confirm the issue is actionable again. Hive enforces the ledger and expiry, not the discipline of claiming before you start. See [claims](https://github.com/hivecommons/hive/blob/v5/src/pkg/claims/claims.go), [issue claims](https://github.com/hivecommons/hive/blob/v5/src/pkg/github/issue_claims.go), [claims config](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/claims.go), [hivectl claim](/docs/hive/hivectl#claim--unclaim--claims--issue-claims). | @@ -296,8 +296,8 @@ These are confirmations of the practices above, not additional product gates. | --- | --- | --- | | One PR or issue: literal `hold` or item Hold | That item's Hive work/merge eligibility; other items continue. Held PR cannot merge via Hive; an issue hold is not a retroactive hold on every existing linked PR, so hold those too. | GitHub label-capable maintainer; dashboard owner/repo-write permission. Remove label/resume item after review; confirm head unchanged or re-review. [Item API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_repo_hold.go), [client](https://github.com/hivecommons/hive/blob/v5/src/pkg/github/client.go), [hold guard](https://github.com/hivecommons/hive/blob/v5/src/cmd/hive/main_helpers.go). | | One repo: Auto-merge off | All Hive merge paths for that repo; PR creation can continue. Existing PRs **cannot** merge through Hive. Repo Pause additionally removes repo work enumeration/sweep activity. | Owner or repo-write user may switch off/pause; **owner** required to restore auto-merge. Resume repo and separately restore chosen merge policy. Confirm card + refused test merge. [Repo API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_repo_pause.go), [repo policy](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/repo_policy.go). | -| One agent: Pause | That worker's execution/scheduling; other workers and App sweep remain. Existing green PRs **can** still merge. | Dashboard owner; Resume reverses manual pause. Confirm agent paused state. [Agent control](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_agent_control.go), [pause implementation](https://github.com/hivecommons/hive/blob/v5/src/pkg/agent/manager_pause.go). | -| Every agent: fleet breaker, plus explicitly pause any remaining on-demand workers | Breaker pauses currently running non-on-demand agents, preserving already-paused states; it is **not a merge brake**. Existing PRs **can** merge via sweep. | Owner, using the fleet breaker button in the dashboard's top bar. Release resumes only breaker-owned pauses; manually paused agents stay paused. Confirm captured set and each agent status. [Breaker API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_breaker.go), [pause implementation](https://github.com/hivecommons/hive/blob/v5/src/pkg/agent/manager_pause.go). | +| One agent: Pause | That worker's execution/scheduling; other workers and App sweep remain. Existing green PRs **can** still merge. | Dashboard owner; Resume reverses manual pause. Confirm agent paused state. [Agent control](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_agent_control.go), [pause implementation](https://github.com/hivecommons/hive/blob/v5/src/crux/agent/manager_pause.go). | +| Every agent: fleet breaker, plus explicitly pause any remaining on-demand workers | Breaker pauses currently running non-on-demand agents, preserving already-paused states; it is **not a merge brake**. Existing PRs **can** merge via sweep. | Owner, using the fleet breaker button in the dashboard's top bar. Release resumes only breaker-owned pauses; manually paused agents stay paused. Confirm captured set and each agent status. [Breaker API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_breaker.go), [pause implementation](https://github.com/hivecommons/hive/blob/v5/src/crux/agent/manager_pause.go). | | All automatic merging: disable Auto-merge on every repo, or step down below L6 | Repo gate blocks Hive merge authority on existing and new PRs. Workers can still create work according to the remaining level/modes. Disabling only `auto_merge.self_authored` stops **only the App sweep**, not the scanner lane; restarting relays may be necessary for startup-captured sweep flags. | Owner for level change; owner/repo-write for repo off. Restore selected repo switches as owner; re-applying L6 enables active repos again. Confirm each repository card and the **Audit Log**. [Repo policy](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/repo_policy.go), [pack API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_packs.go), [sweep wiring](https://github.com/hivecommons/hive/blob/v5/src/cmd/hive/main.go). | | Contributor work: Suspend contributions, revoke/stop active relays if necessary | Stops offering/admitting contribution work as configured, not Hive's other workers or automatic merges. Existing contributor PRs **can** merge; active work must be handled separately. | Owner in contribution admin; contributor can stop their own relay. Unsuspend/re-authorize only after triage; confirm queue paused and active leases/workers accounted for. [Contribution API](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/api_contribute.go), [relay reference](/docs/hive/contributor-relay). | diff --git a/docs/content/hive/securing-your-hive.md b/docs/content/hive/securing-your-hive.md index 70f7a7b6..bf2e841c 100644 --- a/docs/content/hive/securing-your-hive.md +++ b/docs/content/hive/securing-your-hive.md @@ -2,6 +2,8 @@ # Securing your hive: a first-time operator's guide +Last reviewed: 2026-10-23 + You have connected a hive to a repository. Before you pick a level and walk away, you need one answer: **who can make Hive do what in my repo?** The mechanisms are documented in [security-model.md](/docs/hive/security-model), the diff --git a/docs/content/hive/security-model.md b/docs/content/hive/security-model.md index 6ca52407..64634076 100644 --- a/docs/content/hive/security-model.md +++ b/docs/content/hive/security-model.md @@ -2,6 +2,8 @@ # Security model: how Hive keeps autonomous agents from doing damage +Last reviewed: 2026-10-23 + Hive runs AI coding agents against real repositories. This page is the inventory of the controls that limit what an agent can do, with the file or setting behind each one. Hive's design rule is that anything a human would @@ -158,8 +160,12 @@ merge-eligible list at that commit. `HIVE_PROXY_INJECT_GH_AUTH=true`, the agent's token file holds only a placeholder and the proxy swaps in the real token on GitHub API requests. It requires GitHub App authentication and is refused if advisory proxy mode is -also set, because advisory mode trusts a self-asserted agent name. Open work -is tracked in [#9586](https://github.com/hivecommons/hive/issues/9586). +also set, because advisory mode trusts a self-asserted agent name. A +default-on version with a signing sidecar was proposed in +[#9586](https://github.com/hivecommons/hive/issues/9586) and declined in +September 2026; injection stays opt-in. Rotation of contributor registration +tokens is a separate open item +([#13804](https://github.com/hivecommons/hive/issues/13804)). **Forced egress.** The entrypoint installs an iptables rule redirecting all outbound port-443 traffic through the proxy, so agents cannot bypass it with @@ -172,6 +178,18 @@ process cannot forge. Only `api.github.com` is inspected; other hosts are tunnelled without inspection. See [net-admin-requirement.md](/docs/hive/net-admin-requirement). +**Plain-HTTP forwards.** The redirect covers port 443 only, so plain HTTP +reaches the proxy through its explicit proxy port. Those forwards are +identified like any other request and dialled only when the resolved address +is globally routable: loopback services beside the proxy, the link-local +metadata endpoint, RFC 1918/CGNAT cluster addresses and multicast are refused +with `403` and counted as violations (loopback excepted — agents are handed +`NO_PROXY` for it, so it is logged as the client misconfiguration it is). The +check runs at dial time on the resolved address, so a hostname pointed inward +is refused on the same evidence as a literal IP. Set +`HIVE_PROXY_ALLOW_PRIVATE_FORWARD=true` only where a private-network plain-HTTP +destination is intended. + **Hub-to-spoke channel.** Heartbeats carry a per-hive bearer key derived from the hub master secret and are compared in constant time. The payload is telemetry only: health, version, agent and governor summaries and 24-hour @@ -351,9 +369,10 @@ until code-owner review is required in branch protection. * **Hub registration has no review step.** Any spoke with the derived keys can register. * **Trust tiers do not decay.** -* **Trajectory review and the `ioscan` classifier fail open.** The - deterministic rules, canaries, proxy and token tiers hold when a model is - down. +* **Trajectory review fails open; the `ioscan` classifier fails open only + at L1–L4.** At L5–L6 text is withheld when the classifier cannot answer. + The deterministic rules, canaries, proxy and token tiers hold when a model + is down. ## 12. How to audit a running hive diff --git a/docs/content/hive/security-threat-model.md b/docs/content/hive/security-threat-model.md index 01d65ec6..b0e7c1b5 100644 --- a/docs/content/hive/security-threat-model.md +++ b/docs/content/hive/security-threat-model.md @@ -2,6 +2,8 @@ # Hive security threat model +Last reviewed: 2026-10-23 + Hive runs AI coding agents against real GitHub repositories. Untrusted text from GitHub enters Hive, agents act on it, and deterministic Hive components limit what can leave. This page lists what is worth protecting, where the @@ -72,16 +74,19 @@ and what remains open. The control-by-control reference is ## Residual risks * **Shared container.** Agents are separated by user ID, not by container - or virtual machine. Credential-free sandboxed execution is tracked in - [#2804](https://github.com/hivecommons/hive/issues/2804). + or virtual machine. An opt-in sandbox mode + ([#2804](https://github.com/hivecommons/hive/issues/2804), shipped) runs + each task in its own rootless container with no credentials, but it is + not the default. Stronger isolation options, including OpenShell, are + under evaluation. * **Live tokens in session.** Agents that push or open pull requests still hold a scoped live token. Proxy-side credential injection, which replaces it with a placeholder, is opt-in. * **Network is limited, not absent.** Agents can still reach GitHub reads, model backends and opaque `github.com` tunnels. -* **The optional classifier fails open.** The deterministic rules, canaries - and proxy denials are the floor; the language-model classifier adds - coverage only when it is up. +* **The classifier fails open at L1–L4.** It is on by default from L4 and + fails closed at L5–L6. The deterministic rules, canaries and proxy denials + are the floor; the language-model classifier adds coverage above it. * **Trajectory review reads transcripts, not reasoning**, and fails open. * **Hub compromise is high impact.** The hub still coordinates callbacks, registry data, terminal authorization and fleet visibility. diff --git a/docs/content/hive/stable-soak-policy.md b/docs/content/hive/stable-soak-policy.md index 93cb5d9f..792e56cf 100644 --- a/docs/content/hive/stable-soak-policy.md +++ b/docs/content/hive/stable-soak-policy.md @@ -212,7 +212,7 @@ all four: hub's **My Hives** page the version pill reads `stable (v5)`; if it reads `stable ()` the hub could not attribute the digest to a tracked branch, and if it reads `stable (?)` the channel tag did not resolve at all - (see [release-channels.md](/docs/hive/release-channels#the-version-pill-stable-v4)). + (see [release-channels.md](/docs/hive/release-channels#the-version-pill-stable-v5)). Record which hive reported it. 3. **The gate works unassisted.** The next scheduled `promote-stable.yml` run diff --git a/docs/content/hive/troubleshooting.md b/docs/content/hive/troubleshooting.md index abfa73c2..b30819a8 100644 --- a/docs/content/hive/troubleshooting.md +++ b/docs/content/hive/troubleshooting.md @@ -209,7 +209,7 @@ If an agent returns to "needs login" immediately after resuming, the credentials ## A permission prompt seems to block an agent -On the containerized runtime you normally do **not** need to configure an auto-approve phrase. Claude Code agents launch with `--dangerously-skip-permissions` (`src/pkg/agent/manager.go`), and the manager's `dismissInferencePrompts` routine polls the pane and auto-dismisses the startup consent screens (the "Bypass Permissions mode" dialog, the custom-API-key prompt, and generic "Enter to confirm" menus) dynamically, without a hardcoded phrase list. +On the containerized runtime you normally do **not** need to configure an auto-approve phrase. Claude Code agents launch with `--dangerously-skip-permissions` (`src/crux/agent/manager_launch.go`). Hive seeds `skipDangerousModePermissionPrompt: true` into the Claude settings each agent reads (the shared `/data/home/.claude/settings.json` for `claude` agents, the per-agent HOME and `--settings` file for inference backends) so the "WARNING: Claude Code running in Bypass Permissions mode" screen does not appear at all. If it appears anyway, a kick that finds it selects "Yes, I accept" before delivering instead of restarting the CLI, and the watcher re-runs the `dismissInferencePrompts` routine for any `claude` or inference agent that stays on a consent screen for more than 30 seconds. That routine also dismisses the custom-API-key prompt and generic "Enter to confirm" menus without a hardcoded phrase list. A hive log line `stuck on consent screen, restarting before kick` means the screen could not be accepted in place. Inference backends (`litellm`, `vllm`, `llm-d`) run Claude Code with an isolated per-agent `HOME`. Hive pre-seeds that HOME's `.claude.json` with onboarding complete and workspace trust entries for `/data/agents/` and any repo/worktree directories already present below it, then repairs those entries before each Claude launch. This keeps fresh pods from stopping on Claude Code's "Quick safety check: Is this a project you created or one you trust?" dialog. diff --git a/docs/content/hive/work-sources.md b/docs/content/hive/work-sources.md index fac35ec4..8c721daa 100644 --- a/docs/content/hive/work-sources.md +++ b/docs/content/hive/work-sources.md @@ -22,6 +22,27 @@ the other three. Hosted and dashboard-managed hives can configure the same block from **Settings → Work source**; self-hosted operators can also edit `governor.work_source` in `hive.yaml`. Custom CA bundles and TLS trust overrides for Jira Data Center are not documented here because they are still in progress and are not part of v5. +## Trackers and forges + +`work_source` picks the **tracker**: where work items are read from. The +**forge** — where the repositories, branches and change requests live — is a +separate setting, `project.forge`, which a `project.repo_policies[].forge` +entry can override for one repository: + +| Forge (`project.forge`) | Instance setting | Token | Status | +| --- | --- | --- | --- | +| `github` (default) | `github.base_url` / `github.api_url` | GitHub App | Supported | +| `gitlab` | `gitlab.gitlab_url` (default `https://gitlab.com`) | env var named by `gitlab.token_env` (default `GITLAB_TOKEN`) | Adapter only | +| `gitea` (Gitea and Forgejo) | `gitea.gitea_url` (required) | env var named by `gitea.token_env` (default `GITEA_TOKEN`) | Adapter only | + +The GitLab and Gitea/Forgejo adapters read repos, issues and change requests, +and can comment, label and set the `hold` gate; GitLab can also merge. A hive +still cannot run on them, because enumeration, the merge gate and the agents' +`gh` path are GitHub-only. Every work source above therefore still targets a +GitHub repository. See [Forges: GitLab, Gitea and +Forgejo](https://github.com/hivecommons/hive/blob/v5/src/docs/integrations/forges.md) for the operation list and what runs through +the adapters today, and [Forge setup](https://github.com/hivecommons/hive/blob/v5/src/docs/forge-app-setup.md) for the reasons. + ## Run stages (`run_stages: true`) Run stages let the governor offer a pending run stage such as `spec`, `plan`, diff --git a/docs/content/hotshot/linux.md b/docs/content/hotshot/linux.md index 29608f5d..aa1ba43a 100644 --- a/docs/content/hotshot/linux.md +++ b/docs/content/hotshot/linux.md @@ -20,8 +20,11 @@ your desktop environment, press the hotkey, select a region, and hotshot: |---|---| | Claude Code (`claude`) | `[/path/to/shot.png] ` (bracketed) | | GitHub Copilot CLI / aider / OpenCode | bare backslash-escaped path + space | +| Gemini CLI | `@` + backslash-escaped path + space | | unknown | bracketed (historical default) | +claude + copilot/aider/opencode in the same terminal process tree (e.g. gnome-terminal / kitty / Windows Terminal tabs) -> escaped bare path, because the focused tab cannot be told apart and every supported CLI accepts the escaped path. Gemini CLI gets its `@path` form only when it is the only known CLI in the tree. + This is the same clipboard/typing contract as the macOS app. ## Install @@ -31,6 +34,7 @@ git clone https://github.com/hivecommons/hotshot.git cd hotshot/linux ./install.sh # installs to ~/.local/bin/hotshot-capture ./install.sh --gnome-hotkey # also binds Ctrl+Shift+PrintScreen on GNOME +./install.sh --prefix DIR # install somewhere other than ~/.local/bin ``` The installer reports which dependencies are missing for your session type. @@ -88,8 +92,8 @@ hotshot-capture [--region|--full] [--no-type] [--dir DIR] `wl-copy` serve one MIME target per invocation, so by default the clipboard carries the **image only** (paste with Ctrl-V in Claude Code or GUI apps); the typed injection delivers the path. If [CopyQ](https://hluk.github.io/CopyQ/) - is running, hotshot puts **both** `image/png` and the text path on the - clipboard, matching macOS exactly. + is running, hotshot puts `image/png`, the `text/plain` path, and a percent-encoded + `text/uri-list` file URL on a single clipboard entry, matching macOS. - **Focus tracking**: instead of a menu-bar app that remembers your last terminal, the Linux port captures the focused window at hotkey press time — press the hotkey while your terminal is focused, then select the region. diff --git a/docs/content/hotshot/readme.md b/docs/content/hotshot/readme.md index 32d87a0d..d9937b0a 100644 --- a/docs/content/hotshot/readme.md +++ b/docs/content/hotshot/readme.md @@ -4,7 +4,7 @@ Take a screenshot, and it lands in your terminal. That's it. -**hotshot** is a tiny app that watches for new screenshots and automatically injects them into whichever terminal session you were last using. Built for AI coding assistants like [Claude Code](https://claude.ai/code), GitHub Copilot CLI, aider, and OpenCode that accept image paths as input. +**hotshot** is a tiny app that watches for new screenshots and automatically injects them into whichever terminal session you were last using. Built for AI coding assistants like [Claude Code](https://claude.ai/code), GitHub Copilot CLI, aider, OpenCode, and Gemini CLI that accept image paths as input. If you're like me, you use screenshots constantly to debug your work — a broken UI, a weird error message, a dashboard that doesn't look right. Normally you'd screenshot it, find the file, copy the path, switch to your terminal, paste it in. **hotshot** skips all of that — just take a screenshot the way you always do and it lands in your terminal. @@ -27,6 +27,7 @@ All platforms honor the same contract: | CLI detection (focused terminal's processes) | `ps -t ` | `/proc` descendants | Win32_Process tree | | Claude Code → typed `[path] ` | ✅ | ✅ | ✅ | | Copilot CLI / aider / OpenCode → bare escaped path | ✅ backslash-escaped | ✅ backslash-escaped | double-quoted if it contains spaces or shell metacharacters; refused if it contains `"`, `$` or `` ` `` | +| Gemini CLI → `@` + escaped path | ✅ backslash-escaped | ✅ backslash-escaped | `@"path"` (always double-quoted, no escaping); refused if it contains `"` | | Unknown CLI → bracketed default | ✅ | ✅ | ✅ | The rest of this page documents the **macOS** app. @@ -109,7 +110,7 @@ hotshot remembers which terminal you last clicked on. It supports two injection **File mode** (on by default): 1. macOS saves the screenshot to your configured folder (Desktop, Downloads, etc.) 2. hotshot detects the new file within a couple of seconds -3. Detects which CLI is running in the target terminal session (via its tty) and injects the matching format — `[bracketed]` for Claude Code, a bare shell-escaped path for GitHub Copilot CLI/aider/OpenCode — and also loads the clipboard with the image + file URL + plain-text path so you can ⌘V it anywhere +3. Detects which CLI is running in the target terminal session (via its tty) and injects the matching format — `[bracketed]` for Claude Code, a bare shell-escaped path for GitHub Copilot CLI/aider/OpenCode, `@path` for Gemini CLI — and also loads the clipboard with the image + file URL + plain-text path so you can ⌘V it anywhere **Clipboard mode** (on by default): 1. You take a screenshot to clipboard with ⌃⌘⇧3 or ⌃⌘⇧4 @@ -132,6 +133,9 @@ Click the camera icon in your menu bar. Everything is configurable: | **Inject last screenshot** | — | Menu action: injects the most recent screenshot file path | | **Inject clipboard image** | — | Menu action: sends Ctrl-V to paste current clipboard image | | **Change screenshot folder** | Your macOS default | Pick any folder — opens a standard folder picker | +| **Use macOS default folder** | — | Menu action, shown only while a custom folder is set: reverts to the macOS screenshot location | + +Overriding the folder only changes where hotshot saves and watches; ⌘⇧3/4 files still go to the macOS location unless you change that too (System Settings › Screenshot app › Options › Save to, or `defaults write com.apple.screencapture location`). ## Works with these terminals @@ -150,6 +154,7 @@ Any CLI tool that accepts image file paths as input: - [GitHub Copilot CLI](https://github.com/github/copilot-cli) - [aider](https://aider.chat) - [OpenCode](https://github.com/anomalyco/opencode) +- [Gemini CLI](https://github.com/google-gemini/gemini-cli) - Any tool where you can paste a file path and it reads the image ## FAQ @@ -167,7 +172,7 @@ By default, wherever your Mac saves screenshots (usually Desktop or Downloads). That's on purpose. Watch mode only injects screenshots you took: files written by a browser, Mail, Messages or AirDrop carry macOS's quarantine attribute (`com.apple.quarantine`), which screen captures never do, so hotshot skips them rather than typing an image you didn't capture into your AI session. Use "Inject last screenshot" from the menu, or paste the path yourself, if you really want it. **What format is the path injected in?** -hotshot inspects the processes on the target terminal session's tty (iTerm2 and Terminal.app) and picks the format that CLI understands: Claude Code gets `[/path/to/screenshot.png]`, while GitHub Copilot CLI, aider, and OpenCode get a bare backslash-escaped path (what Finder drag-and-drop inserts). If the CLI can't be determined, the historical bracketed format is used. The clipboard simultaneously carries the PNG image, a file URL, and the escaped plain-text path, so pasting (⌘V, or Ctrl-V in Claude Code) works everywhere. +hotshot inspects the processes on the target terminal session's tty (iTerm2 and Terminal.app) and picks the format that CLI understands: Claude Code gets `[/path/to/screenshot.png]`, while GitHub Copilot CLI, aider, and OpenCode get a bare backslash-escaped path (what Finder drag-and-drop inserts), and Gemini CLI gets the same path prefixed with `@` (`@/path/to/screenshot.png`, its file-reference syntax). On Windows Gemini CLI gets the double-quoted form instead (`@"C:\path\to\screenshot.png"`), since it does no backslash unescaping there. If the CLI can't be determined, the historical bracketed format is used. The clipboard simultaneously carries the PNG image, a file URL, and the escaped plain-text path, so pasting (⌘V, or Ctrl-V in Claude Code) works everywhere. **Does it work over SSH / remote sessions?** Clipboard mode works with remote sessions (tmux, zellij, OpenShell) as long as the clipboard is shared between your Mac and the remote terminal. For sessions without shared clipboard, check out [clipssh](https://github.com/samuellawrentz/clipssh) or [clipaste](https://github.com/hqhq1025/clipaste). diff --git a/docs/content/hotshot/roadmap.md b/docs/content/hotshot/roadmap.md index 64417086..61687031 100644 --- a/docs/content/hotshot/roadmap.md +++ b/docs/content/hotshot/roadmap.md @@ -13,12 +13,14 @@ the macOS app, Linux script, and Windows helper. - Clipboard enrichment with image data, file URLs, and shell-escaped text paths. - Hermetic Linux and Windows tests plus extracted `HotshotCore` helpers for shared macOS decision logic. +- First tagged release (v0.1.0) and a Homebrew install path via the + `hivecommons/homebrew-hive` tap (`brew install hivecommons/hive/hotshot`). ## Near term -1. **Package distribution:** publish first-class install paths beyond manual - scripts, starting with Homebrew and documenting follow-on candidates such as - winget and distro packages. +1. **Package distribution:** extend first-class install paths beyond Homebrew + on macOS, documenting follow-on candidates such as winget and distro + packages. 2. **Cross-platform parity:** keep Linux Wayland/X11 and Windows behavior aligned with macOS for clipboard modes, typed injection, warning paths, and safe degradation when helper tools are missing. diff --git a/docs/content/hotshot/windows.md b/docs/content/hotshot/windows.md index 872dee1b..da41929a 100644 --- a/docs/content/hotshot/windows.md +++ b/docs/content/hotshot/windows.md @@ -24,8 +24,11 @@ built-in Snipping Tool overlay, and hotshot: |---|---| | Claude Code (`claude`) | `[C:\path\to\shot.png] ` (bracketed) | | GitHub Copilot CLI / aider / OpenCode | bare path (double-quoted if it contains spaces or shell metacharacters; refused if it contains `"`, `$` or `` ` ``) + space | +| Gemini CLI (`gemini`) | `@"C:\path\to\shot.png" ` (`@` plus the always double-quoted raw path; refused if it contains `"`) | | unknown | bracketed (historical default) | +Two or more of claude / copilot / aider / opencode / gemini in the same terminal process tree (e.g. gnome-terminal / kitty / Windows Terminal tabs) -> bare path, because the focused tab cannot be told apart and every supported CLI accepts the escaped path. + This is the same clipboard/typing contract as the macOS app. ## Install diff --git a/docs/content/pluk/readme.md b/docs/content/pluk/readme.md index 0bf7d776..87e22955 100644 --- a/docs/content/pluk/readme.md +++ b/docs/content/pluk/readme.md @@ -12,31 +12,42 @@ Pluk classifies, subscribes to, and reacts to JSONL event streams from AI coding ## Install +Requirements: + +- Node.js 22 or newer +- tmux (`attach`, `send` and `watch --capture` drive tmux sessions) + ```bash npm install -g @hivecommons/pluk ``` +Optional: install [rationguard](https://www.npmjs.com/package/@hivecommons/rationguard) only if you want `pluk attach --rationguard`. pluk never fetches it for you; without it on `PATH` (or `--rationguard-bin`), `--rationguard` exits with an error. + +```bash +npm install -g @hivecommons/rationguard +``` ## Quick Start -One command to create a tmux session, start an AI CLI, attach pluk event capture, and wire up rationguard for real-time excuse detection: +One command to create a tmux session, start an AI CLI, and attach pluk event capture: ```bash -pluk attach my-agent --cli=claude --rationguard --rebuttal=send +pluk attach my-agent --cli=claude ``` -This does four things: +This does three things: 1. Creates a tmux session named `my-agent` 2. Attaches pluk via `tmux pipe-pane` to classify all terminal output, including start-up output 3. Starts `claude` inside it -4. Starts `rationguard watch` to detect rationalizations and send rebuttals back -To just attach pluk without rationguard: +To also wire up rationguard for real-time excuse detection (requires rationguard to be installed; see [Install](#install)): ```bash -pluk attach my-agent --cli=claude +pluk attach my-agent --cli=claude --rationguard --rebuttal=send ``` +This additionally starts `rationguard watch` to detect rationalizations and send rebuttals back. + To attach to an existing tmux session (e.g., one already running goose): ```bash @@ -121,7 +132,7 @@ Unknown, misspelled, or wrong-subcommand `--flags` exit 2 (`unknown flag --name | `--rationguard` | Start rationguard watcher alongside pluk (requires `rationguard` on `PATH` — `npm install -g @hivecommons/rationguard` — or `--rationguard-bin`; pluk never fetches it from the registry implicitly) | | `--rationguard-bin=` | Explicit rationguard command to run instead of the one on `PATH`, e.g. `--rationguard-bin='npx --yes @hivecommons/rationguard@0.13.0'` (also `PLUK_RATIONGUARD_BIN`); the flag is ignored with a warning without `--rationguard` | | `--rebuttal=log\|send` | Log rebuttals or auto-send them when rationguard detects excuses; other values exit with code 2 before attaching. Ignored with a warning without `--rationguard` | -| `--dangerous` | Skip CLI permission prompts (`--dangerously-skip-permissions` for claude, `--allow-all-tools` for copilot, `--full-auto` for codex, `--non-interactive` for goose, `--approval-mode yolo` for omp) | +| `--dangerous` | Skip CLI permission prompts (`--dangerously-skip-permissions` for claude, `--allow-all-tools` for copilot, `--full-auto` for codex, `GOOSE_MODE=auto` for goose, `--approval-mode yolo` for omp, `--yolo` for gemini, `--yes-always` for aider) | | `--dir=/path` | Working directory for the agent | | `--cli-args="..."` | Extra arguments to pass to the CLI | | `--no-open` | Don't attach: without `--rationguard`, leave the session detached and print the attach hint; with `--rationguard`, don't open a new window (macOS) or print the hint (elsewhere) | @@ -156,7 +167,7 @@ It writes one JSON line per period, plus a final one on exit, to **stderr** — stdout stays pure event JSONL: ```json -{"pluk_diagnostics":1,"command":"watch","uptime_s":120,"final":false,"linesSeen":842,"framesPolled":0,"captureFailures":0,"classifyErrors":0,"inputErrors":0,"eventsEmitted":31,"eventsFiltered":4} +{"pluk_diagnostics":1,"command":"watch","uptime_s":120,"final":false,"linesSeen":842,"framesPolled":0,"captureFailures":0,"classifyErrors":0,"inputErrors":0,"eventsEmitted":31,"emitErrors":0,"eventsFiltered":4} ``` Only fixed counter categories are reported — never the session name, raw @@ -228,7 +239,7 @@ const watcher = watch({ | `error` | Error in output | | `model_changed` | Model was switched | | `session_ended` | CLI session ended | -| `command_received` | Command sent via pluk-send | +| `command_received` | Reserved; not currently emitted by `pluk send` / `pluk-send` | ## Event Schema diff --git a/docs/content/promptargs/readme.md b/docs/content/promptargs/readme.md index 58b04881..ba51f2e7 100644 --- a/docs/content/promptargs/readme.md +++ b/docs/content/promptargs/readme.md @@ -149,7 +149,7 @@ A blank looks like this: `{{name}}` A blank with a default looks like this: `{{name=hello}}` (promptargs extension) -Padding inside the braces is fine too — `{{ name }}` and `{{ name=hello }}` mean the same thing. +Padding inside the braces is fine too — `{{ name }}` and `{{ name=hello }}` mean the same thing, and so does `{{ name = hello }}` (padding around `=` is not part of the default). Names may use letters, digits, `_` and `-` (`{{file-name}}`). Dotted names such as `{{repo.owner}}` are rejected with an error — use `{{repo_owner}}`; the switch names `no-interactive`, `cross`, `json`, `status` and `help` are also rejected, as they are reserved for CLI switches. Likewise, the template file names `help`, `env`, `ui`, `init`, `list` and `show` are reserved for the subcommands of the same name; such a template is listed as unreachable by `promptargs list` (and `show` warns), because the subcommand always wins. @@ -234,7 +234,10 @@ Status lines showing iteration progress go to stderr: | Syntax | What it does | |--------|-------------| | `--file=a.go,b.go,c.go` | Comma-separated list | +| `--expected="returns 200\, not 500"` | `\,` is a literal comma: one value, no fan-out | +| `--dir='C:\out\\,D:\out'` | `\\,` is a literal backslash then a separator: two values `C:\out\` and `D:\out` | | `--file="src/*.go"` | Glob pattern (expands to matching files) | +| `--file="src/**/*.go"` | Recursive glob (`**` matches any depth) | | `--file=@list.txt` | Reads one value per line from a file | ### Multiple arrays: zip vs cross-product @@ -356,7 +359,7 @@ In the **Builder UI** (`promptargs ui`), click "Show terminal env" to see all av Common useful env vars: `NODE_ENV`, `AWS_REGION`, `CI`, `USER`, `EDITOR`, `PATH`, `SHELL`. -Credential-looking names are intentionally **excluded** from auto-detection: anything containing `TOKEN`, `SECRET`, `PASSWORD`/`PASSWD`/`PASSPHRASE`, `CREDENTIAL`, `API_KEY`/`ACCESS_KEY`/`PRIVATE_KEY`, `BEARER`, `COOKIE`, `WEBHOOK`, `JWT`, `HMAC`, `OAUTH`, `CONNECTION_STRING`, or the `_`-delimited words `AUTH`, `KEY`/`KEYS`, `PWD`, `PAT`, `PASS`, `DSN` (so `GITHUB_TOKEN`, `SIGNING_KEY`, `MYSQL_PWD`, `GITHUB_PAT` and `SLACK_WEBHOOK_URL` are all refused, while `PATH`, `PWD`, `KEYBOARD` and `AUTHOR` still work). Credential-**shaped** values are excluded whatever the name: URLs with a password in them (`postgres://user:pass@host/db`), PEM blocks, JWTs, and well-known token prefixes (`ghp_`, `github_pat_`, `glpat-`, `sk-`, `xoxb-`, `AKIA…`, `npm_…`, …). A repo-controlled template can therefore never expand `{{GITHUB_TOKEN}}` or `{{DATABASE_URL}}` into a secret, even though an explicit `--flag=value` can still pass any value you choose. This also applies to the Builder UI's "Show terminal env" list. +Credential-looking names are intentionally **excluded** from auto-detection: anything containing `TOKEN`, `SECRET`, `PASSWORD`/`PASSWD`/`PASSPHRASE`, `CREDENTIAL`/`CREDENTIALS`/`CREDS`, `API_KEY`/`ACCESS_KEY`/`PRIVATE_KEY`, `BEARER`, `COOKIE`, `WEBHOOK`, `JWT`, `HMAC`, `OAUTH`, `CONNECTION_STRING`, or the `_`-delimited words `AUTH`, `KEY`/`KEYS`, `PWD`, `PAT`, `PASS`, `DSN` (so `GITHUB_TOKEN`, `SIGNING_KEY`, `MYSQL_PWD`, `GITHUB_PAT` and `SLACK_WEBHOOK_URL` are all refused, while `PATH`, `PWD`, `KEYBOARD` and `AUTHOR` still work). Credential-**shaped** values are excluded whatever the name: URLs with a password in them (`postgres://user:pass@host/db`), PEM blocks, JWTs, and well-known token prefixes (`ghp_`, `github_pat_`, `glpat-`, `sk-`, `xoxb-`, `AKIA…`, `npm_…`, `hf_`, `pypi-`, `dop_v1_`, `lin_api_`, `shpat_`, `SG.`, `dapi`, `glc_`, `ya29.`, …). A repo-controlled template can therefore never expand `{{GITHUB_TOKEN}}` or `{{DATABASE_URL}}` into a secret, even though an explicit `--flag=value` can still pass any value you choose. This also applies to the Builder UI's "Show terminal env" list. ### Priority @@ -531,7 +534,7 @@ If you installed the skill (see [Install](#as-an-agent-skill)), use it directly | `--no-interactive` | Skip questions, use defaults only | | `--cross` | Cross-product mode (all combinations) | | `--json` | Output as JSON | -| `--status` | Output status line only | +| `--status` | Output status line only (never prompts; implies `--no-interactive`) | | `... \| claude -p "do this"` | Pipe to Claude | | `... \| goose run` | Pipe to Goose | @@ -554,4 +557,4 @@ promptargs is part of [Hive Commons](https://hivecommons.dev), a family of tools Apache-2.0 -Pirate flag icon: [Twemoji](https://twemoji.twitter.com/), [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). +Pirate flag icon: [Twemoji](https://github.com/twitter/twemoji), [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). diff --git a/docs/content/rationguard/readme.md b/docs/content/rationguard/readme.md index 878da076..56147e0f 100644 --- a/docs/content/rationguard/readme.md +++ b/docs/content/rationguard/readme.md @@ -116,7 +116,9 @@ rationguard watch scanner --rebuttal=send elsewhere with `--run-dir=/path/to/pluk/run` or by setting the `PLUK_RUN_DIR` environment variable (the flag takes precedence if both are set). Add `--verbose` to `watch` to print debug output, and `--patterns-dir=` -to override pluk's CLI pattern directory. A pluk-send failure that falls +to override pluk's CLI pattern directory (applies only with `--mode=watch`; +`--patterns-dir` is ignored, with a warning, in the default +`--mode=subscribe`; `--cli` is accepted there without a warning). A pluk-send failure that falls back to tmux is always logged as one line; add `--log-format=json` to emit bounded JSON lines (`event`, `session`, `delivery`, `timestamp`) on stderr instead. @@ -126,7 +128,7 @@ stderr (`event`, `category`, `confidence`, `matched` count; never the text). Library users can pass `check(text, config, { onEvent })` to receive the same events and feed their own counters. -Add `--diagnostics` (or `--diagnostics=`, a positive number; anything else is rejected) to `watch` for opt-in, +Add `--diagnostics` (or `--diagnostics=`, a number from 1 to 2147483.647; anything else is rejected) to `watch` for opt-in, local-only health visibility on a long-running session: a bounded JSON summary (flush/match/rebuttal counts) is printed to stderr on a fixed interval (default 60s) and once more when the watcher stops. It never @@ -158,7 +160,7 @@ Connect to a pluk event stream and check agent output as it happens. ```bash # Watch a session (subscribe to pluk JSONL log) -rationguard watch my-agent --cli=claude +rationguard watch my-agent # Auto-rebut: send rebuttals back to the agent rationguard watch my-agent --rebuttal=send @@ -307,13 +309,13 @@ A project-local `./.rationguard/custom-excuses.json` is read-only to the CLI and ## Programmatic API ```typescript -import { check, Watcher, generatePromptBlock } from '@hivecommons/rationguard'; +import { check, Watcher, generatePromptBlock, getAllExcuses } from '@hivecommons/rationguard'; // Check text const result = check('Standing by for instructions.'); if (!result.clean) { for (const match of result.matches) { - console.log(`${match.excuse.category}: ${match.excuse.rebuttal}`); + console.log(`${match.excuse?.category}: ${match.excuse?.rebuttal}`); } } @@ -330,8 +332,17 @@ await watcher.start(); // Generate prompt block for system prompt injection const block = generatePromptBlock(); + +// Detect against learned and project excuses too +const full = check('Standing by for instructions.', { excuses: getAllExcuses() }); + +// Prompt blocks are destined for agent instructions: project-local excuses +// (`source: 'project'`) are dropped automatically, exactly as `rationguard prompt` does +const fullBlock = generatePromptBlock({ excuses: getAllExcuses() }); ``` +`check()` and `generatePromptBlock()` called without a config use only the built-in `DEFAULT_EXCUSES`. `getAllExcuses()` returns the same builtin + `~/.rationguard` + project-local set the CLI and `Watcher` use. User and project excuses are tagged `source: 'user'` / `'project'`; builtins carry no `source`. `generatePromptBlock()` never emits `source: 'project'` excuses, whatever it is passed — they come from the (potentially untrusted) working directory and are only ever used for detection. + --- ## Cheat Sheet diff --git a/docs/content/spektacular/getting-started.md b/docs/content/spektacular/getting-started.md index 7224eead..6e17c384 100644 --- a/docs/content/spektacular/getting-started.md +++ b/docs/content/spektacular/getting-started.md @@ -200,7 +200,8 @@ omp This installs the six skills in `.omp/skills/`, command wrappers and native input-preserving handlers in `.omp/commands/`, and eight always-applied -standing-rule files in `.omp/rules/`, alongside the rules in root `AGENTS.md`. +standing-rule files in `.omp/rules/`, which carry the same short rules as +root `AGENTS.md` and link to the full guidance in `.spektacular/guidance/`. It does not overwrite your own omp instruction or settings files. **Start omp in the project's top folder.** Spektacular commands only work diff --git a/docs/content/spektacular/knowledge-base.md b/docs/content/spektacular/knowledge-base.md index 9a73a910..1f268277 100644 --- a/docs/content/spektacular/knowledge-base.md +++ b/docs/content/spektacular/knowledge-base.md @@ -244,9 +244,9 @@ The rules, each of which is a real behaviour rather than a convention: than its prose, so two entries differing only in their tags stay distinct during de-duplication. -You rarely write this block by hand. The `spek-knowledge` skill proposes tags when -it captures an entry, drawn from the vocabulary already in use, and shows them -alongside the destination for confirmation before anything is written. +You rarely write this block by hand. The `spek-knowledge` skill chooses tags when +it captures an entry, drawn from the vocabulary already in use, writes the entry +without asking, and then tells you the tags it chose alongside the destination. ## De-duplication and consolidation @@ -347,10 +347,13 @@ category — keeping the always-applied retrieval tier compact. The entry is fil The skill also loads the tag vocabulary already in use (`spektacular knowledge tags`) and proposes tags for the entry, preferring an established tag over a near-duplicate so the vocabulary converges instead of -fragmenting as the knowledge base grows. As always, the assistant proposes the -destination — the tier, the store name, and the path — along with the tags and the -body, and waits for explicit confirmation before writing. That is one gate, not -several: tags are confirmed with everything else. +fragmenting as the knowledge base grows. The skill then writes the entry itself, +without asking, and tells you which entry it wrote: the tier, the store name, the +path, the tags, and why it is worth keeping. Agents write knowledge the same way +whenever they surface something durable during a workflow, and an orchestrated +child lists the entries it wrote in its hand-back. The report is how you see the +knowledge base change; a wrong entry is fixed by asking for it to be revised or +deleted, and the project's version history holds every earlier state. ### Auditing the tags on existing entries @@ -359,7 +362,9 @@ same way. There is no separate command: invoke the `spek-knowledge` skill and as it to audit the knowledge base tags. The skill discriminates the audit intent from the wording of the request, the same way it tells a lookup from a contribution. -The flow is read-only until you approve something. It enumerates the entries in +The audit is the one knowledge flow that still asks before it writes: it is a +review of entries that already exist, so it proposes tag changes and applies each +only once you agree to it. It enumerates the entries in scope with `knowledge list`, loads the vocabulary in use with `knowledge tags`, reads each entry, and for each one reports two things: @@ -382,32 +387,82 @@ reduced strength. But it will report `apples` sitting beside `apple` as removabl because prefix matching already reaches it and a redundant tag is how a vocabulary silently doubles. -### Reviewing whether entries are still true - -The audit above asks whether an entry is well *labelled*. Its sibling, the -maintenance review, asks the question that matters more over time: whether the -entry is still *true*. It is the same skill and the same shape — invoke -`spek-knowledge` and ask whether the knowledge base is still accurate — and it is -read-only until you approve something, one entry at a time. - -Each entry in scope is classified as **current**, **stale**, **incorrect** or -**unverifiable**, and every stale or incorrect verdict has to name the specific -file, command or behaviour that changed, so you can check a finding rather than -take it on trust. "Looks old" is not a finding. - -One rule decides whether such a review helps or harms, and it is worth stating -plainly: an entry describing a standard the code has not met yet is **current**, -not stale. In this project an entry states the target and the code is what has yet -to meet it, so a difference between the two is work to do, never evidence against -the entry. Only an entry whose subject no longer exists is stale. Without that -rule a review would propose deleting exactly the entries doing the most work. - -The review also reports any category description that no longer matches the -project's current definition of that category, naming where it is and pointing at -`spektacular init ` as the remedy. It reports drift and never repairs it — -bringing a store back into line stays something you run deliberately. An entry you -agree to remove is removed with `spektacular knowledge delete`, and only after you -agree to that entry specifically. +### Pruning the knowledge base + +The audit above asks whether an entry is well *labelled*. Pruning asks the +questions that matter more over time: whether anyone still uses an entry, and +whether it is still *true*. Run it with `spektacular knowledge prune`, or invoke +`spek-knowledge` and ask whether the knowledge base is still accurate. Prune runs +in two passes and acts without asking; `--dry-run` is the preview, which produces +the same report and changes nothing, not an entry and not a usage record. + +**Pass one: unused entries.** The command removes every entry that was created +more than `knowledge.prune.max_age_days` ago (90 by default, set in +`.spektacular/config.yaml`), has been tracked by the usage index for at least that +long, and has not been retrieved in that time. A discovery does not keep an entry: +one that searches keep surfacing but nobody reads is still unused. The tracking +clock is what protects entries written before the index existed, which are only +tracked from their first use after the upgrade. + +**Pass two: entries that are no longer true.** The command then hands the agent +one instruction per remaining entry, with the entry's text inlined, and the agent +gives each one verdict: + +- **current** — the entry still describes how the project works, or states a + standard it is meant to meet, or cannot be checked from the code. +- **corrected** — the subject still exists but the entry described it wrongly; the + agent rewrites it with `knowledge write`, which keeps its creation date and + usage history. +- **deleted** — the entry's subject no longer exists; the agent removes it with + `knowledge delete`, and its usage history goes with it. + +One rule decides whether pruning helps or harms, and it is worth stating plainly: +an entry describing a standard the code has not met yet is **current**, not stale. +In this project an entry states the target and the code is what has yet to meet +it, so a difference between the two is work to do, never evidence against the +entry. Only an entry whose subject no longer exists is deleted. Without that rule +a review would delete exactly the entries doing the most work. "Looks old" is not +a finding either. + +Every correction and deletion carries evidence naming the file, command, flag or +behaviour checked, and the agent reports each verdict with +`spektacular knowledge prune verdict`, which checks it against the store before +handing out the next entry: deleted means the entry is gone, corrected means its +text changed, current means it is untouched, and in a dry run nothing may change. +The final report lists every entry as removed, corrected, deleted or current (or +would remove, would correct, would delete in a preview), with its evidence. +Prune's own reads never count as usage. An interrupted prune resumes where it +stopped when run again, and `--force` starts it over. Recovery from a prune is +through the project's version history. + +The `spek-knowledge` skill also reports any category description that no longer +matches the project's current definition of that category, naming where it is and +pointing at `spektacular init ` as the remedy. It reports drift and never +repairs it — bringing a store back into line stays something you run deliberately. + +## The usage index + +Each knowledge store keeps a usage index, `.usage-index.json`, at its root. For +every entry it records when the entry was created, when the index started +tracking it, and two histories: **discoveries**, one for each search that +returned the entry, and **retrievals**, one for each read of it, including loads +of the always-applied categories. Each history keeps a total count, the time of +the latest use and a short list of recent events. A retrieval made while a +workflow runs names that workflow and its spec: the spec the caller passes (the +plan workflow passes its own), or otherwise the one workflow in progress when +exactly one is. + +An entry's creation date is set by its first write and survives every rewrite. +Entries that predate the index gain a record the first time a knowledge command +writes the index, dated from the commit that first added them when the store is +in a git work tree, and otherwise from the file's modification time. Records for +deleted entries are dropped. Parallel workflow runs update the index under a lock, +so no use is lost. + +The index is Spektacular's own bookkeeping, not an entry: it never appears in a +listing or a search, and `knowledge read`, `knowledge write` and +`knowledge delete` refuse its path. Read it with +`spektacular knowledge usage [--tier T] [--filter N]`. ## Command reference @@ -418,12 +473,15 @@ Each command has a `--schema` mode that prints its input/output schema. | Command | Purpose | |---------|---------| | `spektacular knowledge search [--tier T] [--filter N] [--tag G]` | Keyword-search the stores the request covers (always-applied categories excluded); ranked, one tier-, store- and category-tagged result per matching document, each with title, score, excerpts, tags, and a content checksum. A document need not contain every query word. Repeatable `--tag` restricts results to entries carrying every tag listed | -| `spektacular knowledge read --data '{"tier":"repo","name":"docs","path":"architecture/x.md"}'` | Read one entry's full body from one addressed store | +| `spektacular knowledge read --data '{"tier":"repo","name":"docs","path":"architecture/x.md"}'` | Read one entry's full body from one addressed store. An optional `"spec"` in `--data` attributes the retrieval to that spec's workflow | | `spektacular knowledge list [--tier T] [--filter N]` | List every entry across the stores the request covers | | `spektacular knowledge write --data '{"tier":"repo","name":"docs","path":"gotchas/x.md"}' --file ` | Write an entry into one addressed store (content from `--file`, or stdin) | -| `spektacular knowledge delete --data '{"tier":"repo","name":"docs","path":"gotchas/x.md"}'` | Remove one entry from one addressed store. An address naming an undeclared store is refused with the names available in that tier; an address holding nothing reports success and changes nothing, so a maintenance pass that retries is safe. A category's own generated `README.md` is refused, since it is rendered from the project's definition of that category rather than written by anyone | +| `spektacular knowledge delete --data '{"tier":"repo","name":"docs","path":"gotchas/x.md"}'` | Remove one entry from one addressed store. An address naming an undeclared store is refused with the names available in that tier; an address holding nothing reports success and changes nothing, so a prune that retries is safe. A category's own generated `README.md` is refused, since it is rendered from the project's definition of that category rather than written by anyone | | `spektacular knowledge sources` | List the configured stores by tier and name, with their locations | | `spektacular knowledge categories` | List the category definitions — purpose, boundary, tier, and entry shape | | `spektacular knowledge tags [--tier T] [--filter N]` | List the tag vocabulary already in use across the stores the request covers, each with the number of entries carrying it, most-used first | -| `spektacular knowledge always-applied [--tier T] [--filter N]` | Read every always-applied entry (conventions and glossary) across the stores the request covers, each tagged with its category | -| `spektacular knowledge conventions [--tier T] [--filter N]` | Read every convention across the stores the request covers (the conventions-only view) | +| `spektacular knowledge always-applied [--tier T] [--filter N] [--spec S]` | Read every always-applied entry (conventions and glossary) across the stores the request covers, each tagged with its category. `--spec` names the spec you are working on, so the retrievals are attributed to its workflow | +| `spektacular knowledge conventions [--tier T] [--filter N] [--spec S]` | Read every convention across the stores the request covers (the conventions-only view) | +| `spektacular knowledge usage [--tier T] [--filter N]` | Show each entry's creation date and discovery and retrieval history from the store's usage index | +| `spektacular knowledge prune [--dry-run] [--force] [--tier T] [--filter N]` | Remove old, unread entries, then check every remaining entry against the project; `--dry-run` previews without changing anything | +| `spektacular knowledge prune verdict --data '{"tier":"repo","name":"docs","path":"gotchas/x.md","verdict":"current","evidence":"…"}'` | Record the verdict and evidence for the entry prune is checking, and get the next entry or the final report | diff --git a/docs/content/spektacular/readme.md b/docs/content/spektacular/readme.md index 34f89beb..e9d7a00c 100644 --- a/docs/content/spektacular/readme.md +++ b/docs/content/spektacular/readme.md @@ -6,7 +6,7 @@ Spektacular — **Spek** for short. The specs it produces are called **speks**. -Agent-agnostic CLI tool for spec-driven development, providing skills and integrations for coding agents (Claude, Bob, Codex, GitHub Copilot CLI) to plan and implement work from a written spek. +Agent-agnostic CLI tool for spec-driven development, providing skills and integrations for coding agents (Claude, Bob, Codex, GitHub Copilot CLI, oh-my-pi, Cursor) to plan and implement work from a written spek. > **Status:** early development — see the [releases page](https://github.com/hivecommons/spektacular/releases) for the latest version. @@ -18,7 +18,7 @@ Its core competencies: - **Self-contained binary plus installed agent skills.** A single binary that, on `init`, installs the skills (and commands) your coding agent needs to run the Spek workflows. - **State-machine-driven workflow.** Spec, plan, and implement each run as a stepwise state machine. Spek hands the agent one per-step prompt at a time (`new` / `goto` / `steps`), so every stage is resumable — stop, inspect, edit, and resume without losing work. -- **Agent-agnostic, multi-agent support.** Works with claude, bob, codex, copilot, and oh-my-pi (omp); pick the one your team already uses, or register your own. +- **Agent-agnostic, multi-agent support.** Works with claude, bob, codex, copilot, oh-my-pi (omp), and cursor; pick the one your team already uses, or register your own. - **Project knowledge base.** A searchable, layered store of conventions, architecture, gotchas, and learnings that feeds context into planning. - **Project design documents.** The worked design a feature is built to (an API shape, a user-facing flow, a data format) kept wherever your team already keeps it and referenced by the spek that needs it, so speks stay readable and planning is bound to the design that was agreed. @@ -45,6 +45,31 @@ spektacular status [name] --watch # keep the readable tree on screen, sc When `plan.strict_spec_changes` is true, `status` reports a plan's `document_status: "stale"` and `current_step: "stale"`, and its spec's state as `stale`, once the linked spek is modified after a final plan; `implement new` and subsequent implement steps refuse that plan until it is replanned and re-approved. With the default non-strict setting, a later spek edit does not invalidate an existing plan. A spek change recorded with `spektacular spec amend` during an implement run does not make the plan stale in either mode; any other edit to the spek's body still does under the strict setting. +**Spans.** Every run's log shows where its time went. Spektacular records each command, each workflow step and, during implement, each plan task as a timed span by itself, with every command nested under its step. Agents mark the helper agents they launch and the questions they ask the user, as the workflow instructions tell them: + +```bash +spektacular trace start --type sub-agent|user-wait|work [--parent ] # prints a span id +spektacular trace stop [--status ok|error] [--message ] +``` + +A span nests under the current step unless `--parent` names another open span, and one left open is closed, marked abandoned, when its step ends or its run finishes. With logging off both commands succeed and record nothing. See [Logging and debugging](https://spektacular.dev/debugging/). + +**Run statistics.** Ask the logs where a run's time went; the timings need no agent transcript: + +```bash +spektacular stats list # every recorded run, newest first +spektacular stats run [--name ] # where one run's time went: steps, tasks, helpers, waits, gaps +spektacular stats compare # the two latest runs of a kind, side by side +spektacular stats summary --kind implement # median and 90th percentile per step +spektacular stats commands # every recorded command, with its step and arguments +spektacular stats spans # helpers and waits, including an epic orchestrator's +spektacular stats view [--name ] # the run as a single-file HTML waterfall you can open or share +``` + +For runs driven by Claude Code, `stats run`, `compare` and `summary` also show tokens, cost and time per thousand output tokens for each step, task and helper, read from Claude Code's own session files with no extra setup (`--sessions-dir ` reads them from another folder). Set prices per million tokens under `stats.prices` in `.spektacular/config.yaml`; none are built in. Other agents show these figures as unknown. + +Every form takes the same selection (`--name`, `--kind`, `--agent`, and a repeatable `--run `), prints a readable table by default, and gives the structured report with `--format json`, whose durations are integer milliseconds and whose field names are stable. `--dir ` reads a logs directory copied from anywhere, such as a test run's, with no project needed. Reporting never changes a log, and `stats` commands are never logged. The figures are defined on the documentation site's [Run statistics](https://spektacular.dev/run-stats/) page. `stats view` writes a single HTML file that opens in any browser without a server; see the [Run viewer](https://spektacular.dev/run-viewer/) page on the documentation site. + **Epics.** When a piece of work is split into an epic, ask your agent to *plan this epic*, review the plans, then *implement this epic*. Independent specs are worked on in parallel, each spec is built in its own worktree and merged before the specs that depend on it, and repeating either request picks up where it stopped. Naming an epic in `status` adds a `run` view that says, for planning and for implementing, what each spec still needs and what blocks the epic. See [Epics](https://spektacular.dev/epics/). ### Addressing specs, plans and changelog records @@ -76,12 +101,12 @@ brew install hivecommons/homebrew-repo/spektacular go install github.com/hivecommons/spektacular@latest ``` -Or download a pre-built binary from the [releases page](https://github.com/hivecommons/spektacular/releases). See the [install docs](https://spektacular.dev/install/) for apt and other methods. You also need a supported coding agent CLI (claude, bob, codex, copilot, or omp) installed and configured. +Or download a pre-built binary from the [releases page](https://github.com/hivecommons/spektacular/releases). See the [install docs](https://spektacular.dev/install/) for apt and other methods. You also need a supported coding agent CLI (claude, bob, codex, copilot, omp, or cursor) installed and configured. Once installed, the minimal path is initialise → spec → plan → implement: ```bash -# 1. Initialise your project for a coding agent (claude, bob, codex, copilot, or omp) +# 1. Initialise your project for a coding agent (claude, bob, codex, copilot, omp, or cursor) spektacular init claude # 2. Scaffold a spek, then fill in your requirements @@ -101,13 +126,41 @@ Speks are plain markdown with a small set of structured sections (overview, requ ## Supported agents -Spek ships with five coding-agent integrations. `spektacular init ` runs the chosen agent's install step, writing its workflow skills (and, where the agent has no skill mechanism, command wrappers) into your project: +Spek ships with six coding-agent integrations. `spektacular init ` runs the chosen agent's install step, writing its workflow skills (and, where the agent has no skill mechanism, command wrappers) into your project: - **claude** — installs the workflow skills under `.claude/skills/` and ensures the project's `CLAUDE.md` imports `@AGENTS.md`, so the Spek agent rules take effect. -- **bob** — installs skills under `.bob/skills/` and command wrappers under `.bob/commands/`. +- **bob** — installs skills under `.bob/skills/` and command wrappers under `.bob/commands/`. Bob stops every + sub-agent after `session.maxTurns` turns (100 by default) and hands back whatever it said last, which cuts + epic children off mid-step. `init bob` warns while a limit is set; `spektacular init bob --raise-bob-turn-limit` + sets it to `0` (no limit) in `~/.bob/settings/settings.json`. The epic skills resume a cut-off child either way. - **codex** — installs skills under `.agents/skills/`. - **copilot** — GitHub Copilot CLI; installs the eight workflow skills under `.github/skills/` and the same standing rules in root `AGENTS.md`, with no command wrappers or extra instruction files. - **omp** — installs the same six skills under `.omp/skills/`, Markdown wrappers and native input-preserving handlers under `.omp/commands/`, and eight always-applied `spek-*.md` rules under `.omp/rules/`. The rules mirror only Spektacular's sections of root `AGENTS.md`, keeping them visible even when omp selects another instruction file. Other project instructions, rules, skills and settings are left alone. +- **cursor** — installs skills under `.cursor/skills/`. Cursor reads root `AGENTS.md` itself, so no `.cursor/rules/` files or command wrappers are written. + +### Goal reviewer + +The implement workflow's `check_goal` step has a separate, read-only reviewer judge each task's +acceptance criteria before any is ticked. `spektacular init ` installs that reviewer as a +helper named `spek-goal-reviewer`, in the agent's own sub-agent format, on a cheaper model than the +agent's default: + +| Agent | Helper | Default model | Read-only | +|---|---|---|---| +| claude | `.claude/agents/spek-goal-reviewer.md` | `haiku` | `Read`, `Grep`, `Glob` tools only | +| codex | `.codex/agents/spek-goal-reviewer.toml` | `gpt-6-luna` | by instruction only: Codex has no read-only mode for a helper | +| copilot | `.github/agents/spek-goal-reviewer.agent.md` | `Claude Haiku 4.5` | `read` and `search` tools only | +| omp | `.omp/agents/spek-goal-reviewer.md` | `@smol` (your own cheap model role) | `read`, `grep`, `glob` tools only | +| bob | none | Bob's lighter model | Bob's built-in read-only `explore` helper is the reviewer | + +Set `implement.goal_check.models.` in `.spektacular/config.yaml` to use another model for +one agent; the others keep their defaults. The model is written into the helper when it is +installed, so re-run `spektacular init ` after changing it (`migrate` refreshes it on an +upgrade). Your own agent settings that pin a helper's model still win. Copilot ignores a helper's +model when the session model is `Auto`, and Copilot in VS Code also loads `.claude/agents/`, so a +project set up for both may list two copies of the same reviewer; both carry the same brief. An +agent without the helper falls back to any fresh-context sub-agent given +`spektacular skill review-task-goal`. Switch the check off with `implement.goal_check.enabled: false`. ### GitHub Copilot CLI @@ -163,6 +216,18 @@ In omp, start a workflow with `/spek-new add user auth to the admin pages`, or u Build a versioned binary with `make build`, then run the credential-free runtime regression check with `python3 tests/omp_smoke.py ./bin/spektacular` and omp on `PATH`. It checks actual command discovery, all six command inputs, instruction conflicts, deduplication, shared projects and disabled foreign providers. Verified against omp **18.6.0**; it uses isolated temporary settings and a closed local model endpoint rather than a model service. +### Using Cursor + +Run, in the project's top folder: + +```bash +spektacular init cursor +``` + +Then open that folder in the Cursor editor, or start the Cursor CLI there. Start a workflow from Agent chat with `/spek-new add user auth to the admin pages`; every installed skill (`spek-plan`, `spek-implement`, `spek-knowledge`, `spek-manage-repos`, `spek-design`, `spek-plan-epic`, `spek-implement-epic`) is listed in the `/` menu the same way, and Agent may also pick one up when a request matches its description. The standing rules live in root `AGENTS.md`, which Cursor applies to every Agent chat; your own `.cursor/rules/` are left alone. + +**Shared projects:** Cursor also loads skills from `.agents/skills/` and `.claude/skills/`, so a project initialised for codex or claude as well carries a second copy of each skill. Migration refreshes only the recorded agent's copy; after upgrading, run `spektacular init` once for **each** agent so no copy falls behind. + Each integration is deliberately small: an agent implements a narrow `Agent` interface — `Name()` (its CLI identifier) and `Install()` (which writes its workflow artefacts) — and registers itself with the agent package from an `init()` function. Adding a new agent means implementing those two methods and registering the type. Both the coding agent and the storage layer are pluggable behind defined Go interfaces — the `Agent` interface in `internal/agent` and the `Store` interface in `internal/store` (the read/write/search surface backing the spek, plan, and knowledge stores). Only the `file` store ships today. For the full interface signatures and how to add your own backend or agent, see the [extending documentation](https://spektacular.dev/extending/) and the [plugins overview](https://spektacular.dev/plugins/). @@ -173,7 +238,7 @@ Running `spektacular init ` creates: ```text .spektacular/ -├── config.yaml # agent, command, debug, store settings, and the repo registry +├── config.yaml # agent, command, log, store settings, and the repo registry ├── repo.yaml # the colocated repo's own configuration ├── specs/ # your specification files ├── plans/ # generated plans (plan.md, research.md, context.md) @@ -237,12 +302,18 @@ Agents (and you) reach knowledge through the `spektacular knowledge` commands ra - `knowledge delete` — remove one addressed entry, via `--data '{"tier":"…","name":"…","path":"…"}'`; an address holding nothing succeeds and changes nothing, and a category's own generated description is refused - `knowledge list` — list entries across the stores the request covers; takes `--tier` and `--filter` - `knowledge sources` — list the configured stores by tier and name, with their locations +- `knowledge usage` — show each entry's creation date and discovery and retrieval history from the store's usage index; takes `--tier` and `--filter` +- `knowledge prune` — remove old, unread entries, then check every remaining entry against the project, one instruction per entry answered with `knowledge prune verdict`; `--dry-run` previews without changing anything Every subcommand accepts `--schema` to print its input/output JSON schema and exit. ### Capturing knowledge -When research surfaces a durable learning, gotcha, or convention worth keeping, the agent **proposes** the destination — the tier, the store name, and the path — along with the exact content, and waits for your explicit confirmation before writing; it never persists to a knowledge store unprompted. In a Spek-initialised repo, the `spek-knowledge` skill is the entry point for reading, contributing to, and updating the knowledge base in any session, and coding agents route what they would otherwise save to their own per-user memory into the project knowledge base instead, so captured knowledge lands in git and travels with the project. +When research surfaces a durable learning, gotcha, or convention worth keeping, the agent writes it to the knowledge base itself through the `spek-knowledge` skill and tells you which entry it wrote, where (the tier, the store name, and the path) and why. Nothing waits for your approval: the report is how you see the knowledge base change, and the project's version history is the undo. + +Each store keeps a usage index, `.usage-index.json` at its root, recording when every entry was created and each time a search surfaced it (a discovery) or a read returned it (a retrieval), written only by the knowledge commands and read with `knowledge usage`. `spektacular knowledge prune` (preview it with `--dry-run`) removes entries nobody has retrieved within `knowledge.prune.max_age_days` (90 days by default), then has the agent correct or delete the rest where they no longer describe the project, and reports every entry with its verdict and evidence. + +In a Spek-initialised repo, the `spek-knowledge` skill is the entry point for reading, contributing to, and updating the knowledge base in any session, and coding agents route what they would otherwise save to their own per-user memory into the project knowledge base instead, so captured knowledge lands in git and travels with the project. ## Configuration @@ -256,7 +327,7 @@ Configuration is split across two files, and a colocated single-repo project sim ### Project configuration (`config.yaml`) ```yaml -schema: 3 # settings format version, written by Spektacular; `migrate` raises it +schema: 5 # settings format version, written by Spektacular; `migrate` raises it written_by: 0.16.0 # the Spektacular version that last wrote this file (informational) skills_version: 0.16.0 # the Spektacular version that last installed the agent skills name: my-project # required, slug-safe; namespaces changelog entries @@ -264,8 +335,12 @@ source: git@example.com:org/my-project.git # optional; the project's git addres command: spektacular agent: claude auto_commit: "off" # off | workflow | full: automatic git commits (local only) -debug: - enabled: false # true records every command and response to a session log; SPEKTACULAR_SESSION_LOG=off leaves one call out +log: + provider: file # the logger; only file ships + level: standard # off | standard | verbose (full responses and state); status is never logged + retention: 10 # most recent workflow runs kept, and as many days of standalone commands + config: + directory: logs # relative to the folder holding config.yaml spec: provider: file id_method: timestamp # timestamp + random suffix; counter is legacy and branch-local @@ -293,6 +368,8 @@ knowledge: provider: file config: location: ../team-kb # relative to the folder holding config.yaml, as repos are + prune: + max_age_days: 90 # optional; days an entry may go unretrieved before prune removes it design: sources: # optional, where the project's design documents live; - name: api # declared by the project only, never by a repo @@ -398,7 +475,7 @@ export ANTHROPIC_API_KEY=sk-ant-... Then run: ```bash -harbor run -p tests/harbor/spec-workflow -a claude-code -m claude-sonnet-4-6 -o tests/harbor/jobs +harbor run -p tests/harbor/spec-workflow -a claude-code -m claude-sonnet-5-5 -o tests/harbor/jobs ``` Makefile wrappers run the suites for you, building the binary and wiring up the agent-specific placeholders: @@ -407,11 +484,42 @@ Makefile wrappers run the suites for you, building the binary and wiring up the make harbor-test-spec # spec workflow (claude) make harbor-test-spec-codex # spec workflow (codex) make harbor-test-plan # plan workflow (claude) +make harbor-test-plan-codex # plan workflow (codex) +make harbor-test-plan-bob # plan workflow (IBM Bob) make harbor-test-implement # implement workflow (claude) make harbor-test-repo # guided repo add, answering each question (claude) make harbor-test-repo-delegated # guided repo add, handing the whole set over (claude) +make harbor-test-epic # plan a whole epic through background sub-agents (claude) +make harbor-test-epic-codex # the same, with codex +make harbor-test-epic-bob # the same, with IBM Bob +make harbor-test-implement-epic # implement a planned four-spec epic, two specs in parallel (claude) +make harbor-test-implement-epic-codex # the same, with codex +make harbor-test-implement-epic-bob # the same, with Bob at its default turn limit +make harbor-test-implement-epic-bob-unlimited # the same, with Bob's turn limit removed +make harbor-test-standing-rules # standing rules in AGENTS.md, in a project initialised before the session (claude) +make harbor-test-standing-rules-codex # the same, with codex +make harbor-test-standing-rules-bob # the same, with IBM Bob +make harbor-test-background-child # probe: does headless Claude Code keep a background child past ten minutes? ``` +`harbor-test-background-child` is a probe, not a workflow suite. Headless Claude Code starts one +background child that runs for fifteen minutes and ends its turn, and the verifier prints +`PROBE OUTCOME: after s; notice-woken turn: ` without asserting which way +it went. Its recorded run (2026-10-24, Claude Code 2.1.310: the child was stopped about ten minutes +after the main agent ended its turn) is checked in under +`tests/harbor/shared/tests/data/probe-background-child/`, and the epic skills' headless guidance +follows it. Re-run it when Claude Code changes, and update that guidance if the outcome does. + +Bob has no Harbor agent of its own. `harbor-test-epic-bob` runs it through +`tests/harbor/agents/bob_agent.py`, which extends Harbor's generic ACP agent: it installs Bob from +IBM's published tarball and launches `bob acp` as described in `tests/harbor/agents/bob.json`. The plan suite's verifier reads Bob's run from the trajectory Harbor +records for an ACP agent, since Bob leaves no Claude transcript. +Like the Claude targets, it signs in with the login already on your machine, uploading your Bob +Shell login into the container before Bob starts, so run `bob` and sign in once first. Set +`BOBSHELL_API_KEY` to use an API key instead. If the container's Bob refreshes the session, you may +need to sign in to Bob again locally. Bob runs without `-m`, because its ACP server cannot choose a +model. + #### Test results Results are written to `tests/harbor/jobs/` (gitignored). Each run produces: @@ -420,10 +528,15 @@ Results are written to `tests/harbor/jobs/` (gitignored). Each run produces: tests/harbor/jobs// ├── result.json # Overall pass/fail and metrics └── spec-workflow__/ - ├── agent/ # Agent output log + ├── agent/ # Agent output log and native session transcripts + ├── artifacts/ + │ ├── spektacular/ # The project's .spektacular, with logs/ (Spektacular's run logs) + │ └── agent-logs/bob/ # Bob's own debug log, on Bob runs only ├── verifier/ │ ├── test-stdout.txt # pytest output - │ └── reward.txt # 1 = pass, 0 = fail + │ ├── junit.xml # pytest results, one testcase per check + │ ├── reward.txt # 1 = pass, 0 = fail + │ └── metrics.json # the run's metrics record (see Run metrics below) └── trial.log # Full trial log ``` @@ -434,6 +547,127 @@ tests/harbor/jobs// | `tests/harbor/spec-workflow` | Full spec creation workflow, end to end | | `tests/harbor/plan-workflow` | Full plan generation workflow, end to end | | `tests/harbor/repo-workflow` | Guided repo add, checked against the agent's own transcript | +| `tests/harbor/epic-workflow` | Plans a seeded three-spec epic with `spek-plan-epic`, checked against CLI state and Spektacular's logs, so the same verifier serves every agent | +| `tests/harbor/implement-epic-workflow` | Implements a seeded, planned four-spec epic with `spek-implement-epic`: storage and format in parallel, commands after both are merged, export after commands. Checked against CLI state, git, the finished app's behaviour and Spektacular's logs | + +Every suite records the agent's run at Spektacular's default log level, with no special setting +(the pre-built projects' seed scripts run with logging off and switch it back to the default as +their last step, and the verifiers switch logging off before their own calls). The project's +`.spektacular/logs/`, collected as each run's `artifacts/spektacular/logs/`, holds one file per +workflow run and one per day for commands outside any run, whichever agent ran, apart from +`status`, which is never logged. Harbor reads these logs only through `spektacular stats` (`list`, +`run`, `commands` and `spans`), never by parsing them, so running `spektacular stats run --dir +/artifacts/spektacular/logs` on a collected trial shows the same step timings as its +metrics record. Reading them locally needs a `spektacular` that has `stats` on `PATH`, or one named +by `SPEKTACULAR_BIN`. + +The epic suites print a timeline from those logs at the top of their results: when each spec's +child started and finished, its slowest step, and the hand-off gaps. The orchestrator starts a +`sub-agent` span when it launches a child and stops it when it handles the child's hand-back, so +*spawned after* runs from that span's start to the child's first command and *picked up after* +from the child's last command to the span's end (or, for a span never stopped, to the +orchestrator's next recorded command or span); *merged after* runs to the orchestrator's +`epic merge`. Because `status` is never logged, these gaps are not comparable with timelines from +before. The timeline also lists every child that had to be resumed and the step it stopped at, +and, on Bob runs, how often Bob ended a task at its turn limit. Run it on a collected job with +`python3 tests/harbor/shared/timeline.py //artifacts/spektacular/logs [//artifacts/agent-logs/bob]`. + +#### Run metrics + +Every suite's verifier leaves `verifier/metrics.json` beside `reward.txt`, in the same shape for +every agent and suite. `tests/harbor/shared/metrics.py` writes it after the reward is written, and +the verifier ignores how it exits, so recording metrics never changes a run's pass or fail. The +record's identity comes from a `build.json` that each `make harbor-test-*` target writes into the +suite it renders, beside the recorder and the shared helpers it copies there. A suite run straight +from its source directory, without its make target, has no recorder in its tests and leaves no +record. + +A value a run's logs do not supply is `null`, and its field is listed under `unknown` with the +reason. It is never written as zero. + +| Field | Meaning | +|---|---| +| `schema_version` | The record's shape, currently `3`. Version 3 takes every step, helper, wait and gap from Spektacular's logs, in place of version 2's transcript-based step output, child placement and idle time; the report still reads version-1 and version-2 records | +| `run` | `suite`, `scenario`, `agent`, `model`, `spektacular_version` (`git describe`), `commit`, and `dirty` (the tree had uncommitted changes when the suite was built) | +| `result` | `reward` as written to `reward.txt`. `checks` holds `passed`, `total` and `score` (passed over total, skipped tests excluded). `groups` gives the same per group, under its `name`: the test class, or the test module for suites written as plain functions | +| `totals` | From the agent's log: `wall_time_s`, `model_time_s` (time spent in model calls, summed across sub-agents, so it can exceed the wall time), `turns`, `cost_usd`, `input_tokens` (uncached), `output_tokens`, `cache_read_tokens` and `cache_write_tokens`. From Spektacular's logs: `trace_calls` and `trace_calls_per_task` (the run's `spektacular trace` calls, and their average per plan task), and, summed over every workflow run, `user_wait_s` (time waiting on the user), `helper_wait_s` (time a helper agent was running), `gap_s` and `gaps` (the time in, and the number of, stretches with no recorded event longer than `gap_threshold_s`, 300 by default) | +| `steps` | One entry in `workflows` per workflow run in Spektacular's logs, in start order: its `run_id`, `kind`, `name`, `status`, `wall_s`, `user_wait_s`, `helper_wait_s`, `gap_s` and `gaps`, and in `steps` each step it entered, in order of first entry, with its `step` name, `started_at`, `entries`, `duration_s` (over every entry), its split into `working_s`, `helper_wait_s` and `user_wait_s` (which add up to its duration), `commands`, `helpers` (how many helper agents were started in it) and `incomplete` (entries that did not finish). `workflows` is `null` when the logs are missing or hold no workflow run | +| `subagents` | From Spektacular's logs: `started`, `completed`, `failed`, `unfinished` (not stopped, or cut off), `durations_s` for each helper that finished, and `helpers`, every `sub-agent` span: `span_id`, `parent`, `name`, `spec`, `kind`, `step`, `task_id`, `outside_run` (an epic orchestrator's helper, started outside any run, whose `spec` is the lane of the child launched under it and whose `step` is `null`), `started_at`, `ended_at`, `duration_s`, `end_reason`, `status` and `complete`. From the agent's log: `briefs`, every child agent the run started, nested ones included, with its `id` (its launching call), `parent`, `kind`, `description`, `models`, `prompt` (the full context it was given) and `prompt_chars` | +| `unknown` | One `{field, reason}` per measure the record could not fill | + +Everything but tokens, models, cost and briefs comes from the logs Spektacular keeps under +`.spektacular/logs/`, recorded at the default log level, which reach the trial's artifacts with +the project's `.spektacular`. The recorder reads them only through `spektacular stats` (`list`, +`run`, `commands` and `spans`), never by parsing them, so every step, helper and wait figure means +the same for every agent, and `spektacular stats run --dir /artifacts/spektacular/logs` on a +collected trial gives the same numbers. A step's figures add up over every entry into it, and a +resumed child's hand-off is not inside any step. When the logs are missing, or `stats` cannot read +them, every log-sourced field is `null` and listed under `unknown` with the reason, and none is +filled in from the agent's transcript. + +Claude's totals come from the `result` summary at the end of its transcript, with tokens summed over +every model it used. When background sub-agents split a Claude run into several summaries, turns are +summed, cost, tokens and model time come from the last summary, and wall time is the span from the +transcript's first message to its last. The other agents report less, but only in the transcript's +fields: steps, helpers and waits are the same for every agent. + +| Agent | Unknown in its record | +|---|---| +| Bob | Tokens, cost, model time and model. Each brief carries only its `kind`, `description` and `prompt` (and so `prompt_chars`); its `models` are unknown. Its wall time comes from Bob's own shell logs | +| Codex | `briefs` (`null`: Codex's log shows no helpers), model time and cache writes. Cost too, unless Harbor can price the model | +| Oracle (no agent) | Every agent total, the model and `briefs`. Wall time comes from Spektacular's logs | + +#### Comparing runs + +`tests/harbor/shared/report.py` reads the records collected under `tests/harbor/jobs/`, and groups +runs by suite and scenario, so, for example, repo-workflow's one-at-a-time and delegated runs are +kept apart. It opens +only each trial's `verifier/metrics.json` and `result.json`, never the agent's own files, so it runs +as your normal user even though Claude runs leave root-owned files under `agent/`. Trials without a +record, such as runs from before the recorder, are skipped. `make harbor-report` runs it, passing +`ARGS` through: + +```bash +make harbor-report # one row per run, grouped by suite +make harbor-report ARGS='tests/harbor/jobs/fix2-*' # only some jobs (directories or globs) +make harbor-report ARGS='--run plan-workflow-claude__' # one run's step split, by a trial name from the table +make harbor-report ARGS='--run epic-workflow-claude__ --prompts' # ... and every helper's brief +make harbor-report ARGS='--compare plan-workflow-claude__ plan-workflow-claude__' # two trials side by side +make harbor-report ARGS="--baseline 'tests/harbor/jobs/before-*' --candidate 'tests/harbor/jobs/after-*'" +``` + +The table shows each run's agent and model, wall time, user wait and gap time in minutes, turns, +output and cache-read tokens, cost, check score and pass or fail, and marks a run whose gaps take +more than a quarter of its wall time `◀ gaps` (the threshold is `GAP_SHARE` at the top of +`report.py`). `--run` takes a trial directory, a trial name, or a prefix that names one trial. For +each workflow run it lists every step with its entries, duration and share of the run, and its +split into working, helper-wait and user-wait time, with the helpers it started; then the run's +totals (wall, user wait, helper wait and gaps over the threshold), the helper counts, a table of +the helpers (name, spec, step, seconds and how it ended), and every unknown measure with its +reason. A step taking more than a third of the run's wall time is marked `◀ dominant`. Add +`--prompts` to `--run` to print each helper's brief after it; without `--run` it is a usage error. + +`--compare A B` takes two trial directories or names and prints, for each workflow (matched by +kind and name, else in order within a kind, since a suite's generated spec names differ from +trial to trial), every step's duration, working, helper-wait and user-wait time and helpers for A +and B with B's difference from A, then the totals (wall, user wait, helper wait, gaps, cost and +tokens) the same way. A step only one trial entered shows `-` on the other side, never 0. A +version-1 or version-2 record holds none of the split, so those figures print `unknown`. + +`--baseline` and `--candidate` each take job directories or globs: the runs before a change and the +runs after it. For each suite (and scenario) present in both, it prints the median of wall time, +user wait, helper wait, gap time, turns, output tokens, total tokens, cost and check score in each +set, and the change. A median is +taken over the runs whose value is known. A measure is marked +`◀ regression` when the candidate's median is at least twice the baseline's, or when the check +score falls by 0.1 or more. A measure no run in a set knows prints `unknown` there and is never +flagged. +Suites present in only one set are listed as unmatched. The thresholds are constants at the top of +`report.py`. + +The recorder can also be run by hand on a collected trial, for example to re-record it: +`python3 tests/harbor/shared/metrics.py --agent-dir /agent --log-dir /artifacts/spektacular/logs --bob-logs /artifacts/agent-logs/bob --junit /verifier/junit.xml --reward /verifier/reward.txt --build-info --out `. +Reading `agent/` there needs the same permissions as the files Harbor left in it. The repo-workflow suite is the one whose assertions are about the conversation rather than the files: that only the repo itself is asked for cold, that every later question carries a proposed @@ -446,6 +680,22 @@ pytest with no container and proves each rule fails on a transcript that breaks python3 -m pytest tests/harbor/repo-workflow/tests/test_verifier_selfcheck.py ``` +The shared helpers have self-checks of their own, also without a container: the run log reader +(over canned `stats` answers, which include the Go golden files, so a renamed field breaks them +too), the delegation rules, the recorder (including every way an input can be missing or broken), +the timeline, the report, the wiring of the recorder into every suite, and a check that the shared +scripts stay within 3,000 lines. An integration test builds a throwaway `spektacular` from the +working tree and runs the reader, the recorder and the timeline over fixture logs; it needs Go, +and skips without it. Some run on real runs: trimmed copies of +collected trials, checked in under `tests/harbor/shared/tests/data/`, with every message's text +blanked to the same length. `python3 tests/harbor/shared/tests/trim_reference.py ` +adds one, and writes nothing if trimming would change a value the recorder computes. They live in +`tests/harbor/shared/tests/`, which the make targets never copy into an image: + +```bash +uvx --with pytest pytest --import-mode=importlib tests/harbor/shared/tests +``` + ## Building from Source ```bash