From raw GitHub history to guard-railed comments — every gear of an autonomous triage pipeline, end to end.
system snapshot · 2026-08-03 · NousResearch/hermes-agent
the mechanics are current; the counts age gracefully.
Harvest pulls the full repo history into a private graph database; Derivation folds edges into duplicate/topic complexes; Triage runs a three-stage LLM pass (assess, verify, draft) per complex; Curation tries to refute every draft before it may leave; Execution posts through a server-side guardrail chain under a standing order whose hourly pace an adaptive AIMD governor tunes against GitHub's live limits; Clockwork paces it all with timers plus an event-driven dispatcher — including an expansion pass that seeds a first-post draft for every uncovered open node and an LLM-verdicted comment-mention lane that feeds the fold graph; Observe writes every run into a journal that feeds alarms, reports and a live dashboard — all standing on a least-privilege Postgres foundation.
🕸️Complex Rebuild (:33)union-find over fold edgesshadow staging → atomic swap
Global connected components over the fold set: closes always, pipeline edges always, file heuristic ≥ 0.75, embedding ≥ 0.88, LLM-verdicted comment mentions ≥ 0.9 (new 2026-08-03); raw reference edges stay deliberately excluded — only the verdict gate lets a mention fold.
App-side union-find takes ~0.6 s where SQL label propagation took >25 minutes.
Result is staged in shadow tables, then swapped atomically by RENAME with a 10 s lock timeout; a failed lock rolls back to a consistent old generation.
The slot first consumes dirty batches, then rebuilds, then runs both post-swap guards.
Snapshot: 12,447 complexes from 80,590 fold edges.
One LLM call writes the consolidation comment as a triage_draft; the draft unit never posts.
Drafts carry a mermaid graph: duplicate families as undirected subgraphs, best-fix edge labels, every complex issue, clickable nodes, top-N plus "+K more".
Draft validation includes the English-only gate — one German signal word fails the draft.
Unverified relations say "unverified" — no silent downgrade to a plausible-sounding default.
doneerrorre-entry only on stand moverestalestand moved → re-enqueue
🤖LLM Backendgpt-5.6-sol · tool-lessinjection canary at start
Pluggable backend layer; production path is Codex through the Hermes runtime, invoked tool-less.
A toolset-inertness canary runs before the first real call of every process — the review path is provably static.
The default backend is a fixture (test double): every production entry point must pin the env, a documented operational trap.
The context window figure is measured live from the runtime, not taken from training data.
📜Prior Dossierown posts fed back structureddelta re-assess on moved stand
When a posted complex moves, re-assessment gets a structured dossier: own past comments byte-exact, active and superseded verdicts, assessment notes — all source-attributed.
The dossier is labeled as "earlier OWN LLM judgment, to re-validate — not fact".
Echo protection: the own post reaches the prompt ONLY through this block, never through discussion lines.
Output is a verdict supersede plus a followup draft; the block caps at 40 rendered verdicts with the newest own post always complete.
🧼Curation Slots (:22/:52)pre-curate the pool · ×6in-drain path · same gates
The pre-curation slot works ahead: uncurated drafts run both stages with parallel LLM calls (batch 150, parallelism 20), so the drain mostly draws from a pass pool.
Second entry path: whatever reaches the drain uncurated runs the same run_curation inline — not a single gate changes between paths.
Failed candidates are steered out and journaled, never posted.
Snapshot example: 30 candidates → 17 pass / 13 fail / 14 repaired in one slot.
🔧Repair Loopexactly one re-draft on failburst unit drains backlog
After an LLM fail with substantive reasons, the reasons go back into the prompt for EXACTLY ONE re-draft, followed by a final re-curation.
The one-repair maximum is structural (selection is uncurated-only), not a counter.
A transient systemd burst unit exists to drain the historical fail backlog through the same curate_one path, batch-journaled with its own kill switch — fail drafts have no other re-entry.
First-pass repairs carry their weight: measured batches pass mostly without any repair at all.
🔤English-Only Gatesignal-word regex · no LLMenforced twice
All GitHub-bound text is strictly English, enforced deterministically by a signal-word regex.
The gate runs twice: at draft validation and again as curation check g.
The curation prompt itself is English — the whole outward-facing path speaks one language.
🚚Auto-Drain (every minute)posts curated drafts, cappedfollowups first · limit 30
The drain selects candidates issue-first and pass-pool-first, newest-first, up to the slot limit (60), and walks each through refresh → arm → guardrail chain → gh post → mark posted; a draft that failed a post attempt re-enters only after an exponential per-draft backoff (2·2ⁿ⁻¹ min, capped at 60).
The hourly cap is owned by the post governor — an AIMD state machine inside the pace tick: +25/h per clean window, ×0.6 on gh errors, a 6/h probe cadence while blocked, ceiling 480/h just under GitHub's content limit.
Steered-out branches (skipped_stale, curation fails, the blocked-* family) are all journaled per run.
Three start paths exist: the minute timer, the pace dispatcher, and the Hermes trigger layer — see Clockwork.
🌡️Freshness Gatelive refresh before every postage ≤ 10 min, server-checked
Immediately before a post, ONE GraphQL call refreshes state, updated_at and head_oid of every complex member, last-write-wins, with a logged proof line.
The server then enforces a maximum refresh age (10 min policy) — a stale refresh means blocked-norefresh, not a hopeful post.
A failed refresh skips the draft fail-closed; state moved between claim and post counts as skipped_stale.
Origin is a verbatim user directive: quality before quantity.
🎖️Standing Ordergrade · caps · provenancerevoke = kill arm №1
Every post series runs under an armed standing order carrying an autonomy grade and caps (steady and backfill).
Arming records trigger provenance: cron, event or manual — not hard-coded.
The active order at snapshot: grade 1; the daily steady cap is effectively unbounded since the hourly authority moved to the post governor (2026-08-03). Revoking the order is the first arm of the kill-switch triad.
Quota caps always follow the ACTIVE order — a lesson from a false cap alarm.
🚦Refusal Verdictsblocked-* family · ≥8 exitsevery refusal persisted
Every refusal is a named verdict, never an exception: dry-run, blocked-header, blocked-target-closed, blocked-reply-disabled, blocked-reply-cap, blocked-reply-cooldown, blocked-norefresh, blocked-dedup, blocked-cap.
Each one writes a post_attempt_log row plus an immutable audit line — refusals are data, not silence.
The dedup claim uses INSERT … ON CONFLICT semantics: no row claimed means someone already posted here.
Completion is symmetric: vollzug_mark_posted or vollzug_mark_error, and errors are reclaimable.
5gh api · issue comments (review path retired 2026-08-03)
🧐PR Reviewsretired 2026-08-03 · PRs post as commentsone policy row brings it back
Retired 2026-08-03 (post.pr_as_review=false): GitHub throttles review creation separately from issue comments (HTTP 422, only ~15% of reviews passed), so PR targets now post as ordinary issue comments through the freely flowing channel.
While active, the review event was mapped structurally from graph facts: APPROVE only if the target IS the complex's active best-fix edge; otherwise COMMENT. LLM text never chooses the event.
REQUEST_CHANGES is deliberately unused — there is no structural signal for it.
An echo guard filters the bot's own reviews out of its inputs.
Three books under the chain: post_quota (atomic minute/hour/day reservations), post_attempt_log (every decision), vollzug_audit_log (append-only; delete and update are denied).
Reservation failure equals "cap reached" — fail-closed by construction.
Redaction patterns applied to journal details are shared with the alarm system.
Every slot script documents its raster position; unit copies live in the repo, and /etc↔repo parity is a session close gate.
Oneshot lesson: a running oneshot reports ActiveState=activating the whole time — health checks must read ActiveState, is-active deceives.
⏩Pace Dispatcher (:01/5)backlog signals pull work earlytimers stay the backstop
Every five minutes, four backlog signals (dirty nodes → rebuild, queued → triage, uncurated → curation, pass pool → drain) may start a slot EARLY; the timers remain the guaranteed floor.
The post governor (an AIMD state machine inside the pace tick, 2026-08-03) sets the posting caps itself: +25/h per clean 10-minute window, ×0.6 on gh errors, 6/h probe cadence while fully blocked, ceiling 480/h. Its error signal counts the issue channel only, and a window that was clean but quota-capped still counts as growth — self-throttling is not "no traffic".
A conflict matrix keeps mutually exclusive units apart (rebuild ⊥ triage ⊥ nightly pass); drain and curation run independently.
Minimum gaps per cycle are computed against the run journal; starts are non-blocking; busy detection reads ActiveState.
Fail-closed behind its own flag; stage 2 (repo activity as a signal) is deliberately a separate, open step.
🛑Kill Switches3 arms + 9 fail-closed flags
The triad: revoke the standing order · disable triage · drop the cap — three independent arms.
Nine enabled/gate flags in ops.config, one per cycle family; every slot script checks its flag BEFORE working and skips loudly.
Any value other than true means off — fail-closed, never fail-open.
Every attempted post is a ledger row with a claimed→posted|error state machine.
The dedup anchor is (target kind, target number, author) — claiming happens by conditional upsert; no row means someone already posted.
GitHub comment/review IDs are backfilled by the comment harvest; posted_stand freezes the complex state at post time.
🧠Embedding Store1024-d vectors · norm-checked
Vectors are stored with a norm CHECK (0.99–1.01) — an unnormalized vector cannot enter.
A text hash makes the pending selection idempotent; ANN neighbors are same-kind by construction.
Backups exclude the vector data: regenerable for cents.
🎚️Policy Thresholds14 knobs as data, not code
All scoring and folding thresholds are table rows — changing one is an UPDATE, not a deploy.
The 14 knobs include ANN k, fold thresholds, fanout caps, component-size limits and the freshness age.
Every derivation run prints the active snapshot; operational mutations run as single statements with a separate verify.
⚙️Ops Schemajournal · config · alarms · prices
Operational truth is separated from graph truth in its own schema: run journal, run events, config, alarms, alarm watermarks, model prices.
The config table carries all kill switches and operating parameters (38 keys at snapshot).
Consumers mutate alarms only through definer functions.
🛡️Post-Swap Guardsview re-bind + grant probedrift fails the unit, loudly
Views and grants bind to table OIDs — a rename swap once left both pointing at the old generation (a stale-read every second hour).
Guard 1 re-binds all dependent views after the swap and verifies view freshness equals table freshness.
Guard 2 probes a SELECT as the dashboard role against the live side.
Any drift fails the rebuild unit loudly instead of serving a silent stale hour.
Cross-cutting safeguards
🔒Injection Hardeningneutralize · nonce · canary
Hostile content flow is mapped end to end; three defenses: role/turn markers are broken, untrusted content sits in a content-secret nonce block, and an injection canary proves the backend inert.
The guardrail chain is body-blind — LLM text never reaches a scope decision; PR review events map from graph facts only.
The hardening was a mandatory gate before grade-1 autonomy.
🧪Probe Suites~30 red-first gates
About 30 standalone probe modules across all layers; every build wave is proven RED first, then green.
Probe fixtures never anchor on live data (a learned rule); close-out sweeps count their gates explicitly (e.g. 8/8, 25/25).
One suite enforces its own maintenance-window guard and refuses to run against a live flag without an explicit override.
📚Governance Corpusqueue · boards · warnings
Build and operations decisions are versioned truth: a work queue with state, two decision boards, amendments, and a documented rule that re-deciding happens BEFORE a wave is built.
User directives are quoted verbatim and bind gates; stop conditions are fixed (a red gate without a plan-conforming fix stops the loop).
An RLHF-warnings framework (22 axes) is applied as working discipline across sessions.