liquid-loop 0.6.2__tar.gz → 0.6.4__tar.gz

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.
Files changed (23) hide show
  1. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/PKG-INFO +1 -1
  2. liquid_loop-0.6.4/liquid_loop/cognitive_budget.py +105 -0
  3. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/workspace.py +67 -3
  4. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/PKG-INFO +1 -1
  5. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/SOURCES.txt +3 -0
  6. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/pyproject.toml +1 -1
  7. liquid_loop-0.6.4/tests/test_peek_seal.py +115 -0
  8. liquid_loop-0.6.4/tests/test_seal_persistence.py +63 -0
  9. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/LICENSE +0 -0
  10. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/README.md +0 -0
  11. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/__init__.py +0 -0
  12. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/__main__.py +0 -0
  13. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/cli.py +0 -0
  14. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/entropy.py +0 -0
  15. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop/storage.py +0 -0
  16. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/dependency_links.txt +0 -0
  17. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/entry_points.txt +0 -0
  18. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/requires.txt +0 -0
  19. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/liquid_loop.egg-info/top_level.txt +0 -0
  20. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/setup.cfg +0 -0
  21. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/tests/test_entropy.py +0 -0
  22. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/tests/test_self_evolve.py +0 -0
  23. {liquid_loop-0.6.2 → liquid_loop-0.6.4}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liquid-loop
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
5
5
  Author: fishbook0001
6
6
  Maintainer: fishbook0001
@@ -0,0 +1,105 @@
1
+ """认知预算稳态器(落地 PEEK arXiv:2604.09932 固定预算驱逐)
2
+
3
+ 把 PEEK 三模块蒸馏并适配液环:
4
+ Distiller evidence.weight + anchor.value_score + recency → 蒸馏价值
5
+ Cartographer 价值 × (0.5+0.5*访问热度) × 流动性 → 重要性
6
+ Evictor 超额时驱逐最低重要性证据,标记 archived(冷归档,零丢失)
7
+
8
+ 设计哲学(呼应液环零向量/零丢失):
9
+ 驱逐 ≠ 删除。超预算证据标记 archived=True,从活跃检索集移出(recall 跳过),
10
+ 但数据与审计链完整保留,可随时解冻。记忆系统因此维持"稳态预算"而非无限膨胀。
11
+
12
+ 预算来源:环境变量 LIQUID_EVIDENCE_BUDGET(默认 0 = 不限制)。
13
+ """
14
+ from __future__ import annotations
15
+ import os
16
+ from dataclasses import dataclass
17
+ from datetime import datetime, timezone
18
+ from typing import Optional
19
+
20
+ LIQUIDITY_HEAT = {"hot": 1.0, "warm": 0.7, "cold": 0.4, "frozen": 0.1}
21
+
22
+
23
+ @dataclass
24
+ class _BudgetItem:
25
+ id: str
26
+ weight: float = 1.0
27
+ anchor_value: float = 1.0
28
+ access_count: int = 0
29
+ liquidity: str = "warm"
30
+ gap_hours: float = 0.0
31
+ distilled_value: float = 0.0
32
+ importance: float = 0.0
33
+
34
+
35
+ def _distill(it: _BudgetItem) -> float:
36
+ recency = 1.0 / (1.0 + it.gap_hours / 168.0)
37
+ v = 0.4 * it.weight + 0.3 * it.anchor_value + 0.3 * recency
38
+ it.distilled_value = max(0.0, min(1.0, v))
39
+ return it.distilled_value
40
+
41
+
42
+ def _rank(items: list[_BudgetItem]) -> list[_BudgetItem]:
43
+ for it in items:
44
+ if it.distilled_value == 0.0:
45
+ _distill(it)
46
+ access_heat = min(it.access_count / 10.0, 1.0)
47
+ liquidity_heat = LIQUIDITY_HEAT.get(it.liquidity, 0.4)
48
+ it.importance = it.distilled_value * (0.5 + 0.5 * access_heat) * liquidity_heat
49
+ return sorted(items, key=lambda x: x.importance, reverse=True)
50
+
51
+
52
+ def _evict(items: list[_BudgetItem], budget: int) -> list[_BudgetItem]:
53
+ if len(items) <= budget:
54
+ return []
55
+ ranked = _rank(items)
56
+ return ranked[budget:] # 尾部最低重要性
57
+
58
+
59
+ class CognitiveBudgetStabilizer:
60
+ """接 WorkspaceState,超预算时冷归档低价值证据"""
61
+
62
+ def __init__(self, state, budget: Optional[int] = None):
63
+ self.state = state
64
+ self.budget = budget if budget is not None else int(os.environ.get("LIQUID_EVIDENCE_BUDGET", "0"))
65
+
66
+ @staticmethod
67
+ def _gap_hours(anchor) -> float:
68
+ if not anchor or not anchor.last_accessed:
69
+ return 9999.0
70
+ try:
71
+ last = datetime.fromisoformat(anchor.last_accessed)
72
+ return (datetime.now(timezone.utc) - last).total_seconds() / 3600
73
+ except ValueError:
74
+ return 9999.0
75
+
76
+ def stabilize(self) -> dict:
77
+ if self.budget <= 0:
78
+ return {"evicted": 0, "budget": 0, "total": len(self.state.evidences),
79
+ "note": "预算未设置(LIQUID_EVIDENCE_BUDGET=0),跳过"}
80
+ evs = self.state.evidences
81
+ if len(evs) <= self.budget:
82
+ return {"evicted": 0, "budget": self.budget, "total": len(evs)}
83
+ anchor_map = {a.id: a for a in self.state.anchors}
84
+ items: list[_BudgetItem] = []
85
+ for e in evs:
86
+ a = anchor_map.get(e.anchor_id)
87
+ items.append(_BudgetItem(
88
+ id=e.id,
89
+ weight=e.weight,
90
+ anchor_value=a.value_score if a else 0.5,
91
+ access_count=a.access_count if a else 0,
92
+ liquidity=a.liquidity if a else "warm",
93
+ gap_hours=self._gap_hours(a),
94
+ ))
95
+ evicted = _evict(items, self.budget)
96
+ evicted_ids = {it.id for it in evicted}
97
+ for e in evs:
98
+ if e.id in evicted_ids:
99
+ e.archived = True # 冷归档,零丢失
100
+ return {
101
+ "evicted": len(evicted_ids),
102
+ "evicted_ids": list(evicted_ids),
103
+ "budget": self.budget,
104
+ "total": len(evs),
105
+ }
@@ -9,6 +9,8 @@ import os
9
9
  import re
10
10
  from collections import Counter
11
11
 
12
+ from .cognitive_budget import CognitiveBudgetStabilizer
13
+
12
14
 
13
15
  def now() -> str:
14
16
  return datetime.now(timezone.utc).isoformat()
@@ -84,6 +86,7 @@ class Anchor:
84
86
  last_accessed: str = ""
85
87
  value_score: float = 1.0
86
88
  anchor_strength: float = 1.0
89
+ seal_adjust: float = 0.0 # SEAL 自评层增量;_recalc 合成进 stability,不被覆盖(解 v0.6.3 假落地)
87
90
 
88
91
  def decay_value(self, factor: float = 0.95, evidence_count: int = 0) -> float:
89
92
  if not self.created_at:
@@ -149,6 +152,7 @@ class Evidence:
149
152
  timestamp: str = field(default_factory=now)
150
153
  weight: float = 1.0
151
154
  quality: str = "normal"
155
+ archived: bool = False # 预算超额冷归档标记(PEEK 落地,零丢失)
152
156
 
153
157
 
154
158
  @dataclass
@@ -266,11 +270,18 @@ class WorkspaceState:
266
270
  return e
267
271
 
268
272
  def _on_evidence_added(self, anchor_id: str):
269
- """证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测"""
273
+ """证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测→预算稳态"""
270
274
  self._decay_anchor(anchor_id)
271
275
  self._nucleate(anchor_id)
272
276
  self._recalc_anchor(anchor_id)
273
277
  self._detect_conflicts(anchor_id)
278
+ self._stabilize_budget()
279
+
280
+ def _stabilize_budget(self):
281
+ """认知预算稳态(PEEK 落地):超预算时冷归档最低价值证据。默认无预算不动作。"""
282
+ result = CognitiveBudgetStabilizer(self).stabilize()
283
+ if result.get("evicted", 0) > 0:
284
+ self.updated_at = now()
274
285
 
275
286
  def _decay_anchor(self, anchor_id: str):
276
287
  for e in self.evidences:
@@ -360,7 +371,10 @@ class WorkspaceState:
360
371
  avg_weight = sum(e.weight for e in group) / len(group)
361
372
  for a in self.anchors:
362
373
  if a.id == anchor_id:
363
- a.stability = avg_weight
374
+ # base = 证据权重均值;stability = base + SEAL 自评层(seal_adjust 不被覆盖)
375
+ base = avg_weight
376
+ a.base_stability = base
377
+ a.stability = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
364
378
  evidence_count = len(group)
365
379
  a.decay_value(evidence_count=evidence_count)
366
380
  a.recalc_strength(evidence_count)
@@ -851,6 +865,51 @@ class SelfRefineEngine:
851
865
  repairs.append(f"REPAIR: 为锚点[{target_anchor.name}]新增evidence: {gold[:40]}")
852
866
  return repairs
853
867
 
868
+ # ── SEAL 落地(arXiv:2605.24426 失败诊断双优化)──
869
+ def diagnose(self, failure: Dict[str, Any]) -> Dict[str, Any]:
870
+ """SEAL 诊断:失败探测归因到锚点,判定失败类型。
871
+
872
+ fail_type:
873
+ retrieval 召回失败(记忆存在但未检索到)→ 策略侧 boost_stability
874
+ reason 推理失败(检索到但判断不中) → 策略侧 downweight_noise
875
+ """
876
+ reason = failure.get("reason", "")
877
+ if "检索" in reason or "无相关" in reason:
878
+ fail_type = "retrieval"
879
+ else:
880
+ fail_type = "reason"
881
+ source = failure.get("source", "")
882
+ target = None
883
+ if source:
884
+ target = next((a for a in self.state.anchors if a.id == source), None)
885
+ if not target:
886
+ ev = next((e for e in self.state.evidences if e.id == source), None)
887
+ if ev:
888
+ target = next((a for a in self.state.anchors if a.id == ev.anchor_id), None)
889
+ if fail_type == "retrieval":
890
+ tune = "boost_stability"
891
+ root = "召回失败:记忆存在但未检索到(权重/描述召回不足)"
892
+ else:
893
+ tune = "downweight_noise"
894
+ root = "推理失败:检索到但关键词判断不中(噪声/描述歧义)"
895
+ return {"fail_type": fail_type, "target_anchor": target.id if target else None,
896
+ "root_cause": root, "tune": tune}
897
+
898
+ def apply_strategy(self, diag: Dict[str, Any]) -> List[Dict[str, Any]]:
899
+ """SEAL 双优化 - 策略侧:修复确认→boost_stability;噪声→downweight。"""
900
+ if not diag.get("target_anchor"):
901
+ return []
902
+ a = next((x for x in self.state.anchors if x.id == diag["target_anchor"]), None)
903
+ if not a:
904
+ return []
905
+ if diag["tune"] == "boost_stability":
906
+ a.seal_adjust = min(0.5, round(a.seal_adjust + 0.1, 3))
907
+ else:
908
+ a.seal_adjust = max(-0.5, round(a.seal_adjust - 0.1, 3))
909
+ base = getattr(a, "base_stability", a.stability)
910
+ projected = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
911
+ return [{"op": diag["tune"], "anchor": a.name, "seal_adjust": a.seal_adjust, "projected_stability": projected}]
912
+
854
913
  def run(self, evidence_ids: List[str] = None) -> Dict[str, Any]:
855
914
  """执行完整后向自进化周期。"""
856
915
  probes = self.generate_probes(evidence_ids)
@@ -862,10 +921,15 @@ class SelfRefineEngine:
862
921
  failures = [r for p, r in zip(probes, results)
863
922
  if not r.get("passed") and p.get("type") != "overview"]
864
923
  repairs = self.repair(failures)
924
+ # SEAL 双优化 - 策略侧:诊断失败 → 调锚点 stability
925
+ diagnoses = [self.diagnose(f) for f in failures]
926
+ strategy_actions = []
927
+ for d in diagnoses:
928
+ strategy_actions.extend(self.apply_strategy(d))
865
929
  self.state.self_refine_probes = probes
866
930
  self.state.self_refine_results = results
867
931
  self.state.self_refine_repair_count += len(repairs)
868
- return {"total": len(probes), "passed": passed, "failed": len(failures), "pass_rate": round(passed / len(probes), 2), "repairs": repairs, "failed_details": [{"q": r.get("question", "")[:60], "reason": r.get("reason", "")} for r in failures[:5]]}
932
+ return {"total": len(probes), "passed": passed, "failed": len(failures), "pass_rate": round(passed / len(probes), 2), "repairs": repairs, "diagnoses": diagnoses, "strategy_actions": strategy_actions, "failed_details": [{"q": r.get("question", "")[:60], "reason": r.get("reason", "")} for r in failures[:5]]}
869
933
 
870
934
 
871
935
  # ==============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liquid-loop
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
5
5
  Author: fishbook0001
6
6
  Maintainer: fishbook0001
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  liquid_loop/__init__.py
5
5
  liquid_loop/__main__.py
6
6
  liquid_loop/cli.py
7
+ liquid_loop/cognitive_budget.py
7
8
  liquid_loop/entropy.py
8
9
  liquid_loop/storage.py
9
10
  liquid_loop/workspace.py
@@ -14,5 +15,7 @@ liquid_loop.egg-info/entry_points.txt
14
15
  liquid_loop.egg-info/requires.txt
15
16
  liquid_loop.egg-info/top_level.txt
16
17
  tests/test_entropy.py
18
+ tests/test_peek_seal.py
19
+ tests/test_seal_persistence.py
17
20
  tests/test_self_evolve.py
18
21
  tests/test_workspace.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "liquid-loop"
7
- version = "0.6.2"
7
+ version = "0.6.4"
8
8
  description = "Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,115 @@
1
+ """PEEK 预算稳态 + SEAL 双优化 落地测试(液环仓库内)"""
2
+ import pytest
3
+ from liquid_loop.workspace import WorkspaceState, SelfRefineEngine
4
+ from liquid_loop.cognitive_budget import CognitiveBudgetStabilizer
5
+
6
+
7
+ # ───────────── PEEK 落地:认知预算稳态器 ─────────────
8
+
9
+ def test_budget_stabilizer_evicts_low_value(monkeypatch):
10
+ monkeypatch.setenv("LIQUID_EVIDENCE_BUDGET", "3")
11
+ st = WorkspaceState()
12
+ a_hot = st.add_anchor("hot")
13
+ a_hot.value_score = 0.9
14
+ a_hot.access_count = 20
15
+ a_hot.liquidity = "hot"
16
+ a_cold = st.add_anchor("cold")
17
+ a_cold.value_score = 0.2
18
+ a_cold.access_count = 0
19
+ a_cold.liquidity = "frozen"
20
+ for i in range(3):
21
+ st.add_evidence("hot", f"重要证据{i} 液环锚点")
22
+ for i in range(3):
23
+ st.add_evidence("cold", f"陈旧证据{i} 旧记忆")
24
+ # 6 条证据,预算 3 → 应冷归档 3 条最低价值(cold 锚点)
25
+ arch = [e for e in st.evidences if e.archived]
26
+ assert len(arch) == 3
27
+ assert all(e.anchor_id == a_cold.id for e in arch)
28
+
29
+
30
+ def test_budget_no_limit_when_unset(monkeypatch):
31
+ monkeypatch.delenv("LIQUID_EVIDENCE_BUDGET", raising=False)
32
+ st = WorkspaceState()
33
+ a = st.add_anchor("x")
34
+ for i in range(10):
35
+ st.add_evidence("x", f"证据{i}")
36
+ arch = [e for e in st.evidences if e.archived]
37
+ assert arch == [] # 无预算不驱逐
38
+
39
+
40
+ def test_budget_stabilizer_direct_call(monkeypatch):
41
+ monkeypatch.setenv("LIQUID_EVIDENCE_BUDGET", "2")
42
+ st = WorkspaceState()
43
+ a = st.add_anchor("x")
44
+ a.value_score = 0.5
45
+ for i in range(5):
46
+ st.add_evidence("x", f"证据{i}")
47
+ res = CognitiveBudgetStabilizer(st).stabilize()
48
+ assert res["evicted"] == 3
49
+ assert res["total"] == 5
50
+ assert res["budget"] == 2
51
+
52
+
53
+ # ───────────── SEAL 落地:失败诊断双优化 ─────────────
54
+
55
+ def test_seal_diagnose_retrieval():
56
+ st = WorkspaceState()
57
+ a = st.add_anchor("液环", description="液环记忆理论")
58
+ st.add_evidence("液环", "液环锚点结晶")
59
+ eng = SelfRefineEngine(st)
60
+ failure = {"source": a.id, "reason": "检索失败,无相关记忆", "question": "?", "gold": "?"}
61
+ diag = eng.diagnose(failure)
62
+ assert diag["fail_type"] == "retrieval"
63
+ assert diag["target_anchor"] == a.id
64
+ assert diag["tune"] == "boost_stability"
65
+
66
+
67
+ def test_seal_diagnose_reason_downweights():
68
+ st = WorkspaceState()
69
+ a = st.add_anchor("稳态")
70
+ eng = SelfRefineEngine(st)
71
+ failure = {"source": a.id, "reason": "关键词不匹配,gold=稳态"}
72
+ diag = eng.diagnose(failure)
73
+ assert diag["fail_type"] == "reason"
74
+ eng.apply_strategy(diag)
75
+ # v0.6.4:改 seal_adjust 层,不直接动 stability
76
+ assert a.seal_adjust == -0.1
77
+ st.add_evidence(a, "补充证据") # 触发 _recalc 合成
78
+ assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
79
+ assert a.stability < a.base_stability # 负增量生效,自洽性降权
80
+
81
+
82
+ def test_seal_apply_strategy_boost():
83
+ st = WorkspaceState()
84
+ a = st.add_anchor("液环")
85
+ eng = SelfRefineEngine(st)
86
+ diag = {"fail_type": "retrieval", "target_anchor": a.id, "tune": "boost_stability"}
87
+ acts = eng.apply_strategy(diag)
88
+ assert acts[0]["op"] == "boost_stability"
89
+ assert a.seal_adjust == 0.1
90
+ st.add_evidence(a, "补充证据") # 触发 _recalc 合成
91
+ assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
92
+ assert a.stability >= a.base_stability # 正增量生效
93
+
94
+
95
+ def test_seal_run_integration_dual_optimize(monkeypatch):
96
+ st = WorkspaceState()
97
+ a = st.add_anchor("液环", description="液环")
98
+ st.add_evidence("液环", "液环锚点")
99
+ eng = SelfRefineEngine(st)
100
+
101
+ def fake_verify(p):
102
+ if p.get("type") == "fact":
103
+ # 模拟"检索失败但记忆应存在":source 指向锚点,gold 为新内容(需补证据)
104
+ return {"passed": False, "reason": "检索失败,无相关记忆",
105
+ "question": p.get("question", ""), "gold": "全新记忆内容需补回",
106
+ "source": a.id}
107
+ return {"passed": True, "reason": "ok"}
108
+
109
+ monkeypatch.setattr(eng, "verify", fake_verify)
110
+ out = eng.run()
111
+ assert out["failed"] >= 1
112
+ assert len(out["strategy_actions"]) >= 1
113
+ assert out["strategy_actions"][0]["op"] == "boost_stability"
114
+ # 双优化:memory 侧(repairs)与 strategy 侧(strategy_actions)都应存在
115
+ assert len(out["repairs"]) >= 1
@@ -0,0 +1,63 @@
1
+ """E8/v0.6.4 — 验证 SEAL 双优化不再被 _recalc_anchor 覆盖(解 v0.6.3 假落地)。
2
+
3
+ 修复核心:stability = base(证据权重均值) + seal_adjust(SEAL 自评层增量);
4
+ apply_strategy 改 seal_adjust 而非直接动 stability,_recalc 合成时保留。
5
+
6
+ 本测试直接构造诊断(绕过 SelfRefineEngine.verify 的脆弱归因),专注验证
7
+ apply_strategy → seal_adjust → _recalc 合成的持久性。
8
+ """
9
+ from liquid_loop.workspace import WorkspaceState, SelfRefineEngine
10
+
11
+
12
+ def _boost(a, eng):
13
+ diag = {"fail_type": "retrieval", "target_anchor": a.id,
14
+ "root_cause": "召回失败", "tune": "boost_stability"}
15
+ return eng.apply_strategy(diag)
16
+
17
+
18
+ def _down(a, eng):
19
+ diag = {"fail_type": "reason", "target_anchor": a.id,
20
+ "root_cause": "推理失败", "tune": "downweight_noise"}
21
+ return eng.apply_strategy(diag)
22
+
23
+
24
+ def test_seal_boost_persists_through_recalc():
25
+ """SEAL boost 后即便再 add_evidence 触发 _recalc,stability 仍含 seal_adjust。"""
26
+ st = WorkspaceState()
27
+ eng = SelfRefineEngine(st)
28
+ a = st.add_anchor("probe_seal")
29
+ st.add_evidence(a, "初始证据")
30
+ _boost(a, eng)
31
+ assert a.seal_adjust == 0.1
32
+ st.add_evidence(a, "再来一条证据") # 触发 _recalc_anchor
33
+ assert a.seal_adjust == 0.1 # 增量持久
34
+ expected = round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
35
+ assert a.stability == expected
36
+ assert a.stability >= a.base_stability # 正增量应 >= base
37
+
38
+
39
+ def test_seal_downweight():
40
+ st = WorkspaceState()
41
+ eng = SelfRefineEngine(st)
42
+ a = st.add_anchor("noise")
43
+ st.add_evidence(a, "x")
44
+ acts = _down(a, eng)
45
+ assert a.seal_adjust == -0.1
46
+ assert acts[0]["op"] == "downweight_noise"
47
+
48
+
49
+ def test_seal_adjust_applied_in_recalc():
50
+ """开 SEAL 的锚点 stability = base + seal_adjust;对照无 SEAL 锚点 adjust=0。"""
51
+ st = WorkspaceState()
52
+ eng = SelfRefineEngine(st)
53
+ a = st.add_anchor("x")
54
+ st.add_evidence(a, "e1")
55
+ _boost(a, eng)
56
+ st.add_evidence(a, "e2") # 触发 _recalc
57
+ # 开 SEAL:stability 合成 seal_adjust(含 clamp,与期望值精确匹配)
58
+ assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
59
+ # 对照:无 SEAL 的锚点 seal_adjust=0,stability=base
60
+ b = st.add_anchor("y")
61
+ st.add_evidence(b, "f1")
62
+ assert b.seal_adjust == 0.0
63
+ assert abs(b.stability - b.base_stability) < 1e-6
File without changes
File without changes
File without changes