Noisegate
Gate the noise. Keep the signal.
Noisegate is a small, deterministic compaction layer for Hermes Agent. It catches the terminal walls, test-log avalanches, build spam, and other context-gremlins before they flood your model window.
It does not call a model. It does not vibe-summarize your logs. It does not pretend a chopped-off wall of text is "analysis".
It keeps the useful bits, leaves exact content alone, and gets out of the way when it is not sure.
uvx --from noisegate-hermes noisegate install-hermes
That is the normal install/update path for an existing Hermes Agent setup.
The problem
Agents are great at running tools.
Tools are great at producing nonsense amounts of output. Truly Olympic levels of nonsense.
A single test run, package install, Docker build, or search command can dump thousands of lines into context. Most of it is noise. Some of it matters. If you blindly truncate it, you lose the useful part. If you blindly keep it, you burn context and make the next model call worse.
Noisegate sits in the middle:
noisy tool output -> Noisegate -> compact signal-rich result
It is built for agent work where the context window is valuable and exactness matters.
What you get
Noisegate gives you two surfaces:
- a Hermes plugin that can compact noisy tool results before they enter the conversation
- a CLI you can use directly in terminals, scripts, CI jobs, and smoke tests
It knows how to reduce common noisy outputs:
pytestandunittestapt/apt-get,pip, anduvpackage-install/update logs, including resolver failures before auv runcommand startsnpm,pnpm, andyarngit statusandgit log- Docker build-style logs and explicit
docker logs/docker compose logsoutput - generic long output with deterministic head/tail fallback
And it refuses to touch things that should stay exact:
- file reads, including
fd/fdfindexecution forms that directly display matching files when no later noisy command owns the captured output - patches and diffs
- source/code search output from
rg,grep,ag, andack - skill documents
- memory, LCM, Hindsight, MCP, search, and web extraction results
- unknown future tools unless explicitly allowed
That last bit matters. A compactor that damages retrieved context is worse than no compactor.
Hermes-LCM stays a separate recovery layer. Noisegate does not depend on it and does not replace it:
lcm_* tool results stay exact, externalized_ref metadata is left alone, and compacted
terminal-style output preserves LCM externalized payload placeholders such as
[Externalized tool output: ... ref=...]. The early terminal hook still disables Noisegate raw
artifacts, so pre-redaction terminal output is not persisted just because an LCM ref appears.
The maintainer checklist lives in docs/product-contract.md. Product changes should improve context value and safety boundaries, not just make output shorter.
Install or update
Noisegate is distributed as the Python package noisegate-hermes. The Hermes plugin must be installed into the same Python environment that runs hermes.
Most users want this:
uvx --from noisegate-hermes noisegate install-hermes
Use the same command for first install and updates.
It finds hermes on PATH, verifies that the launcher points at a Hermes Python console script or supported Hermes shim inside a virtual environment, installs noisegate-hermes there, enables the noisegate plugin, removes any stale plugins.disabled entry for noisegate, and runs noisegate doctor. Native Windows launchers are opaque binaries, so Noisegate validates those by requiring an adjacent virtual-environment Python.
Preview the exact commands first. Dry-run mode does not run the install/enable/doctor commands and does not restart or reload Hermes:
uvx --from noisegate-hermes noisegate install-hermes --dry-run
If Hermes is running as a long-lived gateway/service, restart or reload that Hermes process through your normal maintenance flow after installing so the plugin/config change is picked up. Do not kick the chair out from under in-flight agent work.
From a checkout, install that exact checkout into Hermes:
uv run noisegate install-hermes --package .
Noisegate registers two Hermes hooks:
transform_terminal_output
transform_tool_result
npm wrapper
The npm package is a thin installer wrapper. The Python package is still the real thing.
If you prefer npm-shaped commands:
npx -p noisegate-hermes noisegate install-hermes
If your npm client does not resolve the single-bin shortcut, use:
npx -p noisegate-hermes noisegate-hermes-installer install-hermes
Manual fallback for unusual Hermes launchers
Prefer noisegate install-hermes. Use this only when you need to inspect or reproduce the low-level install flow manually.
HERMES_PYTHON="$(head -1 "$(command -v hermes)" | sed 's/^#!//')"
case "$(basename "$HERMES_PYTHON")" in
python*|pypy*) ;;
*) echo "Hermes launcher is not a Python console script; use noisegate install-hermes" >&2; exit 1 ;;
esac
HERMES_PREFIX="$(dirname "$(dirname "$HERMES_PYTHON")")"
if [ ! -f "$HERMES_PREFIX/pyvenv.cfg" ]; then
echo "Hermes Python is not inside a virtual environment; use noisegate install-hermes" >&2
exit 1
fi
uv pip install --python "$HERMES_PYTHON" noisegate-hermes
"$HERMES_PYTHON" - <<'PY'
from hermes_cli.config import load_config, save_config
cfg = load_config()
plugins = cfg.setdefault("plugins", {})
changed = False
raw_enabled = plugins.get("enabled")
enabled = list(raw_enabled) if isinstance(raw_enabled, list) else []
if "noisegate" not in enabled:
enabled.append("noisegate")
if raw_enabled != enabled:
plugins["enabled"] = enabled
changed = True
raw_disabled = plugins.get("disabled")
if isinstance(raw_disabled, list):
disabled = [name for name in raw_disabled if name != "noisegate"]
if raw_disabled != disabled:
plugins["disabled"] = disabled
changed = True
if changed:
save_config(cfg)
PY
"$HERMES_PYTHON" -m noisegate.cli doctor
Try it in 30 seconds
Run the health check:
noisegate doctor
Compact a noisy stream:
python - <<'PY' | noisegate reduce --command "pytest"
for i in range(300):
print(f"collecting test line {i:03d}")
print("FAILED tests/test_example.py::test_signal")
print("AssertionError: expected signal, got noise")
PY
Wrap a command and keep its exit code:
noisegate wrap -- pytest -q
Bypass compaction when exact output matters:
NOISEGATE_BYPASS=1 noisegate reduce < exact-output.txt
noisegate wrap --raw -- ./script-that-must-stay-exact
noisegate wrap --full -- ./another-exact-command
CLI reference
noisegate reduce --command "pytest" < noisy.log
noisegate reduce --command "pytest" --metadata < noisy.log # reducer diagnostics on stderr
noisegate reduce-json --metadata < hermes-tool-result.json # envelope diagnostics on stderr
noisegate wrap -- pytest -q
noisegate wrap --metadata -- pytest -q
noisegate wrap --store-artifact -- pytest -q
noisegate wrap --raw -- cat exact-output.txt
noisegate install-hermes --dry-run
noisegate doctor
noisegate cat ng_<artifact-id>
noisegate cat --artifact-dir /tmp/noisegate-artifacts ng_<artifact-id>
noisegate artifacts list --artifact-dir /tmp/noisegate-artifacts
noisegate artifacts stats --artifact-dir /tmp/noisegate-artifacts
noisegate artifacts verify --artifact-dir /tmp/noisegate-artifacts
wrap runs a command without a shell, captures stdout and stderr in arrival order, writes the compacted result to stdout, and exits with the wrapped command's exit code. It captures up to 4 MiB of combined output by default; use --max-capture-bytes <n> to change that.
If capture is truncated, Noisegate adds this marker:
[noisegate: capture truncated]
reduce-json accepts a Hermes-like envelope with a result string, a nested result object, or a direct JSON tool result. Bad JSON fails open and is written back unchanged; non-text result values such as lists and null remain valid JSON and pass through unchanged. Mixed payloads can compact both the nested result and direct top-level text fields, but JSON-string results stay parseable and the complete serialized envelope is returned unchanged unless the final form is smaller.
Envelope-shaped payloads belong on the CLI path. The Hermes plugin hook expects the host to pass tool_name separately, but blank hook calls may honor an embedded compactable tool_name or infer terminal behavior only from terminal-like direct payloads. Explicit host call arguments are authoritative for named hook calls. Across CLI envelopes, protected exact-output intent from usable command, cmd, shell_command, code, or quoted argv aliases wins over stale noisy aliases; blank aliases do not mask a usable command.
For exit hints, a non-zero exit, exit_code, returncode, or return_code wins over a failed/error status, and a failed/error status wins over numeric zero. Existing noisegate metadata is never overwritten: transformed direct payloads fall back to _noisegate, then add further leading underscores until a free key is available.
reduce-json treats the complete serialized envelope as its artifact decision boundary. With artifact mode enabled it first computes a private no-write preview and accepts only a smaller final envelope. When that accepted envelope needs exactly one raw artifact, Noisegate performs the artifact-writing pass and delivers its recovery ID. Envelopes that would need multiple raw artifacts still compact inline but skip artifact storage as one conservative unit, so a later field cannot leave an earlier orphan behind. An outer no-gain decision returns the original payload without creating an orphaned raw artifact or a recovery ID that was never delivered.
Use --metadata (or --debug) on reduce, reduce-json, or wrap when you need to explain a compaction decision without changing stdout. Diagnostics are written as one JSON object on stderr. reduce and wrap include the chosen command_class, reducer or attempted reducer, unchanged reason code, and chars/lines saved. reduce-json reports envelope-level size metrics, includes reducer details when it directly reduces a plain result string, exposes that field's sizes under field_* keys, and leaves field-level reducer details in the transformed JSON metadata when compaction happens inside nested tool-result JSON.
doctor prints the effective config as well as environment warnings, so ignored or fallback values such as NOISEGATE_ARTIFACTS=maybe are visible before they cause confusing artifact behavior.
For AI agents
If you are an AI agent working in this repo, read this first.
One-line instruction:
Install and enable Noisegate for Hermes, then stop dumping terminal walls into context; use NOISEGATE_BYPASS when exact bytes matter.
Operational rules:
- Use Noisegate for noisy terminal/tool output, not for exact source material.
- Do not compact file reads, patches, diffs, retrieved context, skill docs, memory results, MCP results, or web extraction output.
Treat code-search output (
rg,grep,ag,ack) as source context and leave it exact. - Do not treat Noisegate as a raw-output archive. Raw artifacts are off by default.
- Keep Hermes-LCM optional. Noisegate must work without it.
- Do not write raw terminal output into Hindsight.
- If compaction fails, preserve the original output.
- Before committing, run the quality gate and scan the diff for secrets and personal/private data.
Safe smoke test for a lane or installation:
noisegate doctor
python - <<'PY' | noisegate reduce --command "pytest"
for i in range(260):
print(f"line {i:03d}")
print("FAILED tests/test_demo.py::test_keeps_signal")
print("AssertionError: signal survived")
PY
printf '%s\n' '[noisegate:bypass]' 'line that must stay exact' | noisegate reduce --command "pytest"
Expected result:
doctorreports a healthy package/plugin state- long noisy output gets smaller
- the failure line stays visible
- bypass output stays unchanged
- no config writes, git writes, service restarts, or artifact writes happen unless explicitly requested
- install-hermes dry-runs show exact install/enable commands before changing Hermes
Safety model
Noisegate is intentionally conservative. Paranoid, even. That is a feature.
Compacted output is kept within the configured max_chars and max_lines caps. If a readable omission marker plus preserved content cannot fit, Noisegate leaves the original output unchanged instead of emitting marker fragments or dropping important failure text.
For Hermes hook traffic, it compacts only this explicit allowlist:
terminal
process
read_terminal
browser_console
Protected surfaces include:
read_file
write_file
patch
apply_patch
skill_view
skill_manage
session_search
memory
hindsight_*
lcm_*
mcp_* / mcp__*
web_search
web_extract
execute_code
search_files
simple direct terminal file-display commands (`cat`, `sed -n`, `head`, `tail`, `less`, `more`, `nl`, `bat`, `git show REV:path`)
rg / grep / ag / ack source search
git diff / unified diffs / V4A patches
unknown future tools
execute_code is protected even when its printed output looks like compactable test, dependency, package-manager, or log spam. That boundary is deliberate: execute_code only returns script stdout, and stdout can be source code, JSON, configs, diffs, copied web excerpts, or other exact context. If a script intentionally wants compaction for a known noisy command, use an explicit command-aware route instead, such as the terminal tool, noisegate wrap -- <command>, or a reduce-json envelope labeled as a noisy command/tool. When in doubt, keep execute_code raw.
Bypass controls:
NOISEGATE_BYPASS
NOISEGATE_RAW
[noisegate:bypass]
[noisegate:raw]
Environment flags:
NOISEGATE_DISABLE=1 # turn compaction off
NOISEGATE_ARTIFACTS=1 # opt in to private raw-output artifacts
NOISEGATE_ARTIFACT_DIR=/path/to/artifacts
NOISEGATE_ARTIFACT_SIZE_CAP=1000000 # max stored raw-output bytes per artifact
noisegate doctor reports ignored or fallback environment values, so typos like NOISEGATE_ARTIFACTS=maybe do not fail silently.
Hermes calls transform_terminal_output before its built-in terminal redaction pass. Noisegate still compacts inline terminal output there, but it disables raw artifact storage on that early hook so pre-redaction output is not persisted.
Artifacts
Raw terminal output is not stored by default.
When artifact mode is enabled, Noisegate writes the original output to a private filesystem store:
- directory mode
0700 - file mode
0600 - default size cap of 1,000,000 bytes
- content-addressed IDs shaped like
ng_<sha256-prefix> - path containment and symlink traversal checks
Retrieve an artifact:
noisegate cat ng_<artifact-id>
Inspect the store:
noisegate artifacts list --artifact-dir /tmp/noisegate-artifacts
noisegate artifacts stats --artifact-dir /tmp/noisegate-artifacts
noisegate artifacts verify --artifact-dir /tmp/noisegate-artifacts
verify recomputes hashes and returns a non-zero exit code if an artifact was tampered with, has an invalid private-store path, or if a live temp artifact file is still present. Stale temp files created by interrupted writes are removed during verification/new writes without printing their raw contents.
Hermes-LCM and memory layers
Noisegate is not Hermes-LCM and does not require Hermes-LCM.
If Hermes-LCM is installed, Noisegate acts as an inline compaction step before noisy output inflates active context. Downstream context or transcript layers usually see the compacted result.
Hindsight is semantic long-term memory, not a raw output bucket. Noisegate must not write raw terminal output into Hindsight.
Development
Start clean:
git status --short --branch
git remote -v
Run the local quality gate:
uv run ruff check .
uv run python -m pytest -q
uv run python scripts/check_release.py
uv run python scripts/check_contributors.py
(cd npm/noisegate && npm ci --ignore-scripts && npm test && npm pack --dry-run)
rm -rf dist
uv build
uvx twine check dist/*
git diff --check
Release and publishing
Noisegate uses release-cycle publishing, not random local tokens and crossed fingers:
- PyPI package:
noisegate-hermes - npm package:
noisegate-hermesinstaller wrapper - the main release workflow publishes the GitHub Release, then PyPI, then npm
- npm publish waits until the matching PyPI version is visible
- GitHub Actions publish through OIDC/trusted publishing where supported
- npm publish uses provenance (
npm publish --provenance) - no long-lived publish tokens in git or workflow files
mainis branch-protected; release changes go through PR review
Release helpers:
uv run python scripts/prepare_release.py 0.2.0
uv run python scripts/check_release.py --tag v0.2.0
uv run python scripts/build_release_notes.py v0.2.0 --repo Tosko4/noisegate --output dist/release-notes.md
build_release_notes.py combines the versioned changelog section with GitHub PR metadata for the tag range. Release notes must include update instructions, categorized PRs, each PR's OP/author handle, and first-time contributors whose first merged PR landed since the previous release.
Release metadata must stay aligned across:
pyproject.toml->[project].versionnoisegate/_version.py->__version__noisegate/plugin.yaml-> plugin manifestversionuv.lock-> locked editable project versionnpm/noisegate/package.json-> npm wrapper versionnpm/noisegate/package-lock.json-> npm wrapper lockfile version
GitHub Actions run linting, tests, release metadata checks, contributor checks, package build, twine check, npm wrapper checks, npm dry-run packing, and wheel install smoke tests on Python 3.11, 3.12, and 3.13.
What Noisegate is not
Noisegate is not a model summarizer.
It is not a replacement for logs.
It is not a database.
It is not a magic context engine.
It is a small gate in front of noisy agent output. That is the whole point.
Noisegate was built from the ground up for Hermes after exploring existing approaches to terminal-output compaction.
License
MIT. See LICENSE.
Metadata
Release files for noisegate-hermes 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| noisegate_hermes-0.2.0.tar.gz | 2.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| noisegate_hermes-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.8 MB
Release files / noisegate_hermes-0.2.0.tar.gz
| Download URL | noisegate_hermes-0.2.0.tar.gz |
|---|---|
| Size | 2.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
33b57608c5d78ea52eead094a4290428acaf5bb78c318e46cc5c5357403e2484
|
|
BLAKE2b-256 checksum How to use checksums |
44ef737cb42a34eb55790a6149f496b371fee07b95899c3b6f848fc4bc3c43af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / noisegate_hermes-0.2.0-py3-none-any.whl
| Download URL | noisegate_hermes-0.2.0-py3-none-any.whl |
|---|---|
| Size | 92.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5e26b23eebc2301d90d505aeca2db616955b9a2fbbe615a6cf7b43e123b554c9
|
|
BLAKE2b-256 checksum How to use checksums |
a3dbde851739e36c75b8245da25bcc6f0db8a5cf4a118d40f5c417d3eb915a5f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency log