@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 +10 -0
- package/client.py +1 -0
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/pyproject.toml +1 -1
- package/python_sdk.py +14 -9
- package/tools.py +30 -1
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
package/package.json
CHANGED
package/plugin.yaml
CHANGED
package/pyproject.toml
CHANGED
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|