Skip to content

test: add unit tests for 8 untested core modules - #60

Merged
teknium1 merged 2 commits into
NousResearch:mainfrom
0xbyt4:test/expand-coverage
Feb 27, 2026
Merged

test: add unit tests for 8 untested core modules#60
teknium1 merged 2 commits into
NousResearch:mainfrom
0xbyt4:test/expand-coverage

Conversation

@0xbyt4

@0xbyt4 0xbyt4 commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds 210 new unit tests covering 8 previously untested modules
  • Total test suite: 380 passing, 0 failing
  • Increases coverage from ~10% to meaningful coverage of core logic

Modules Tested

Module Tests What's covered
cron/jobs.py 34 Schedule parsing (duration, interval, cron, ISO), job CRUD, due-job detection
tools/memory_tool.py 32 Security scanning (injection, exfil, unicode), MemoryStore add/replace/remove, persistence, dispatcher
toolsets.py 19 Toolset resolution, cycle detection, validation, custom toolsets, structural consistency
tools/file_operations.py 37 Write deny list, result dataclasses (ReadResult, WriteResult, etc.), shell helpers
agent/prompt_builder.py 24 Context injection scanning, content truncation, skill description parsing, context file discovery
agent/model_metadata.py 16 Token estimation, default context lengths, API fetch with cache mocking
hermes_state.py 28 SessionDB SQLite CRUD, FTS5 full-text search, export, prune, schema/WAL validation

Test plan

  • uv run pytest tests/ -v — 380 passed, 3 skipped (croniter optional), 0 failed

Add comprehensive test coverage for:
- cron/jobs.py: schedule parsing, job CRUD, due-job detection (34 tests)
- tools/memory_tool.py: security scanning, MemoryStore ops, dispatcher (32 tests)
- toolsets.py: resolution, validation, composition, cycle detection (19 tests)
- tools/file_operations.py: write deny list, result dataclasses, helpers (37 tests)
- agent/prompt_builder.py: context scanning, truncation, skills index (24 tests)
- agent/model_metadata.py: token estimation, context lengths (16 tests)
- hermes_state.py: SessionDB SQLite CRUD, FTS5 search, export, prune (28 tests)

Total: 210 new tests, all passing (380 total suite).
These tests documented the macOS symlink bypass bug with
platform-conditional assertions. The fix and proper regression
tests are in PR NousResearch#61 (tests/tools/test_write_deny.py), so remove
them here to avoid ordering conflicts between the two PRs.
@teknium1
teknium1 merged commit 274e623 into NousResearch:main Feb 27, 2026
linxule added a commit to linxule/hermes-agent that referenced this pull request Apr 27, 2026
…k variant

Parity port from linxule/hermes-kimi-plugin@93476f5 — same four fixes plus
the NousResearch#60 fold-in:

1. connect# off-by-one: counter + ts now bumped pre-dispatch in
   _group_subscribe_once so the gap log inside _on_group_event reads the
   correct connect# (matches the subscribe-stream-live log emitted moments
   later). Subscribe-live log + backoff clamp stay post-dispatch as the
   operator-visible recovery announcement.

2. Monotonic clock: arrival-time tracking + since-reconnect correlation
   now use time.monotonic() (immune to NTP/leap-second/VM-suspend).

3. _BoundedLRU shared cap: _last_arrival_time_per_room gets its own
   _ARRIVAL_TIME_CACHE_DEFAULT_MAX (10000), so eviction can't blind
   Phase 0 gap detection under cardinality pressure.

4. Anchor-only-on-first-page in list_group_messages: pageToken and
   start_message_id/end_message_id are now mutually-exclusive cursoring
   modes.

Fold-in (NousResearch#60): _fetch_group_message uses max_pages=2 for tight-range
pagination resilience.

Tests: 200 passing on fork (194 unit + 6 plugin-integration). Same +10/-1
delta as standalone. Existing handler-exception test updated to reflect
new pre-dispatch state semantic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
test: add unit tests for 8 untested core modules
MickaelV0 added a commit to Roxabi/hermes-agent that referenced this pull request May 24, 2026
The upstream image entrypoint runs as root and tries to `su hermes`,
which fails under our DropCapability=all (no CAP_SETUID). Force the
container to start as uid 10000 directly via User= alongside UserNS=.
The earlier comment ("starts directly as hermes") was inaccurate —
verified 2026-05-22 on digest 9d0a12c0… that removing User= produces
"error: failed switching to 'hermes': operation not permitted" and a
crashloop.

Also update CLAUDE.md provider wiring: llmcli now joins roxabi.network
(llmCLI NousResearch#60 resolved), so the canonical path is `llmcli:18091` via
container-name DNS — not `host.containers.internal:18091`, which is no
longer reachable from bridge containers (curl returns 000).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@acoastalfog

Copy link
Copy Markdown

Implemented in commit d997e70 (Render semantic KB write receipts) on codex/system-elegance-offline-control-plane.

What changed:

  • Hermes KB journeys now render compact semantic write receipt packets with prod write status, publication status, reconciliation status, transaction id, changed paths, object ids, and operation ids.
  • Added aliases for offline/status packet variants and redaction tests for source bodies, login/account strings, URLs, tokens, and private paths.
  • Kept Hermes as a mobile/Telegram renderer only; no durable write logic moved into Hermes.

Verification:

  • uv run --with pytest-timeout pytest tests/plugins/test_kb_journeys.py -k semantic_write_receipt -q -> 2 passed
  • uv run --with pytest-timeout pytest tests/plugins/test_kb_journeys.py -q -> 57 passed
  • python -m compileall plugins/kb_journeys/__init__.py tests/plugins/test_kb_journeys.py -> passed
  • git diff --check -> passed

Remaining gates: no live Telegram canary or prod MCP packet replay was run. Renderer aliases may need a small follow-up if kb-engine emits a packet type spelling outside the tested set.

@mms358-create

Copy link
Copy Markdown

Implemented in commit 64c2f20a0 (AFK-V2: #60 V2 Response Finalizer). All acceptance criteria met:

  • One typed finalizer interface: handles normal, empty, partial-stream (length), interrupted, budget-exhausted, tool-failed, and provider-failed outcomes via OutcomeKind enum + _classify_outcome() + _recover_text()
  • Final text sanitization: strips // blocks (open+closed), tool markup; never exposes hidden reasoning or raw command output
  • Mutation grounding: detects I've saved/deleted/sent/created that... and appends MUTATION_UNVERIFIED VerificationBlocker when no ok/mutation_verified evidence
  • Plugin output transforms: OutputTransformHook protocol, hooks fire exactly once; hook failure is logged and skipped
  • Gateway result shape: FinalizedResponse.to_gateway_result() consistent for WhatsApp (gateway) and API entrypoints
  • Session persistence: messages tuple preserved through finalization
  • WhatsApp/API can select via HERMES_V2_RESPONSE_FINALIZER (default off)
  • Issue fix(install): create ~/.hermes before moving Node.js directory #53 stub replay: 4/4 before/after
  • Rollback: disable flag; V1 path (_provider_result_to_gateway_outcome) remains intact

ohungerCH added a commit to ohungerCH/hermes-agent that referenced this pull request Jun 28, 2026
…enqueue v2 -> cronjob/create_job/scheduler -> Codex)

Lane-neutral: web-only/model/provider/repeat-Pins bleiben hartkodierte Modul-Literale
(Threat F). reasoning_effort ist das EINZIGE input-honored Feld; unbekannte/getamperte
Werte clampen auf "high". Worker akzeptiert enqueue v1 UND v2 (abwaertskompatibel).
Engine-Smoke gruen gegen den Worktree. NICHT deployt (Image-Rebuild = morgens mit Owner).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ohungerCH added a commit to ohungerCH/hermes-agent that referenced this pull request Jun 28, 2026
…job uebergeben (Codex-Stream-Stall)

"high"/"xhigh" lassen Codex' Stream lange stumm denken -> der 12s-no-events->Reconnect-
Watchdog killt den Stream -> Broken-Pipe-Endlosschleife (Job haengt ewig auf running).
Recherche faellt bewusst auf den Provider-Default (config '') zurueck = bewaehrtes Verhalten,
unabhaengig vom App-ui_hints-Selektor. Plumbing bleibt dormant; reaktivieren NACH einem
Reconnect-Watchdog-Fix der langes Denken toleriert (Folge-Task der NousResearch#60).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
test: add unit tests for 8 untested core modules
ohungerCH added a commit to ohungerCH/hermes-agent that referenced this pull request Jul 11, 2026
…s-Merge, Differenzierer erhalten

Standard-3-Wege-Merge (KEIN Rebase/Force): Eltern[1]=21b01cb50 (Prä-Update-HEAD),
Eltern[2]=v2026.7.7.2 (b7751df, Upstream-Release "chore: release v0.18.2"). fork/main
bewegt sich nur vorwärts -> Codex zieht per FF. Baseline gepinnt auf den Release-Tag, nicht
den driftenden origin/main. Mechanik/Analyse: docs/llm/2026-07-11_PLAN_hermes-018-migration.md.

WARUM Standard-Merge statt M3-read-tree: _config_version 30->33 trägt Upstream-Migrations-
Funktionen; ein Wholesale-checkout hätte die lautlos verworfen. merge-tree: 60/78 auto-merge,
18 echte Konflikte (via Workflow aufgelöst + adversarial verifiziert), 41 our-only unverändert.

DIFFERENZIERER ERHALTEN (Vollständigkeits-Gate + Symbol/Marker-Survival-Sweep: 100 Symbole
+ 35 Marker überleben): Vault (tools/vault/*, 9 Dateien), trusted_surface, stt_redaction,
research_enqueue_worker, threat_patterns (4-Tupel + DE-Muster), wrap-escape (NousResearch#34),
untrusted_origin (kanban Deprivilege), Recall-Passthrough (memory/tool_executor/runtime_helpers).

SICHERHEITS-KORREKTUREN (zwei echte Silent-Drops, vom Testlauf gefangen — der Resolve-Workflow
+ adversariale Verifikation sahen sie NICHT):
* no_mcp-Boden (hermes_cli/tools_config.py): der blosse Config-Pin `api_server: []` GENÜGT in
  0.18.2 NICHT — ohne no_mcp-Sentinel schleust _get_platform_tools alle MCP-Server + (bewiesen)
  16 Toolsets inkl. computer_use/code_execution/browser auf den untrusted Voice-Pfad (RCE).
  Fix: In-Code-Output-Backstop `if platform in FAIL_CLOSED_PLATFORMS: return set()` am
  Funktions-Ausgang (churn-robust). ADR-0039 Invariante 1. Zur Laufzeit bewiesen.
* _kanban_worker_skill_available (hermes_cli/kanban_db.py): Merge droppte Funktion + Aufrufstelle
  (upstream refaktorierte drumherum). Restauriert (Def + Call vor `if task.skills:` in
  _default_spawn). 32 Deprivilege-Tests wieder grün.

BEWUSSTE DROPS (Fork-Lock-Reduktion): §8b-Vault-Seed aus DEFAULT_CONFIG (Kern-Edit -> gehört in
externes config_file, Fabel-5); no_mcp-Kern-Clamp (ersetzt durch Output-Backstop).

BEWUSSTE DIVERGENZEN / QUARANTÄNEN (test-only, jede dokumentiert; Produktionscode unberührt):
write_approval NousResearch#46783 (unser origin-konditionales Gate vs Upstreams unbedingtes Staging ->
approval.py-Reconciliation deferred an Fabel-5); api_server-Boden-Tests (Upstream nimmt
api_server=tool-tragend an, wir klemmen ihn — 2 skip + 1 zur Regressionswache adaptiert);
reasoning_effort-Forwarding (schon bei HEAD deaktiviert, e57d515/NousResearch#60 Codex-Stream-Stall);
kanban-subscribe Test-Isolations-Leak (reset_session_vars); cron-ticker->housekeeping-Refactor;
stale -worktree-Pfad -> runtime/hermes-main; hook-handler skip-if-absent (externes Deploy-Artefakt).

VERIFIKATION: 1578 passed / 32 skipped (betroffene Bereiche: §6-Drift-Stellen, Vault, alle
Differenzierer, Touched-Files). uv.lock via `uv lock` regeneriert (psycopg/asyncpg/cryptography).
DEFERRED (nicht "in the tree"-blockierend): ruff/ty-Lauf; Externalisierung der Sicherheits-
Schichten auf native Hooks; Bahn-A-Mount der hochpriv-Tools (Fabel-5-Aktivierung).

Rollback: Tag backup/pre-018 @ 21b01cb; Vorwärts `git revert -m 1 <merge-commit>` (NIE reset).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Meraniya pushed a commit to Meraniya/hermes-agent that referenced this pull request Aug 6, 2026
…ealing fallback (NousResearch#60)

generate_launchd_plist() now routes launchd through gateway-wrapper.sh when
present + executable, so ~/.hermes/.env is sourced into the process
environment (launchd has no native EnvironmentFile) and the preflight
stale-gateway reaper runs before every start. The wrapper execs the python +
module args passed after it, preserving --profile end-to-end. Falls back to
the direct-python form if the wrapper is missing/non-executable, so the
gateway never fails to start under KeepAlive (self-heal).

Fixes the intermittent Slack-enablement drop (DAN-1491): SLACK_BOT_TOKEN is
now in os.environ at launch via the wrapper, independent of ~/.hermes/.env
file state at the startup instant, so the registry-driven is_connected gate
(NousResearch#31116) no longer silently leaves Slack disabled when .env is momentarily
missing/empty/mid-replacement during a config/secret restore.

Refs DAN-1522..DAN-1527 (Hermes launchd wrapper-entrypoint project).

Co-authored-by: Claude <noreply@anthropic.com>
Soju06 added a commit to Soju06/hermes-agent that referenced this pull request Aug 13, 2026
nekwo added a commit to nekwo/hermes-agent that referenced this pull request Aug 17, 2026
…stead of fighting it

Two daemon threads raced from `ready`: the read-model build the launcher's canvas
is waiting on, and ~5-8s of provider warmup CPU (OpenAI SDK import ~1.7s, SSL
context / CA verification ~0.7s, and since BW-H3 the `model_tools` import plus
the discovery/check_fn storm ~1.2s+). Under the GIL the second is subtracted
from the first, and NOTHING the provider warmup produces is consumable before the
canvas is authoritative — its purpose is the first chat turn's latency, which is
after. Two independent investigations derived this same fix (HY-H2 = HC-H3);
adopted once.

- one daemon thread, sequential: `snapshot_prewarm()` then
  `_prewarm_provider_runtime()`, each step exception-isolated so a build that
  raised still leaves the providers warm (the property two threads had for free
  and a naive `then` would silently drop);
- the thread still starts BEFORE the `ready` emit, untouched: only the build that
  started first can be shared, so a warmup started after that frame loses the
  race to the launcher's first request and queues a redundant second build. The
  invariant the old comment block asserted — "the read-model build must not queue
  behind this one's ~3s SDK import" — is now provable rather than raced, because
  that import has not started yet;
- `_prewarm_provider_runtime` becomes an injectable `serve_loop` parameter with
  the same contract `snapshot_prewarm` has (HC-H3), wired at the real entry
  point. It was an unconditional `Thread.start()` inside the loop, which meant
  every unit test of the loop imported the OpenAI SDK to observe a ready frame.

The brief's one-line version of this fix — reorder the two `Thread.start()` calls
— stays REJECTED as a no-op, not as wrong-direction: starts issued microseconds
apart schedule nothing, and both measured boots reached `model_tools` ~5s in
regardless of order. Both sources refused it independently for the same mechanism.

Cost named rather than hidden: a chat turn sent inside the (now shorter) boot
window pays the cold SDK import inline, exactly as every turn did before the
prewarm existed — best effort by the prewarm's own contract, and the canvas is not
yet authoritative in that window. If receipts show first-turn warmup misses, the
refinement is "provider prewarm starts at first-request-enqueue OR
build-completion, whichever is first", not a revert.

Tests (tests/agent_runtime/test_harness_serve.py, 3 new + 2 tightened): the
gate-ordering witness — a snapshot-prewarm fake parked on a gate the TEST holds,
with the provider recorder asserted EMPTY at that instant (the BW-L5
never-completing-fake pattern; the mutant cannot un-append and cannot release the
gate); `ready` still on the wire before either prewarm completes, read off the
frames after the loop returns with the gate STILL held; a failed build still warms
the providers; and HC-H3's injectable-parameter contract at the entry point.
Ordering, counts and emptiness only — the seconds are read off EG-2.1's receipts
(plan NousResearch#60), never asserted in a test. Mutations run and reverted: restore the two
parallel threads (2 red), join the prewarm thread before ready (1), drop the
per-step isolation (1), drop the entry-point wiring (1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nekwo added a commit to nekwo/hermes-agent that referenced this pull request Aug 18, 2026
…nd the office settle waits for the hydrate instead of for a frame

Two checkpoint-reddening defects, both mine, both from the MC-4 commits.

1. THE INVENTORY GATE WALKED THE VIRTUALENV.
`test_the_pin_covers_every_production_call_site_there_is` used `rglob("*.py")`
with two string exclusions. Measured: 10,112 files found, 7,121 OPENED and
AST-parsed on every run, 5,802 of them third-party code under `.venv-ci/`
(~51 MiB). Two independent defects, not one:

* CORRECTNESS. The claim is "every production `stream_frames` caller in THIS
  repo". A vendored package defining anything of that name would red the gate
  for a reason nobody here owns, and the message would send the reader hunting
  a caller they cannot edit. The docstring said "outside `tests/`"; `.venv-ci/`
  is outside `tests/` and is exactly what it did not mean.
* IT HUNG THE CHECKPOINT. A virtualenv is not a stable tree — it can be written
  while it is read — and the full run timed out inside this walk, blocked on
  opening a file under `.venv-ci`.

The scope now comes from `git ls-files --cached --others --exclude-standard`.
Both halves are load-bearing: tracked-only would go quietly VACUOUS in the
window an author is adding a caller not yet `git add`-ed — the gate reporting
success on a set missing the file it exists to catch — and `--exclude-standard`
is what removes the vendored trees. This is NOT the hand-typed-denylist defect
MCF-2 recorded: there is one authority, `.gitignore`, already the repo's own
answer to what is its own code, and a new vendored directory is visible either
way — ignored and out of scope, or unignored and loud in `git status`. A
denylist would admit it silently. A git failure raises rather than falling back
to the walk, which would restore the old behaviour when nobody was watching.

Files opened and parsed: 7,121 -> 1,292 (5.5x fewer; 28.3s -> 12.0s as context
only, per ruling NousResearch#60 the assertion is the count). Same answer both ways.

Two kills, because the scope and the inventory are two claims (C30): a real
third caller added to a tracked production file reds it; and a planted
git-ignored file (under `.pytest_cache/`, outside `tests/`, so it is not
excluded twice) calling `stream_frames` must NOT red it — reverting the scope to
the walk reds that one, with `.venv-ci` paths in the failure message.

2. THE OFFICE SETTLE WAITED ON A PROXY THAT ARM 2 INVALIDATED.
The coordinator's first checkpoint failed at
`test_unsubscribe_stops_delivery_against_the_real_producer`. It is NEITHER a
delivery regression NOR a deadline that lost a race, and both framings would
have led to a wrong fix.

`_settled_subscription` waited on `frames_delivered >= 1` as a proxy for "the
mandatory re-hydrate has landed" — exact only while the hydrate was necessarily
a generation's FIRST frame. Arm 2 ended that: the boot build now emits liveness
on the cadence, this fixture runs at 0.25s, and the office sink turns a
heartbeat into no notification at all. So the settle returned while the producer
was STILL BUILDING, the test's write landed before the hydrate was built, was
absorbed into it, and reached the sink as one `runtime.office.resync` instead of
the `runtime.office.patch` awaited.

Proven by holding the boot build, which reproduces it deterministically: peer
methods `[]` at settle, `['runtime.office.resync']` at the deadline AND three
seconds past it. The patch was never coming, so widening the deadline would have
fixed nothing and hidden this.

The fix synchronises on the EVENT rather than on a count that used to imply it:
the fixture records the frame types each producer generation yields, and the
settle waits for the hydrate to have been produced AND delivered (its 1-based
position in that list is exactly the delivered count that means "the sink has
run for it", the pump being FIFO). A new case pins it, holding the build on a
`threading.Event` released by evidence — that liveness was actually emitted —
so a build too fast to heartbeat cannot make it pass vacuously. Reverting the
settle to the old proxy reds that case with `['heartbeat']`, which is the
mechanism itself.

Goldens byte-identical, fence unchanged, no production code touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants