feat: RetrievalProfile — named Synapse config profiles with inheritance (#489)#519
Conversation
…emPalace#441) - New mempalace/synapse.py: SynapseDB with retrieval logging, LTP scoring, synaptic tagging, and consolidation candidates - searcher.py: integrate Synapse scoring after search_memories() result construction (fire-and-forget, fail-safe) - config.py: add synapse_enabled, synapse_ltp_window_days, synapse_tagging_window_hours - mcp_server.py: tool_status reports Synapse state and consolidation candidates - 20 new tests in tests/test_synapse.py - Default: synapse_enabled=false (opt-in) - No changes to existing search behavior when disabled Made-with: Cursor
- Config: master switch + ltp/tagging/association toggles, tuning params, log_retrievals and log_retention_days (read from ~/.mempalace/config.json) - SynapseDB: configurable LTP/tagging max boosts, cleanup_old_logs, get_log_stats - searcher: apply axis flags; log retrievals only when synapse_log_retrievals - mcp status: cleanup + refresh_stats(ltp_max_boost) + log_stats in response - Tests: LTP/tagging disabled neutrality, log cleanup retention Made-with: Cursor
…ng params (MemPalace#441) - config.py: synapse_ltp_enabled, synapse_tagging_enabled, synapse_association_enabled, synapse_ltp_max_boost, synapse_tagging_max_boost, synapse_log_retrievals, synapse_log_retention_days - synapse.py: cleanup_old_logs with VACUUM, get_log_stats, max_boost params on LTP/tagging/refresh_stats - searcher.py: per-axis switches control scoring factors - mcp_server.py: tool_status shows axis flags and log stats - 3 new tests (23 total): disabled axes, log cleanup Made-with: Cursor
…usters - log_retrieval upserts co_retrieval pairs; rebuild_co_retrieval_from_log after log cleanup - get_association_scores_batch from co-occurrence strength within hit set - get_top_co_pairs + get_co_occurrence_clusters (union-find on top edges) - config: synapse_association_max_boost, synapse_association_coefficient - tool_status: co_retrieval_top_pairs, co_occurrence_clusters, association tuning keys - Tests: pair increment, association batch, rebuild parity, clusters Made-with: Cursor
…rchive nudges - Chrom metadata synapse_mark=new on new drawers (MCP + miner) - build_soft_archive_proposal for MemPalace#336-style archive wing suggestions - mempalace_status: enriched consolidation_details, phase3 block, tagging-window count - Config: consolidation_inactive_days, soft_archive_suggestions, target_wing Made-with: Cursor
…iner conflicts Made-with: Cursor
…erge Made-with: Cursor
…, wing filter (MemPalace#451) Addresses @web3guru888 review feedback: - search_memories() accepts per-query Synapse overrides (None = config default) - MCP mempalace_search exposes synapse_ltp_enabled, synapse_tagging_enabled - SynapseDB.connection() context manager reduces connection churn - cleanup_old_logs gates VACUUM behind 1000-row threshold - get_consolidation_candidates accepts wing filter - MempalaceConfig instantiated once per search call - 6 new tests (35 total in test_synapse.py)
MemPalace#451) Addresses @web3guru888 observation: log_retrieval failure inside synapse_db.connection() context manager no longer risks rolling back the entire connection. Log writes are non-fatal — search results are returned regardless of logging success. - 1 new test (36 total in test_synapse.py)
…nce (MemPalace#451) - New synapse_profiles.py: ProfileManager with 6-layer merge chain (hardcoded → global synapse_* → config.json default → synapse_profiles.json default → named profile → per-query overrides) - Depth-1 inheritance from 'default', axes_enabled list - searcher.py: resolve profile before scoring, per-query overrides - mcp_server.py: synapse_profile arg with dynamic known_profiles, available_profiles in status - cli.py: mempalace synapse show-profile [NAME] - 14 new tests in test_synapse_profiles.py (50 total synapse tests) - Backward compatible: no profile = existing behavior via global_merged
…e came from (MemPalace#519) - RetrievalProfile tracks source per key (hardcoded / global / default / profile / per-query) - to_annotated_dict() returns {key: {value, source}} pairs - cli show-profile prints '← source' annotation per line - 4 new tests (18 total in test_synapse_profiles.py)
|
This is a proper implementation of the design we worked through in #451 and #489. The 6-layer merge chain is exactly right, and the A few things stand out positively: The
Test coverage is excellent. 531 synapse tests + 207 profile tests for 659 + 238 lines of new code is a healthy ratio. The fact that One design question for the maintainers: the MCP schema exposes Looks ready for review by maintainers. Well done getting this implemented so quickly — the #451 discussion paid off. |
|
@web3guru888 Good call on the observability — implemented in the latest push. Search responses now include both fields: {
"synapse_requested_profile": "oriemt",
"synapse_profile_used": "default"
}When the names match, no fallback occurred. When they differ, the caller knows exactly what happened — no log parsing needed. MCP 20 profile tests, 586 total passing. Ready for maintainer review. |
…ponse (MemPalace#519) - searcher.py: adds synapse_requested_profile and synapse_profile_used to result - mcp_server.py: forwards both fields in tool response metadata - Enables callers (including LLMs) to detect silent profile fallback - 2 new tests (20 total in test_synapse_profiles.py) - Addresses @web3guru888 observability suggestion
This is the right design. The typo-vs-fallback distinction ( The two-field pattern ( 586 tests is a solid foundation — 54 of those being synapse-specific means profile behavior is well-covered. Looking forward to the maintainer review on this one. |
|
@web3guru888 Documented in the PR body — added a profile observability table clarifying null vs string semantics for synapse_requested_profile. Thanks for the thorough reviews throughout this whole Synapse series. |
|
Reviewing as someone who contributed the OODA profile definitions — the implementation looks solid. A few notes: merge chain is correct and the precedence order matches what we discussed. The 6-layer resolution (per-query arg → named profile → default → global synapse_*) gives users the right level of control at each layer without being surprising.
Profile validation at load time: I'd add a validation step when the
Otherwise the implementation is consistent with what was designed in #451. Happy to +1 once the |
…emPalace#519) - ProfileManager._validate(): fail fast on unknown axes, invalid half_life_days, ltp_max_boost < 1.0, tagging_max_boost < 1.0, non-positive window values - Confirmed axes_enabled:[] disables all Synapse axes (raw similarity only) - to_annotated_dict() includes axes_enabled with source tracking - 6 new tests (26 total in test_synapse_profiles.py) - Addresses @web3guru888 review: axes_enabled edge case + load-time validation
|
@web3guru888 All three points addressed in the latest push. 1. # observe profile: axes_enabled: [] → all axes OFF
assert profile.ltp_enabled is False
assert profile.tagging_enabled is False
assert profile.association_enabled is False
# high-LTP drawer scores identical to fresh drawer2. annotated = profile.to_annotated_dict()
assert "axes_enabled" in annotated
assert annotated["axes_enabled"]["value"] == ["ltp", "association"]
assert "profile" in annotated["axes_enabled"]["source"]3. Load-time validation in # Fails fast with clear messages:
# - Unknown axis name → ValueError("Unknown axis 'magic' in axes_enabled")
# - half_life_days <= 0 → ValueError("half_life_days must be > 0 or null")
# - ltp_max_boost < 1.0 → ValueError("ltp_max_boost must be >= 1.0")
# - Same for tagging_max_boost, association_max_boost, window values
# - half_life_days: null is valid (decay off, as in observe profile)26 profile tests, 592 total passing. Ready for your +1. |
|
+1 — all three points fully addressed. The 26 profile tests + 592 total passing. This is ready. 🟢 (For anyone reviewing later: the OODA test fixtures we contributed are in |
…emPalace#519) - HARDCODED_DEFAULTS: added 'description' as passthrough string - cli show-profile: displays description as header line - New tests/fixtures/ooda_profiles.json: 4 OODA profiles contributed by @web3guru888 (orient, observe, decide, act — multiplicative-compatible) - 5 new tests (31 total in test_synapse_profiles.py) - scoring_mode: 'weighted' tracked as follow-up issue Made-with: Cursor
|
@web3guru888 Latest push adds:
31 profile tests + 36 synapse tests = 67 Synapse-specific tests. All review points addressed. |
|
@matrix9neonebuchadnezzar2199-sketch — really nice to see the OODA profiles land as actual test fixtures. The A few quick checks I'd run against the fixtures:
31 profile tests + 67 Synapse tests total is a healthy coverage baseline. The +1 to merge. This is ready. |
- ooda_profiles: decide exploit mode (7d half-life, tagging, no LTP/assoc) - act: LTP + tagging boosts + moderate settings; axes include ltp/tagging so ProfileManager does not disable them from axes_enabled list - Add 4 tests (web3guru888): exploit mode, act tagging, cross-profile isolation Made-with: Cursor
|
@web3guru888 All three suggestions implemented in the latest push. 1. Decide profile — exploit mode: Short half-life + tagging only = surface the most recent, actively-tagged drawers. 2. Act profile — moderate with rapid reinforcement: Note: adjusted 3. Cross-profile isolation: orient = pm.resolve("orient") # ltp_enabled=True, max_boost=2.0
decide = pm.resolve("decide") # ltp_enabled=False
orient_again = pm.resolve("orient") # still ltp_enabled=True, max_boost=2.0No state leakage between resolve calls — each profile starts fresh from the merge chain. 35 profile tests, 601 total passing. Thanks for the thorough review collaboration. |
|
@matrix9neonebuchadnezzar2199-sketch — all three land exactly right. Decide profile: short half-life + tagging-only is the correct exploit mode. No association drift, no LTP amplification — just recency and active intent signals. Exactly what you want when the agent is in a decision loop. Act profile: the Cross-profile isolation: the resolve-test pattern is the right way to verify this. No state leakage between resolve calls is exactly what you need for reliable OODA cycling — if Orient bled into Decide, the exploit phase would carry forward exploration weights. 35 profile tests, 601 total — this is ready. LGTM, +1 to merge. |
web3guru888
left a comment
There was a problem hiding this comment.
All three implementation requests confirmed in the latest push. OODA profiles land as test fixtures with correct parameters (orient=180d LTP, explore=60d assoc, decide=7d tagging-only, act=low decay no-amplification). The show-profile command output is clean, the axes_enabled:[] test validates the disabled-axes case, and load-time validation catches typos at config load rather than query time.
From our integration's perspective, having named profiles with validated fields is exactly the right contract for production use — particularly for the decision loop scenario where a short half-life matters and LTP amplification would be wrong. LGTM, ready to merge.
Adds a named-profile system for Synapse parameters, enabling per-query and per-phase scoring control without argument explosion.
Summary
New
synapse_profiles.pymodule withProfileManagerandRetrievalProfile. Profiles are defined inconfig.jsonundersynapse_profilesor in an optionalsynapse_profiles.jsonoverride file.Merge chain (6 layers)
Inheritance
Depth-1 only — all profiles inherit from
"default". No profile-to-profile chaining.{ "synapse_profiles": { "default": { "half_life_days": 90, "ltp_enabled": true, "ltp_max_boost": 2.0, "tagging_enabled": true, "association_enabled": true }, "orient": { "half_life_days": 180, "ltp_window_days": 60, "tagging_enabled": false }, "decide": { "half_life_days": 30, "ltp_window_days": 14 } } }API usage
MCP
mempalace_searchacceptssynapse_profile(freeform string, soft validation with fallback to"default"). Tool description dynamically lists known profile names.axes_enabled
Per-profile axis opt-in/out list. Useful for ablation testing:
{ "evaluate": { "axes_enabled": ["tagging"], "tagging_window_hours": 48 } }Profile observability
Search responses include
synapse_requested_profileandsynapse_profile_used:synapse_requested_profilesynapse_profile_usednull/ absent"default""orient""orient""oriemt""default"This distinction lets callers (including LLMs) detect silent fallback without log parsing.
CLI
Changes
mempalace/synapse_profiles.py—ProfileManager,RetrievalProfile,HARDCODED_DEFAULTSmempalace/config.py—synapse_profilespropertymempalace/searcher.py— profile resolution before scoring, per-query overridesmempalace/mcp_server.py—synapse_profilearg,available_profilesin statusmempalace/cli.py—mempalace synapse show-profile [NAME]tests/test_synapse_profiles.py— 14 testsTest results
test_synapse_profiles.py: 14 passedtest_synapse.py: 36 passedBackward compatibility
No profiles defined = existing behavior. Global
synapse_*keys in~/.mempalace/config.jsonare merged via the global layer.Depends on
mainand should be merged after feat: Synapse Phase 1 — biologically-inspired memory scoring layer (#441) #451.Follow-up
show-profilesource annotation (← profile / ← default / ← hardcoded) per @web3guru888 suggestion