@signetai/connector-hermes-agent 0.193.2 → 0.194.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/hermes-plugin/README.md +2 -2
- package/hermes-plugin/__init__.py +60 -19
- package/package.json +3 -3
package/hermes-plugin/README.md
CHANGED
|
@@ -56,10 +56,10 @@ Environment variables:
|
|
|
56
56
|
|
|
57
57
|
The plugin bridges Hermes Agent's memory lifecycle to the Signet daemon:
|
|
58
58
|
|
|
59
|
-
1. **Session start** — Calls Signet's session-start hook, which returns identity files (AGENTS.md, SOUL.md, USER.md, MEMORY.md), scored memories, and knowledge graph constraints.
|
|
59
|
+
1. **Session start** — Calls Signet's session-start hook, which returns identity files (AGENTS.md, SOUL.md, USER.md, MEMORY.md), scored memories, and knowledge graph constraints. The deterministic `stableSystemPrompt` is returned by `system_prompt_block()`; state-dependent `dynamicContext` is staged for Hermes' API-only prefetch path rather than being added to the canonical transcript.
|
|
60
60
|
|
|
61
61
|
2. **Per-turn recall** — On each user message, calls the user-prompt-submit hook. Signet runs hybrid search (BM25 + vector similarity + knowledge graph traversal + predictive scoring) and returns the most relevant memories.
|
|
62
62
|
|
|
63
|
-
3. **Session end** — Sends
|
|
63
|
+
3. **Session end** — Sends a transcript with internal Signet memory delimiters removed to Signet's session-end hook, which queues it for the memory pipeline: extraction, knowledge graph updates, retention decay, and MEMORY.md synthesis.
|
|
64
64
|
|
|
65
65
|
4. **Explicit tools** — The agent can call canonical Signet tools such as `memory_search` and `memory_store` directly during conversation for on-demand memory operations. Legacy `signet_*` names are handled for compatibility but are not advertised to the model.
|
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
25
|
import os
|
|
26
|
+
import re
|
|
26
27
|
import threading
|
|
27
28
|
from pathlib import Path
|
|
28
29
|
from typing import Any, Dict, List, Optional
|
|
@@ -39,6 +40,20 @@ except ImportError: # pragma: no cover — only missing during Hermes bootstrap
|
|
|
39
40
|
|
|
40
41
|
logger = logging.getLogger(__name__)
|
|
41
42
|
|
|
43
|
+
_INTERNAL_MEMORY_BLOCK_RE = re.compile(
|
|
44
|
+
r"<\\?\s*(?:signet-memory-context|signet-memory|memory-context)(?=[\s/>])(?:[^>\"']|\"[^\"]*\"|'[^']*')*>.*?(?:<\\?\s*/\s*(?:signet-memory-context|signet-memory|memory-context)\s*>|$)",
|
|
45
|
+
re.IGNORECASE | re.DOTALL,
|
|
46
|
+
)
|
|
47
|
+
_INTERNAL_MEMORY_CLOSE_RE = re.compile(
|
|
48
|
+
r"<\\?\s*/\s*(?:signet-memory-context|signet-memory|memory-context)\s*>",
|
|
49
|
+
re.IGNORECASE,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _strip_internal_memory_context(value: str) -> str:
|
|
54
|
+
"""Keep provider-only memory wrappers out of Hermes transcript state."""
|
|
55
|
+
return _INTERNAL_MEMORY_CLOSE_RE.sub("", _INTERNAL_MEMORY_BLOCK_RE.sub("", value))
|
|
56
|
+
|
|
42
57
|
|
|
43
58
|
# ---------------------------------------------------------------------------
|
|
44
59
|
# Tool schemas
|
|
@@ -326,6 +341,10 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
326
341
|
self._project = ""
|
|
327
342
|
self._inject_cache = ""
|
|
328
343
|
self._inject_lock = threading.Lock()
|
|
344
|
+
# Session-start dynamic context is kept separate from the ordinary
|
|
345
|
+
# per-turn result. queue_prefetch() clears the latter before starting
|
|
346
|
+
# a new recall, but must not erase the first API-only context block.
|
|
347
|
+
self._session_prefetch_result = ""
|
|
329
348
|
self._prefetch_result = ""
|
|
330
349
|
self._notification_result = ""
|
|
331
350
|
self._prefetch_lock = threading.Lock()
|
|
@@ -390,8 +409,9 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
390
409
|
def initialize(self, session_id: str, **kwargs) -> None:
|
|
391
410
|
"""Connect to the Signet daemon and call session-start hook.
|
|
392
411
|
|
|
393
|
-
Retrieves identity, memories, and
|
|
394
|
-
the daemon.
|
|
412
|
+
Retrieves identity, memories, and the cache-stable prompt contract
|
|
413
|
+
from the daemon. The stable prefix is cached for system_prompt_block;
|
|
414
|
+
dynamic session context is staged for Hermes' API-only prefetch path.
|
|
395
415
|
"""
|
|
396
416
|
if SignetClient is None:
|
|
397
417
|
logger.warning("Signet plugin: SignetClient not importable — skipping initialization")
|
|
@@ -429,23 +449,30 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
429
449
|
self._session_key = session_id or "hermes-default"
|
|
430
450
|
self._project = _resolve_agent_workspace(agent_id, kwargs)
|
|
431
451
|
|
|
432
|
-
# Call session-start hook — get identity + memories +
|
|
452
|
+
# Call session-start hook — get identity + memories + split context
|
|
433
453
|
result = self._client.session_start(
|
|
434
454
|
self._session_key,
|
|
435
455
|
project=self._project,
|
|
436
456
|
)
|
|
437
457
|
if result:
|
|
438
|
-
|
|
439
|
-
if
|
|
458
|
+
raw_stable_prompt = result.get("stableSystemPrompt") or result.get("inject", "")
|
|
459
|
+
stable_prompt = raw_stable_prompt if isinstance(raw_stable_prompt, str) else ""
|
|
460
|
+
dynamic_context = result.get("dynamicContext", "")
|
|
461
|
+
if stable_prompt:
|
|
440
462
|
with self._inject_lock:
|
|
441
|
-
self._inject_cache =
|
|
463
|
+
self._inject_cache = stable_prompt
|
|
464
|
+
with self._prefetch_lock:
|
|
465
|
+
self._prefetch_generation += 1
|
|
466
|
+
self._session_prefetch_result = dynamic_context if isinstance(dynamic_context, str) else ""
|
|
467
|
+
self._prefetch_result = ""
|
|
468
|
+
self._notification_result = ""
|
|
442
469
|
# Capture identity and warnings for downstream consumers
|
|
443
470
|
self._identity = result.get("identity")
|
|
444
471
|
self._warnings = result.get("warnings", [])
|
|
445
472
|
self._session_initialized = True
|
|
446
473
|
logger.debug(
|
|
447
474
|
"Signet session-start: %d chars inject, %d memories",
|
|
448
|
-
len(
|
|
475
|
+
len(stable_prompt) + (len(dynamic_context) if isinstance(dynamic_context, str) else 0),
|
|
449
476
|
len(result.get("memories", [])),
|
|
450
477
|
)
|
|
451
478
|
else:
|
|
@@ -454,16 +481,17 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
454
481
|
def system_prompt_block(self) -> str:
|
|
455
482
|
"""Return the Signet system prompt injection.
|
|
456
483
|
|
|
457
|
-
On the first call, returns the
|
|
458
|
-
|
|
459
|
-
|
|
484
|
+
On the first call, returns only the deterministic session-start
|
|
485
|
+
prefix. Dynamic context is returned by prefetch(), where Hermes can
|
|
486
|
+
attach it to its API-only copy of the user message. Subsequent calls
|
|
487
|
+
return a minimal header.
|
|
460
488
|
"""
|
|
461
489
|
if not self._client:
|
|
462
490
|
return ""
|
|
463
491
|
|
|
464
492
|
with self._inject_lock:
|
|
465
493
|
if self._inject_cache:
|
|
466
|
-
# First call — return
|
|
494
|
+
# First call — return the stable prefix and clear the cache.
|
|
467
495
|
block = self._inject_cache
|
|
468
496
|
self._inject_cache = ""
|
|
469
497
|
return block
|
|
@@ -502,7 +530,8 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
502
530
|
logger.debug("Signet notification prefetch failed: %s", e)
|
|
503
531
|
|
|
504
532
|
with self._prefetch_lock:
|
|
505
|
-
parts = [self._prefetch_result, self._notification_result]
|
|
533
|
+
parts = [self._session_prefetch_result, self._prefetch_result, self._notification_result]
|
|
534
|
+
self._session_prefetch_result = ""
|
|
506
535
|
self._prefetch_result = ""
|
|
507
536
|
self._notification_result = ""
|
|
508
537
|
|
|
@@ -520,7 +549,7 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
520
549
|
|
|
521
550
|
# Accumulate transcript for checkpoint/session-end
|
|
522
551
|
with self._transcript_lock:
|
|
523
|
-
self._transcript_lines.append(f"user: {query}")
|
|
552
|
+
self._transcript_lines.append(f"user: {_strip_internal_memory_context(query)}")
|
|
524
553
|
|
|
525
554
|
# Capture mutable state before spawning the thread to avoid
|
|
526
555
|
# data races: sync_turn() can update _last_assistant_message
|
|
@@ -550,6 +579,10 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
550
579
|
# the cached prefix mid-conversation.
|
|
551
580
|
if not result.get("sessionKnown", True) and self._session_initialized:
|
|
552
581
|
logger.debug("Signet daemon restarted mid-session, restoring session claim")
|
|
582
|
+
with self._prefetch_lock:
|
|
583
|
+
# Do not replay a pre-restart session-start block
|
|
584
|
+
# into an already-running Hermes conversation.
|
|
585
|
+
self._session_prefetch_result = ""
|
|
553
586
|
reinit = client.session_start(
|
|
554
587
|
session_key, project=project, claim_only=True,
|
|
555
588
|
)
|
|
@@ -559,7 +592,7 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
559
592
|
"the next prompt may be treated as a new session"
|
|
560
593
|
)
|
|
561
594
|
return
|
|
562
|
-
inject = result.get("inject", "")
|
|
595
|
+
inject = result.get("dynamicContext") or result.get("inject", "")
|
|
563
596
|
notification = result.get("notifications")
|
|
564
597
|
notification_inject = notification.get("inject", "") if isinstance(notification, dict) else ""
|
|
565
598
|
recall_inject = inject
|
|
@@ -607,7 +640,7 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
607
640
|
# Accumulate assistant side of transcript
|
|
608
641
|
if assistant_content:
|
|
609
642
|
with self._transcript_lock:
|
|
610
|
-
self._transcript_lines.append(f"assistant: {assistant_content}")
|
|
643
|
+
self._transcript_lines.append(f"assistant: {_strip_internal_memory_context(assistant_content)}")
|
|
611
644
|
self._queue_notification_refresh("sync_turn")
|
|
612
645
|
|
|
613
646
|
def _queue_notification_refresh(self, hook: str) -> None:
|
|
@@ -661,6 +694,7 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
661
694
|
self._inject_cache = ""
|
|
662
695
|
with self._prefetch_lock:
|
|
663
696
|
self._prefetch_generation += 1
|
|
697
|
+
self._session_prefetch_result = ""
|
|
664
698
|
self._prefetch_result = ""
|
|
665
699
|
self._notification_result = ""
|
|
666
700
|
|
|
@@ -676,10 +710,13 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
676
710
|
project=self._project,
|
|
677
711
|
)
|
|
678
712
|
if result:
|
|
679
|
-
|
|
680
|
-
|
|
713
|
+
stable_prompt = result.get("stableSystemPrompt") or result.get("inject", "")
|
|
714
|
+
dynamic_context = result.get("dynamicContext", "")
|
|
715
|
+
if stable_prompt and isinstance(stable_prompt, str) and stable_prompt.strip():
|
|
681
716
|
with self._inject_lock:
|
|
682
|
-
self._inject_cache =
|
|
717
|
+
self._inject_cache = stable_prompt
|
|
718
|
+
with self._prefetch_lock:
|
|
719
|
+
self._session_prefetch_result = dynamic_context if isinstance(dynamic_context, str) else ""
|
|
683
720
|
self._identity = result.get("identity")
|
|
684
721
|
self._warnings = result.get("warnings", [])
|
|
685
722
|
self._session_initialized = True
|
|
@@ -732,6 +769,10 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
732
769
|
|
|
733
770
|
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
|
|
734
771
|
"""Call session-end hook to trigger memory extraction from transcript."""
|
|
772
|
+
with self._prefetch_lock:
|
|
773
|
+
self._session_prefetch_result = ""
|
|
774
|
+
self._prefetch_result = ""
|
|
775
|
+
self._notification_result = ""
|
|
735
776
|
if not self._client:
|
|
736
777
|
return
|
|
737
778
|
|
|
@@ -746,7 +787,7 @@ class SignetMemoryProvider(MemoryProvider):
|
|
|
746
787
|
role = msg.get("role", "unknown")
|
|
747
788
|
content = msg.get("content", "")
|
|
748
789
|
if content:
|
|
749
|
-
transcript_lines.append(f"{role}: {content}")
|
|
790
|
+
transcript_lines.append(f"{role}: {_strip_internal_memory_context(str(content))}")
|
|
750
791
|
transcript = "\n\n".join(transcript_lines)
|
|
751
792
|
|
|
752
793
|
if not transcript:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signetai/connector-hermes-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.194.0",
|
|
4
4
|
"description": "Signet connector for Hermes Agent — installs Signet as a pluggable memory provider",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"typecheck": "tsc --noEmit"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@signetai/connector-base": "0.
|
|
29
|
-
"@signetai/core": "0.
|
|
28
|
+
"@signetai/connector-base": "0.194.0",
|
|
29
|
+
"@signetai/core": "0.194.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0",
|