hermes-plugin-qdrant-memory 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/PKG-INFO +55 -32
  2. hermes_plugin_qdrant_memory-0.2.0/hermes_plugin_qdrant_memory.egg-info/PKG-INFO → hermes_plugin_qdrant_memory-0.2.2/README.md +51 -49
  3. hermes_plugin_qdrant_memory-0.2.0/README.md → hermes_plugin_qdrant_memory-0.2.2/hermes_plugin_qdrant_memory.egg-info/PKG-INFO +72 -30
  4. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/hermes_plugin_qdrant_memory.egg-info/SOURCES.txt +1 -0
  5. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/hermes_plugin_qdrant_memory.egg-info/requires.txt +1 -1
  6. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/pyproject.toml +5 -3
  7. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/dedupe.py +8 -0
  8. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/mcp_server.py +19 -6
  9. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/plugin.yaml +1 -1
  10. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/runtime.py +3 -5
  11. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/version.py +1 -1
  12. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_ci_gate.py +8 -5
  13. hermes_plugin_qdrant_memory-0.2.2/tests/test_explicit_dedupe.py +80 -0
  14. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_manifest_contract.py +7 -0
  15. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/LICENSE +0 -0
  16. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/MANIFEST.in +0 -0
  17. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/hermes_plugin_qdrant_memory.egg-info/dependency_links.txt +0 -0
  18. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/hermes_plugin_qdrant_memory.egg-info/entry_points.txt +0 -0
  19. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/hermes_plugin_qdrant_memory.egg-info/top_level.txt +0 -0
  20. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/__init__.py +0 -0
  21. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/cli.py +0 -0
  22. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/config.py +0 -0
  23. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/config_schema.py +0 -0
  24. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/embedding.py +0 -0
  25. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/extraction.py +0 -0
  26. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/inventory.py +0 -0
  27. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/ledger.py +0 -0
  28. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/migration.py +0 -0
  29. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/models.py +0 -0
  30. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/ownership.py +0 -0
  31. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/progress.py +0 -0
  32. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/provider.py +0 -0
  33. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/qdrant_store.py +0 -0
  34. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/reset.py +0 -0
  35. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/retry.py +0 -0
  36. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/standalone.py +0 -0
  37. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/qdrant_memory/tools.py +0 -0
  38. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/scripts/demo.sh +0 -0
  39. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/setup.cfg +0 -0
  40. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_backend_contracts.py +0 -0
  41. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_bot_scope.py +0 -0
  42. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_bounded_recovery.py +0 -0
  43. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_checkpoint_lifecycle.py +0 -0
  44. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_cli_output.py +0 -0
  45. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_config_embedding.py +0 -0
  46. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_deletion_ordering.py +0 -0
  47. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_discovery_cli.py +0 -0
  48. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_host_lifecycle.py +0 -0
  49. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_input_validation.py +0 -0
  50. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_inventory_export.py +0 -0
  51. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_live_metrics.py +0 -0
  52. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_maintenance.py +0 -0
  53. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_migration.py +0 -0
  54. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_migration_endpoints.py +0 -0
  55. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_migration_progress.py +0 -0
  56. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_migration_resume.py +0 -0
  57. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_migration_superseded.py +0 -0
  58. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_profile_scope.py +0 -0
  59. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_provider.py +0 -0
  60. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_recovery_benchmark.py +0 -0
  61. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_recovery_evaluation.py +0 -0
  62. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_remote_qdrant.py +0 -0
  63. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_reset_recovery.py +0 -0
  64. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_retention.py +0 -0
  65. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_review_regressions.py +0 -0
  66. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_session_provenance.py +0 -0
  67. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_setup_contract.py +0 -0
  68. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_shutdown_contract.py +0 -0
  69. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_store_runtime.py +0 -0
  70. {hermes_plugin_qdrant_memory-0.2.0 → hermes_plugin_qdrant_memory-0.2.2}/tests/test_writer_ownership.py +0 -0
@@ -1,12 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hermes-plugin-qdrant-memory
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Durable Qdrant memory for Hermes, standalone CLI and local MCP
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/cnkang/hermes-plugin-qdrant-memory
7
7
  Project-URL: Repository, https://github.com/cnkang/hermes-plugin-qdrant-memory
8
8
  Project-URL: Issues, https://github.com/cnkang/hermes-plugin-qdrant-memory/issues
9
9
  Project-URL: Changelog, https://github.com/cnkang/hermes-plugin-qdrant-memory/blob/main/CHANGELOG.md
10
+ Project-URL: MCP, https://github.com/cnkang/qdrant-memory-mcp
11
+ Project-URL: Skill, https://github.com/cnkang/skills/tree/main/qdrant-memory
10
12
  Requires-Python: >=3.11
11
13
  Description-Content-Type: text/markdown
12
14
  License-File: LICENSE
@@ -14,7 +16,7 @@ Requires-Dist: qdrant-client<2,>=1.15
14
16
  Requires-Dist: httpx<1,>=0.28
15
17
  Requires-Dist: portalocker<4,>=3.2
16
18
  Provides-Extra: mcp
17
- Requires-Dist: mcp<2,>=1.30; extra == "mcp"
19
+ Requires-Dist: mcp<3,>=1.30; extra == "mcp"
18
20
  Dynamic: license-file
19
21
 
20
22
  # Qdrant Memory for Hermes
@@ -26,12 +28,12 @@ Dynamic: license-file
26
28
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
27
29
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
28
30
 
29
- > **v0.1.0 — limited technical preview.** Give Hermes Agent durable, private
30
- > long-term memory: your conversations become searchable memories in your own
31
- > Qdrant store, with recall, update and scoped deletion under your control.
32
- > The preview supports one writer per destination, with the documented
33
- > durability limits. See the [validation record](docs/validation.md) for the
34
- > current certification.
31
+ > **Runtime 0.2.2** supports MCP Python SDK 1.x and 2.x for the standalone
32
+ > CLI/MCP. MCP distribution 0.1.2 pins Runtime 0.2.2. Explicit writes retain
33
+ > canonical-text deduplication without an LLM.
34
+ > The Hermes Plugin Catalog has its own reviewed immutable pin. A PyPI release
35
+ > does not upgrade it. One writer per destination remains required.
36
+ > See [release notes](docs/releases/0.2.2.md) and [validation](docs/validation.md).
35
37
 
36
38
  [English](README.md) | [简体中文](README.zh-CN.md)
37
39
 
@@ -45,6 +47,16 @@ as a community-maintained plugin.
45
47
 
46
48
  ## How it works
47
49
 
50
+ For non-Hermes users, install in a private Python 3.11+
51
+ environment: `pip install 'hermes-plugin-qdrant-memory[mcp]==0.2.2'` (omit `[mcp]`
52
+ for CLI only). `qdrant-memory` is the CLI and `mcp` is its server subcommand.
53
+ Standalone does not require Hermes. See [setup and quick start](docs/standalone.md).
54
+ MCP-focused agents may instead use the [metadata-only distribution](https://github.com/cnkang/qdrant-memory-mcp).
55
+ Public releases: [Runtime on PyPI](https://pypi.org/project/hermes-plugin-qdrant-memory/)
56
+ and [MCP distribution on PyPI](https://pypi.org/project/qdrant-memory-mcp/).
57
+ For native Hermes use its official plugin installation mechanism below. Before
58
+ an authorized upgrade, verify the same profile, collection and embedding pipeline.
59
+
48
60
  ```mermaid
49
61
  flowchart LR
50
62
  turn["Hermes turn"] --> admit["Provider callback"]
@@ -66,7 +78,7 @@ flowchart LR
66
78
  - A fully local setup works: embedded Qdrant and local Ollama keep the data
67
79
  on your machine.
68
80
  - Data locality follows the destination. Extraction uses the LLM configured
69
- in Hermes, and embedding uses the configured embedding service; either can
81
+ in Hermes, and embedding uses the configured embedding service. Either can
70
82
  be remote. See [Data flow and privacy](#data-flow-and-privacy).
71
83
  - Not supported in this preview: multi-writer Server/Cloud, hybrid search and
72
84
  a web UI.
@@ -75,9 +87,9 @@ flowchart LR
75
87
 
76
88
  The plugin primarily provides Hermes Agent's durable memory integration. As an
77
89
  additional capability, its shared backend can serve other agents through the
78
- Runtime 0.2.0's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
79
- interface, without requiring Hermes. These interfaces support explicit scoped
80
- memory operations and maintenance/migration; automatic extraction and prefetch
90
+ Runtime's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
91
+ interface. Standalone use does not require Hermes. These interfaces support explicit scoped
92
+ memory operations and maintenance/migration. Automatic extraction and prefetch
81
93
  remain Hermes lifecycle features. See [standalone setup](docs/standalone.md) for
82
94
  configuration and boundaries, and the agent-neutral
83
95
  [qdrant-memory skill](https://github.com/cnkang/skills/tree/main/qdrant-memory)
@@ -85,19 +97,20 @@ for multi-agent installation and usage guidance.
85
97
 
86
98
  The optional MCP server exposes four scoped tools: `qdrant_memory_search`,
87
99
  `qdrant_memory_add`, `qdrant_memory_update` and `qdrant_memory_delete`. Compatible
88
- clients launch it as a local stdio process and discover/call those tools;
89
- maintenance and migration use the operator CLI. See the
100
+ clients launch it as a local stdio process and discover/call those tools.
101
+ Maintenance and migration use the operator CLI. See the
90
102
  [MCP capabilities and connection guide](docs/mcp.md) for setup, shared-memory
91
- boundaries and the verified agent matrix. This is a tested source preview;
92
- the published v0.1.0 plugin release does not include the MCP entrypoint.
103
+ boundaries and the verified agent matrix. Runtime 0.2.0 published these interfaces.
104
+ The published v0.1.0 plugin release does not include the MCP entrypoint.
93
105
 
94
106
  ## See it in action
95
107
 
96
108
  ![Terminal recording: install the plugin, store a preference, and recall it in a new session](https://raw.githubusercontent.com/cnkang/hermes-plugin-qdrant-memory/main/assets/demo.gif)
97
109
 
98
- The recording shows the full flow on a clean profile: install from the
99
- official Hermes Plugin Catalog, teach a preference, wait for the background
100
- extraction, recall it in a new session, and clean up the demo scope. All steps
110
+ The recording shows the full flow on a clean profile. It installs from the
111
+ official Hermes Plugin Catalog, teaches a preference, and waits for the
112
+ background extraction. Then it recalls the preference in a new session and
113
+ cleans up the demo scope. All steps
101
114
  run in embedded mode. `scripts/demo.sh` reproduces the same walkthrough with
102
115
  the pinned release.
103
116
 
@@ -128,14 +141,18 @@ The install resolves the catalog's reviewed commit and prints the entry's declar
128
141
  tools, requirements and known issues before it clones anything.
129
142
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
130
143
 
131
- **Version-locked: install the pinned release through the Hermes CLI.**
144
+ **Version-locked: install the verified Runtime 0.2.2 source through the Hermes CLI.**
145
+
146
+ This immutable source revision includes MCP SDK 1.x/2.x support and the
147
+ explicit-write fix. It does not
148
+ depend on a PyPI release or a catalog update.
132
149
 
133
150
  ```bash
134
151
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
135
152
  ollama pull qwen3-embedding:4b
136
153
  # Start Ollama if it is not running. Keep its service available.
137
154
  hermes plugins install https://github.com/cnkang/hermes-plugin-qdrant-memory \
138
- --ref 5babd3e4b4c022aad38a2b6db82cbd5b601f1ef8 # v0.1.0 release commit
155
+ --ref 2fd43ad2259bd98d039394b8b714e5e89b5ab3a8 # verified Runtime 0.2.2 source
139
156
  hermes plugins enable qdrant-memory
140
157
  hermes memory setup
141
158
  hermes config set memory.provider qdrant-memory
@@ -144,12 +161,15 @@ hermes qdrant-memory init
144
161
 
145
162
  For this version-locked form, `hermes plugins install` checks out the given ref, and
146
163
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
147
- `--ref` takes a full 40-character commit SHA; the
164
+ `--ref` takes a full 40-character commit SHA. The
148
165
  [releases page](https://github.com/cnkang/hermes-plugin-qdrant-memory/releases)
149
166
  lists the release commits. Do not pip-install into a managed Hermes
150
167
  environment. Omit `--ref` to track `main`, the development line.
151
168
 
152
- **Alternative: clone the release tag manually.**
169
+ **Historical release: clone a published tag manually.**
170
+
171
+ The v0.1.0 tag below predates the standalone interfaces and the explicit-write
172
+ fix. Use the pinned source above for Runtime 0.2.2.
153
173
 
154
174
  ```bash
155
175
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
@@ -178,7 +198,7 @@ import and registration. For development installs, see
178
198
  [development setup](docs/development.md).
179
199
 
180
200
  The release notes, supported Hermes/Python versions and the current
181
- limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md);
201
+ limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md).
182
202
  `main` stays the development line.
183
203
 
184
204
  For local development, symlink the repository into a disposable profile:
@@ -223,7 +243,10 @@ mappings) stays in the local Hermes home. Back up that home together with your
223
243
  Qdrant data. Migration from Mem0 is one-way, and version 0.1 has no vector
224
244
  reuse or hybrid retrieval.
225
245
 
226
- Similarity is a candidate-selection signal, not proof that two facts are identical.
246
+ Explicit add deduplicates only canonical text in the same scope and never calls
247
+ an LLM. Different texts are saved regardless of similarity. Update replaces only
248
+ the authorized exact ID. For automatic Hermes extraction,
249
+ similarity is a candidate-selection signal, not proof that two facts are identical.
227
250
  `SAME` skips, `SUPERSEDES` updates, `CONFLICT` creates a linked record, and
228
251
  `UNRELATED` adds a record. Migration uses source IDs instead of semantic merging.
229
252
 
@@ -305,7 +328,7 @@ default profile scope uses an agent (`hermes`). Pass `--agent hermes` to
305
328
  target it, or `--all-agents` to delete every agent scope of the user. Without
306
329
  `--all-agents`, the result reports `other_agent_scopes` when other agent
307
330
  scopes still hold memories. A literal `*` is an ordinary agent id, not a
308
- wildcard. `--dry-run` previews the per-scope counts;
331
+ wildcard. `--dry-run` previews the per-scope counts.
309
332
  `--confirm` authorizes deletion. An interrupted deletion fails closed: other
310
333
  commands and provider startup return `scope_delete_recovery_required` until
311
334
  `delete-all --confirm` resumes it. Deletion intents are versioned and explicit.
@@ -313,8 +336,8 @@ A pre-versioned intent cannot show whether `*` is a single agent or all agents,
313
336
  so the plugin refuses it (`scope_delete_legacy_intent`). Resolve it with
314
337
  `--resolve-legacy single_agent`, or with `--resolve-legacy all_agents` when the
315
338
  record's agent is `*` or absent. Turns admitted after the deletion can still
316
- add new memories; everything admitted before it — stored memories, prepared
317
- writes and unprocessed turn events — is removed or invalidated. `list`,
339
+ add new memories. Everything admitted before it, including stored memories, prepared
340
+ writes and unprocessed turn events, is removed or invalidated. `list`,
318
341
  `export` and `delete-all` do not require the embedding service.
319
342
 
320
343
  ## Data flow and privacy
@@ -325,10 +348,10 @@ carries and where it can go:
325
348
  | Component | Carries | Destination |
326
349
  | --- | --- | --- |
327
350
  | Chat / agent turns | Your conversation | The model configured for the Hermes session |
328
- | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model — with a cloud model, turn text reaches that cloud |
329
- | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction; runs only when similarity selects a review candidate |
351
+ | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model. With a cloud model, turn text reaches that cloud |
352
+ | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction. It runs only when similarity selects a review candidate |
330
353
  | Embeddings (`embedding`) | Memory and search text | Ollama by default, or your OpenAI-compatible endpoint |
331
- | Storage (`qdrant`) | Memory payloads and provenance; the ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
354
+ | Storage (`qdrant`) | Memory payloads and provenance. The ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
332
355
 
333
356
  Three common deployments:
334
357
 
@@ -483,7 +506,7 @@ contains `memories` objects with `id`/`text`, and `queries` with `query`/`releva
483
506
  The default fixture now contains 24 labeled queries, including Chinese, English,
484
507
  mixed-language and hard-negative cases. The
485
508
  [original 12-query recording](docs/retrieval-baseline.json) is a historical
486
- synthetic regression baseline; rerun the current fixture for current measurements.
509
+ synthetic regression baseline. Rerun the current fixture for current measurements.
487
510
  These fixtures do not establish production recall quality. See the [recovery benchmark](docs/recovery-benchmark.md)
488
511
  for a separate synthetic replay and retained-storage measurement.
489
512
 
@@ -1,22 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: hermes-plugin-qdrant-memory
3
- Version: 0.2.0
4
- Summary: Durable Qdrant memory for Hermes, standalone CLI and local MCP
5
- License-Expression: MIT
6
- Project-URL: Homepage, https://github.com/cnkang/hermes-plugin-qdrant-memory
7
- Project-URL: Repository, https://github.com/cnkang/hermes-plugin-qdrant-memory
8
- Project-URL: Issues, https://github.com/cnkang/hermes-plugin-qdrant-memory/issues
9
- Project-URL: Changelog, https://github.com/cnkang/hermes-plugin-qdrant-memory/blob/main/CHANGELOG.md
10
- Requires-Python: >=3.11
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: qdrant-client<2,>=1.15
14
- Requires-Dist: httpx<1,>=0.28
15
- Requires-Dist: portalocker<4,>=3.2
16
- Provides-Extra: mcp
17
- Requires-Dist: mcp<2,>=1.30; extra == "mcp"
18
- Dynamic: license-file
19
-
20
1
  # Qdrant Memory for Hermes
21
2
 
22
3
  [![CI](https://github.com/cnkang/hermes-plugin-qdrant-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/cnkang/hermes-plugin-qdrant-memory/actions/workflows/ci.yml)
@@ -26,12 +7,12 @@ Dynamic: license-file
26
7
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
27
8
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
28
9
 
29
- > **v0.1.0 — limited technical preview.** Give Hermes Agent durable, private
30
- > long-term memory: your conversations become searchable memories in your own
31
- > Qdrant store, with recall, update and scoped deletion under your control.
32
- > The preview supports one writer per destination, with the documented
33
- > durability limits. See the [validation record](docs/validation.md) for the
34
- > current certification.
10
+ > **Runtime 0.2.2** supports MCP Python SDK 1.x and 2.x for the standalone
11
+ > CLI/MCP. MCP distribution 0.1.2 pins Runtime 0.2.2. Explicit writes retain
12
+ > canonical-text deduplication without an LLM.
13
+ > The Hermes Plugin Catalog has its own reviewed immutable pin. A PyPI release
14
+ > does not upgrade it. One writer per destination remains required.
15
+ > See [release notes](docs/releases/0.2.2.md) and [validation](docs/validation.md).
35
16
 
36
17
  [English](README.md) | [简体中文](README.zh-CN.md)
37
18
 
@@ -45,6 +26,16 @@ as a community-maintained plugin.
45
26
 
46
27
  ## How it works
47
28
 
29
+ For non-Hermes users, install in a private Python 3.11+
30
+ environment: `pip install 'hermes-plugin-qdrant-memory[mcp]==0.2.2'` (omit `[mcp]`
31
+ for CLI only). `qdrant-memory` is the CLI and `mcp` is its server subcommand.
32
+ Standalone does not require Hermes. See [setup and quick start](docs/standalone.md).
33
+ MCP-focused agents may instead use the [metadata-only distribution](https://github.com/cnkang/qdrant-memory-mcp).
34
+ Public releases: [Runtime on PyPI](https://pypi.org/project/hermes-plugin-qdrant-memory/)
35
+ and [MCP distribution on PyPI](https://pypi.org/project/qdrant-memory-mcp/).
36
+ For native Hermes use its official plugin installation mechanism below. Before
37
+ an authorized upgrade, verify the same profile, collection and embedding pipeline.
38
+
48
39
  ```mermaid
49
40
  flowchart LR
50
41
  turn["Hermes turn"] --> admit["Provider callback"]
@@ -66,7 +57,7 @@ flowchart LR
66
57
  - A fully local setup works: embedded Qdrant and local Ollama keep the data
67
58
  on your machine.
68
59
  - Data locality follows the destination. Extraction uses the LLM configured
69
- in Hermes, and embedding uses the configured embedding service; either can
60
+ in Hermes, and embedding uses the configured embedding service. Either can
70
61
  be remote. See [Data flow and privacy](#data-flow-and-privacy).
71
62
  - Not supported in this preview: multi-writer Server/Cloud, hybrid search and
72
63
  a web UI.
@@ -75,9 +66,9 @@ flowchart LR
75
66
 
76
67
  The plugin primarily provides Hermes Agent's durable memory integration. As an
77
68
  additional capability, its shared backend can serve other agents through the
78
- Runtime 0.2.0's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
79
- interface, without requiring Hermes. These interfaces support explicit scoped
80
- memory operations and maintenance/migration; automatic extraction and prefetch
69
+ Runtime's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
70
+ interface. Standalone use does not require Hermes. These interfaces support explicit scoped
71
+ memory operations and maintenance/migration. Automatic extraction and prefetch
81
72
  remain Hermes lifecycle features. See [standalone setup](docs/standalone.md) for
82
73
  configuration and boundaries, and the agent-neutral
83
74
  [qdrant-memory skill](https://github.com/cnkang/skills/tree/main/qdrant-memory)
@@ -85,19 +76,20 @@ for multi-agent installation and usage guidance.
85
76
 
86
77
  The optional MCP server exposes four scoped tools: `qdrant_memory_search`,
87
78
  `qdrant_memory_add`, `qdrant_memory_update` and `qdrant_memory_delete`. Compatible
88
- clients launch it as a local stdio process and discover/call those tools;
89
- maintenance and migration use the operator CLI. See the
79
+ clients launch it as a local stdio process and discover/call those tools.
80
+ Maintenance and migration use the operator CLI. See the
90
81
  [MCP capabilities and connection guide](docs/mcp.md) for setup, shared-memory
91
- boundaries and the verified agent matrix. This is a tested source preview;
92
- the published v0.1.0 plugin release does not include the MCP entrypoint.
82
+ boundaries and the verified agent matrix. Runtime 0.2.0 published these interfaces.
83
+ The published v0.1.0 plugin release does not include the MCP entrypoint.
93
84
 
94
85
  ## See it in action
95
86
 
96
87
  ![Terminal recording: install the plugin, store a preference, and recall it in a new session](https://raw.githubusercontent.com/cnkang/hermes-plugin-qdrant-memory/main/assets/demo.gif)
97
88
 
98
- The recording shows the full flow on a clean profile: install from the
99
- official Hermes Plugin Catalog, teach a preference, wait for the background
100
- extraction, recall it in a new session, and clean up the demo scope. All steps
89
+ The recording shows the full flow on a clean profile. It installs from the
90
+ official Hermes Plugin Catalog, teaches a preference, and waits for the
91
+ background extraction. Then it recalls the preference in a new session and
92
+ cleans up the demo scope. All steps
101
93
  run in embedded mode. `scripts/demo.sh` reproduces the same walkthrough with
102
94
  the pinned release.
103
95
 
@@ -128,14 +120,18 @@ The install resolves the catalog's reviewed commit and prints the entry's declar
128
120
  tools, requirements and known issues before it clones anything.
129
121
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
130
122
 
131
- **Version-locked: install the pinned release through the Hermes CLI.**
123
+ **Version-locked: install the verified Runtime 0.2.2 source through the Hermes CLI.**
124
+
125
+ This immutable source revision includes MCP SDK 1.x/2.x support and the
126
+ explicit-write fix. It does not
127
+ depend on a PyPI release or a catalog update.
132
128
 
133
129
  ```bash
134
130
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
135
131
  ollama pull qwen3-embedding:4b
136
132
  # Start Ollama if it is not running. Keep its service available.
137
133
  hermes plugins install https://github.com/cnkang/hermes-plugin-qdrant-memory \
138
- --ref 5babd3e4b4c022aad38a2b6db82cbd5b601f1ef8 # v0.1.0 release commit
134
+ --ref 2fd43ad2259bd98d039394b8b714e5e89b5ab3a8 # verified Runtime 0.2.2 source
139
135
  hermes plugins enable qdrant-memory
140
136
  hermes memory setup
141
137
  hermes config set memory.provider qdrant-memory
@@ -144,12 +140,15 @@ hermes qdrant-memory init
144
140
 
145
141
  For this version-locked form, `hermes plugins install` checks out the given ref, and
146
142
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
147
- `--ref` takes a full 40-character commit SHA; the
143
+ `--ref` takes a full 40-character commit SHA. The
148
144
  [releases page](https://github.com/cnkang/hermes-plugin-qdrant-memory/releases)
149
145
  lists the release commits. Do not pip-install into a managed Hermes
150
146
  environment. Omit `--ref` to track `main`, the development line.
151
147
 
152
- **Alternative: clone the release tag manually.**
148
+ **Historical release: clone a published tag manually.**
149
+
150
+ The v0.1.0 tag below predates the standalone interfaces and the explicit-write
151
+ fix. Use the pinned source above for Runtime 0.2.2.
153
152
 
154
153
  ```bash
155
154
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
@@ -178,7 +177,7 @@ import and registration. For development installs, see
178
177
  [development setup](docs/development.md).
179
178
 
180
179
  The release notes, supported Hermes/Python versions and the current
181
- limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md);
180
+ limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md).
182
181
  `main` stays the development line.
183
182
 
184
183
  For local development, symlink the repository into a disposable profile:
@@ -223,7 +222,10 @@ mappings) stays in the local Hermes home. Back up that home together with your
223
222
  Qdrant data. Migration from Mem0 is one-way, and version 0.1 has no vector
224
223
  reuse or hybrid retrieval.
225
224
 
226
- Similarity is a candidate-selection signal, not proof that two facts are identical.
225
+ Explicit add deduplicates only canonical text in the same scope and never calls
226
+ an LLM. Different texts are saved regardless of similarity. Update replaces only
227
+ the authorized exact ID. For automatic Hermes extraction,
228
+ similarity is a candidate-selection signal, not proof that two facts are identical.
227
229
  `SAME` skips, `SUPERSEDES` updates, `CONFLICT` creates a linked record, and
228
230
  `UNRELATED` adds a record. Migration uses source IDs instead of semantic merging.
229
231
 
@@ -305,7 +307,7 @@ default profile scope uses an agent (`hermes`). Pass `--agent hermes` to
305
307
  target it, or `--all-agents` to delete every agent scope of the user. Without
306
308
  `--all-agents`, the result reports `other_agent_scopes` when other agent
307
309
  scopes still hold memories. A literal `*` is an ordinary agent id, not a
308
- wildcard. `--dry-run` previews the per-scope counts;
310
+ wildcard. `--dry-run` previews the per-scope counts.
309
311
  `--confirm` authorizes deletion. An interrupted deletion fails closed: other
310
312
  commands and provider startup return `scope_delete_recovery_required` until
311
313
  `delete-all --confirm` resumes it. Deletion intents are versioned and explicit.
@@ -313,8 +315,8 @@ A pre-versioned intent cannot show whether `*` is a single agent or all agents,
313
315
  so the plugin refuses it (`scope_delete_legacy_intent`). Resolve it with
314
316
  `--resolve-legacy single_agent`, or with `--resolve-legacy all_agents` when the
315
317
  record's agent is `*` or absent. Turns admitted after the deletion can still
316
- add new memories; everything admitted before it — stored memories, prepared
317
- writes and unprocessed turn events — is removed or invalidated. `list`,
318
+ add new memories. Everything admitted before it, including stored memories, prepared
319
+ writes and unprocessed turn events, is removed or invalidated. `list`,
318
320
  `export` and `delete-all` do not require the embedding service.
319
321
 
320
322
  ## Data flow and privacy
@@ -325,10 +327,10 @@ carries and where it can go:
325
327
  | Component | Carries | Destination |
326
328
  | --- | --- | --- |
327
329
  | Chat / agent turns | Your conversation | The model configured for the Hermes session |
328
- | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model — with a cloud model, turn text reaches that cloud |
329
- | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction; runs only when similarity selects a review candidate |
330
+ | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model. With a cloud model, turn text reaches that cloud |
331
+ | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction. It runs only when similarity selects a review candidate |
330
332
  | Embeddings (`embedding`) | Memory and search text | Ollama by default, or your OpenAI-compatible endpoint |
331
- | Storage (`qdrant`) | Memory payloads and provenance; the ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
333
+ | Storage (`qdrant`) | Memory payloads and provenance. The ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
332
334
 
333
335
  Three common deployments:
334
336
 
@@ -483,7 +485,7 @@ contains `memories` objects with `id`/`text`, and `queries` with `query`/`releva
483
485
  The default fixture now contains 24 labeled queries, including Chinese, English,
484
486
  mixed-language and hard-negative cases. The
485
487
  [original 12-query recording](docs/retrieval-baseline.json) is a historical
486
- synthetic regression baseline; rerun the current fixture for current measurements.
488
+ synthetic regression baseline. Rerun the current fixture for current measurements.
487
489
  These fixtures do not establish production recall quality. See the [recovery benchmark](docs/recovery-benchmark.md)
488
490
  for a separate synthetic replay and retained-storage measurement.
489
491
 
@@ -1,3 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-plugin-qdrant-memory
3
+ Version: 0.2.2
4
+ Summary: Durable Qdrant memory for Hermes, standalone CLI and local MCP
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/cnkang/hermes-plugin-qdrant-memory
7
+ Project-URL: Repository, https://github.com/cnkang/hermes-plugin-qdrant-memory
8
+ Project-URL: Issues, https://github.com/cnkang/hermes-plugin-qdrant-memory/issues
9
+ Project-URL: Changelog, https://github.com/cnkang/hermes-plugin-qdrant-memory/blob/main/CHANGELOG.md
10
+ Project-URL: MCP, https://github.com/cnkang/qdrant-memory-mcp
11
+ Project-URL: Skill, https://github.com/cnkang/skills/tree/main/qdrant-memory
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: qdrant-client<2,>=1.15
16
+ Requires-Dist: httpx<1,>=0.28
17
+ Requires-Dist: portalocker<4,>=3.2
18
+ Provides-Extra: mcp
19
+ Requires-Dist: mcp<3,>=1.30; extra == "mcp"
20
+ Dynamic: license-file
21
+
1
22
  # Qdrant Memory for Hermes
2
23
 
3
24
  [![CI](https://github.com/cnkang/hermes-plugin-qdrant-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/cnkang/hermes-plugin-qdrant-memory/actions/workflows/ci.yml)
@@ -7,12 +28,12 @@
7
28
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
8
29
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
9
30
 
10
- > **v0.1.0 — limited technical preview.** Give Hermes Agent durable, private
11
- > long-term memory: your conversations become searchable memories in your own
12
- > Qdrant store, with recall, update and scoped deletion under your control.
13
- > The preview supports one writer per destination, with the documented
14
- > durability limits. See the [validation record](docs/validation.md) for the
15
- > current certification.
31
+ > **Runtime 0.2.2** supports MCP Python SDK 1.x and 2.x for the standalone
32
+ > CLI/MCP. MCP distribution 0.1.2 pins Runtime 0.2.2. Explicit writes retain
33
+ > canonical-text deduplication without an LLM.
34
+ > The Hermes Plugin Catalog has its own reviewed immutable pin. A PyPI release
35
+ > does not upgrade it. One writer per destination remains required.
36
+ > See [release notes](docs/releases/0.2.2.md) and [validation](docs/validation.md).
16
37
 
17
38
  [English](README.md) | [简体中文](README.zh-CN.md)
18
39
 
@@ -26,6 +47,16 @@ as a community-maintained plugin.
26
47
 
27
48
  ## How it works
28
49
 
50
+ For non-Hermes users, install in a private Python 3.11+
51
+ environment: `pip install 'hermes-plugin-qdrant-memory[mcp]==0.2.2'` (omit `[mcp]`
52
+ for CLI only). `qdrant-memory` is the CLI and `mcp` is its server subcommand.
53
+ Standalone does not require Hermes. See [setup and quick start](docs/standalone.md).
54
+ MCP-focused agents may instead use the [metadata-only distribution](https://github.com/cnkang/qdrant-memory-mcp).
55
+ Public releases: [Runtime on PyPI](https://pypi.org/project/hermes-plugin-qdrant-memory/)
56
+ and [MCP distribution on PyPI](https://pypi.org/project/qdrant-memory-mcp/).
57
+ For native Hermes use its official plugin installation mechanism below. Before
58
+ an authorized upgrade, verify the same profile, collection and embedding pipeline.
59
+
29
60
  ```mermaid
30
61
  flowchart LR
31
62
  turn["Hermes turn"] --> admit["Provider callback"]
@@ -47,7 +78,7 @@ flowchart LR
47
78
  - A fully local setup works: embedded Qdrant and local Ollama keep the data
48
79
  on your machine.
49
80
  - Data locality follows the destination. Extraction uses the LLM configured
50
- in Hermes, and embedding uses the configured embedding service; either can
81
+ in Hermes, and embedding uses the configured embedding service. Either can
51
82
  be remote. See [Data flow and privacy](#data-flow-and-privacy).
52
83
  - Not supported in this preview: multi-writer Server/Cloud, hybrid search and
53
84
  a web UI.
@@ -56,9 +87,9 @@ flowchart LR
56
87
 
57
88
  The plugin primarily provides Hermes Agent's durable memory integration. As an
58
89
  additional capability, its shared backend can serve other agents through the
59
- Runtime 0.2.0's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
60
- interface, without requiring Hermes. These interfaces support explicit scoped
61
- memory operations and maintenance/migration; automatic extraction and prefetch
90
+ Runtime's `qdrant-memory --home PATH` JSON CLI and optional local stdio MCP
91
+ interface. Standalone use does not require Hermes. These interfaces support explicit scoped
92
+ memory operations and maintenance/migration. Automatic extraction and prefetch
62
93
  remain Hermes lifecycle features. See [standalone setup](docs/standalone.md) for
63
94
  configuration and boundaries, and the agent-neutral
64
95
  [qdrant-memory skill](https://github.com/cnkang/skills/tree/main/qdrant-memory)
@@ -66,19 +97,20 @@ for multi-agent installation and usage guidance.
66
97
 
67
98
  The optional MCP server exposes four scoped tools: `qdrant_memory_search`,
68
99
  `qdrant_memory_add`, `qdrant_memory_update` and `qdrant_memory_delete`. Compatible
69
- clients launch it as a local stdio process and discover/call those tools;
70
- maintenance and migration use the operator CLI. See the
100
+ clients launch it as a local stdio process and discover/call those tools.
101
+ Maintenance and migration use the operator CLI. See the
71
102
  [MCP capabilities and connection guide](docs/mcp.md) for setup, shared-memory
72
- boundaries and the verified agent matrix. This is a tested source preview;
73
- the published v0.1.0 plugin release does not include the MCP entrypoint.
103
+ boundaries and the verified agent matrix. Runtime 0.2.0 published these interfaces.
104
+ The published v0.1.0 plugin release does not include the MCP entrypoint.
74
105
 
75
106
  ## See it in action
76
107
 
77
108
  ![Terminal recording: install the plugin, store a preference, and recall it in a new session](https://raw.githubusercontent.com/cnkang/hermes-plugin-qdrant-memory/main/assets/demo.gif)
78
109
 
79
- The recording shows the full flow on a clean profile: install from the
80
- official Hermes Plugin Catalog, teach a preference, wait for the background
81
- extraction, recall it in a new session, and clean up the demo scope. All steps
110
+ The recording shows the full flow on a clean profile. It installs from the
111
+ official Hermes Plugin Catalog, teaches a preference, and waits for the
112
+ background extraction. Then it recalls the preference in a new session and
113
+ cleans up the demo scope. All steps
82
114
  run in embedded mode. `scripts/demo.sh` reproduces the same walkthrough with
83
115
  the pinned release.
84
116
 
@@ -109,14 +141,18 @@ The install resolves the catalog's reviewed commit and prints the entry's declar
109
141
  tools, requirements and known issues before it clones anything.
110
142
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
111
143
 
112
- **Version-locked: install the pinned release through the Hermes CLI.**
144
+ **Version-locked: install the verified Runtime 0.2.2 source through the Hermes CLI.**
145
+
146
+ This immutable source revision includes MCP SDK 1.x/2.x support and the
147
+ explicit-write fix. It does not
148
+ depend on a PyPI release or a catalog update.
113
149
 
114
150
  ```bash
115
151
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
116
152
  ollama pull qwen3-embedding:4b
117
153
  # Start Ollama if it is not running. Keep its service available.
118
154
  hermes plugins install https://github.com/cnkang/hermes-plugin-qdrant-memory \
119
- --ref 5babd3e4b4c022aad38a2b6db82cbd5b601f1ef8 # v0.1.0 release commit
155
+ --ref 2fd43ad2259bd98d039394b8b714e5e89b5ab3a8 # verified Runtime 0.2.2 source
120
156
  hermes plugins enable qdrant-memory
121
157
  hermes memory setup
122
158
  hermes config set memory.provider qdrant-memory
@@ -125,12 +161,15 @@ hermes qdrant-memory init
125
161
 
126
162
  For this version-locked form, `hermes plugins install` checks out the given ref, and
127
163
  `hermes plugins enable` prepares the declared dependencies through Hermes PM.
128
- `--ref` takes a full 40-character commit SHA; the
164
+ `--ref` takes a full 40-character commit SHA. The
129
165
  [releases page](https://github.com/cnkang/hermes-plugin-qdrant-memory/releases)
130
166
  lists the release commits. Do not pip-install into a managed Hermes
131
167
  environment. Omit `--ref` to track `main`, the development line.
132
168
 
133
- **Alternative: clone the release tag manually.**
169
+ **Historical release: clone a published tag manually.**
170
+
171
+ The v0.1.0 tag below predates the standalone interfaces and the explicit-write
172
+ fix. Use the pinned source above for Runtime 0.2.2.
134
173
 
135
174
  ```bash
136
175
  export HERMES_HOME="/absolute/path/to/your/hermes-profile"
@@ -159,7 +198,7 @@ import and registration. For development installs, see
159
198
  [development setup](docs/development.md).
160
199
 
161
200
  The release notes, supported Hermes/Python versions and the current
162
- limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md);
201
+ limitations live in [docs/releases/v0.1.0.md](docs/releases/v0.1.0.md).
163
202
  `main` stays the development line.
164
203
 
165
204
  For local development, symlink the repository into a disposable profile:
@@ -204,7 +243,10 @@ mappings) stays in the local Hermes home. Back up that home together with your
204
243
  Qdrant data. Migration from Mem0 is one-way, and version 0.1 has no vector
205
244
  reuse or hybrid retrieval.
206
245
 
207
- Similarity is a candidate-selection signal, not proof that two facts are identical.
246
+ Explicit add deduplicates only canonical text in the same scope and never calls
247
+ an LLM. Different texts are saved regardless of similarity. Update replaces only
248
+ the authorized exact ID. For automatic Hermes extraction,
249
+ similarity is a candidate-selection signal, not proof that two facts are identical.
208
250
  `SAME` skips, `SUPERSEDES` updates, `CONFLICT` creates a linked record, and
209
251
  `UNRELATED` adds a record. Migration uses source IDs instead of semantic merging.
210
252
 
@@ -286,7 +328,7 @@ default profile scope uses an agent (`hermes`). Pass `--agent hermes` to
286
328
  target it, or `--all-agents` to delete every agent scope of the user. Without
287
329
  `--all-agents`, the result reports `other_agent_scopes` when other agent
288
330
  scopes still hold memories. A literal `*` is an ordinary agent id, not a
289
- wildcard. `--dry-run` previews the per-scope counts;
331
+ wildcard. `--dry-run` previews the per-scope counts.
290
332
  `--confirm` authorizes deletion. An interrupted deletion fails closed: other
291
333
  commands and provider startup return `scope_delete_recovery_required` until
292
334
  `delete-all --confirm` resumes it. Deletion intents are versioned and explicit.
@@ -294,8 +336,8 @@ A pre-versioned intent cannot show whether `*` is a single agent or all agents,
294
336
  so the plugin refuses it (`scope_delete_legacy_intent`). Resolve it with
295
337
  `--resolve-legacy single_agent`, or with `--resolve-legacy all_agents` when the
296
338
  record's agent is `*` or absent. Turns admitted after the deletion can still
297
- add new memories; everything admitted before it — stored memories, prepared
298
- writes and unprocessed turn events — is removed or invalidated. `list`,
339
+ add new memories. Everything admitted before it, including stored memories, prepared
340
+ writes and unprocessed turn events, is removed or invalidated. `list`,
299
341
  `export` and `delete-all` do not require the embedding service.
300
342
 
301
343
  ## Data flow and privacy
@@ -306,10 +348,10 @@ carries and where it can go:
306
348
  | Component | Carries | Destination |
307
349
  | --- | --- | --- |
308
350
  | Chat / agent turns | Your conversation | The model configured for the Hermes session |
309
- | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model — with a cloud model, turn text reaches that cloud |
310
- | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction; runs only when similarity selects a review candidate |
351
+ | Memory extraction (`llm.mode: inherit`) | The turn's user and assistant text | The same session model. With a cloud model, turn text reaches that cloud |
352
+ | Relation checks | The candidate memory and the similar stored memory | Same routing as extraction. It runs only when similarity selects a review candidate |
311
353
  | Embeddings (`embedding`) | Memory and search text | Ollama by default, or your OpenAI-compatible endpoint |
312
- | Storage (`qdrant`) | Memory payloads and provenance; the ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
354
+ | Storage (`qdrant`) | Memory payloads and provenance. The ledger stays local | Embedded local path, self-hosted server, or Qdrant Cloud |
313
355
 
314
356
  Three common deployments:
315
357
 
@@ -464,7 +506,7 @@ contains `memories` objects with `id`/`text`, and `queries` with `query`/`releva
464
506
  The default fixture now contains 24 labeled queries, including Chinese, English,
465
507
  mixed-language and hard-negative cases. The
466
508
  [original 12-query recording](docs/retrieval-baseline.json) is a historical
467
- synthetic regression baseline; rerun the current fixture for current measurements.
509
+ synthetic regression baseline. Rerun the current fixture for current measurements.
468
510
  These fixtures do not establish production recall quality. See the [recovery benchmark](docs/recovery-benchmark.md)
469
511
  for a separate synthetic replay and retained-storage measurement.
470
512
 
@@ -41,6 +41,7 @@ tests/test_cli_output.py
41
41
  tests/test_config_embedding.py
42
42
  tests/test_deletion_ordering.py
43
43
  tests/test_discovery_cli.py
44
+ tests/test_explicit_dedupe.py
44
45
  tests/test_host_lifecycle.py
45
46
  tests/test_input_validation.py
46
47
  tests/test_inventory_export.py
@@ -3,4 +3,4 @@ httpx<1,>=0.28
3
3
  portalocker<4,>=3.2
4
4
 
5
5
  [mcp]
6
- mcp<2,>=1.30
6
+ mcp<3,>=1.30
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hermes-plugin-qdrant-memory"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Durable Qdrant memory for Hermes, standalone CLI and local MCP"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -16,19 +16,21 @@ dependencies = ["qdrant-client>=1.15,<2", "httpx>=0.28,<1", "portalocker>=3.2,<4
16
16
  qdrant-memory = "qdrant_memory.standalone:main"
17
17
 
18
18
  [project.optional-dependencies]
19
- mcp = ["mcp>=1.30,<2"]
19
+ mcp = ["mcp>=1.30,<3"]
20
20
 
21
21
  [project.urls]
22
22
  Homepage = "https://github.com/cnkang/hermes-plugin-qdrant-memory"
23
23
  Repository = "https://github.com/cnkang/hermes-plugin-qdrant-memory"
24
24
  Issues = "https://github.com/cnkang/hermes-plugin-qdrant-memory/issues"
25
25
  Changelog = "https://github.com/cnkang/hermes-plugin-qdrant-memory/blob/main/CHANGELOG.md"
26
+ MCP = "https://github.com/cnkang/qdrant-memory-mcp"
27
+ Skill = "https://github.com/cnkang/skills/tree/main/qdrant-memory"
26
28
 
27
29
  [project.entry-points."hermes_agent.memory_providers"]
28
30
  qdrant-memory = "qdrant_memory:register"
29
31
 
30
32
  [dependency-groups]
31
- test = ["pytest>=8,<10", "pytest-cov>=6,<8", "ruamel.yaml>=0.18.16,<0.20", "mcp>=1.30,<2"]
33
+ test = ["pytest>=8,<10", "pytest-cov>=6,<8", "ruamel.yaml>=0.18.16,<0.20", "mcp>=1.30,<3"]
32
34
 
33
35
  [tool.setuptools.packages.find]
34
36
  include = ["qdrant_memory*"]
@@ -3,6 +3,14 @@
3
3
  from .models import content_hash
4
4
 
5
5
 
6
+ def decide_explicit(text, scope, store):
7
+ """Deduplicate authorized explicit writes only by canonical hash in scope."""
8
+ exact = store.lookup_hash(scope, content_hash(text))
9
+ if exact:
10
+ return "SKIP", str(exact.id), {}
11
+ return "ADD", None, {}
12
+
13
+
6
14
  def decide(text, scope, store, extractor, cfg):
7
15
  """Return ADD, UPDATE or SKIP after scoped hash and factual relation checks.
8
16
 
@@ -11,19 +11,17 @@ def create_server(runtime):
11
11
  """Bind the four fixed-scope tools to an official SDK server instance."""
12
12
  import anyio
13
13
  from mcp.server import Server
14
- from mcp.types import CallToolResult, TextContent, Tool
14
+ from mcp.types import CallToolResult, ListToolsResult, TextContent, Tool
15
15
 
16
- server = Server("qdrant-memory")
17
-
18
- @server.list_tools()
19
16
  async def list_tools():
20
17
  """Advertise only the shared explicit-memory schemas."""
18
+ # Both SDKs require an async handler; allow cancellation before building schemas.
19
+ await anyio.lowlevel.checkpoint()
21
20
  return [
22
21
  Tool(name=item["name"], description=item["description"], inputSchema=item["parameters"])
23
22
  for item in schemas()
24
23
  ]
25
24
 
26
- @server.call_tool(validate_input=False)
27
25
  async def invoke(name, arguments):
28
26
  """Validate and dispatch without echoing sensitive invalid arguments."""
29
27
  try:
@@ -38,7 +36,22 @@ def create_server(runtime):
38
36
  isError=True,
39
37
  )
40
38
 
41
- return server
39
+ if hasattr(Server, "list_tools"):
40
+ # SDK 1.x registers handlers with decorators.
41
+ server = Server("qdrant-memory")
42
+ server.list_tools()(list_tools)
43
+ server.call_tool(validate_input=False)(invoke)
44
+ return server
45
+
46
+ async def on_list_tools(context, params):
47
+ """Adapt SDK 2.x request handlers to the shared tool catalogue."""
48
+ return ListToolsResult(tools=await list_tools())
49
+
50
+ async def on_call_tool(context, params):
51
+ """Keep validation and sanitized tool errors identical across SDK versions."""
52
+ return await invoke(params.name, params.arguments)
53
+
54
+ return Server("qdrant-memory", on_list_tools=on_list_tools, on_call_tool=on_call_tool)
42
55
 
43
56
 
44
57
  def serve(home):
@@ -1,5 +1,5 @@
1
1
  name: qdrant-memory
2
- version: 0.2.0
2
+ version: 0.2.2
3
3
  description: Durable native memory with Qdrant and Hermes LLM extraction
4
4
  manifest_version: 2
5
5
  author: Kang Liu
@@ -5,7 +5,7 @@ import time
5
5
  from itertools import islice
6
6
  from threading import RLock
7
7
 
8
- from .dedupe import decide
8
+ from .dedupe import decide, decide_explicit
9
9
  from .models import Scope, content_hash, enforce_limits, now, payload, point_id
10
10
  from .progress import report
11
11
  from .retry import run_with_retry
@@ -163,11 +163,9 @@ class Runtime:
163
163
  return failures
164
164
 
165
165
  def add(self, text, scope, source="manual_tool", session_id="", **fields):
166
- """Add or reconcile a scoped fact through hash and factual relation review."""
166
+ """Save an explicit scoped fact with deterministic exact-text deduplication."""
167
167
  with self.lock:
168
- action, identifier, relation = decide(
169
- text, scope, self.store, self.extractor, self.cfg["dedupe"]
170
- )
168
+ action, identifier, relation = decide_explicit(text, scope, self.store)
171
169
  self.decisions[action] += 1
172
170
  self.ledger.increment(action)
173
171
  if action == "SKIP":
@@ -1,3 +1,3 @@
1
1
  """Single version source shared by registration and migration manifests."""
2
2
 
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.2"
@@ -7,16 +7,18 @@ from pathlib import Path
7
7
  import pytest
8
8
  from ruamel.yaml import YAML
9
9
 
10
- CASES = [("success", "success", "success", "success", "success", 0)] + [
11
- tuple(result if position == failed else "success" for position in range(5)) + (1,)
12
- for failed in range(5)
10
+ CASES = [("success", "success", "success", "success", "success", "success", 0)] + [
11
+ tuple(result if position == failed else "success" for position in range(6)) + (1,)
12
+ for failed in range(6)
13
13
  for result in ("failure", "skipped", "cancelled")
14
14
  ]
15
15
 
16
16
 
17
- @pytest.mark.parametrize("lint,tests,sonar,snyk,snyk_code,expected", CASES)
17
+ @pytest.mark.parametrize("lint,tests,mcp,sonar,snyk,snyk_code,expected", CASES)
18
18
  @pytest.mark.skipif(os.name == "nt", reason="The checked workflow step is POSIX Bash.")
19
- def test_required_gate_executes_actual_workflow_step(lint, tests, sonar, snyk, snyk_code, expected):
19
+ def test_required_gate_executes_actual_workflow_step(
20
+ lint, tests, mcp, sonar, snyk, snyk_code, expected
21
+ ):
20
22
  """Verify required gate executes actual workflow step."""
21
23
  workflow = (Path(__file__).resolve().parents[1] / ".github/workflows/ci.yml").read_text(
22
24
  encoding="utf-8"
@@ -28,6 +30,7 @@ def test_required_gate_executes_actual_workflow_step(lint, tests, sonar, snyk, s
28
30
  **os.environ,
29
31
  "LINT_RESULT": lint,
30
32
  "TEST_RESULT": tests,
33
+ "MCP_RESULT": mcp,
31
34
  "SONAR_RESULT": sonar,
32
35
  "SNYK_RESULT": snyk,
33
36
  "SNYK_CODE_RESULT": snyk_code,
@@ -0,0 +1,80 @@
1
+ """Explicit intent and automatic factual adjudication remain separate policies."""
2
+
3
+ import math
4
+
5
+ import pytest
6
+
7
+ from qdrant_memory.models import Scope
8
+ from qdrant_memory.tools import dispatch
9
+
10
+ from .helpers import LLM, runtime
11
+
12
+
13
+ @pytest.mark.parametrize("score", [0.92, 0.97, 1.0])
14
+ @pytest.mark.parametrize("with_extractor", [False, True])
15
+ def test_explicit_add_keeps_distinct_similar_facts(tmp_path, score, with_extractor):
16
+ """Similarity cannot discard explicit intent, even when an LLM reports SAME."""
17
+ llm = LLM("SAME")
18
+ rt = runtime(tmp_path, llm=llm)
19
+ if not with_extractor:
20
+ rt.extractor = None
21
+ scope = Scope("alice", "hermes")
22
+ rt.store.embedder.embed_query = lambda text: (
23
+ [1.0, 0.0, 0.0] if "preferred" in text else [score, math.sqrt(1 - score**2), 0.0]
24
+ )
25
+ try:
26
+ first = dispatch(rt, "qdrant_memory_add", {"text": "cats preferred"}, scope, "s")
27
+ assert rt.store.search("cats avoided", scope)[0].score == pytest.approx(score)
28
+ second = dispatch(rt, "qdrant_memory_add", {"text": "cats avoided"}, scope, "s")
29
+ assert first["action"] == second["action"] == "ADD"
30
+ assert first["id"] != second["id"]
31
+ duplicate = rt.add(" cats preferred ", scope)
32
+ assert duplicate == {"action": "SKIP", "id": first["id"]}
33
+ for other in (Scope("bob", "hermes"), Scope("alice", "other")):
34
+ assert rt.add("cats preferred", other)["id"] != first["id"]
35
+ assert rt.store.count(other) == 1
36
+ assert rt.store.count(scope) == 2
37
+ assert not llm.calls
38
+ finally:
39
+ rt.store.close()
40
+ rt.ledger.close()
41
+
42
+
43
+ @pytest.mark.parametrize(
44
+ "relation,count,texts",
45
+ [
46
+ ("SAME", 1, {"cats preferred"}),
47
+ ("SUPERSEDES", 1, {"cats avoided"}),
48
+ ("CONFLICT", 2, {"cats preferred", "cats avoided"}),
49
+ ("UNRELATED", 2, {"cats preferred", "cats avoided"}),
50
+ ],
51
+ )
52
+ def test_automatic_events_retain_factual_relations(tmp_path, relation, count, texts):
53
+ """Real extraction and durable event processing still adjudicate similar facts."""
54
+ llm = LLM(relation)
55
+ rt = runtime(tmp_path, llm=llm)
56
+ scope = Scope("alice", "hermes")
57
+ try:
58
+ first = rt.add("cats preferred", scope)
59
+ key = rt.ledger.enqueue_event(
60
+ {
61
+ "kind": "turn",
62
+ "scope": scope.as_dict(),
63
+ "source": "conversation",
64
+ "session_id": "s",
65
+ "user": "cats avoided",
66
+ "assistant": "ack",
67
+ }
68
+ )
69
+ rt.process_event(key)
70
+ assert rt.store.count(scope) == count
71
+ hits = rt.store.search("cats", scope)
72
+ assert {h.payload["text"] for h in hits} == texts
73
+ if relation == "CONFLICT":
74
+ conflict = next(h for h in hits if h.payload["text"] == "cats avoided")
75
+ assert conflict.payload["metadata"]["conflicts_with"] == first["id"]
76
+ assert any(c["purpose"] == "qdrant-memory.relation" for c in llm.calls)
77
+ assert rt.ledger.row("events", key)["status"] == "COMMITTED"
78
+ finally:
79
+ rt.store.close()
80
+ rt.ledger.close()
@@ -17,6 +17,13 @@ def test_manifests_declare_v2_metadata_and_match_runtime_dependencies():
17
17
 
18
18
  assert manifests[0] == manifests[1]
19
19
  manifest = manifests[0]
20
+ from qdrant_memory.version import VERSION
21
+
22
+ assert str(manifest["version"]) == project["version"] == VERSION
23
+ for name in ("README.md", "README.zh-CN.md", "docs/standalone.md", "docs/mcp.md"):
24
+ text = (ROOT / name).read_text(encoding="utf-8")
25
+ assert f"hermes-plugin-qdrant-memory[mcp]=={VERSION}" in text
26
+ assert "hermes-plugin-qdrant-memory[mcp]==0.2.0" not in text
20
27
  assert manifest["manifest_version"] == 2
21
28
  assert manifest["author"] == "Kang Liu"
22
29
  assert manifest["license"] == "MIT"