diff --git a/plugins/memory/supermemory/README.md b/plugins/memory/supermemory/README.md
index c1f41c415..7e7786d83 100644
--- a/plugins/memory/supermemory/README.md
+++ b/plugins/memory/supermemory/README.md
@@ -1,6 +1,6 @@
# Supermemory Memory Provider
-Semantic long-term memory with profile recall, semantic search, explicit memory tools, and session-end conversation ingest.
+Semantic long-term memory with profile recall, semantic search, explicit memory tools, and full-session conversation ingest (one ingest per session) for richer profiles.
## Requirements
@@ -45,22 +45,34 @@ Config file: `$HERMES_HOME/supermemory.json`
## Tools
-| Tool | Description |
-|------|-------------|
-| `supermemory_store` | Store an explicit memory |
-| `supermemory_search` | Search memories by semantic similarity |
-| `supermemory_forget` | Forget a memory by ID or best-match query |
-| `supermemory_profile` | Retrieve persistent profile and recent context |
+Kebab-case names are registered for the agent; snake_case aliases remain supported.
+
+| Tool | Alias | Description |
+|------|-------|-------------|
+| `supermemory-save` | `supermemory_store` | Store an explicit memory |
+| `supermemory-search` | `supermemory_search` | Search memories by semantic similarity |
+| `supermemory-forget` | `supermemory_forget` | Forget a memory by ID or best-match query |
+| `supermemory-profile` | `supermemory_profile` | Retrieve persistent profile and recent context |
+
+## Source attribution
+
+All Supermemory API calls send `x-sm-source: hermes`, and document writes stamp
+`metadata.sm_source: hermes`. This is a **functional routing key, not telemetry**:
+it groups Hermes-written memories into a dedicated "Hermes" Space in the
+Supermemory app, so you can filter, browse, and bulk-manage them per source agent
+(alongside Codex, Claude Code, etc.) from the Supermemory UI.
## Behavior
When enabled, Hermes can:
- prefetch relevant memory context before each turn
-- store cleaned conversation turns after each completed response
-- ingest the full session on session end for richer graph updates
+- buffer the full conversation and ingest it as **one session** at session end (or on `/reset`, branch, compression, or shutdown)
+- ingest the full session to the conversations endpoint for richer profile/graph updates
- expose explicit tools for search, store, forget, and profile access
+The session is written once via the conversations endpoint, which drives Supermemory's entity extraction and profile building while keeping a clean, retrievable full transcript.
+
## Profile-Scoped Containers
Use `{identity}` in the `container_tag` to scope memories per Hermes profile:
@@ -87,7 +99,7 @@ For advanced setups (e.g. OpenClaw-style multi-workspace), you can enable custom
```
When enabled:
-- `supermemory_search`, `supermemory_store`, `supermemory_forget`, and `supermemory_profile` accept an optional `container_tag` parameter
+- `supermemory-search`, `supermemory-save`, `supermemory-forget`, and `supermemory-profile` accept an optional `container_tag` parameter
- The tag must be in the whitelist: primary container + `custom_containers`
- Automatic operations (turn sync, prefetch, memory write mirroring, session ingest) always use the **primary** container only
- Custom container instructions are injected into the system prompt
diff --git a/plugins/memory/supermemory/__init__.py b/plugins/memory/supermemory/__init__.py
index a21ae53cc..0d03f4eaa 100644
--- a/plugins/memory/supermemory/__init__.py
+++ b/plugins/memory/supermemory/__init__.py
@@ -269,7 +269,22 @@ class _SupermemoryClient:
self._container_tag = container_tag
self._search_mode = search_mode if search_mode in _VALID_SEARCH_MODES else _DEFAULT_SEARCH_MODE
self._timeout = timeout
- self._client = Supermemory(api_key=api_key, timeout=timeout, max_retries=0)
+ self._client = Supermemory(
+ api_key=api_key,
+ timeout=timeout,
+ max_retries=0,
+ default_headers={"x-sm-source": "hermes"},
+ )
+
+ def _merge_metadata(self, metadata: Optional[dict]) -> dict:
+ # sm_source routes Hermes writes into the "Hermes" Space in the Supermemory
+ # app so the user can filter / bulk-manage them per source agent. This is a
+ # functional routing key for the user, not vendor telemetry.
+ merged = {"sm_source": "hermes", **(metadata or {})}
+ legacy_source = merged.pop("source", None)
+ if legacy_source and "type" not in merged:
+ merged["type"] = str(legacy_source)
+ return merged
def add_memory(self, content: str, metadata: Optional[dict] = None, *,
entity_context: str = "", container_tag: Optional[str] = None,
@@ -280,7 +295,7 @@ class _SupermemoryClient:
"container_tags": [tag],
}
if metadata:
- kwargs["metadata"] = metadata
+ kwargs["metadata"] = self._merge_metadata(metadata)
if entity_context:
kwargs["entity_context"] = _clamp_entity_context(entity_context)
if custom_id:
@@ -349,18 +364,22 @@ class _SupermemoryClient:
preview = (target.get("memory") or "")[:100]
return {"success": True, "message": f'Forgot: "{preview}"', "id": memory_id}
- def ingest_conversation(self, session_id: str, messages: list[dict]) -> None:
- payload = json.dumps({
+ def ingest_conversation(self, session_id: str, messages: list[dict], metadata: dict | None = None) -> None:
+ payload: dict = {
"conversationId": session_id,
"messages": messages,
"containerTags": [self._container_tag],
- }).encode("utf-8")
+ }
+ if metadata:
+ payload["metadata"] = self._merge_metadata(metadata)
+
req = urllib.request.Request(
_CONVERSATIONS_URL,
- data=payload,
+ data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {self._api_key}",
"Content-Type": "application/json",
+ "x-sm-source": "hermes",
},
method="POST",
)
@@ -447,6 +466,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
self._custom_containers: List[str] = []
self._custom_container_instructions = ""
self._allowed_containers: List[str] = []
+ self._session_turns: List[Dict[str, str]] = []
@property
def name(self) -> str:
@@ -501,13 +521,13 @@ class SupermemoryMemoryProvider(MemoryProvider):
self._search_mode = self._config["search_mode"]
self._entity_context = self._config["entity_context"]
self._api_timeout = self._config["api_timeout"]
-
- # Multi-container setup
self._enable_custom_containers = self._config["enable_custom_container_tags"]
self._custom_containers = self._config["custom_containers"]
self._custom_container_instructions = self._config["custom_container_instructions"]
self._allowed_containers = [self._container_tag] + list(self._custom_containers)
+ self._session_turns = []
+
agent_context = kwargs.get("agent_context", "")
self._write_enabled = agent_context not in {"cron", "flush", "subagent"}
self._active = bool(self._api_key)
@@ -534,7 +554,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
lines = [
"# Supermemory",
f"Active. Container: {self._container_tag}.",
- "Use supermemory_search, supermemory_store, supermemory_forget, and supermemory_profile for explicit memory operations.",
+ "Use supermemory-search, supermemory-save, supermemory-forget, and supermemory-profile (aliases: supermemory_search, supermemory_store, supermemory_forget, supermemory_profile).",
]
if self._enable_custom_containers and self._custom_containers:
tags_str = ", ".join(self._allowed_containers)
@@ -567,31 +587,11 @@ class SupermemoryMemoryProvider(MemoryProvider):
clean_user = _clean_text_for_capture(user_content)
clean_assistant = _clean_text_for_capture(assistant_content)
- if not clean_user or not clean_assistant:
+ if not clean_user and not clean_assistant:
return
- if self._capture_mode == "all":
- if len(clean_user) < _MIN_CAPTURE_LENGTH or len(clean_assistant) < _MIN_CAPTURE_LENGTH:
- return
- if _is_trivial_message(clean_user):
- return
- content = (
- f"[role: user]\n{clean_user}\n[user:end]\n\n"
- f"[role: assistant]\n{clean_assistant}\n[assistant:end]"
- )
- metadata = {"source": "hermes", "type": "conversation_turn"}
-
- def _run():
- try:
- self._client.add_memory(content, metadata=metadata, entity_context=self._entity_context)
- except Exception:
- logger.debug("Supermemory sync_turn failed", exc_info=True)
-
- if self._sync_thread and self._sync_thread.is_alive():
- self._sync_thread.join(timeout=2.0)
- self._sync_thread = None
- self._sync_thread = threading.Thread(target=_run, daemon=True, name="supermemory-sync")
- self._sync_thread.start()
+ # Buffer every turn for the single full-session document written at end/switch/shutdown
+ self._session_turns.append({"user": clean_user, "assistant": clean_assistant})
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
if not self._active or not self._write_enabled or not self._client or not self._session_id:
@@ -609,12 +609,68 @@ class SupermemoryMemoryProvider(MemoryProvider):
if len(cleaned) == 1 and len(cleaned[0].get("content", "")) < 20:
return
try:
- self._client.ingest_conversation(self._session_id, cleaned)
+ self._client.ingest_conversation(
+ self._session_id,
+ cleaned,
+ metadata={
+ "type": "full_session",
+ "session_id": self._session_id,
+ "message_count": len(cleaned),
+ },
+ )
except urllib.error.HTTPError:
logger.warning("Supermemory session ingest failed", exc_info=True)
except Exception:
logger.warning("Supermemory session ingest failed", exc_info=True)
+ # Clear buffer so shutdown() doesn't duplicate on normal exit
+ self._session_turns = []
+
+ def on_session_switch(
+ self,
+ new_session_id: str,
+ *,
+ parent_session_id: str = "",
+ reset: bool = False,
+ **kwargs,
+ ) -> None:
+ """Flush any buffered turns from the old session as one document, then reset for the new session."""
+ if not self._active or not self._write_enabled or not self._client:
+ self._session_id = str(new_session_id or "").strip() or self._session_id
+ self._session_turns = []
+ return
+
+ old_session_id = self._session_id
+ old_turns = list(self._session_turns)
+
+ # Flush previous session via conversations ingest (with metadata)
+ if old_turns and old_session_id:
+ messages: list[dict] = []
+ for turn in old_turns:
+ if turn.get("user"):
+ messages.append({"role": "user", "content": turn["user"]})
+ if turn.get("assistant"):
+ messages.append({"role": "assistant", "content": turn["assistant"]})
+
+ try:
+ self._client.ingest_conversation(
+ old_session_id,
+ messages,
+ metadata={
+ "type": "full_session",
+ "session_id": old_session_id,
+ "message_count": len(old_turns) * 2,
+ "partial": not reset,
+ },
+ )
+ except Exception:
+ logger.debug("Supermemory session-switch ingest failed", exc_info=True)
+
+ # Reset for new session
+ self._session_id = str(new_session_id or "").strip() or old_session_id
+ self._session_turns = []
+ self._turn_count = 0
+
def on_memory_write(self, action: str, target: str, content: str) -> None:
if not self._active or not self._write_enabled or not self._client:
return
@@ -625,7 +681,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
try:
self._client.add_memory(
content.strip(),
- metadata={"source": "hermes_memory", "target": target, "type": "explicit_memory"},
+ metadata={"target": target, "type": "explicit_memory"},
entity_context=self._entity_context,
)
except Exception:
@@ -638,6 +694,31 @@ class SupermemoryMemoryProvider(MemoryProvider):
self._write_thread.start()
def shutdown(self) -> None:
+ # Emergency fallback (crashes only). Buffer is cleared on normal on_session_end().
+ if self._active and self._write_enabled and self._client and self._session_turns and self._session_id:
+ logger.warning("Supermemory: Saving session via shutdown (session=%s, turns=%d)", self._session_id, len(self._session_turns))
+
+ messages: list[dict] = []
+ for turn in self._session_turns:
+ if turn.get("user"):
+ messages.append({"role": "user", "content": turn["user"]})
+ if turn.get("assistant"):
+ messages.append({"role": "assistant", "content": turn["assistant"]})
+
+ try:
+ self._client.ingest_conversation(
+ self._session_id,
+ messages,
+ metadata={
+ "type": "full_session",
+ "session_id": self._session_id,
+ "message_count": len(self._session_turns) * 2,
+ "partial": True,
+ },
+ )
+ except Exception:
+ logger.debug("Supermemory shutdown ingest failed", exc_info=True)
+
for attr_name in ("_prefetch_thread", "_sync_thread", "_write_thread"):
thread = getattr(self, attr_name, None)
if thread and thread.is_alive():
@@ -665,8 +746,25 @@ class SupermemoryMemoryProvider(MemoryProvider):
return sanitized
def get_tool_schemas(self) -> List[Dict[str, Any]]:
+ def with_kebab_aliases(schemas: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ aliases = {
+ "supermemory_store": "supermemory-save",
+ "supermemory_search": "supermemory-search",
+ "supermemory_forget": "supermemory-forget",
+ "supermemory_profile": "supermemory-profile",
+ }
+ expanded = list(schemas)
+ for schema in schemas:
+ kebab = aliases.get(schema.get("name", ""))
+ if not kebab:
+ continue
+ copy = json.loads(json.dumps(schema))
+ copy["name"] = kebab
+ expanded.append(copy)
+ return expanded
+
if not self._enable_custom_containers:
- return [STORE_SCHEMA, SEARCH_SCHEMA, FORGET_SCHEMA, PROFILE_SCHEMA]
+ return with_kebab_aliases([STORE_SCHEMA, SEARCH_SCHEMA, FORGET_SCHEMA, PROFILE_SCHEMA])
# When multi-container is enabled, add optional container_tag to relevant tools
container_param = {
@@ -678,7 +776,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
schema = json.loads(json.dumps(base)) # deep copy
schema["parameters"]["properties"]["container_tag"] = container_param
schemas.append(schema)
- return schemas
+ return with_kebab_aliases(schemas)
def _tool_store(self, args: dict) -> str:
content = str(args.get("content") or "").strip()
@@ -692,7 +790,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
if not isinstance(metadata, dict):
metadata = {}
metadata.setdefault("type", _detect_category(content))
- metadata["source"] = "hermes_tool"
+ metadata.pop("source", None)
try:
result = self._client.add_memory(content, metadata=metadata, entity_context=self._entity_context, container_tag=tag)
preview = content[:80] + ("..." if len(content) > 80 else "")
@@ -777,6 +875,13 @@ class SupermemoryMemoryProvider(MemoryProvider):
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
if not self._active or not self._client:
return tool_error("Supermemory is not configured")
+ aliases = {
+ "supermemory-save": "supermemory_store",
+ "supermemory-search": "supermemory_search",
+ "supermemory-forget": "supermemory_forget",
+ "supermemory-profile": "supermemory_profile",
+ }
+ tool_name = aliases.get(tool_name, tool_name)
if tool_name == "supermemory_store":
return self._tool_store(args)
if tool_name == "supermemory_search":
diff --git a/plugins/memory/supermemory/plugin.yaml b/plugins/memory/supermemory/plugin.yaml
index 23321bdb5..8100b3213 100644
--- a/plugins/memory/supermemory/plugin.yaml
+++ b/plugins/memory/supermemory/plugin.yaml
@@ -1,5 +1,5 @@
name: supermemory
-version: 1.0.0
+version: 1.0.1
description: "Supermemory semantic long-term memory with profile recall, semantic search, explicit memory tools, and session ingest."
pip_dependencies:
- supermemory
diff --git a/scripts/release.py b/scripts/release.py
index d7f6dcfea..3bf9b2dbf 100755
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -70,6 +70,7 @@ AUTHOR_MAP = {
"524706+Twanislas@users.noreply.github.com": "Twanislas",
"9592417+adam91holt@users.noreply.github.com": "adam91holt",
"kchuang1015@users.noreply.github.com": "kchuang1015",
+ "maheshthedev@gmail.com": "MaheshtheDev",
"kyssta-exe@users.noreply.github.com": "kyssta-exe",
"45688690+fujinice@users.noreply.github.com": "fujinice",
"276689385+carltonawong@users.noreply.github.com": "carltonawong",
diff --git a/tests/plugins/memory/test_supermemory_provider.py b/tests/plugins/memory/test_supermemory_provider.py
index d5f1c5bb1..2d0d0c9e2 100644
--- a/tests/plugins/memory/test_supermemory_provider.py
+++ b/tests/plugins/memory/test_supermemory_provider.py
@@ -50,8 +50,8 @@ class FakeClient:
def forget_by_query(self, query, *, container_tag=None):
return self.forget_by_query_response
- def ingest_conversation(self, session_id, messages):
- self.ingest_calls.append({"session_id": session_id, "messages": messages})
+ def ingest_conversation(self, session_id, messages, metadata=None):
+ self.ingest_calls.append({"session_id": session_id, "messages": messages, "metadata": metadata})
@pytest.fixture
@@ -136,23 +136,28 @@ def test_prefetch_skips_profile_between_frequency(provider):
assert "User Profile (Persistent)" not in result
-def test_sync_turn_skips_trivial_message(provider):
+def test_sync_turn_buffers_short_messages(provider):
+ # Trivial filtering is no longer applied at sync time — every non-empty turn
+ # is buffered and only the full session is written at session boundaries.
provider.sync_turn("ok", "sure", session_id="session-1")
+ assert provider._session_turns == [{"user": "ok", "assistant": "sure"}]
assert provider._client.add_calls == []
-def test_sync_turn_persists_cleaned_exchange(provider):
+def test_sync_turn_buffers_cleaned_exchange(provider):
provider.sync_turn(
"Please remember this\nignore",
"Got it, storing the context",
session_id="session-1",
)
- provider._sync_thread.join(timeout=1)
- assert len(provider._client.add_calls) == 1
- content = provider._client.add_calls[0]["content"]
- assert "ignore" not in content
- assert "[role: user]" in content
- assert "[role: assistant]" in content
+ assert len(provider._session_turns) == 1
+ turn = provider._session_turns[0]
+ assert "ignore" not in turn["user"]
+ assert turn["user"].startswith("Please remember this")
+ assert turn["assistant"] == "Got it, storing the context"
+ # Buffering only — no per-turn writes to the client
+ assert provider._client.add_calls == []
+ assert provider._client.ingest_calls == []
def test_on_session_end_ingests_clean_messages(provider):
@@ -169,6 +174,28 @@ def test_on_session_end_ingests_clean_messages(provider):
{"role": "user", "content": "hello"},
{"role": "assistant", "content": "hi there"},
]
+ assert payload["metadata"]["type"] == "full_session"
+ assert payload["metadata"]["session_id"] == "session-1"
+ assert payload["metadata"]["message_count"] == 2
+ # Buffer is cleared after a normal session-end ingest.
+ assert provider._session_turns == []
+
+
+def test_merge_metadata_stamps_sm_source():
+ # sm_source routes Hermes writes into the "Hermes" Space in the Supermemory
+ # app (functional routing, not telemetry) — must always be present.
+ from plugins.memory.supermemory import _SupermemoryClient
+
+ client = _SupermemoryClient.__new__(_SupermemoryClient)
+ merged = client._merge_metadata({"type": "explicit_memory"})
+ assert merged["sm_source"] == "hermes"
+ assert merged["type"] == "explicit_memory"
+
+ # Legacy "source" is migrated into "type" when type is absent.
+ merged2 = client._merge_metadata({"source": "conversation_turn"})
+ assert merged2["sm_source"] == "hermes"
+ assert merged2["type"] == "conversation_turn"
+ assert "source" not in merged2
def test_on_memory_write_tracks_thread(provider):
@@ -179,7 +206,7 @@ def test_on_memory_write_tracks_thread(provider):
assert provider._client.add_calls[0]["metadata"]["type"] == "explicit_memory"
-def test_shutdown_joins_and_clears_threads(provider, monkeypatch):
+def test_shutdown_joins_threads_and_flushes_buffer(provider, monkeypatch):
started = threading.Event()
release = threading.Event()
@@ -196,15 +223,16 @@ def test_shutdown_joins_and_clears_threads(provider, monkeypatch):
monkeypatch.setattr(provider._client, "add_memory", slow_add_memory)
+ # sync_turn now only buffers — no thread is spawned.
provider.sync_turn(
"Please remember this request in long-term memory",
"Absolutely, I will keep that in long-term memory.",
session_id="session-1",
)
- assert started.wait(timeout=1)
- assert provider._sync_thread is not None
+ assert provider._sync_thread is None
+ assert len(provider._session_turns) == 1
- started.clear()
+ # on_memory_write still runs on a background thread.
provider.on_memory_write("add", "memory", "Jordan likes concise docs")
assert started.wait(timeout=1)
assert provider._write_thread is not None
@@ -212,10 +240,18 @@ def test_shutdown_joins_and_clears_threads(provider, monkeypatch):
release.set()
provider.shutdown()
+ # All tracked threads joined and cleared.
assert provider._sync_thread is None
assert provider._write_thread is None
assert provider._prefetch_thread is None
- assert len(provider._client.add_calls) == 2
+ # Explicit memory write went through.
+ assert len(provider._client.add_calls) == 1
+ # Buffered turn was flushed as a partial full-session ingest.
+ assert len(provider._client.ingest_calls) == 1
+ payload = provider._client.ingest_calls[0]
+ assert payload["session_id"] == "session-1"
+ assert payload["metadata"]["partial"] is True
+ assert payload["metadata"]["type"] == "full_session"
def test_store_tool_returns_saved_payload(provider):
diff --git a/website/docs/user-guide/features/memory-providers.md b/website/docs/user-guide/features/memory-providers.md
index 00f2555d6..43b70334d 100644
--- a/website/docs/user-guide/features/memory-providers.md
+++ b/website/docs/user-guide/features/memory-providers.md
@@ -66,7 +66,7 @@ AI-native cross-session user modeling with dialectic reasoning, session-scoped c
hermes memory setup # select "honcho" — runs the Honcho-specific post-setup
```
-On a fresh install, configure Honcho directly with `hermes memory setup honcho`. The legacy `hermes honcho setup` command still works (it now redirects to `hermes memory setup`), but is only registered after Honcho is selected as the active memory provider.
+The legacy `hermes honcho setup` command still works (it now redirects to `hermes memory setup`), but is only registered after Honcho is selected as the active memory provider.
**Config:** `$HERMES_HOME/honcho.json` (profile-local) or `~/.honcho/config.json` (global). Resolution order: `$HERMES_HOME/honcho.json` > `~/.hermes/honcho.json` > `~/.honcho/config.json`. See the [config reference](https://github.com/NousResearch/hermes-agent/blob/main/plugins/memory/honcho/README.md) and the [Honcho integration guide](https://docs.honcho.dev/v3/guides/integrations/hermes).
@@ -498,11 +498,11 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env
**Key features:**
- Automatic context fencing — strips recalled memories from captured turns to prevent recursive memory pollution
-- Session-end conversation ingest for richer graph-level knowledge building
+- Full-session ingest — the entire conversation is sent once at session boundaries
+- Session-end conversation ingest (to `/v4/conversations`) for richer profile + graph building in Supermemory
- Profile facts injected on first turn and at configurable intervals
-- Trivial message filtering (skips "ok", "thanks", etc.)
- **Profile-scoped containers** — use `{identity}` in `container_tag` (e.g. `hermes-{identity}` → `hermes-coder`) to isolate memories per Hermes profile
-- **Multi-container mode** — enable `enable_custom_container_tags` with a `custom_containers` list to let the agent read/write across named containers. Automatic operations (sync, prefetch) stay on the primary container.
+- **Multi-container mode** — enable `enable_custom_container_tags` with a `custom_containers` list to let the agent read/write across named containers. Automatic operations stay on the primary container.
Multi-container example
diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md
index 79c8489a1..8658733db 100644
--- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md
+++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md
@@ -498,9 +498,9 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env
**主要特性:**
- 自动上下文隔离——从捕获的轮次中剥离已召回的记忆,防止递归记忆污染
-- 会话结束时的对话导入,用于构建更丰富的图谱级知识
+- 在会话边界时将整个会话**一次性导入**
+- 会话结束时同时导入到对话端点(`/v4/conversations`),用于 Supermemory 的 profile 和图谱构建
- 在第一轮及可配置间隔注入 profile 事实
-- 无意义消息过滤(跳过"ok"、"thanks"等)
- **Profile 范围容器**——在 `container_tag` 中使用 `{identity}`(例如 `hermes-{identity}` → `hermes-coder`),按 Hermes profile 隔离记忆
- **多容器模式**——启用 `enable_custom_container_tags` 并配置 `custom_containers` 列表,让 Agent 跨命名容器读写。自动操作(同步、预取)保持在主容器上。