Commit graph

548 commits

Author SHA1 Message Date
Teknium
e7172ab1ba feat(mcp): fnmatch glob support in tools.include/exclude filters
The include/exclude filter matched exact names only — glob-style entries
('*_radar_*') silently matched nothing, so a Cloudflare flat-mode config
meant to trim 3,320 tools to ~1,900 actually registered 3,319. Unmatched
patterns produced no warning.

- matches_name_filter(): exact membership first (O(1) for literal lists),
  then fnmatch.fnmatchcase for entries containing * ? [ — same pattern
  semantics as approvals.deny. Entries without metacharacters stay
  strictly literal ('docs' never matches 'docs_search').
- _should_register() uses it for both include and exclude (symmetric)
- hermes mcp tools picker (mcp_config.py) pre-selection uses the same
  matcher so the UI agrees with runtime registration

E2E against the live Cloudflare capture with the real exclude list:
3,320 -> 1,905 surviving (1,415 excluded); radar/DLP gone,
purge_cache/dns_records kept. 220/220 mcp_tool tests (4 new).
2026-07-26 08:26:09 -07:00
Teknium
e9fe060ebf feat(tools): tier-2 server-summary hint + per-server listing degradation; 5% default budget
Teknium review changes on the tiered policy:

1. threshold_pct default 10 -> 5 (listing budget = min(5% of context,
   listing_max_tokens)); unknown-context fallback 20K -> 10K.

2. Tier 2 no longer leaves the model blind: when even names-only doesn't
   fit, the bridge description now carries a one-line-per-server summary
   ('cloudflare (3320 tools)') plus an instruction to search FIRST rather
   than substitute a generic tool or claim the capability is missing —
   the measured tier-2 failure mode (core-tool substitution) at zero
   meaningful token cost (~50 tokens/server).

3. Listing degradation is now PER SERVER, largest first: one oversized
   server (Cloudflare) collapses to its summary line while small
   co-attached servers (Linear) keep their full per-tool listings
   ('mixed' form). Previously global: attaching Cloudflare next to
   Linear silently cost Linear its listing. Greedy fit is deterministic
   (size then label) so the rendered block stays byte-stable per catalog
   — prompt-prefix cache safe.

E2E on real captures (defaults, 200K ctx): linear alone -> tier 1 full;
unreal alone -> tier 2 groups (5% budget) / tier 1 names at 1M;
cloudflare alone -> tier 2 groups; linear+cloudflare -> tier 1 MIXED
(linear fully listed, cloudflare summarized). 48/48 tests.
2026-07-26 08:26:09 -07:00
Teknium
0986ac393f feat(tools): tiered tool disclosure — always defer MCP/plugin tools, scale the listing with catalog size
Tier 0: no MCP/plugin tools -> everything eager (pass-through).
Tier 1: deferred tools whose catalog listing fits min(threshold_pct%
        of context, listing_max_tokens) -> bridge + skills-style
        listing, degrading to names-only over budget.
Tier 2: listing over budget even names-only (Cloudflare's flat API
        surface: 3,320 tools, names alone ~32K tokens) -> bare bridge,
        discovery through tool_search only.

The old activation threshold (defer only when schemas > threshold_pct
of context) let mid-size catalogs ride eager and pay full schema cost;
with servers like Cloudflare (~597K tokens of schema, would not even
fit a 200K window) the binary gate is the wrong shape. Activation is
now driven purely by deferrable-tool presence; threshold_pct is
repurposed as the listing budget's context-relative leg.

- AssemblyResult gains tier + listing_form for observability
- listing_max_tokens default 4000 -> 20000 (cap 60000) so an 830-tool
  catalog keeps a names-only listing while Cloudflare-scale drops to
  bare bridge
- E2E verified against real captures: Linear 24 tools -> tier 1 full,
  Epic UE 5.8 830 tools -> tier 1 names-only, Cloudflare 3,320 tools
  -> tier 2 (both 200K and 1M context)
2026-07-26 08:26:09 -07:00
teknium1
e869accc1a feat(tools): skills-style catalog listing for tool_search progressive disclosure
Deferred MCP/plugin tools become invisible once the tool_search bridge
activates — live benchmarking (48 runs, Claude Haiku 4.5) showed models
substituting visible core tools (terminal/web_search/browser) for deferred
capabilities or declaring them nonexistent instead of searching: 16/24
task success vs 24/24 with eager loading.

Skills never had this failure mode because every skill keeps a ~21-token
name+description listing line in the system prompt. This ports that exact
pattern to the tool bridge: when tool_search activates, a grouped manifest
of every deferred tool (name + first sentence of description, clipped to
60 chars, grouped per MCP server / toolset) is embedded in the tool_search
bridge description.

- tools/tool_search.py: build_catalog_listing() with deterministic
  ordering (byte-stable across assemblies -> prompt prefix stays
  cacheable); token-budget fallbacks full -> names-only -> legacy bare
  count; bridge_tool_schemas(listing=...) embeds it and instructs the
  model to skip tool_search when the exact name is visible (one fewer
  round-trip per use)
- config: tools.tool_search.listing auto|on|off (default auto),
  listing_max_tokens (default 4000, clamped 200..20000); legacy bool
  shapes keep working
- tests: 8 new tests (config parsing/clamps, short-desc clipping,
  deterministic rendering, budget fallbacks, bridge embedding, assembly
  on/off paths); full file green (47 passed)
- docs: tool-search.md config table + rationale
- scripts/tool_search_livetest2.py: benchmark harness v2 with real
  per-call token accounting (normalize_usage spy) and a third 'listing'
  mode for A/B/C comparison
2026-07-26 08:26:09 -07:00
Teknium
243a01d5d7 fix(curator): make the autonomous write policy consistent (#67140)
The background write guard decided ownership from `isinstance(usage_rec, dict)`,
so a local skill with NO usage record passed. That successful write called
bump_patch(), which created a `created_by: null` record — and the identical
write was refused from then on. "Allowed exactly once, then never" is a race
with our own bookkeeping, not a policy. Reproduced on main: patch #1 succeeds,
patch #2 with the same arguments is refused.

Option B from the issue. Option A (split `session_review` from
`scheduled_curator` and let the session fork patch user-owned skills it
consulted) would widen autonomous write permission onto skills the user owns
with no user present to consent — wrong direction for a no-user-present actor.

- skill_manager_tool: missing and explicit-null records now resolve
  IDENTICALLY, both fail closed. The refusal names the reason and points at
  `hermes curator adopt <name>`.
- background_review: both review prompts told the reviewer to patch any skill
  consulted in the session and claimed pinned skills could be improved, while
  enforcement refused both. Prompts now list pinned, external, and user-owned
  skills as protected, and tell the reviewer to RECOMMEND adoption instead of
  attempting a write that will be refused.
- skill_usage: document that `created_by` is a curator-management policy flag,
  not a provenance claim, and add `is_curator_managed()` so call sites read as
  the question they ask. Field name retained — it is on disk in every
  `.usage.json` and renaming would strand those records.
- curator CLI: `hermes curator list-unmanaged` itemizes unmanaged skills with
  the reason each is unmanaged (completes the #67139 spec).

Foreground writes are untouched: a user-directed edit to a user-owned skill
still works, including on pinned skills.

Sibling tests: 9 failures in test_skill_manager_tool.py were fixtures that
created record-less skills to exercise OTHER guards (consolidation-delete,
read-before-write) and relied on ownership falling through. Fixed at the
fixture, since the real curator only ever operates on managed sediment. One
test asserted the old "manually authored" wording; rewritten to assert the
behavior contract instead of the string.

Validation: 274 targeted tests + all 7 background-review files (60 tests) pass.
E2E on a temp HERMES_HOME (30 checks) covers the flip, foreground writes,
adoption unblocking, pin semantics, prompt/enforcement parity, and the new verb.
Each new test sabotage-verified: revert the fix, confirm it goes red.

Fixes #67140
2026-07-25 19:27:17 -07:00
Teknium
72de75c0ab feat(curator): surface unmanaged skills and add curator adopt
`hermes curator status` reported only the skills it manages, staying silent
about curation-eligible skills it can never touch. On a 237-skill library that
meant 112 skills were invisible to every automatic transition with no signal
anywhere — the curator looked broken when it was working as designed.

A skill becomes curator-managed only when `created_by: agent` lands on its
usage record, and only the background review fork writes that marker. Two
populations therefore never qualify: records written before the marker existed
(no key at all, authorship unknowable) and every foreground
`skill_manage(create)` (unset by design — those skills belong to the user).

- skill_usage: `list_unmanaged_skill_names()` / `unmanaged_report()` enumerate
  the blind spot, tagging each row with `has_provenance_key` so the two causes
  are distinguishable. `adopt_skill()` writes the marker on user declaration
  and refuses bundled, hub-installed, external, and protected built-ins.
  Adoption never resets the inactivity clock.
- curator CLI: status prints an `unmanaged (no provenance marker)` block on
  BOTH the managed and no-managed-skills paths; new `adopt` verb takes names or
  `--all-unmanaged`, with `--dry-run` and a confirmation prompt on bulk.
- skills_sync: `_backfill_optional_provenance()` matched candidates by
  repo-derived path only, so a skill installed at `mlops/chroma` that upstream
  later moved to `mlops/vector-databases/chroma` was skipped forever and
  `hermes skills repair-optional` could not fix it. Falls back to an
  unambiguous name match, still gated on identical content, and records the
  ACTUAL install path.

Provenance stays a declaration, never an inference: a high patch count proves
the agent MAINTAINS a skill, not that it authored one, since Hermes edits
user-written skills on the user's behalf routinely. An "looks agent-made"
heuristic would eventually archive hand-written work.

Validation: 347 targeted tests pass. Each new test verified via sabotage run
(revert the fix, confirm the test goes red) — one initially passed for the
wrong reason because the fixture pinned `prune_builtins` off, masking the guard
under test; fixed to force the shipped default on.
2026-07-25 18:10:24 -07:00
teknium1
064b6e40c5 fix(image-gen): stop reporting the Codex tool_choice 400 as an account limit
The Codex image backend rejected our own request shape for every account, and
we then translated that rejection into "Image generation is not enabled for the
current Codex account. Switch the image provider to OpenAI API key, FAL, or
xAI." — telling every affected user to abandon a provider that had never
actually been tried. That message is why this reads as a setup failure rather
than a bug: the wire error was replaced with a confident, wrong diagnosis.

Removes the classifier and its exception, so any HTTP failure surfaces
verbatim. The paired request-shape fix (previous commit) is what makes the
400 stop happening; this commit makes the next one diagnosable.

Also fixes error-body truncation: bodies were head-truncated at 500 chars, and
Codex error payloads can carry hundreds of bytes of leading metadata, so the
user got a wall of padding and no message. _summarize_error_body() prefers the
parsed error.message and falls back to a truncated raw body.

Docs: drop the unqualified image-to-image claim for the Codex backend and note
that the hosted tool call cannot be forced, so it is best-effort.

Verified E2E against a local fake Codex backend: success path writes a real PNG
with no tool_choice on the wire; the 400 path now returns api_error carrying
"Tool choice 'image_generation' not found in 'tools' parameter" (148 chars)
instead of the entitlement message. Sabotage run confirms all 4 regression
tests fail when the old behavior is restored.

Refs #19505, #49008, #31335.
2026-07-25 16:41:57 -07:00
amanning3390
939670cc4e docs(acp): document HERMES_ACP_SKIP_CONFIGURED_MCP for ACP hosts
Adds a 'Host integration' section to the ACP guide and a row in the
environment variable reference so the next ACP host implementer does not
have to read the adapter source.

Documents the exact contract the tests already pin: the value must be
exactly `1`; unset/empty/`0`/`false` keep the default behavior; only
globally configured config.yaml MCP discovery is skipped, and servers
supplied by the ACP session through session/new are still registered.

Framed as a host-set process marker rather than user configuration - the
same shape as the existing HERMES_KANBAN_TASK entry - so it does not read
as a behavioral setting that belongs in config.yaml.

Co-authored-by: amanning3390 <adam.manning@pro-serveinc.com>
Signed-off-by: amanning3390 <adam.manning@pro-serveinc.com>
2026-07-24 15:51:20 -07:00
abundantbeing
4a9447c726 fix(api-server): expose model options inventory
Add authenticated GET /api/model/options to the gateway API server,
sharing the dashboard/TUI picker payload builder so external clients
can sync to the user's configured Hermes provider catalog instead of
scraping the single OpenAI-compatible /v1/models alias.

- new shared hermes_cli.inventory.build_model_options_payload() wraps
  build_models_payload with the stable picker shape and safe
  custom-provider probe policy (probe current only on normal open,
  probe all + cache bust on explicit refresh)
- dashboard web_server and TUI gateway model.options refactored onto
  the shared builder; dashboard build moved off the event loop via
  run_in_threadpool
- capabilities endpoint advertises model_options
- docs for both API server and programmatic integration

Salvaged from PR #54689 by @abundantbeing.
2026-07-24 11:20:07 -07:00
teknium1
9f384783e7 fix(api): gate bare-model passthrough + route-alias model leak
Follow-ups on the salvaged #54426 routing contract:

- Bare `model` without `provider` on the OpenAI-compatible endpoints
  (/v1/chat/completions, /v1/responses) is now opt-in via
  gateway.platforms.api_server.direct_model_requests (default off) —
  generic OpenAI clients hardcode model names ('gpt-4o', ...) and
  existing deployments rely on those falling back to the gateway
  default. Explicit `provider` requests and the Hermes-native
  session-chat + /v1/runs surfaces are always honored.
  Idea credit: PR #22825 by @mssteuer.
- A model_routes alias with no `model` key can no longer leak the
  alias string as the executing model name (defensive; parse-time
  validation already drops such routes).
- Fix mis-indented _run_agent call args in _handle_session_chat_stream.
- Docs: document the opt-in flag.
2026-07-24 09:54:08 -07:00
abundantbeing
d66a82000c feat(api): honor provider-aware request routing
Carry model, provider, and model_options through the API server's
execution surfaces (session chat, Chat Completions, Responses, /v1/runs)
without mutating global configuration. Precedence: session /model
override -> model_routes alias -> direct request selection -> global
defaults. Conflicting route/provider mixes fail closed with 400.
model_options stays request-scoped regardless of which selection wins.

Salvaged from PR #54426 by @abundantbeing.
2026-07-24 09:54:08 -07:00
Aakash Kattelu
2aa359ea70 feat(honcho): add OAuth device-code login (RFC 8628) for headless environments
Adds a device authorization grant flow alongside the existing loopback
OAuth flow, so `hermes setup` can connect to Honcho cloud from SSH and
other no-browser environments.

- oauth.py: new HTTP seams — _http_post_form_status (non-raising, since
  RFC 8628 polling reads the OAuth error off a 400) and _http_get_json
  for the RFC 8414 metadata probe
- oauth_flow.py: DeviceCode, request_device_code, poll_for_token with
  slow_down backoff (+5s, capped at 60s) bounded by expires_in, typed
  errors (AccessDenied, DeviceCodeExpired, AuthorizationTimeout), and
  supports_device_login (fail-closed metadata gate); device flow ends in
  the same install_grant tail as loopback so refresh/status work
  unchanged
- oauth_flow.py: loopback callback now serves a "sign-in was not
  completed" page on consent cancel instead of the success page
- cli.py: cloud menu offers oauth / device / apikey; the device option
  only appears when the host advertises the grant, and becomes the
  default when no browser is detected
- 18 new tests covering the full flow against a local fake AS, backoff
  schedule, error mapping, deadline bound, metadata gate, and wizard
  branches

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 07:53:12 -07:00
Teknium
23476207bc feat(moa): default advisor fanout to user_turn — the cheapest cadence
Flips the default fan-out cadence from per_iteration (advisors re-run on
every tool iteration, multiplying advisor spend by tool-loop depth) to
user_turn (advisors run once on the first message of each user turn; the
acting aggregator works the rest of the tool loop with that turn's
advice). Until per-mode benchmarks justify a costlier default, MoA
defaults to the cheapest, lowest-impact cadence (#67199).

One default for everyone — no split legacy/new-preset semantics; presets
that want per-step advising set fanout: per_iteration explicitly. All
three modes (user_turn / per_iteration / every_n:N) remain selectable;
every_n:1 still collapses to per_iteration (semantic identity), while
unparseable values now fall to user_turn (the default).

Docs updated with a default-change note; the per-iteration rerun test
pins its mode explicitly.

Co-authored-by: skyer-flyyy <188930297+skyer-flyyy@users.noreply.github.com>
2026-07-23 21:07:18 -07:00
Julien Talbot
b9b100da11 docs(xai): clarify x_search vs xurl routing without schema cross-refs
Make the x_search / xurl boundary explicit in the skill, feature docs,
toolset metadata, setup note, and reference pages, while keeping the
model-facing x_search schema generic (no static xurl name).

Regression tests assert behavioral routing invariants rather than frozen
prose snapshots. Drop the stale CI-only plugin/hangup hunks already on
main so this rebases cleanly.
2026-07-23 21:06:47 -07:00
Teknium
b718725121 fix(skills): move computer-use skill into the autonomous-ai-agents category
skills/computer-use/SKILL.md sat at the root of the bundled skills
tree as an uncategorized single-skill directory. Move it to
skills/autonomous-ai-agents/computer-use/ alongside claude-code,
codex, opencode, and hermes-agent.

- git mv skills/computer-use -> skills/autonomous-ai-agents/computer-use
  (history preserved)
- root-level-skill docstring examples (commands.py,
  generate-skill-docs.py) now use a generic placeholder instead of
  naming a specific skill, since categorizing root-level skills is
  ongoing
- docs: move the bundled page to autonomous-ai-agents-computer-use,
  update sidebars.ts, skills-catalog.md, and the two skill-path
  references in features/computer-use.md

E2E validated: fresh sync_skills() copies to the new nested path;
existing installs with the old flat copy keep it untouched (manifest
name-keyed, hash unchanged -> skipped, no duplicate);
_get_category_from_path resolves 'autonomous-ai-agents'; docusaurus
build green; 396 targeted tests pass.
2026-07-23 21:06:39 -07:00
Teknium
e3d524b482 refactor(skills): restructure hermes-agent into a hub + absorb themes, desktop-plugins, tui-widgets, petdex
The hermes-agent skill body was a 51KB monolith loaded in full on every
trigger. It is now a lightweight hub (~12KB): identity, quick start, the
tmux orchestration guide (kept in-body — the autonomous-ai-agents category
contract), surface orientation, and hard invariants, with a routing table
into 18 reference files that carry the depth.

Absorbed four Hermes-specific skills as first-class references so their
content gains a discoverable home and room to grow without bloating any
primary body:

- skills/hermes-themes            -> references/themes.md + templates/skin.yaml
- skills/hermes-desktop-plugins   -> references/desktop-plugins.md + templates/plugin.js
- skills/productivity/tui-widgets -> references/tui-widgets.md + templates/clock.mjs
- skills/productivity/petdex      -> references/petdex.md

New references extracted from the old body: cli-reference, slash-commands,
providers-and-models, configuration, project-context-files,
security-privacy, background-systems, windows-quirks, troubleshooting,
contributor-guide (native-mcp and webhooks already existed). Also commits
delegate-task-concurrency-diagnosis and portal-auth-for-third-party-apps,
which the old body referenced but the repo never shipped.

Description updated to cover the widened scope:
'Use, configure, theme, extend, and orchestrate Hermes Agent.' (60 chars)
2026-07-23 19:46:02 -07:00
Teknium
850f576f3d feat(moa): add every_n fanout cadence with cached-guidance reuse
Extends the fanout enum with 'every_n:<N>' (N >= 2): advisors run on the
first iteration of each user turn and every Nth tool iteration after it;
off-cadence iterations REUSE the cached guidance from the last on-cadence
run via the same cache mechanism the user_turn fanout uses, so the
aggregator still gets advice on every step. The cadence counter is scoped
per user turn (resets on a new user message) and only advances when the
advisory state actually changes, so streaming retries never consume a
cadence slot. Mapping form {mode: every_n, n: N} normalizes to the
canonical string. Unknown/degenerate values fall back to per_iteration.

Addresses issue #63393 (advisor fan-out multiplies turn latency/cost by
the tool-iteration count). Redesigned from PR #63448: the submitted shape
skipped references entirely on off-cadence iterations (aggregator ran
advice-less); this version keeps the last advice in play, credited for
the idea and cadence framing.

Config-gated, default-off (default fanout remains per_iteration).

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
2026-07-23 17:50:40 -07:00
Teknium
6ced760a3d test(gateway): cover gateway.api_server YAML discovery + extra bridge; docs: document config.yaml support
Follow-up for salvaged PR #66633 (fixes #66630):
- 5 tests: nested gateway.api_server discovery, port/key/host/model_name
  bridged into extra, explicit extra wins over top-level key, non-platform
  gateway keys (streaming/timeout) not misparsed, gateway.platforms.api_server
  path regression-guarded.
- docs: api-server.md (en + zh-Hans) now documents the gateway.api_server
  YAML section instead of 'not yet supported'.
2026-07-23 11:54:32 -07:00
Teknium
558dab0e3e feat(slack): opt-in reaction triggers, removed events, hooks, channel handoff
Build the full reaction pipeline on top of the #29916 base:

- Opt-in gate: slack.reaction_triggers (default OFF — reaction events
  stay acked-and-dropped so busy channels don't wake the agent on every
  emoji). 'true' routes reactions on the bot's OWN messages; an explicit
  emoji-name list routes those emojis from any message (handoff flows).
- reaction_removed events now route too, distinguished by the
  cross-platform text convention reaction:added:<emoji> /
  reaction:removed:<emoji> (matches the Feishu and Photon adapters, so
  agents and skills see one shape everywhere).
- Authorization: the reactor becomes the synthesized message's user, so
  the early _is_user_authorized gate and allowed_channels whitelist
  apply exactly as for typed messages. _hermes_force_process only skips
  the mention requirement (a reaction on the bot's own message is
  definitionally addressed to the bot), mirroring Feishu/Photon.
- Gateway hooks (#33111 by @johnkattenhorn): every human reaction on a
  message item fires reaction:added / reaction:removed through the new
  BasePlatformAdapter.set_reaction_handler → GatewayRunner
  ._handle_reaction_event → HookRegistry.emit, independent of the
  routing opt-in. Documented in hooks.md.
- Channel handoff (#45265 by @Kev-fs): slack.reaction_trigger_target
  routes the reaction turn to a configured channel (top-level via
  _hermes_no_thread_response + reply-anchor suppression in
  gateway/platforms/base.py) or C123:<ts> thread.
- Manifest: reaction_removed event subscription added alongside
  reaction_added/reactions:read.
- Docs: slack.md Reaction Triggers section; hooks.md event table rows.

Also credits #44508 by @harrisonmedmedmetrics (inbound reaction_added
handling — same plumbing class, superseded by this consolidated shape).

Co-authored-by: johnkattenhorn <john.kattenhorn.personal@gmail.com>
Co-authored-by: Kev-fs <kevin@fleetsmarts.net>
Co-authored-by: harrisonmedmedmetrics <harrison@medmetricsrx.com>
2026-07-23 11:50:08 -07:00
brooklyn!
9024835bf2
Merge pull request #69602 from NousResearch/bb/voice-interrupt-note
feat(voice): tell the model when the user interrupts its spoken reply
2026-07-22 18:13:19 -05:00
brooklyn!
0ac07fdafd
Merge pull request #69511 from NousResearch/bb/voice-streaming
feat(voice): streaming, conversational TTS with barge-in across all surfaces
2026-07-22 18:13:08 -05:00
Brooklyn Nicholson
177a57f70c feat(voice): desktop flags interrupted submits
markVoicePlaybackInterrupted() / takeVoicePlaybackInterrupted() mirror
the backend latch in the renderer (the barge happens client-side, where
the audio plays). VAD barges and typing over playback mark it; the next
prompt.submit carries interrupted:true, which the TUI gateway latches
into the model note.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
83456d0bc6 docs(voice): streaming TTS + barge-in across surfaces 2026-07-22 17:47:25 -05:00
ethernet
d84e11af4d
rip out brew + pip/PyPI wheel support (#68217)
Removes Homebrew and PyPI wheel/sdist as Hermes distribution paths while
preserving the supported source, Docker, and Nix workflows.

Changes:
- Removes the Homebrew formula, PyPI publish workflow, sdist manifest
  (MANIFEST.in), and wheel/sdist release-attachment logic from scripts/release.py.
- Keeps setuptools metadata and entry points required by editable installs
  and Docker/Nix builds, but adds a setup.py guard that rejects wheel/sdist
  builds outside a sealed Nix derivation (HERMES_NIX_BUILD=1).
- Removes pip/Homebrew install detection, PyPI update checks, the pip
  self-update path, the deprecation-banner state, the postinstall subcommand,
  wheel data-directory fallbacks in agent/i18n.py and hermes_constants.py,
  and the ACP Registry manifest/version-lockstep release logic.
- Adds /nix/store/ path detection so `nix run` / `nix profile install`
  installs (which don't set HERMES_MANAGED) are correctly identified as
  "nix" rather than falling through to "git"/"unknown".
- Retired install-method values ("pip", "homebrew") in existing
  .install_method stamps (both code-scoped and home-scoped) are ignored by
  the allowlist reader and fall through to "unknown" instead of resurrecting
  a retired enum value.
- Updates Nix packaging to ship bare runtime data (locales, optional-mcps)
  through store symlinks and wrapper env vars instead of wheel data-files.
- Removes the ACP Registry manifest/icon and their version-lockstep tests.
- Deletes or rewrites packaging, pip-update, Homebrew, and ACP Registry
  tests; adds parametrized coverage for the packaging build guard covering
  BOTH sdist and wheel paths (the guards live in separate cmdclass entries
  — a passing sdist test proves nothing about the wheel path).
- Updates installation/platform documentation and related user-facing copy.
- Adjusts the supply-chain scan so deleted install-hook files do not trigger
  a finding, while additions or modifications still require the existing
  ci-reviewed label gate.

Supported installation paths (unchanged):
- git installer (install.sh)
- Docker
- Nix/NixOS
- editable development installs (uv sync, uv pip install -e ., pip install -e .)
2026-07-22 16:51:01 -04:00
Teknium
295e20358c
feat(delegation): let subagents use execute_code (#69325)
Children inherit the parent's env, repo, and toolsets but were denied
execute_code ('children should reason step-by-step, not write scripts').
That forces subagents doing mechanical multi-step work (batch file
reads, fetch-N-pages loops, filter-before-context reductions) to burn
reasoning iterations one tool call at a time.

- Remove execute_code from DELEGATE_BLOCKED_TOOLS
- Stop stripping the code_execution toolset from child bundles
- No recursion risk: the sandbox bridges only the 7 SANDBOX_ALLOWED_TOOLS
  (web/file/terminal) — delegate_task and execute_code itself are not
  reachable from inside a sandbox script
- Update schema text, AGENTS.md, and delegation docs
- Tests: blocked-constant, strip, and child-assembly tests updated;
  new test pins execute_code as intentionally unblocked
2026-07-22 06:58:45 -07:00
Naveen Fernando
f7c198dec8 docs: clarify OpenViking local setup
(cherry picked from commit a6807170f1)
(cherry picked from commit 6fb4e9aa8a)
2026-07-22 14:05:28 +05:30
Teknium
afb7bf6a5a
feat(skills): bundle docx, xlsx, and pdf office skills; refresh powerpoint (#68595)
Non-technical users asking for Word docs, spreadsheets, or PDF work had
no bundled skill coverage — docx/xlsx creation required discovering and
installing hub skills, and PDF manipulation had no skill at all beyond
OCR extraction and nano-pdf edits.

- skills/productivity/docx: create (docx-js), edit (unzip -> XML -> zip),
  tracked changes, comments, validation. Adapted from anthropics/skills.
- skills/productivity/xlsx: openpyxl creation/editing, mandatory
  LibreOffice recalc gate, formula-compatibility rules, financial-model
  conventions. Points at optional excel-author for finance-grade work.
- skills/productivity/pdf: merge/split/rotate/watermark/encrypt, form
  filling (AcroForm + flat overlay scripts), text/table extraction,
  reportlab creation, forms.md + reference.md companions.
- skills/productivity/powerpoint: synced to current upstream pptx skill —
  richer pptxgenjs corruption footguns, template workflow, validate.py +
  validators + thumbnail.py, font-substitution QA guidance; drops the
  stale pack.py/editing.md/pptxgenjs.md workflow files.
- Cross-linked ocr-and-documents, nano-pdf, excel-author via
  related_skills so each office skill routes to its siblings.
- deliverable-mode docs mention the new skills; regenerated per-skill
  docs pages, catalogs, and sidebar.
- tests/skills/test_office_document_skills.py: frontmatter contracts,
  referenced-script existence, schema-map integrity, cross-link
  resolution, script compilation.

E2E validated: docx create->render->edit->validate, xlsx recalc
(SUM + _xlfn.TEXTJOIN evaluate correctly), pdf create->merge->extract,
pptx generate->validate->thumbnail.
2026-07-21 05:39:23 -07:00
ethernet
a632e68a01 fix(lsp): never report stale diagnostics — wait for fresh post-edit data
Slow language servers (tsserver on large projects especially) publish
diagnostics long after an edit. The client's wait/report path had three
holes that together surfaced the PREVIOUS edit's errors as if they were
current ("ghost diagnostics"), sending the agent chasing errors it had
already fixed:

1. open_file only cleared the diagnostic stores on first open — on the
   didChange path (every subsequent edit) stale push/pull entries
   survived.
2. wait_for_diagnostics' predicates were satisfiable by that leftover
   state (`path in _published`, `path in _pull_diagnostics`), so the
   "wait" often returned instantly with old data.
3. diagnostics_for merged the stale push store unconditionally, so even
   a fresh clean pull got the old error merged back in.

Fix: anchor freshness on a per-file didChange timestamp.

- Pull results record their request send-time and are dropped when a
  didChange raced past them; the pull store is invalidated on every
  change, not just first open.
- wait_for_diagnostics now returns bool (fresh data vs timeout), only
  counts pushes published at/after the change (and version >= ours when
  the server echoes versions), and accepts an explicit timeout — the
  user's lsp.wait_timeout config now actually controls the inner wait
  budget instead of only the outer thread-join.
- diagnostics_for(fresh_only=True) excludes stores that predate the
  latest change; all manager report paths use it.
- On timeout the manager returns [] ("no data") instead of stale
  state, logs a WARNING via eventlog, and does NOT mark the server
  broken — slow is not dead.
- seed-on-first-push no longer marks the file published, so the TS
  seed push can't satisfy a waiter.

Tests: new "stale" and "slow_push" mock-server scripts model the slow
tsserver, plus client- and service-level regression tests
(tests/agent/lsp/test_stale_diagnostics.py).
2026-07-20 13:43:36 -04:00
RenoMG
95c616be20 fix(supermemory): complete self-hosted endpoint routing 2026-07-20 00:40:40 -07:00
Teknium
39b30bacf7 docs(x_search): comment out reasoning_effort in sample config blocks
The copy-paste config sample had reasoning_effort: low active, which
would silently downshift effort for anyone pasting the block. Keep it
commented like other optional keys. Also add the contributor email
mapping for the salvage.
2026-07-19 23:58:33 -07:00
YAMAGUCHI Seiji
5befa15aba feat: configure X Search reasoning effort 2026-07-19 23:58:33 -07:00
Teknium
9b428ddd08
feat(x_search): default model grok-4.20-reasoning -> grok-4.5 (#67719)
grok-4.5 is xAI's newest release (their versioning is non-monotonic:
4.5 > 4.20) and is the model xAI's own docs use for the server-side
x_search tool. Users who explicitly pinned x_search.model keep their
choice; everyone else picks up the new default via the config
deep-merge — no _config_version bump needed.

- tools/x_search_tool.py: DEFAULT_X_SEARCH_MODEL
- hermes_cli/config.py: DEFAULT_CONFIG x_search.model + comment
- agent/reasoning_timeouts.py: 300s stale-timeout floor entry for
  grok-4.5 (grok-4.20-reasoning entry kept for pinned users)
- docs: x-search.md en + zh-Hans (config sample + troubleshooting)
- tests: default-model assertion + timeout-floor positive case
2026-07-19 16:32:20 -07:00
Teknium
299e409f15
feat(delegation): live-viewable subagent transcripts — tail your subagents while they work (#67479)
* feat(delegation): live-viewable subagent transcripts for delegate_task

Each child now streams an append-only, human-readable log to
<hermes_home>/cache/delegation/live/<delegation_id>/task-<n>.log while it
runs, and the dispatch return includes the paths so the caller can tail
them immediately instead of waiting blind for the consolidated summary.

- New tools/delegation_live_log.py: LiveTranscriptWriter (per-event append
  + flush, one-line rendering with truncation, never raises into the agent
  loop), wrap_progress_callback (tees the child's existing
  tool_progress_callback events into the log, preserves the _flush
  contract), dispatch-time creation with pre-headered files so tail -f
  attaches immediately, manifest.json (goals/task count/per-task status),
  and 7-day retention pruning on new dispatches.
- delegate_task: wraps each child's progress callback with the writer;
  sync results and background dispatch responses gain live_transcripts
  (+ hint field on dispatch); per-task result entries carry
  live_transcript; transcripts finalized with exit-reason markers.
- async_delegation: dispatch_async_delegation_batch accepts an optional
  delegation_id so the live/ dir name matches the returned handle; the
  completion event carries live_transcripts.
- process_registry: consolidated batch-completion block references each
  task's live transcript path.
- Tool schema description documents the live_transcripts return surface;
  docs gain a 'Live Transcripts' section with a tail -f example.

Placement under cache/delegation means the logs are mounted read-only
into remote terminal backends for free. Side-channel only: zero changes
to message content, so prompt caching is unaffected. Transcript-OUT only
— no overlap with the subagent control surfaces of PR #66046.

* fix(delegation): label the kickoff transcript line as user — it is the child's one user message
2026-07-19 10:29:14 -07:00
brooklyn!
360b07794b
docs(desktop): document the desktop plugin SDK (@hermes/plugin-sdk) (#67301)
Add an end-to-end developer guide for extending the native Hermes Desktop
app introduced in #60638: the HermesPlugin contract, PluginContext, every
contribution area (panes, routes, sidebar nav, status/title bar, palette,
keybinds, themes, composer, mount-scoped Contribute), the host API, the
React Query + nanostores data layer, the UI kit + theme variables, the
scoped ctx.rest/ctx.socket backend (plugin_api.py under /api/plugins/<id>)
and its separate enable gate, Settings/defaultEnabled/storage, bundled
plugins, the security model, pitfalls, and a full reference.

- Register the page in the sidebar under Extending -> Plugins.
- Disambiguate from the unrelated web-dashboard plugin SDK from both
  directions, and add a map-table row + a desktop user-guide pointer.
- Fill the gaps in the agent-facing hermes-desktop-plugins skill
  (ctx.rest/socket + backend, React Query, defaultEnabled, Contribute)
  and point it at the new reference so agents know the SDK when writing
  addons.
2026-07-19 04:02:17 +00:00
teknium1
5988fe6cd5 fix: widen headed-mode gate to config, add browser.headed default, tests, docs
Follow-up to @vishnukool's #24064 salvage:
- cleanup skip now uses _is_headed_mode() (config browser.headed OR
  AGENT_BROWSER_HEADED env) instead of env-var-only, with env fallback
  if browser_tool import fails
- browser.headed added to DEFAULT_CONFIG (default false)
- 14 regression tests: resolution precedence, cleanup skip, --headed
  argv injection (local vs cloud), VM cleanup unaffected
- docs: Headed Mode section in browser.md
2026-07-18 10:07:46 -07:00
Gille
35c578177b docs(delegation): align guidance with current contract 2026-07-17 15:55:49 -07:00
teknium1
ae1cd746cb docs(kanban): explain why an unblocked task can later land in triage
The reported confusion was an unblocked task 'unpredictably' ending up in
triage. unblock itself only ever routes to ready/todo; a subsequent same-cause
re-block hitting BLOCK_RECURRENCE_LIMIT is what escalates to triage. Document
this deterministic loop-breaker at the human-facing lifecycle level so users
stop reading it as an LLM decision.
2026-07-17 15:47:39 -07:00
Gille
f29c28d6d0 docs(kanban): clarify unblock status routing 2026-07-17 15:47:39 -07:00
Teknium
05b5e2b6e9 docs(codex): document live app-server display; AUTHOR_MAP entries
- codex-app-server-runtime.md: add a Live display section covering the
  stream/reasoning/tool-card bridge and show_commentary gating.
- release.py: AUTHOR_MAP entries for HaiderSultanArc, jjadeo-oss, juanfradb
  (the latter two for forthcoming follow-up salvages of #62396 / #18050).
2026-07-17 13:44:12 -07:00
Teknium
e4f87557b9
feat(kanban): modal create-task dialog, editable board project directory, comment workflow hint (#66333)
Community feedback (@LSanapalli on X): the inline task-creation form is
cramped inside a ~280px column with no way to resize; board-level
workspace defaults can't be changed after board creation; and users
believe they must block a task, comment, then unblock just to talk to
a worker.

- Create-task dialog: replace the inline column form with a centered
  modal (reuses hermes-kanban-dialog chrome, 36rem wide) with labeled
  fields for title, assignee, priority, skills, workspace kind/path,
  goal mode, and parent task. Same request shape; Enter/Escape behavior
  preserved; submit disabled until a title is present.
- Board settings dialog: new Settings button in the board switcher opens
  a modal to edit display name, description, and the board-level default
  project directory (default_workdir). PATCH /boards/:slug now accepts
  default_workdir (validated absolute existing dir; empty string clears;
  omitted leaves unchanged) and returns the recomputed
  default_workspace_kind so task-creation defaults follow immediately.
- Comment workflow hint: the task drawer's comment box now explains that
  comments land on the thread immediately and reach the worker on its
  next run/kanban_show() — no block/unblock dance needed — with a fuller
  tooltip for when blocking IS the right tool.
- i18n: new keys optional in the kanban namespace with English fallbacks
  in the bundle (established pattern; avoids churning 17 locale files).
- Docs: dashboard section updated for the dialog + Settings button.
2026-07-17 07:23:54 -07:00
teknium1
abc22cdf1a fix(cron): harden execution attempt ledger 2026-07-17 04:58:35 -07:00
Teknium
0f102fa4dc
feat(browser): store full snapshots on truncation; make eval denylist opt-in (#65923)
* feat(browser): store full snapshots on truncation; make eval denylist opt-in

Two harness fixes motivated by BU_Bench results where fixed-verb + lossy
observation cost Hermes heavily vs code-driven browser agents:

1. Snapshot truncation no longer loses content. When a snapshot exceeds
   the 8000-char threshold, the complete accessibility tree is saved to
   cache/web (same truncate-and-store pattern as web_extract) and the
   truncated view / LLM summary includes the file path plus a ready-made
   read_file call. Element refs beyond the cut are recoverable without
   re-snapshotting. Stored copies are force-redacted and capped at 2MB;
   content-hash filenames dedupe repeated snapshots of the same page.

2. The browser_console(expression=...) sensitive-primitive denylist is
   now opt-in via browser.restrict_evaluate (default false). The
   names-based denylist blocked legitimate DOM extraction — any selector
   or expression containing 'fetch', 'cookie', 'input', etc. — which
   crippled the agent's only programmatic page-inspection path. The
   SSRF/private-URL egress guards in _browser_eval are independent of
   this policy and remain always-on. browser.allow_unsafe_evaluate keeps
   its meaning (bypass the denylist) for configs that already set it.

* test: update None-guard test for stored-snapshot pointer in _extract_relevant_content

test_normal_content_returned pinned the exact return value; the summary
now carries a pointer to the stored full snapshot. Assert the summary
passes through and the pointer is present instead.

* feat(browser): align snapshot threshold with web_extract's 15k char budget

SNAPSHOT_SUMMARIZE_THRESHOLD 8000 -> 15000, matching
web_tools.DEFAULT_EXTRACT_CHAR_LIMIT so the snapshot and web_extract
truncate-and-store paths give the model the same per-page budget.
_truncate_snapshot's default max_chars now follows the constant.
Invariant test added; docs (en+zh) and CLI tip updated.
2026-07-16 23:41:26 -07:00
Teknium
75c878217d fix(moa): route per-slot reasoning effort through the canonical parser
_clean_reasoning_effort kept its own whitelist that stopped at 'max',
silently dropping 'ultra' from MoA slot configs. Route it through
hermes_constants.parse_reasoning_effort — the same one-source-of-truth
fix the salvaged commit applies to the gateway — so future effort
levels can't drift here either. Docs updated to list ultra.

Follow-up to salvaged PR #64012.
2026-07-16 06:14:58 -07:00
teknium1
261b0f8240 docs(mcp): document redirect_uri proxied callbacks + redirect_host WAF workaround
Adds the proxied-callback option to the remote/headless OAuth section,
links the mcp-oauth-remote-gateway skill for fully headless gateways,
and documents the WAF pitfall behind redirect_host.
2026-07-16 06:14:34 -07:00
kocaemre
681852a5b9 docs: address audit review feedback 2026-07-16 04:47:40 -07:00
Teknium
176a98c39a docs: refresh salvaged audit fixes against current main
PR #36051's values went stale since May 31: session-store SCHEMA_VERSION
is now 21 (PR said 14), and the dashboard ships 8 built-in themes
(PR said 7). Also document the v16/v18/v20 data migrations added since.
2026-07-16 04:47:40 -07:00
kocaemre
a710becd6c docs: fix 25 documentation/code inconsistencies (audit round 3)
Cross-checked website/docs against the source at main HEAD and corrected
documented commands, env vars, config keys, headers, and default values
that don't match the code. Docs-only; no behavioral changes.

Refs #36048

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 04:47:40 -07:00
Teknium
bd7e480236
fix(compression): give fallback candidates their own timeout budget + escalate repeat-timeout cooldowns (#65143)
Fixes #62452. Two amplifiers turned one slow auxiliary route into a
per-turn multi-minute stall:

1. Fallback candidates inherited the exact effective_timeout the primary
   was called with. When the primary's deadline was short (tuned or
   already burned), an independently healthy fallback died on the same
   clock — the reporter's 163k-token compression needed ~90s on the
   fallback and got the primary's 30s, every turn. fallback_chain
   entries may now declare their own 'timeout' (seconds); both fallback
   candidate call sites (sync + async) resolve it via
   _fallback_entry_timeout, label-scoped so only configured-chain
   candidates are affected. No entry timeout → task-level timeout,
   preserving existing behavior.

2. A session whose transcript structurally cannot be summarized within
   the deadline re-attempted every 60s, re-burning the full timeout on
   every subsequent turn. Consecutive timeout-class failures now
   escalate the cooldown 60s → 300s → 900s (capped); any successful
   summary or session reset clears the streak. Timeout classification
   takes precedence over the streaming-closed 30s rung ('timed out'
   also matches _is_connection_error) and now recognizes the SDK's
   'Request timed out.' phrasing.

Fail-safe behavior is unchanged: all messages are preserved when every
candidate fails; the cooldown only spaces out retries.
2026-07-15 12:28:09 -07:00
Justin Schille
3dca75b45c feat(moa): support per-slot reasoning effort 2026-07-14 21:08:22 -07:00
kshitijk4poor
33dfb7e4ec docs: add Upstage Solar to provider docs (env vars, fallback table, --provider list) 2026-07-15 00:09:24 +05:30