Metadata-Version: 2.4
Name: noisegate-hermes
Version: 0.1.2
Summary: Standalone Hermes Agent plugin and CLI for deterministic terminal/tool-output compaction.
Project-URL: Homepage, https://github.com/Tosko4/noisegate#readme
Project-URL: Repository, https://github.com/Tosko4/noisegate
Project-URL: Issues, https://github.com/Tosko4/noisegate/issues
Project-URL: Changelog, https://github.com/Tosko4/noisegate/releases
Author: Noisegate contributors
License-Expression: MIT
License-File: LICENSE
Keywords: agent,cli,compaction,hermes,plugin
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# Noisegate

<p align="center">
  <img src="assets/noisegate-hero.png" alt="Noisegate: Gate the noise. Keep the signal." width="100%">
</p>

<p align="center">
  <a href="https://github.com/Tosko4/noisegate/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Tosko4/noisegate/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://github.com/Tosko4/noisegate/releases"><img alt="GitHub release" src="https://img.shields.io/github/v/release/Tosko4/noisegate?include_prereleases&sort=semver"></a>
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-black"></a>
  <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-orange">
</p>

**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.

```bash
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:

```text
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:

- `pytest` and `unittest`
- `npm`, `pnpm`, and `yarn`
- `git status` and `git log`
- search output from `rg`, `grep`, `ag`, and `ack`
- Docker build-style logs
- generic long output with deterministic head/tail fallback

And it refuses to touch things that should stay exact:

- file reads
- patches and diffs
- 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.

## 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:

```bash
uvx --from noisegate-hermes noisegate install-hermes
```

Use the same command for first install and updates.

It finds `hermes` on `PATH`, resolves the Python environment that actually runs Hermes, installs `noisegate-hermes` there, enables the `noisegate` plugin, and runs `noisegate doctor`.

Preview the exact commands first:

```bash
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:

```bash
uv run noisegate install-hermes --package .
```

Noisegate registers two Hermes hooks:

```text
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:

```bash
npx -p noisegate-hermes noisegate install-hermes
```

If your npm client does not resolve the single-bin shortcut, use:

```bash
npx -p noisegate-hermes noisegate-hermes-installer install-hermes
```

<details>
<summary>Manual fallback for unusual Hermes launchers</summary>

Prefer `noisegate install-hermes`. Use this only when you need to inspect or reproduce the low-level install flow manually.

```bash
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", {})
enabled = plugins.get("enabled") if isinstance(plugins.get("enabled"), list) else []
disabled = plugins.get("disabled") if isinstance(plugins.get("disabled"), list) else []
if "noisegate" not in enabled:
    enabled.append("noisegate")
plugins["enabled"] = enabled
plugins["disabled"] = [name for name in disabled if name != "noisegate"]
save_config(cfg)
PY
"$HERMES_PYTHON" -m noisegate.cli doctor
```

</details>

## Try it in 30 seconds

Run the health check:

```bash
noisegate doctor
```

Compact a noisy stream:

```bash
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:

```bash
noisegate wrap -- pytest -q
```

Bypass compaction when exact output matters:

```bash
NOISEGATE_BYPASS=1 noisegate reduce < exact-output.txt
noisegate wrap --raw -- ./script-that-must-stay-exact
noisegate wrap --full -- ./another-exact-command
```

## CLI reference

```bash
noisegate reduce --command "pytest" < noisy.log
noisegate reduce-json < hermes-tool-result.json
noisegate wrap -- 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:

```text
[noisegate: capture truncated]
```

`reduce-json` accepts either a Hermes-like envelope with a `result` string or a direct JSON tool result. Bad JSON fails open and is written back unchanged.

## For AI agents

If you are an AI agent working in this repo, read this first.

**One-line instruction:**

```text
Install and enable Noisegate for Hermes, then stop dumping terminal walls into context; use NOISEGATE_BYPASS when exact bytes matter.
```

Operational rules:

1. Use Noisegate for noisy terminal/tool output, not for exact source material.
2. Do not compact file reads, patches, diffs, retrieved context, skill docs, memory results, MCP results, or web extraction output.
3. Do not treat Noisegate as a raw-output archive. Raw artifacts are off by default.
4. Keep Hermes-LCM optional. Noisegate must work without it.
5. Do not write raw terminal output into Hindsight.
6. If compaction fails, preserve the original output.
7. Before committing, run the quality gate and scan the diff for secrets and personal/private data.

Safe smoke test for a lane or installation:

```bash
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:

- `doctor` reports 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:

```text
terminal
process
read_terminal
browser_console
```

Protected surfaces include:

```text
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
git diff / unified diffs
unknown future tools
```

Bypass controls:

```text
NOISEGATE_BYPASS
NOISEGATE_RAW
[noisegate:bypass]
[noisegate:raw]
```

Environment flags:

```bash
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:

```bash
noisegate cat ng_<artifact-id>
```

Inspect the store:

```bash
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:

```bash
git status --short --branch
git remote -v
```

Run the local quality gate:

```bash
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-hermes` installer 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
- `main` is branch-protected; release changes go through PR review

Release helpers:

```bash
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].version`
- `noisegate/_version.py` -> `__version__`
- `noisegate/plugin.yaml` -> plugin manifest `version`
- `uv.lock` -> locked editable project version
- `npm/noisegate/package.json` -> npm wrapper version
- `npm/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.

## Attribution

Noisegate was informed by the MIT-licensed [Tokenjuice](https://github.com/denthought/tokenjuice) project, especially its ideas around deterministic reducers, command classification, safe bypasses, artifact opt-in, and machine-readable metadata.

Noisegate is a fresh Python implementation focused on Hermes Agent.

## License

MIT. See [LICENSE](LICENSE).
