This page is a concise operator reference for fields and runtime knobs that are
easy to miss in hive.yaml.example. It was checked against pkg/config/config.go
and cmd/hive/main.go on branch v4.
For the full centralized environment variable table, including hub, backup, inference, deployment, contributor, and legacy helper-script variables, see Environment variable reference.
Container images install bin/go-wrapper.sh as go in the runtime Go toolchain
and keep the real compiler beside it as go-real. When HIVE_AGENT or
HIVE_AGENT_ID is set, the shim blocks go test, go vet, and go tool vet
inside the hive pod and points agents to CI instead, because those commands can
read or mutate live /data state and have killed agent sessions. Human
operators can debug inside the pod by setting HIVE_ALLOW_LOCAL_GO_TEST=1; all
other Go subcommands pass through unchanged.
Most of hive.yaml.example is optional. The smallest config the hive will start
with (enforced by Config.Validate) is:
project.org— the GitHub org the hive works on. Startup crash-loops withproject.org is requiredif missing.- at least one repo under
project.repos(bare repo name; the org isproject.org). - GitHub credentials — one of
github.token,github.app_id(App auth), orgithub.forge. Missing all three fails withgithub.token, github.app_id or github.forge is required. - at least one agent under
agents:(a barename: { backend, model }is enough; defaults fill in the rest — see agent-configuration.md).
Everything else — governor cadences, knowledge, notifications,
dashboard.auth_token, gateways, hub — is optional and has working defaults.
A minimal hive.yaml:
project:
org: my-org
repos: [my-repo]
github:
token: ${HIVE_GITHUB_TOKEN}
pr_detail_ttl_s: 1800
agents:
scanner:
backend: copilot
model: claude-sonnet-4-6Every hive exposes GET /api/gh-rate-limits behind dashboard auth. The payload
contains GitHub's core/search/GraphQL rate-limit windows, ETag cache counters,
and a one-hour REST top-consumer list grouped by caller, method, and normalized
endpoint. Operators can collect a hosted fleet view with:
make quota-report
# or:
bash src/scripts/gh-quota-report.sh --context hive-oke --top 3For a single dashboard, use --local <url> --token <dashboard-token>; for saved
fixtures or CI-free formatting checks, use --from-file <json>. The command
prints one row per spoke plus the hub when available. The hub endpoint may lag a
spoke rollout; a 404 or other unavailable hub response is reported as
endpoint-missing without hiding spoke data. If none of the mounted/configured
dashboard-token candidates authenticates, the row reports auth-required; the
script does not forge X-Hive-User for localhost because the dashboard
middleware strips unproved identity headers unless the hub proxy proof is valid.
Interpret the top-consumer columns this way:
chargedcounts REST requests that consumed core quota in the last hour.not_modifiedcounts conditionalGETrequests answered from GitHub as304 Not Modifiedand replayed by Hive's ETag cache; these are useful signal but did not spend quota.requestsischarged + not_modifiedplus any other observed responses.- Any
rate_limitedentry, or any spoke below 10% remaining core quota, makes the script exit non-zero so it can be used as a smoke check.
The known hot endpoint in hosted fleets is
/repos/{owner}/{repo}/pulls/{number}. It should be watched first when charged
usage climbs, because repeated PR detail refreshes can dominate the one-hour
window even when list endpoints are mostly free ETag revalidations.
Hosted spokes authenticate as their GitHub App installation and normally show a
5,000 requests/hour core.limit (app/token in the report). A 60/hour limit is
anonymous and means credentials were not applied. The hub must use the App or
installation identity too; do not configure a personal access token on the hub,
because one operator's PAT would become a shared fleet bottleneck and audit
liability.
Before reference, captured 2026-10-08 from the hosted fleet:
| Metric | Value |
|---|---|
| Total charged REST requests/hour | 12995 |
| Hub endpoint | endpoint-missing |
| Top fleet consumer | hive GET /repos/{owner}/{repo}/pulls/{number} 4259/9505 |
| Second fleet consumer | hive GET /repos/{owner}/{repo}/issues/{number}/comments 1819/3298 |
| Third fleet consumer | hive:enrich_pr_ci GET /repos/{owner}/{repo}/pulls/{number} 1157/2325 |
Hive emits OpenTelemetry spans and metrics for every GitHub call, the governor eval cycle, and the ForgeState cache (#13380, Phase 3 of #13373). Both signals share one switch and are off by default; nothing is exported and no behaviour changes until an endpoint is configured.
| Setting | Effect |
|---|---|
otel.enabled: true (alias tracing.enabled) |
Turns the pipeline on. Without an endpoint it stays a no-op. |
otel.endpoint |
OTLP/HTTP collector URL. |
OTEL_EXPORTER_OTLP_ENDPOINT |
Standard OTel env var; used when otel.endpoint is empty. Traces and metrics go to the same collector. |
otel.headers, otel.service_name, otel.insecure |
Exporter headers, service.name resource attribute, plaintext HTTP. |
Spans:
| Span | Where | Attributes |
|---|---|---|
github.request |
Outermost layer of the GitHub HTTP transport chain (one per REST/GraphQL call, including ETag replays and pacer refusals) | http.request.method, http.response.status_code, github.endpoint (templated path, e.g. /repos/{owner}/{repo}/pulls/{number}; no ids), github.repo, hive.caller (REST caller tag, unknown when untagged), hive.pacing_class (high / normal / low), github.cache_hit (304 / ETag-cache served), github.ratelimit.remaining, github.ratelimit.bucket (core, search, graphql, …), github.shed (request refused by the pacer) |
governor.eval_cycle |
One per governor eval cycle | hive.id, hive.acmm_level |
governor.enumerate_actionable |
child of eval_cycle |
items found |
governor.stage_labels |
child of eval_cycle |
hive.stage_labels.items, hive.stage_labels.writes |
governor.kick |
child of eval_cycle |
hive.kicks.assembled, hive.kicks.review_delivered; agent.kick children per dispatched agent |
governor.automerge |
Sibling of eval_cycle (the sweep runs on its own cadence) |
hive.automerge.seen, hive.automerge.merged, hive.automerge.skipped |
GitHub calls made inside those spans attach as github.request children, so a
trace answers "which stage of which cycle spent the quota".
Metrics (OTLP, exported on the SDK's periodic reader):
| Metric | Type | Labels | Meaning |
|---|---|---|---|
github.requests |
counter | caller, endpoint, status |
Every GitHub call; status is the HTTP code, shed, or error. |
github.ratelimit.remaining |
gauge | bucket |
Last X-RateLimit-Remaining seen per bucket (unauthenticated 60/hour buckets are ignored). |
github.secondary_limit_hits |
counter | — | 403/429 responses GitHub flagged as secondary/abuse limits. |
forgestate.staleness_s |
observable gauge | repo |
Seconds since each cached repo was last reconciled or patched. |
forgestate.drift |
counter | repo, kind (issue, pr) |
Entries the reconcile pass found differing between cache and API. |
webhook.event_to_patch_ms |
histogram | repo, writer (forgestate, cache_invalidate) |
Latency from webhook receipt to the cache patch being applied. |
Adaptive reconcile: forgestate.reconcile_interval_s is the base cadence. The
store stretches it from the latest core bucket observation
(forgestate.AdaptiveInterval): below 20% remaining → 2×, below 10% → 4×,
below 5% → 8×. Webhook patches keep the cache fresh in the meantime, and the
interval snaps back once the window resets. When a hive has not yet seen a
rate-limit header the base interval is used unchanged.
Top-level YAML keys accepted by config.Config:
| Block | Purpose | Notes |
|---|---|---|
project |
Managed GitHub org, repos, primary repo, AI author. | Required for normal operation. |
project.agent_issue_decision_policy |
resolve. |
resolve keeps Hive-authored issues from parking at stage/045-needs-decision for non-risk engineering questions (sequencing, naming, numbering, config shape, scope, approach A vs B); agents record Decision taken by hive: … and the relay downgrades to stage/010-actionable. escalate parks every Hive-authored --needs-decision request. Risk parks still require Risk: <category>. |
policies |
Optional git/local policy prompt source. | Hot-reloaded when configured. |
agents |
Agent definitions and behavior metadata. | Per-agent overlays may also live under data.agents_dir. |
governor |
Cadences, labels, sensing, health, budgets, inference gateways, trajectory review. | See notable fields below. |
github |
PAT or GitHub App credentials and forge URLs. | Use one auth method. |
notifications |
ntfy, Slack, and Discord webhooks. | All optional; see notifications.md. |
notifications.discord.rfc_channel_id |
empty (off). | Discord channel that receives RFC lifecycle notices (RFC issue opened, closed, or taken off hold) through notifications.discord.bot_token. Env override HIVE_DISCORD_RFC_CHANNEL_ID; see RFC lifecycle notices. |
dashboard |
Web UI port, snapshots, auth token, frame allowlist, authorized users. | auth_token can come from HIVE_DASHBOARD_TOKEN. |
agent_sandbox |
Podman-rootless sandbox launcher for hub/pod agents (pkg/sandbox). |
Opt-in and two-gate: this block's own enabled: true sandboxes nothing by itself — each agent also needs sandbox.enabled: true under agents.<name>. The dashboard's Security tab writes only this global flag, so enabling it there alone can leave every agent unconfined — but the tab now shows this: security.sandboxWarnings in GET /api/config/governor carries config.AgentSandboxGateWarnings's diagnosis (also logged at WARN at boot/reload), rendered both in the page's coherence-warnings box and inline under the toggle, naming the still-unconfined agents and the fixing key (#4918). See sandbox-isolation.md and the getting-started confinement section. |
data |
Metrics, logs, session, and agent overlay directories. | Defaults are /data/... in containers. |
knowledge |
Wiki layers, vaults, git/document sources, connectors (knowledge.connectors, see knowledge-connectors.md), primer, curator, bead synthesizer. |
Disabled unless enabled: true. |
hub |
Hub/spoke hosted-hive metadata. | Usually provisioner-owned. |
hive_id |
Stable spoke identifier. | Usually provisioner-owned. |
acmm_level |
Current ACMM pack level. | May be set by hub/dashboard. |
quality |
Optional quality-lane capabilities. | formal: true enables agent-authored Spin models only at ACMM L5/L6; see Formal verification. |
runs |
Long-running run settings: runs.external.flue, the report-only external-execution binding pilot, and runs.external.omp, the OMP workbench host behind the same adapter. |
Both default OFF and independent. enabled: true with no mode runs in shadow (persists admissions, observes, never starts external work); mode: report-only dispatches. Flue dispatches to a runtime at endpoint pinned to workflow_version and needs a build with the extwork_flue tag; OMP offers work over the contributor relay to a workbench peer declaring ext-exec/omp whose declared version matches workflow_version, and needs the extwork_omp tag. Neither host receives a repository credential, dashboard token, or publication tool; OMP (tier T3) is additionally refused any write-capable stage. See External workflow admission. |
auto_merge |
The App self-merge sweep: whether and how the Forge App merges its own CI-green PRs. | Default ON, but inert below acmm_level: 6. See App self-merge sweep below. |
issues.close_on_merge |
true. |
When a hive-authored or trusted-author PR merges and closing keywords or Hive claim metadata show it fixed an open issue, Hive comments Fixed by #<pr> (merged <sha>). Closing. Reply /reopen if the problem persists. and closes the issue as completed. Issues labelled needs-reporter-confirmation, epic, or needs-human stay open: Hive comments that the fix merged, adds hive/likely-done, and waits for /fixed or more instructions. Both comments carry <!-- hive-close-on-merge -->, Hive's already-handled marker: an issue with that comment is never closed on merge again, so /reopen after a Hive close sticks. An issue reopened after the PR merged stays open even when GitHub's closing keyword, not Hive, closed it: before closing, Hive reads the issue timeline since that PR's merge, and a reopened event by anyone but Hive (including github-actions[bot] from /reopen) skips the close and posts one comment carrying <!-- hive-reopened-after-merge pr=<pr> -->, so later sweeps skip that PR without rereading the timeline. The marker is per PR: a different PR that merges after the reopen can still close the issue. If the timeline cannot be read, Hive leaves the issue open and retries on the next sweep. |
| Issue↔PR link safeguard | Always on with issue stage and claim-label sweeps. | GitHub shows its issue-list PR icon only when a default-branch PR body contains a closing keyword. When Hive sees a Hive-authored default-branch PR covering an issue and the PR body does not say the issue stays open, Hive appends Fixes #N to that PR body. Existing Hive-authored default-branch PRs that used Refs #N but are recorded by Hive stage/claim metadata as the implementation PR are retrofitted once with the same closer and <!-- hive:issue-link-fixed -->; human-authored PR bodies are never edited. If the body says Part of #N or otherwise leaves the issue open, Hive does not add a closer; it posts one stage-entry comment on the issue with the PR link and explains that GitHub will not show the icon without a closing keyword. Non-default-branch PRs get the same issue comment. |
issues.close_on_merge_backfill_interval |
1h. |
How often Hive scans open issues carrying Hive PR-linkage signals (stage/040-covered-by-pr or the retired 🐝 covered by PR, hive/likely-done, claim/covered comments, or timeline PR references) and applies the same close-on-merge policy to PRs that merged before the current process saw them. |
issues.close_on_merge_interval |
10m. |
How often the close-on-merge ledger sweep walks every merged-PR claim in the duplicate-PR claim ledger. Passes in between (every governor eval cycle and manual rescan) only look at claims whose PR merged since the last full walk, so a fresh merge still closes its issue on the next pass without re-reading the whole ledger (#11778). Both the ledger sweep and the backfill pause while the GitHub API budget mode is not normal; Hive logs one line on each pause and resume. Env override: HIVE_CLOSE_ON_MERGE_INTERVAL (Go duration). |
issues.close_on_merge_recheck |
24h. |
How long a merged-PR claim keeps its settled close-on-merge verdict before the ledger sweep re-verifies it against the API. Once a sweep reaches a final verdict for a claim — issue closed, already closed, Hive marker present, awaiting confirmation, untrusted author, reopened by a person, or PR not merged — it stamps close_on_merge_settled_at on the claim and later sweeps skip the claim with no API call until this window elapses. Transient failures (issue, timeline or comment reads/writes that errored) never settle and retry on the next sweep as before. The marker follows the claim while the same PR claims the issue and resets when a different PR takes it over. Env override: HIVE_CLOSE_ON_MERGE_RECHECK (Go duration). |
issues.reporter_confirmation |
false. |
Legacy opt-in switch for hives that want every human-filed bug-family issue to wait for reporter verification before close. Leave false for the default close-on-merge policy; add hive: needs-confirmation to an individual issue when only that issue needs the gate. hive: reporter-confirmed and hive: close-on-merge remain accepted legacy bypasses. |
issues.sha_hold_trusted_label |
needs-sha. |
Soft label the SHA-hold sweep applies to a kind/bug without a commit SHA when the reporter's author association is in project.issue_filter.reporter_trust.trusted_associations (default OWNER/MEMBER/COLLABORATOR), instead of the hard hold (#12002). The notice is the same; the issue stays actionable. A SHA-hold hold already on such an issue is swapped for this label. Set to hold to restore the hard hold for every reporter. |
issues.lift_hive_hard_suppress |
true. |
Runs the hard-suppress reconcile (#12002): a hold or blocked label on an open issue is lifted when Hive provably applied it (newest label event is a labeled by the App bot; for hold, paired with the <!-- hive:sha-hold --> notice) and a PR cross-referencing the issue merged after it. Hive posts one note carrying <!-- hive:hard-suppress-lifted -->. Human-applied labels are never lifted. Skipped with the other unstick steps when the API budget is critical. |
issues.hard_suppress_reconcile_interval |
30m. |
Minimum time between hard-suppress reconcile passes. |
issues.rfc_existing_check.enabled |
false. |
Triage existing-coverage check (#13575): new open issues labelled rfc, enhancement or kind/feature get one comment that lists the existing settings that may already cover the request. Each setting links to Settings with the search set to its key. The comment carries <!-- hive:rfc-existing-coverage --> and is never posted twice. The check never labels or closes issues. Tuning keys are post_when_nothing_found, labels, max_matches and max_age; see Triage: does this RFC already exist?. |
runs.triage.prior_art.enabled |
true. |
Prior-art gate (#13575): before any agent claims a feature / RFC / epic issue, the scheduler searches knowledge beads, the Settings index, merged PRs (12 months), closed issues, docs and code for the issue's key terms. A human-filed issue whose best finding reaches runs.triage.prior_art.threshold (default 70) is parked at stage/017-possible-existing with one <!-- hive:prior-art-gate --> comment quoting up to max_findings (default 5) findings and the two replies (/hive decision proceed — <gap> / /hive decision exists); a clanker-filed one is parked the same way and closed already-exists after bot_close_ttl (default 72h) with no reply; a hive-filed one is never parked (findings note only). kinds (default feature, rfc, epic, enhancement) picks the gated kinds; max_parks_per_cycle (default 5) bounds one cycle. See Prior-art gate. |
contribute.help_links |
Defaults to Hive contributor docs and the Hive issue tracker. | Up to 5 {label, url} links shown on /contribute Onboarding, Operations, and printed once by relays on connect. URLs must be absolute http:// or https://; labels are plain text and length-capped. Prefer the Hive Commons Discord redirect (https://hivecommons.dev/discord) over channel URLs for contributors who may not already be server members. |
contribute.max_parallel_fixes |
2. |
Ceiling on how many stage-072 fix-pr sub-sessions one contributor's relay may run beside its primary session (#12436). The relay advertises its own count (max_parallel_fixes, from HIVE_MAX_PARALLEL_FIXES); the hub clamps it to this. A relay that advertises no parallel_sessions always has capacity 1 (preempt mode). Fixes beyond capacity queue server-side and are served before new work. Settings → Hub → Fix budget. |
contribute.min_focus_window |
30m. |
Preempt mode only: after a preempted primary task resumes, the hub does not preempt that contributor again until it has run uninterrupted for this long. Settings → Hub → Fix budget. |
variables |
Trusted variable resolver definitions. | Env-only substitution works without this block. |
classification |
Go-consumed subset of hive-project.yaml's classification: block — today only review_bots, the external review-bot logins whose inline threads on hive-authored PRs the hive addresses and resolves itself. |
Off unless review_bots.logins names a bot. The same key in hive-project.yaml is read when hive.yaml has none. See review-bot-threads.md. |
claims |
The worker-claim ledger (#8380): who is on an issue right now, ranked human > agent > contributor > external, so a person can take an issue over from a hive agent or a relay contributor and the displaced holder is told to stop. | Default ON. enabled: false turns recording, enforcement and /api/claims off. human_ttl_s 4h, agent_ttl_s 2h, contributor_ttl_s 30m, max_ttl_s 24h; the owner can also set these four from Governor Config → Features → Work intake & issue lifecycle → Issue claims (a value of 0 or Reset to default uses the default; human, agent and contributor may not exceed the max), and a saved change applies without a restart to claims made or renewed afterwards while existing claims keep their expiry (#10981); comment: false / label: false suppress the GitHub issue comment / stage/020-claimed label mirror (label: false also suppresses the stage/030-pr-in-progress start label an agent claim mirrors — formerly claimed / hive/in-progress, #11841). A governor kick only lists the issues it names (30-minute hold, no comment, not a claim); the agent claim, with its comment, is recorded on the agent's first start signal on an issue — its own hive-open-issue claim request, a hive-open-pr request naming it, or a comment with an explicit start statement (<!-- hive:start -->, "starting now", an opening "Starting …" / "Implementing …"; see the recognized forms); a report-only comment (triage, verification, gate check, status), an explicit deferral (#12387) or escalation, and any label request record no claim. never_claim_labels (default [epic], case-insensitive; [] turns it off) names umbrella/tracking labels no automated producer may claim and no claim ever stages stage/020/stage/030; the same admission gate also refuses issues parked on a person (stage/045-needs-decision, needs-spec, …), needs-human and held issues. escalate_after_claims (default 2, negative turns it off) counts those claims only: once one agent's last that-many claims on a free issue were followed by no linked PR, referencing commit, label or assignee change and no close/reopen, the issue is withheld from every kick, labelled needs-human and given one comment listing the claims, instead of being claimed again. A hive/likely-done issue an agent already started on after the merged PR (its verification) is withheld from kicks until a person comments, labels, assigns, closes or reopens it, so it is verified once rather than every cycle (#10527). reclaim_cooldown_s (default 21600 = 6h; negative turns it off) keeps a relay contributor session off an issue it released without shipping — task failed, abandoned, or its lease revoked — while other contributors may retry at once after the short global failure cooldown; before it, a relay whose environment could not run a task took the same issue back every ten minutes, flapping its claim (#13093). A relay's disconnect release (after the #7838 grace window), a revoked lease and a lease that aged out unresumed each release the issue claim on GitHub too — release marker posted, claim labels removed — so hive agents may claim the issue on the next cycle; contributor_flap_threshold (default 3; negative turns it off) disconnect releases of one issue inside contributor_flap_window_s (default 7200 = 2h) put the issue in contributor cool-off for contributor_flap_cooloff_s (default 21600 = 6h): withheld from every contributor, immediately eligible for hive agents, work detail contributor cool-off: N drops in W; and contributor_starvation_s (default 14400 = 4h; negative turns it off) is how long contributor claims may keep hive agents off an issue with no PR before the claim becomes advisory and a hive agent may claim it anyway (#13579). An issue labelled blocked (the review backlog's parking for a finding whose file exists only on an unmerged PR, or hive-open-issue comment --blocked-on-pr) is refused by the same admission gate until the tracked PR merges or closes. Driven from hivectl claim — see hivectl.md. |
removed_agents |
Persistent tombstones for deleted agents. | Dashboard/overlay-owned; do not seed casually. |
good_first_issue |
Hive-curated good first issue advertising: a 15-minute sweep scores open, unclaimed issues and gives the few that qualify GitHub's good first issue + help wanted labels and one welcome comment pointing at this hive's contributor relay. Not a hold — a hive agent starting the issue removes the label. |
Default ON; enabled: false opts out. Silent until dashboard.public_url (or a hub-assigned dashboard URL) exists. See Hive-curated good first issues. |
decisions.delegate |
"Let hive decide": the activity monitor's Decide… › Let hive decide action has the hive decide an issue or PR parked at Needs human decision itself, as the owner's delegate under the owner-delegate policy, recording it as a human decision would be (/hive decision, park lifted, queue approval on a PR) with an audit marker. |
Default ON (enabled: true); enabled: false hides the action hive-wide; repos.<owner/repo>.enabled overrides per repository. llm.enabled (default OFF) consults the owner-delegate decider model for items the rules leave at the reversible default, on the governor reviewer endpoint; llm.model overrides the model. Rails and policy in Dashboard sections → Let hive decide. |
chat.grounded_answers |
Hive Chat grounded answers: a chat message that is not a bare command (/agents stuck, /prs waiting on review, "which agent completed last?") is answered by one read-only model turn over a capped snapshot of the hive state the dashboard already holds (agents, open PRs, actionable issues, decisions, governor, alerts, version), citing the items used or saying it does not have the data. Chat never takes actions on this path; every attempt is audited as chat_grounded_answer. |
Default ON (enabled: true); enabled: false makes scoped questions answer with the command's usage instead. model overrides the governor reviewer model for the turn. See Hive Chat. |
governor.first_timer_welcome |
First-time contributor welcome: when a human whose GitHub author_association is FIRST_TIME_CONTRIBUTOR/FIRST_TIMER opens a PR, post one comment (per author per repo, marker <!-- hive:first-timer-welcome -->) that thanks them and invites them to run their own hive — profile, leaderboard, trust-tier progression, offline review-fix/rebase handling. Bots, author/clanker/author/hive PRs, drafts and known relay contributors are skipped. |
Default ON; enabled: false opts out; template_path overrides the text. Silent without a public dashboard URL. See Welcoming first-time contributors. |
sla |
Delivery SLA targets (sla.targets.start_to_pr_h, pr_to_merge_h, open_to_merge_h, hours; defaults 4 / 24 / 72) the dashboard's Throughput section colours its Delivery SLA spans against. |
Metrics are derived from the lifecycle timeline; no extra GitHub calls. Also editable from Settings → Thresholds. See Delivery SLA. |
sentinel |
Suspicious-activity alerts: flags open PRs (any author) that touch sensitive files or look like security overrides, privilege escalation, or codebase damage. | Default ON; observe-and-alert only. See Suspicious-activity alerts. |
The keys the agent runtime owns — the part of Hive being carved out as Crux (#13536) — are grouped in Go into two structs that config.Config and config.GovernorConfig embed with yaml:",inline". The grouping is a code-organisation boundary only: every key keeps the position it has always had in hive.yaml, so no file needs to change and a Save() writes the same shape back.
| Struct | Embedded in | YAML keys (unchanged position) |
|---|---|---|
AgentRuntimeConfig (pkg/config/agent_config.go) |
Config |
agents (including the agents.github_api_hourly_cap scalar and the agents.session_recycle block), backends, providers, tool_approval, agent_sandbox, turn |
AgentRuntimeGovernorConfig (pkg/config/agent_config.go) |
GovernorConfig |
governor.watchdog, governor.litellm, governor.vllm, governor.llm-d, governor.bob, governor.trajectory, governor.kick_limits, governor.gateways, governor.rotation, governor.provider_budget |
Go callers keep reading cfg.Agents, cfg.Governor.Gateways and so on through field promotion; only composite literals name the embedded struct (config.Config{AgentRuntimeConfig: config.AgentRuntimeConfig{Agents: …}}). pkg/config/testdata/agent_runtime_golden.{yaml,json} pins the parse of a representative file so the boundary cannot drift the schema.
| Field | Default / behavior | Operator note |
|---|---|---|
github.pr_detail_ttl_s |
1800 seconds. HIVE_GITHUB_PR_DETAIL_TTL overrides for tests/experiments. |
Reuses GET /pulls/{number} detail responses while the cheap PR list still reports the same head.sha and updated_at, and GitHub has resolved mergeable_state. |
github.graphql_pr_batch |
true |
Uses one paginated GraphQL query per repository scan to populate the PR detail cache and CI rollup cache, replacing most per-PR GET /pulls/{number} and check-runs reads. Set false to return to the REST-only scan path. |
github.graphql_pr_batch_page_size |
50 |
Page size for the GraphQL pullRequests(first:) batch. Values above GitHub's 100 maximum are rejected. |
governor.logging.kick_log_source |
scrollback. Also transcript or auto; unknown values mean scrollback. HIVE_KICK_LOG_SOURCE (when set to a valid value) wins over this key. |
What per-kick logs (archives and the live full log) are built from: the tmux scrollback, the backend CLI's own transcript (Copilot CLI, Claude Code), or the transcript when a reader exists else the scrollback. Editable under Settings → Logging → Kick log source; applies to the next kick without a restart, and each log's header source: line (and the kick history page) shows which was used. See agent-logging.md. |
governor.labels.automerge |
Defaults to lgtm. |
Label applied when a merger/owner queues a PR for Hive auto-merge-on-green. With Prow's commit binding (prow-github-actions v3), Hive binds lgtm by also setting the prow/lgtm commit status (needs the App's statuses: write permission); alternatively choose a distinct label such as hive/auto-merge. Distinct from the App self-merge sweep, which needs no label and no human queuer. |
project.repo_policies[].auto_merge |
effective false below L6; unset = true at L6 |
Per-repo off switch. Switching to L6 turns this on for every active repo; owners can toggle repos afterward. false lets Hive open PRs for that repo but blocks all Hive merge paths (hive-merge, App self-authored sweep, and proxy-visible direct REST/GraphQL merge attempts). The dashboard repo-card switch persists this key and takes effect without restart. |
project.repo_policies[].label_driven |
Off (unset) for every repo. | Opt-in for repos whose maintainers accept and park issues with labels and their own bots (for example needs-triage → triage/accepted). On such a repo the un-park sweep posts no "What to reply" notice and never removes needs-human, needs-decision or needs-direction on a /hive comment alone; instead a trusted triager applying an approval label releases the issue and counts as human sign-off — see Label-driven approval. Editable from the dashboard: Settings → Repos → per-repo "Label-driven approvals (Prow-style)" (write or owner role). Details in maintainer-commands.md. |
project.repo_policies[].label_driven_approval_labels |
Unset: inherits project.label_driven_approval_labels. |
Per-repo approval-label list; only read when label_driven is set. Editable from the dashboard sub-row of the repo's label-driven toggle. |
project.repo_policies[].companion_repos |
Unset (no companions). | List of sibling repositories (name in the project org, or org/name) that this repo's tooling expects checked out beside it under the agent workspace root — for example a docs/website repo a spec tool registers at <workspace>/<org>/<sibling>/…. Contributor task prompts and resident-agent kicks carry a shallow-clone plan for each companion at <workspace root>/<org>/<sibling> ($HIVE_WORKSPACE_DIR for contributor agents, $HOME/workspace for resident agents), and agents are told that a tool reporting one of those paths missing means clone it, never park the issue. Self-references and malformed entries are dropped. See running-at-level-6.md. |
project.label_driven_approval_labels |
["triage/accepted"] |
Labels whose trusted addition approves an issue on every label-driven repo without its own list. Ignored by repos without label_driven: true. Dashboard: Settings → Repos → "Default approval labels". |
project.label_driven_trust_org_members |
true |
Whether a public member of the repo's owning org who applies an approval label is trusted (Prow's own rule). Dashboard: Settings → Repos → "trust public org members". |
project.label_driven_trusted_bots |
["kubestellar-prow[bot]"] |
Bot logins whose label additions count as a trusted triager (Prow command bots relaying /triage accepted). github-actions[bot] is not in the default because it is the shared identity of every workflow in the repo, including ones triggered by untrusted events; operators whose Prow emulation runs as github-actions[bot] must add it explicitly per deployment. Dashboard: Settings → Repos → "Trusted bots". |
project.label_driven_supersede_park_actors |
[] (off) |
Automation logins (for example a lifecycle bot running as github-actions[bot]) whose park labels a later trusted acceptance supersedes on label-driven repos: Hive removes the label, says so in the release comment, and releases the issue (#13605). Park labels added by people, by unlisted bots, or by a listed bot after the acceptance still block; hold is never superseded. Per-repo override: project.repo_policies[].label_driven_supersede_park_actors. |
auto_merge.allow_unprotected_base |
Deprecated no-op. | Accepted so older configs keep loading. hive-merge now merges into any protected or unprotected branch the App can write, subject to the CI-evidence gate and GitHub's own merge rules. |
auto_merge.no_ci_ok |
Empty by default. | Explicit repo list whose zero-CI merge-request verdict may pass; failing or pending CI evidence is still enforced. |
auto_merge.human_merge_paths |
Unset (off). | Per-repo map of owner/repo to glob patterns (same syntax as intent.guardrail_path_patterns) for paths a person must merge. Applies to every merge lane (App self-merge, trusted-author, label-queued, merge-request relay) whatever the intent tier; a matching PR is held with one comment, and an incomplete changed-file list fails closed. Details in the sweep key table below. |
forgestate.enabled |
false |
Runs the per-repo ForgeState cache (#13373) and switches the automerge sweep lanes (self-authored, trusted-author, contributor lane) to it: no /pulls list per 10 s tick, passes triggered by check_suite/check_run/status/pull_request_review/pull_request webhooks for the affected PR plus a 5-minute floor pass, and a 60 s freshness-bounded read-through before every merge; a stale snapshot never merges (#13377). forgestate.reconcile_interval_s (900), freshness_bound_s (60), drift_threshold (0.05) and search_budget (20) tune the cache. |
evidence.enabled |
On by default. | The review relay writes one review evidence bundle per PR head to /data/evidence/<owner>/<repo>/<number>/<head>.json when it records a verdict or posts a review; the sentinel sweep adds its findings to the flagged head's bundle; and every merge path adds the head's CI check-run summary, the human approvals and label changes, and the merge event. Owners and mergers read bundles through GET /api/review/evidence and GET /api/review/evidence/list (see the API reference). Set false to stop writing new bundles; existing ones are left in place. |
evidence.signing_key_file |
Unset (bundles unsigned). | Path to an Ed25519 private key (32-byte seed or 64-byte key, hex or base64). When set and readable, bundles are signed. Dashboard/API readers require a valid signature under its public half and fail closed when the configured key is unavailable; unsigned mode verifies only the bundle content hash. Writers still record "signed": false when the key is empty, absent or unusable and log a warning for an unusable key, so an evidence write failure never blocks review or merge work. Keep the key off shared hosts. |
evidence.complexity.enabled |
On by default (when evidence.enabled is on). |
Once a month, for each managed repo, the hive downloads the default-branch tarball through the GitHub App and writes one Go cyclomatic-complexity snapshot to /data/evidence/<owner>/<repo>/complexity/<YYYY-MM>.json (see Monthly complexity snapshot). Report-only: it never affects merge eligibility. Cheap by design — one archive download and an AST pass per repo per month, no module downloads and no compile. An existing month is never rewritten; the hive re-checks hourly and retries a failed repo on the next check. The last 24 months are kept per repo. Set false to stop writing snapshots; existing ones stay. |
evidence.complexity.static_analysis |
false. |
Also run go vet and (when installed) staticcheck on the snapshot checkout and record finding counts per tool. Off by default because it compiles each repo and downloads its modules, which is not cheap on a spoke with many or large repos. |
review.all_authors |
Deprecated no-op; accepted and logged once. | Every open PR is reviewed regardless of author — hive agent, human, clanker, dependabot or any other bot — whenever review is on (require_approval and fan_out); there is no author-class opt-out below that switch (#12974). The knob used to be required for human PRs. It never lets an agent push to those PRs (see the next row). Remove it from hive.yaml. |
review.fix_human_prs |
Retained for compatibility; treated as push-disabled. | No longer lets review-fix kicks push commits onto PRs whose login author is not a hive agent/App, even when the PR is hive-attributed (#13124); the one exception is a PR the stage reconciler adopted in place (stage/074-adopted-by-hive). A changes_requested verdict on such a PR stops at the published review, with the proposed fix as a suggestion or patch block in the comment, and the refusal is audited as review_fix_withheld with the setting's actual value. When the setting is on, the review-thread reconciler still answers review-bot threads on those PRs, by suggestion only, never by checkout or push (#12897). Existing configs may still store true for UI/audit continuity, but DCO squash attribution means Hive cannot add a compliant follow-up commit without forging the PR author's sign-off. See review-swarm.md. |
review.severity.block_at |
Unset; owner-only. | Lowest finding priority that blocks merge: P1 (ship fast, P0–P1 block), P2 (strict, P0–P2 block) or P3 (everything blocks). Also drives classification.review_bots.min_priority when that key is not set in hive.yaml or hive-project.yaml. Any other value fails load naming the field. Features -> Review gate -> Severity & backlog. See review-swarm.md. |
review.severity.comment_below / backlog_below |
true / true; owner-only. |
Whether findings below the blocking line are posted as non-blocking comments and filed to review.backlog. |
review.backlog.destination |
github_issue; owner-only. |
github_issue, github_project, linear or jira. The last three need the matching governor.work_source.type (github_projects, linear, jira); on a mismatch the hive falls back to github_issue. Destination fields: project_column_id (required for github_project), linear_state, jira_status. |
review.backlog.labels / max_per_pr_per_day |
[agent/reviewer] / 10; owner-only. from-review, origin/review, and origin/reviewer are accepted and mapped to agent/reviewer. |
Labels on every backlog item and the per-PR-per-day cap (negative fails load; 0 = default). Reviewer is an agent lane, so agent/reviewer is the routing/ownership/provenance label. Non-lane hive issue filers continue to use origin/selfreport, origin/governor, origin/dashboard, origin/forge, or origin/convergence; the one-time startup migration (/data/origin-label-migration-v4.done) removes redundant lane-origin labels and backfills only non-lane origins. |
review.reviewers.autoscale.* |
enabled: false; when on: min: 1, max: 4 (≤ the replicas cap 5), target_backlog_per_reviewer: 2, scale_up_cycles: 2, scale_down_cycles: 5, cooldown: 5m, cpu_per_reviewer: 1.0, mem_per_reviewer_mb: 2048, agent unset (first role: reviewer agent, else reviewer_agents[0]); owner-only. |
Sizes the reviewer lane to its backlog (#14685): adds a reviewer-N replica after scale_up_cycles cycles with more PRs waiting for a free reviewer than target_backlog_per_reviewer per reviewer (or a wait older than the review-pending stage dwell), drains the top replica after scale_down_cycles idle cycles and retires it only when its turn ends. Scale-ups are refused by named guards (at-max, cooldown, saturated kicks, provider-budget, provider-headroom, max-parallel-reviews, cgroup cpu / memory). Decisions log as reviewer autoscale: 2→3 reason=…; state is in /api/status.reviewerAutoscale. See review-swarm.md. |
review.contributor_prs.base_sync |
Off by default; owner-only. | Lets PR follow-up use GitHub's update-branch API on hive-authored lane PRs whose head is in a contributor fork, whose mergeable_state is behind/dirty, and whose author enabled "allow edits by maintainers". behind PRs get a GitHub-authored merge commit; dirty/422 falls back to the hive-base-moved repair note and kick. This requires an agent/proxy mode with ISSUES_PRS_MERGE because PUT /pulls/{n}/update-branch is merge-scoped. It does not rewrite history and does not add a human/bot-authored DCO commit; the repository's "Sign-off survives the squash" check examines the PR's own commits, not GitHub's update-branch merge commit. |
governor.advisory.post_retry_backoff_s |
3600 seconds. HIVE_ADVISORY_POST_RETRY_BACKOFF_S overrides the default when the key is unset; a negative value disables the pause. |
How long the advisory digest write is paused after GitHub refuses it with 403 Resource not accessible by integration. That refusal is structural (foreign-authored comment, repo dropped from the installation, or repo transferred to another owner), so per-cycle retries only burn API quota. The failed edit first falls back to creating a fresh App-authored comment; if the create is refused too, the error is recorded for the hub health row — naming the new owner/repo when the repository has moved — and attempts pause for this window. See advisory.md. |
governor.trajectory.enabled |
Defaults to enabled. | The lane no-ops until a reviewer endpoint and model resolve from governor.trajectory or governor.litellm. |
governor.kick_limits.max_issues / max_prs |
100 / 50 |
Caps on every issue list and every PR list (actionable, stale drafts, merge-eligible, CI-failing) rendered into a kick prompt; a cut list ends with an explicit "… and N more" line. An absent key or a negative value means the default; an explicit 0 opts out of the cap entirely (KickListUnlimited — every item is listed), and anything above 500 is pinned to 500. Editable without touching this file under Settings → Repos → Kick prompt list caps. Truncation drops the newest, lowest-priority items: issues are listed oldest first, and PRs in review-priority order (fixes, then refactors/docs, then tests, oldest first within each class), so lowering a cap never hides the most urgent work. Raise max_prs only if your agents actually work more than 50 PRs per turn; the tail of a long list costs tokens and delivery time on every kick without changing behaviour (#7368). |
project.agents_md_max_bytes |
8192; values under 2048 are raised to 2048. |
Caps the repo AGENTS.md block prepended to a kick. Whole sections (split at #/## headings; each resolved skill is its own unit) are kept or dropped, never clipped mid-rule; when any are dropped the kick carries a one-line ⚠️ AGENTS.md exceeded N bytes; … — read AGENTS.md … warning naming them and the scheduler logs it. See agents-md.md. |
project.style |
unset (built-in text applies) | Hive-wide house style: a short how-to-change rule list prepended to every kick on every backend, directly under the header. A managed repo's root HIVE_STYLE.md (read from project.checkouts_dir) overrides it; precedence repo > hive > built-in. Inline text wins over project.style_file. See house-style.md. |
project.style_file |
unset | Path on the hive host holding the hive-wide house style; used when project.style is empty. |
project.style_max_bytes |
4096; values under 512 are raised to 512. |
Caps each house-style candidate. An oversize candidate is skipped whole (not clipped) and the next source applies; the skip is audited as house_style_skip. The repo file is also run through ioscan at the issue-body policy and refused whole on a block. |
dashboard.snapshot_frame_ancestors |
Empty list means CSP frame-ancestors 'none'. |
Entries must be exact https:// origins; paths, wildcards, credentials, query, and fragments are rejected. |
dashboard.authorized_users |
Empty means no per-user direct-route allowlist. | Entries can be user or user:role; roles are read, read-write, merger, owner. |
dashboard.public_url |
Empty means OAuth redirect URIs (Linear agent install, OpenRouter funding) fall back to hub.dashboard_url, then the request's forwarded/Host origin. |
Set to this dashboard's externally reachable origin (https://hive.example.com, no path/query) on a standalone hive whose callback is published on a different hostname or whose ingress rewrites Host. Must be an absolute http(s):// origin; a trailing slash is trimmed and anything else fails config load. See linear-agent.md. |
dashboard.experiment_lab |
Off by default. | Hides the Experiment Lab (formerly Strategy Lab) while that surface is being reworked. Set dashboard.experiment_lab: true to show the Experiment Lab section, sidebar nav entry, and Nous status controls. |
dashboard.strategy_lab |
Off by default. Deprecated alias of dashboard.experiment_lab, kept for one release (#11695). |
true still enables the Experiment Lab and logs a one-line deprecation at config load; the value is folded into dashboard.experiment_lab so the next config save writes the documented key. |
hub.contribute_announcement |
Empty/off by default. | Operator notice for contributors. Set from Governor → Hub or /contribute Management (owner/read-write): text is server-sanitised plain text capped at 500 characters and rendered only through text-safe sinks (empty clears), level is info/warning, expires_at is optional RFC3339, and the server rotates id whenever text changes so dismissed Operations/Profile banners reappear. Active notices show on /contribute Operations/Profile, above the Onboarding command block, and in connected relay terminals. |
hub.contribute_repo_filters |
Empty map means repos inherit the hive-wide contributor title, author, and label filters exactly as before. | Optional full owner/repo keys that narrow one repository's /contribute queue without widening the hive. Hive-wide skip labels and hive-wide filters always run first; a repo filter can only add deny matches or require an allow-mode title/author/label match for that repo. Edit from /contribute → Management → Repos for Contribute → Filters or Governor → Hub. Operations lists configured overrides and withheld diagnostics name repo matches, e.g. Repo filter label "2-discussing" denied for projectbluefin/common. |
hub.contribute_wall_enabled |
Off by default. | Opts the hive into the public contributor wall on /contribute Operations. The Management tab and Governor Hub settings mirror this switch. When off, the panel is hidden and write endpoints refuse posts. |
hub.contribute_wall_retention_days |
0 means the dashboard default (90 days). |
Bounds wall posts stored on the hub data volume next to contributor profiles. Older posts are pruned on load/write; use a positive number to change the retention window. |
hub.nps_enabled |
Unset means on for hosted spokes (hub.hive_type: hosted) and off for every other install. HIVE_NPS_ENABLED overrides it. |
Enables the dashboard NPS feedback prompt, whose responses are forwarded to the hub over the spoke's authenticated hub link, without the user's identity, and are visible only to hub admins. A hive with no hub link sends nothing unless a relay is configured (below). See nps.md. |
hub.nps_relay_url |
Empty (relay disabled). HIVE_NPS_RELAY_URL overrides it. |
Base URL of the NPS relay. A standalone spoke (NPS enabled, no hub link) self-registers a generated key there and posts signed responses to it (no token to configure); a hub pulls them from it. https only, except http to a loopback host. See nps.md. |
hub.nps_relay_pull_secret |
Empty. HIVE_NPS_RELAY_PULL_SECRET overrides it. |
Secret, hub only. The credential the hub uses to pull and ack relay entries. Prefer the env var. |
hub.snapshot_url |
Empty (unset): no snapshot URL is advertised in the hub heartbeat. | Public URL of this hive's snapshot page, sent to the hub in the heartbeat (normally written by hosted-hive provisioning). Must be an absolute http(s):// URL with an optional path; credentials are rejected and a trailing slash is trimmed. A non-http(s) scheme, missing host, or credentials fails config load and is a 400 from PUT /api/config/governor/hub (nothing from that save is applied). Query strings and fragments are also a 400 from the dashboard, but at config load a legacy value carrying one is preserved verbatim (no trailing-slash trimming) with a startup warning so existing hives keep booting; remove the suffix before editing the field through the dashboard. |
knowledge.connectors[] |
Empty (no connectors). | Scheduled sync of external knowledge into vault facts; types git, document, github-wiki, confluence, notion. Credentials only via auth.env / auth.file (inline secrets fail validation). Facts land in /data/knowledge/connectors/<layer>; status at GET /api/knowledge/connectors, owner-only POST /api/knowledge/connectors/{name}/sync. See knowledge-connectors.md. |
knowledge.connectors[].scope (confluence) |
deployment auto-detects Cloud for *.atlassian.net; max_pages 2000; full_sync_every 10. |
base_url plus spaces and/or root_page_ids; Cloud needs email (Basic auth with the API token), Data Center uses the token as a bearer PAT. Give the account read-only space permission. |
knowledge.connectors[].scope (notion) |
All pages shared with the integration; max_pages 2000; full_sync_every 10. |
Optional root_page_ids / database_ids; include_archived fetches bodies of archived pages. Pages must be shared with the internal integration (••• → Connections). Requests are spaced 350 ms apart. |
knowledge.publish |
Unset (connector empty): publishing off. |
One-way mirror of curator-promoted facts into one connector target: connector (a knowledge.connectors name whose type can publish), layers (project/org/community; personal is never published), root, optional include_types, dry_run, propose_via. Facts are read from each layer's knowledge.layers[].path. Started at boot (a bad connector logs knowledge publish mirror disabled and leaves publishing off); state in /data/knowledge/connector-state/_publish. Status in the publish object of GET /api/knowledge/connectors; owner-only POST /api/knowledge/publish/sync. See knowledge-connectors.md. |
knowledge.bead_synthesizer.publish |
Off (enabled: false). |
Publishes synthesized facts into a repository through one PR on hive/bead-synth-knowledge: enabled, repo (empty = project.primary_repo), path (default docs/knowledge/; .hive/knowledge/ is the alternative; relative, no ..), base_branch (empty = repo default branch), mode (pr default or commit), cadence (per_run default or daily). ACMM L<=4 hives publish via PR only; commit is warned about and still opens a PR. See knowledge-curator.md. |
variables.security.* |
Deny by default. | allow_exec, allow_http, and GitHub prompt-source allowlists are honored only from the trusted seed, not dashboard overlays. |
agents.<name>.prompt_source (size cap) |
64 KiB (promptsrc.DefaultMaxPromptBytes); HIVE_PROMPT_SOURCE_MAX_BYTES overrides it. |
A fetched kick template over the cap is rejected whole, not truncated (#12130): the kick uses the last successfully fetched copy if there is one, otherwise the ordinary kick_template → pack → convention → embedded-default chain, and the scheduler logs prompt from GitHub exceeds size cap; REJECTED whole (not truncated), falling back with repo, path, size_bytes and cap_bytes. Saving an agent whose source is over the cap fails in the dashboard with the same sizes rather than baking a cut-off policy. A half-policy is a policy with its closing rules missing; no remote policy is the safer failure. Every shipped template fits with >2× headroom (TestShippedTemplatesFitUnderCap). |
data.claude_sessions_dir |
/data/home/.claude/projects |
Where the dashboard reads Claude Code session JSONL for per-agent token/cost accounting. Point it at the agents' real session directory if you relocate HOME. |
data.copilot_sessions_dir |
/data/home/.copilot/session-state |
Same, for the Copilot CLI backend's session state. |
Opt-in per repository (project.repo_policies[].label_driven: true,
#11313). With it unset nothing below applies and the hive
behaves exactly as before. With it set:
- The un-park sweep treats a trusted addition of an approval label (default
triage/accepted) like/hive approve: it removes the park labels Hive itself added (needs-human,needs-decision,needs-direction) and addsapproved-direction, without requiring a/hivecomment. Park labels a person added, andhold/do-not-merge, are left alone and keep the issue parked; Hive posts one "accepted, but still held by …" note per acceptance. A park label added by a lifecycle bot listed inlabel_driven_supersede_park_actorsbefore the acceptance is removed too (#13605); the list is empty by default. pr_self_authorizationcounts that label as human sign-off, the same way it countsapproved-direction./hive approveand/hive decisionon such a repo may also be issued by collaborators with the GitHub triage role (read viarole_name, so custom roles based on triage count too); elsewhere write+ is still required.
Who is trusted to apply the label. Only the actor of the latest
labeled timeline event for that label is checked, in this order: a bot in
label_driven_trusted_bots (default kubestellar-prow[bot] only); a collaborator whose role_name is triage,
write, maintain or admin; a public member of the owning org when
label_driven_trust_org_members is on. Any other actor, a label Hive applied
itself, or a timeline that cannot be read → not approved (fail closed, logged
once). Removing and re-adding the label re-evaluates the newest event only.
Webhook. An issues labeled/unlabeled webhook marks the issue dirty so
the next sweep runs within ~30 s instead of the 5-minute poll; polling remains
the fallback. This dirty-mark, and the check that once-only reply markers were
authored by the hive's own identity, apply to every repo and are the only
behaviour changes for repos that did not opt in.
The PR-request watcher opens the requested PR after its safety gates pass. It no longer runs hub-side changelog, DCO, docs, or Go-test preflights and has no hub-side PR preflight configuration. CI is the sole verdict for repository rules; agents fix red checks on the open PR and push follow-up commits so the failure and repair stay visible in CI history.
The issue-request watcher (the hive-open-issue relay) classifies each failure
before deciding whether to retry. Permanent errors — a --repo that is not
owner/name, a documentation placeholder such as owner/repo, a repo outside
the hive's primary + fleet set, GitHub 404/410/422/401, or a 403
that is not a rate limit — are quarantined on the first tick. Transient errors
(5xx, network, rate limits, an effect-ledger entry left Unknown) back off
exponentially; an Unknown ledger entry is reconciled against the repo's open
issues and expired after one hour if the lookup keeps failing, and a kind: issue create is given up after 3 consecutive reconciliation failures or 8
consecutive identical errors rather than the general 24-hour horizon.
When a request is given up the watcher renames it
/var/run/hive-metrics/issue-requests/<agent>-<nonce>.json.failed, writes
<agent>-<nonce>.result.json with ok: false and the final error, and logs
one ERROR — issue-request watcher: request cannot succeed, quarantined as .failed — naming the path, agent, repo and reason. Malformed requests land as
.bad, authorization refusals as .denied. Delete a .failed file once
understood, or rename it back to .json to retry after fixing the cause. A
pile of .failed files from one agent with repo: owner/repo means that
agent is smoke-testing the relay live: point it at hive-open-issue --dry-run.
See hive-open-issue.md.
For runtime precedence and provenance, see config-layering.md.
Three different mechanisms merge PRs automatically, and they share the word "automerge" without sharing much configuration:
- The human queue — a merger/owner applies the
governor.labels.automergelabel (defaultlgtm) and Hive squash-merges the PR once CI is green. A human decision starts it. See contributor-trust-and-roles.md. - The App self-merge sweep (
SweepSelfAuthoredAutoMerges,src/pkg/github/automerge/automerge_sweep.go) — a background loop that merges the App's own open PRs with no human queue-approval at all. This is what the top-levelauto_merge:block controls. - The merge-request watcher (
hive-merge) — agents request merges through a result-file protocol and the watcher verifies CI evidence itself before merging. It readsauto_merge.no_ci_okfor repos with no CI by design; the olderallow_unprotected_basekey is now accepted but ignored. See hive-merge.md.
The self-merge sweep exists because Prow structurally forbids self-approval: a
PR the Forge App itself opens can never collect the lgtm+approved labels
tide requires, since nobody but the App authored it and the App cannot review
its own work. The sweep merges such PRs directly over the GitHub REST API
(squash), bypassing tide entirely.
Neither the self-merge sweep nor the trusted-bot lane needs Prow's lgtm /
approved labels: tide is a status GitHub reports but branch protection does
not require, so the sweep merges once the required contexts are green.
Held and never-started required checks. A required context can be missing
from a PR head for reasons polling cannot cure. When the head was pushed by
github-actions[bot] (for example a GITHUB_TOKEN autofix commit onto a
Dependabot branch), GitHub may create the pull_request runs and hold them
for approval (action_required) under the repository or organization workflow
approval policy; the required contexts never appear and the PR sits at
required-check-missing:<ctx> or required-check-pending:<ctx> indefinitely
(#11552). For the App self-merge, trusted-bot, and
registry-trusted clanker lanes only, the sweep now inspects the head's workflow
runs when a required check is missing or pending:
- held runs whose head repository is the base repository cause Hive to post one
/ok-to-testissue comment per head and reportworkflow-approval-required:<ctx>. The repository-local, self-contained.github/workflows/hive-ok-to-test.ymllistens for that comment and approves the held runs with the repositoryGITHUB_TOKEN(not the Hive App token). It references no reusable workflow, so it also works under "allow select actions" policies and on GHES. No Prow and no Hive App Actions-write permission are required. - if the held runs remain
action_requiredafter the grace period, Hive reportsworkflow-approval-required:missing-workflow:<ctx>so the attention panel and stage entry name the likely missing workflow, and the 085 entry comment carries the full workflow rendered for that hive's own App login. Render it withhivectl ok-to-test-template(--app-login <app>[bot]to override the login read from/api/config) or copysrc/docs/templates/hive-ok-to-test.yml. - a head with no
pull_requestruns at all that is older than 30 minutes is reported asrequired-check-never-started:<ctx>: GitHub never scheduled the workflows (seen on #11553), and only a new push, a close/reopen, or@dependabot rebasewill create them. - workflow-approval and never-started holds are logged once per PR head as
automerge hold; later ticks only count the PR in the skip stats instead of emittingautomerge skipevery tick. - fork heads, sentinel-alert PRs, and human-authored lanes are never touched; a
first-time contributor's held run is still a person's decision — with one
exception: a contributor-lane PR whose author the contributor registry
admitted at a
require_tierstier has its held runs approved once per head by the App (auto_merge.contributor_lane.approve_held_runs_for_registry_trusted, defaulttrue; needs Actions: Read and write). The operator already trusts that author to merge, so holding their CI is pure drag (hivecommons/docs#481).
Per-repo project.repo_policies[].auto_merge: false is stronger than the
hive-wide L6 setting: the merge relay refuses the repo, the self-authored sweep
skips it before listing/merging, and the proxy returns 403 for direct
REST/GraphQL merge attempts that name the repo. Existing hold labels (including
hive-pause/<hive-id>) remain in place because disabled repos are never
advanced into the merge path.
| Key | Default | Meaning |
|---|---|---|
auto_merge.self_authored |
on when unset | The only off switch. false disables the sweep and App-authored PRs fall back to fully manual merges. |
auto_merge.max_merges |
3 (DefaultAutoMergeSweepMaxMerges) when 0/unset |
Caps merges per sweep pass. |
auto_merge.min_head_age |
3m |
Minimum PR head age before automerge trusts an unknown required-check set. A fresh head pushed more recently than this waits with pending: head pushed ... ago (< min_head_age) unless auto_merge.required_checks is declared and every required check is complete and successful. Head age is measured from the head commit's committer date (never earlier than the PR's creation), not the PR's updated_at, so label and comment activity — including the hive's own — does not restart it. |
auto_merge.required_checks |
unset | Default operator-declared status-check contexts / check-run names (e.g. ["build-gate"]) that the sweep's green gate requires on repos without a per-repo override. See below. |
auto_merge.repos.<repo>.required_checks |
inherits default | Per-repo override for heterogeneous hives. <repo> may be the bare name from project.repos or owner/repo; an explicit empty list declares that GitHub has no required contexts, while Hive still requires positive CI evidence from at least one real successful check-run before merge. |
auto_merge.allow_unprotected_base |
deprecated no-op | Accepted for compatibility only. hive-merge no longer refuses solely because a base branch has no GitHub branch protection; it may merge into any branch the App can write after positive CI evidence. |
auto_merge.no_ci_ok |
unset (refuse) | Merge-request watcher key, not a sweep key. Per-repo opt-in that downgrades only the "unverified" CI verdict (zero statuses, check runs, and workflow runs) to green, for adopted repos with no CI by design. Red and pending verdicts are never downgraded (#6281). See hive-merge.md. |
auto_merge.human_merge_paths |
unset | Per-repo map of owner/repo to glob patterns (same syntax as intent.guardrail_path_patterns) for paths a person must merge, e.g. Danathar/goodreads-mcp: [".claude/settings.json", ".claude/hooks/**"]. Every App merge path honors it: the automerge sweep lanes (App self-merge, trusted-author, label-queued) and the agent merge-request relay refuse a PR that touches a listed path, add hold, and post one <!-- hive-human-merge-path --> comment naming the path(s); a person must merge it. If the changed-file list cannot be fetched completely for a configured repo, the merge is withheld (#11038, #11039). |
auto_merge.require_current_base |
unset (off) | Repo list (owner/repo) whose App self-merge, trusted-author and contributor lanes only merge a head that contains the current base tip. A green head tested against an older base is updated with GitHub update-branch (a merge commit; no author commit is rewritten) and merges once CI is green on the new head, so a semantic conflict with work merged since turns that PR red instead of breaking the base (#13634). Costs one compare call per merge candidate. |
auto_merge.merge_forward_trusted_human |
unset (off) | Repo list (owner/repo) where a role/trusted + author/human PR on a same-repo branch that GitHub reports as conflicting (dirty) gets the base merged into its head as one App-authored, signed-off merge commit via the GitHub merges API (no human commit is rewritten), then merges once CI is green on the new head. A real conflict pushes nothing and posts one <!-- hive:merge-forward-conflict head=… --> note naming the files both sides changed. Fork heads are never written. Attempted once per head (#13636). |
auto_merge.squash_only |
unset (no repo) | Repo list (owner/repo) whose branch policy forbids merge commits. Elsewhere, a role/trusted PR whose head mixes the contributor's commits with App-linked, App-signed ones (the hive's own in-place adoption fixes), every commit self-signed, is labelled merge-method/merge and merged with a merge commit instead of parking at stage/085 as adoption-stalled:hive-signoff-attribution; a listed repo keeps squash-only policy and re-homes an in-place adoption to an App-authored superseder instead (#13975). |
auto_merge.credit_issue_authors |
true |
Append Co-authored-by: <login> <id+login@users.noreply.github.com> for the human authors of the issues a PR closes (`Fixes |
auto_merge.bot_takeover.enabled |
true |
When github-actions[bot] is in auto_merge.trusted_bot_authors, re-author its same-repo PRs whose required pull_request checks can never run (the bot opened them with a workflow GITHUB_TOKEN). After the head is older than both min_head_age and 30 minutes with no pull_request run, the App pushes the same change as one signed-off commit to hive/takeover-<n>, opens a PR with the original title/body plus Supersedes #<n>, comments Superseded by #N on the bot PR and closes it. One attempt per bot head SHA. See hive-merge.md. |
auto_merge.trusted_authors.enabled |
false |
Enables the opt-in human-authored PR tier: Hive may merge a green PR only when the author already holds the required hive role and, by default, GitHub push/maintain/admin permission on that repo. |
auto_merge.trusted_authors.repos |
empty = all watched repos | Optional repo allow-list for the trusted-author tier. The dashboard renders this as a watched-repo multi-select; selecting nothing preserves the all-repos default. |
auto_merge.trusted_authors.require_role |
merger |
Minimum dashboard.authorized_users role the PR author must hold (merger or owner). |
auto_merge.trusted_authors.require_github_permission |
true |
Also require GitHub to report author write access; API errors fail closed. Fork PRs still need this check even if the setting is false. |
auto_merge.trusted_authors.exclude_labels |
hold, do-not-merge, needs-human |
Labels that keep a PR out of trusted-author auto-merge. |
auto_merge.contributor_lane.enabled |
true |
Enables the contributor-lane tier: Hive merges a green, soaked, reviewer-approved PR whose author holds a require_tiers tier in the contributor registry (registry_tiers_sufficient), or whose linked issue carries this hive's App-authored hive:claim kind=contributor marker naming the PR author with a tier= in require_tiers and verified=true. Grants unattended merge only to contributors an operator granted such a tier and whose GitHub login was proven; set false to make every contributor PR wait for a human /lgtm + /approve. See hive-merge.md. |
auto_merge.contributor_lane.soak_minutes |
30 |
How long the current head must stay green before the tier merges it; a new push restarts the soak. 0/unset means the default (30); the shortest real soak is 1. |
auto_merge.contributor_lane.require_reviewer_verdict |
true |
Require an approving aggregate reviewer verdict for the current head. Independently of this key, a hive with no reviewer agent never merges through the tier (contributor-lane:no-reviewer). |
auto_merge.contributor_lane.require_tiers |
trusted, merger |
Contributor trust tiers whose verified claims the tier may merge. Only operator-granted tiers (trusted, merger, advisor) are accepted; newcomer and contributor are rejected at validation. |
auto_merge.contributor_lane.registry_tiers_sufficient |
true |
Admit a PR whose author's contributor-registry profile carries a require_tiers tier without a linked issue or hive:claim marker (logged as contributor-lane:registry-trusted). All other gates still apply. false requires the claim marker for every contributor. |
auto_merge.contributor_lane.auto_promotion_scope |
hive |
Where the clean-merged-PR ratchet (auto_merge.contributor_tier.auto_promote_after_merged) counts an author's merges: hive counts across every repository the hive manages (one search/issues query finds the repos holding the author's merges; the PR's own repo is always walked first), repo counts only the PR's repository. Revert and sentinel-alert evidence blocks promotion in either scope. |
auto_merge.contributor_tier.auto_promote_after_merged |
5 |
Clean merged PRs (no revert, no sentinel label, not undone by a later merged revert) after which the contributor lane treats an author as trusted without an operator grant, unless the registry marks them revoked. Negative disables the ratchet. |
auto_merge.contributor_lane.approve_held_runs_for_registry_trusted |
true |
When the registry path admitted the PR author and the lane stops at required-check-missing:<ctx>, approve the head's action_required workflow runs once (POST /repos/{owner}/{repo}/actions/runs/{id}/approve, fork heads included), post one hive:contributor-held-runs-approved audit comment and log contributor-lane approved held runs. Never acts for claim-marker, auto-promoted or association admissions, sentinel/exclude-labelled PRs, or diffs touching .github/workflows/**. Needs the App's Actions: Read and write permission. See hive-merge.md. |
auto_merge.contributor_lane.owner_approval_releases_workflow_fence |
true |
A hive owner / trusted merger's explicit approval on the current head — a GitHub APPROVED review on that commit, or a dashboard /hive decision approve bound to it — releases the contributor lane's contributor-lane:workflow-path fence, so the lane merges a PR editing .github/workflows/** / .github/actions/** itself once CI, soak and the other gates are clear. Logs workflow-path fence released by owner approval, leaves one hive:workflow-approval audit comment per head; approval on an older head does not count. An unmarked approval binds to the PR updated_at pinned on the hive's first sweep of the head, not to the push, so one typed between the push and that sweep is judged stale and must be typed again (#14754). Needs the App's Workflows: Read and write; without it the merge is refused, the PR is marked hive:unauthorized (merge-workflow-path) and parks at 090 for a person's click. false keeps every workflow-path PR a person's merge (#14664). See hive-merge.md. |
auto_merge.contributor_lane.repos |
empty = all watched repos | Optional repo allow-list for the contributor-lane tier (bare name or owner/repo). |
auto_merge.contributor_lane.exclude_labels |
hold, do-not-merge, needs-human, blocked, needs-rebase |
Labels that keep a PR out of contributor-lane auto-merge (do-not-merge/* always excluded). |
Owners can edit auto_merge.trusted_authors.* and auto_merge.contributor_lane.*
from Settings → Features → Auto-merge. The dashboard writes the same
owner-only /api/config/auto-merge overlay as the other auto-merge controls, so
hosted spoke changes survive restarts without editing hive.yaml.
Actionable merge failures surface as dashboard system alerts, deduplicated by repo+reason and cleared by the next successful merge in that repo. Alerts name the operator action: grant/install the App with Contents and Pull requests write, approve fork PR workflow runs or relax the repo's fork-workflow approval setting, adjust review/ruleset bypass or approve the PR, make a missing required check report, or enable/use an allowed merge method. Conflicts go back to the agent fix loop, and rate limits do not alert.
The ACMM gate. self_authored: true (or unset) is necessary but not
sufficient: the sweep only starts when the hive's acmm_level is 6 or
higher (config.SelfMergeMinACMMLevel). An unset acmm_level fails closed
— a hive never gets self-merge by accident; the boot log records
self-authored auto-merge sweep disabled: acmm_level below minimum (or auto_merge.self_authored is off) when either condition blocks it. This
matches the ACMM policy matrix: below L6 all agent
PRs are hold-gated and nothing merges its own work. Runtime ACMM level changes
that cross this gate restart the request relay generation, so the sweep starts
or stops without a pod restart. Promotion into L6 does not release existing
level-applied holds; use release_level_holds: true on PUT /api/packs/level
for a deliberate one-off release, otherwise a human must remove hold. The
dashboard's L6 promotion notice lists the currently held App-authored PRs and
the watched repositories' auto-merge toggles; it is informational only.
Eligibility per PR. The sweep only ever considers open, non-draft PRs authored by the App bot login itself (re-verified per PR, not just at listing time — it never touches anyone else's PRs), that GitHub reports mergeable, and whose head commit is green on the required checks. The head SHA is re-fetched and re-verified at the merge step, so a push between evaluation and merge is never squashed unchecked.
Why required_checks exists. Asking GitHub which checks a branch actually
requires (GetRequiredStatusChecks) needs the administration:read scope,
which the Hive GitHub App does not hold. Without a config-declared list the
sweep falls back to that API, then to the branch and ruleset payloads, and
finally to the operator-declared list. When the actual set is unknown, Hive
relies on GitHub's merge endpoint for required-check enforcement while ignoring
known metadata contexts and requiring real CI evidence locally. Declaring the
branch's real required set removes ambiguity. On hives that watch multiple
repositories, keep auto_merge.required_checks only as a default and put
repo-specific gates under auto_merge.repos:
auto_merge:
required_checks: [build-gate] # default for repos without an override
repos:
console:
required_checks: [build-gate]
console-marketplace:
required_checks: [static-validation, card-quality-gate, validate]
homebrew-tap:
required_checks: [brew-ci gate, unit tests + drift check]
docs:
required_checks: []Repo keys may be bare names from project.repos or fully qualified
owner/repo names. If the configured list names a check that has never
reported on the PR head and GitHub's branch-protection/branch/ruleset fallback
chain returns a known actual set that does not include it, Hive logs a once per
repo warning (automerge required_checks override does not match repo protection) and uses the actual set. If the actual set is unknown, Hive keeps
waiting on the configured list rather than silently weakening the gate.
When the branch reports no required checks (nothing is enforced server-side, so
a completed failing check-run or status on the head still blocks), or when the
required-check source remains unknown after the protection/branch/ruleset/config chain, Hive does not
let metadata-only contexts wedge trusted-author or self-authored merges. Known
merge/deploy metadata such as Prow tide, probot DCO when the lowercase prow
dco context has succeeded, and the existing ignorable allowlist are ignored
for the local status gate; when the source is unknown the merge endpoint still
enforces any server-side branch rule. Hive still fails closed with ci-unverified unless at least one
real, non-meta check-run has succeeded on the head SHA, so an unprotected branch
with only tide/DCO and no CI does not merge. The sweep logs the carve-out once
per PR as automerge ignoring meta contexts on unprotected base.
When required_checks is unset and GitHub's branch-protection API is not
available, the sweep also waits for every non-meta, non-ignorable PR-context
check-run name expected for the PR to appear on the new head. The reference is
the PR's previously evaluated head when known, otherwise the head SHA of the
most recently merged PR into the same base branch; base-branch commits are not
used because they include push-only workflows. Check-runs whose
pull_requests list is empty are excluded from the expected set. This avoids
the short post-push window where fast checks have passed but slower PR
workflows have not created check-runs yet, while min_head_age remains the
backstop when no PR-context reference is available.
Rate-limit behavior. The sweep ticks every 10 seconds on hives with ≤4 configured repos. Above that, the interval scales so the sweep's list+candidate calls stay within 25% of the App's hourly REST allowance — a 45-repo hive on a fixed 10s tick used to exceed the whole allowance on list calls alone and starve every other GitHub caller, including the agents.
The supersession sweep (src/pkg/github/pr_supersession_sweep.go) looks for
open PRs whose claimed issue is already closed by a different merged PR. It
keeps one <!-- hive:pr-supersession-sweep --> notice per PR, edited in place.
- Which issues count. When the PR closes issues with a closing keyword
(
Fixes/Closes/Resolves #N), only those are candidates; a barePart of #N/Refs #Nreference is context and a closed parent reached that way never supersedes a PR. Only a PR with no closing keyword at all falls back to its branch-name issue and bare references. If any candidate issue is still open the PR is live work and the sweep leaves it alone — no notice, no label, no close (#12349: #12322,Fixesopen #12117 andPart ofclosed #12080, was escalated toneeds-human). - Hive-authored PRs are closed immediately at ACMM L6 when every file they
touch was also touched by the merged PR. When they touch files the merged
PR did not, the hive resolves the overlap itself instead of asking a person
(
pr_supersession_resolve.go): if the head is behind the base it requests GitHub's update-branch (branch_updateeffect) and waits for the new head; once the head is current it re-reads the files, and either closes the PR (nothing left, or only files the merged PR already changed) or rewrites the body'sCloses #NtoRefs #N (closed by #M; this PR adds <files>), stamps<!-- hive:supersession-resolved issue=N closer=M -->, edits the notice and lets the PR continue in its lane. A refused update (conflict) is noted once per head and left to the fix lane. The hive's own earlierneeds-humanis released when it takes the resolution over; one a person applied is honoured and the PR is left alone. Below L6 the PR is escalated instead. - Escalation goes through the stage engine. Whenever the sweep hands a
PR to a person (contributor PR with files the merged PR did not touch, or a
hive PR below L6) it adds
needs-humanand submits aParkedfact, so the PR lands atstage/085-needs-decisionin the same pass — never a bareneeds-humanbeside an active lane stage (#13782 worestage/050-review-pending+needs-humanuntil the next auto-merge tick). Only a closing claim (Closes #N) parks a contributor PR; a PR that merely references the closed issue (Refs #N,Part of #N, a branch name) gets the notice with the files named and no park. The sweep's ownneeds-humanclears with its cause: when the supersession it announced no longer holds (issue reopened, claim rewritten, link downgraded to a reference) it takes the label off again (supersession-sweep-released-needs-human); a person'sneeds-humanstays (#14155). The notice names the files the merged PR did not touch. Once the notice is on the PR, the sweep re-escalates only when the notice changes (a different merged PR or reason); clearing the label for an unchanged notice sticks. - Human-authored PRs get the notice and are surfaced to operators in the
dashboard Review Queue (below); they are never closed automatically
unless
supersession_sweep.close_contributor_prsis set (and the repo is at L6). With it set, the sweep closes the PR only when all of these hold: every file it touches was also touched by the merged PR; it issize/XS–size/M(or under 100 changed lines when unlabeled); the author has no unresolved review threads; review threads could be read; andgrace_periodhas passed since the notice announced the window with no human (author or maintainer) comment or review comment after the notice. Any doubt keeps the PR open and rewrites the notice to say why. - A closed PR gets the
hive/supersededlabel and one<!-- hive:pr-supersession-autoclose -->comment naming the merged PR and its merge time, with "reopen if that's wrong". Reopening and commenting keeps it open on the next pass. - Every human-authored PR the sweep detects as superseded — whether or not
automatic closure is on — is tagged
supersededin the dashboard Review Queue with a link to the merged PR. When auto-close is active the entry also shows when the grace window ends; otherwise there is no countdown. An owner can Close now (same comment, label and close as the sweep) or Keep open, which addshive/keep-open; the sweep never auto-closes a PR with that label.
| Key | Default | Meaning |
|---|---|---|
supersession_sweep.close_contributor_prs |
false |
Allows closing superseded human-authored PRs after the grace window. Off: they get the notice and a Review Queue close/keep-open decision, but are never closed automatically. |
supersession_sweep.grace_period |
24h (DefaultSupersessionGracePeriod) when 0/unset |
How long a human-authored PR stays open after the grace-window notice. Changing it rewrites the notice and restarts the window. |
The hive owns one ordered stage/NNN-* label per open issue/PR
(010-actionable … 100-merge-queue), a role/* admission-tier label and
an author/* authorship label (human / clanker / hive; names under
governor.labels.author) on PRs, and a red stage/stalled marker when nobody
but the hive has touched an item for the stage's dwell — never while checks
run or the reviewer verdict is pending, those are waits on the hive and
surface as a hive_held count on the review queue instead;
label:stage/stalled across repos is everything a person has walked away
from. Backward stage moves are a one-line comment and the review queue's
transition history (the stage/regressed label is retired, #12534). Default
on; governor.labels.stage.enabled: false stops the reconciler,
governor.labels.stage.dwell.<slug> re-times a stage's stalled threshold,
governor.labels.stage.stalled.require_quiet: false measures the dwell from
stage entry instead of last activity (Settings → Labels). When a clanker
PR enters
stage/072-returned-to-contributor the hive posts one nudge comment per head
(re-nudged once after governor.labels.stage.contributor_nudge.renudge_interval,
default = the 24h dwell; cap 2) and, for a contributor whose relay heartbeat is
within governor.labels.stage.presence_window (default 15m), pushes a fix-pr
interrupt to their relay session, bounded by the per-contributor fix budget
(contribute.max_parallel_fixes / contribute.min_focus_window).
governor.labels.stage.contributor_nudge.enabled: false turns the nudge and
the relay task off. The nudge and the adoption below apply to author/clanker
PRs only; human- and hive-authored PRs just wait at 072. A red PR whose author
is unavailable is adopted (governor.labels.stage.adoption.enabled, default
on) — judged against live hub presence each sweep: relay offline for
governor.labels.stage.adoption.after_offline (default 15m) since its last
heartbeat, no contributor profile here for adoption.after_unregistered
(default 2h) since the first nudge, or online but silent for
contributor_grace_online (default 2h; contributor_grace_offline, 15m, when
the hub reports no presence). hold/do-not-merge blocks adoption. When the
PR allows maintainer edits (adoption.in_place, default on) the hive adopts
it in place: stage/074-adopted-by-hive, the PR's own branch filed in
ci-failing.json as the hive's (adopted: true) for the scanner's red-PR
fix-first kick. On a later head change, forgeable git names/emails never keep
ownership: the hive follows the head only when GitHub links the commit to the
App bot (the stage store only remembers SHAs that already passed that
check); otherwise it hands the PR back as a contributor push
(governor.labels.stage.adoption.strict_attribution, default on; first deploys
without any repo evidence log and keep adoption). Push-capable App-authored
agents use the App bot noreply email (keyed by the bot's GitHub user id, not
the App id) for git commits so fixer pushes normally carry that GitHub linkage while their commit name/sign-off still names the
lane. Once the hive's in-place head is green, reviewer-approved, DCO-clean
and soaked, the contributor lane merges it under the hive's own authority
(contributor-lane:adopted-by-hive) — the original author's trust tier is
not consulted, because the hive owns an adopted head
(governor.labels.stage.adoption.auto_merge, default on; false leaves an
untrusted contributor's adopted PR waiting for a maintainer /lgtm). If the hive cannot push in place, it
pushes hive/adopt-<n> at the contributor head, opens a superseding App PR
that preserves the author's commits, moves the original to 074 and closes it
once the superseder merges. Whether that rework reaches each agent is visible per row
on Operations → Connected contributor agents: the relay's parallel capacity
(capacity N · spawn|preempt, or not advertised), one line per in-flight
item tagged NEW / REWORK with its stage label, and a red N PRs
awaiting rework (not yet dispatched) line when the contributor has
stage/072 PRs the hub has not handed them (parallel_capacity,
in_flight[], fix_queue_depth, pending_rework on /api/contribute/fleet;
see contributor-relay.md). The retired 🐝 covered by PR,
claimed and hive/in-progress labels are removed from open items on the
first cycle after upgrade. Full table, state diagram and migration note:
labels-and-control-signals.md.
Every stage label is derived by one pkg/stageengine reconciler behind
one writer (ADR-0025), built once per
process and shared by the issue and PR sweeps. The rollout switch is
governor.labels.stage.engine.mode (Settings → Labels → Central stage
engine mode, or hive.yaml); the sweeps re-read it before each pass, so
a change takes effect on the next sweep without a restart, and the hive
logs one WARN stage engine: mode changed per transition.
| mode | behaviour |
|---|---|
enforce (default) |
engine's derivation is written; out-of-order transitions are counted as anomalies |
shadow |
pause — the engine derives and counts but writes nothing; every stage/* label stays exactly as found |
An unset mode is enforce. The pre-rollout value off (legacy
pass-through) is rejected at config load since the sweeps' own derivation
path was retired (plan PR-F); set shadow to pause stage writes instead.
Watch /api/status.stageEngine (mode, derivations, anomalies,
healed), the strip at the top of the activity tracker (engine enforce · N anomalies), hive_stage_transition_anomaly_total{kind,from,to} and
hive_stage_duplicate_pills_healed_total{kind} on /metrics; an anomaly
pair not explained by a documented event (merge, close, human label edit)
is a deriver or transition-table fix, never a reason to leave enforce.
The remaining series are hive_stage_derivations_total{kind,stage} and
hive_stage_writer_writes_total{kind,op}.
When a fix lane defers a red PR to a [shared-ci] <check> failing across <owner/repo> incident it stamps <!-- hive-shared-ci-<n> --> on the PR
(see labels and control signals). Nothing on
the PR changes when the incident's fix lands, so the PR stays red until
someone re-runs it. The shared-CI rerun sweep
(src/pkg/github/shared_ci_rerun.go) closes that gap every ~15 minutes:
- It lists
[shared-ci]issues closed in the last 72h in each watched repo, ignoring ones closed as "not planned". With none, it makes no other calls. - For each open PR whose newest shared-CI marker names one of those incidents,
it re-runs the failed jobs (
POST .../actions/runs/{id}/rerun-failed-jobs) of the newest run per workflow on the PR head when that run concludedfailureortimed_out, using the hive's App/daemon credential. It pushes nothing to the branch, so it applies to every author; PRs with a hold ordo-not-mergelabel are skipped. - It then posts one comment carrying
<!-- hive-shared-ci-rerun-<n> -->, which keeps the PR to one re-run per incident. A PR with nothing red on its head gets no comment. At most 10 PRs are re-run per pass; each re-run is logged asshared-ci rerun repo=… pr=… incident=#…and audited asshared-ci-rerun. - It is an unstick step (
shared_ci_reruninapi_budget.unstick_steps): it keeps running inconserveand is shed only incritical.
| Key | Default | Meaning |
|---|---|---|
shared_ci_rerun.enabled |
true (unset) |
Set false to stop re-running PRs when a [shared-ci] incident closes. |
An issue whose fix is already open must not be dispatched again
(#14799). The claim
ledger the admission gates read is refreshed by the eval cycle, so a PR a
person opened by hand between scans was invisible to the /contribute relay
lane and the scanner, and the issue was handed out a second time; the two
same-issue PRs then landed at stage/085-needs-decision for the owner to pick
between. Two mechanisms close that gap
(src/pkg/github/open_pr_links.go, src/pkg/github/pr_duplicate_fix_sweep.go):
- Pre-dispatch guard. Before any automated producer claims an issue —
the central
IssueClaimBlockReasongate, the ledger admission check, the issue-request watcher and the/contributerelay lane — the hive lists the repo's open PRs (once per repo per sweep, cached for a minute) and parses each title/body with the claim ledger's closing-keyword grammar (Fixes|Closes|Resolves #N; aRefs #Nis not a fix). When an open PR links the issue, the claim is skipped, the issue is labelledhas-open-pr(created on first use; never re-added), the refusal is logged asclaim skipped: open PR already linkedwith the PR number, URL and author, and the tracker's withheld list shows An open pull request already links this issue (claim skipped) with the PR as evidence (reasonopen_pr_linked). A failed listing fails open: the ledger-backed guards still apply, and a flaky listing must not stall every claim. The gate also refuses an issue the confirming read says is already closed (issue closed), so an issue closed between enumerations is never dispatched from the cached snapshot. - Webhook claim linking. A
pull_requestdelivery (opened, reopened, edited, synchronize, ready-for-review, draft, closed) is parsed with the same closing-keyword and branch-name grammar and written into the claim ledger the moment it lands (#14814;src/pkg/github/webhook_pr_claims.go): an open PR's claims are recorded (and the cached actionable snapshot re-filtered, so the hub's own agents stop seeing the issue too), a PR edited to stop naming an issue or closed without merging releases it, and a merged PR's claims become merged claims. Before this the link was only made on the full reconcile pass — about an hour apart on a busy spoke — and an owner PR opened withFixes #Nin between left #N offerable for that long; the/contributelane handed #14765 out nine minutes after #14772 (Fixes #14765) opened. The receiver's response lists the issues asclaimed_issues, the governor logswebhook PR claims applied to claim ledgerwith the PR, state and counts, and the open-PR listing cache for the repo is invalidated so the pre-dispatch guard re-lists on its next call. Anissuesclosed delivery (or a PR merged with a closing keyword, which GitHub delivers as the issue's own close) already evicts the issue from the status snapshot the lane offers from. - Duplicate-fix sweep. Every ~15 minutes (an unstick step, alongside the
supersession sweep) the hive looks for an open issue two or more open PRs
close and resolves the pair deterministically. The earlier-created PR
wins while its checks are green or pending: the later hive-authored PR
receives one comment —
Superseded by #<keeper> (earlier fix for #<issue>). Closing as duplicate.— is labelledhive/supersededand closed, with the reasonduplicate-of:<keeper>in the log and theduplicate-fix-closedaudit entry. A later PR a person authored (author/human) is never closed: it gets the comment, with a request to close it or say why both should land, and stays open at its current stage (duplicate-fix-commented). Only when the earlier PR is red or stale (no update for 24h) and the later PR is green does the hive ask the owner: the later PR is parkedneeds-human→stage/085-needs-decisionwith a comment naming both PRs and the check states (duplicate-fix-escalated). A closed issue is left to the supersession sweep, held /do-not-mergePRs are skipped, a PR already carrying the sweep's<!-- hive:duplicate-fix -->marker is never written to again, and at most 10 writes are made per pass.
| Key | Default | Meaning |
|---|---|---|
scanner.skip_issues_with_open_pr |
true (unset) |
Set false to turn off both the pre-dispatch guard and the duplicate-fix sweep; the claim ledger alone then decides, as before. |
The sentinel evaluates open PRs in the watched repos — human, bot, or agent —
and, when a PR matches one of the behaviors below, posts one
<!-- hive-sentinel --> comment per head SHA listing the matched rules and
paths (pkg/sentinel, pkg/github/sentinel_sweep.go). Since
#13379 it runs in two
ways. A verified pull_request opened/synchronize/reopened webhook
evaluates that one PR at once, reading only the PR and its changed files
(SweepSentinelPR); deliveries for the same PR coalesce while one is
pending. The full sweep (SweepSentinel) is the reconcile cadence: every
github.SentinelReconcileInterval (15 minutes) it enumerates open PRs from
the ForgeState cache when forgestate.enabled is on (registered at boot by
cmd/hive/forgestate_boot.go; with the cache off it lists the repo as
before), skips every head it has already
evaluated with no GitHub call, reads the remaining candidates once each, and
so bounds how long a PR whose delivery was lost sits unevaluated. The log
line sentinel sweep complete carries cached_repos/listed_repos. Both
paths share one option set and the detection, comment and label logic. For untrusted authors, it also adds the
alert label. The label is a hard hold: while present, Hive will not submit an
APPROVE review, apply LGTM/approval labels, or merge the PR through any
auto-merge lane (dashboard queue, self-authored App sweep, trusted bot authors,
or trusted authors). Trusted authors (the Hive App, configured trusted bots and
owner/trusted-author allow-list entries) default to an informational notice and
audit/dashboard finding only; no alert label is added unless the operator opts
into blocking them. A maintainer who removes the label keeps the PR clear until
the author pushes again: the sweep reads the clearance back from the alert
comment it left for that head (alert comment present, label absent), so it
survives a hub restart or a second replica and nothing else needs to be posted.
| Behavior | Fires when |
|---|---|
sensitive_path |
Any changed file matches a sensitive-path glob. |
owner_self_nomination |
The PR author adds their own login to an OWNERS/CODEOWNERS/MAINTAINERS file. |
permission_escalation |
A workflow/CI file widens permissions: (write-all, contents: write, id-token: write, …), or a role/RBAC manifest grants */cluster-admin. |
secret_exposure |
Added lines look like credentials (PEM blocks, AWS/GitHub/Slack tokens, password=), or a workflow echoes/uploads secrets.*. |
ci_gate_weakening |
Added continue-on-error, removed a required-check/branch-protection line, deleted a workflow or test job, added --no-verify/skip ci. |
test_removal |
Tests are deleted or heavily reduced while production code is not. |
security_policy_edit |
SECURITY.md, GOVERNANCE.md, .github/rulesets/**, dependabot/codeql config, or Hive's own policy/proxy rules change. |
remote_code_execution |
Added curl … | sh, wget … | bash, eval $(curl …), base64-decoded execution, or an unpinned third-party action in a workflow. |
| Key | Default | Meaning |
|---|---|---|
sentinel.enabled |
on when unset | false disables the sweep entirely. |
sentinel.label |
sentinel-alert |
Label added to untrusted flagged PRs (created red if missing). Neutral on purpose: it means "look closely", not "malicious". This configured label is also added to Hive's hold/exclude set and blocks Hive approval plus every auto-merge lane until a human removes it. |
sentinel.trusted_authors_block |
false |
When false, trusted author findings are notice-only: comment, audit/dashboard record, no label, no approval/merge block. Set true to add the blocking label for trusted authors too. |
sentinel.sensitive_paths |
shipped defaults (sentinel.DefaultSensitivePaths) |
Glob list (same syntax as intent.guardrail_path_patterns; ** crosses directories) that replaces the defaults when set. Defaults cover OWNERS/CODEOWNERS/MAINTAINERS, SECURITY.md, GOVERNANCE.md, .github/workflows/**, actions, dependabot, codeql, rulesets, CI config, Makefile/Justfile, pre-commit hooks, policies/**, hive.yaml*, gh-wrapper*, proxy rules, Dockerfiles, install.sh, dependency manifests and lockfiles, release workflows, .env*/key material, deploy/**, Terraform, Helm/k8s manifests. |
sentinel.disabled_behaviors |
empty | Rule names from the table above to switch off. Unknown names are rejected. |
sentinel.exempt_logins |
empty | GitHub logins never flagged (e.g. a release bot). Use sparingly. |
sentinel.repos |
empty = all watched repos | Optional owner/repo allow-list. |
sentinel.max_actions |
20 |
Caps label/comment/remediation actions per pass. |
Owners edit all of this from Settings → Security → Suspicious Activity;
the dashboard writes the owner-only /api/config/governor/security overlay.
Every alert is also written to the dashboard audit log as sentinel-alert,
and, when evidence.enabled is on, recorded under sentinel in the flagged
head's review evidence bundle.
On the dashboard's Projects cards, every open PR that carries the configured sentinel label is drawn as a red pill (status chip and main pill), taking precedence over the held, needs-human and merge colours, and the pill legend has a matching entry. The red clears on the next status refresh after a maintainer removes the label. Trusted-author PRs that only received a sentinel comment are not red.
Pre-built images are published by .github/workflows/docker.yml to ghcr.io/hivecommons/hive (plus hive-contributor and hive-hub) and mirrored by digest into the matching ghcr.io/kubestellar/* packages. Post-transfer, hivecommons is the native publishing org; the workflow retags the already-built digest into kubestellar so that spokes still pinned to the old org keep resolving, and both orgs serve digest-identical manifest lists for the same tag. (A missing cross-org credential is a hard failure in that direction precisely because a one-sided publish would leave kubestellar serving stale tags to live spokes.)
A build of a release line publishes, in one multi-architecture manifest operation:
ghcr.io/hivecommons/hive:<line>-latest— the rolling tag for the current HEAD of that branch:v4-latestonv4,v5-latestonv5,v6-latestonv6;ghcr.io/hivecommons/hive:<git-short-sha>— immutable per-commit tags;- that line's moving release channel, if it owns one —
v5merge builds own:candidate(and:latest),v6merge builds own:edge;v4is a maintenance line and publishes no channel, onlyv4-latestand short-SHA tags (#7721 Phase 1). Channels are retags of the same digest; see release-channels.md.
:latestfollowsv5. Onlyv5builds move the global:latesttag (INCLUDE_LATESTisfalseindocker.ymlonv4andv6), so it tracks the same digest ascandidate. The cross-line ambiguity previously tracked in #6711 — where:latestalternated between lines depending on which run finished last — was resolved by the #7721 Phase 1 channel remap. For production, still prefer a named channel (stable) or a digest over:latest.
Channel ownership is deliberately per-line: without the split, every merge to one line would silently re-point another line's channel back onto its own build minutes after a deliberate promotion (src/scripts/publish-image-tags.sh). Two consequences follow that are easy to get backwards:
:stableis not published by a branch build at all. The separate stable-promotion workflow advances it by digest to the newestv5build that crossed the 24-hour line, after the soak gate passes.:edgeridesv6, so it is an active-development build of the next line, not a fresher:stable. It is the newest build, not the most proven one.
PR and short-lived branch builds compile the image as a CI gate, but only the long-lived release lines (v4, v5, and v6) push tags. Before tagging, the workflow verifies its SHA is still branch HEAD, so a stale queued build cannot move a rolling tag backward.
Note:
v2-latestwas the rolling tag of the retiredv2branch. Do not use it for new deployments — preferstablefor production, or pin a digest. (src/docker-compose.yamlwas bumped off it in #4206; standalone image references now come from one source of truth,src/deploy/standalone-images.sh.)
Standalone deployment assets do not carry their own image references. They take
them from src/deploy/standalone-images.sh,
which names the Hive, gateway, and auto-update-profile images in fully
qualified form. Change a reference there rather than in an individual asset.
Fully qualified is deliberate: podman auto-update refuses a short name, and
Podman's Quadlet generator warns on one, so a reference that resolves through
the host's unqualified-search-registries is not portable across the two
runtimes. Docker may still spell the same image without the docker.io/
prefix — the contract test compares the normalized forms, so the two spellings
are equal as long as they name the same image. Digest pins are carried through
unchanged and are checked for as well.
src/deploy/test_standalone_image_refs.sh
runs in CI and fails the build when an asset stops agreeing with that file,
when a digest pin is dropped, or when the retired v2-latest tag reappears. It
already covers the Podman asset paths, so drift detection switches on by itself
when those assets land.
- Production:
stable, or pin a digest for full reproducibility. - Following mainline:
v4-latest. - Reproducing an incident: the
<git-short-sha>tag from the workflow run.
docker buildx imagetools inspect ghcr.io/hivecommons/hive:stable
docker pull ghcr.io/hivecommons/hive@sha256:<digest>In Compose, replace the tag with the digest form:
services:
hive:
image: ghcr.io/hivecommons/hive@sha256:<digest>To relate an image to source, compare the <git-short-sha> tag published by the same workflow with commits on v4, or inspect the Docker workflow run for the commit SHA that produced the digest.
- Agent cadences are evaluated from persisted state: the last-kick map lives in
/data/hive-state.jsonand is honored across pod restarts — a Deployment roll does not re-kick every cadenced agent at boot (#3817). A fresh install (no persisted state) still kicks every cadenced agent on the first eval. There is no global default interval; a zero/absent interval means the agent is never cadence-kicked. - Continuous mode is a generic per-mode cadence value for any configured agent: set a mode cell to
continuous, optionally withcontinuous_cooldown(default60s) andcontinuous_budget_pct(default80);scanneris only a common first user, not a special case. The legacyagents.<name>.continuous: truebool remains a shorthand for every non-QUIET mode. Continuous uses the agent manager's existing turn-end signal — the CLI is back at its input prompt and no work marker is active — to schedulenextKick = ended_at + cooldown; the run loop wakes on that time (at most one early eval per 30s, backing off 1m→15m while the eval cycle keeps skipping a due agent for a dashboard pause, first-scan deferral, provider-budget gate, or upgrade drain) instead of waiting for the next cadence eval tick, and status exposeslastKickEnd/idleSincefor the gap. Enabling continuous on an idle agent, including entering a mode whose cadence iscontinuous, schedules its first continuous kick after one cooldown; entering a mode where the agent is not continuous clears pending continuous kicks and status reportscontinuousBlocked: "not_in_mode". It supersedes interval cadence while active, but only after an observed session end or idle enablement. It never types into a busy session and still respectsenabled: false, operator/fleet-breaker pause, governor-modepause/off(including QUIET), on-demand, non-kick channels, provider rate-limit/quota backoff, budget/provider holds, and upgrade/restart holds. The continuous budget guard stops re-kicks once the current token-budget window reachescontinuous_budget_pctofgovernor.budget.total_tokens; normal cadence resumes until the window falls below the threshold or resets. Failed deliveries back off exponentially (capped) and/api/statusexposescontinuous,continuousModes,continuousBlocked, anycontinuousBackoff, and per-agentcontinuousKicks/continuousTokenscounters. If a kicked turn never reports an end (missed pane turn-end signal),continuous_turn_ceiling(default45m,-1disables) presumes it over, logscontinuous turn never reported an end, and re-arms after the cooldown rather than idling until a pod restart; a non-zeropresumedTurnEndsin status means turn-end detection is missing for that agent. Operators set per-modeContinuousvalues in the agent settings Cadences form; the Governor cadence table is read-only and shows an∞ continuouschip in each continuous mode cell. - Manual dashboard/API kicks that rely on Hive's generated work list wait for the first governor scan after boot. Until that scan populates the scheduler snapshot,
POST /api/kick/{agent}returns202withstatus: "deferred"and Hive delivers one deduplicated kick for that agent as soon as the first scan completes. - Kick-visibility conditions surfaced on the dashboard include
copilot-question-form, which means the Copilot CLI asked an unattached human for clarification; Hive dismisses that form with Escape and retries delivery instead of dropping the kick. - The governor token budget uses a rolling window of
governor.budget.period_days(default 7 days), with a soft warning atgovernor.budget.critical_pct(default 90%). When spend reaches the limit, kicks are suppressed for all agents except those explicitly budget-exempt. - Bob coin budgets are configured under the same governor budget window. The coin conversion comes from your Bob account/team; the values below are illustrative only (they are not Hive's built-in defaults and not public Bob pricing guidance) — always set them to the conversion your Bob team gives you:
governor:
budget:
usd: 25.00 # optional period cap in Bob-equivalent USD
coins:
bob:
tokens_per_coin: 1000000 # illustrative only; use the conversion your Bob team gives you
usd_per_coin: 1.00 # illustrative only; use the conversion your Bob team gives you
label: "BC"
budget: 50 # optional period cap in Bob coinsHIVE_BOB_TOKENS_PER_COIN, HIVE_BOB_USD_PER_COIN, HIVE_BOB_COIN_BUDGET, and HIVE_BOB_USD_BUDGET override those values at runtime. budget.usd and budget.coins.bob.budget are first-class caps: set either or both. When both are positive, the governor trips on whichever cap is exhausted first and surfaces the exhausted unit. The Cost panel shows Bob coins alongside the configured USD equivalent so operators can enter and compare either unit. Coin conversion values are account-specific; always confirm the conversion with your Bob team rather than relying on Hive's built-in fallback values.
Every kick is typed into the agent's terminal after a /clear, so the whole prompt is paid for — in model input tokens and in delivery time (about 400 runes/s, so roughly 2.5 s per KiB) — on every cadence tick. governor.kick_limits.max_issues / max_prs are the only knobs that change that size without editing a policy, which makes it tempting to lower them. The numbers below (#12133) show why the shipped defaults stay at 100 / 50.
Where the bytes go. Measured with go test ./pkg/scheduler -run TestKickSizeMeasurement -v (an ACMM L6 scanner in ISSUES_PRS_MERGE on the shipped scanner-automerge.md, three authorised repos, 70-character titles, merge-eligible and CI-failing lists scaled with the backlog — 100 and 20 at the 302-PR row):
| Open issues / PRs | Caps | Kick size | Of which lists |
|---|---|---|---|
| 0 / 0 | 100 / 50 | 32.9 KiB | — (policy, seams, overlays, repos) |
| 10 / 5 | 100 / 50 | 35.8 KiB | 2.9 KiB |
| 25 / 10 | 100 / 50 | 38.8 KiB | 5.9 KiB |
| 40 / 20 | 100 / 50 | 43.1 KiB | 10.2 KiB |
| 60 / 30 | 100 / 50 | 47.7 KiB | 14.8 KiB |
| 100 / 50 | 100 / 50 | 56.8 KiB | 23.9 KiB |
| 302 / 302 (the #7368 backlog) | 100 / 50 | 63.4 KiB | 30.5 KiB (issues 11.5, PRs 6.9, merge-eligible 4.8, CI-failing ≈ 7) |
| 302 / 302 | 40 / 20 | 47.0 KiB | 14.1 KiB |
The fixed cost — the policy text and the mandatory seams — is 32.9 KiB: 52 % of the worst-case kick and ~85 % of a typical one. Lowering the caps to 40 / 20 saves nothing until a repo set has more than 40 open issues or 20 open PRs, and at most ~16 KiB (≈ 40 s of typing) at the 302-PR extreme. It would also make the merge-eligible list drain a 100-PR automerge queue in five kicks instead of two, which at a 10-minute cadence is the starvation the caps were sized against (docs/scale-envelope.md). The lever that actually moves kick size is the policy itself, not the list caps.
Watch it rather than guess. Each rendered kick logs one line:
level=INFO msg="kick rendered" agent=scanner repo="" kick_bytes=58123 issues_total=137 issues_listed=100 prs_total=61 prs_listed=50 max_issues=100 max_prs=50
kick_bytes is the prompt as typed (canary preamble included). When issues_listed / prs_listed sit well below the caps on every kick, lowering them changes nothing; when *_total is persistently far above *_listed, the backlog — not the cap — is the problem, and the overflow marker in the prompt already tells the agent the list is partial. Per-agent kicks share the same global caps; set them under Settings → Repos → Kick prompt list caps or in governor.kick_limits, with 0 meaning unlimited.
Pausing agents (including the navbar fleet breaker) does not take the Hive instance down: the dashboard, governor scans, GitHub polling, token scanners, repo-cost collectors, telemetry/heartbeats, and provider-headroom probes can keep running. Those components should not make model calls by themselves. Model usage is expected from agent sessions reached through governor/manual/CEL/resume kicks; those kick paths share the same pause and budget gates. Knowledge priming reads configured knowledge stores and attaches facts to kicks; it does not independently call the model while every agent is paused. Provider-budget probes are the exception by design: after a provider spend-limit rebuff, Hive may release one kick after governor.provider_budget.probe_interval_s to test recovery.
- The Governor dashboard PRs by model panel includes rework evidence for 7d/30d/all windows: first-pass merge rate, average/worst review rounds, fix attempts, follow-up commits after first review, human change requests, median time to merge, and a top-10 Most reworked PRs list. The data is served by
/api/governor/pr-modelsfrom the same cached PR snapshot as the model outcome counts. - The provider spending limit is a separate signal from the token budget above (#4294): the token budget counts what the hive spends, while this is the inference gateway refusing to spend more money — a LiteLLM key past its daily dollar cap, a project out of quota, an account out of credit. It is detected from the gateway's own error body (never from a bare 429, which stays on the ordinary retry path), raises an error-level dashboard alert naming the limit that was hit, and withholds every agent kick while it is in force. It does not pause agents: pause state stays a human decision.
Spoke GitHub App installations share one core REST bucket across the scan loop and all agent-requested writes. Hive now derives an API budget mode from the observed core rate-limit window:
| Mode | Condition | Default behavior |
|---|---|---|
normal |
remaining >= github.api_reserve |
Full eval fan-out. Optional sweeps run every governor.optional_sweep_every_n_cycles cycles (default 1). |
conserve |
remaining < github.api_reserve (default 800) |
Critical paths and unstick steps (escalation, SHA hold, supersession sweep, shared-CI rerun) continue; optional advisory/recommendation/duplicate/review-thread/collector enrichment sweeps are skipped. Eval interval is multiplied by governor.conserve_interval_multiplier (default 2). |
critical |
remaining < github.api_critical (default 250) |
Same shedding as conserve plus the unstick steps, with the eval interval stretched by 4x. |
Mode exits use hysteresis: Hive leaves conserve/critical only after quota rises above the threshold plus 100 calls or after the reset time passes. The effective interval is capped by governor.eval_interval_max_s (default 1800). /api/status and /api/gh-rate-limits expose api_budget with the mode, remaining/limit/reset, mode since, skipped_steps, unstick_steps and enrichment_steps; the dashboard shows an API budget pill beside the GitHub rate-limit display.
- Recovery from a provider spending limit is automatic, via a probe. Withholding kicks also withholds the inference calls that would reveal the provider is serving again, so the hive suppresses only while the last refusal is recent and then lets a single kick through to test the gateway; the probe re-arms suppression the moment it is released, so at most one probe run flies per interval. A still-clipped key refuses the probe and suppression resumes for another interval; once the provider's window resets the probe succeeds, normal kicking resumes with no operator action, and a one-time recovery notification is sent (the entering notification is likewise sent once per clip, not once per cycle). Tune with
governor.provider_budget.probe_interval_s(default 1800 — 30 minutes):
governor:
provider_budget:
probe_interval_s: 1800Lower it to resume sooner after a reset at the cost of a rebuffed run per probe; raise it to waste less while noticing later.
The Throughput section of the dashboard carries a Delivery SLA block
(below the merged/opened metric tiles; moved there from the Overview) that
answers "how long does it take this hive to turn an issue into a merged PR?"
without spending any GitHub API quota. Everything is derived from the
lifecycle timeline the hive already keeps (#11841
follow-on): the issue's created_at retained when it is enumerated, the
classified stage, the started entry written with the stage/030-pr-in-progress
label (formerly hive/in-progress), the pr_opened entry recorded when a hive PR that Fixes/Closes the
issue is opened, and the merge observed by the PR watcher, the close-on-merge
ledger or the audit stream, fanned out to every issue the PR linked.
Per repo and fleet-wide, five spans are aggregated over rolling 7-day and
30-day windows as {p50_h, p95_h, n} (nearest-rank percentiles, hours):
| Span | From → to |
|---|---|
open_to_triage |
issue created → classified |
triage_to_start |
classified → stage/030-pr-in-progress (started) |
start_to_pr |
started → linked PR opened |
pr_to_merge |
linked PR opened → merged |
open_to_merge |
issue created → merged |
A journey enters a window by the timestamp of the span's end event, so a
PR merged today counts in today's pr_to_merge even if it was opened last
month.
In the dashboard the three headline tiles (start → PR, PR → merge,
open → merge) sit under the Throughput merged/opened metrics and follow its
repository filter; they show the 7-day percentiles for every period pill
except all, which switches to the 30-day window. Repos ▾ expands a
per-repo table and the stale list, and the collapsed Throughput summary line
appends the open → merge p50 and the stale in-progress count.
History. Daily buckets (day, p50 of start_to_pr, pr_to_merge and
open_to_merge, plus n_started, n_pr, n_merged) for the last 30 days
are persisted to /data/sla-history.json (written atomically, pruned to
30 days, throttled to one write a minute and flushed on shutdown). Restarting the hive or evicting old
journeys from the timeline never shrinks a stored day, so the Throughput
sparklines always have 30 days behind them. A corrupt file is moved aside
as sla-history.json.corrupt and history restarts from the live timeline.
Stale in-progress. Issues that have carried stage/030-pr-in-progress (formerly hive/in-progress) for longer
than the agent claim TTL (claims.agent_ttl_s, default 2h) with no linked
PR are listed as stale_in_progress with their start time, age and agent.
The badge in the Delivery SLA header counts them and links to each issue; it is the first
place to look when an agent claimed work and went quiet.
Targets. Each span's p50 is coloured green at or under its target,
yellow up to 1.5× the target and red beyond. Targets are hours in
hive.yaml and can also be set from Settings → Thresholds → Delivery SLA
targets (PUT /api/config/sla), applying on the next status poll:
sla:
targets:
start_to_pr_h: 4 # started → linked PR opened
pr_to_merge_h: 24 # PR opened → merged
open_to_merge_h: 72 # issue created → merged0 or an omitted key means the default. Values above one year are
rejected.
Reading the panel. The three tiles show the current-window p50, n,
p95 and target with a 30-day sparkline of daily p50s; 7d/30d toggles
the window. Repos ▾ expands a per-repo table (start→PR p50/p95,
PR→merge p50/p95, merged in 7d, 30-day open→merge sparkline) and the
stale-issue list. The same numbers are in GET /api/status under sla
(fleet) and repos[].sla; see api-reference.md.
The dashboard top bar includes a fleet breaker: a circular pause/play control plus a status pill. The GET /api/breaker state is readable by authenticated viewers, but engaging or releasing the breaker is owner-only (POST /api/breaker/engage, POST /api/breaker/release).
Engage pauses every agent that is currently running and is not on_demand. Agents already paused before engage are skipped and keep their original pause reason. Release resumes only the exact agents the breaker paused and still owns (PausedTrigger == fleet-breaker); if an operator manually re-pauses an agent while the breaker is engaged, release leaves it paused. The breaker state is persisted so a crash/restart can still release the same captured set.
Fixes #2953.
When a hive notices an unusual error in itself — a restart storm, repeated
kick failures, a recovered panic, degraded model discovery, inference
auth failures or advisory post failures — it files an issue on
hivecommons/hive titled [selfreport] <class>: <short message>, or adds
itself to an open issue that already describes the problem (fingerprint
match, hive/selfreport + class match, or keyword match on a human-filed
issue). Joining posts one "Also seen on " comment per 24h and
bumps the Affected hives (N) block in the body, where each hive has its own
line — - <hive> (instance <id>) — owner(s): @login — seen N, last <time> —
naming the GitHub login(s) of the hive owner (dashboard.authorized_users
owner-role entries plus the device-flow operator) so a maintainer can see who
is affected. Each report also names the repositories the hive watches
(project.repos) and their owners, and the body block keeps an
Affected repos (N) / Affected repo owners (N) union across reporting
hives (#12338). Every selfreport carries
origin/selfreport (the provenance axis every hive-created issue records,
see labels-and-control-signals.md)
next to the deprecated hive/selfreport.
governor:
fleet_report:
enabled: true # unset = on for hub-managed hives, off (dry-run) for self-hosted
repo: hivecommons/hive
max_per_day: 5
include_repos: true # false: never name project.repos / repo owners (set this if any watched repo is private)
include_owners: true # false: render "(anonymous)" instead of the hive owner's GitHub login(s)Rate limits: max_per_day new issues per hive per 24h, at least 1h between
new filings of different classes, no re-filing of a fingerprint closed
upstream while the hive keeps observing the condition (7-day cooldown from
the last observation), deferred while the GitHub API budget is not normal. Issues
are never auto-closed; the hive posts one "cleared" comment when the
condition stops. Hub-managed hives that cannot write to the report repo relay
through the hub (POST /api/selfreport/ingest, heartbeat bearer). The
dashboard shows "Self-reports: N open (last: → #NNN)" in the Fleet
Report panel and the toggle/repo/cap under Settings → Governor →
"Self-Reported Issues". Full details: fleet-report.md.
Each agent's CLI (Copilot, Claude, …) is a long-lived Node/V8 process whose
heap grows with every turn and never shrinks while idle. The health tick
samples the resident memory of the whole process tree under each agent's
tmux pane and recycles a session that is over budget — but only while the
agent is idle at its input prompt with no kick pending, so a turn is never
interrupted. A recycle is an ordinary restart (last_restart_reason: session recycle): claims, queues and the kick log are preserved exactly as on any
other restart, and the governor re-kicks on the next cadence. After a recycle
the same agent is left alone for ten minutes even if it is over budget again.
agents:
session_recycle: # fleet default; on unless enabled: false
max_rss_mb: 1536 # recycle an idle session whose process tree exceeds this (MiB)
max_age: 6h # …or whose CLI session is older than this
min_idle: 2m # pane + transcript must be silent this long first
scanner:
session_recycle: # optional per-agent override; absent fields inherit the fleet value
max_rss_mb: 2048The sampled value is shown as Session RSS (with the session age and a
↻N recycle count) in the agent detail panel. The log line is
agent session recycled with rss_mb, age and reason.
Agents reach api.github.com through the Hive GitHub proxy, which injects the
agent-scoped App installation token and records charged requests. The App
installation quota is shared with the daemon's scan, merge, and maintenance
loops, so the proxy enforces two safeguards:
agents.github_api_hourly_cap(default300) limits each agent to that many charged GitHub API responses in a sliding one-hour window. A charged response is any proxied response other than304 Not Modified; the proxy's own token minting is not agent-attributed and is not counted. Set the value to0to disable the fleet default, or setagents.<name>.github_api_hourly_capto override one agent (0disables that agent's cap).github.agent_reserve_floor(default400) protects the shared core bucket. When the latest rate-limit reading showscore.remainingbelow the floor, agent read requests receive the same proxy429even if the agent has not reached its personal cap. This keeps daemon merge/scan work from being starved by agent polling. Only readings from the hive's own bucket update that snapshot: responses whoseX-RateLimit-Limitis 60 (GitHub's per-IP unauthenticated bucket, e.g. an agent'sghcall without a token) or whose limit differs from the daemon's are ignored, so they cannot trip the floor or the API budget mode.
The proxy returns JSON shaped for the GitHub CLI:
{"message":"hive: agent GitHub API hourly cap reached (N/cap); stop polling and continue with local work","documentation_url":"…docs/operator-reference.md#github-api-quota"}It also sets Retry-After to when the one-hour window (or observed reset) can
make progress, logs once at 80% of each agent window, and sends one
stop-polling nudge per agent window. Hive-authored write relays are not
agent-attributed and are not capped by this path.
Open-PR scans use github.graphql_pr_batch by default to move mergeability,
review-decision, linked-issue and CI-rollup reads into GitHub's separate
GraphQL bucket. /api/gh-rate-limits reports these calls as caller
hive:pr_batch on endpoint /graphql and exposes graphql_pr_batch counters
for repositories, PRs, pages, REST fallbacks, errors, and the last GraphQL
query cost. Per-PR REST reads still happen when the batch is disabled, a repo's
GraphQL query fails, or GitHub returns mergeable: UNKNOWN for that PR.
See GitHub webhooks: cadence, patch and reconcile for the cadence keys, the fallback to polling when deliveries degrade, and the accuracy guarantees of the ForgeState contract.
A spoke accepts GitHub App webhooks on the public POST /api/webhook/github,
either delivered directly by GitHub or relayed by the hub (the hub forwards the
original signed body to the hive that manages the repository). The receiver
fails closed: it rejects every delivery until GITHUB_WEBHOOK_SECRET is set to
the App's webhook secret, and it verifies X-Hub-Signature-256 over the raw
body exactly as the hub does. Deliveries for repositories the hive does not
manage are ignored.
- Secret. Set
GITHUB_WEBHOOK_SECRETin the spoke's environment (a Secret mount on Kubernetes; never inhive.yaml) to the App's webhook secret. On a hosted spoke this is the same value as the hub'swebhook-secret.key. Without it the receiver answers 503 and the loop stays on the polling cadence. - GitHub App webhook. Point the App's webhook URL at the hub
(
POST /api/github/webhook) for hosted spokes, or at the spoke's own/api/webhook/githubfor a stand-alone hive (a repository or organization webhook with the same secret works too). Subscribe the App to these events:pull_request,pull_request_review,pull_request_review_comment,issue_comment,issues,check_suite,check_run,status,push, and, withgovernor.work_source.type: github_projects,projects_v2_item(organization-scoped; see Work sources). - Hub relay. The hub verifies the HMAC and relays the body byte-for-byte,
with the original signature and
X-GitHub-Delivery, to the spoke whose reported dashboard URL manages the repository. Nothing to configure on the spoke beyond the shared secret. - Pacing (optional).
governor.eval_interval_webhook_s(default900) is the base tick while webhooks are healthy;governor.webhook_wake_debounce_s(default10) andgovernor.webhook_wake_min_gap_s(default30) pace the early cycles described below.review.event_drivenadditionally letspull_requestdeliveries dispatch reviews early.
Verify: /api/status.webhooks.events_1h climbs after a push or comment,
/api/status.evalMode reads webhook, and the spoke log shows
github webhook wake: running an early eval cycle with the delivery's reason.
Through the hub, every enrolled hive that tracks the delivery's repository and has a dashboard URL receives the relay, so multiple hives (for example v5 and v6 spokes) tracking one repo all stay on webhook mode.
pull_request,pull_request_review,pull_request_review_comment,check_suite,check_run,status,issue_comment(on PRs only) andpushinvalidate the cached PR detail and the GraphQL batch check-run/review entries for the affected PR and mark it dirty. Check and status events are matched by PR number and head SHA (fork PRs arrive without a PR list);pushinvalidates every open PR whose base or head branch is the pushed branch.issueslabeled/unlabeled/reopenedmark the issue for the next un-park sweep.pull_requestopened/synchronize/reopenedalso hands the PR to the sentinel, which evaluates it now instead of waiting for its reconcile cadence (#13379).projects_v2_item(created/edited/deleted/reordered/archived/restored/converted) patches the GitHub Projects work-source cache: the delivery names the item and its project, the hive re-reads that one item (one GraphQL call) and upserts it, or drops it ondeleted/archived. The payload carries no repository, so the spoke receiver routes it ahead of the repository-keyed events and the response carriesprojects_patch. The hub relay forwards repository-keyed deliveries only, so a hosted spoke using a Projects board needs the organization webhook pointed at its own/api/webhook/githubfor this event.- Every invalidating delivery wakes the governor loop. The wake is
debounced for
governor.webhook_wake_debounce_sso a push and its check fan-out coalesce into one cycle, and webhook-triggered cycles are at leastgovernor.webhook_wake_min_gap_sapart so a delivery storm stretches the batch instead of spinning cycles. The wake cycle does not re-list repositories. It plans from the governor's cached actionable snapshot (the one the last cadence tick enumerated, patched by the targeted re-reads described below), so a wake costs the targeted re-read plus the dirty-PR detail refreshes and nothing else: repositories with no dirty PR reuse their previous GraphQL PR batch (graphql_pr_batch.webhook_skips), clean PRs are served from the PR detail cache pastgithub.pr_detail_ttl_s, and only the dirty PRs are re-read. The log line isgithub webhook wake: running an early eval cycle(reason,deliveries,coalesced,source=cached), followed byeval cycle planning from cached snapshot. - Webhooks are healthy while the last delivery for a managed repository is
newer than
2 × governor.eval_interval_s. The first healthy window puts the loop in webhook mode: the governor's base eval interval rises togovernor.eval_interval_webhook_s(default900, capped bygovernor.eval_interval_max_s) — safe because the wakes above carry the latency — and/api/status.evalModereadswebhook,/api/status.webhooks.modereadswebhook. The API budget stretch still applies; the larger interval wins. - Fallback to polling (webhooks.md).
Webhook mode degrades when the window lapses while GitHub was active
(a cadence pass listed an item updated after the newest delivery) or when
deliveries reach the receiver but fail verification; a quiet repository
with no deliveries and no activity keeps the stretched tick. The loop
re-checks as the window closes (not just at the next stretched tick),
resets its ticker to
governor.eval_interval_s, logs one INFOwebhooks degraded; polling cadence restored(withreason), andevalModereadspoll,webhooks.modereadspollingwithsinceandmode_reason. The way back has hysteresis: deliveries must keep arriving for2consecutive healthy windows (webhooks.healthy_windowscounts them) before one INFOwebhooks healthy; webhook cadence engagedrestores the webhook cadence. pull_requestopened,reopened,synchronize,ready_for_reviewandreview_requesteddeliveries also queue an early review dispatch whenreview.event_drivenis on; see Event-driven dispatch.- A recorded review verdict also wakes the governor loop. When the
relay records a verdict bound to a review dispatch, the head that wrote it
is free and the PR may be ready for its next head or its decision kick, so
the loop runs a cached-snapshot cycle now instead of waiting for an
unrelated delivery or
governor.eval_interval_webhook_s. Advisory verdicts do not wake it. The first verdict wakes at once; later ones insidegovernor.webhook_wake_min_gap_sfold into one wake at the end of the gap.governor.webhook_wake_debounce_sdoes not apply to this wake (its debounce is fixed at 0, so a verdict is never held back), and the spoke logsreview verdict wake: running an early eval cycle from the cached snapshot(#14705). - Every status publish (cadence or wake) invalidates the
/api/stage-trackercache and sends astage-trackerevent on/api/events, so the dashboard's activity monitor refetches at once instead of waiting outdashboard.stage_tracker.cache_ttl_splus its poll. Invalidation only marks the cached body stale: the publish, and every webhook below, returns at once even while a tracker rebuild is running. The next poll starts one rebuild outside any lock; polls that land during it get the previous body withsource: staleandmax-age=0rather than queueing, so a slow build (one sentinel evidence read per open PR, on NFS in hosted hives) can never back up webhook handlers, the governor's publish, or an unbounded line of dashboard requests (#14681). - The activity monitor row moves without waiting for a cycle. A delivery
that names a PR or issue this hive tracks (
pull_request*,check_suite/check_run,status,pushto a PR branch,issue_comment,issues— any action) also drops the/api/stage-trackercache at once and queues a targeted re-read of just that item: oneGETof the PR or issue, debounced bydashboard.stage_tracker.webhook_refresh_debounce_ms(default2000) and coalesced per item, so a check fan-out of twentycheck_rundeliveries costs one request. The re-read patches that row of the status snapshot in place — labels, title, draft flag, last activity; a closed or merged item leaves — then drops the tracker cache again and sends thestage-trackerSSE nudge. The same re-read is handed to the governor, which upserts the item into its cached actionable snapshot — a new issue or PR appears, a closed or merged one leaves, a label change moves it between the actionable / held / sidelined buckets — so the wake cycle that follows plans against the patched item. No repository is re-enumerated. The read goes through the paced REST transport and is skipped outright while the API budget iscritical; it refreshes the PR detail cache, so the following cycle does not pay for it again. The re-read is also remembered fordashboard.stage_tracker.webhook_refresh_replay_window_s(default1800) and re-applied to every status snapshot published meanwhile: an eval cycle that enumerated the item before the close still publishes from that enumeration minutes later, and without the replay its publish put the closed row back until the next full enumeration.dashboard.stage_tracker.webhook_refresh: falseturns the re-read off (deliveries then still invalidate caches and wake the loop, and wake cycles fall back to a full enumeration because nothing else patches the snapshot). Independently of the re-read, the one payload fact the tracker trusts without a confirming read is a closed or mergedstateon the issue or pull request the delivery embeds: on any action (labeled,unlabeled,created, not onlyclosed) that row leaves at once, so a refused or failed re-read cannot leave a closed item listed open. As a backstop for deliveries that never arrived, on each status publish it also lists each repo's items closed since its last look everydashboard.stage_tracker.closed_sweep_interval_s(default300; one list call per repo; negative disables) and evicts any still shown.
The full model — the three writers, the accuracy guarantees of #13373, every cadence key with its default, and the webhook-outage fallback — is webhooks.md. In brief: inbound webhooks cost nothing against the GitHub rate limit; outbound API calls do. The governor therefore keeps three distinct paths and never lets a delivery trigger a full re-list:
| Path | Trigger | GitHub cost | What it does |
|---|---|---|---|
| Reconcile | cadence tick — governor.eval_interval_webhook_s (default 900) while webhooks are healthy, governor.eval_interval_s otherwise |
full: lists issues and PRs for every tracked repo | Authoritative. Rebuilds the actionable snapshot from scratch and overwrites every webhook patch. If the pass fails outright (every repo refused: rate limit, 429, 5xx) the previous snapshot — patches included — stays published, marked stale for every repo (stale_repos, repo card countsStale), and the next tick retries; a repo refused on its own is served from its last-good enumeration the same way. |
| Webhook patch | one delivery naming an issue/PR | one GET per item, debounced and coalesced |
Upserts, moves or removes that single item in both the activity-monitor row and the governor's cached snapshot. Counted in /api/status.webhookPatches. |
| Cached cycle | webhook wake, review wake, continuous-agent wake | dirty-PR detail refreshes only | Runs the governor's decision pass (kicks, automerge, stage moves) from the patched snapshot without listing. source=cached in the wake log; counted in /api/status.cachedCycles. Before the first reconcile has produced a snapshot a wake enumerates instead. |
Cadence ticks reconcile; webhooks keep the picture current in between. The priority order is accuracy of issue/PR state, then latency, then API thrift, which fixes four rules for the patch path:
- The payload is a hint, never state. A delivery only names the item;
the GET decides what the row says. A
closeddelivery whose re-read finds the item open leaves it open. - Upserts are monotonic. A re-read whose
updated_atis older than the copy already held (two deliveries' GETs landing out of order) is dropped. Each applied read is recorded in the snapshot'sitem_patches(fetch time,updated_at, head SHA,X-GitHub-Deliveryid) for audit. - Closed/merged removes at once; reopened re-adds — both after the confirming GET.
- Irreversible actions are freshness-bounded. Before an issue close or a
PR merge planned from the snapshot (stage sync, close-on-merge, automerge
sweep), the governor refuses outright while that repo's share is stale and
otherwise re-reads the item once if its copy is older than 60 s
(
DecisionFreshnessBound), proceeding only if it is still open. Refused actions logirreversible action deferredand retry next pass.
Startup runs a full enumeration before the first decision pass; a cold cache
whose GitHub share failed is published stale, never as "0 items".
/api/status.staleSince (and per-repo countsStaleSince) carries the
stale-as-of time and the Overview shows a stale · as of HH:MM badge.
Raising eval_interval_webhook_s lowers list-call volume further at the cost
of a longer window before a missed delivery is corrected by reconciliation.
With the per-repo state cache on (forgestate: block in hive.yaml,
default off; #13373) the
governor work source stops listing GitHub on the cadence tick too
(#13375). The cache's own
reconcile loop (forgestate.reconcile_interval_s, default 900, shortened
to one minute while a repo is stale or its drift is high) is the only
full list; every governor pass — cadence tick, webhook wake, review wake —
takes each repo's open issues and PRs from the cached snapshot and classifies
them exactly as before. What changes:
- Startup gate. No decision pass runs before every repo has completed
its first successful reconcile. The loop logs
forgestate: waiting for the first reconcile before the first decision passonce a minute while it waits andfirst reconcile completewhen it is released. A repo whose cache is not ready in a later pass is served from its last-good enumeration and marked stale, like a transient GitHub refusal. - Stale is inherited. A repo whose last reconcile failed keeps its
last-good items and appears in
stale_reposwithstale_sinceset to the last good reconcile, so the same close/merge guard applies. - Freshness bound before a kick. Every GitHub ref in an assembled kick
goes through the same 60 s bound as close and merge: a copy younger than
the bound is kicked as-is, an older one is confirmed with one targeted
GET(dropped from the kick if it closed or merged meanwhile), and a ref in a stale repo is withheld (kick ref withheld: snapshot not fresh enough to act on). A kick whose refs all fail is not sent. - Single-item reads feed the cache. The webhook patch
GETand the freshness confirmationGETare observed into the cache (as a webhook patch carrying theX-GitHub-Deliveryid, or as a read-through), and the hive's own label add/remove, issue close and PR merge are written through immediately, so the next pass sees them without waiting for a webhook. item_patchescovers every cached item. With the cache, the pass'sgenerated_atsays nothing about an item's age, so the fetch time of every item served from the cache is recorded there; the closed attributed PR history scan is reused for one reconcile interval rather than re-run on every wake.- Automerge lanes read the same cache. The self-authored, trusted-author
and contributor-lane sweeps take their open-PR set from the cached
snapshot and run event-driven with a 5-minute floor pass; see
hive-merge.md→ "Event-driven sweeps on the ForgeState cache" (#13377).
Repos added to the config after boot list live until the next restart rebuilds the store with them. Enabling the cache changes no classification: only the two list calls per repo move behind it.
After a GitHub rate-limit refusal (a secondary-limit 403 or a primary-limit reset) the shared transport enters a slow-start caution window and paces requests about 2s apart. Pacing is prioritised so periodic housekeeping cannot starve the governor:
- the reconcile pass (
EnumerateActionable) runs high priority and is bounded only by its own deadline; - ordinary requests may queue at most 30s ahead;
- housekeeping (self-authored and queued automerge sweeps, the review-request
watcher, advisory digest lookups, the dashboard metrics and fleet-stats
collectors) runs low priority: a request that would queue more than 5s
ahead is refused immediately (
github pacing: request shed) instead of taking a slot, and a tick whose first slot is already further out than the tick could wait is skipped outright (low-priority work shed, logged at most once a minute per caller) and retried on its next tick; - the self-authored automerge sweep backs its tick off to at least 60s while the window is engaged and resumes its adaptive interval when it ends;
- GitHub App installation-token mints (and the other App-JWT endpoints) do not go through the pacer at all: they are a separate rate-limit bucket, and a mint that queued behind paced traffic failed every request that needed the token.
- The review-request watcher runs on events. The relay that submits
hive-reviewrequests (review_request_watcher.go) no longer polls its drop box every 10 s. A request file drop wakes it through a directory notification, andissue_comment,pull_request_review,pull_request_review_commentandissueslabeled/unlabeleddeliveries wake it for a pass (/api/status.webhooksdeliveries reportwoke_review_watcher). While webhooks are healthy the unprompted scan is a five-minute reconcile (ReviewRequestWebhookFallbackInterval); when webhooks go stale, or the drop box cannot be watched, the watcher returns to the legacy 10 s poll. A--reviserequest for a repo outsidereview.revise_reposis a policy denial (.denied, one WARN), not a retried failure. - The advisory issue is resolved from the ForgeState cache. When a
forgestatestore is bound to the GitHub client (forgestate.enabled, #13373),EnsureAdvisoryIssueanswers from the cache: the memoized advisory issue is read through the cache within its freshness bound (zero GitHub calls on a healthy cycle), a stale or missing cached copy costs one targetedGETof that issue, and a closed advisory issue is reopened in place and written through. The search → label list → title-scan path runs only when the cache cannot answer (repo not yet reconciled, or no labelled advisory issue to confirm).
/api/status and /api/gh-rate-limits expose
webhooks: {healthy, last_event_at, events_1h, invalidations_1h, targeted_refreshes_1h, tracker_invalidations_1h, mode, since, mode_reason, healthy_windows} (the middle two count the single-item re-reads and the
tracker cache drops webhooks caused; mode is webhook | polling and
since when it began — see webhooks.md);
/api/status adds evalMode (webhook | poll), evalIntervalS,
lastWakeReason, lastWakeAt and webhookWakes;
graphql_pr_batch.webhook_skips counts batch queries skipped because nothing
changed.
governor:
eval_interval_s: 300
eval_interval_webhook_s: 900
webhook_wake_debounce_s: 10
webhook_wake_min_gap_s: 30
dashboard:
stage_tracker:
webhook_refresh: true # re-read the PR/issue a delivery names
webhook_refresh_debounce_ms: 2000
webhook_refresh_replay_window_s: 1800 # re-apply re-reads to snapshots published meanwhile
closed_sweep_interval_s: 300 # list items closed since the last look and evict rows still shown; -1 disablesgithub.token: ${HIVE_GITHUB_TOKEN} creates the main GitHub client when a GitHub
App is not configured. The code reads issues, PRs, commits, contents, checks,
commit statuses, contributors, and search results; it can also create/update
issues and comments, add/remove/create labels, open PRs, approve PRs for the
merge queue, and squash-merge queued PRs.
Minimum practical PAT permissions for full PAT-authenticated operation:
- Classic PAT:
repofor private repositories (public_repois enough only when every managed repo is public and no private org data is needed). - Fine-grained PAT: select the managed repositories and grant:
- Contents: read/write (branch pushes by agents and content reads)
- Pull requests: read/write (list, open, approve, merge)
- Issues: read/write (list, comments, labels, advisory issue)
- Checks: read-only and Commit statuses: read-only (CI/merge gating)
- Administration: read-only (branch-protection verification before merge requests)
- Metadata: read-only (required by GitHub)
If you use a GitHub App, configure equivalent repository permissions on the App
installation instead of broadening the PAT. A repo-moved GitHub App verdict
means the App/key/installation are healthy, but the hive still names the old
repository owner after an org transfer; update the configured project org rather
than trying to add the moved repository back to the old installation.
Added for #14681 (hive process RSS growing hundreds of MiB/min with no pprof to name the culprit).
| Name | Source | Default | Purpose |
|---|---|---|---|
debug.pprof_addr |
hive.yaml |
127.0.0.1:6060 |
net/http/pprof listener. Loopback only: a non-loopback host (0.0.0.0, pod IP) is rejected at startup with a warning and the default is used instead. off disables the listener. Profiles are under /debug/pprof/; /debug/runtime prints the same counters as the mem: log line. |
GOMEMLIMIT |
env | 85% of the cgroup memory limit | Go soft memory limit. When the env var is unset the hive reads /sys/fs/cgroup/memory.max (v2) or memory/memory.limit_in_bytes (v1) and sets MemoryLimitPercent (85%) of it via debug.SetMemoryLimit; an unlimited cgroup leaves the runtime default. Set the env var to override. Logged at startup as runtime: memory limit with `source=env |
mem: runtime snapshot |
log (INFO, every 60 s) | always on | heap_inuse, heap_live, heap_objects, total_sys, next_gc, gc_cycles, goroutines, threads (OS threads from /proc/self/status), gomemlimit. Read from runtime/metrics, so no stop-the-world. |
The same snapshot is embedded in GET /api/health as runtime so the
dashboard health probe and kubectl exec … curl see goroutine count and heap
without the pprof port. Taking profiles on a pod:
kubectl -n <ns> port-forward deploy/hive 6060:6060 &
go tool pprof -top http://127.0.0.1:6060/debug/pprof/heap # inuse_space
curl -s 'http://127.0.0.1:6060/debug/pprof/goroutine?debug=1' | head # parked goroutines| Name | Source | Purpose |
|---|---|---|
--config |
flag | Path to hive.yaml; default /etc/hive/hive.yaml unless HIVE_CONFIG is set. |
HIVE_CONFIG |
env | Sets the default of --config. An explicit --config outranks it — and the image ships one (CMD ["--config", "/etc/hive/hive.yaml"]), so entrypoint.sh appends --config "$HIVE_CONFIG" to the launch argv when the variable is set. Without that append the variable is inert in the container (#4973). |
HIVE_MODE=hub |
env | Starts the hub server instead of a spoke dashboard. |
HIVE_HUB_PORT |
env | Hub listen port in hub mode; default 3001. |
HIVE_SINGLETON_LOCK |
env | Internal/escape hatch for the process singleton lock path; value off disables the guard. |
| Name | Purpose |
|---|---|
HIVE_GITHUB_TOKEN |
Main PAT fallback when github.token is empty; also used for token identity/fleet stats fallback. Wrong-scope tokens fail with generic 403s at request time — see github-app-setup.md for the required scopes per ACMM tier. |
GH_APP_KEY_FILE |
GitHub App private-key path fallback when github.key_file is empty. |
HIVE_DASHBOARD_TOKEN |
Shared dashboard/API token fallback for dashboard.auth_token. Any non-empty string is accepted with no strength check — generate one with openssl rand -hex 32; see env-vars.md. |
| Name | Purpose |
|---|---|
HIVE_HUB_URL |
Hub URL override for heartbeat/spoke registration. |
HIVE_CLUSTER_ID |
Hosted cluster ID override. |
HIVE_ID |
Hive ID override. |
HIVE_LEVEL |
ACMM level bootstrap/override used by hosted flows. |
HIVE_COVERAGE_BADGE_URL |
Source of the coverage percentage on the ci-maintainer card: an http(s) badge URL (shields JSON or SVG) or repo://<ref>/<path> read from the primary repo via the App client. See env-vars.md. |
| Name | Purpose |
|---|---|
HIVE_METRICS_ENABLED |
Registers Prometheus /metrics when set to 1, true, yes, or on; off by default (route not registered — scrapes 404) because it exposes estimated cost data. |
HIVE_METRICS_TOKEN |
Required whenever metrics are enabled (#3804): scrapers must send Authorization: Bearer <token> (configure Prometheus bearer_token). Enabled-but-tokenless serves 403 with an error naming both variables, and the hive logs a startup warning; the cost/agent series are never served unauthenticated. |
Note on
tokens_24h: despite its name, the per-spoke heartbeat fieldtokens_24h(stored on the hub astotalTokens24h) is a cumulative total, not a rolling 24-hour window. Read it as lifetime token consumption for the spoke. See token-tracking.md for the full heartbeat and/api/saas/usagerollup details.
| Name | Default | Purpose |
|---|---|---|
HIVE_CONTEXT7_MAX_SNIPPETS |
40 |
Snippets kept from one Context7 llms.txt fetch (document import or context7 connector). |
HIVE_CONTEXT7_MAX_SNIPPET_BYTES |
4096 |
A single snippet larger than this is dropped whole. |
HIVE_CONTEXT7_MAX_TOTAL_BYTES |
16384 |
Total content kept per fetch, marker included; values under 1024 are raised to 1024. |
Snippets are dropped whole (never clipped) by relevance to the topic, the
stored content ends with a … truncated: N snippets / M bytes omitted (size cap) line, and the fetch logs the library. Unparseable or non-positive values
fall back to the default. The kick primer's own byte cap is the yaml key
knowledge.primer.max_bytes (default 4096); see
knowledge-curator.md.
| Name | Default | Purpose |
|---|---|---|
HIVE_VLLM_ENDPOINT |
unset | Comma-separated vLLM endpoint list; set only to a cluster-reachable endpoint. Unset disables the built-in vLLM gateway route. |
HIVE_LLMD_ENDPOINT |
http://hive-llm-d-epp.hive-inference.svc.cluster.local:8000 |
Comma-separated llm-d endpoint list. |