From d30a9bacaf61d2444b1a3e0f89ce0ce4d9b2de0c Mon Sep 17 00:00:00 2001 From: fajarhide Date: Sun, 14 Jun 2026 23:08:16 +0700 Subject: [PATCH 1/8] docs/tests: add Hermes + OMNI integration best practices --- README.md | 3 +- docs/HERMES_OMNI_INTEGRATION.md | 284 ++++++++++++++++++++++++++++++++ src/agents/hermes.rs | 24 +++ 3 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 docs/HERMES_OMNI_INTEGRATION.md diff --git a/README.md b/README.md index bb16969..aba36df 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,8 @@ enable_readfile_distillation = false **For Users:** - [The Ultimate Guide (HOW_TO_USE.md)](docs/HOW_TO_USE.md) — Everything you need: Installation, `omni learn`, Custom TOML Filters, and CLI Commands. - [OpenClaw Integration](https://clawhub.ai/fajarhide/omni-signal-engine) — Official OpenClaw plugin for native OMNI distillation. Install: `openclaw plugins install clawhub:@fajarhide/omni-signal-engine` -- [Hermes Agent Integration](https://github.com/wysie/hermes-omni-plugin) — Community Hermes Agent plugin for native OMNI distillation. Install: `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python git+https://github.com/wysie/hermes-omni-plugin.git` +- [Hermes Agent Integration Best Practices](docs/HERMES_OMNI_INTEGRATION.md) — Recommended setup, verification, tuning, and pitfalls for running OMNI inside Hermes Agent. +- [Hermes Agent Integration](https://github.com/wysie/hermes-omni-plugin) — Community Hermes Agent plugin for native OMNI distillation and rewind retrieval. Install: `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python git+https://github.com/wysie/hermes-omni-plugin.git` **For Developers & System Integrators:** - [Loop Engineering Guide (LOOP_ENGINEERING.md)](docs/LOOP_ENGINEERING.md) — How to integrate OMNI's context pressure with autonomous agent scripts (e.g., Maker-Checker pattern, shell loops). diff --git a/docs/HERMES_OMNI_INTEGRATION.md b/docs/HERMES_OMNI_INTEGRATION.md new file mode 100644 index 0000000..7ce7dfc --- /dev/null +++ b/docs/HERMES_OMNI_INTEGRATION.md @@ -0,0 +1,284 @@ +# OMNI + Hermes Agent — Integration Best Practices + +> Authoritative source for getting maximum value from OMNI while running +> Hermes Agent. +> Complements `omni init --hermes` by documenting the *why*, the recommended +> defaults, how to verify, and the edge cases that trip users up. + +--- + +## 1. What this integration actually does + +| Layer | Mechanism | What changes | +|---|---|---| +| **Hooks** | `~/.hermes/plugins/omni-signal-engine/__init__.py` → `omni --pre-hook`, `--post-hook`, `--session-start` | Terminal tool outputs are distilled *before* they enter Hermes context. | +| **MCP** | `mcp_servers.omni = ["/opt/homebrew/bin/omni", "--mcp"]` | 27 OMNI tools (`omni_insight`, `omni_retrieve`, `omni_history`, …) become first-class Hermes tools. | +| **Shared state** | SQLite store at `~/.omni/omni.db` | Cross-session knowledge, rewind breadcrumbs, token savings telemetry. | + +Hermes itself is not fork-modified; the bridge is purely via the plugin entry +point and MCP stdio transport. + +--- + +## 2. Prerequisites + +```bash +# 1. OMNI binary +brew install fajarhide/tap/omni + +# 2. Verify +omni --version # expect v0.6.0+ +omni doctor # all green + +# 3. Hermes venv python (needed for `hermes plugins enable`) +export HERMES_VENV="${HERMES_HOME:-$HOME/.hermes}/hermes-agent/venv" +export HERMES_PY="$HERMES_VENV/bin/python" +"$HERMES_PY" --version # expect 3.11+ +``` + +--- + +## 3. Installation (idempotent) + +```bash +# A. Generate Hermes plugin scaffolding +omni init --hermes + +# B. Enable the plugin +hermes plugins enable omni-signal-engine + +# C. Enable Hermes plugin entry point +"$HERMES_PY" -m pip install hermes-omni-plugin + +# D. Register MCP server (27 tools) +hermes mcp add omni \ + --command /opt/homebrew/bin/omni \ + --args --mcp \ + --env OMNI_AGENT_ID=hermes +# When prompted "Enable all 27 tools?" answer: Y +``` + +> The official README also lists a community install command: +> `pip install git+https://github.com/wysie/hermes-omni-plugin.git`. +> That package and the `omni init --hermes` scaffold are functionally +> equivalent; pick one path, not both, to avoid duplicate entry points. + +--- + +## 4. Recommended Hermes config additions + +```yaml +# ~/.hermes/config.yaml + +# 4a. Make sure the plugin is enabled +plugins: + enabled: + - omni-signal-engine + +# 4b. Register OMNI MCP server (paste hasil `omni init --hermes` / `hermes mcp add`) +mcp_servers: + omni: + command: "/opt/homebrew/bin/omni" + args: ["--mcp"] + env: + OMNI_AGENT_ID: "hermes" + +# 4c. Enable Hermes context compression so OMNI's pressure warnings line up +compression: + enabled: true + threshold: 0.50 # compress at 50 % context usage + target_ratio: 0.20 # keep 20 % of original +``` + +Minimal config checklist: +- ✅ `plugins.enabled` contains `omni-signal-engine` +- ✅ `mcp_servers.omni` points to the real `omni` binary +- ✅ `compression.enabled = true` + +--- + +## 5. Verification checklist + +```bash +# 1. OMNI happy path +omni doctor + +# 2. Plugin is loaded +hermes plugins list | grep omni +# expect: omni-signal-engine enabled + +# 3. Hermes sees 27 OMNI tools (after restart) +hermes tools list | grep mcp_omni_ + +# 4. Quick functional test +cat /Users/fajarhide/project/06_PERSONAL_WORKSPACE/token-efficient/omni/tests/fixtures/cargo_test_500.txt | \ + omni --post-hook 2>&1 | head -20 +# expect: passaing test lines stripped, failures preserved +``` + +For a full live test, start a fresh Hermes session and run a noisy command +through Hermes' `terminal` tool: + +```text +Run a command that produces > 5 000 tokens of output: + terminal("npm install", timeout=120) +Then inspect the session; tool result size should be materially smaller than +raw npm output. Confirm with: + omni stats +``` + +--- + +## 6. Best practices + +### 6a. Lean on OMNI for *high-noise* tool calls only + +| Typical noise | Typical signal | Action | +|---|---|---| +| `npm install`, `cargo build`, `docker build` | Dependency warnings, cache hits, progress bars | OMNI shines — expect 70–99 % savings | +| `cargo test` / `pytest` passing suite | 1 failing test buried in 10 000 lines | OMNI surfaces failure + stack, drops the rest | +| `kubectl get pods`, `terraform plan` | Healthy rows vs CrashLoopBackOff | OMNI keeps unhealthy rows, drops healthy | +| `cat src/` (file dumps) | Imports, API shapes, risk markers | OMNI reshape → outline; do *cat* through agent that calls `read_file` since OMNI only hooks terminal stdout | + +> OMNI is not a replacement for `read_file` / `grep`; it is a terminal-output +> conditioner. If you structured information through files, rely on Hermes' +> built-in file tools and let OMNI handle shell noise. + +### 6b. Use the MCP tools as Hermes' "OMNI controls" + +After MCP is registered, prompt Hermes to: + +- `omni_insight` — recurring errors across the session +- `omni_history` — what got compressed and why +- `omni_retrieve ` — pull the raw unfiltered output of a previous tool call +- `omni_session --status` — hot files, active errors, token pressure +- `omni_stop` analog: set env `OMNI_PASSTHROUGH=1` for one command when you need 100 % raw terminal output + +Treat those tools as observability into OMNI itself; they exist so you don't +have to wonder "did OMNI just drop something important?". + +### 6c. Tune by project, not globally + +```yaml +# ~/.omni/config.toml (Hermes projects tend to read large logs and test outputs) + +[global] +aggressiveness = "balanced" + +[agents.hermes] +aggressiveness = "aggressive" # Hermes sessions are long; lean into compression +enable_readfile_distillation = true +``` + +`aggressiveness` accepted values: `conservative`, `balanced`, `aggressive`. + +### 6d. Pin the files OMNI keeps warm + +```yaml +# optional: ~/.omni/config.toml +[pinned_files] +paths = [ + "AGENTS.md", + ".omni/CONTEXT.md", + "CLAUDE.md", +] +``` + +Pinned files are **not** dropped during aggressive session compaction in +Hermes. Use this for project rules the agent must never lose. + +### 6e. Watch context pressure, don't ignore it + +When OMNI emits `[omni:context pressure: WARNING]` or `[omni:context pressure: CRITICAL]`: + +1. Pause new tool calls that dump large outputs (`npm install`, full-file reads). +2. Use Hermes existing slash `omni_session --status` or ask the agent to call `omni_session "context"`. +3. Do not re-run the same heavy command hoping for different output; OMNI's + `active_errors` already tracks *why* the last iteration failed. + +Hermes' own `compression` feature kicks in at `threshold: 0.50`, but OMNI +reports pressure independently. Keeping both on gives early warnings + the +actual compaction, rather than a single tripwire. + +### 6f. Multi-agent workflows (Hermes + Claude Code or Cursor side-by-side) + +```bash +# Each agent sets OMNI_AGENT_ID on its own hook/MCP registration: +# Hermes: OMNI_AGENT_ID=hermes (via plugin + mcp env) +# Claude: OMNI_AGENT_ID=claude (via `omni init --claude`) +# Cursor: OMNI_AGENT_ID=cursor (via `omni init --cursor`) +``` + +Result: one `~/.omni/omni.db`, but session attribution + active-error lists +are per-agent, and `omni_agents` shows who else is active on the same +workspace. + +### 6g. Learning from Hermes usage + +```bash +# Feed Hermes terminal traces into OMNI's auto-learn +omni learn +``` + +Output is TOML filter proposals for `~/.omni/signals/`. Review and copy into +`~/.omni/signals/` rather than editing built-in signals. This is where +project-specific commands (internal CLIs, bespoke deploy scripts) get tuned. + +--- + +## 7. Common pitfalls and how to avoid them + +| Symptom | Probable cause | Fix | +|---|---|---| +| Hermes tools output is *longer* after enabling OMNI | `omni doctor --fix` to trust project `.omni/filters/`; otherwise project-level signals override global | Run `omni doctor --fix`, restart Hermes | +| `[OMNI: omitted X lines]` shows up but tool result is empty/short | Input was under the 95 % reduction guardrail; OMNI passthrough raw | Expected — harmless; do not double-filter | +| `omni_retrieve` returns empty | Raw output not in RewindStore (streaming distillers skip rewind by default) | Fall back to `omni_history` for the same command | +| Gateway restart doesn't load plugin | Plugin directory is `~/.hermes/plugins/…` *without* trailing slash; or ~/.hermes ownership mismatch | `hermes plugins enable omni-signal-engine` fixes directory; then `hermes gateway restart` | +| `hermes mcp add` times out | `connect_timeout` too short; switch to YAML config instead | See §4b | +| `OMNI_PASSTHROUGH=1` has no effect in Hermes terminal tool | Hermes cleans env before tool calls; inject via `terminal(env_passthrough=[OMNI_PASSTHROUGH], ...)` | Toggle per-call instead of global export | + +--- + +## 8. Performance budget + +Guaranteed by OMNI's Rust release profile on modern Mac/Linux: + +- Pipeline per-tool latency: **< 100 ms** (including binary startup) +- Streaming pipeline: **memory flat** even for 10 000-line logs +- Fail-open: any hook error is swallowed; raw terminal output passes through + +Do not add heavy I/O *inside* Hermes' `post_tool_call` plugin handler +— `subprocess.run(..., capture_output=True)` is already the cheapest possible +boundary. Avoid reading files, network calls, or LLM calls in the plugin. + +--- + +## 9. Ops checklist after Hermes updates + +1. `omni doctor` — confirm hooks still registered +2. `hermes plugins list` — `omni-signal-engine` still enabled +3. `omni stats` — savings telemetry is still recording +4. If a Hermes release changes hook lifecycles, re-run `omni init --hermes --hook` + to rewrite the plugin scaffold without touching MCP or config. + +--- + +## 10. Decision tree + +```text +Q: Should I keep OMNI on? +├─ Output is pure noise (npm install, cargo build) → ALWAYS ON +├─ Output contains secrets / tokens (env, kubeconfig) → ALWAYS ON + OMNI_PASSTHROUGH=0 +├─ Output is sequential logs I read top-to-bottom → ALWAYS ON +├─ Output is JSON / YAML I want intact → OMNI may downsample; set env per-call +└─ Output is small (< 2 KB) → OMNI bypasses automatically (no overhead) +``` + +--- + +## 11. References + +- OMNI docs: `docs/HOW_TO_USE.md`, `docs/LOOP_ENGINEERING.md` +- Hermes skill: `skill_view(name="hermes-agent")` +- Hermes native MCP: `skill_view(name="hermes-agent", file_path="references/native-mcp.md")` +- OMNI source: `src/hooks/pipe.rs`, `src/agents/hermes.rs` diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index 059f6e2..662d98a 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -298,4 +298,28 @@ plugins: assert!(!super::is_hermes_agent("claude")); assert!(!super::is_hermes_agent("cursor")); } + + #[test] + fn hermes_default_config_enables_efficient_mode_and_pins_files() { + let config = super::hermes_default_config(); + assert_eq!( + config.mode, + Some(crate::guard::config::DistillationMode::Efficient) + ); + assert_eq!(config.enable_readfile_distillation, Some(true)); + assert_eq!(config.enable_grep_distillation, Some(true)); + assert_eq!(config.enable_webfetch_distillation, Some(true)); + let pinned = config.pinned_files.unwrap_or_default(); + assert!(pinned.contains(&"AGENTS.md".to_string())); + assert!(pinned.contains(&".omni/CONTEXT.md".to_string())); + } + + #[test] + fn hermes_command_patterns_includes_common_hermes_tools() { + let pats = super::hermes_command_patterns(); + assert!(pats.contains(&"terminal")); + assert!(pats.contains(&"python")); + assert!(pats.contains(&"npm")); + assert!(pats.contains(&"hermes")); + } } From b97b35b235301e5ce4dd45d6024f2a75af9a45d7 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Sun, 14 Jun 2026 23:26:44 +0700 Subject: [PATCH 2/8] feat(hermes): auto-write Hermes/OMNI defaults and enable compression --- docs/HERMES_OMNI_INTEGRATION.md | 30 ++--- src/agents/hermes.rs | 208 ++++++++++++++++++++++++++++---- 2 files changed, 200 insertions(+), 38 deletions(-) diff --git a/docs/HERMES_OMNI_INTEGRATION.md b/docs/HERMES_OMNI_INTEGRATION.md index 7ce7dfc..b9b15a2 100644 --- a/docs/HERMES_OMNI_INTEGRATION.md +++ b/docs/HERMES_OMNI_INTEGRATION.md @@ -40,28 +40,30 @@ export HERMES_PY="$HERMES_VENV/bin/python" ## 3. Installation (idempotent) +`omni init --hermes` now performs a fully automated setup. It will: +- install the `omni-signal-engine` plugin scaffold in `~/.hermes/plugins/omni-signal-engine/`, +- register the OMNI MCP server in `~/.hermes/config.yaml` if it is not already present and enable Hermes `compression` when safe to do so, +- write Hermes-optimized OMNI defaults to `~/.omni/config.toml` (`mode = "efficient"`, pinned files, etc.) **without overwriting an existing OMNI config**. + ```bash -# A. Generate Hermes plugin scaffolding +# Single command setup omni init --hermes -# B. Enable the plugin +# Enable the plugin (required once) hermes plugins enable omni-signal-engine -# C. Enable Hermes plugin entry point -"$HERMES_PY" -m pip install hermes-omni-plugin +# Restart Hermes to load the plugin and MCP server +hermes gateway restart +``` -# D. Register MCP server (27 tools) -hermes mcp add omni \ - --command /opt/homebrew/bin/omni \ - --args --mcp \ - --env OMNI_AGENT_ID=hermes -# When prompted "Enable all 27 tools?" answer: Y +Finally, install the Hermes entry point into the Hermes venv so the plugin resolves: + +```bash +"$HERMES_PY" -m pip install hermes-omni-plugin ``` -> The official README also lists a community install command: -> `pip install git+https://github.com/wysie/hermes-omni-plugin.git`. -> That package and the `omni init --hermes` scaffold are functionally -> equivalent; pick one path, not both, to avoid duplicate entry points. +> Use either `hermes-omni-plugin` or the `omni init --hermes` scaffold, not both at once, +> to avoid duplicate plugin registrations. --- diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index 662d98a..4b297dc 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -5,11 +5,20 @@ use std::path::{Path, PathBuf}; pub struct HermesIntegration; -/// Returns the Hermes plugin directory. fn plugin_dir() -> PathBuf { hermes_home_dir().join("plugins").join("omni-signal-engine") } +fn omni_home_dir() -> PathBuf { + dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".omni") +} + +fn omni_config_path() -> PathBuf { + omni_home_dir().join("config.toml") +} + fn hermes_home_dir() -> PathBuf { dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) @@ -49,6 +58,18 @@ fn configured_omni_mcp(config_path: &Path) -> bool { .unwrap_or(false) } +fn configured_compression(config: &str) -> bool { + let has_compression = config.contains("compression:"); + let has_enabled = config.contains("enabled: true") || config.contains("enabled:true"); + has_compression && has_enabled +} + +fn configured_compression_in_config(config_path: &Path) -> bool { + fs::read_to_string(config_path) + .map(|config| configured_compression(&config)) + .unwrap_or(false) +} + impl AgentIntegration for HermesIntegration { fn id(&self) -> &'static str { "hermes" @@ -59,18 +80,19 @@ impl AgentIntegration for HermesIntegration { } fn install(&self, exe_path: &str) -> anyhow::Result<()> { + let mut actions = Vec::new(); + let mut warnings = Vec::new(); + let dest = plugin_dir(); fs::create_dir_all(&dest)?; - println!(" {} Generating Hermes plugin files...", "↓".cyan()); - let plugin_yaml_content = r#"name: omni-signal-engine version: "1.0" description: OMNI Signal Engine integration for Hermes Agent hooks "#; let init_py_content = format!( - r#"\"\"\"OMNI integration for Hermes Agent\"\"\" + r#""""""OMNI integration for Hermes Agent"""""" import subprocess import os @@ -108,28 +130,166 @@ def register(ctx): fs::write(dest.join("plugin.yaml"), plugin_yaml_content)?; fs::write(dest.join("__init__.py"), init_py_content)?; - - println!( - " {} Installed Hermes plugin to ~/.hermes/plugins/omni-signal-engine/", + actions.push(format!( + "{} Installed Hermes plugin to ~/.hermes/plugins/omni-signal-engine/", "✓".green() - ); + )); - println!( - " {} Run {} to enable the plugin", - "→".cyan(), - "hermes plugins enable omni-signal-engine".bright_black() - ); + let config_path = hermes_config_path(); + let requires_manual_plugin_step = !fs::metadata(&config_path) + .ok() + .map(|meta| meta.is_file()) + .unwrap_or(false); + + if requires_manual_plugin_step { + actions.push(format!( + "{} Run {} to enable the OMNI plugin", + "→".cyan(), + "hermes plugins enable omni-signal-engine".bright_black() + )); + warnings.push( + "Hermes config not found; enable the OMNI plugin once Hermes is initialized.".to_string(), + ); + } - println!( - "\n {} To add the OMNI MCP Server, append this to your ~/.hermes/config.yaml:", - "ℹ".blue() - ); - println!("{}", " mcp_servers:".bright_black()); - println!("{}", " omni:".bright_black()); - println!(" command: \"{}\"", exe_path.bright_black()); - println!("{}", " args: [\"--mcp\"]".bright_black()); - println!("{}", " env:".bright_black()); - println!("{}", " OMNI_AGENT_ID: \"hermes\"".bright_black()); + if let Ok(config) = fs::read_to_string(&config_path) { + if configured_omni_mcp(&config_path) { + actions.push( + format!( + "{} OMNI MCP server is already registered in ~/.hermes/config.yaml", + "✓".green() + ) + .to_string(), + ); + } else { + let mcp_block = "\nmcp_servers:\n omni:\n command: \"{}\"\n args: [\"--mcp\"]\n env:\n OMNI_AGENT_ID: \"hermes\"\n\n"; + let mcp_block = format!("{}", mcp_block.replace("{}", exe_path)); + + let mut updated = String::new(); + let mut inserted = false; + for line in config.lines() { + updated.push_str(line); + updated.push('\n'); + if !inserted && line.trim_start().starts_with("plugins:") { + updated.push_str(&mcp_block); + inserted = true; + actions.push( + format!( + "{} Registered OMNI MCP server inside ~/.hermes/config.yaml", + "✓".green() + ) + .to_string(), + ); + } + } + + if !inserted { + updated.push_str(&mcp_block); + actions.push( + format!( + "{} Registered OMNI MCP server at the end of ~/.hermes/config.yaml", + "✓".green() + ) + .to_string(), + ); + } + + fs::write(&config_path, updated)?; + + if configured_compression_in_config(&config_path) { + actions.push( + format!( + "{} Hermes compression is already enabled in ~/.hermes/config.yaml", + "✓".green() + ) + .to_string(), + ); + } else if !requires_manual_plugin_step { + if let Ok(current) = fs::read_to_string(&config_path) { + let compression_block = "\ncompression:\n enabled: true\n threshold: 0.50\n target_ratio: 0.20\n\n"; + + let mut updated = current; + if !updated.contains("compression:") { + updated.push_str(&compression_block); + actions.push( + format!( + "{} Enabled Hermes compression in ~/.hermes/config.yaml", + "✓".bright_green() + ) + .to_string(), + ); + fs::write(&config_path, updated)?; + } + } + } + } + } else { + warnings + .push("Could not read ~/.hermes/config.yaml to register the OMNI MCP server.".to_string()); + } + + let omni_config_path = omni_config_path(); + fs::create_dir_all(omni_home_dir())?; + let default_config = crate::agents::hermes::hermes_default_config(); + let file_already_exists = fs::metadata(&omni_config_path) + .ok() + .map(|meta| meta.is_file()) + .unwrap_or(false); + + if !file_already_exists { + let mut config_lines = Vec::new(); + config_lines.push("[global]".to_string()); + config_lines + .push(format!("mode = \"{}\"", format!("{:?}", default_config.mode.unwrap_or_default()).to_lowercase())); + if let Some(readfile) = default_config.enable_readfile_distillation { + config_lines.push(format!("enable_readfile_distillation = {}", readfile)); + } + if let Some(grep) = default_config.enable_grep_distillation { + config_lines.push(format!("enable_grep_distillation = {}", grep)); + } + if let Some(webfetch) = default_config.enable_webfetch_distillation { + config_lines.push(format!("enable_webfetch_distillation = {}", webfetch)); + } + if let Some(pinned) = &default_config.pinned_files { + if !pinned.is_empty() { + config_lines.push("pinned_files = [".to_string()); + for path in pinned { + config_lines.push(format!(" \"{}\",", path)); + } + config_lines.push("]".to_string()); + } + } + + fs::write(&omni_config_path, format!("{}\n", config_lines.join("\n")))?; + actions.push( + format!( + "{} Wrote Hermes OMNI defaults to {}", + "✓".green(), + omni_config_path.display().to_string().bright_black() + ) + .to_string(), + ); + } else { + actions.push( + format!( + "{} Existing OMNI config preserved at {}", + "✓".green(), + omni_config_path.display().to_string().bright_black() + ) + .to_string(), + ); + } + + for message in &actions { + println!(" {}", message); + } + + if !warnings.is_empty() { + println!("\n {}", "Warnings:".yellow()); + for warning in &warnings { + println!(" - {}", warning); + } + } Ok(()) } @@ -236,7 +396,7 @@ mod tests { #[test] fn detects_packaged_hermes_omni_plugin_in_config() { - let config = r#" + let config = r#"" plugins: enabled: - disk-cleanup From 9e1b4c860a7e4be89eedc4e09005d398c5863b91 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Sun, 14 Jun 2026 23:57:39 +0700 Subject: [PATCH 3/8] refactor: improve Hermes configuration logic and add tool output filtering via new signal definitions --- signals/tools/hermes.toml | 28 +++++++++++++++ src/agents/hermes.rs | 75 ++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 signals/tools/hermes.toml diff --git a/signals/tools/hermes.toml b/signals/tools/hermes.toml new file mode 100644 index 0000000..2a451ed --- /dev/null +++ b/signals/tools/hermes.toml @@ -0,0 +1,28 @@ +[schema] +id = "hermes-gateway" +name = "Hermes Agent Gateway" +description = "Filters out Hermes wrapper noise, headers, and trailers around tool executions." + +[[rules]] +id = "hermes_tool_header" +type = "regex" +pattern = "^\\[Hermes\\] (Executing tool|Running command|Tool execution).*$" +action = "drop" + +[[rules]] +id = "hermes_gateway_noise" +type = "regex" +pattern = "^\\[Hermes Gateway\\] (Forwarding response|Processing|Handling request).*$" +action = "drop" + +[[rules]] +id = "hermes_tool_trailer" +type = "regex" +pattern = "^\\[Hermes\\] (Tool execution completed|Finished|Success).*$" +action = "drop" + +[[rules]] +id = "hermes_divider" +type = "regex" +pattern = "^-{10,}.*$" +action = "drop" diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index 4b297dc..f2b0984 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -148,7 +148,8 @@ def register(ctx): "hermes plugins enable omni-signal-engine".bright_black() )); warnings.push( - "Hermes config not found; enable the OMNI plugin once Hermes is initialized.".to_string(), + "Hermes config not found; enable the OMNI plugin once Hermes is initialized." + .to_string(), ); } @@ -163,7 +164,7 @@ def register(ctx): ); } else { let mcp_block = "\nmcp_servers:\n omni:\n command: \"{}\"\n args: [\"--mcp\"]\n env:\n OMNI_AGENT_ID: \"hermes\"\n\n"; - let mcp_block = format!("{}", mcp_block.replace("{}", exe_path)); + let mcp_block = mcp_block.replace("{}", exe_path); let mut updated = String::new(); let mut inserted = false; @@ -205,12 +206,13 @@ def register(ctx): .to_string(), ); } else if !requires_manual_plugin_step { + #[allow(clippy::collapsible_if)] if let Ok(current) = fs::read_to_string(&config_path) { let compression_block = "\ncompression:\n enabled: true\n threshold: 0.50\n target_ratio: 0.20\n\n"; let mut updated = current; if !updated.contains("compression:") { - updated.push_str(&compression_block); + updated.push_str(compression_block); actions.push( format!( "{} Enabled Hermes compression in ~/.hermes/config.yaml", @@ -224,43 +226,50 @@ def register(ctx): } } } else { - warnings - .push("Could not read ~/.hermes/config.yaml to register the OMNI MCP server.".to_string()); + warnings.push( + "Could not read ~/.hermes/config.yaml to register the OMNI MCP server.".to_string(), + ); } let omni_config_path = omni_config_path(); fs::create_dir_all(omni_home_dir())?; let default_config = crate::agents::hermes::hermes_default_config(); - let file_already_exists = fs::metadata(&omni_config_path) - .ok() - .map(|meta| meta.is_file()) - .unwrap_or(false); - if !file_already_exists { - let mut config_lines = Vec::new(); - config_lines.push("[global]".to_string()); - config_lines - .push(format!("mode = \"{}\"", format!("{:?}", default_config.mode.unwrap_or_default()).to_lowercase())); - if let Some(readfile) = default_config.enable_readfile_distillation { - config_lines.push(format!("enable_readfile_distillation = {}", readfile)); - } - if let Some(grep) = default_config.enable_grep_distillation { - config_lines.push(format!("enable_grep_distillation = {}", grep)); - } - if let Some(webfetch) = default_config.enable_webfetch_distillation { - config_lines.push(format!("enable_webfetch_distillation = {}", webfetch)); - } - if let Some(pinned) = &default_config.pinned_files { - if !pinned.is_empty() { - config_lines.push("pinned_files = [".to_string()); - for path in pinned { - config_lines.push(format!(" \"{}\",", path)); - } - config_lines.push("]".to_string()); - } + let mut config_lines = Vec::new(); + config_lines.push("\n[agents.hermes]".to_string()); + config_lines.push(format!( + "mode = \"{}\"", + format!("{:?}", default_config.mode.unwrap_or_default()).to_lowercase() + )); + if let Some(readfile) = default_config.enable_readfile_distillation { + config_lines.push(format!("enable_readfile_distillation = {}", readfile)); + } + if let Some(grep) = default_config.enable_grep_distillation { + config_lines.push(format!("enable_grep_distillation = {}", grep)); + } + if let Some(webfetch) = default_config.enable_webfetch_distillation { + config_lines.push(format!("enable_webfetch_distillation = {}", webfetch)); + } + if let Some(pinned) = &default_config + .pinned_files + .as_ref() + .filter(|p| !p.is_empty()) + { + config_lines.push("pinned_files = [".to_string()); + for path in *pinned { + config_lines.push(format!(" \"{}\",", path)); } + config_lines.push("]".to_string()); + } - fs::write(&omni_config_path, format!("{}\n", config_lines.join("\n")))?; + let existing = fs::read_to_string(&omni_config_path).unwrap_or_default(); + if !existing.contains("[agents.hermes]") { + let mut updated = existing; + if !updated.is_empty() && !updated.ends_with('\n') { + updated.push('\n'); + } + updated.push_str(&format!("{}\n", config_lines.join("\n"))); + fs::write(&omni_config_path, updated)?; actions.push( format!( "{} Wrote Hermes OMNI defaults to {}", @@ -272,7 +281,7 @@ def register(ctx): } else { actions.push( format!( - "{} Existing OMNI config preserved at {}", + "{} Hermes OMNI config already exists at {}", "✓".green(), omni_config_path.display().to_string().bright_black() ) From 19cf4e76865394616e28f4b1e4164b210b408f31 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Mon, 15 Jun 2026 00:04:28 +0700 Subject: [PATCH 4/8] feat: implement hermes startup validation and integrate into session start hooks --- src/agents/hermes.rs | 32 ++++++++++++++++++++++++++++++++ src/hooks/session_start.rs | 15 ++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index f2b0984..1dcc409 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -19,6 +19,38 @@ fn omni_config_path() -> PathBuf { omni_home_dir().join("config.toml") } +pub fn validate_startup() -> Option { + let mut warnings = Vec::new(); + + let config_path = hermes_home_dir().join("config.yaml"); + if let Ok(config_str) = fs::read_to_string(&config_path) { + if !config_str.contains("mcp_servers:") || !config_str.contains("omni:") { + warnings.push("OMNI MCP server is not registered in ~/.hermes/config.yaml. Tools will be missing. Run `omni init --hermes` to fix."); + } + if !config_str.contains("compression:") || !config_str.contains("enabled: true") { + warnings.push("Hermes compression is not enabled. Context Pressure warnings will be ignored. Run `omni init --hermes` to fix."); + } + } else { + warnings.push("Could not find ~/.hermes/config.yaml. Is Hermes installed?"); + } + + let plugin_file = plugin_dir().join("plugin.py"); + if !plugin_file.exists() { + warnings.push( + "OMNI Hermes plugin file (`plugin.py`) is missing. Pre/Post hooks will not execute.", + ); + } + + if warnings.is_empty() { + None + } else { + Some(format!( + "\n [OMNI Hermes Integration Validation Failed]\n- {}\n", + warnings.join("\n- ") + )) + } +} + fn hermes_home_dir() -> PathBuf { dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) diff --git a/src/hooks/session_start.rs b/src/hooks/session_start.rs index b9cc4d5..8e22563 100644 --- a/src/hooks/session_start.rs +++ b/src/hooks/session_start.rs @@ -178,12 +178,21 @@ pub fn process_payload(input_str: &str, store: Arc, cfg: SessionConfig) - return serde_json::to_string(&out).ok(); } - // Fresh session: only return output if we have watchPaths to register - if !watch_paths.is_empty() { + let mut system_prompt_addition = String::new(); + let agent_id = multiagent::detect_agent_id(); + #[allow(clippy::collapsible_if)] + if agent_id == "hermes" { + if let Some(err_msg) = crate::agents::hermes::validate_startup() { + system_prompt_addition.push_str(&err_msg); + } + } + + // Fresh session: return output if we have watchPaths or a system prompt to register + if !watch_paths.is_empty() || !system_prompt_addition.is_empty() { let out = HookOutput { hook_specific_output: HookSpecificOutput { hook_event_name: "SessionStart".to_string(), - system_prompt_addition: String::new(), + system_prompt_addition, watch_paths, }, }; From b5f2aa6ba3d1c303ad036824cf15ef3f2294c029 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Mon, 15 Jun 2026 10:36:23 +0700 Subject: [PATCH 5/8] feat: enhance Hermes integration with comprehensive startup diagnostics and optimized plugin hooks --- README.md | 4 +- src/agents/hermes.rs | 261 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 224 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index aba36df..0b12c8b 100644 --- a/README.md +++ b/README.md @@ -261,8 +261,8 @@ enable_readfile_distillation = false **For Users:** - [The Ultimate Guide (HOW_TO_USE.md)](docs/HOW_TO_USE.md) — Everything you need: Installation, `omni learn`, Custom TOML Filters, and CLI Commands. - [OpenClaw Integration](https://clawhub.ai/fajarhide/omni-signal-engine) — Official OpenClaw plugin for native OMNI distillation. Install: `openclaw plugins install clawhub:@fajarhide/omni-signal-engine` -- [Hermes Agent Integration Best Practices](docs/HERMES_OMNI_INTEGRATION.md) — Recommended setup, verification, tuning, and pitfalls for running OMNI inside Hermes Agent. -- [Hermes Agent Integration](https://github.com/wysie/hermes-omni-plugin) — Community Hermes Agent plugin for native OMNI distillation and rewind retrieval. Install: `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python git+https://github.com/wysie/hermes-omni-plugin.git` +- [Official Hermes Agent Integration](docs/HERMES_OMNI_INTEGRATION.md) — Recommended setup, verification, tuning, and pitfalls for running OMNI inside Hermes Agent. +- [Community Hermes Agent Integration](https://github.com/wysie/hermes-omni-plugin) — Community Hermes Agent plugin for native OMNI distillation and rewind retrieval. Install: `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python git+https://github.com/wysie/hermes-omni-plugin.git` **For Developers & System Integrators:** - [Loop Engineering Guide (LOOP_ENGINEERING.md)](docs/LOOP_ENGINEERING.md) — How to integrate OMNI's context pressure with autonomous agent scripts (e.g., Maker-Checker pattern, shell loops). diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index 1dcc409..e04e841 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -19,25 +19,72 @@ fn omni_config_path() -> PathBuf { omni_home_dir().join("config.toml") } +/// Comprehensive startup validation for Hermes integration. +/// +/// Checks: config.yaml (MCP + compression), plugin files, OMNI binary +/// availability, and OMNI config presence. Returns `None` when all +/// checks pass, or a formatted diagnostics string that gets injected +/// into the Hermes `systemPromptAddition` so the agent can self-heal. pub fn validate_startup() -> Option { - let mut warnings = Vec::new(); + let mut warnings: Vec<&str> = Vec::new(); + // ── 1. Hermes config.yaml ── let config_path = hermes_home_dir().join("config.yaml"); if let Ok(config_str) = fs::read_to_string(&config_path) { + // MCP server registration if !config_str.contains("mcp_servers:") || !config_str.contains("omni:") { - warnings.push("OMNI MCP server is not registered in ~/.hermes/config.yaml. Tools will be missing. Run `omni init --hermes` to fix."); + warnings.push( + "OMNI MCP server is NOT registered in ~/.hermes/config.yaml. \ + 27 MCP tools (omni_retrieve, omni_loop_memory, omni_knowledge, …) \ + will be unavailable. Run `omni init --hermes` to fix.", + ); } + // Compression bridge if !config_str.contains("compression:") || !config_str.contains("enabled: true") { - warnings.push("Hermes compression is not enabled. Context Pressure warnings will be ignored. Run `omni init --hermes` to fix."); + warnings.push( + "Hermes compression is NOT enabled. Context Pressure warnings \ + from OMNI will be surfaced but Hermes will not act on them. \ + Run `omni init --hermes` to fix.", + ); } } else { warnings.push("Could not find ~/.hermes/config.yaml. Is Hermes installed?"); } - let plugin_file = plugin_dir().join("plugin.py"); - if !plugin_file.exists() { + // ── 2. Plugin scaffold ── + let plugin_init = plugin_dir().join("__init__.py"); + if !plugin_init.exists() { warnings.push( - "OMNI Hermes plugin file (`plugin.py`) is missing. Pre/Post hooks will not execute.", + "OMNI Hermes plugin (`__init__.py`) is missing. \ + Pre/Post hooks will not execute. Run `omni init --hermes` to install.", + ); + } + + // ── 3. OMNI binary reachable ── + #[allow(clippy::collapsible_if)] + if let Ok(exe) = std::env::current_exe() { + if !exe.exists() { + warnings.push("OMNI binary path does not exist on disk. Hooks will fail at runtime."); + } + } + + // ── 4. OMNI config for Hermes ── + let omni_cfg = omni_config_path(); + if omni_cfg.exists() { + #[allow(clippy::collapsible_if)] + if let Ok(content) = fs::read_to_string(&omni_cfg) { + if !content.contains("[agents.hermes]") { + warnings.push( + "~/.omni/config.toml exists but has no [agents.hermes] section. \ + Hermes-optimized defaults (Efficient mode, pinned files) are inactive. \ + Run `omni init --hermes` to add them.", + ); + } + } + } else { + warnings.push( + "~/.omni/config.toml does not exist. OMNI is using built-in defaults \ + instead of Hermes-optimized settings. Run `omni init --hermes`.", ); } @@ -45,8 +92,15 @@ pub fn validate_startup() -> Option { None } else { Some(format!( - "\n [OMNI Hermes Integration Validation Failed]\n- {}\n", - warnings.join("\n- ") + "\n [OMNI × Hermes Startup Validation — {} issue(s)]\n{}\n\ + → Fix all: `omni init --hermes && hermes gateway restart`\n", + warnings.len(), + warnings + .iter() + .enumerate() + .map(|(i, w)| format!(" {}. {}", i + 1, w)) + .collect::>() + .join("\n") )) } } @@ -124,40 +178,69 @@ description: OMNI Signal Engine integration for Hermes Agent hooks "#; let init_py_content = format!( - r#""""""OMNI integration for Hermes Agent"""""" -import subprocess + r#""""""OMNI Context OS integration for Hermes Agent. + +This plugin wires three lifecycle hooks so OMNI can distill +terminal output, track sessions, and manage context pressure. + +It also exposes helper utilities that make the 27 OMNI MCP tools +(loop_memory, knowledge, retrieve, learn) work seamlessly during +Hermes autonomous multi-step loops. +"""""" +import json import os +import subprocess +import time + +_OMNI_BIN = "{}" +_STEP_COUNTER = 0 +_SESSION_START_TS = None + + +def _omni_env(): + """Return a copy of the environment with OMNI_AGENT_ID set.""" + env = os.environ.copy() + env["OMNI_AGENT_ID"] = "hermes" + # Forward loop control vars when present + for var in ("OMNI_LOOP_ID", "OMNI_LOOP_GOAL", "OMNI_LOOP_BUDGET"): + if var in os.environ: + env[var] = os.environ[var] + return env + + +def _run_omni(*args): + """Run the OMNI binary with fail-open semantics.""" + try: + return subprocess.run( + [_OMNI_BIN] + list(args), + env=_omni_env(), + capture_output=True, + timeout=5, + ) + except Exception: + return None # fail-open: never block Hermes + def register(ctx): + global _SESSION_START_TS + _SESSION_START_TS = time.time() + def on_post_tool_call(tool_name, params, result): - env = os.environ.copy() - env["OMNI_AGENT_ID"] = "hermes" - try: - subprocess.run(["{}", "--post-hook"], env=env, capture_output=True) - except Exception: - pass + global _STEP_COUNTER + _STEP_COUNTER += 1 + _run_omni("--post-hook") def on_pre_tool_call(tool_name, params): - env = os.environ.copy() - env["OMNI_AGENT_ID"] = "hermes" - try: - subprocess.run(["{}", "--pre-hook"], env=env, capture_output=True) - except Exception: - pass + _run_omni("--pre-hook") def on_session_start(): - env = os.environ.copy() - env["OMNI_AGENT_ID"] = "hermes" - try: - subprocess.run(["{}", "--session-start"], env=env, capture_output=True) - except Exception: - pass + _run_omni("--session-start") ctx.register_hook("post_tool_call", on_post_tool_call) ctx.register_hook("pre_tool_call", on_pre_tool_call) ctx.register_hook("on_session_start", on_session_start) "#, - exe_path, exe_path, exe_path + exe_path ); fs::write(dest.join("plugin.yaml"), plugin_yaml_content)?; @@ -347,50 +430,127 @@ def register(ctx): Ok(()) } - fn doctor_check(&self, _fix_mode: bool, _warnings: &mut Vec) -> bool { + fn doctor_check(&self, fix_mode: bool, warnings: &mut Vec) -> bool { let dest = plugin_dir(); let config_path = hermes_config_path(); let directory_plugin_installed = dest.join("plugin.yaml").exists(); let configured_plugin = configured_omni_plugin(&config_path); let mcp_configured = configured_omni_mcp(&config_path); + let compression_on = configured_compression_in_config(&config_path); + let omni_cfg = omni_config_path(); + let has_hermes_section = fs::read_to_string(&omni_cfg) + .map(|c| c.contains("[agents.hermes]")) + .unwrap_or(false); let installed = directory_plugin_installed || configured_plugin.is_some(); println!("\n {}", "Hermes Agent:".cyan()); + + // Plugin status if directory_plugin_installed { println!( - " {:<15} {} {}", + " {:>15} {} {}", "Plugin:".bright_black(), "~/.hermes/plugins/omni-signal-engine/".bright_black(), "[OK]".green().bold() ); } else if let Some(plugin_name) = configured_plugin { println!( - " {:<15} {} {}", + " {:>15} {} {}", "Plugin:".bright_black(), format!("{} in ~/.hermes/config.yaml", plugin_name).bright_black(), "[OK]".green().bold() ); } else { println!( - " {:<15} {}", + " {:>15} {}", "Plugin:".bright_black(), - "not installed".bright_black() + "not installed [MISSING]".red().bold() ); + warnings.push("Hermes OMNI plugin is not installed.".to_string()); } + // MCP status println!( - " {:<15} {}", + " {:>15} {}", "MCP Server:".bright_black(), if mcp_configured { - "configured in ~/.hermes/config.yaml [OK]".green().bold() + "registered [OK]".green().bold() } else { - "not configured".bright_black() + "not registered [MISSING]".red().bold() } ); + if !mcp_configured { + warnings.push("OMNI MCP server is not registered in Hermes config.".to_string()); + } + + // Compression status + println!( + " {:>15} {}", + "Compression:".bright_black(), + if compression_on { + "enabled [OK]".green().bold() + } else { + "disabled [WARN]".yellow().bold() + } + ); + if !compression_on { + warnings.push( + "Hermes compression is disabled; context pressure warnings will not trigger compaction." + .to_string(), + ); + } + + // OMNI config section + println!( + " {:>15} {}", + "OMNI Config:".bright_black(), + if has_hermes_section { + "[agents.hermes] present [OK]".green().bold() + } else { + "[agents.hermes] missing [WARN]".yellow().bold() + } + ); + if !has_hermes_section { + warnings.push( + "~/.omni/config.toml has no [agents.hermes] section; using built-in defaults." + .to_string(), + ); + } + + // Auto-fix: re-run the full init to repair all gaps + #[allow(clippy::collapsible_if)] + if fix_mode && (!installed || !mcp_configured || !compression_on || !has_hermes_section) { + if let Ok(exe) = std::env::current_exe() { + let exe_str = exe.to_string_lossy().to_string(); + println!( + " {:>15} {}", + "Auto-fix:".bright_black(), + "Re-running omni init --hermes...".yellow() + ); + match self.install(&exe_str) { + Ok(()) => { + println!( + " {:>15} {}", + "".bright_black(), + "\u{2713} Auto-fix applied. Restart Hermes to activate." + .green() + .bold() + ); + } + Err(e) => { + println!( + " {:>15} {}", + "".bright_black(), + format!("\u{2717} Auto-fix failed: {}", e).red().bold() + ); + } + } + } + } if installed && !mcp_configured { println!( - " {:<15} {}", + " {:>15} {}", "Note:".bright_black(), "MCP is optional; native Hermes plugin detection passed.".bright_black() ); @@ -433,7 +593,7 @@ pub fn is_hermes_agent(agent_id: &str) -> bool { #[cfg(test)] mod tests { - use super::{config_mentions_omni_mcp, config_mentions_omni_plugin}; + use super::{config_mentions_omni_mcp, config_mentions_omni_plugin, configured_compression}; #[test] fn detects_packaged_hermes_omni_plugin_in_config() { @@ -523,4 +683,27 @@ plugins: assert!(pats.contains(&"npm")); assert!(pats.contains(&"hermes")); } + + #[test] + fn detects_compression_enabled() { + let config = "compression:\n enabled: true\n threshold: 0.50"; + assert!(configured_compression(config)); + } + + #[test] + fn detects_compression_disabled() { + let config = "plugins:\n enabled:\n - foo"; + assert!(!configured_compression(config)); + } + + #[test] + fn validate_startup_returns_some_when_hermes_not_installed() { + // On CI / dev machines without Hermes, validation should surface warnings + let result = super::validate_startup(); + // We can't assert None because it depends on the host environment, + // but we CAN assert the function doesn't panic and returns a coherent type. + if let Some(msg) = &result { + assert!(msg.contains("OMNI × Hermes") || msg.contains("Startup Validation")); + } + } } From 23106e1da3a390d93f9695e0b61668ba446eb2fd Mon Sep 17 00:00:00 2001 From: fajarhide Date: Mon, 15 Jun 2026 10:50:55 +0700 Subject: [PATCH 6/8] refactor: replace regex-based filtering in hermes.toml with a structured schema including transformation rules and unit tests --- signals/tools/hermes.toml | 78 +++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/signals/tools/hermes.toml b/signals/tools/hermes.toml index 2a451ed..a47f8fe 100644 --- a/signals/tools/hermes.toml +++ b/signals/tools/hermes.toml @@ -1,28 +1,58 @@ -[schema] -id = "hermes-gateway" -name = "Hermes Agent Gateway" -description = "Filters out Hermes wrapper noise, headers, and trailers around tool executions." +schema_version = 1 -[[rules]] -id = "hermes_tool_header" -type = "regex" -pattern = "^\\[Hermes\\] (Executing tool|Running command|Tool execution).*$" -action = "drop" +[filters.hermes] +description = "Strip Hermes-specific wrapper noise: tool-call headers/trailers, multi-tool separators, and gateway boilerplate" +match_command = "^(terminal|read_file|web_search|browser_navigate|browser_screenshot|execute_code|delegate_task).*" +strip_ansi = true +transform_rules = [ + { pattern = "(?m)^\\s*term:\\s*(.*)$", replacement = "$1" }, + { pattern = "(?m)^>\\s*(.*)$", replacement = "$1" } +] +strip_lines_matching = [ + "^(Hermes Agent|Hermes Gateway|Hermes CLI) .*$", + "^\\-{10,}$", + "^Tool Result .*$" +] +priority = 150 -[[rules]] -id = "hermes_gateway_noise" -type = "regex" -pattern = "^\\[Hermes Gateway\\] (Forwarding response|Processing|Handling request).*$" -action = "drop" +[[tests.hermes]] +name = "boilerplate header trailer and divider" +input = """Hermes Agent • Session 7a3c • terminal +------------------ +term: npm run build +------------------ +vite build ok +------------------ +Hermes Gateway • forwarding done +""" +expected = "npm run build\nvite build ok" -[[rules]] -id = "hermes_tool_trailer" -type = "regex" -pattern = "^\\[Hermes\\] (Tool execution completed|Finished|Success).*$" -action = "drop" +[[tests.hermes]] +name = "parallel tool call noisy example" +input = """Tool Result • id 01 +> running cargo test +... +running 30 tests +---- +Tool Result • id 02 +> running npm run lint +... +✔ lint passed +""" +expected = """running cargo test +... +running 30 tests +---- +running npm run lint +... +✔ lint passed""" -[[rules]] -id = "hermes_divider" -type = "regex" -pattern = "^-{10,}.*$" -action = "drop" +[[tests.hermes]] +name = "preserve failure signal" +input = """------------------ +term: cargo test +------------------ +test result: FAILED. 2 passed; 1 failed; 0 ignored +""" +expected = """cargo test +test result: FAILED. 2 passed; 1 failed; 0 ignored""" From 34353e26016c6e0070485f5e5dd53cce5bffd570 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Mon, 15 Jun 2026 11:19:32 +0700 Subject: [PATCH 7/8] feat: implement Hermes compaction feedback loop on OMNI context pressure and document autonomous MCP tool integration --- docs/HERMES_OMNI_INTEGRATION.md | 20 +++++++++++ src/agents/hermes.rs | 64 +++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/docs/HERMES_OMNI_INTEGRATION.md b/docs/HERMES_OMNI_INTEGRATION.md index b9b15a2..c1b9c0b 100644 --- a/docs/HERMES_OMNI_INTEGRATION.md +++ b/docs/HERMES_OMNI_INTEGRATION.md @@ -226,6 +226,26 @@ Output is TOML filter proposals for `~/.omni/signals/`. Review and copy into `~/.omni/signals/` rather than editing built-in signals. This is where project-specific commands (internal CLIs, bespoke deploy scripts) get tuned. +### 6h. Advanced Tools Usage (Level 4 Autonomous Loops) + +With the Hermes integration complete, OMNI MCP tools can be utilized natively by Hermes during autonomous multi-step execution loops (e.g., Maker-Checker workflows). + +1. **`mcp_omni_loop_memory`** + Hermes can track long-running state variables using `mcp_omni_loop_memory` so they survive session compactions. + - *Example:* Use it to track the original user prompt or high-level goals during deeply nested debugging. + +2. **`mcp_omni_knowledge`** + Cross-session information storage for codebase architectural decisions. + - *Example:* When Hermes learns the structure of a microservice, it can invoke `mcp_omni_knowledge` to record it. In subsequent sessions, Hermes can retrieve it before exploring the code again. + +3. **`mcp_omni_retrieve`** + Fetch the raw (unfiltered) output of a heavily distilled terminal command. + - *Example:* If OMNI condensed a 50MB log file down to 20 lines, but Hermes needs the exact stack trace located in the omitted chunk, Hermes can call `mcp_omni_retrieve` passing the hash provided by OMNI's notice. + +4. **`mcp_omni_learn`** + Trigger OMNI to dynamically detect repetitive noise patterns and suggest custom TOML filters on-the-fly. + - *Example:* If a custom project script spews unique baggage, Hermes can ask OMNI to analyze the text and propose a `strip_lines_matching` rule. + --- ## 7. Common pitfalls and how to avoid them diff --git a/src/agents/hermes.rs b/src/agents/hermes.rs index e04e841..c0e848b 100644 --- a/src/agents/hermes.rs +++ b/src/agents/hermes.rs @@ -228,7 +228,19 @@ def register(ctx): def on_post_tool_call(tool_name, params, result): global _STEP_COUNTER _STEP_COUNTER += 1 - _run_omni("--post-hook") + res = _run_omni("--post-hook") + + # Explicit feedback loop: OMNI pressure -> Hermes compaction + if res and res.stdout: + out = res.stdout.decode('utf-8', errors='ignore') + if "[omni:context pressure: CRITICAL]" in out or "[omni:context pressure: WARNING]" in out: + try: + if hasattr(ctx, 'request_compaction'): + ctx.request_compaction("OMNI context pressure threshold reached") + elif hasattr(ctx, 'compact'): + ctx.compact() + except Exception: + pass def on_pre_tool_call(tool_name, params): _run_omni("--pre-hook") @@ -311,32 +323,32 @@ def register(ctx): } fs::write(&config_path, updated)?; + } - if configured_compression_in_config(&config_path) { - actions.push( - format!( - "{} Hermes compression is already enabled in ~/.hermes/config.yaml", - "✓".green() - ) - .to_string(), - ); - } else if !requires_manual_plugin_step { - #[allow(clippy::collapsible_if)] - if let Ok(current) = fs::read_to_string(&config_path) { - let compression_block = "\ncompression:\n enabled: true\n threshold: 0.50\n target_ratio: 0.20\n\n"; - - let mut updated = current; - if !updated.contains("compression:") { - updated.push_str(compression_block); - actions.push( - format!( - "{} Enabled Hermes compression in ~/.hermes/config.yaml", - "✓".bright_green() - ) - .to_string(), - ); - fs::write(&config_path, updated)?; - } + if configured_compression_in_config(&config_path) { + actions.push( + format!( + "{} Hermes compression is already enabled in ~/.hermes/config.yaml", + "✓".green() + ) + .to_string(), + ); + } else if !requires_manual_plugin_step { + #[allow(clippy::collapsible_if)] + if let Ok(current) = fs::read_to_string(&config_path) { + let compression_block = "\ncompression:\n enabled: true\n threshold: 0.50\n target_ratio: 0.20\n\n"; + + let mut updated = current; + if !updated.contains("compression:") { + updated.push_str(compression_block); + actions.push( + format!( + "{} Enabled Hermes compression in ~/.hermes/config.yaml", + "✓".bright_green() + ) + .to_string(), + ); + fs::write(&config_path, updated)?; } } } From badf120f5bfbc97cfed776024b4652592ca4c397 Mon Sep 17 00:00:00 2001 From: fajarhide Date: Mon, 15 Jun 2026 11:30:37 +0700 Subject: [PATCH 8/8] docs: add tutorial for configuring Hermes autonomous maker-checker loops with OMNI MCP tools --- examples/autonomous-loops/hermes-loop.md | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/autonomous-loops/hermes-loop.md diff --git a/examples/autonomous-loops/hermes-loop.md b/examples/autonomous-loops/hermes-loop.md new file mode 100644 index 0000000..4eccbf8 --- /dev/null +++ b/examples/autonomous-loops/hermes-loop.md @@ -0,0 +1,58 @@ +# OMNI + Hermes Autonomous Maker-Checker Loop + +This guide demonstrates how to configure Hermes Agent to execute a Level-4 Autonomous Loop (Maker-Checker pattern) natively utilizing OMNI's advanced MCP tools (`omni_loop_memory`, `omni_knowledge`, `omni_retrieve`, `omni_learn`). + +## 1. Prerequisites + +Ensure OMNI is installed and Hermes integration is active: +```bash +omni init --hermes +hermes plugins enable omni-signal-engine +hermes gateway restart +``` + +## 2. Launching Hermes with Loop Control + +When executing an autonomous loop, you should explicitly set the loop tracking variables so that OMNI's SessionStart and PreCompact hooks can embed checkpointing information directly into the Context Pressure warnings. + +```bash +export OMNI_LOOP_ID="hermes-maker-checker-001" +export OMNI_LOOP_GOAL="Refactor the authentication module and fix all related tests." +export OMNI_LOOP_BUDGET=500000 + +# Start Hermes session +hermes session start +``` + +## 3. The Hermes System Prompt (Agent Rules) + +In your Hermes project settings (or initial prompt), instruct the agent on how to use the MCP tools to govern its own lifecycle. + +```markdown +You are an autonomous Maker-Checker agent. You will execute tasks in a continuous loop until the goal is achieved. OMNI Context OS is attached to your session to optimize your context. + +### Loop Management Rules: +1. **Checkpointing:** At the start of a major phase (e.g., "Starting backend refactor"), call the `mcp_omni_loop_memory` tool with `action="set"` to record your current intent. This memory will survive if your context is compacted. +2. **Context Pressure:** If you see `[omni:context pressure: WARNING]` injected into the output, you must stop reading new large files and immediately call `mcp_omni_session` with `action="status"` to review active errors. +3. **Retrieving Details:** If you run a noisy test command and see `[OMNI: omitted X lines]`, and you need the exact stack trace, use `mcp_omni_retrieve` with the provided Hash. +4. **Learning Project Patterns:** If a custom internal script (e.g., `./bin/deploy.sh`) generates extreme noise that OMNI doesn't filter perfectly, call `mcp_omni_learn` to ask OMNI to generate a project-specific TOML filter. +5. **Knowledge Transfer:** When you successfully deduce the architecture of a complex module, use `mcp_omni_knowledge` with `action="store"` to save it globally. +``` + +## 4. Execution Trace Example + +1. **Agent decides to run tests:** + - *Hermes:* `terminal("cargo test --all")` + - *OMNI Hook:* Intercepts output, strips 50,000 lines of passing tests, returns 1 failure. + +2. **Agent encounters Context Pressure:** + - *OMNI Hook:* Detects budget usage > 80%. Returns `[omni:context pressure: WARNING]`. + - *Hermes Plugin:* Catches the warning and automatically calls `ctx.compact()` to compress history. + - *OMNI PreCompact Hook:* Saves `OMNI_LOOP_GOAL` and current `loop_memory` as an Engram so Hermes doesn't lose its train of thought. + +3. **Agent utilizes `omni_retrieve`:** + - *OMNI:* `... [OMNI: omitted 500 lines of Webpack build noise. Hash: a1b2c3d4] ...` + - *Hermes:* Needs to see the specific Webpack warning. Calls `mcp_omni_retrieve(hash="a1b2c3d4")`. + - *OMNI:* Returns the raw 500 lines directly via the MCP channel. + +By following this workflow, Hermes remains highly autonomous while OMNI acts as its intelligent Context OS, preventing context window bloat during loops > 100 iterations.