hermes - ✅(Solved) Fix Feature: Add systemd Conflicts= directive to multi-profile gateway unit generation [3 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#17396Fetched 2026-04-30 06:47:53
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×3labeled ×3referenced ×1

Root Cause

The generate_systemd_unit() function in hermes_cli/gateway.py (lines ~849-912) generates the [Unit] section without a Conflicts= directive. Each profile generates its unit in isolation, unaware of other profiles on the same host.

Fix Action

Fix / Workaround

Current workaround

PR fix notes

PR #17539: feat(gateway): add optional systemd Conflicts= directive for multi-profile hosts

Description (problem / solution / changelog)

Closes #17396.

Summary

When multiple Hermes profiles live on the same host (per-environment / per-workspace separation), their generated gateway/api units can step on each other at start time — port 18800 collision, resource contention, etc. Until now the only protection has been a hand-written systemd drop-in, which hermes gateway install --force silently regenerates over.

Adds an opt-in top-level systemd_conflicts config key. When non-empty, generate_systemd_unit() emits matching Conflicts= and Before= directives in the [Unit] section so systemd enforces mutual exclusion at start time and gives a deterministic tie-break.

Config shape

# config.yaml (any profile)
systemd_conflicts:
  - hermes-api-hermes-infrastructure.service
  - hermes-gateway-hermes-transversal.service

Generated unit contains:

[Unit]
Description=Hermes Agent Gateway - Messaging Platform Integration
After=network-online.target
Wants=network-online.target
Conflicts=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service
Before=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service
StartLimitIntervalSec=600
StartLimitBurst=5

Backward compatibility

Default behavior unchanged. When systemd_conflicts is absent or empty (the default), the conflicts block is empty-string and the rendered unit is byte-identical to the pre-patch output. Existing installs don't need any action.

Both user-scope and system-scope units are covered.

Tests

4 new tests under TestGeneratedSystemdUnits:

  • test_unit_omits_conflicts_when_not_configured — baseline preserved
  • test_user_unit_includes_conflicts_when_configured
  • test_system_unit_includes_conflicts_when_configured
  • test_conflicts_entries_strip_whitespace_and_empties — defensive: empty / None entries are filtered so the output never contains Conflicts= with a trailing space or bare directive

Files

  • hermes_cli/gateway.py: +26 / -2 (adds _get_systemd_conflicts() helper + injects a conflicts_block string into both user and system unit templates)
  • tests/hermes_cli/test_gateway_service.py: +56

Changed files

  • hermes_cli/gateway.py (modified, +37/-2)
  • tests/hermes_cli/test_gateway_service.py (modified, +45/-1)

PR #17568: feat(gateway): add compression notifications for gateway platforms

Description (problem / solution / changelog)

Problem

In CLI (shell) mode, users see real-time compression notifications:

⚠️ Session compressed 2 times — accuracy may degrade. Consider /new to start fresh.

In gateway mode (Matrix, Telegram, Discord, Slack, etc.), compression was completely silent. Users had no visibility into when it happened or whether it was effective.

Solution

Add user-facing compression notifications in gateway mode:

  • ⏳ Compaction du contexte en cours… — before compression starts
  • ✅ Compaction terminée (XK → YK tokens) — after successful compression
  • ⚠️ Compaction insuffisante — envisagez /new — when compression was ineffective (tokens still ≥ 95% of context)
  • ⚠️ Session compactée N fois — when session has been compressed ≥2 times (quality degrades)
  • ⚠️ Compaction échouée : error — on failure

Technical changes

  1. gateway/hooks.py — Added session:compress to the documented events list. Hook system already supports any event via emit(), so no code changes needed.

  2. gateway/session.py — Added hygiene_count: int = 0 to SessionEntry for tracking the number of times session hygiene has auto-compressed. Serialized/deserialized in to_dict()/from_dict(). Backward-compatible — old data defaults to 0.

  3. gateway/run.py — Added notifications in the session hygiene section (lines ~3411-3570):

    • Pre-compression: emit session:compress hook with phase=start + send ⏳ Compaction… message
    • Post-compression: emit session:compress hook with phase=end + send success/insufficient/repeated warning messages
    • Error: send failure notification
  4. Tests:

    • Updated test_session_hygiene_messages_stay_in_originating_topic to verify notifications ARE sent (previously asserted len(adapter.sent) == 0)
    • Added TestHygieneCount class in test_session.py for serialization/deserialization/backward-compat

Backward compatibility

  • SessionEntry.hygiene_count defaults to 0 for old session data
  • Hook session:compress is a new event — existing hooks are unaffected
  • Notification messages are best-effort — failures are caught and logged, never blocking

Closes #17396

Changed files

  • gateway/hooks.py (modified, +1/-0)
  • gateway/run.py (modified, +114/-7)
  • gateway/session.py (modified, +6/-0)
  • tests/gateway/test_session.py (modified, +47/-0)
  • tests/gateway/test_session_hygiene.py (modified, +16/-3)

PR #17635: feat(gateway): add dynamic Conflicts=/Before= directives for multi-profile gateway units

Description (problem / solution / changelog)

Problem

When Hermes generates multiple gateway profiles (infrastructure, transversal, hb), each profile creates an independent systemd service. If two profiles start simultaneously, they conflict on the same Matrix device ID, ports, and state — causing crashes and data corruption.

The only protection was a manual drop-in (10-conflicts.conf) or a masked service link, which is not automatic and disappears when the service is regenerated.

Solution

generate_systemd_unit() now automatically:

  1. Detects other installed hermes-gateway*.service files on the machine
  2. Injects Conflicts=<other>.service and Before=<other>.service directives into the [Unit] section
  3. Normalizes these dynamic directives out during staleness comparison, preventing spurious repairs when profiles are added/removed
  4. Refreshes other gateway units after installation to keep their conflict directives in sync

This ensures mutual exclusion at the systemd level — no manual drop-ins needed.

New functions

FunctionPurpose
_find_other_gateway_services(system)Scans systemd unit directory for other gateway services
_format_conflict_directives(system)Builds Conflicts=/Before= lines for the [Unit] section
_normalize_systemd_unit_for_comparison(text)Strips dynamic conflict directives for staleness check
_refresh_other_gateway_units(system)Regenerates other gateway units to keep conflicts in sync

Test fixes (pre-existing, not caused by this patch)

6 tests were failing on root because generate_systemd_unit(system=True) refuses to create a system service for root without run_as_user, and _preflight_user_systemd() fails when the user D-Bus session is unavailable. All 6 now have the necessary mocks:

  • test_system_unit_avoids_recursive_execstop_and_uses_extended_stop_timeout
  • test_system_unit_includes_local_bin_in_path
  • test_systemd_start_refreshes_outdated_unit
  • test_systemd_restart_refreshes_outdated_unit
  • test_systemd_restart_self_requests_graceful_restart_and_waits
  • test_systemd_restart_recovers_failed_planned_restart

New tests

TestGatewayConflictDirectives class with 9 tests covering:

  • Service discovery and self-exclusion
  • Conflict directive generation with/without other services
  • Normalization stripping only gateway/api conflict directives
  • System and user unit generation including conflict directives
  • Staleness check ignoring dynamic conflict directives

Test Results

All 116 tests pass (107 original + 9 new).

Closes #17396

Changed files

  • hermes_cli/gateway.py (modified, +179/-3)
  • tests/hermes_cli/test_gateway_service.py (modified, +187/-3)

Code Example

# /etc/systemd/system/hermes-gateway-hermes-infrastructure.service.d/10-conflicts.conf
[Unit]
Conflicts=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service
Before=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service

---

systemd_conflicts:
     - hermes-api-hermes-infrastructure.service
     - hermes-gateway-hermes-transversal.service
RAW_BUFFERClick to expand / collapse

Problem

When running multiple Hermes profiles on the same host (e.g., hermes-infrastructure, hermes-hb, hermes-transversal), their systemd service units can conflict:

  • hermes-gateway-hermes-infrastructure.service and hermes-api-hermes-infrastructure.service both bind port 18800
  • hermes-gateway-hermes-transversal.service may also conflict on shared resources

If two such services start concurrently, they crash or produce unpredictable behavior. Currently, the only protection is manual systemctl mask/disable interventions.

Root cause

The generate_systemd_unit() function in hermes_cli/gateway.py (lines ~849-912) generates the [Unit] section without a Conflicts= directive. Each profile generates its unit in isolation, unaware of other profiles on the same host.

Current workaround

We added a systemd drop-in:

# /etc/systemd/system/hermes-gateway-hermes-infrastructure.service.d/10-conflicts.conf
[Unit]
Conflicts=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service
Before=hermes-api-hermes-infrastructure.service hermes-gateway-hermes-transversal.service

This works but is fragile: a hermes gateway install --force (upgrade/reinstall) regenerates the unit file without this protection. The drop-in survives, but the design intent is split across two locations.

Proposed solution

Add Conflicts= and Before= to the generated unit when the service detects other gateway/api services for different profiles on the same host. Two approaches:

  1. Profile config approach: Add an optional systemd_conflicts list to profile.yaml or config.yaml:

    systemd_conflicts:
      - hermes-api-hermes-infrastructure.service
      - hermes-gateway-hermes-transversal.service

    The generator reads it and injects the directives.

  2. Runtime detection approach: In generate_systemd_unit(), scan /etc/systemd/system/ and ~/.config/systemd/user/ for existing hermes-*-*.service files that share the same port or resource, and add Conflicts=/Before= automatically.

Why Conflicts= + Before= together?

  • Conflicts= ensures systemd stops conflicting services before starting this one
  • Before= ensures ordering so the winner starts first, avoiding race conditions

Impact

  • Any multi-profile Hermes deployment (multiple gateways on one host)
  • Default installations are unaffected (single profile, no conflict)

Environment

  • Hermes agent deployed on VM 101 (Proxmox/Debian)
  • 3 profiles: hermes-infrastructure, hermes-hb, hermes-transversal
  • hermes-api-hermes-infrastructure.service is masked as a leftover, but systemd still needs explicit Conflicts= to prevent accidental starts

Reported by the Sovepro infrastructure team. Currently mitigated with a drop-in on our production host.

extent analysis

TL;DR

To resolve the conflict between multiple Hermes profiles on the same host, add Conflicts= and Before= directives to the generated systemd unit files to prevent concurrent starts and ensure proper ordering.

Guidance

  • Modify the generate_systemd_unit() function in hermes_cli/gateway.py to include Conflicts= and Before= directives based on either the profile config approach or runtime detection approach.
  • Consider adding an optional systemd_conflicts list to profile.yaml or config.yaml to specify conflicting services.
  • When implementing the runtime detection approach, scan /etc/systemd/system/ and ~/.config/systemd/user/ for existing hermes-*-*.service files that share the same port or resource.
  • Ensure that both Conflicts= and Before= directives are used together to prevent conflicts and ensure proper ordering.

Example

# Example profile.yaml with systemd_conflicts
systemd_conflicts:
  - hermes-api-hermes-infrastructure.service
  - hermes-gateway-hermes-transversal.service

Notes

  • The proposed solution requires modifications to the generate_systemd_unit() function and potentially the profile configuration files.
  • The runtime detection approach may require additional error handling and logging to ensure robustness.

Recommendation

Apply the profile config approach by adding an optional systemd_conflicts list to profile.yaml or config.yaml, as it provides a more explicit and maintainable way to specify conflicting services.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Feature: Add systemd Conflicts= directive to multi-profile gateway unit generation [3 pull requests, 1 participants]