dsh-biocore 0.1.0 → 0.1.2
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/README.md +2 -2
- package/lib/py/bridge.py +15 -7
- package/lib/py-bridge.js +4 -3
- package/package.json +3 -2
- package/python/biocore_memory/__init__.py +21 -0
- package/python/biocore_memory/adapters/__init__.py +2 -0
- package/python/biocore_memory/adapters/astrbot.py +24 -0
- package/python/biocore_memory/adapters/dsh_cli.py +335 -0
- package/python/biocore_memory/co_recall.py +324 -0
- package/python/biocore_memory/consolidate/__init__.py +11 -0
- package/python/biocore_memory/consolidate/gated.py +104 -0
- package/python/biocore_memory/consolidate/neighbors.py +68 -0
- package/python/biocore_memory/consolidate/reflection.py +45 -0
- package/python/biocore_memory/consolidate/tree.py +95 -0
- package/python/biocore_memory/consolidate/writeback.py +181 -0
- package/python/biocore_memory/core/__init__.py +4 -0
- package/python/biocore_memory/core/config.py +127 -0
- package/python/biocore_memory/core/permissions.py +69 -0
- package/python/biocore_memory/core/types.py +116 -0
- package/python/biocore_memory/core/util.py +26 -0
- package/python/biocore_memory/embedding_local.py +447 -0
- package/python/biocore_memory/embedding_mode.py +156 -0
- package/python/biocore_memory/engine.py +2476 -0
- package/python/biocore_memory/engine_helpers.py +266 -0
- package/python/biocore_memory/event/__init__.py +3 -0
- package/python/biocore_memory/event/_types.py +16 -0
- package/python/biocore_memory/event/consistency.py +117 -0
- package/python/biocore_memory/event/extract.py +48 -0
- package/python/biocore_memory/event/media.py +111 -0
- package/python/biocore_memory/event/notes.py +85 -0
- package/python/biocore_memory/event/notes_link.py +87 -0
- package/python/biocore_memory/event/vector.py +69 -0
- package/python/biocore_memory/evict/__init__.py +3 -0
- package/python/biocore_memory/evict/adaptive.py +73 -0
- package/python/biocore_memory/evict/consequence.py +58 -0
- package/python/biocore_memory/evict/heat.py +54 -0
- package/python/biocore_memory/evict/inhibition.py +105 -0
- package/python/biocore_memory/evict/policy.py +188 -0
- package/python/biocore_memory/evict/score.py +23 -0
- package/python/biocore_memory/identity/__init__.py +6 -0
- package/python/biocore_memory/identity/api.py +99 -0
- package/python/biocore_memory/identity/history.py +9 -0
- package/python/biocore_memory/identity/ledger.py +191 -0
- package/python/biocore_memory/identity/narrative.py +358 -0
- package/python/biocore_memory/identity/snapshot.py +13 -0
- package/python/biocore_memory/identity/writeback.py +61 -0
- package/python/biocore_memory/index/__init__.py +3 -0
- package/python/biocore_memory/index/cue_index.py +80 -0
- package/python/biocore_memory/index/entities.py +85 -0
- package/python/biocore_memory/index/entity_boost.py +104 -0
- package/python/biocore_memory/index/graph.py +172 -0
- package/python/biocore_memory/recall/__init__.py +5 -0
- package/python/biocore_memory/recall/api.py +65 -0
- package/python/biocore_memory/recall/bm25.py +118 -0
- package/python/biocore_memory/recall/channels.py +98 -0
- package/python/biocore_memory/recall/complexity.py +62 -0
- package/python/biocore_memory/recall/constraints.py +75 -0
- package/python/biocore_memory/recall/embedding.py +745 -0
- package/python/biocore_memory/recall/expand.py +26 -0
- package/python/biocore_memory/recall/filters.py +40 -0
- package/python/biocore_memory/recall/fusion.py +18 -0
- package/python/biocore_memory/recall/mmr.py +70 -0
- package/python/biocore_memory/recall/ppr.py +85 -0
- package/python/biocore_memory/recall/retrieval_practice.py +102 -0
- package/python/biocore_memory/relay/__init__.py +2 -0
- package/python/biocore_memory/relay/card.py +51 -0
- package/python/biocore_memory/slots/__init__.py +18 -0
- package/python/biocore_memory/slots/admission.py +64 -0
- package/python/biocore_memory/slots/boundary.py +128 -0
- package/python/biocore_memory/slots/evict_bridge.py +106 -0
- package/python/biocore_memory/slots/registry.py +135 -0
- package/python/biocore_memory/slots/switch.py +42 -0
- package/python/biocore_memory/store/__init__.py +4 -0
- package/python/biocore_memory/store/audit.py +74 -0
- package/python/biocore_memory/store/compress.py +81 -0
- package/python/biocore_memory/store/dedupe.py +178 -0
- package/python/biocore_memory/store/json_store.py +154 -0
- package/python/biocore_memory/store/media.py +145 -0
- package/python/biocore_memory/store/provenance.py +73 -0
- package/python/biocore_memory/store/rate_limit.py +93 -0
- package/python/biocore_memory/store/security.py +107 -0
- package/python/biocore_memory/store/source_consistency.py +123 -0
- package/python/biocore_memory/store/tiering.py +61 -0
- package/python/biocore_memory/store/tiering_exec.py +50 -0
- package/python/biocore_memory/store/tombstone.py +23 -0
- package/python/biocore_memory/store/versions.py +69 -0
- package/python/biocore_memory/thread/__init__.py +2 -0
- package/python/biocore_memory/thread/card.py +58 -0
- package/python/biocore_model/__init__.py +37 -0
- package/python/biocore_model/affective_labor.py +162 -0
- package/python/biocore_model/appraisal.py +257 -0
- package/python/biocore_model/appraisal_action.py +78 -0
- package/python/biocore_model/archive_queue.py +60 -0
- package/python/biocore_model/attachment_behavior.py +130 -0
- package/python/biocore_model/attachment_regulation.py +75 -0
- package/python/biocore_model/attachment_state.py +41 -0
- package/python/biocore_model/attribution.py +73 -0
- package/python/biocore_model/attributor.py +162 -0
- package/python/biocore_model/autonomic.py +91 -0
- package/python/biocore_model/autonomy_affect.py +51 -0
- package/python/biocore_model/autonomy_check.py +67 -0
- package/python/biocore_model/autonomy_loop.py +122 -0
- package/python/biocore_model/autonomy_mode.py +114 -0
- package/python/biocore_model/autonomy_model.py +733 -0
- package/python/biocore_model/autonomy_params.py +131 -0
- package/python/biocore_model/autonomy_reason.py +73 -0
- package/python/biocore_model/autonomy_tick.py +227 -0
- package/python/biocore_model/bio_appraisal.py +49 -0
- package/python/biocore_model/bio_autonomy_state.py +171 -0
- package/python/biocore_model/bio_config.py +221 -0
- package/python/biocore_model/bio_const.py +128 -0
- package/python/biocore_model/bio_daily.py +267 -0
- package/python/biocore_model/bio_emotion.py +538 -0
- package/python/biocore_model/bio_emotion2.py +214 -0
- package/python/biocore_model/bio_goal.py +220 -0
- package/python/biocore_model/bio_identity.py +82 -0
- package/python/biocore_model/bio_noise.py +119 -0
- package/python/biocore_model/bio_physiology2.py +114 -0
- package/python/biocore_model/bio_snapshot.py +324 -0
- package/python/biocore_model/bio_state.py +272 -0
- package/python/biocore_model/bio_tick.py +310 -0
- package/python/biocore_model/bio_util.py +82 -0
- package/python/biocore_model/blocks.py +264 -0
- package/python/biocore_model/boundary.py +251 -0
- package/python/biocore_model/boundary_gate.py +188 -0
- package/python/biocore_model/commitment_extractor.py +517 -0
- package/python/biocore_model/compassion_fatigue.py +84 -0
- package/python/biocore_model/connection_bids.py +120 -0
- package/python/biocore_model/core_bridge.py +177 -0
- package/python/biocore_model/core_client.py +127 -0
- package/python/biocore_model/core_commands.py +112 -0
- package/python/biocore_model/core_mode.py +104 -0
- package/python/biocore_model/core_mode_ui.mjs +104 -0
- package/python/biocore_model/criticality.py +81 -0
- package/python/biocore_model/defense_mechanisms.py +272 -0
- package/python/biocore_model/delta_budget.py +99 -0
- package/python/biocore_model/diag_trace.py +274 -0
- package/python/biocore_model/dissonance.py +134 -0
- package/python/biocore_model/emotion_geometry.py +108 -0
- package/python/biocore_model/emotion_probe.py +209 -0
- package/python/biocore_model/entity_graph.py +931 -0
- package/python/biocore_model/entity_resolve.py +260 -0
- package/python/biocore_model/envy.py +193 -0
- package/python/biocore_model/exit_or_silence.py +230 -0
- package/python/biocore_model/expr_guard.py +24 -0
- package/python/biocore_model/expr_intent.py +77 -0
- package/python/biocore_model/expr_state.py +204 -0
- package/python/biocore_model/expr_template.py +105 -0
- package/python/biocore_model/expr_tone.py +81 -0
- package/python/biocore_model/extreme_scenarios.py +205 -0
- package/python/biocore_model/feedback_flow.py +227 -0
- package/python/biocore_model/feedback_guard.py +129 -0
- package/python/biocore_model/feedback_llm.py +163 -0
- package/python/biocore_model/feedback_memory.py +96 -0
- package/python/biocore_model/feedback_user.py +93 -0
- package/python/biocore_model/frustration_curve.py +110 -0
- package/python/biocore_model/granularity.py +283 -0
- package/python/biocore_model/graph_extract.py +248 -0
- package/python/biocore_model/graph_recall.py +221 -0
- package/python/biocore_model/intent_render.py +91 -0
- package/python/biocore_model/intero_core.py +235 -0
- package/python/biocore_model/intero_style.py +116 -0
- package/python/biocore_model/layered_memory.py +297 -0
- package/python/biocore_model/learning_probe.py +120 -0
- package/python/biocore_model/memory_dual.py +224 -0
- package/python/biocore_model/memory_forgetting.py +125 -0
- package/python/biocore_model/memory_store.py +1399 -0
- package/python/biocore_model/metacog.py +153 -0
- package/python/biocore_model/migrate.py +733 -0
- package/python/biocore_model/migrate_store.py +337 -0
- package/python/biocore_model/mind_wander.py +111 -0
- package/python/biocore_model/mood.py +48 -0
- package/python/biocore_model/narrative.py +172 -0
- package/python/biocore_model/needs_esteem.py +116 -0
- package/python/biocore_model/needs_events.py +137 -0
- package/python/biocore_model/needs_sleep.py +72 -0
- package/python/biocore_model/noise_bank.py +95 -0
- package/python/biocore_model/persona_flow.mjs +25 -0
- package/python/biocore_model/pipeline.py +240 -0
- package/python/biocore_model/pipeline_api.py +200 -0
- package/python/biocore_model/plugin_config.py +83 -0
- package/python/biocore_model/plugin_routes.py +1435 -0
- package/python/biocore_model/policy_learning.py +132 -0
- package/python/biocore_model/possible_selves.py +110 -0
- package/python/biocore_model/predictor.py +178 -0
- package/python/biocore_model/preset.py +205 -0
- package/python/biocore_model/proactive_context.py +153 -0
- package/python/biocore_model/prompt_rules.py +90 -0
- package/python/biocore_model/prospective.py +183 -0
- package/python/biocore_model/provider_fallback.py +97 -0
- package/python/biocore_model/reconstruction.py +132 -0
- package/python/biocore_model/regulation_loop.py +179 -0
- package/python/biocore_model/regulation_strategies.py +294 -0
- package/python/biocore_model/relation_stage.py +130 -0
- package/python/biocore_model/relationship_stage.py +95 -0
- package/python/biocore_model/render.py +901 -0
- package/python/biocore_model/render_guard.mjs +34 -0
- package/python/biocore_model/repair_after_boundary.py +350 -0
- package/python/biocore_model/reply_limit.py +183 -0
- package/python/biocore_model/reply_split.py +203 -0
- package/python/biocore_model/rollback.py +143 -0
- package/python/biocore_model/runtime.py +479 -0
- package/python/biocore_model/sanitize.py +200 -0
- package/python/biocore_model/scheduler.py +123 -0
- package/python/biocore_model/schema.py +120 -0
- package/python/biocore_model/secrecy.py +145 -0
- package/python/biocore_model/self_esteem.py +50 -0
- package/python/biocore_model/self_intent.py +437 -0
- package/python/biocore_model/self_interest.py +263 -0
- package/python/biocore_model/self_verification.py +178 -0
- package/python/biocore_model/services.py +78 -0
- package/python/biocore_model/situation.py +254 -0
- package/python/biocore_model/sleep_circadian.py +116 -0
- package/python/biocore_model/social_pain.py +69 -0
- package/python/biocore_model/social_repair.py +111 -0
- package/python/biocore_model/social_theory.py +131 -0
- package/python/biocore_model/source_monitoring.py +292 -0
- package/python/biocore_model/stages.py +487 -0
- package/python/biocore_model/state_tag.py +231 -0
- package/python/biocore_model/switch_groups.mjs +201 -0
- package/python/biocore_model/tabs_state.mjs +37 -0
- package/python/biocore_model/template_expr.py +252 -0
- package/python/biocore_model/tools/caps_check.mjs +23 -0
- package/python/biocore_model/tools/init_symbols_check.mjs +11 -0
- package/python/biocore_model/tools/missing_ids.mjs +16 -0
- package/python/biocore_model/tools/scope_check.mjs +19 -0
- package/python/biocore_model/topic_control.py +268 -0
- package/python/biocore_model/turn_memory.py +152 -0
- package/python/biocore_model/ui_compat.mjs +48 -0
- package/python/biocore_model/ui_settings.py +266 -0
- package/python/biocore_model/ui_settings.py.bak_authority +108 -0
- package/python/biocore_model/value_order.py +186 -0
- package/python/biocore_model/view_mode.mjs +8 -0
- package/python/biocore_model/voice_bank.py +177 -0
- package/python/biocore_model/waiting_sense.py +229 -0
- package/python/biocore_model/x_directives.py +142 -0
- package/python/biocore_runtime/__init__.py +7 -0
- package/python/biocore_runtime/pipeline.py +1915 -0
- package/python/dsh_adapter/README.md +96 -0
- package/python/dsh_adapter/adapter.py +1077 -0
- package/python/dsh_adapter/cli.py +107 -0
- package/python/dsh_adapter/data/demo_state.json +164 -0
- package/python/dsh_adapter/data/verify_state.json +164 -0
- package/python/dsh_adapter/tests/__init__.py +1 -0
- package/python/dsh_adapter/tests/test_adapter.py +494 -0
- package/ui/panel-layout.js +3 -3
- package/ui/panel.css +53 -6
- package/ui/panel.html +33 -32
- package/ui/panel.js +9 -1
- package/lib/py/__pycache__/bridge.cpython-310.pyc +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Notes bridge: attach an A-MEM style note to one memory entry.
|
|
3
|
+
|
|
4
|
+
Calls event.notes.make_note(text, tags, links) and stores the bounded result
|
|
5
|
+
as metadata["note"] = {title, summary, tags, links}. Only metadata is written:
|
|
6
|
+
text / id / role / ts / vector are never modified.
|
|
7
|
+
|
|
8
|
+
Interface:
|
|
9
|
+
enrich_entry(entry, enabled=None, tags=None, links=None) -> entry
|
|
10
|
+
|
|
11
|
+
The module is disabled by default (NOTES_LINK_ENABLED=False): callers gate on
|
|
12
|
+
'enabled' before writing. Passing enabled=False forces a no-op.
|
|
13
|
+
No IO, no LLM, no host imports.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .notes import make_note
|
|
18
|
+
|
|
19
|
+
enabled = False
|
|
20
|
+
NOTES_LINK_ENABLED = enabled
|
|
21
|
+
|
|
22
|
+
NOTE_KEY = "note"
|
|
23
|
+
MAX_LINKS = 8
|
|
24
|
+
MAX_SOURCE_ITEMS = 32
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _as_list(value):
|
|
28
|
+
if value is None:
|
|
29
|
+
return []
|
|
30
|
+
if isinstance(value, str):
|
|
31
|
+
return [value]
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
return list(value.keys())
|
|
34
|
+
try:
|
|
35
|
+
return list(value)
|
|
36
|
+
except TypeError:
|
|
37
|
+
return [value]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _entry_tags(entry):
|
|
41
|
+
tags = []
|
|
42
|
+
for key in ("tags", "labels", "topics"):
|
|
43
|
+
tags.extend(_as_list(entry.get(key)))
|
|
44
|
+
cue = entry.get("cue_index")
|
|
45
|
+
if isinstance(cue, dict):
|
|
46
|
+
tags.extend(_as_list(cue.get("entities")))
|
|
47
|
+
tags.extend(_as_list(cue.get("place")))
|
|
48
|
+
tags.append(cue.get("event_name"))
|
|
49
|
+
metadata = entry.get("metadata")
|
|
50
|
+
if isinstance(metadata, dict):
|
|
51
|
+
tags.extend(_as_list(metadata.get("tags")))
|
|
52
|
+
return tags[:MAX_SOURCE_ITEMS]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _entry_links(entry):
|
|
56
|
+
links = []
|
|
57
|
+
for key in ("related_ids", "links"):
|
|
58
|
+
links.extend(_as_list(entry.get(key)))
|
|
59
|
+
cue = entry.get("cue_index")
|
|
60
|
+
if isinstance(cue, dict):
|
|
61
|
+
links.extend(_as_list(cue.get("related_ids")))
|
|
62
|
+
metadata = entry.get("metadata")
|
|
63
|
+
if isinstance(metadata, dict):
|
|
64
|
+
links.extend(_as_list(metadata.get("links")))
|
|
65
|
+
return links[:MAX_SOURCE_ITEMS]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def enrich_entry(entry, enabled=None, tags=None, links=None):
|
|
69
|
+
"""Attach metadata["note"] and return the entry (unchanged if disabled)."""
|
|
70
|
+
if enabled is False or not isinstance(entry, dict):
|
|
71
|
+
return entry
|
|
72
|
+
text = entry.get("text") or entry.get("content") or ""
|
|
73
|
+
if not str(text or "").strip():
|
|
74
|
+
return entry
|
|
75
|
+
note_tags = tags if tags is not None else _entry_tags(entry)
|
|
76
|
+
note_links = links if links is not None else _entry_links(entry)
|
|
77
|
+
note = make_note(text, tags=note_tags, links=note_links)
|
|
78
|
+
metadata = entry.get("metadata")
|
|
79
|
+
if not isinstance(metadata, dict):
|
|
80
|
+
metadata = {}
|
|
81
|
+
entry["metadata"] = metadata
|
|
82
|
+
metadata[NOTE_KEY] = note
|
|
83
|
+
return entry
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = ["enabled", "NOTES_LINK_ENABLED", "enrich_entry", "NOTE_KEY",
|
|
87
|
+
"MAX_LINKS"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Seven-dimension event vector (host-neutral; no I/O, no prompt)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from ..core.types import EventVector
|
|
5
|
+
from ..core.util import clamp01
|
|
6
|
+
|
|
7
|
+
DIMENSIONS = ("intensity", "self_relevance", "schema_conflict",
|
|
8
|
+
"relation_weight", "novelty", "unresolved", "repetition")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_vector(meta=None, affect=None) -> EventVector:
|
|
12
|
+
"""Build the seven-dim event vector from metadata and an L1 affect snapshot.
|
|
13
|
+
|
|
14
|
+
Missing values default to neutral (0.0); all dims are clamped to [0, 1].
|
|
15
|
+
Deterministic and pure: no host access, no LLM, no writes.
|
|
16
|
+
"""
|
|
17
|
+
m = meta if isinstance(meta, dict) else {}
|
|
18
|
+
a = affect if isinstance(affect, dict) else {}
|
|
19
|
+
return EventVector(
|
|
20
|
+
intensity=clamp01(m.get("intensity", a.get("intensity", 0.0))),
|
|
21
|
+
self_relevance=clamp01(m.get("self_relevance",
|
|
22
|
+
m.get("self_defining", a.get("self_relevance", 0.0)))),
|
|
23
|
+
schema_conflict=clamp01(m.get("schema_conflict",
|
|
24
|
+
m.get("conflict", a.get("schema_conflict", 0.0)))),
|
|
25
|
+
relation_weight=clamp01(m.get("relation_weight",
|
|
26
|
+
a.get("relation_weight", a.get("trust", 0.0)))),
|
|
27
|
+
novelty=clamp01(m.get("novelty", a.get("novelty", 0.0))),
|
|
28
|
+
unresolved=clamp01(m.get("unresolved", a.get("unresolved", 0.0))),
|
|
29
|
+
repetition=clamp01(m.get("repetition", a.get("repetition", 0.0))),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def vector_dict(meta=None, affect=None) -> dict:
|
|
34
|
+
"""Return extract_vector() as a plain dict (JSON-friendly)."""
|
|
35
|
+
return extract_vector(meta, affect).as_dict()
|
|
36
|
+
|
|
37
|
+
def value_score(meta=None, affect=None) -> float:
|
|
38
|
+
"""Deterministic memory-value score (trust is deliberately excluded).
|
|
39
|
+
|
|
40
|
+
value = BASE + W_I*intensity + W_V*|valence| + W_S*self_relevance
|
|
41
|
+
+ W_M*mainline - W_R*routine
|
|
42
|
+
Missing fields default to neutral; result clamped to [0, 1].
|
|
43
|
+
"""
|
|
44
|
+
from ..core.config import (VALUE_BASE, VALUE_W_INTENSITY, VALUE_W_VALENCE,
|
|
45
|
+
VALUE_W_SELF, VALUE_W_MAINLINE, VALUE_W_ROUTINE)
|
|
46
|
+
m = meta if isinstance(meta, dict) else {}
|
|
47
|
+
a = affect if isinstance(affect, dict) else {}
|
|
48
|
+
vec = extract_vector(m, a)
|
|
49
|
+
try:
|
|
50
|
+
valence = abs(float(m.get("valence", a.get("valence", 0.0)) or 0.0))
|
|
51
|
+
except Exception:
|
|
52
|
+
valence = 0.0
|
|
53
|
+
mainline = m.get("mainline", a.get("goals") and 0.5 or 0.0)
|
|
54
|
+
try:
|
|
55
|
+
mainline = float(mainline or 0.0)
|
|
56
|
+
except Exception:
|
|
57
|
+
mainline = 0.0
|
|
58
|
+
routine = m.get("routine", 0.0)
|
|
59
|
+
try:
|
|
60
|
+
routine = float(routine or 0.0)
|
|
61
|
+
except Exception:
|
|
62
|
+
routine = 0.0
|
|
63
|
+
value = (VALUE_BASE
|
|
64
|
+
+ VALUE_W_INTENSITY * vec.intensity
|
|
65
|
+
+ VALUE_W_VALENCE * clamp01(valence)
|
|
66
|
+
+ VALUE_W_SELF * vec.self_relevance
|
|
67
|
+
+ VALUE_W_MAINLINE * clamp01(mainline)
|
|
68
|
+
- VALUE_W_ROUTINE * clamp01(routine))
|
|
69
|
+
return round(clamp01(value), 6)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Adaptive forgetting rate (default OFF; pure function, no IO).
|
|
3
|
+
|
|
4
|
+
adaptive_score(base, load, importance) = base * (1 + 0.5*load) * (1 - 0.3*importance)
|
|
5
|
+
clamped to 0..1. load and importance are normalized to 0..1.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
ADAPTIVE_ENABLED = False
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _clamp(value):
|
|
13
|
+
try:
|
|
14
|
+
return max(0.0, min(1.0, float(value)))
|
|
15
|
+
except Exception:
|
|
16
|
+
return 0.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def adaptive_score(base, load=0.0, importance=0.0):
|
|
20
|
+
"""Return the bounded adaptive eviction score (higher = evict earlier)."""
|
|
21
|
+
b = _clamp(base)
|
|
22
|
+
l = _clamp(load)
|
|
23
|
+
i = _clamp(importance)
|
|
24
|
+
return round(_clamp(b * (1.0 + 0.5 * l) * (1.0 - 0.3 * i)), 6)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _base_of(entry, base_scores):
|
|
28
|
+
if isinstance(base_scores, dict):
|
|
29
|
+
try:
|
|
30
|
+
return float(base_scores.get(str(entry.get("id") or "")) or 0.0)
|
|
31
|
+
except Exception:
|
|
32
|
+
return 0.0
|
|
33
|
+
for key in ("evict_score", "score", "base_score"):
|
|
34
|
+
if key in entry:
|
|
35
|
+
try:
|
|
36
|
+
return float(entry.get(key) or 0.0)
|
|
37
|
+
except Exception:
|
|
38
|
+
return 0.0
|
|
39
|
+
return 0.0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _load_of(entry, load):
|
|
43
|
+
if load is not None:
|
|
44
|
+
return _clamp(load)
|
|
45
|
+
for key in ("load", "pressure", "allostatic_load"):
|
|
46
|
+
if key in entry:
|
|
47
|
+
return _clamp(entry.get(key))
|
|
48
|
+
return 0.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def rank(entries, base_scores=None, load=None):
|
|
52
|
+
"""Return [(id, score)] ordered by adaptive score descending (evict-first)."""
|
|
53
|
+
rows = [e for e in (entries or []) if isinstance(e, dict)]
|
|
54
|
+
if not ADAPTIVE_ENABLED:
|
|
55
|
+
out = [(str(e.get("id") or ""), _clamp(_base_of(e, base_scores))) for e in rows]
|
|
56
|
+
out.sort(key=lambda x: (-x[1], x[0]))
|
|
57
|
+
return out
|
|
58
|
+
scored = []
|
|
59
|
+
for entry in rows:
|
|
60
|
+
score = adaptive_score(_base_of(entry, base_scores),
|
|
61
|
+
_load_of(entry, load), entry.get("importance", 0.0))
|
|
62
|
+
scored.append((str(entry.get("id") or ""), score))
|
|
63
|
+
scored.sort(key=lambda x: (-x[1], x[0]))
|
|
64
|
+
return scored
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def select(entries, budget, base_scores=None, load=None):
|
|
68
|
+
"""Return ids of the top-budget entries by adaptive eviction score."""
|
|
69
|
+
try:
|
|
70
|
+
limit = max(0, int(budget or 0))
|
|
71
|
+
except Exception:
|
|
72
|
+
limit = 0
|
|
73
|
+
return [eid for eid, _score in rank(entries, base_scores, load)[:limit]]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Consequence-aware eviction (pure; default OFF)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..core.util import clamp01
|
|
6
|
+
|
|
7
|
+
CONSEQUENCE_ENABLED = False
|
|
8
|
+
PROMISE_WORDS = ("答应", "承诺", "保证", "约定", "说好", "一定会", "不再", "永远")
|
|
9
|
+
DEADLINE_WORDS = ("明天", "后天", "截止", "ddl", "deadline", "之前", "周", "月", "日", "点")
|
|
10
|
+
RISK_WORDS = ("危险", "风险", "生病", "医院", "药", "分手", "绝交", "吵架", "难过", "崩溃",
|
|
11
|
+
"secret", "密码", "隐私", "重要")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _matches(text, words):
|
|
15
|
+
value = str(text or "").lower()
|
|
16
|
+
return any(str(word).lower() in value for word in words)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _score(entry):
|
|
20
|
+
e = entry if isinstance(entry, dict) else {}
|
|
21
|
+
text = " ".join(str(e.get(k) or "") for k in ("text", "title", "event", "big_summary"))
|
|
22
|
+
importance = clamp01(e.get("importance"), 0.5)
|
|
23
|
+
promise = 0.30 if _matches(text, PROMISE_WORDS) or e.get("promise") else 0.0
|
|
24
|
+
deadline = 0.25 if _matches(text, DEADLINE_WORDS) or e.get("deadline") else 0.0
|
|
25
|
+
risk = 0.20 if _matches(text, RISK_WORDS) or e.get("risk") else 0.0
|
|
26
|
+
explicit = 0.25 * clamp01(e.get("consequence"), 0.0) if "consequence" in e else 0.0
|
|
27
|
+
return clamp01(0.35 * importance + promise + deadline + risk + explicit, 0.0)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def consequence(entry):
|
|
31
|
+
"""0..1 consequence of forgetting an entry (higher = should keep)."""
|
|
32
|
+
if not CONSEQUENCE_ENABLED or not isinstance(entry, dict):
|
|
33
|
+
return 0.0
|
|
34
|
+
try:
|
|
35
|
+
return round(_score(entry), 4)
|
|
36
|
+
except Exception:
|
|
37
|
+
return 0.0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def select_by_consequence(entries, budget):
|
|
41
|
+
"""Return ids to evict first: lowest consequence, protected entries skipped."""
|
|
42
|
+
if not CONSEQUENCE_ENABLED:
|
|
43
|
+
return []
|
|
44
|
+
try:
|
|
45
|
+
limit = max(0, int(budget or 0))
|
|
46
|
+
except Exception:
|
|
47
|
+
limit = 0
|
|
48
|
+
if not limit:
|
|
49
|
+
return []
|
|
50
|
+
rows = []
|
|
51
|
+
for entry in entries or []:
|
|
52
|
+
if not isinstance(entry, dict):
|
|
53
|
+
continue
|
|
54
|
+
if entry.get("pin") or entry.get("pinned") or entry.get("self_defining") or entry.get("identity"):
|
|
55
|
+
continue
|
|
56
|
+
rows.append((_score(entry), str(entry.get("id") or entry.get("memory_id") or "")))
|
|
57
|
+
rows.sort(key=lambda row: (row[0], row[1]))
|
|
58
|
+
return [eid for _score_value, eid in rows if eid][:limit]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""MemoryOS-style heat score (pure; default OFF)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from ..core.util import clamp01
|
|
9
|
+
|
|
10
|
+
HEAT_ENABLED = False
|
|
11
|
+
ALPHA = 1.0
|
|
12
|
+
BETA = 1.0
|
|
13
|
+
GAMMA = 1.0
|
|
14
|
+
TAU_HOURS = 24.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _num(entry, keys):
|
|
18
|
+
for key in keys:
|
|
19
|
+
try:
|
|
20
|
+
value = float(entry.get(key) or 0.0)
|
|
21
|
+
return max(0.0, value)
|
|
22
|
+
except Exception:
|
|
23
|
+
continue
|
|
24
|
+
return 0.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _last_ts(entry):
|
|
28
|
+
for key in ("last_recall", "last_access", "last_visit_time", "ts", "created_ts"):
|
|
29
|
+
try:
|
|
30
|
+
value = float(entry.get(key) or 0.0)
|
|
31
|
+
if value > 0:
|
|
32
|
+
return value
|
|
33
|
+
except Exception:
|
|
34
|
+
continue
|
|
35
|
+
return 0.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def heat(entry, now=None):
|
|
39
|
+
"""heat = alpha*visits + beta*interactions + gamma*recency, normalized 0..1."""
|
|
40
|
+
if not HEAT_ENABLED or not isinstance(entry, dict):
|
|
41
|
+
return 0.0
|
|
42
|
+
try:
|
|
43
|
+
stamp = float(now if now is not None else time.time())
|
|
44
|
+
visits = _num(entry, ("visits", "visit_count", "N_visit", "hits"))
|
|
45
|
+
interactions = _num(entry, ("interactions", "interaction_count", "L_interaction"))
|
|
46
|
+
last = _last_ts(entry)
|
|
47
|
+
age_hours = max(0.0, stamp - last) / 3600.0 if last else 0.0
|
|
48
|
+
recency = 0.5 ** (age_hours / TAU_HOURS)
|
|
49
|
+
v = visits / (1.0 + visits)
|
|
50
|
+
i = interactions / (1.0 + interactions)
|
|
51
|
+
total = ALPHA + BETA + GAMMA
|
|
52
|
+
return clamp01((ALPHA * v + BETA * i + GAMMA * recency) / total, 0.0)
|
|
53
|
+
except Exception:
|
|
54
|
+
return 0.0
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Retrieval-induced forgetting (RIF) inhibition (pure; default OFF).
|
|
3
|
+
|
|
4
|
+
Retrieving a target suppresses competing memories (Anderson, Bjork & Bjork
|
|
5
|
+
1994). Entries that lost the retrieval competition get a bounded inhibition
|
|
6
|
+
budget that decays with a 7-day half-life.
|
|
7
|
+
|
|
8
|
+
Interface:
|
|
9
|
+
mark_inhibited(entries, selected_ids, increment=0.2, cap=1.0) -> [ids]
|
|
10
|
+
Adds 'increment' to every entry whose id is not in selected_ids
|
|
11
|
+
(in place, bounded by cap) and returns the touched ids in input order.
|
|
12
|
+
decay(entry, days=7.0) -> float
|
|
13
|
+
Inhibition value after 'days' of exponential decay (7-day half-life).
|
|
14
|
+
filter_inhibited(entries, threshold=0.5, days=None) -> list
|
|
15
|
+
Entries whose (optionally decayed) inhibition is below the threshold.
|
|
16
|
+
|
|
17
|
+
The module is disabled by default: callers gate on 'enabled' before applying
|
|
18
|
+
mutations. Helpers are deterministic and never touch the file system.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
|
|
24
|
+
enabled = False
|
|
25
|
+
INHIBITION_ENABLED = enabled
|
|
26
|
+
|
|
27
|
+
INCREMENT = 0.2
|
|
28
|
+
MAX_INHIBITION = 1.0
|
|
29
|
+
HALF_LIFE_DAYS = 7.0
|
|
30
|
+
THRESHOLD = 0.5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _num(value, default=0.0):
|
|
34
|
+
try:
|
|
35
|
+
value = float(value)
|
|
36
|
+
except (TypeError, ValueError):
|
|
37
|
+
return default
|
|
38
|
+
return value if math.isfinite(value) else default
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _clamp(value, low, high):
|
|
42
|
+
return max(low, min(high, value))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _entry_id(entry):
|
|
46
|
+
if not isinstance(entry, dict):
|
|
47
|
+
return ""
|
|
48
|
+
return str(entry.get("id") or entry.get("memory_id") or "")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _value(entry):
|
|
52
|
+
if not isinstance(entry, dict):
|
|
53
|
+
return 0.0
|
|
54
|
+
return _clamp(_num(entry.get("inhibition", 0.0), 0.0), 0.0, MAX_INHIBITION)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _rows(entries):
|
|
58
|
+
try:
|
|
59
|
+
return list(entries or [])
|
|
60
|
+
except TypeError:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def mark_inhibited(entries, selected_ids, increment=INCREMENT,
|
|
65
|
+
cap=MAX_INHIBITION):
|
|
66
|
+
"""Inhibit every unselected entry by 'increment' (bounded); return ids."""
|
|
67
|
+
selected = {str(value) for value in (selected_ids or []) if str(value)}
|
|
68
|
+
step = _clamp(_num(increment, INCREMENT), 0.0, MAX_INHIBITION)
|
|
69
|
+
ceiling = _clamp(_num(cap, MAX_INHIBITION), 0.0, MAX_INHIBITION)
|
|
70
|
+
touched = []
|
|
71
|
+
for entry in _rows(entries):
|
|
72
|
+
entry_id = _entry_id(entry)
|
|
73
|
+
if not entry_id or entry_id in selected:
|
|
74
|
+
continue
|
|
75
|
+
old = _value(entry)
|
|
76
|
+
new = min(ceiling, old + step)
|
|
77
|
+
if new <= old:
|
|
78
|
+
continue
|
|
79
|
+
entry["inhibition"] = round(new, 4)
|
|
80
|
+
touched.append(entry_id)
|
|
81
|
+
return touched
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def decay(entry, days=HALF_LIFE_DAYS):
|
|
85
|
+
"""Return the inhibition after 'days' of decay (7-day half-life)."""
|
|
86
|
+
age = max(0.0, _num(days, HALF_LIFE_DAYS))
|
|
87
|
+
return round(_value(entry) * math.pow(0.5, age / HALF_LIFE_DAYS), 4)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def filter_inhibited(entries, threshold=THRESHOLD, days=None):
|
|
91
|
+
"""Return entries with inhibition below 'threshold' (not suppressed)."""
|
|
92
|
+
limit = _clamp(_num(threshold, THRESHOLD), 0.0, MAX_INHIBITION)
|
|
93
|
+
kept = []
|
|
94
|
+
for entry in _rows(entries):
|
|
95
|
+
if not isinstance(entry, dict):
|
|
96
|
+
continue
|
|
97
|
+
value = _value(entry) if days is None else decay(entry, days)
|
|
98
|
+
if value < limit:
|
|
99
|
+
kept.append(entry)
|
|
100
|
+
return kept
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = ["enabled", "INHIBITION_ENABLED", "mark_inhibited", "decay",
|
|
104
|
+
"filter_inhibited", "INCREMENT", "MAX_INHIBITION",
|
|
105
|
+
"HALF_LIFE_DAYS", "THRESHOLD"]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Read-only eviction decisions (never deletes, never writes).
|
|
3
|
+
|
|
4
|
+
eviction_order() keeps the legacy candidate ordering used by the engine.
|
|
5
|
+
tick(entries, now) is the M2 read-only decision pass: it partitions entries
|
|
6
|
+
into archive / flag / keep for the caller to act on later.
|
|
7
|
+
|
|
8
|
+
Protection rules (never archived):
|
|
9
|
+
- pinned / desktop-anchor style pin;
|
|
10
|
+
- self-defining / identity entries;
|
|
11
|
+
- explicit promises / open loops / commitments;
|
|
12
|
+
- P0/P1 priority entries.
|
|
13
|
+
|
|
14
|
+
Archive-first signals (deterministic, bounded):
|
|
15
|
+
- low salience (importance and event intensity both low);
|
|
16
|
+
- redundancy (duplicate text, later copies first);
|
|
17
|
+
- old versions (version chain at/over the configured max, or superseded);
|
|
18
|
+
- long unused (no access for >= 30 days, when now is supplied);
|
|
19
|
+
- otherwise a low eviction score (< ARCHIVE_SCORE).
|
|
20
|
+
Between ARCHIVE_SCORE and FLAG_SCORE the entry is only flagged.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from ..core.config import MAX_VERSIONS
|
|
25
|
+
from ..core.util import clamp01
|
|
26
|
+
from .score import eviction_score
|
|
27
|
+
|
|
28
|
+
ARCHIVE_SCORE = 0.30
|
|
29
|
+
FLAG_SCORE = 0.50
|
|
30
|
+
STALE_SECONDS = 30 * 86400.0
|
|
31
|
+
LOW_SALIENCE = 0.35
|
|
32
|
+
REDUNDANCY_ARCHIVE = 0.60
|
|
33
|
+
# L5 schema-based eviction (default OFF: tick behaves exactly as before).
|
|
34
|
+
SCHEMA_EVICTION_ENABLED = False
|
|
35
|
+
SCHEMA_ARCHIVE = 0.35
|
|
36
|
+
CONSEQUENCE_ARCHIVE = 0.40
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _entry_id(entry):
|
|
40
|
+
return str(entry.get("id") or entry.get("memory_id") or "")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _event_intensity(entry):
|
|
44
|
+
vector = entry.get("event_vector")
|
|
45
|
+
if hasattr(vector, "as_dict"):
|
|
46
|
+
try:
|
|
47
|
+
vector = vector.as_dict()
|
|
48
|
+
except Exception:
|
|
49
|
+
vector = None
|
|
50
|
+
if isinstance(vector, dict):
|
|
51
|
+
return clamp01(vector.get("intensity"), 0.0)
|
|
52
|
+
return clamp01(entry.get("event_intensity"), 0.0)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _salience(entry):
|
|
56
|
+
return clamp01(0.6 * clamp01(entry.get("importance"), 0.5)
|
|
57
|
+
+ 0.4 * _event_intensity(entry), 0.5)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_protected(entry):
|
|
61
|
+
if entry.get("pin") or entry.get("pinned"):
|
|
62
|
+
return True
|
|
63
|
+
if entry.get("self_defining") or entry.get("identity"):
|
|
64
|
+
return True
|
|
65
|
+
for key in ("promise", "promises", "commitment", "open_loop", "open_loops"):
|
|
66
|
+
if entry.get(key):
|
|
67
|
+
return True
|
|
68
|
+
priority = str(entry.get("priority") or "").upper()
|
|
69
|
+
if entry.get("p0") or entry.get("p1") or priority in ("P0", "P1"):
|
|
70
|
+
return True
|
|
71
|
+
metadata = entry.get("metadata") if isinstance(entry.get("metadata"), dict) else {}
|
|
72
|
+
return bool(metadata.get("pin") or metadata.get("pinned")
|
|
73
|
+
or metadata.get("self_defining") or metadata.get("promise"))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _schema_congruence(entry):
|
|
77
|
+
"""Read schema congruence from entry or its event_vector (0..1)."""
|
|
78
|
+
vector = entry.get("event_vector")
|
|
79
|
+
if hasattr(vector, "as_dict"):
|
|
80
|
+
try:
|
|
81
|
+
vector = vector.as_dict()
|
|
82
|
+
except Exception:
|
|
83
|
+
vector = None
|
|
84
|
+
if isinstance(vector, dict) and vector.get("schema_conflict") is not None:
|
|
85
|
+
return clamp01(1.0 - clamp01(vector.get("schema_conflict"), 0.0))
|
|
86
|
+
return clamp01(entry.get("schema_congruence"), 1.0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _consequence(entry):
|
|
90
|
+
"""0..1 consequence of forgetting; missing means neutral/low."""
|
|
91
|
+
return clamp01(entry.get("consequence"), 0.5)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _is_old_version(entry):
|
|
95
|
+
if entry.get("superseded") or entry.get("old_version"):
|
|
96
|
+
return True
|
|
97
|
+
versions = entry.get("previous_versions")
|
|
98
|
+
if isinstance(versions, (list, tuple)) and len(versions) >= max(1, int(MAX_VERSIONS)):
|
|
99
|
+
return True
|
|
100
|
+
metadata = entry.get("metadata") if isinstance(entry.get("metadata"), dict) else {}
|
|
101
|
+
return bool(metadata.get("superseded") or metadata.get("old_version"))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _age_seconds(entry, now):
|
|
105
|
+
if not now:
|
|
106
|
+
return 0.0
|
|
107
|
+
last = 0.0
|
|
108
|
+
for key in ("last_recall", "last_access", "ts", "created_ts"):
|
|
109
|
+
try:
|
|
110
|
+
last = max(last, float(entry.get(key) or 0.0))
|
|
111
|
+
except Exception:
|
|
112
|
+
continue
|
|
113
|
+
if last <= 0:
|
|
114
|
+
# No access timestamp at all: unknown age counts as long-unused.
|
|
115
|
+
return float(now)
|
|
116
|
+
try:
|
|
117
|
+
return max(0.0, float(now) - last)
|
|
118
|
+
except Exception:
|
|
119
|
+
return 0.0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def eviction_order(rows):
|
|
123
|
+
"""Legacy candidate order: protected/desktop/index-retained first."""
|
|
124
|
+
out = []
|
|
125
|
+
for row in rows or []:
|
|
126
|
+
if not isinstance(row, dict):
|
|
127
|
+
continue
|
|
128
|
+
entry = row.get("entry") if isinstance(row.get("entry"), dict) else row
|
|
129
|
+
key = {"id": str(entry.get("id") or ""),
|
|
130
|
+
"score": float(row.get("score", 0.0) or 0.0),
|
|
131
|
+
"pinned": bool(entry.get("pin")),
|
|
132
|
+
"desktop": bool(entry.get("desktop")),
|
|
133
|
+
"index_retained": bool(entry.get("index_retained"))}
|
|
134
|
+
out.append(key)
|
|
135
|
+
out.sort(key=lambda r: (not r["pinned"], not r["desktop"],
|
|
136
|
+
not r["index_retained"], -r["score"]))
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def tick(entries, now=0.0):
|
|
141
|
+
"""Partition entries into {"archive": [ids], "flag": [ids], "keep": [ids]}.
|
|
142
|
+
|
|
143
|
+
Read-only: no entry is mutated or removed. Ordering is deterministic:
|
|
144
|
+
archive/flag by (score ascending, id); keep keeps input order.
|
|
145
|
+
"""
|
|
146
|
+
items = [e for e in (entries or []) if isinstance(e, dict)]
|
|
147
|
+
total = max(1, len(items))
|
|
148
|
+
seen_text = set()
|
|
149
|
+
scored = []
|
|
150
|
+
for position, entry in enumerate(items):
|
|
151
|
+
entry_id = _entry_id(entry)
|
|
152
|
+
if not entry_id:
|
|
153
|
+
continue
|
|
154
|
+
text = " ".join(str(entry.get("text") or "").split()).lower()
|
|
155
|
+
duplicate = bool(text) and text in seen_text
|
|
156
|
+
if text:
|
|
157
|
+
seen_text.add(text)
|
|
158
|
+
if _is_protected(entry):
|
|
159
|
+
scored.append((entry_id, 1.0, "keep", entry))
|
|
160
|
+
continue
|
|
161
|
+
redundancy = 1.0 if duplicate else (
|
|
162
|
+
1.0 if clamp01(entry.get("redundancy"), 0.0) >= REDUNDANCY_ARCHIVE else 0.0)
|
|
163
|
+
score = eviction_score(entry, now=now,
|
|
164
|
+
position=position / float(total),
|
|
165
|
+
redundancy=redundancy)
|
|
166
|
+
stale = _age_seconds(entry, now) >= STALE_SECONDS
|
|
167
|
+
low_salience = _salience(entry) < LOW_SALIENCE
|
|
168
|
+
schema_archive = (SCHEMA_EVICTION_ENABLED
|
|
169
|
+
and _schema_congruence(entry) < SCHEMA_ARCHIVE
|
|
170
|
+
and _consequence(entry) < CONSEQUENCE_ARCHIVE)
|
|
171
|
+
must_archive = (duplicate or redundancy >= REDUNDANCY_ARCHIVE
|
|
172
|
+
or _is_old_version(entry) or stale or low_salience
|
|
173
|
+
or schema_archive)
|
|
174
|
+
if must_archive or score < ARCHIVE_SCORE:
|
|
175
|
+
decision = "archive"
|
|
176
|
+
elif score < FLAG_SCORE:
|
|
177
|
+
decision = "flag"
|
|
178
|
+
else:
|
|
179
|
+
decision = "keep"
|
|
180
|
+
scored.append((entry_id, score, decision, entry))
|
|
181
|
+
archive = [item for item in scored if item[2] == "archive"]
|
|
182
|
+
flagged = [item for item in scored if item[2] == "flag"]
|
|
183
|
+
keep = [item[0] for item in scored if item[2] == "keep"]
|
|
184
|
+
archive.sort(key=lambda item: (item[1], item[0]))
|
|
185
|
+
flagged.sort(key=lambda item: (item[1], item[0]))
|
|
186
|
+
return {"archive": [item[0] for item in archive],
|
|
187
|
+
"flag": [item[0] for item in flagged],
|
|
188
|
+
"keep": keep}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Read-only eviction score (never deletes by itself)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from ..core.config import (EVICT_W_CONFIDENCE, EVICT_W_POSITION,
|
|
5
|
+
EVICT_W_REDUNDANCY, EVICT_W_SALIENCE,
|
|
6
|
+
EVICT_W_STALENESS)
|
|
7
|
+
from ..core.util import clamp01
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def eviction_score(entry, now=0.0, position=0.5, redundancy=0.0):
|
|
11
|
+
e = entry if isinstance(entry, dict) else {}
|
|
12
|
+
salience = clamp01(e.get("importance"), 0.5)
|
|
13
|
+
pos = clamp01(position, 0.5)
|
|
14
|
+
red = clamp01(redundancy, 0.0)
|
|
15
|
+
conf = clamp01(e.get("memory_confidence"), 0.5)
|
|
16
|
+
try:
|
|
17
|
+
age = max(0.0, float(now or 0.0) - float(e.get("last_recall", e.get("ts", 0.0)) or 0.0))
|
|
18
|
+
except Exception:
|
|
19
|
+
age = 0.0
|
|
20
|
+
stale = min(1.0, age / 86400.0 / 30.0) if now else 0.0
|
|
21
|
+
return round(EVICT_W_SALIENCE * salience + EVICT_W_POSITION * pos
|
|
22
|
+
+ EVICT_W_REDUNDANCY * red
|
|
23
|
+
+ EVICT_W_CONFIDENCE * conf + EVICT_W_STALENESS * stale, 4)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# OWNER: engine-core
|
|
2
|
+
from .writeback import (IDENTITY_FIELDS, MAX_STEP, SAME_DIRECTION_MIN, # noqa
|
|
3
|
+
evidence_count, is_identity, is_major_trigger,
|
|
4
|
+
writeback_decision, writeback_gate)
|
|
5
|
+
from .history import history_rows # noqa
|
|
6
|
+
from .snapshot import identity_snapshot # noqa
|