[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1031":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":9,"pushedAt":9,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":15,"starSnapshotCount":15,"syncStatus":28,"lastSyncTime":29,"discoverSource":30},1031,"hermes-lcm","stephenschoettler\u002Fhermes-lcm","stephenschoettler","Lossless Context Management plugin for Hermes Agent — DAG-based context engine that never loses a message",null,"Python",701,57,7,5,0,30,66,303,90,9.29,false,"main",true,[],"2026-06-12 02:00:22","\u003Cp align=\"center\">\n  \u003Cimg src=\"banner.png\" alt=\"HERMES-LCM\" width=\"800\">\n\u003C\u002Fp>\n\n[![CI](https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm\u002Factions\u002Fworkflows\u002Fci.yml)\n[![Release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Fstephenschoettler\u002Fhermes-lcm)](https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm\u002Freleases)\n\n**Lossless Context Management plugin for [Hermes Agent](https:\u002F\u002Fgithub.com\u002FNousResearch\u002Fhermes-agent)**\n\n> Bounded context, unbounded memory. Nothing is ever lost.\n\nBased on the [LCM paper](https:\u002F\u002Fpapers.voltropy.com\u002FLCM) by Ehrlich & Blackman (Voltropy PBC, Feb 2026).\nInspired by [lossless-claw](https:\u002F\u002Fgithub.com\u002Fmartian-engineering\u002Flossless-claw) for OpenClaw.\n\n---\n\n## The Problem\n\nWhen active context fills up, agents usually replace older turns with a flat\nsummary. Details can fall out of the prompt, and recovery depends on a separate\nhistory path the model may not use.\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Fstandard_compression.png\" alt=\"Standard compression\" width=\"700\">\n\u003C\u002Fp>\n\n## The Fix\n\nPersist the conversation, compact old context into a hierarchical summary DAG,\nand give the agent tools to drill back into the exact material that was\ncompacted.\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Flcm_compression.png\" alt=\"LCM compression\" width=\"700\">\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Farchitecture.png\" alt=\"Architecture\" width=\"700\">\n\u003C\u002Fp>\n\n## What It Does\n\n- **SQLite message store** - preserves raw messages by default before compaction\n- **Summary DAG** - compacts older context into depth-aware summary nodes\n- **Bounded recovery** - pages raw messages, child summaries, and externalized payloads without flooding the main context\n- **Agent tools** - `lcm_grep`, `lcm_describe`, `lcm_expand`, and `lcm_expand_query`\n- **Source-aware retrieval** - filters raw rows and summaries by descendant source lineage\n- **Session controls** - ignore noisy sessions or keep sessions read-only with glob patterns\n- **Large output controls** - optional externalization and transcript GC for oversized tool results\n- **Diagnostics** - `lcm_status`, `lcm_doctor`, and optional `\u002Flcm` slash commands\n\n## LCM vs built-in compression\n\nHermes core may persist original conversation history in `state.db` before\nbuilt-in compression rewrites the active prompt. Built-in compression can still\nbe lossy in the active context, but previous content may be recoverable later\nthrough host-level history tools such as `session_search`.\n\n`hermes-lcm` is different because recall is part of the active context engine:\n\n- plugin-local store and DAG built specifically for drill-down\n- current-session retrieval through LCM tools, not an auxiliary cross-session search step\n- explicit source-lineage and session-boundary rules\n\nPosition LCM around retrieval quality, autonomy, and drill-down behavior. Do not\nclaim that Hermes core has no persisted record of pre-compression history.\n\n## Requirements\n\n- Hermes Agent with the pluggable context engine slot ([PR #7464](https:\u002F\u002Fgithub.com\u002FNousResearch\u002Fhermes-agent\u002Fpull\u002F7464))\n- Python 3.11+\n- No required third-party runtime dependencies. `tiktoken` is used if available; otherwise LCM falls back to character-based token estimates. `regex` is used if available to apply timeouts to message ignore patterns; if it is not installed, message-level regex filtering is disabled with a warning rather than running unbounded stdlib `re` matches.\n\n## Install\n\nCanonical install path: clone `hermes-lcm` as a general user plugin.\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm \\\n  ~\u002F.hermes\u002Fplugins\u002Fhermes-lcm\n```\n\nFor a profile-specific install:\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm \\\n  ~\u002F.hermes\u002Fprofiles\u002Fmyprofile\u002Fplugins\u002Fhermes-lcm\n```\n\nFrom an existing checkout, install a symlink:\n\n```bash\n.\u002Fscripts\u002Finstall.sh\n# Optional profile-aware install:\nHERMES_PROFILE=myprofile .\u002Fscripts\u002Finstall.sh\n```\n\n## Activate\n\nThe plugin has two names:\n\n- plugin manifest name: `hermes-lcm`\n- runtime context engine name: `lcm`\n\nBoth must be configured:\n\n```yaml\nplugins:\n  enabled:\n    - hermes-lcm\n\ncontext:\n  engine: lcm\n```\n\nRestart Hermes after changing plugin or context-engine config.\n\n## Update\n\nIf you cloned directly into the plugin directory:\n\n```bash\ncd ~\u002F.hermes\u002Fplugins\u002Fhermes-lcm && git pull --ff-only\n```\n\nFor a profile-specific install:\n\n```bash\ncd ~\u002F.hermes\u002Fprofiles\u002Fmyprofile\u002Fplugins\u002Fhermes-lcm && git pull --ff-only\n```\n\nIf you installed a symlink from a separate checkout:\n\n```bash\n.\u002Fscripts\u002Fupdate.sh\n```\n\nRestart Hermes after updating.\n\n## Verify\n\nRun:\n\n```bash\nhermes plugins\n```\n\nExpected signals:\n\n- plugin list includes `hermes-lcm`\n- selected context engine is `lcm`\n- tool list includes `lcm_grep`, `lcm_load_session`, `lcm_describe`, `lcm_expand`, `lcm_expand_query`, `lcm_status`, and `lcm_doctor`\n\nTypical output:\n\n```text\nPlugins (1):\n  ✓ hermes-lcm v0.9.1 (7 tools)\n\nProvider Plugins:\n  Context Engine: lcm\n```\n\nFor source checkouts, `lcm_status`, `\u002Flcm status`, `lcm_doctor`, and\n`\u002Flcm doctor` also report the loaded plugin path and best-effort git identity:\n`plugin_git_commit`, `plugin_git_branch`, and `plugin_git_dirty`.\n\n## Troubleshooting\n\n### `hermes plugins` shows `lcm (not found)` but LCM tools exist\n\nIf `plugins.enabled` contains `hermes-lcm`, `context.engine: lcm` is set, and\nthe runtime exposes LCM tools, LCM is loaded. The `lcm (not found)` line is a\nHermes host discovery\u002Fstatus mismatch, not an LCM storage or compaction failure.\n\n### `\u002Flcm status` looks unbound after restart\n\nAfter a fresh Hermes restart, `\u002Flcm status` may show `session_id: (unbound)` or\n`threshold_tokens: (uninitialized)`. Send one normal Hermes message first, then\nrun `lcm_status` or `\u002Flcm status` again for live per-session fields.\n\n## Configuration\n\nMost installs only need `plugins.enabled` and `context.engine: lcm`. Useful\nenvironment variables:\n\n| Variable | Default | Use |\n|----------|---------|-----|\n| `LCM_CONTEXT_THRESHOLD` | `0.75` | Fraction of the context window that triggers LCM compaction |\n| `LCM_FRESH_TAIL_COUNT` | `64` | Recent messages protected from compaction |\n| `LCM_LEAF_CHUNK_TOKENS` | `20000` | Token floor for leaf compaction chunks |\n| `LCM_NEW_SESSION_RETAIN_DEPTH` | `2` | DAG depth retained after manual `\u002Fnew` (`-1` all, `0` none) |\n| `LCM_IGNORE_SESSION_PATTERNS` | empty | Comma-separated session globs excluded from LCM storage |\n| `LCM_STATELESS_SESSION_PATTERNS` | empty | Comma-separated session globs kept read-only |\n| `LCM_IGNORE_MESSAGE_PATTERNS` | empty | Comma-separated regex patterns; matching message content (plain text, extracted text parts for structured\u002Fmultimodal content, or normalized JSON fallback when no text parts exist) is excluded from LCM storage |\n| `LCM_LARGE_OUTPUT_EXTERNALIZATION_ENABLED` | `false` | Store oversized tool outputs in plugin-managed JSON files |\n| `LCM_LARGE_OUTPUT_EXTERNALIZATION_THRESHOLD_CHARS` | `12000` | Externalization threshold for tool output text |\n| `LCM_LARGE_OUTPUT_TRANSCRIPT_GC_ENABLED` | `false` | Rewrite already-externalized summarized tool rows to compact placeholders |\n| `LCM_SUMMARY_MODEL` | auxiliary | Override summarization model |\n| `LCM_EXPANSION_MODEL` | summary model \u002F auxiliary | Override `lcm_expand_query` synthesis model |\n| `LCM_EXPANSION_CONTEXT_TOKENS` | `32000` | Context budget used by the auxiliary LLM for `lcm_expand_query` |\n| `LCM_SUMMARY_TIMEOUT_MS` | `60000` | Timeout for one summarization call |\n| `LCM_EXPANSION_TIMEOUT_MS` | `120000` | Timeout for one `lcm_expand_query` synthesis call |\n| `LCM_DATABASE_PATH` | auto | SQLite database path, profile-scoped by default |\n| `LCM_ENABLE_SLASH_COMMAND` | `false` | Enable the optional `\u002Flcm` operator command surface |\n| `LCM_DOCTOR_CLEAN_APPLY_ENABLED` | `false` | Permit destructive `\u002Flcm doctor clean apply` in trusted operator contexts |\n\nAdvanced compaction, assembly, and extraction knobs are defined in `config.py`.\n\n### Threshold ownership\n\nWhen `context.engine: lcm` is active, `LCM_CONTEXT_THRESHOLD` is the compaction\nthreshold LCM uses. Hermes core `compression.threshold` belongs to the built-in\ncompressor. Hermes core `compression.enabled` is still the global gate that\nallows compaction, so leave it enabled when using LCM.\n\nIf startup\u002Fstatus output shows a host-side compression percentage that disagrees\nwith LCM, trust live LCM status after a normal message has initialized the\nsession.\n\n### Session pattern syntax\n\nPattern matching checks multiple keys: raw `session_id`, `platform`, and\n`platform:session_id`.\n\n- `*` matches within one colon-delimited segment\n- `**` can span across colons\n\nExample: `cron:*` can match Hermes cron sessions, while exact raw session IDs\nstill work.\n\n### Noise suppression\n\nLCM offers two layers of noise filtering, sized to two different shapes of\nnoise:\n\n- **Session-level filters** (`LCM_IGNORE_SESSION_PATTERNS`,\n  `LCM_STATELESS_SESSION_PATTERNS`) catch the case where the noisy traffic\n  arrives as its own session or platform, for example a dedicated `cron:*`\n  session. Match keys cover the session id, the platform, and\n  `platform:session_id`.\n- **Message-level patterns** (`LCM_IGNORE_MESSAGE_PATTERNS`) catch the case\n  where cron alerts or other noise are injected into a normal Telegram or\n  WhatsApp conversation as ordinary user-visible messages. From LCM's\n  perspective the session\u002Fplatform is `telegram` or `whatsapp`, not `cron`,\n  so only the message content is distinctive.\n\nMessage-level patterns are Python regex strings, comma-separated, compiled\nonce at engine start. They run against plain message text. For structured\nmultimodal payloads, LCM matches against concatenated text parts first, so\nanchored patterns bind to the text an operator sees. If a structured payload\ncontains no text parts, matching falls back to the normalized JSON form that\nLCM would have written to the store. Matching messages are skipped before\nstorage, so new matching rows do not enter the messages table or FTS index.\nFiltering is role-agnostic by default, since cron alerts can be re-emitted\nunder any role depending on the gateway.\n\nExample operator config:\n\n```\nLCM_IGNORE_MESSAGE_PATTERNS=^Cronjob Response:,^>>>Cronjob Response\u003C\u003C\u003C:\n```\n\nInvalid regex entries are logged at warning level and dropped; the\nsurviving patterns in the same list still take effect, so a misconfigured\nentry never crashes ingest. Pattern matching uses a 50 ms per-pattern timeout\nwhen the optional `regex` package is installed. If `regex` is not installed,\nLCM logs a warning and disables message-level regex filtering rather than\nrunning unbounded stdlib `re` matches in the ingest path.\n\nOne operator-facing limitation to know about:\n\n- **Compaction-window edge.** The filter runs at ingest time. When a\n  matching message is part of the chunk being summarized in the same\n  turn it arrived, the message's text may appear inside the resulting\n  summary node text. In long-running sessions where compaction triggers\n  every several dozen turns, this can affect multiple summary nodes per\n  day rather than only happening rarely. The summary node's `source_ids`\n  will not reference the filtered message (it was never written to the\n  store), so DAG lineage stays clean; only the serialized summary text\n  can carry it. Closing this window is tracked as follow-up work.\n\n`lcm_status` surfaces the full filter contract under `session_filters`, including\n`ignore_session_patterns`, `stateless_session_patterns`, `ignore_message_patterns`,\ntheir `*_source` fields (`default` or `env`), the current session's `ignored` and\n`stateless` booleans, and a process-lifetime `ignored_message_count` so operators\ncan confirm their patterns are loaded and watch how often message filters fire.\nThe counter resets on engine restart.\n\n### Large tool-output handling\n\nExternalization is opt-in. When enabled, oversized tool results are written to\nplugin-managed JSON files and referenced from summaries. They remain inspectable\nlater through `lcm_describe(externalized_ref=...)` and\n`lcm_expand(externalized_ref=...)`.\n\nTranscript GC is separate and also opt-in. It only rewrites already-externalized,\nalready-summarized tool-role rows to compact placeholders. It keeps the same\n`store_id`, keeps payload files, skips pinned messages, and preserves lossless\nrecovery through `externalized_ref`. After GC, `lcm_grep` will not match the\noriginal giant tool blob text directly; search summaries or refs instead.\n\n## Agent Tools\n\nUse these tools for current-session recall after compaction. Use `session_search`\nfor earlier separate sessions or broad cross-session history.\n\n| Tool | Use |\n|------|-----|\n| `lcm_grep` | Search current-session raw messages and summaries. Opt into `session_scope='all'` or `session_scope='session'` (with `session_id`) for bounded archive recovery over rows already present in `lcm.db`, including externally backfilled rows that may carry source strings such as `openclaw-lcm:*`; broader scopes return raw-message hits only. Use `session_search` for earlier separate sessions or broad cross-session recall. |\n| `lcm_load_session` | Load one ordered raw-message transcript page for an explicit `session_id`. This is not search: it returns raw rows in `store_id` order, bounded by `limit`, with per-message content bounded by `max_content_chars`, and continues with `after_store_id` from `next_cursor`. |\n| `lcm_describe` | Inspect the current-session DAG or preview an `externalized_ref` without loading full content. |\n| `lcm_expand` | Recover source messages, child summaries, or externalized payloads with pagination. Use `store_id` to fetch a single raw message regardless of session, suitable for drilling into a cross-session `lcm_grep` result. |\n| `lcm_expand_query` | Answer a question using expanded current-session LCM context while returning a bounded answer. |\n| `lcm_status` | Show runtime health, context pressure, config, source lineage, and lifecycle stats. |\n| `lcm_doctor` | Run database, FTS, lifecycle, config, and context-pressure diagnostics. |\n\n### Retrieval contract\n\nLCM retrieval tools default to current-session scope. `lcm_grep` accepts\n`session_scope='all'` or `session_scope='session'` as an explicit opt-in for\nbounded archive search over rows already present in `lcm.db` (raw-message hits\nonly). Once a session id is known, `lcm_load_session` can enumerate that session's\nraw transcript in chronological `store_id` pages without a search query. Use\nHermes `session_search` for broad cross-session history outside the LCM database.\n\nWithin the current session, `source` filters raw rows directly and filters\nsummary nodes by descendant raw-message source lineage. `unknown` is a real\nsource value, not a wildcard. Legacy blank-source rows are treated as `unknown`.\n\nCarried-over summary nodes can become current-session content after `\u002Fnew`, but\ntheir source eligibility still comes from the descendant raw messages.\n\n### Lossless raw recovery contract\n\nTool responses are bounded so one retrieval call cannot flood the main context.\nLossless recovery means raw content is stored with stable source lineage and can\nbe recovered in deterministic pages.\n\n- `lcm_expand(node_id=...)` pages immediate sources with `source_offset` and `source_limit`\n- `lcm_load_session(session_id=...)` pages ordered raw session rows with `after_store_id` and `next_cursor`; each row includes bounded content plus truncation metadata, and large individual rows can be recovered with `lcm_expand(store_id=...)` using `content_offset`\n- oversized raw messages continue with `content_offset`\n- `lcm_expand(externalized_ref=...)` pages payload content with `content_offset`\n- `lcm_expand_query` uses `context_max_tokens` for auxiliary context and reports truncation\u002Fpagination hints when needed\n\n### lossless-claw\u002FOpenClaw import utility\n\n`hermes-lcm` includes an opt-in operator script for backfilling raw message rows from a lossless-claw\u002FOpenClaw LCM SQLite database into the local hermes-lcm SQLite store:\n\n```bash\npython scripts\u002Fimport_lossless_claw.py \\\n  --source-db ~\u002F.openclaw\u002Fpath\u002Fto\u002Flcm.db \\\n  --target-db ~\u002F.hermes\u002Flcm.db \\\n  --agent sammy\n```\n\nThe script is intentionally conservative:\n\n- dry-run is the default; pass `--apply` to write\n- run it against an explicit target DB path, preferably while Hermes is stopped for that profile\n- writes create a timestamped target DB backup first when the target already exists\n- only raw messages are imported; summary DAG import is out of scope\n- imported rows keep explicit provenance in `session_id` and `source`, for example `openclaw-lcm:agent:sammy:\u003Csource-session>`\n- the default provenance identity is the concrete source `conversations.session_id`, preserving source session boundaries even when many conversations share one `session_key`\n- pass `--session-identity session_key` only when you intentionally want conversations with the same source session key grouped into one imported LCM session\n- reruns are idempotent for the same `--import-id`; the default `import_id` is path-derived, so pass a stable `--import-id` if you may import the same copied DB from different paths\n- changing `--agent`, `--namespace`, or `--session-identity` under the same `--import-id` is treated as the same import and will skip already-tracked source messages; use a new `--import-id` for a different mapping\n- no OpenClaw config or separate secret tables are imported, but raw transcripts and tool payloads are imported and may contain sensitive user data\n\nThis is a local archive migration path. It does not make LCM a general memory provider, and it does not change the current-session retrieval contract for agent tools.\n\n## Slash Commands\n\nSlash commands are disabled by default. Enable them only in trusted operator\ncontexts:\n\n```bash\nexport LCM_ENABLE_SLASH_COMMAND=1\n```\n\nAvailable commands:\n\n- `\u002Flcm` or `\u002Flcm status` - current runtime\u002Fsession status\n- `\u002Flcm doctor` - read-only health checks\n- `\u002Flcm doctor clean` - read-only scan for obvious junk\u002Fnoise session candidates\n- `\u002Flcm doctor clean apply` - backup-first cleanup for safe pattern-matched candidates; requires `LCM_DOCTOR_CLEAN_APPLY_ENABLED=true`\n- `\u002Flcm doctor repair` - read-only SQLite\u002FFTS repair diagnostics\n- `\u002Flcm doctor repair apply` - backup-first SQLite\u002FFTS repair\n- `\u002Flcm doctor source` - read-only scan for legacy blank-source rows\n- `\u002Flcm doctor source apply` - backup-first normalization of legacy blank-source rows to `unknown`\n- `\u002Flcm doctor retention` - read-only retention analysis\n- `\u002Flcm backup` - timestamped SQLite backup\n- `\u002Flcm help` - command help\n\nApply paths are intentionally narrow and backup-first. Start with diagnostics\nbefore cleanup or repair.\n\n## How It Works\n\n1. **Ingest** - persist each message in SQLite with FTS metadata\n2. **Compact** - summarize older messages outside the fresh tail into D0 leaf nodes\n3. **Condense** - merge same-depth nodes into higher-depth summaries\n4. **Escalate** - shrink oversize summaries from detailed to bullets to deterministic truncate\n5. **Assemble** - combine system prompt, highest-depth summaries, and fresh tail\n6. **Retrieve** - use LCM tools to drill into compacted history or synthesize from expanded context\n\n## Development\n\nImportant files:\n\n```text\nplugin.yaml      manifest\n__init__.py      plugin registration and optional slash-command registration\nengine.py        LCMEngine main orchestrator\nstore.py         SQLite message store and FTS\ndag.py           summary DAG and FTS\nconfig.py        env var defaults and overrides\ncommand.py       \u002Flcm command handlers\ntools.py         lcm_grep, lcm_load_session, lcm_describe, lcm_expand, lcm_expand_query\nschemas.py       tool schemas shown to the model\ntests\u002F           standalone pytest coverage\n```\n\nRun tests:\n\n```bash\npip install pytest\npython -m pytest tests\u002F -v\n```\n\nNo Hermes Agent checkout is required for the test suite; tests include a\nlightweight ABC stub.\n\n## Contributing\n\nIssues and PRs welcome. Bug fixes and correctness improvements are highest\npriority. New features should be scoped, backwards-compatible, and tested.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for branch, validation, and PR guidance.\nSee the [releases page](https:\u002F\u002Fgithub.com\u002Fstephenschoettler\u002Fhermes-lcm\u002Freleases) for changelogs.\n\n## License\n\nMIT\n","hermes-lcm 是一个为 Hermes Agent 设计的无损上下文管理插件，确保在对话过程中不会丢失任何消息。其核心功能包括使用 SQLite 存储原始消息、构建层次化的摘要 DAG 以压缩旧上下文、提供有界恢复工具以便于回溯具体材料等。技术特点上，该插件通过维护本地存储和DAG结构支持实时检索与深度探索，并提供了多种命令行工具增强用户体验。适用于需要高精度上下文保留及高效历史信息查询的应用场景，如复杂对话系统或需要长期记忆支持的人工智能助手。",2,"2026-06-11 02:41:10","CREATED_QUERY"]