@atomicmemory/hermes-plugin 0.1.18 → 0.2.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2026-07-29
4
+
5
+ ### Changed
6
+
7
+ - **Breaking (tool contract):** `atomicmemory_conclude` now requires a `content_class` parameter (`summary`, `redacted`, or `raw`) describing the sensitivity of the text being stored. A call that omits it, or passes an unrecognized value, is rejected at the tool boundary with a message naming the parameter.
8
+
9
+ This is required to store facts against AtomicMemory Core 1.2.0 and later. Core defaults to `RAW_CONTENT_POLICY=reject`, which refuses a verbatim write carrying no content class — previously `conclude` sent an unclassified write and received `422 raw_content_rejected`, with nothing indicating which tool call caused it. The class is never chosen on the caller's behalf: guessing it could label a raw transcript as hosted-safe.
10
+
11
+ Existing deployments need no configuration change, but a model that calls `conclude` without the new parameter will get a tool error instead of a stored fact until it adapts.
12
+
3
13
  ## 0.1.13 - 2026-05-15
4
14
 
5
15
  ### Fixed
package/client.py CHANGED
@@ -158,4 +158,5 @@ class AtomicMemoryClient(Protocol):
158
158
  scope: Scope,
159
159
  provenance: Provenance,
160
160
  metadata: dict[str, Any] | None = None,
161
+ content_class: str | None = None,
161
162
  ) -> IngestResult: ...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomicmemory/hermes-plugin",
3
- "version": "0.1.18",
3
+ "version": "0.2.0",
4
4
  "description": "AtomicMemory native Hermes memory provider — Python SDK-backed, cross-tool memory by default.",
5
5
  "publishConfig": {
6
6
  "access": "public",
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: atomicmemory
2
- version: 0.1.18
2
+ version: 0.2.0
3
3
  description: "AtomicMemory native Hermes memory provider — Python SDK-backed, cross-tool memory by default."
4
4
  pip_dependencies:
5
5
  - "atomicmemory>=1.1.2,<2.0.0"
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "atomicmemory-hermes"
7
- version = "0.1.18"
7
+ version = "0.2.0"
8
8
  description = "AtomicMemory native Hermes memory provider."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
package/python_sdk.py CHANGED
@@ -175,16 +175,21 @@ class PythonSdkAtomicMemoryClient:
175
175
  scope: Scope,
176
176
  provenance: Provenance,
177
177
  metadata: dict[str, Any] | None = None,
178
+ content_class: str | None = None,
178
179
  ) -> IngestResult:
179
- raw = self._require_client().ingest(
180
- {
181
- "mode": "verbatim",
182
- "content": content,
183
- "scope": scope,
184
- "provenance": _provenance_dict(provenance),
185
- "metadata": metadata,
186
- }
187
- )
180
+ payload: dict[str, Any] = {
181
+ "mode": "verbatim",
182
+ "content": content,
183
+ "scope": scope,
184
+ "provenance": _provenance_dict(provenance),
185
+ "metadata": metadata,
186
+ }
187
+ # Only sent when the caller chose one. Never defaulted here: an
188
+ # unclassified write must fail closed at core rather than be relabeled
189
+ # safe by the bridge.
190
+ if content_class is not None:
191
+ payload["content_class"] = content_class
192
+ raw = self._require_client().ingest(payload)
188
193
  return _ingest_result(raw)
189
194
 
190
195
  def _require_client(self) -> Any:
package/tools.py CHANGED
@@ -64,8 +64,19 @@ CONCLUDE_SCHEMA = {
64
64
  "type": "object",
65
65
  "properties": {
66
66
  "conclusion": {"type": "string", "description": "The fact to store verbatim."},
67
+ "content_class": {
68
+ "type": "string",
69
+ "enum": ["summary", "redacted", "raw"],
70
+ "description": (
71
+ "Sensitivity of the text you are storing: 'summary' if you distilled it "
72
+ "yourself, 'redacted' if you removed sensitive spans, 'raw' if it is a "
73
+ "verbatim prompt, response, diff, or source excerpt. Required: a core "
74
+ "running the default raw-content policy refuses raw or unclassified "
75
+ "writes, and this tool will not guess on your behalf."
76
+ ),
77
+ },
67
78
  },
68
- "required": ["conclusion"],
79
+ "required": ["conclusion", "content_class"],
69
80
  },
70
81
  }
71
82
 
@@ -127,15 +138,33 @@ def _handle_context(provider: AtomicMemoryMemoryProvider, args: dict[str, Any])
127
138
  )
128
139
 
129
140
 
141
+ # Mirrors the v1 ContentClass enum. Kept local so a malformed tool call is
142
+ # rejected at this boundary rather than surfacing later as a core 422 with no
143
+ # indication of which tool produced it.
144
+ CONTENT_CLASSES = ("summary", "redacted", "raw")
145
+
146
+
130
147
  def _handle_conclude(provider: AtomicMemoryMemoryProvider, args: dict[str, Any]) -> str:
131
148
  conclusion = _clean_str(args.get("conclusion"))
132
149
  if not conclusion:
133
150
  return tool_error("Missing required parameter: conclusion")
151
+ content_class = _clean_str(args.get("content_class"))
152
+ if not content_class:
153
+ return tool_error(
154
+ "Missing required parameter: content_class (one of "
155
+ f"{', '.join(CONTENT_CLASSES)}). A core running the default raw-content "
156
+ "policy rejects unclassified writes; this tool does not choose for you."
157
+ )
158
+ if content_class not in CONTENT_CLASSES:
159
+ return tool_error(
160
+ f"Invalid content_class {content_class!r}; expected one of {', '.join(CONTENT_CLASSES)}."
161
+ )
134
162
  provider._require_client().ingest_verbatim(
135
163
  content=conclusion,
136
164
  scope=ingest_scope_dict(user_id=provider._user_id),
137
165
  provenance=provider._ingest_provenance(session_id=provider._session_id),
138
166
  metadata={"kind": "fact"},
167
+ content_class=content_class,
139
168
  )
140
169
  return json.dumps({"result": "Fact stored."})
141
170