跳到主要内容

aphrodite

❖ Communityv2.2.6

CCR compression plugin - 28-type classifier, 13 tools, opt-in context engine, TOML-driven templates.

Open in Hermes Desktop
hermes plugins install aphrodite

What it adds

Tools 13

aphrodite_retrieveaphrodite_compressaphrodite_statsaphrodite_rebuildaphrodite_filesaphrodite_diffaphrodite_searchaphrodite_directiveaphrodite_testaphrodite_catalogaphrodite_reclassifyaphrodite_prefetchaphrodite_prefetch_status

Hooks 6

on_session_starttransform_tool_resultpre_llm_calltransform_terminal_outputpost_llm_callpre_tool_call

Environment variables it needs 1

APHRODITE_API_KEY

README

From the reviewed commit e95afcd ↗; it updates when the author re-pins.

Aphrodite 💋 Hermes Plugin

[!NOTE]

CCR compression plugin for Hermes Agent - thin Python loader + Rust dylib. Sub-ms tool output compression, 30-type classifier, 13 tools, 6 hooks, context engine. Skills ship dev-side, not with the plugin.

Aphrodite intercepts tool output before it reaches the LLM and replaces it with compact, structured previews. The agent sees 15 tokens of metadata instead of 500 tokens of raw text - and retrieves the full content only when it actually needs it. All compression logic runs in the Rust dylib.

plugin hermes license


Install ⚡

Hermes plugin + explicit setup

Terminal

git clone https://github.com/PlayForm/Aphrodite-Hermes.git
cd Aphrodite-Hermes
ln -s "$(pwd)" ~/.hermes/plugins/aphrodite
bash download.sh # explicit setup step: binary + dylib from GitHub Releases
cd ..
hermes plugins enable aphrodite
hermes

The ln -s line links the plugin into ~/.hermes/plugins/ so Hermes can discover it. Create it at install time - the plugin never creates, converts, or recreates this link: ~/.hermes/plugins/aphrodite is Hermes-owned (layout_check.py is report-only; the install path may be a symlink or a real directory).

Binaries are never shipped inside the repository. The plugin downloads them from the GitHub release (tag pinned in BINARY_VERSION, checksums generated by Build.yml into the in-tree SHA256SUMS.txt) with an explicit setup step - bash ./download.sh (or pwsh ./download.ps1 on Windows) - which verifies SHA-256 against the in-tree checksum list and refuses on mismatch. register() never downloads and never writes into ~/.hermes/plugins/aphrodite/: if the binaries are missing it logs the setup command and the plugin stays disabled until they are present. No Rust toolchain required.

[!IMPORTANT]

Native Windows: run pwsh ./download.ps1 instead of download.sh - no Git Bash/WSL needed. See Windows install for the full walkthrough, and Troubleshooting if the proxy doesn't come up after enabling the plugin.

LLM provider configuration (required)

The aphrodite proxy is an OpenAI-compatible LLM API proxy - it forwards requests upstream - so it needs its own provider credentials even though Hermes already has a provider configured. The plugin cannot read Hermes' provider config, and there is no keyless / compression-only mode: without a key the proxy refuses to start and the plugin is unusable.

Terminal

export APHRODITE_API_KEY="sk-..."                 # REQUIRED
export APHRODITE_API_URL="https://api.openai.com" # optional
export APHRODITE_MODEL="default-model"            # optional

Alternatives: run aphrodite setup, or add api_key / api_url / model to ~/.hermes/aphrodite/aphrodite.toml. If the proxy fails to start, ~/.hermes/aphrodite/proxy-stderr.log shows the reason - no API key configured means the key is missing.

What changes after install

After installing and launching Hermes once:

Filesystem

~/.hermes/
├── plugins/
│   └── aphrodite → /path/to/Aphrodite-Hermes    ← manual symlink to this repo
├── aphrodite/
│   ├── binaries/
│   │   ├── aphrodite                            ← proxy binary (fetched by `download.sh` ~35 MB)
│   │   └── libaphrodite_hermes.dylib            ← plugin dylib (fetched by `download.sh`)
│   ├── ccr.db                                    ← SQLite CCR store (on first run)
│   └── proxy-stderr.log                          ← proxy logs (on failure)

The plugin also adds to your Hermes config:

config.yaml

# Added automatically on enable
plugins:
    enabled:
        - aphrodite

# Recommended additions (manual)
context:
    engine: aphrodite
    engine_threshold_pct: 100 # 100 = engine effectively off; lower = compress sooner
model:
    context_length: 1000000

Two proxy processes launch on :9797 (cache) and :9798 (token). On registration the plugin probes both health endpoints and reuses an already-running proxy pair instead of launching a second instance.

Verify it's working

Terminal

# In a Hermes session:
aphrodite_stats

# Or via CLI:
curl http://127.0.0.1:9798/health
# → {"status":"healthy","version":"<installed binary version - see BINARY_VERSION>"}

Clean uninstall

Terminal

hermes plugins disable aphrodite
rm ~/.hermes/plugins/aphrodite # remove the manual symlink you created
pkill -f "$HOME/.hermes/aphrodite/binaries/aphrodite"

Architecture 🏗️

The plugin is a thin Python registration shim over a Rust dylib - every hook, tool, and byte of compression logic lives in Rust. Python exists only to load the dylib via ctypes and register its surface with Hermes.

Layers

Call chain

 Hermes Agent (hooks + tool dispatch)
    │
    ▼
 plugins/aphrodite/__init__.py        ← ~1,150-line Python loader
    │  ctypes FFI, registers hooks/tools/engine - no logic
    ▼
 libaphrodite_hermes.dylib            ← Hermes bridge (JSON contract)
    │  6 hooks · 13 tools · schemas
    ▼
 libaphrodite (core engine)           ← ALL compression logic
    │  hooks · resolve · retrieve · marker · preview
    │  stage2 · struct_extract · state · session
    │  catalog · prefetch · poll_worker · directives
    │  config_loader · builtin_directives
    ▼
 CCR store (SQLite :9798 / in-memory :9797 / inline)

Data flow

Plugin mode

 Tool executes → output intercepted by hook
      ↓
 classify → preview → store (SQLite / in-memory / inline)
      ↓
 Agent ← [type:enriched preview] (not raw output)
      ↓
 aphrodite_retrieve(hash) → full content (only when needed)

Dual listeners

The plugin auto-launches two proxy processes (and reuses an already-running pair instead of starting a second instance):

Listener Port CCR backend Threshold Best for
Cache :9797 In-memory (DashMap, 10K entries) >8 KB Speed, transient sessions
Token :9798 SQLite (persistent) >1 KB Durability, tool relay

Hooks

Six Hermes hooks drive the plugin (provides_hooks in plugin.yaml), all dispatched to the Rust dylib:

Hook Role
on_session_start Engine bootstrap, directive seeding, proxy health check
transform_tool_result Compress every tool result before it reaches the LLM
transform_terminal_output Compress terminal output with exit-code context
pre_llm_call Inject directives, compress overflowing middle turns
post_llm_call Capture savings, update adaptive thresholds
pre_tool_call Prefetch-aware dispatch, auto-background slow calls

[!NOTE]

No hot-reload: the dylib is resolved once per process from the installed binary set and loaded directly (ctypes.CDLL on the canonical path). The load is deterministic from the pinned tree - it never races a rebuild. To pick up a new build, restart the Hermes session.


Tools 🛠️

Tool Description
aphrodite_retrieve Resolve <<<CCR:hash|type|size>>> markers
aphrodite_compress Compress content via CCR with type hint
aphrodite_stats Proxy health, engine status, inline store size
aphrodite_rebuild Report binary/proxy version + a rebuild hint (does not rebuild or restart itself)
aphrodite_files Tracked file references grouped by tool
aphrodite_diff Conversation turn history with summaries
aphrodite_search Search CCR store by keyword or type
aphrodite_directive List/swap/add/remove/reset active behavioral directives
aphrodite_test Smoke test suite: quick (1 sample) or full (3 samples)
aphrodite_catalog Full CCR catalog with hashes, types, sizes, previews
aphrodite_reclassify Retroactive metadata enrichment
aphrodite_prefetch Read + compress files on demand; markers returned inline
aphrodite_prefetch_status Live prefetch schedule: loading, ready, errors

Configuration ⚙️

All tuning in aphrodite.toml - searched in ./aphrodite.toml, then ~/.hermes/aphrodite/aphrodite.toml (when APHRODITE_CONFIG_PATH is unset):

aphrodite.toml

[compression]
engine_threshold_pct = 45    # shipped default (dylib status flag); 100+ disables engine compression
engine_protect_first = 2     # messages to keep at start
engine_protect_last = 5      # messages to keep at end
engine_min_msgs = 8          # minimum before activating
tool_threshold_token = 512   # token proxy threshold (bytes)
tool_threshold_cache = 4096  # cache proxy threshold (bytes)
terminal_threshold  = 1024   # terminal output threshold (bytes)
inline_threshold    = 2048   # inline-vs-durable CCR storage cutoff (bytes)
code_multiplier     = 3.0    # keep code in context longer
context_engine      = true   # dylib status flag; default-on, no env var needed

[previews]
model_family = "code_first"  # compact | code_first | balance
code_structure_map = true    # show fn/struct/class sigs

[prompts]
retrieve_guidance = "minimal"
ccr_marker_hint = false

Env var overrides: APHRODITE_ENGINE_THRESHOLD_PCT, APHRODITE_CONTEXT_ENGINE, etc. See docs/config/env-vars.md.

Runtime home (one decision, shared by both halves)

The runtime home - where aphrodite.toml, binaries/, directives/, ccr.db, and proxy-stderr.log live - is resolved by one shared decision, made identically by the plugin shim and the Rust binary:

  1. APHRODITE_HOME - explicit override; never second-guessed.
  2. <hermes-home>/aphrodite - $HERMES_HOME when set (Docker image, profile gateways), else ~/.hermes/aphrodite.

At startup the shim exports its decision into APHRODITE_HOME (and APHRODITE_DIRECTIVES_DIR), so the dylib and the spawned proxy binary resolve the same directory by construction - the two halves cannot diverge. The startup log names the result: runtime home: <path> (decided by APHRODITE_HOME override|HERMES_HOME|default).

Upgrading from ≤ 2.1.5: when the Hermes-home-derived runtime home does not hold an install but the pre-2.2 ~/.hermes/aphrodite does, the old home is adopted (a one-line warning in the log) - nothing is migrated and binaries/ / aphrodite.toml keep resolving. Adoption never fires for a throwaway/scratch HERMES_HOME (e.g. the hermes plugins validate probe, which must stay isolated from the real install). Set APHRODITE_HOME to pin the location explicitly. The Rust binary follows the same resolution, so standalone runs (aphrodite setup, proxy launches) agree even without the shim.

Set APHRODITE_NO_AUTO_LAUNCH=1 to skip the proxy auto-launch entirely, e.g. when a cargo watch dev loop runs the proxy itself.

Directives

Custom behavioral directives are name.md files in ~/.hermes/aphrodite/directives/ - an empty file means an intentionally empty directive. The plugin does NOT ship a directives/ set: the binary provides them (embedded builtins) and materializes them into the user-data home at startup/setup, so the plugin dir stays a pure loader. The dylib reads them from APHRODITE_DIRECTIVES_DIR (defaults to ~/.hermes/aphrodite/directives, user override wins). If no directive directory is found, the compiled built-in set loads as a fallback - its activation is logged. Manage them at runtime with aphrodite_directive (list/swap/add/load/remove/reset).


Dev Install (Rust source)

Terminal

git clone https://github.com/PlayForm/Aphrodite.git
cd Aphrodite
cargo build -p aphrodite-hermes
# Dylib: target/debug/libaphrodite_hermes.dylib (crate aphrodite-hermes;
# `-p aphrodite` alone builds only the proxy binary). The loader resolves the
# dylib from the canonical runtime home first (env override
# APHRODITE_HERMES_DYLIB_PATH wins when set), so a dev loop copies it there:
mkdir -p ~/.hermes/aphrodite/binaries
cp target/debug/libaphrodite_hermes.dylib ~/.hermes/aphrodite/binaries/

An already-running dev proxy (cargo run -p aphrodite) that answers the health endpoints is reused instead of relaunched - the plugin probes both ports before launching (see _start_proxy).


Files

Layout

Aphrodite-Hermes/
├── __init__.py          ← ~1,150-line Python loader (ctypes FFI)
├── plugin.yaml          ← 13 tools, 6 hooks, context engine
├── download.sh          ← Explicit binary fetch (macOS/Linux/Git Bash/WSL; optional - validates in-tree checksums)
├── download.ps1         ← Explicit binary fetch (native Windows PowerShell; optional)
├── BINARY_VERSION       ← Pinned binary release tag
├── SHA256SUMS.txt       ← In-tree checksums (GENERATED by Build.yml; download.sh verifies against this)
├── tests/               ← Plugin test suite
├── README.md            ← This file
└── .gitignore

Binaries are never stored in this directory and register() never downloads: the plugin downloads from the GitHub release only via the explicit download.sh / download.ps1 setup step into ~/.hermes/aphrodite/binaries/ (the canonical runtime home), validated against the in-tree SHA256SUMS.txt (refuse on mismatch). The plugin never modifies ~/.hermes/plugins/aphrodite/.

← Back to the catalog · catalog built Oct 8, 2026