Skip to content

Commit e11e3d9

Browse files
committed
Add content_class to provider contract and require it in Hermes
## Summary Add a `content_class` field to the verbatim ingest contract in the provider-contract schema, and require the Hermes `atomicmemory_conclude` tool to supply it before storing a fact. ## Changes - Add a `ContentClass` enum (`summary`, `redacted`, `raw`) and a `content_class` field on `VerbatimIngest` in the provider-contract v1 schema. - Forward `content_class` through the Hermes Python SDK client's `ingest_verbatim` call. - Add `content_class` to the `atomicmemory_conclude` tool schema as a required parameter; reject calls that omit it or pass an unrecognized value at the tool boundary, with an error naming the parameter and valid options. - Add schema tests covering accepted, rejected, and unknown `content_class` values, plus provider tests covering the new tool-boundary validation. - Bump plugin package versions to `0.2.0` to reflect the breaking tool-contract change. ## Why AtomicMemory Core's raw-content policy (default: reject) refuses a verbatim write that doesn't declare a content sensitivity class. Previously, the Hermes `conclude` tool sent unclassified writes, which core rejected with a `422` and no indication of which tool call was responsible. Requiring `content_class` at the tool boundary fails closed with a clear, attributable error instead. The class is never inferred by the plugin — the caller must choose it, so a raw transcript can't be silently relabeled as hosted-safe. ## Validation - Added provider-contract schema tests exercising accepted `content_class` values, rejection of unknown values, and rejection of unrelated unknown fields (`packages/sdk/src/memory/__tests__/provider-contract-schema.test.ts`). - Added Hermes provider tests covering forwarding of caller-chosen `content_class`, rejection of a missing value, and rejection of an invalid value (`plugins/hermes/tests/test_provider.py`).
1 parent 02eac66 commit e11e3d9

21 files changed

Lines changed: 147 additions & 24 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"name": "claude-code",
1313
"source": "./plugins/claude-code",
1414
"description": "Persistent semantic memory for Claude Code - user preferences, project context, prior decisions, and codebase facts that survive across sessions.",
15-
"version": "0.1.18",
15+
"version": "0.2.0",
1616
"category": "productivity",
1717
"homepage": "https://docs.atomicstrata.ai/integrations/coding-agents/claude-code",
1818
"license": "Apache-2.0"

packages/sdk/schema/v1/provider-contract.schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
"type": "string",
3434
"enum": ["fact", "episode", "summary", "procedure", "document"]
3535
},
36+
"ContentClass": {
37+
"title": "ContentClass",
38+
"description": "Sensitivity class of supplied content: 'summary' (distilled, hosted-safe), 'redacted' (sensitive spans removed by the caller), or 'raw' (verbatim prompt/response/diff/source). This contract exposes it on verbatim ingest only; core's HTTP API also consults it on extraction paths, where it decides whether the raw transcript is withheld from the durable audit episode. A core running RAW_CONTENT_POLICY=reject (the default) refuses a verbatim write of 'raw' or unclassified content. Never inferred by a provider — the caller chooses it, so omitting it fails closed rather than mislabeling raw content as safe.",
39+
"type": "string",
40+
"enum": ["summary", "redacted", "raw"]
41+
},
3642
"Message": {
3743
"title": "Message",
3844
"type": "object",
@@ -104,6 +110,7 @@
104110
"mode": { "const": "verbatim" },
105111
"content": { "type": "string" },
106112
"kind": { "$ref": "#/$defs/MemoryKind" },
113+
"content_class": { "$ref": "#/$defs/ContentClass" },
107114
"scope": { "$ref": "#/$defs/Scope" },
108115
"provenance": { "$ref": "#/$defs/Provenance" },
109116
"metadata": { "type": "object", "additionalProperties": true }

packages/sdk/src/memory/__tests__/provider-contract-schema.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,31 @@ describe('provider-contract v1 schemas', () => {
8686
expect(ingest({ ...GOOD_INGEST, mode: 'binary' })).toBe(false);
8787
});
8888

89+
// The SDK forwards content_class to core on the verbatim path, and core
90+
// refuses an unclassified verbatim write under RAW_CONTENT_POLICY=reject.
91+
// VerbatimIngest is additionalProperties:false, so the schema has to carry
92+
// the field or the very payload the SDK emits is contract-invalid.
93+
// Verbatim only, deliberately: core also consults content_class on
94+
// extraction paths for audit-transcript redaction, but exposing it there
95+
// changes what is durably retained — tracked as tech debt, not done here.
96+
it.each(['summary', 'redacted', 'raw'])('accepts content_class %s on verbatim', (contentClass) => {
97+
const { ingest } = buildValidators();
98+
expect(ingest({ ...GOOD_INGEST, content_class: contentClass })).toBe(true);
99+
});
100+
101+
it('rejects an unknown content_class', () => {
102+
const { ingest } = buildValidators();
103+
expect(ingest({ ...GOOD_INGEST, content_class: 'public' })).toBe(false);
104+
});
105+
106+
it('still rejects an unknown ingest field', () => {
107+
// Guards the guard: proves additionalProperties:false is doing work, so
108+
// the acceptance above means the field was added rather than the schema
109+
// having stopped constraining anything.
110+
const { ingest } = buildValidators();
111+
expect(ingest({ ...GOOD_INGEST, not_a_real_field: 'x' })).toBe(false);
112+
});
113+
89114
it('rejects a retrieval receipt missing trace_id', () => {
90115
const { searchPage } = buildValidators();
91116
const receipt: Record<string, unknown> = { ...GOOD_SEARCH_PAGE.retrieval };

plugins/claude-code/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "atomicmemory",
3-
"version": "0.1.18",
3+
"version": "0.2.0",
44
"description": "Persistent semantic memory for Claude Code — user preferences, project context, prior decisions, and codebase facts that survive across sessions.",
55
"author": {
66
"name": "AtomicMemory",

plugins/claude-code/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@atomicmemory/claude-code-plugin",
3-
"version": "0.1.18",
3+
"version": "0.2.0",
44
"description": "AtomicMemory plugin for Claude Code — persistent semantic memory across sessions.",
55
"private": false,
66
"license": "Apache-2.0",

plugins/codex/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "atomicmemory",
3-
"version": "0.1.18",
3+
"version": "0.2.0",
44
"description": "AtomicMemory memory layer for Codex. Pluggable semantic memory — swap backends through the SDK's MemoryProvider model by config, not code change.",
55
"author": {
66
"name": "AtomicMemory",

plugins/codex/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@atomicmemory/codex-plugin",
3-
"version": "0.1.18",
3+
"version": "0.2.0",
44
"description": "AtomicMemory plugin for OpenAI Codex — plugin manifest, MCP server config, and memory protocol skill.",
55
"private": true,
66
"license": "Apache-2.0",

plugins/codex/skills/atomicmemory/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ description: >
1010
license: Apache-2.0
1111
metadata:
1212
author: AtomicMemory
13-
version: "0.1.18"
13+
version: "0.2.0"
1414
category: ai-memory
1515
tags: "memory, semantic-search, codex, pluggable"
1616
---

plugins/cursor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@atomicmemory/cursor-plugin",
3-
"version": "0.1.18",
3+
"version": "0.2.0",
44
"description": "AtomicMemory integration for Cursor - MCP configuration and project rules for persistent semantic memory.",
55
"private": true,
66
"license": "Apache-2.0",

plugins/hermes/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

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+
313
## 0.1.13 - 2026-05-15
414

515
### Fixed

0 commit comments

Comments
 (0)