Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

50 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hermes-plugins

Coding-oriented Hermes plugins derived from dsh-plugins and oh-my-pi. Plugins remain independently installable and reuse native todo, verification, review, MCP, Covey and papercuts stores.

This upgrade requires the accompanying Hermes host changes. The base revision b141dd1881499a98df0853d500e4d666c394a971 alone is insufficient. Local implementation and qualification do not install plugins, restart services or change an active profile.

Inventory

Plugin Implemented contract
classifier-core The shared classifier transport for every plugin that asks the /v1/systemone service: one process-wide ClassifierClient per endpoint (path-loaded through one registered module, so siblings share it), one breaker (breaker_failures consecutive service errors open it for every consumer of that endpoint for breaker_reset seconds; exactly one half-open probe decides), and accepted/declined counters per caller keyed by decline reason. Failures never raise: the caller gets None and the fail-open path is visible through the classifier_status tool and hermes classifier-status (probe: true adds one fresh GET /health; exit 0 only when the breaker is closed and no caller was ever declined as not_configured). Registers no hooks, makes no network call at register time; endpoint, served model, key environment and timeout budgets are configured here and nowhere else.
shadow-gate Promotion gate for shadow-mode decisions: reads the append-only per-decision JSONL ledgers that producer plugins write through the shared ledger.py module, pairs each outcome with the latest earlier verdict for its key, and reports per decision whether an explicitly configured decisions policy (min_pairs, min_agreement, max_false_positive, positive verdict/outcome sets) is satisfied. Records and reports only; qualifies nothing by default, changes no behaviour — an unlisted decision is NOT_QUALIFIED with reason no_policy. shadow_status and hermes shadow-status (NOT_QUALIFIED is a normal answer, exit 0; only an unusable ledger or invalid --decision exits 2).
plugins-doctor Read-only doctor for the plugin estate (hermes plugins-doctor, --json): links — every entry under the plugins dirs resolves to a directory containing a plugin.yaml; settings — enabled plugins' settings validate against their manifest config_schema through the host's own validator; seams — the host contracts the "Native prerequisites" list promises are present here; stale_import — live processes still importing plugin source whose bytes have since changed, proved against digests recorded at each process's register. Every verdict is a (check, plugin, severity, detail) row; nothing is repaired, enabled or rewritten, and the exit status is 1 only when a row is a fail.
continuation Explicit requirement declarations and owned check observations bind native evidence IDs to source hashes, execution workspace and task. Independent review runs asynchronously and releases the turn while pending; its binding and verdict still gate the next completion. Reviewer coverage applies to declared criteria, not undeclared todo bookkeeping. With the rules plugin present, pre_verify also holds until every rule-declared check ref is covered by a BOUND declaration, and the blocker names the exact acceptance declare/bind calls that satisfy it (rule_obligations, on by default); N consecutive holds on the same blocker (friction_after_holds, default 3, 0 disables) file one gate-friction row with the papercuts sink. plugins/continuation/closure.py is the single typed reader for "did this session close?", used by acceptance status and sibling plugins alike.
prewalk Only prewalk_ready performs the model handoff, after a native todo plan and evidence-grounded execution brief. Core located context is required; falsification/replan/acceptance lists are optional. Scratch writes do not signal readiness. Optional replanning is bounded and off by default. The validated handoff is persisted in the plugin's durable state, and the post_compaction observer arms exactly one re-injection of the stored brief into that session's next turn (counted as handoff_reinjections in prewalk_status); a session with no stored brief receives nothing.
covey-tasks Native durable worklists, fenced claims, compact checkpoint/resume handoffs and native discovery diagnostics. Optional linked-worktree ownership guards prevent cooperative workers sharing one workspace. Delivered loop-guard blocks checkpoint the claimed task per blocker_checkpoint (off/first/exhausted; default exhausted, because a checkpoint releases the claim). finish with governed: true is opt-in per call, needs the continuation plugin's closed review closure for the session and records the derived evidence digest; refusals are not_governed_closed, evidence_mismatch, governance_unavailable.
intake Moved to swarmlord/hermes-plugins/intake (fleet-specific); see "Text intake" below.
lilith Model-provider plugin (moved out of the hermes-agent fork): the Lilith local SGLang endpoint with model-aware reasoning controls — Qwen thinking budgets/scaffold with tool-turn auto-disable, DeepSeek-V4 thinking translation instead of Qwen template fields, GLM-5.3-Flash delegated to the ZAI profile's semantics and adapted to SGLang's enable_thinking. Registers at import; installs under the Hermes home's plugins/model-providers/ (not the profile plugins dir). Contract tests pin the request shapes.
rules Scoped rule delivery plus rules_explain: applicability, provenance, precedence, explicit conflicts, omitted entries and check references. Guidance is never executable authorization. A matched rule declaring check_refs/verification_refs that hit a real file-mutating call records one acceptance obligation per rule per session (opaque refs plus the resolved paths, session-scoped, dropped at reset/finalize); recording is independent of the one-time guidance lane, nothing here executes a ref, and rules_explain reports what is owed plus the store's degradation counters.
browser-bladebro bladebro real-browser lane: provides the browser_* tools (names, schemas and results carried over from Hermes' built-in browser tools, which upstream removed 2026-11-03) plus browser_run batches and certified local HTML scenarios, which bind immutable artifact bytes and observed interactions to native verification evidence. Separate lane from core browser_exec (CDP/browser-harness), no behavioral coupling; no override grant needed. Ordinary HTTP browsing is not build attestation. Tabs are per task. on_session_finalize/on_session_reset close a session's tabs and clear its state, and subagent_stop closes a child's tabs. Per-turn on_session_end is deliberately not used, so tabs survive across turns.
loop-guard Bounded structured-error, failed-verification and short-cycle recovery. Delivered interventions, exact next-call hypotheses and matching recovery are distinguished from suppression or exhausted budgets. The first spent budget that would emit one more block files a single gate-friction row with the papercuts sink (friction, on by default; surfaced as friction={reported,last_reason} in loop_recovery status).
auto-review-classifier Advisory screening of operation effects, resolved targets and missing context, with redacted egress and operation-specific native approvals. Selected risky approvals revalidate scope after consent. Asks through the shared classifier-core client; without it every call fails open.
meta-classifier Fork of auto-review-classifier merged with droid 0.244.0's reverse-engineered classifier decisions: Jev LOW/MEDIUM/HIGH or luna off/low/medium/high execute-risk tiers with redacted egress and operation-bound approvals, Droid Shield secret triage before git commit/git push (fail-closed like droid), Factory-Judge local-signals analytics (opt-in JSONL), an agent-facing decision_model tool and hermes meta-classifier-replay. browser_decision ports browser-use/jev-ultrafast's action space: one classifier request returns an operation (click/type/select/scroll_*/wait/done/blocked) plus the compatible element index (and option index for dropdowns) from an observed table only — a suggestion the caller executes and verifies itself. Nine opt-in addons (injection fence, command outcome/secret scrub, completion nudge, subagent report quality, request shape, per-failure friction, memory candidates, test value, delegation complexity) are metadata-first and off by default. While dry_turn is on (default), it enforces keyword-steering's /dry: a call its own risk analysis classes as mutating is refused with rule_key dry_turn and no classifier request at all; read-only tools pass untouched. With shadow_decisions on (default) the risk tier of every command escalated to a human and every Droid Shield screen is appended as a verdict row to the shadow-gate ledger (meta_classifier.risk, meta_classifier.shield), keyed so the human's later approval pairs with it as the outcome row — evidence for a promotion decided elsewhere, never read back here. Classifier calls go through the shared classifier-core transport; the plugin declares no endpoint, model or key of its own. Run it instead of, not alongside, auto-review-classifier.
tool-fff Retired. Use native search_files; no override consent, extra search registration or claimed acceleration.
tool-docling Content-addressed Python Docling conversion, source/converter provenance, selective retrieval and explicit missing extraction metadata. Publication is locked and atomic.
subagent-effort Shadow telemetry and separate paired experiments. Explicit effort choices remain unchanged; automatic routing is NOT_QUALIFIED. Asks through the shared classifier-core client and appends verdict and realized-outcome rows for the subagent_effort.difficulty decision to the shadow-gate ledger; recommendations are still never applied — qualification is an operator-written decisions policy there.
auto-answer-classifier Evidence-supported ranking of all eligible conventional choices, scoped human-approved preferences, and per-question timeout provenance. Human answers and explicit skips remain authoritative. The ranking asks through the shared classifier-core client; without it every recommendation abstains.
keyword-steering Built-in OMP-compatible magic keywords (ultrathink raises the turn's reasoning effort to ultra, orchestrate and workflow steer delegation and staged workflows, cheap/deep/local pin a model-routing role for the turn and dry declares a read-only turn) plus explicit configured modes referencing existing skills/rules. All are deterministic, expire with the turn, expose status and withhold conflicting guidance. The routing keywords map keyword→role through routing_keywords (default same-name); the pin is answered by model-routing with source turn_pin, the consumer's own explicit target still wins, two routing keywords in one turn are contradictory so both are withheld, and per-keyword outcomes appear in steering_status.magic_effects. /dry is only stated here — meta-classifier with dry_turn on is what withholds the mutating calls. Common-word keywords need a //# sigil unless bare: true; negated or common-word hits are declined, optionally tie-broken by the classifier (ambiguous hits only, redacted, fail-open to decline); mode refs are resolved at registration. Only these explicit keywords move effort.
model-routing Named roles (model + optional provider, fallback: inherit|refuse) route delegate model_role, the prewalk executor and review owners to configured models — explicit pins always win, implicit lookups never refuse. route_status/route_apply expose and materialize pins; cross-provider targets are supported. pin_turn_role/clear_turn_pin serve keyword-steering's routing keywords: while a pin is held, every role request in that session answers with the pinned model and source turn_pin (explicit where the host's receipt law does not recognize that label), the reason names the role and who asked, and route_status lists held pins — runtime state only, expired with the turn. Nothing picks a model from classifier output, cost or quota.
papercuts Typed, redacted reports and native recurrence/triage. Opt-in authoritative tool-failure capture has a cross-process quota; the native binary continues owning the store. gate_friction (off) installs the sink that files continuation, loop-guard and chain-compaction refusals as workflow papercuts — filing shares auto_capture's redaction and daily quota, so enable both; the friction mode shows the per-source/per-reason counters (disabled, no_sink included). draft_rules (off) turns papercuts at or above draft_rule_after recurrences into inert rule drafts (enabled: false) under draft_rule_dir, which the rules plugin never scans; mode: drafts runs the step on demand.
fast-compaction Lossless-by-deletion compaction overlay (port of tamaratran/fast-jev-compaction): registers as the ContextEngine but subclasses the built-in compressor and only intercepts compress(). register_engine: false yields the host's single engine slot to an overlay that composes this pruner (for example chain-compaction), keeping the plugin enabled for its settings, pruner and status tool. Unpinned tool calls/results are paired, the whole conversation is fitted into a bounded state, and the classifier service answers two keep/drop heads per call in batches of 16 calls (the service's 32-head limit) through the shared classifier-core client — its endpoint/served_model/api_key_env settings live there, and without that plugin every prune falls back with the counted reason classifier_core_unavailable; configured pinned_tools (default prewalk_ready) keep a plan handoff's pair out of both the classifier and the proactive eviction path; stale pairs are dropped or head-truncated with a one-line note, everything kept stays verbatim, user/assistant text is never rewritten. Below min_reduction, a malformed answer, an unreachable endpoint, un-redactable state or an unfittable history, the call falls through to the untouched built-in summary path — the overlay never removes the fallback. Optional proactive_prune_mode mounts the host's mid-session seam (turn_preflight): fifo evicts oldest unpinned tool pairs with zero network cost, classifier runs the keep/drop pass first and degrades to fifo per-turn; the host's trigger, reclaim floor and regrowth-runway gates stay authoritative either way. fast_compaction_status reports attempts, prunes, per-reason fallbacks and proactive tallies.
chain-compaction Ordered compaction cascade — the overlay engine: runs configured stages (drop_reasoning — pure deletion of chain-of-thought from assistant turns older than the protected window, visible text and tool calls byte-identical, first in the default pipeline; prune — reuses the fast-compaction classifier pruner; clip — head+tail truncation of oversized tool results; drop_stale — positional removal of old tool pairs; summary — the normal compaction engine itself) in sequence, advancing to the next whenever a stage fails, crashes or leaves the cumulative reduction below target_reduction, and committing verbatim output the moment the reduction is substantial enough. pinned_tools (default prewalk_ready) keep a plan handoff's pair out of every reduction path. summary is terminal and configurable: without it the built-in summary is only the under-delivery backstop — and that under-delivery files one gate-friction row with the papercuts sink before the handoff (absent papercuts, counted as friction_unavailable and never filed) — and with it last (for example prune,summary) the stages compose — the earlier ones reduce, the built-in summarises what survived, and no earlier stage may commit early and skip it. Extra strategies plug in through stage_modules (a .py path or dotted module exposing STAGE_NAME and run_stage(messages, cfg, tokens) -> (messages, stats)); a module that fails to load is logged and skipped, never fatal, and a name may not shadow a built-in stage. Optional proactive_prune_mode (off/fifo/classifier, same contract as fast-compaction) mounts the host's mid-session eviction seam. chain_compaction_status reports the configured order, external stages, per-stage miss tallies and the last trace. The host allows exactly one context-engine registration, so the overlay owns it and the others contribute stages: with chain-compaction selected, set fast-compaction.settings.register_engine: false so the two do not contend for the slot.

Throughput posture

These plugins optimize for completed work per unit of agent time. Clean paths add no prompts or ceremony; classifiers and telemetry stay off launch-critical paths; rule guidance rides successful tool results instead of forcing retries; asynchronous review releases the turn while preserving the binding; optional planning fields do not block a valid handoff; external adapters cache or reuse repeated identity/observation work. Hard stops remain only for concrete evidence, authorization, credential, or mutation-safety risks.

Deliberate differences from OMP

These plugins intentionally narrow OMP's behavior wherever implicit magic would undermine evidence, custody or explicit choice. If you are coming from OMP, expect these differences:

Area OMP behavior Here What to expect
Prewalk handoff The planning model can hand off implicitly (for example on the first write). Only prewalk_ready switches models, and only with a native todo plan plus explicit located files, symbols/callers, constraints, chosen approach and acceptance IDs. The execution target may be an explicit target_model/target_provider or the routed executor role, on the same or another provider. No implicit model switches. A plan that cannot point at real context stays on the planning model; scratch writes never count as readiness. A cross-provider target is validated at arm time and refuses to arm if unresolvable.
Completion A turn completes when the model stops. continuation holds coding turns in pre_verify until the native todo ledger is clear, verification evidence is fresh, and an independent review closed on the exact reviewed change; declared acceptance criteria must bind real native evidence. Deliberately more demanding, slower completion: "Completion unverified" until the evidence exists. A blocked or cancelled disposition is never completion.
Magic keywords Typed trigger phrases silently change behavior (modes, effort). Restored OMP-compatible built-ins: ultrathink raises the current turn's reasoning effort to ultra through the host seam (guidance-only fallback on hosts without it), orchestrate and workflow steer delegation and staged-workflow discipline. Same deterministic rules as user modes: prose-only whole-word triggers, identifiers/quotes/code/paths never trigger, conflicts withhold both sides, steering_status shows what activated and what the effort request did. Built-in names are reserved; user modes configure their own triggers. Typing ultrathink works out of the box and is visible, never silent: the reminder states the raise and steering_status records the provenance receipt.
Subagent effort Native effort controls can change reasoning effort per call. subagent-effort is shadow-only: verdicts and recommendations are recorded, never applied. Automatic routing stays NOT_QUALIFIED until paired evidence qualifies it. Effort changes only through explicit user intent (the ultrathink keyword or the model/provider configuration) via PluginContext.set_turn_reasoning — never from a classifier's recommendation.
Model routing OMP routes model selection by role, including metered/quota-aware routing. model-routing supports role routing: configured roles (model + optional provider) resolve with precedence explicit > route > inherit, fallback: inherit|refuse for explicit delegate model_role requests, and cross-provider targets validated at arm time. Metered/quota-based routing is not ported. Role routing works via model-routing; nothing chooses a model from classifier output, cost or quota.

Native prerequisites

The matching host source supplies these shared contracts; there is no substitute scheduler or evidence database in this repository:

  • agent.verification_evidence: explicit row lookup, external evidence, source/artifact metadata, terminal evidence IDs, disabled-ledger refusal and a durable edit watermark. A later check cannot revive pre-edit evidence.
  • hermes_cli.plugins / plugins_dispatch: all callbacks run; block wins over approval; delivery/suppression receipts use unique directive identities. post_tool_call_settled fires exactly once after every post_tool_call dispatch, including when bounded observers are suppressed or time out, so call-observation reservations settle without being wedged forever. Modified operations are presented at approval. A selected internal revalidate(final_args) must return exactly True after consent or the operation is vetoed.
  • PluginContext.cancel_model_call(reason, *, hard=False, agent=None): a plugin-facing mid-stream cancellation seam. It requests interruption of the in-flight model/tool loop; the host stream monitor observes the request within its ~0.3s cadence and aborts in-flight provider streams (marks the request cancelled, closes request-local sockets, joins the streaming worker; the aborted call is not silently retried). Returns an honest receipt ({"cancelled": bool, "reason": ...}), never raises, and reports cancelled: False on surfaces with no resolvable agent (e.g. gateway). It is a cancellation request, not a sandbox: completed work is not stopped and completion is not immediate.
  • PluginContext.set_turn_reasoning(effort, reason="", agent=None): turn-scoped explicit reasoning control. Validates effort against the native vocabulary (minimal … xhigh, max, ultra, plus disable values), records the override for the current turn only (the turn finalizer clears it), and returns an honest receipt; unsupported values are refused, never coerced. For explicit user intent only (e.g. the ultrathink magic keyword) — never an inferred or classifier-driven switch.
  • Native final-output transforms compose rather than overwrite each other's warnings; the original streamed text remains available for reconciliation.
  • tools.clarify_tool: each question reports answered, skipped or timed_out with its qid.
  • PluginContext.call_mcp(..., replay_on_failure=False): trusted out-of-band no-replay policy and a host-minted token identifying the exact MCP connection. Remote result fields are not connection authority.

Install the complete corresponding host patch before enabling consumers, then construct new agents. Updating source or configuration does not hot-reload already imported modules. The implementation checkout used for qualification is /home/common/projects/hermes-agent; the installed host and active profiles were not modified.

Installation

Install/link each selected directory containing plugin.yaml into the intended profile's $HERMES_HOME/plugins/, then use that profile's normal enable/configuration commands. For an unmanaged profile after installing the required host:

hermes config set agent.verify_on_stop true
hermes plugins enable continuation
hermes plugins enable prewalk

Use hermes -p <profile> or an explicit HERMES_HOME. Managed installations must change their authoritative managed configuration instead. Classifier keys come from the environment named by api_key_env, never from manifests. Missing services/credentials do not fabricate verdicts.

Shared classifier transport and shadow ledger

Two plugins are infrastructure rather than features; no consumer implements either one itself.

classifier-core is the only place in this repository that speaks HTTP to the /v1/systemone service. Consumers (fast-compaction, chain-compaction, auto-review-classifier, auto-answer-classifier, subagent-effort, meta-classifier) path-load the one module it registers under sys.modules["hermes_plugins_classifier_core"], so the whole process shares ONE ClassifierClient per endpoint and ONE breaker: breaker_failures consecutive service errors open it for every consumer of that endpoint for breaker_reset seconds, exactly one half-open probe request decides whether it closes, and every transition is logged once at WARNING with the endpoint only. Nothing raises: a declined request returns None to the caller and the reason (not_configured, breaker_open, text_limits, unusable_body, service_error, cancelled) is counted against that caller by name. The per-plugin transport settings this replaces — endpoint, served_model, api_key_env — were removed from those consumers and are configured only under classifier-core; the Bearer key is read at call time from the environment named by api_key_env and is never stored or logged. classifier_status and hermes classifier-status show breaker state, consecutive failures, totals and per-caller counters; probe: true (or --probe) adds one fresh GET /health, because a breaker snapshot alone cannot tell a dead service from an idle one. The command exits 0 only when the breaker is closed and no caller was ever declined as not_configured. The plugin registers no hooks and makes no network call at register time. With the plugin or its client.py absent, each consumer takes its own documented decline — classifier_core_unavailable, prune_unavailable:classifier_core, an abstention, a fail-open — and the counting path makes it visible rather than silent.

shadow-gate is where a shadow decision earns qualification or does not. Producers append rows through the shared ledger.py module to append-only JSONL ledgers, one file per decision (the validated decision name IS the filename); each outcome pairs with the latest earlier verdict for its key, and every row that supplied no pair — including a verdict superseded by a later one — stays visible as unpaired. shadow_status and hermes shadow-status report per decision the pair count, agreement, false-positive rate and unpaired rows, and whether the explicitly configured decisions policy (min_pairs, min_agreement, max_false_positive, non-empty positive_verdicts/positive_outcomes sets) is satisfied. Nothing qualifies without a policy: an unlisted decision reports NOT_QUALIFIED with reason no_policy, and QUALIFIED states only that the measured evidence meets the operator's thresholds — applying a qualified decision stays the consuming plugin's own explicit choice, and no consumer applies one today. Row writers as shipped: subagent-effort records verdict rows per completed classification and realized-outcome rows per child stop for subagent_effort.difficulty; meta-classifier records verdict rows for meta_classifier.risk and meta_classifier.shield when a directive is minted and writes the paired outcome when the human's approval answers it. An absent or unusable ledger is counted (ledger_unavailable), never a crash; rows above max_rows_per_decision are kept newest-first through an atomic rewrite readers never see half-done.

Plugin doctor

hermes plugins-doctor turns the installation contract above and the "Native prerequisites" list from prose into rows. Four checks, each verdict a (check, plugin, severity, detail) row:

  • links — every entry under the plugins dir (and the active profile's) resolves to a directory containing a plugin.yaml.
  • settings — every enabled plugin's settings validate against its manifest config_schema through the host's own validator; a present-but-wrong value is a fail, a missing optional key a warn.
  • seams — the host contracts the "Native prerequisites" section names are actually present on this host: that list is enforced here, not taken on trust.
  • stale_import — editing source or configuration does not hot-reload already-imported modules, so every process that registers plugins leaves a digest record of each plugin entry's bytes; a still-live pid holding an older digest is running code the operator has since replaced, and the doctor names it.

It repairs nothing and rewrites nothing outside its own load records (stale_records_keep, default 16 pids — the only bytes it writes): the operator decides. The CLI exits 1 only when a row is a fail; --json prints the same rows as an array.

Requirement-bound completion

  1. Create native todos for the actual requested outcomes.
  2. Use acceptance declare before verification. Each criterion identifies its observable, check kind/identity, relevant source paths and optional references. Match native todo IDs; every required completed todo needs a declaration.
  3. Run the declared check through native tool execution. Bind its actual verification_evidence.id with acceptance bind. Terminal command targets and execution cwd must match; a passing sibling package or unrelated lint run cannot substitute.
  4. Inspect acceptance status. Blocked/cancelled dispositions remain explicit; cancellation is not completion.
  5. Independent native /review receives the actual todo content/structure, criterion mapping, source/evidence identity and a labelled partial task/history capture. It must challenge relevance and return coverage for every criterion:
{"review_verdict":"clean","findings":[],"coverage":{"criterion-id":"covered"}}

Other verdicts are findings and unverified; coverage entries may be gap or cancelled. A finished reviewer process, malformed response, missing declaration or absent evidence is not a clean result. Source, task, todo or evidence changes invalidate prior review, including changes while a synchronous reviewer runs. Native compaction preserves same-agent declarations without accepting foreign-session observations.

Native agent.verify_on_stop must be enabled, the cwd must be a recognized coding project, and terminal commands must qualify for the native ledger. For example, this host recognizes python3 -m pytest; an absolute interpreter path is not necessarily recognized as the same check. Browser evidence requires an actually observed owned browser_capture, not a caller-supplied row alone.

max_review_rounds and native agent.max_verify_nudges bound intervention. Exhaustion releases the turn with Completion unverified, not a fabricated success. Review children/detached forks cannot recursively trigger reviews.

Terminal check_identity is the exact raw executed command, including its launcher, selectors and options; native heuristic/full-suite classification cannot broaden it. Declarations pin the execution workspace, so moving to another task cwd requires redeclaration and fresh checks. Review mapping must fit the native 12,000-character message limit (including its prefix); overflow remains unverified rather than truncating away requirements.

Explicit planning handoff

Set plugins.entries.prewalk.settings.target_model to an execution model, optionally with target_provider when it is served by a provider other than the planner's. An explicit target beats the routed executor role (see Model routing); with neither set, prewalk stays inactive. Call prewalk_arm, populate native todos, then call prewalk_ready with:

  • Located workspace-relative files and relevant symbols/callers. Files must be confined regular files; no callers requires a concrete callers_absence_reason.
  • Constraints, non-goals, chosen approach and rejected alternatives.
  • Falsification checks, replanning triggers and acceptance IDs; optional rule references.

The tool validates the brief and performs the handoff through the host model-switch seam switch_agent_model, falling back to native same-endpoint switch_model on hosts without the seam (on such hosts a cross-provider target refuses to arm). It does not claim to prove that every model-authored explanation is correct. prewalk_status and prewalk_reset expose state; prewalk_status also shows the target model/provider and its source (explicit or route). replan_enabled defaults to false; when enabled, prewalk_replan allows one return to the planner's original model and provider with a concrete failed assumption/check.

The validated brief is also persisted in the plugin's durable state, because it is the one record a compaction summarises away. The post_compaction observer arms exactly one re-injection: the next pre_llm_call of the session that was compacted gets the stored brief back as a single context block, counted as handoff_reinjections in prewalk_status and cleared by prewalk_reset/session reset/finalize. A session with no stored brief injects nothing, so an unplanned session sees no extra prompt at all.

These tools may be deferred behind Hermes's native catalog. Discover the actual schema via tool_search/tool_describe; the bridge uses {"calls":[{"name":"prewalk_arm","arguments":{}}]}, not a bare tool_name.

Model routing

model-routing resolves named roles to concrete models. Configure named roles under plugins.entries.model-routing.settings.roles (role names match [a-z][a-z0-9_-]*); each role has a required non-empty model and an optional provider validated against the configured providers without network access. An omitted provider means the calling surface's own parent provider (a parentless surface uses the configured main provider). settings.fallback selects what an unknown role does: inherit (default) or refuse. Unknown keys or providers fail plugin registration with the offending name:

plugins:
  entries:
    model-routing:
      settings:
        roles:
          executor:
            model: glm-5.3-flash
            provider: openai
          reviewer:
            model: glm-5.3
        fallback: inherit

Resolution precedence is explicit > route > inherit. An explicit model — a per-call/surface model or an auxiliary.*/delegation.* pin — always wins and the role is ignored. A configured role routes to its {model, provider}. Anything else inherits today's behavior. With fallback: refuse, only an explicit delegate model_role request is rejected; implicit lookups (review owners, the prewalk executor) never refuse and inherit instead.

Surfaces:

  • Delegate model_role: a per-task model_role beats the top-level one, and an explicit per-task model/provider pin beats both. A refused request rejects the task with the reason and spawns nothing. /review children take an explicit auxiliary.review pin first, then model_role/roles.reviewer, then inherit.
  • Prewalk executor: an explicit target_model (with optional target_provider) beats roles.executor; with neither set, prewalk keeps today's behavior.
  • Review owners: an explicit auxiliary.<owner> pin beats roles.reviewer, which beats inherit. A routed reviewer counts as routed for the existing parent-capability skip, like an explicit pin.
  • route_status reports the configuration, effective resolutions, a bounded append-only audit of the plugin's own resolutions and the host seam. route_apply materializes pins — prewalk target_model/target_provider, delegation.model/provider and auxiliary.{curator,background_review,review} — through the native config writer with a provenance marker, never touches unrelated keys and reports drift.

Role routing needs a Hermes host with the register_role_resolver/switch_agent_model seams. Without them the plugin still loads and its tools work, route_status reports host_seam: unavailable, and nothing routes.

Cross-provider routing

Roles and explicit targets may name a model on a different provider:

  • Prewalk can hand off to another provider's model. Destination credentials and runtime are resolved exactly the way CLI/gateway startup resolves them (resolve_runtime_provider); at arm time a dry run validates the target, and an unresolvable provider refuses arming with the provider and reason named.
  • A delegate child built on another provider gets that destination runtime's own configured trust map (capabilities), never the parent's — the same map gateway startup gives an agent on that provider. Direct delegation.base_url endpoints without a provider-configured trust map stay {}.
  • Same-provider switches keep the agent's exact current endpoint (api_key/base_url/api_mode); nothing is re-resolved by provider name.

No path chooses a model from classifier output, cost or quota.

Recoverable Covey work

covey_tasks keeps durable shared work separate from native conversation todos. Its actions are status, lists, show, create, add, start, finish, retry, fail, release, abandon, checkpoint, resume, candidates, availability and stuck. Native diagnostics explain eligibility; this plugin does not schedule work. finish remains deliberately direct — the default path performs no governance read — and direct completion is still not governed review/apply, landing or verification; an opt-in per-call form is described below.

Configure an explicit capable binary and absolute local DB:

plugins:
  entries:
    covey-tasks:
      settings:
        binary: /home/common/projects/covey/target/debug/covey
        db_path: /absolute/local/path/covey.db

A version string alone is insufficient: capability probes reject stale binaries. Build from the qualified Covey checkout with cargo build --locked --bin covey if needed. There is no ambient COVEY_BIN/legacy-config fallback or default DB. Reads do not initialize a missing database; explicit create does.

Create a finite worklist and add known tasks before starting. start(subtask_id) claims one task per live agent. Optional workspace must be a real linked Git worktree, not the main checkout. A lock in the resolved Git metadata directory reserves that workspace across processes. Native file/patch/terminal/process mutation guards enforce the live claim and task cwd. Release restores previous cwd state; a successful subsequent unbound start clears the obsolete workspace guard. This is cooperative isolation, not a hostile-shell sandbox.

Change an agent's DB configuration only while its tool and claim operations are quiescent, preferably in a new session. Sequential DB selection restores cwd without transferring or releasing another DB's claims. Concurrent hot-switching for the same agent is not invocation-scoped cwd isolation; fixed-configuration parallel agents remain separately owned.

checkpoint atomically saves a compact covey-handoff.v1 receipt and retries/releases the task. Its handoff contains objective, changes, verification references, failed approaches and remaining work; workspace identity is attached by the runtime. resume obtains a fresh claim and returns validated_handoff: structurally checked recovery context, not renewed proof that old checks remain valid. Reverify current source. Native evidence requirements apply to checkpoint as well as finish.

A delivered loop-guard block can checkpoint the claimed task automatically, per blocker_checkpoint: off counts without acting, first checkpoints on the first delivered block, and the default exhausted waits until loop-guard has spent its own redirect_cap budget. The default waits because the native checkpoint is a retry that releases the claim — shedding ownership over one stalled guess is the failure mode the default avoids. Observed blocks and every checkpoint attempt, with its skip reason, are reported by covey_tasks status; an unknown value refuses registration.

governed: true on finish is opt-in per call, never a setting, and makes that one finish prove completion instead of asserting it: it refuses unless the continuation plugin's review closure for the current session is closed, and records the evidence digest derived from that reviewed evidence. The refusals are not_governed_closed (open closure, with its blockers, unbound criteria and review verdict), evidence_mismatch (a caller-supplied digest that is not the derived one) and governance_unavailable (the closure could not be read — governed means proven, so it fails closed). covey_tasks status reports governed_finish.available so the capability is never silently missing.

Mutations expose a non-secret request_id. outcome_unknown is not rollback: inspect the native task, reconcile, and explicitly reuse the same request ID if retrying. Leases last 300 seconds and renew every 60 seconds only while a claim exists. Normal teardown releases ownership; after process loss, the lease expires and a newer fence is required.

Session tokens are cooperative handles, visible to same-user processes through CLI argv and the DB; redaction does not make them a security boundary. Back up an existing DB with SQLite's backup API before migration, not a copy that omits WAL sidecars. Disable the plugin/start a new session to roll back integration; never downgrade a migrated DB in place.

Text intake

intake moved to the swarmlord repo (swarmlord/hermes-plugins/intake) on 2026-11-05: it is the fleet's intake stage, so it lives with the harness and the topology it serves. Its operating doc is hermes-plugins/intake/README.md there.

Rules and operating modes

Rules use rule_dirs (default ~/.hermes/rules) plus ancestor .cursor/rules, .windsurf/rules and .claude/rules directories. Nearer project rules override same-name ancestors/global rules. Existing scopes combine with AND semantics; path-specific guidance never becomes unconditional prompt text.

---
id: python-contract
description: Exercise changed Python behavior
agents: [sisyphus]
tools: [write_file, patch]
globs: ["src/*.py"]
check_refs: ["tests/test_contract.py"]
constraints:
  api_compatibility: preserve
---
Preserve the public contract and exercise the changed behavior.

rules_explain exposes matching reasons/sources, precedence, explicit contradictory constraint values, check/verification references and budget omissions. Conflicts are declared metadata/content conflicts, not semantic understanding of arbitrary prose. References are data: neither prose nor check references execute commands or grant permission.

A rule that declares check_refs/verification_refs stays data — until it matches. When such a rule matches a real file-mutating tool call, the plugin records one acceptance obligation for that rule in the session's native plugin state (opaque refs plus the resolved paths); recording is independent of the one-time guidance lane (acceptance_obligations: false turns it off), the store never resolves, imports or executes a ref, and obligations drop at session reset/finalize. The continuation gate reads the same store through the shared module instance: pre_verify holds the turn until every obligation ref is covered by a BOUND acceptance declaration — its refs list containing that exact ref, or its check identity equal to it — the same evidence an ordinary required outcome needs, so a declared-but-unbound criterion satisfies nothing. The hold is a normal labeled blocker, and its message carries the exact calls that clear it:

acceptance {"action":"declare","id":"<criterion id>","observable":"<what must be observable>","check_kind":"terminal","check_identity":"<ref>","paths":["<workspace-relative path>"],"refs":["<ref>"]}
acceptance {"action":"bind","id":"<criterion id>","evidence_id":<native evidence id>}

An absent rules plugin or an unreadable store means no obligations and no hold — never a silent hold or a silent drop; rules_explain reports what is owed plus the store's degradation counters.

keyword-steering.settings.modes maps each mode to refs, optional triggers and conflicts_with. References point to your existing skills/rules rather than copied prompts. Genuine prose triggers activate only for the current user turn; code, quoted text, paths and identifiers are excluded. Contractions are prose (don't and it's no longer swallow the sentence); fenced blocks close only on their own fence. orchestrate and workflow are ordinary English words, so by default they activate only as /workflow or #workflow (magic_keywords: {workflow: {bare: true}} restores bare OMP matching; user modes accept bare: false too). The orchestrate routing sentence (model_role, route_status) is emitted only when the route_status tool is registered. A negation within three words (don't ultrathink this) declines the hit. With settings.classifier.enabled, only these ambiguous hits (common-word bare hits, negated hits) are sent, natively redacted, to the self-hosted classifier for one steering: directive|mention|negated label; an unavailable classifier declines the hit, and every hit's verdict and confirmed_by appear in steering_status.hits. Mode refs are resolved against the active skills dir and settings.ref_roots at registration; unresolved refs warn, are listed in steering_status.unresolved_refs and are annotated in the injected guidance. Conflicting modes are both withheld, the model is told to report the conflict to the user and ask which applies, a warning is logged, and steering_status explains requested/active/withheld state. The built-in magic keywords (ultrathink, orchestrate, workflow, cheap, deep, local, dry, individually toggleable via settings.magic_keywords) are reserved names on the same machinery: ultrathink explicitly raises the turn's reasoning effort to ultra through PluginContext.set_turn_reasoning (guidance-only fallback when the host lacks the seam), and the reminder plus steering_status report exactly what was applied. User-configured modes never change effort or permit deployment. Legacy keywords prompt-copy configuration is rejected rather than silently reinterpreted.

cheap, deep and local are the routing keywords — same sigil rules (a bare match needs /cheap etc. unless bare: true), and each asks model-routing to pin a role for the current turn. routing_keywords maps keyword to role name (default same-name, validated [a-z][a-z0-9_-]*; an unknown keyword or malformed role fails registration). The explicit user keyword outranks the implicit route; a consumer's own explicit target_model still wins; the pin is runtime state only and expires with the turn. While held, every role request in that session resolves to the pinned model with source turn_pin — or explicit where the host's receipt law does not recognize that label — the reason always naming the role and who asked. Per keyword, steering_status.magic_effects records the application with the model or the decline (role_not_configured, model_routing_unavailable when model-routing is not installed); two routing keywords in one turn are contradictory, so both are withheld and the conflict is surfaced. dry declares a read-only turn: this plugin only states it, and meta-classifier with its own dry_turn setting on (the default) is what withholds the mutating calls — a deterministic block carrying rule_key dry_turn, made without any classifier request — and every withheld call is listed in steering_status.

Failure-aware recovery and risk screening

loop-guard records bounded exact call/result history, structured error identities and native failed checks. It detects repeated failures and short cycles without guessing that similar-looking edits are equivalent. Read-only polling is excluded. A blocker retains the failing operation: succeeding at an unrelated check does not clear it.

loop_recovery status shows the blocker/history/budget. A hypothesis names one exact next operation intended to distinguish explanations; it never bypasses native approval. Only delivered interventions spend the budget; suppressed/diagnostic directives do not. Exhaustion labels the final answer with the unresolved blocker. Mid-stream model cancellation is still outside this plugin's capabilities.

The first time the intervention budget is spent and another block would have been emitted, loop-guard files one gate-friction row with the papercuts sink: source=loop-guard, kind=budget_exhausted, identity a digest of the blocker invariant, detail the already-redacted current blocker — once per blocker per session (friction, on by default; a non-boolean keeps the default with a warning). loop_recovery status surfaces the outcome as friction={reported,last_reason}; with papercuts absent nothing is filed and the status reports friction_unavailable — never a crash.

auto-review-classifier screens actual resolved targets/workspace, effects and unavailable context. Recognized credential arguments, flat assignments and nested secret-valued metadata are masked before native redaction and HTTP egress; unsupported nested or unparsed credential-bearing commands refuse screening before HTTP. This is bounded recognition, not a guarantee of detecting every arbitrary secret. Unsafe redaction or unavailable context is reported as unknown/un-screened, not safe. Approval scope includes the operation and target identity; retargeting after consent fails revalidation. This is an advisory screen: classifier failure remains deliberately fail-open, while deterministic host permissions and native approval remain authoritative.

meta-classifier keeps that screen (same redaction, snapshot, approval scope and revalidation; status unknown on incomplete evidence) and replaces the binary safe/risky head with droid's Execute risk criteria: risk_scale: jev sends the captured LOW/MEDIUM/HIGH definitions, brief examples and boundaries; luna sends the four autonomy tiers. The state is Jev-shaped (pendingCommand + a redacted sessionTranscript tail captured from pre_llm_call). Tiers at/above approve_from (default high) ask; policy: deny blocks. Before git commit/git push the Droid Shield port diffs staged (or unpushed) added lines on local backends: a line that native redaction scrubs is a finding (downgrade head, B at cls_threshold 0.3775 → block, else warn), a secret-named line without a finding is a risk context (risk head, B at 0.95 → warn). Scrubbed windows are sent, never raw values; downgrade failures block and risk failures stay silent, exactly as droid. The service exposes labels only, so droid's logprob thresholds ride as multi-label cls_thresholds and are uncalibrated against this model (live replay: the captured run-10 risk context droid warned on at P(B)=0.99 returned no B here). Local signals (signals: true) classify the rendered, redacted transcript with the intent/friction/expertise enum heads in one request on session_end and/or turn_end (skipped when unchanged) into $HERMES_HOME/logs/meta-classifier-signals.jsonl; unusable replies are recorded as errors, never as droid's plausible Partial/OK defaults. decision_model accepts a Jev evaluate request (choice/noul/score), redacts the state and returns labels without probabilities. hermes meta-classifier-replay [--run] [--captures DIR] rebuilds droid's 13 captured payloads with the plugin's schemas (expect only where both droid rubrics agree: git status → low, any push → high).

meta-classifier addons (addons: [...], all off by default) put the same labels-only classifier on Hermes seams droid never had. Every addon writes one record (addon, labels or error) to signals_path; a dead classifier costs nothing on the clean path. injection (transform_tool_result on injection_tools and mcp_*) prefixes results carrying instructions_to_agent: present with an untrusted-data fence naming the injection_target. outcome (same hook, terminal/execute_code) labels succeeded/failed/partial/needs_input, replaces results with secret_in_output: yes by their redacted form, and feeds the turn's outcomes to completion. completion (pre_verify, attempt 0 only) classifies Goal/Last action/Tool outcomes and returns one continue nudge when finished: no, claim_type: asserted or a command was failed; a verified finish passes. subagent (subagent_stop) records child_outcome/unverified_claims/scope and logs a warning on abandoned/yes/creep; returns are ignored by the host, so it never rewrites a report. request (pre_llm_call) adds a one-line user-turn context only for ambiguity: underspecified (ask ONE question) or destructive_request: yes (confirm target, keep recovery path). friction (post_tool_call, status == error only) records droid's frictionCategory/frictionSeverity with a papercuts-compatible signature. memory (post_llm_call) records durable: decision/preference/project_fact/none candidates; it does not write memories. test_value (pre_tool_call on test-path writes/patches) records behavioral/tautological/wiring_only; with test_value_gate: true a non-behavioral label requires native approval. complexity (pre_tool_call on delegate_task spawns) records trivial/moderate/deep next to the chosen effort/role and never routes (routing stays NOT_QUALIFIED). hermes meta-classifier-replay --cases FILE [--run] replays human-labelled JSONL rows {id, text, addon|schema, expect} and prints per-head agreement, so each addon can earn its gate with evidence; the 19-case live smoke on 2026-10-18 agreed on 27/31 expectations (misses: multi-label frictionCategory over-fires at 0.5, durable preference→decision, complexity trivial→moderate).

auto-answer-classifier provides clarification_defaults recommend, record and list. Recommendation uses explicit bounded repository evidence and one all-choice ranking; it never treats option zero as truth. That single ranking is one /v1/systemone request carrying a choice question bound to the option labels and a yes/no question about whether a conventional default is sound here: the answer is accepted only at or above min_sound_default (default 0.8: conventional defaults measured 0.988-0.9995 against 0.050-0.055 for product/authority/security decisions, so the gate sits mid-gap; 0.5 was the old yes/no argmax), min_pick_confidence (default 0.0) and a top-two min_pick_margin (default 0.0), so raising either of the last two turns near-ties and weak picks into abstentions. The reported probabilities, confidence and margin are recorded on the recommendation for later calibration. Supported ordinary inputs are unambiguous repo-wide .editorconfig settings and supported top-level TOML/JSON scalar conventions. Conditional/conflicting/unset values, ambiguous domains and secret-bearing evidence cause abstention. File hashes and ownership are checked again after ranking and before application.

A recorded convention requires an observed native human choice and a separate native remember consent. Only these approved scoped conventions can avoid the prompt outright. Repository recommendations stay advisory until a genuine eligible timeout; each derived answer retains provenance without overwriting human responses. Explicit skips, material choices, destructive decisions and approvals remain human-owned. dry_run never applies a default. Legacy batch results lacking per-row provenance remain advisory only.

Browser backend and acceptance

Configure the native MCP server and grant its name in plugins.entries.browser-bladebro.mcp_allowlist; settings.server defaults to bladebro. Access is denied by default.

Lane: enabling the plugin registers browser_navigate, browser_snapshot, browser_click, browser_type, browser_scroll, browser_back, browser_press, browser_get_images, browser_vision and browser_console as ordinary tools — the names, schemas and result shapes carried over verbatim from Hermes' built-in browser tools, which upstream removed on 2026-11-03. No override grant is needed: there are no built-in names to override, and unloading the plugin leaves the names unclaimed again. This lane coexists with core browser_exec (tools/browser_use_cli.py, CDP via browser-harness) with no behavioral coupling — it is the bladebro real-browser lane. Bladebro runs every call. No local agent-browser/Chromium is needed. Each task (session + task_id) owns one bladebro tab. Every call switches to that tab inside the same native run RPC as the action, so concurrent tasks sharing the backend's single current tab cannot cross. The exception is browser_vision: vision is not a run step, so it holds the plugin lock across switch + capture, which serializes within one process but not across processes. Refs accept the @e5/[5] forms. URL secret/policy and private-address (SSRF) checks route through the current tree's tools.browser_url_policy — the same verdicts core browser_exec applies — for the requested URL and for wherever the page lands. Oversized snapshots use the plugin-owned truncate-and-store path (settings.snapshot_threshold, default 15000, floor 1000; the full text stays readable via read_file). browser_vision attaches bladebro's screenshot natively or falls back to the auxiliary vision model. browser_console(clear) subtracts a baseline because bladebro has no buffer clear. Tabs survive turns and close at session finalize/reset; a delegated child's tabs close at subagent_stop. A tab bladebro lost (idle Chrome restart) is reopened by the next browser_navigate. browser_cdp, browser_dialog and the vault fill tools are not provided by this lane (the removed built-in stack had them); core browser_exec is the current CDP lane. Leave mcp_servers.bladebro.tools unfiltered: tools.include: [] leaves the server unconnected and breaks the plugin. The raw mcp__bladebro__* tools are only reachable through tool search anyway.

Extras: browser_run runs a bounded bladebro step batch on the task's current page. The batch is wrapped in an exact-URL guard bound to the URL read just before dispatch. It keeps native approval for side effects, bounded branching/iteration and no automatic replay after an uncertain outcome. browser_scenario and browser_capture provide certified acceptance. Ordinary browsing works on HTTP sites, including authenticated surfaces, but a login wall or inaccessible state does not count as success.

For certifiable local scenarios, declare sources, a confined self-contained HTML artifact, initial/final predicates and the intended DOM interaction before execution. Direct HTML must itself be declared source. Generated artifacts require a real foreground native build and its passing owned evidence row. Capture transports exact producer-hashed bytes with a restrictive CSP into a fresh owned tab, then observes the declared sequence through native MCP. Source/artifact drift, missing/truncated framing, failed guards, missing connection identity or uncertain dispatch cannot mint passing evidence. The external ledger records both source revision and transported artifact digest; continuation independently checks the capture observation and binds the actual evidence ID.

Qualification boundary: this is local self-contained-artifact proof, not attestation of arbitrary HTTP deployments, origin-dependent applications, multi-file dependency graphs or reproducible toolchains. The restrictive CSP is evidence invalidation, not a browser sandbox: page script can attempt side effects and the final URL guard invalidates evidence rather than preventing them. A byte-identical rebuilt artifact is reported newly_generated=False; qualifying a second build requires genuinely fresh output. A stale/degraded scenario is terminal and refuses rebuild — use a fresh scenario. Backend DOM/keyboard/accessibility/error observations must be described according to what was actually returned; text success and screenshots alone do not establish general accessibility or visual correctness. Unsupported predicates remain unverified rather than becoming model-declared passes.

Traceable documents and friction reports

docling_convert uses Python Docling, qualified with CLI 2.137.0, not docling.rs. Set tool-docling.settings.binary to the intended executable. The source is resolved through native task cwd and sensitive-file policy. Identity includes source contents, input suffix (which can select a different parser), converter executable/version and options; source/converter drift refuses publication. Per-key locking, unique staging and atomic markers prevent partial or concurrent cache corruption. Format-unbound older cache entries are retained on disk but reconverted, not silently reused.

Converter identity retains the complete bounded version report, including reported Docling component versions, rather than its platform footer. A reported package upgrade invalidates cache entries even if the Python launcher is unchanged. This is version-report provenance, not a hash of every transitive dependency. New artifacts use cache-owned document.md/document.json names distinct from the artifact.json marker; cache hits return the adopted marker's filenames.

Results expose source hashes, Markdown/structured JSON artifacts, available self_ref/parent/page/section/table provenance and bounded page/section selection. HTML genuinely lacks page provenance: it is reported missing, never synthesized. Cache hits avoid reconversion but still validate identity. Failed conversions—including exit-zero runs without a valid artifact pair—do not overwrite good artifacts. Extracted document contents remain untrusted source material, not instructions or authorization.

papercuts uses typed fields for category/severity, expected versus observed behavior, version/invocation/result context, reproduction and references. Native deduplication owns recurrence counts and statuses; list/stats/ack/resolve remain native operations. Every string is redacted before filing. Automatic capture defaults off and uses authoritative host failures/delivered loop blocks, not arbitrary text containing “error”; its 20/day quota is updated atomically only after a successful filing. It does not publish externally.

gate_friction (off by default) installs the process-wide sink through which other plugins' gate refusals become workflow papercuts: continuation's repeated holds (repeated_hold), loop-guard's spent budget (budget_exhausted) and chain-compaction's summary backstop all arrive as typed GateFriction values and file only with this plugin enabled and the setting on. Filing shares auto_capture's redaction path and its cross-process daily quota — so leave auto_capture on as well; with auto_capture off the sink reports each refusal as disabled in papercuts mode friction instead of filing it, and with gate_friction off every report is counted no_sink. Producers path-load the one shared sink module, so their counting works with or without this plugin enabled. The friction tool mode prints the sink's per-source, per-reason counters: "no sink" and "quota exhausted" are observable numbers, never a swallowed call.

draft_rules (off) turns recurring grievances into proposals a human can adopt: at session end — or explicitly via mode: drafts — one native list --json call runs, and papercuts at or above draft_rule_after recurrences (default 3, must be an integer ≥ 1) become rule files under draft_rule_dir (default ~/.hermes/rules/drafts). Every draft carries enabled: false, and the directory is not a rule source: the rules plugin reads only the direct *.md files of each configured rule dir, never descending, so a drafts/ subdirectory is inert even inside a scanned dir. Adopting a draft is an explicit human act — move it into a scanned dir, set enabled: true, add the globs/tools scope. Nothing self-activates.

Optional time_lost_seconds records reported observed loss, not an inferred duration. Native deduplication accumulates the known subtotal and timed_report_count; untimed reports remain unknown/partial. list with sort: impact ranks the complete backlog by severity, recurrence, known reported loss, timing coverage and stable ID before applying limit. An observed zero is distinct from missing timing. This is a transparent priority order, not a weighted score or monetary estimate.

Concurrent writers additionally require the patched native papercuts binary, not merely the plugin quota lock. The isolated source snapshot is /home/common/projects/papercuts-hermes-integration, with sealed preimage/ files and lock.patch. Set papercuts.settings.binary explicitly to that snapshot's papercuts when evaluating it. Its report/dedup/ack/resolve read-modify-write transactions share a POSIX file lock. Non-POSIX locking failure is explicit. The installed /home/common/projects/papercuts/papercuts was deliberately left unchanged; installing the native patch is a separate operator action.

Paired effort experiments

subagent-effort remains shadow-only. Classification, lifecycle completion, actual wire controls, native verification, measured repair cycles and independent deterministic oracle results are separate observations. Missing prices are unknown, not zero. There is no automatic apply/escalation policy. Each completed classification appends a verdict row and each child stop a realized-outcome row for the same key to the shadow-gate ledger under the subagent_effort.difficulty decision — with the sibling absent, the rows are counted ledger_unavailable in the plugin's status note. Qualification is a decisions policy the operator writes under shadow-gate; until it is satisfied and acted on explicitly, the recommendation stays a record.

# Offline baseline/oracle checks; no model calls:
python evaluation/evaluate_paired.py --output /tmp/paired-baseline
# Explicit live run in an environment with Hermes and required case dependencies:
python evaluation/evaluate_paired.py --run --efforts medium,xhigh --repeats 2 --seed 20261010 --output /tmp/paired-live

Each pair starts from identical source, uses the same numeric seed, and has isolated workspace/HOME/HERMES_HOME. Seeds vary across repeats; arm order alternates within each case. The corpus carries six frozen cases (suffix removal, page count, JSONL record parsing, CSV catalog aggregation, cross-file memo state, ledger batches) plus four additive cases (YAML service config, SQLite migration, path-security asset fetch, slug control). The v4 run used a six-case plan; the current design plans all ten (40 arms per two-effort, two-repeat run). Public and hidden oracles have reference-positive and broken/mutant-negative controls. The runner retains failures, timeouts, sources, transmitted controls, cumulative execution/repair usage and deterministic assessment cost; it does not claim an independent model code review or known monetary total.

The 2026-10-10 v4 run attempted 24 arms. One timeout pair and one mixed-effort-transmission pair were excluded, leaving 10 eligible comparisons. A broken path-security oracle and incomplete schema-rollback oracle were found during review; original artifacts remain unchanged. evaluation/reassess_paired_v4.py replays corrected oracles against hash-verified frozen candidates, in a separate output directory, without new model calls. These results are explicitly retrospective, not a preregistered rerun.

The current comparative run is paired-live-v5: all 10 cases (the 6 frozen plus 4 additive), 20 pairs / 40 arms, medium versus xhigh, two counterbalanced repeats with shared per-pair seeds and isolated homes. It completed 18 eligible pairs; one memo-flight repeat timed out in both arms at the 180-second child cap and remains in correctness/latency as a censored observation, while two xhigh arms were excluded by the wire-support gate because one request omitted the effort field. Raw unfiltered outcomes were medium 19/20 correct and xhigh 20/20; eligible-pair aggregates were:

Eligible-arm observation medium xhigh
Correct under corrected deterministic oracles 17/18 18/18
Raw unfiltered correctness 19/20 20/20
Cumulative native execution seconds 353.27 385.99
Mean native child latency, seconds 19.63 21.44
Native input + output tokens 721,800 736,040
Native API calls 112 107
Verification ledger rows observed 19 17
Verified corrective revisions 0 0
Native cost estimate unknown unknown

The paired descriptive deltas were 838 fewer tokens and 1.82 seconds faster for medium on average, but the spread is large and this is not a savings claim. Two xhigh requests in separate pairs omitted the effort field and were excluded by the wire-support gate; the timeout pair remains included as censored latency with unobserved tokens/cost.

Lilith transmitted chat_template_kwargs.reasoning_effort, budget fields 8192/65536 and scaffold flags false/true. The client profile sets enable_thinking: true; an executed sandboxed render of the deployed tokenizer's hash-matched template confirms that medium and xhigh produce distinct prompt prefixes. The same render proves that budget/scaffold fields do not change this template; their intended SGLang behavior is not qualified on the deployed vLLM fork. Runtime reasoning-budget enforcement remains unknown. The provider also sends auto_disable_thinking_with_tools on tool turns, so backend thinking may flatten even when the wire effort differs. Earlier v3 missing-effort observations were an instrumentation omission, not evidence of unsupported transmission. Durable receipts are under /home/common/projects/hermes-plugins-qualification/2026-10-10/evidence: paired-live-v5/, effort-template-support.json, paired-v4-closure/ and the unchanged paired-live* attempts. The closure receipt verifies all 24 arm provenance chains and 136 unchanged historical files. These small-sample descriptive totals establish neither general quality equivalence nor monetary savings. Automatic routing remains NOT_QUALIFIED.

The earlier fixed-xhigh six-case observation remains frozen in evaluation/results/2026-10-09. It is historical evidence, not a paired effort comparison.

Configuration cutover

  • Remove tool-fff installations/settings and any search override consent; native search retains backend isolation, credential filtering and bounded results. Native cross-file content offset paging is not an exhaustive/stable census.
  • Replace rules_dir with rule_dirs; use rule/check references, not argument rewriting.
  • Replace first-write prewalk assumptions with explicit prewalk_ready and its complete handoff.
  • Add explicit acceptance declarations and binding. Old clean review snapshots without requirement coverage are not completion authority.
  • Migrate steering keywords to modes referencing existing guidance.
  • Use typed browser/papercuts operations; old free-form CLI argument strings are not supported.
  • Remove obsolete continuation steering settings and experimental effort apply settings. No register_goal_provider API is assumed.
  • Remove per-plugin endpoint, served_model and api_key_env from fast-compaction, chain-compaction, auto-review-classifier, auto-answer-classifier, subagent-effort, meta-classifier and keyword-steering's classifier block (which also drops cooldown: the shared breaker owns outage blackout); those settings are configured only under classifier-core, whose client every consumer path-loads through one shared module instance.
  • chain-compaction.settings.pipeline now defaults to drop_reasoning,prune,clip,drop_stale: pure deletion of chain-of-thought from assistant turns older than the protected window leads, because it is the largest per-turn payload and the only reduction that cannot change an answer (visible text and tool calls stay byte-identical). A configuration that pins the old prune,clip,drop_stale order keeps it; only the default moved.

Verification

Run with the patched Hermes source and a Python environment containing pytest and the case dependency PyYAML. Do not alter the Nix store or a live profile:

export HERMES_SOURCE=/home/common/projects/hermes-agent
export COVEY_TEST_BIN=/home/common/projects/covey/target/debug/covey
export PAPERCUTS_BINARY=/home/common/projects/papercuts-hermes-integration/papercuts
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH="$HERMES_SOURCE${PYTHONPATH:+:$PYTHONPATH}" python -m pytest -p no:cacheprovider tests -q

The suite uses real native loaders/dispatchers and disposable profiles/databases, isolating external network/model boundaries. The full run needs the two external binaries the environment block names — COVEY_TEST_BIN (a capable Covey build; Covey modules require it rather than silently skipping) and PAPERCUTS_BINARY (the patched snapshot for the concurrent-writer cases). Classifier consumers run against the classifier_stub fixture in tests/conftest.py, which installs the stand-in for the shared classifier-core client: install(reply) answers ask, install(labels=...) answers classify, and install(absent=True) exercises the documented sibling-unusable degraded path — no unit run touches a live service. The actual Hermes CLI workflows exercise explicit planning, edits, failed verification, loop intervention/recovery, native evidence binding and independent review through a loopback model boundary. This proves integration, not model coding skill.

Real backend qualification is separate: native MCP browser scenarios, actual Docling conversion, native papercuts subprocesses/concurrent writers, live classifier protocol and retained paired-model experiments. Unit-test doubles do not establish backend compatibility. The matching host's verification/arbitration/output/clarify/MCP tests must also pass after installing its source changes.

Validate every remaining plugin manifest in a disposable Hermes home with the patched host. The native loader warns about provides_middleware although the native validator requires it; retain the declaration rather than conceal middleware. This known loader/validator inconsistency does not grant capabilities.

Original-feature boundaries

  • dsh-jump GUI navigation and separate settings panels are not ported; use native configuration surfaces where available.
  • Child prewalk is part of prewalk, not a second plugin.
  • Mid-stream model cancellation is implemented (PluginContext.cancel_model_call); surfaces without a resolvable agent (gateway) return an honest refusal, and browser tab cleanup still cannot run after an unclean process kill.
  • DSH browser/search/Docling backends are not interchangeable with OMP's native implementations.

Attribution

Derived from dsh-plugins by carrion256 (MIT), itself porting features from oh-my-pi (MIT © Mario Zechner, Can Boeluek, Stencil Labs). Each port inherits the upstream MIT grant.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages