Skip to content

Commit fb20190

Browse files
author
Ismael Marchi
committed
feat(core): introduce plugin architecture for OSS/PRO separation
- Core interfaces for importance scoring, conflict resolution, deduplication - Default OSS implementations (baseline only) - Dynamic plugin loader (synapse_memory_pro) - AutoSaveEngine now strategy-driven - SYNAPSE_MODE enforcement (oss/pro) - Full test coverage for plugin behavior - 265 tests passing, 95% coverage
1 parent 1413f61 commit fb20190

8 files changed

Lines changed: 996 additions & 0 deletions

File tree

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,58 @@ See [mcp-autosave/](mcp-autosave/) for the production MCP Bridge with Supabase p
265265

266266
---
267267

268+
## Plugin Architecture
269+
270+
Synapse Layer uses a **strategy-driven plugin architecture** for clean OSS/PRO separation:
271+
272+
```
273+
┌─────────────────────────────────────────────┐
274+
│ AutoSaveEngine │
275+
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
276+
│ │ Importance│ │ Conflict │ │ Dedup │ │
277+
│ │ Scorer │ │ Resolver │ │ Strategy │ │
278+
│ └─────┬────┘ └─────┬────┘ └─────┬──────┘ │
279+
│ │ │ │ │
280+
│ ┌────▼────────────▼────────────▼────┐ │
281+
│ │ Plugin Loader │ │
282+
│ │ OSS defaults ← PRO override │ │
283+
│ └───────────────────────────────────┘ │
284+
└─────────────────────────────────────────────┘
285+
```
286+
287+
**Core Interfaces** (`synapse_memory.plugins`):
288+
- `ImportanceScorer` — Score event significance (0.0–1.0)
289+
- `ConflictResolver` — Resolve competing events
290+
- `DedupStrategy` — Detect duplicate memories
291+
- `RedactionStrategy` — Extensible content redaction
292+
293+
**OSS** ships with simple, predictable defaults. **PRO** implementations are injected dynamically via `synapse-layer-pro` — no proprietary logic lives in this repo.
294+
295+
```python
296+
# OSS mode (default) — uses built-in defaults
297+
engine = AutoSaveEngine(database=db, redactor=redact)
298+
299+
# PRO mode — automatically loads synapse-layer-pro if installed
300+
# pip install synapse-layer-pro
301+
engine = AutoSaveEngine(database=db, redactor=redact, mode="pro")
302+
303+
# Custom strategies — bring your own
304+
from synapse_memory.plugins import ImportanceScorer
305+
306+
class MyScorer:
307+
def score(self, event) -> float:
308+
return 1.0 if "critical" in event.content else 0.5
309+
310+
engine = AutoSaveEngine(
311+
database=db, redactor=redact,
312+
importance_scorer=MyScorer(),
313+
)
314+
```
315+
316+
Set `SYNAPSE_MODE=pro` to enable PRO features. If `synapse-layer-pro` is not installed, a warning is emitted and OSS defaults are used.
317+
318+
---
319+
268320
## Pricing
269321

270322
| Plan | Price | Memories | Includes |

synapse_memory/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
HandoverToken,
2525
)
2626
from .autosave import AutoSaveEngine, AutoSaveEvent, SaveResult
27+
from .plugins import (
28+
ImportanceScorer,
29+
ConflictResolver,
30+
DedupStrategy,
31+
RedactionStrategy,
32+
SynapseProPlugin,
33+
DefaultImportanceScorer,
34+
DefaultConflictResolver,
35+
DefaultDedupStrategy,
36+
load_pro_plugin,
37+
)
2738

2839
__version__ = "1.0.7"
2940

@@ -63,4 +74,14 @@
6374
"AutoSaveEngine",
6475
"AutoSaveEvent",
6576
"SaveResult",
77+
# Plugin Architecture
78+
"ImportanceScorer",
79+
"ConflictResolver",
80+
"DedupStrategy",
81+
"RedactionStrategy",
82+
"SynapseProPlugin",
83+
"DefaultImportanceScorer",
84+
"DefaultConflictResolver",
85+
"DefaultDedupStrategy",
86+
"load_pro_plugin",
6687
]

synapse_memory/autosave/engine.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@
2929
from .triggers import TriggerDetector
3030
from .formatter import EventFormatter
3131

32+
from synapse_memory.plugins.interfaces import (
33+
ImportanceScorer,
34+
ConflictResolver,
35+
DedupStrategy,
36+
)
37+
from synapse_memory.plugins.defaults import (
38+
DefaultImportanceScorer,
39+
DefaultConflictResolver,
40+
DefaultDedupStrategy,
41+
)
42+
from synapse_memory.plugins.plugin_loader import load_pro_plugin
43+
3244
logger = logging.getLogger("synapse.autosave.engine")
3345

3446

@@ -100,6 +112,11 @@ def __init__(
100112
formatter: Optional[EventFormatter] = None,
101113
cache_maxsize: int = 100,
102114
cache_ttl: float = 60.0,
115+
*,
116+
importance_scorer: Optional[ImportanceScorer] = None,
117+
conflict_resolver: Optional[ConflictResolver] = None,
118+
dedup_strategy: Optional[DedupStrategy] = None,
119+
mode: Optional[str] = None,
103120
) -> None:
104121
self._db = database
105122
self._redactor = redactor
@@ -108,6 +125,34 @@ def __init__(
108125
self._formatter = formatter or EventFormatter()
109126
self._cache = _LRUCache(maxsize=cache_maxsize, ttl=cache_ttl)
110127

128+
# ── Plugin Architecture (OSS/PRO separation) ───────────────
129+
plugin = load_pro_plugin(mode=mode)
130+
131+
self.importance_scorer: ImportanceScorer = (
132+
importance_scorer
133+
or (plugin.importance_scorer if plugin else None)
134+
or DefaultImportanceScorer()
135+
)
136+
self.conflict_resolver: ConflictResolver = (
137+
conflict_resolver
138+
or (plugin.conflict_resolver if plugin else None)
139+
or DefaultConflictResolver()
140+
)
141+
self.dedup_strategy: DedupStrategy = (
142+
dedup_strategy
143+
or (plugin.dedup_strategy if plugin else None)
144+
or DefaultDedupStrategy()
145+
)
146+
147+
self._plugin_loaded = plugin is not None
148+
logger.info(
149+
"AutoSaveEngine initialized: plugin=%s, scorer=%s, resolver=%s, dedup=%s",
150+
"PRO" if self._plugin_loaded else "OSS",
151+
type(self.importance_scorer).__name__,
152+
type(self.conflict_resolver).__name__,
153+
type(self.dedup_strategy).__name__,
154+
)
155+
111156
# ── Public API ─────────────────────────────────────────────────────
112157

113158
def save(self, event: AutoSaveEvent) -> SaveResult:

synapse_memory/plugins/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Synapse Layer — Core Abstractions & Plugin Architecture
3+
4+
Immutable contracts for extensibility:
5+
- ImportanceScorer
6+
- ConflictResolver
7+
- DedupStrategy
8+
- RedactionStrategy
9+
10+
Default OSS implementations provided. PRO implementations
11+
injected dynamically via ``synapse_memory_pro`` package.
12+
13+
Author : Security & Architecture Team @ Synapse Layer
14+
License: Apache 2.0
15+
"""
16+
17+
from .interfaces import (
18+
ImportanceScorer,
19+
ConflictResolver,
20+
DedupStrategy,
21+
RedactionStrategy,
22+
RedactionResult,
23+
SynapseProPlugin,
24+
)
25+
from .defaults import (
26+
DefaultImportanceScorer,
27+
DefaultConflictResolver,
28+
DefaultDedupStrategy,
29+
)
30+
from .plugin_loader import load_pro_plugin
31+
32+
__all__ = [
33+
# Interfaces
34+
"ImportanceScorer",
35+
"ConflictResolver",
36+
"DedupStrategy",
37+
"RedactionStrategy",
38+
"RedactionResult",
39+
"SynapseProPlugin",
40+
# Defaults
41+
"DefaultImportanceScorer",
42+
"DefaultConflictResolver",
43+
"DefaultDedupStrategy",
44+
# Loader
45+
"load_pro_plugin",
46+
]

synapse_memory/plugins/defaults.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Synapse Layer — Default OSS Implementations
3+
4+
Baseline strategies that provide predictable, "good enough" behavior
5+
without any proprietary logic. Designed for transparency and simplicity.
6+
7+
PRO implementations (ML-powered scoring, semantic dedup, conflict
8+
resolution) are available via ``synapse-layer-pro``.
9+
10+
Author : Security & Architecture Team @ Synapse Layer
11+
License: Apache 2.0
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import hashlib
17+
import json
18+
import re
19+
from typing import List
20+
21+
from synapse_memory.autosave.types import AutoSaveEvent
22+
23+
24+
class DefaultImportanceScorer:
25+
"""Baseline importance scorer.
26+
27+
Normalizes the integer importance (0–5) to a 0.0–1.0 float.
28+
No heuristics, no ML — pure linear mapping.
29+
"""
30+
31+
def score(self, event: AutoSaveEvent) -> float:
32+
"""Return event.importance / 5.0, clamped to [0.0, 1.0]."""
33+
raw = max(0, min(5, event.importance))
34+
return round(raw / 5.0, 2)
35+
36+
37+
class DefaultConflictResolver:
38+
"""Baseline conflict resolver.
39+
40+
When multiple events compete, keeps the most recent one
41+
(highest importance as tiebreaker).
42+
"""
43+
44+
def resolve(self, events: List[AutoSaveEvent]) -> AutoSaveEvent:
45+
"""Return the event with highest importance; last wins on tie."""
46+
if not events:
47+
raise ValueError("Cannot resolve empty event list")
48+
return max(events, key=lambda e: (e.importance, id(e)))
49+
50+
51+
class DefaultDedupStrategy:
52+
"""Baseline deduplication via SHA-256 content hash.
53+
54+
Compares normalized (project + content + type) hashes.
55+
No semantic analysis, no embedding similarity.
56+
"""
57+
58+
def is_duplicate(
59+
self,
60+
event: AutoSaveEvent,
61+
recent_events: List[AutoSaveEvent],
62+
) -> bool:
63+
"""True if any recent event has identical normalized hash."""
64+
event_hash = self._hash(event)
65+
return any(self._hash(r) == event_hash for r in recent_events)
66+
67+
@staticmethod
68+
def _hash(event: AutoSaveEvent) -> str:
69+
"""SHA-256 of normalized project + content + type."""
70+
normalized = re.sub(r'\s+', ' ', event.content.strip().lower())
71+
payload = json.dumps(
72+
{
73+
"project": event.project.upper(),
74+
"content": normalized,
75+
"type": event.type,
76+
},
77+
sort_keys=True,
78+
ensure_ascii=False,
79+
)
80+
return hashlib.sha256(payload.encode()).hexdigest()

0 commit comments

Comments
 (0)