liquid-loop 0.6.2__tar.gz → 0.7.0__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 (27) hide show
  1. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/PKG-INFO +23 -2
  2. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/README.md +22 -1
  3. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/__init__.py +3 -3
  4. liquid_loop-0.7.0/liquid_loop/cognitive_budget.py +105 -0
  5. liquid_loop-0.7.0/liquid_loop/mesh/__init__.py +50 -0
  6. liquid_loop-0.7.0/liquid_loop/mesh/v2.py +173 -0
  7. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/storage.py +27 -1
  8. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/workspace.py +127 -16
  9. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/PKG-INFO +23 -2
  10. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/SOURCES.txt +7 -0
  11. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/pyproject.toml +1 -1
  12. liquid_loop-0.7.0/tests/test_consensus_expansion.py +45 -0
  13. liquid_loop-0.7.0/tests/test_nucleate_dual_track.py +115 -0
  14. liquid_loop-0.7.0/tests/test_peek_seal.py +115 -0
  15. liquid_loop-0.7.0/tests/test_seal_persistence.py +63 -0
  16. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/LICENSE +0 -0
  17. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/__main__.py +0 -0
  18. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/cli.py +0 -0
  19. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/entropy.py +0 -0
  20. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/dependency_links.txt +0 -0
  21. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/entry_points.txt +0 -0
  22. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/requires.txt +0 -0
  23. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/top_level.txt +0 -0
  24. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/setup.cfg +0 -0
  25. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/tests/test_entropy.py +0 -0
  26. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/tests/test_self_evolve.py +0 -0
  27. {liquid_loop-0.6.2 → liquid_loop-0.7.0}/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.7.0
4
4
  Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
5
5
  Author: fishbook0001
6
6
  Maintainer: fishbook0001
@@ -147,6 +147,26 @@ liquid-loop snapshot
147
147
 
148
148
  ---
149
149
 
150
+ ## MESH 集成(多智能体共识)
151
+
152
+ 液环从 v0.7.0 起内置官方 MESH 集成 `liquid_loop.mesh`,把"多智能体共识协议"落地为可复用代码,作为 agent-mesh 节点的标准接入层。
153
+
154
+ ```python
155
+ from liquid_loop.mesh import validate_evidence, compute_cci, cognitive_health, fetch_state
156
+
157
+ # agent 写入前契约自检(零向量:content 必须精确字符串,禁 embedding)
158
+ ok, errs = validate_evidence({"agent_id": "vera", "content": "用户偏好简洁输出"})
159
+
160
+ # 从 8790 拉取记忆状态,算主体间性共识指数 CCI
161
+ items = fetch_state("http://127.0.0.1:8790")
162
+ health = cognitive_health(items)
163
+ print(health["CCI"], health["consensus_crystals"])
164
+ ```
165
+
166
+ 零向量哲学:一致性判定走**结构化精确相等 + 审计链哈希**,绝不引入任何 embedding / 相似度。规范详见 `mesh/liquid_loop_mesh_v2_spec.md`。
167
+
168
+ ---
169
+
150
170
  ## 架构对比
151
171
 
152
172
  ```
@@ -194,6 +214,7 @@ liquid-loop/
194
214
  │ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
195
215
  │ ├── storage.py # JSON 持久化 + 审计链写入
196
216
  │ ├── entropy.py # 四维熵值计算
217
+ │ ├── mesh/ # MESH v2 多智能体共识协议集成(validate_evidence / compute_cci / ...)
197
218
  │ └── cli.py # Click CLI (11 命令)
198
219
  ├── examples/
199
220
  │ └── quickstart.py
@@ -220,7 +241,7 @@ pytest -v
220
241
  ## 路线图
221
242
 
222
243
  - [ ] 语义一致性结晶(嵌入相似度替代精确匹配)
223
- - [ ] 多 Agent 液环耦合
244
+ - [x] 多 Agent 液环耦合(`liquid_loop.mesh` v2 共识协议,2026-07-15 落地)
224
245
  - [ ] LX 扩展:世界模型预测纳入液环
225
246
  - [ ] LoCoMo / LongMemEval 基准对比
226
247
  - [ ] 边缘端部署优化(<50KB)
@@ -108,6 +108,26 @@ liquid-loop snapshot
108
108
 
109
109
  ---
110
110
 
111
+ ## MESH 集成(多智能体共识)
112
+
113
+ 液环从 v0.7.0 起内置官方 MESH 集成 `liquid_loop.mesh`,把"多智能体共识协议"落地为可复用代码,作为 agent-mesh 节点的标准接入层。
114
+
115
+ ```python
116
+ from liquid_loop.mesh import validate_evidence, compute_cci, cognitive_health, fetch_state
117
+
118
+ # agent 写入前契约自检(零向量:content 必须精确字符串,禁 embedding)
119
+ ok, errs = validate_evidence({"agent_id": "vera", "content": "用户偏好简洁输出"})
120
+
121
+ # 从 8790 拉取记忆状态,算主体间性共识指数 CCI
122
+ items = fetch_state("http://127.0.0.1:8790")
123
+ health = cognitive_health(items)
124
+ print(health["CCI"], health["consensus_crystals"])
125
+ ```
126
+
127
+ 零向量哲学:一致性判定走**结构化精确相等 + 审计链哈希**,绝不引入任何 embedding / 相似度。规范详见 `mesh/liquid_loop_mesh_v2_spec.md`。
128
+
129
+ ---
130
+
111
131
  ## 架构对比
112
132
 
113
133
  ```
@@ -155,6 +175,7 @@ liquid-loop/
155
175
  │ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
156
176
  │ ├── storage.py # JSON 持久化 + 审计链写入
157
177
  │ ├── entropy.py # 四维熵值计算
178
+ │ ├── mesh/ # MESH v2 多智能体共识协议集成(validate_evidence / compute_cci / ...)
158
179
  │ └── cli.py # Click CLI (11 命令)
159
180
  ├── examples/
160
181
  │ └── quickstart.py
@@ -181,7 +202,7 @@ pytest -v
181
202
  ## 路线图
182
203
 
183
204
  - [ ] 语义一致性结晶(嵌入相似度替代精确匹配)
184
- - [ ] 多 Agent 液环耦合
205
+ - [x] 多 Agent 液环耦合(`liquid_loop.mesh` v2 共识协议,2026-07-15 落地)
185
206
  - [ ] LX 扩展:世界模型预测纳入液环
186
207
  - [ ] LoCoMo / LongMemEval 基准对比
187
208
  - [ ] 边缘端部署优化(<50KB)
@@ -1,10 +1,10 @@
1
- """Liquid Loop — Workspace Cognitive Runtime v0.6.1 (CPE-fused, proactive, rhythmic sampling)"""
2
- __version__ = "0.6.1"
1
+ """Liquid Loop — Workspace Cognitive Runtime v0.7.0 (CPE-fused, SEAL dual-opt, dual-track multi-agent, MESH v2 integration)"""
2
+ __version__ = "0.7.0"
3
3
 
4
4
  from .workspace import (
5
5
  WorkspaceState, Anchor, Evidence, Memory, Conflict,
6
6
  AuditChain, CPERegularizer, SelfRefineEngine,
7
7
  rhythmic_retrieve,
8
8
  )
9
- from .storage import load, save
9
+ from .storage import load, save, locked_state
10
10
  from .entropy import calculate, calculate_detail, calculate as calculate_entropy
@@ -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
+ }
@@ -0,0 +1,50 @@
1
+ """
2
+ liquid_loop.mesh — 液环 MESH 进阶版(v2)共识协议集成
3
+ =====================================================
4
+ 把"多智能体共识协议规范"从文档落地为可复用代码,作为 liquid-loop 的官方
5
+ MESH 集成层。零向量哲学:一致性判定走结构化精确相等 + 审计链哈希,绝不引入
6
+ 任何 embedding / 相似度。
7
+
8
+ 职责(详见 .v2):
9
+ - validate_evidence() agent 侧契约自检(结构化证据 schema)
10
+ - check_contract() 双向契约合规检查(身份 / 写 / 消费 / 冲突)
11
+ - compute_cci() 主体间性共识指数(记忆层)
12
+ - cognitive_health() 统一认知健康仪表盘(CCI / drift / H_e / P_mem)
13
+ - detect_conflict() 同锚点冲突证据检测
14
+ - fetch_state() 从 8790 REST /list 拉取当前状态
15
+ - main() 连接 8790 打印认知健康报告(CLI)
16
+
17
+ 快速使用:
18
+ from liquid_loop.mesh import validate_evidence, compute_cci, cognitive_health
19
+ from liquid_loop.mesh import fetch_state, main
20
+
21
+ ok, errs = validate_evidence({"agent_id": "vera", "content": "..."})
22
+ health = cognitive_health(fetch_state())
23
+ """
24
+ from .v2 import (
25
+ REGISTRY,
26
+ CLAIM_TYPES,
27
+ SCOPES,
28
+ Evidence,
29
+ validate_evidence,
30
+ check_contract,
31
+ compute_cci,
32
+ cognitive_health,
33
+ detect_conflict,
34
+ fetch_state,
35
+ main,
36
+ )
37
+
38
+ __all__ = [
39
+ "REGISTRY",
40
+ "CLAIM_TYPES",
41
+ "SCOPES",
42
+ "Evidence",
43
+ "validate_evidence",
44
+ "check_contract",
45
+ "compute_cci",
46
+ "cognitive_health",
47
+ "detect_conflict",
48
+ "fetch_state",
49
+ "main",
50
+ ]
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ liquid_loop_mesh_v2.py — 液环 MESH 进阶版(v2)参考实现
4
+ ======================================================
5
+ 把"共识协议规范"从文档落地为可复用代码。零向量哲学:一致性判定走
6
+ 结构化精确相等 + 审计链哈希,绝不引入任何 embedding / 相似度。
7
+
8
+ 职责:
9
+ 1. validate_evidence() — agent 侧契约自检(结构化证据 schema)
10
+ 2. check_contract() — 双向契约合规检查(身份/写/消费/冲突)
11
+ 3. compute_cci() — 主体间性共识指数(记忆层)
12
+ 4. cognitive_health() — 统一认知健康仪表盘(CCI / drift / H_e / P_mem)
13
+ 5. detect_conflict() — 同锚点冲突证据检测
14
+ 6. fetch_state() — 从 8790 REST /list 拉取当前状态
15
+
16
+ 依赖:标准库 + 可选 liquid_loop(仅类型提示)。可直接 import 使用,
17
+ 也可 `python3 liquid_loop_mesh_v2.py` 连 8790 打印健康报告。
18
+ """
19
+ from __future__ import annotations
20
+ import json
21
+ import sys
22
+ import urllib.request
23
+ from dataclasses import dataclass, field
24
+ from typing import Optional
25
+
26
+ # ── agent-mesh 注册表(接入 agent 的唯一 id 白名单)──────────────
27
+ REGISTRY = {"workbuddy", "vera", "qwenpaw", "marvis", "parlant-ctrl", "cawpaw", "tabbit", "llama"}
28
+
29
+ CLAIM_TYPES = {"fact", "conclusion", "preference", "conflict"}
30
+ SCOPES = {"private", "consensus"}
31
+
32
+
33
+ # ── 1. 结构化证据 schema 校验 ──────────────────────────────────
34
+ @dataclass
35
+ class Evidence:
36
+ agent_id: str
37
+ content: str
38
+ category: str = "fact"
39
+ claim_type: str = "fact"
40
+ value: Optional[float] = None
41
+ session_id: str = ""
42
+ prev_hash: str = ""
43
+ id: str = ""
44
+
45
+ def to_dict(self) -> dict:
46
+ return {
47
+ "agent_id": self.agent_id,
48
+ "content": self.content,
49
+ "category": self.category,
50
+ "claim_type": self.claim_type,
51
+ "value": self.value,
52
+ "session_id": self.session_id,
53
+ "prev_hash": self.prev_hash,
54
+ "id": self.id,
55
+ }
56
+
57
+
58
+ def validate_evidence(ev: dict) -> tuple[bool, list[str]]:
59
+ """agent 侧契约自检:返回 (ok, 错误列表)。"""
60
+ errs: list[str] = []
61
+ if not ev.get("agent_id"):
62
+ errs.append("agent_id 必填(共用机制要求声明写入者标识)")
63
+ elif ev["agent_id"] not in REGISTRY:
64
+ errs.append(f"agent_id '{ev['agent_id']}' 未在 mesh 注册表(禁止匿名污染)")
65
+ if not ev.get("content"):
66
+ errs.append("content 必填(一致性比对键)")
67
+ ct = ev.get("claim_type", "fact")
68
+ if ct not in CLAIM_TYPES:
69
+ errs.append(f"claim_type '{ct}' 非法,须为 {sorted(CLAIM_TYPES)}")
70
+ # content 精确相等判定 → 禁止任何 embedding/相似度
71
+ if ev.get("content") and not isinstance(ev["content"], str):
72
+ errs.append("content 必须为精确字符串(零向量一致性比对键)")
73
+ return (len(errs) == 0, errs)
74
+
75
+
76
+ # ── 2. 双向契约合规检查 ────────────────────────────────────────
77
+ def check_contract(agent_id: str, did_recall: bool, wrote_structured: bool) -> tuple[bool, list[str]]:
78
+ """①身份 ②结构化写 ③主动消费 ④冲突响应(由 detect_conflict 触发)。"""
79
+ errs: list[str] = []
80
+ if agent_id not in REGISTRY:
81
+ errs.append("① 身份未声明/未注册")
82
+ if not wrote_structured:
83
+ errs.append("② 未结构化写入(缺 claim_type/session_id/prev_hash)")
84
+ if not did_recall:
85
+ errs.append("③ 会话未主动消费 consensus 视图(退化成孤岛)")
86
+ return (len(errs) == 0, errs)
87
+
88
+
89
+ # ── 3. CCI 主体间性共识指数(记忆层)────────────────────────────
90
+ def compute_cci(items: list[dict]) -> dict:
91
+ """CCI_mem = |consensus| / (|consensus| + |private|)。"""
92
+ cons = [x for x in items if x.get("scope") == "consensus"]
93
+ priv = [x for x in items if x.get("scope") == "private"]
94
+ denom = len(cons) + len(priv)
95
+ cci = (len(cons) / denom) if denom else 0.0
96
+ # 共识强度 secondary:平均 contributors 基数
97
+ strengths = [len(x.get("contributors", [])) for x in cons] or [0]
98
+ avg_strength = sum(strengths) / len(strengths) if strengths else 0
99
+ return {
100
+ "cci": round(cci, 4),
101
+ "consensus": len(cons),
102
+ "private": len(priv),
103
+ "avg_contributors": round(avg_strength, 2),
104
+ }
105
+
106
+
107
+ # ── 4. 统一认知健康仪表盘 ─────────────────────────────────────
108
+ def cognitive_health(items: list[dict], drift_rate: float = 0.0,
109
+ H_e: float = 0.0, P_mem: float = 0.0) -> dict:
110
+ cci = compute_cci(items)
111
+ evs = [x for x in items if x.get("type") == "evidence"]
112
+ from collections import Counter
113
+ by_agent = Counter(x.get("agent_id") for x in evs)
114
+ return {
115
+ "CCI": cci["cci"],
116
+ "consensus_crystals": cci["consensus"],
117
+ "private_crystals": cci["private"],
118
+ "drift_rate": drift_rate,
119
+ "H_e": H_e,
120
+ "P_mem": P_mem,
121
+ "total_evidence": len(evs),
122
+ "agents": dict(by_agent),
123
+ }
124
+
125
+
126
+ # ── 5. 冲突检测 ───────────────────────────────────────────────
127
+ def detect_conflict(items: list[dict]) -> list[dict]:
128
+ """同锚点下 claim_type=conflict 或同 content 反义 → 标记冲突 agent。"""
129
+ conflicts = []
130
+ by_cat = {}
131
+ for x in items:
132
+ if x.get("type") in ("evidence", "memory"):
133
+ by_cat.setdefault(x.get("category", "?"), []).append(x)
134
+ for cat, recs in by_cat.items():
135
+ conflict_recs = [r for r in recs if r.get("claim_type") == "conflict"]
136
+ if conflict_recs:
137
+ agents = sorted({r.get("agent_id") for r in conflict_recs if r.get("agent_id")})
138
+ conflicts.append({"anchor": cat, "conflict_agents": agents,
139
+ "kind": "explicit_conflict"})
140
+ return conflicts
141
+
142
+
143
+ # ── 6. 从 8790 拉取状态 ───────────────────────────────────────
144
+ def fetch_state(base: str = "http://127.0.0.1:8790") -> list[dict]:
145
+ req = urllib.request.Request(base + "/list", data=b"{}",
146
+ headers={"Content-Type": "application/json"})
147
+ with urllib.request.urlopen(req, timeout=5) as r:
148
+ d = json.load(r)
149
+ return d if isinstance(d, list) else d.get("result", d.get("items", []))
150
+
151
+
152
+ # ── CLI:打印健康报告 ──────────────────────────────────────────
153
+ def main():
154
+ try:
155
+ items = fetch_state()
156
+ except Exception as e:
157
+ print(f"[ERR] 无法连接 8790: {e}")
158
+ sys.exit(1)
159
+ health = cognitive_health(items)
160
+ print("=== 液环 MESH v2 认知健康仪表盘 ===")
161
+ for k, v in health.items():
162
+ print(f" {k:18}: {v}")
163
+ conf = detect_conflict(items)
164
+ if conf:
165
+ print("\n=== 冲突锚点 ===")
166
+ for c in conf:
167
+ print(" ", c)
168
+ else:
169
+ print("\n[OK] 无显式冲突")
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -36,7 +36,12 @@ def _file_lock(filepath: str, mode: str = "w"):
36
36
  finally:
37
37
  fcntl.flock(f, fcntl.LOCK_UN)
38
38
 
39
+ def _lock_path(workspace_root: Path) -> Path:
40
+ return _ensure_dir(workspace_root) / "state.lock"
41
+
42
+
39
43
  def load(workspace_root: Path) -> WorkspaceState:
44
+ """纯读取,无锁。并发安全由上层 locked_state 上下文保证。"""
40
45
  path = _ensure_dir(workspace_root) / STATE_FILE
41
46
  if not path.exists():
42
47
  return WorkspaceState()
@@ -46,6 +51,7 @@ def load(workspace_root: Path) -> WorkspaceState:
46
51
 
47
52
 
48
53
  def save(state: WorkspaceState, workspace_root: Path):
54
+ """原子写(temp+rename):崩溃永不留下半截文件。锁由 locked_state 持有。"""
49
55
  state.updated_at = now()
50
56
  path = _ensure_dir(workspace_root) / STATE_FILE
51
57
  # 审计链:记录并回写哈希
@@ -59,8 +65,28 @@ def save(state: WorkspaceState, workspace_root: Path):
59
65
  data = asdict(state)
60
66
  # overlap_cache 仅为运行时熵计算缓存,键为 tuple,不可 JSON 序列化,且不具持久价值
61
67
  data.pop("overlap_cache", None)
62
- with open(path, "w", encoding="utf-8") as f:
68
+ tmp = path.with_suffix(".tmp")
69
+ with open(tmp, "w", encoding="utf-8") as f:
63
70
  json.dump(data, f, indent=2, ensure_ascii=False)
71
+ f.flush()
72
+ os.fsync(f.fileno())
73
+ os.replace(tmp, path) # POSIX 原子替换,读端永不看到半截文件
74
+
75
+
76
+ @contextmanager
77
+ def locked_state(workspace_root: Path):
78
+ """单一锁层:排他锁跨 load→modify→save 整段,根除并发丢写(P0)。
79
+ 跨进程安全(fcntl 建议锁);server 与批量喂入脚本共用同一把锁。"""
80
+ d = _ensure_dir(workspace_root)
81
+ lk = open(d / "state.lock", "w")
82
+ fcntl.flock(lk, fcntl.LOCK_EX)
83
+ try:
84
+ st = load(workspace_root)
85
+ yield st
86
+ save(st, workspace_root)
87
+ finally:
88
+ fcntl.flock(lk, fcntl.LOCK_UN)
89
+ lk.close()
64
90
 
65
91
 
66
92
  def _from_dict(data: dict) -> WorkspaceState:
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass, field
2
4
  from datetime import datetime, timezone
3
5
  from typing import Optional, List, Dict, Any
@@ -9,6 +11,8 @@ import os
9
11
  import re
10
12
  from collections import Counter
11
13
 
14
+ from .cognitive_budget import CognitiveBudgetStabilizer
15
+
12
16
 
13
17
  def now() -> str:
14
18
  return datetime.now(timezone.utc).isoformat()
@@ -84,6 +88,7 @@ class Anchor:
84
88
  last_accessed: str = ""
85
89
  value_score: float = 1.0
86
90
  anchor_strength: float = 1.0
91
+ seal_adjust: float = 0.0 # SEAL 自评层增量;_recalc 合成进 stability,不被覆盖(解 v0.6.3 假落地)
87
92
 
88
93
  def decay_value(self, factor: float = 0.95, evidence_count: int = 0) -> float:
89
94
  if not self.created_at:
@@ -149,6 +154,8 @@ class Evidence:
149
154
  timestamp: str = field(default_factory=now)
150
155
  weight: float = 1.0
151
156
  quality: str = "normal"
157
+ archived: bool = False # 预算超额冷归档标记(PEEK 落地,零丢失)
158
+ agent_id: str = "" # 多智能体共用:写入者标识(共用机制,缺省=legacy/单实例)
152
159
 
153
160
 
154
161
  @dataclass
@@ -158,6 +165,8 @@ class Memory:
158
165
  formed_at: str = field(default_factory=now)
159
166
  evidence_ids: list[str] = field(default_factory=list)
160
167
  confidence: float = 0.0
168
+ scope: str = "private" # 结晶来源: private(同 agent≥2一致) / consensus(跨 distinct owner≥2一致)
169
+ contributors: list[str] = field(default_factory=list) # 参与成核的 agent_id 列表
161
170
 
162
171
 
163
172
  @dataclass
@@ -239,10 +248,11 @@ class WorkspaceState:
239
248
  self.updated_at = now()
240
249
  return a
241
250
 
242
- def add_evidence(self, anchor: str, content: str, quality: float = 1.0) -> Optional[Evidence]:
251
+ def add_evidence(self, anchor, content: str, quality: float = 1.0, agent_id: str = "") -> Optional[Evidence]:
243
252
  """向指定锚点添加一条证据。
244
253
 
245
254
  anchor 参数兼容:锚点名称(str) | 锚点ID(str) | Anchor对象
255
+ agent_id:多智能体共用写入者标识(缺省空=legacy/单实例)
246
256
  """
247
257
  target = None
248
258
  if isinstance(anchor, Anchor):
@@ -254,9 +264,13 @@ class WorkspaceState:
254
264
  target = next((a for a in self.anchors if a.id == anchor), None)
255
265
  if not target:
256
266
  return None
267
+ # 幂等去重:同锚点+同内容+同 agent 已存在则复用,防止重复证据膨胀(mesh 重试/高频喂入)
268
+ for ex in self.evidences:
269
+ if ex.anchor_id == target.id and ex.content == content and ex.agent_id == agent_id:
270
+ return ex
257
271
  e = Evidence(
258
272
  id=uid(), anchor_id=target.id, content=content,
259
- quality=quality, timestamp=now(),
273
+ quality=quality, timestamp=now(), agent_id=agent_id,
260
274
  )
261
275
  self.evidences.append(e)
262
276
  target.evidence_ids.append(e.id)
@@ -266,11 +280,18 @@ class WorkspaceState:
266
280
  return e
267
281
 
268
282
  def _on_evidence_added(self, anchor_id: str):
269
- """证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测"""
283
+ """证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测→预算稳态"""
270
284
  self._decay_anchor(anchor_id)
271
285
  self._nucleate(anchor_id)
272
286
  self._recalc_anchor(anchor_id)
273
287
  self._detect_conflicts(anchor_id)
288
+ self._stabilize_budget()
289
+
290
+ def _stabilize_budget(self):
291
+ """认知预算稳态(PEEK 落地):超预算时冷归档最低价值证据。默认无预算不动作。"""
292
+ result = CognitiveBudgetStabilizer(self).stabilize()
293
+ if result.get("evicted", 0) > 0:
294
+ self.updated_at = now()
274
295
 
275
296
  def _decay_anchor(self, anchor_id: str):
276
297
  for e in self.evidences:
@@ -278,31 +299,68 @@ class WorkspaceState:
278
299
  e.weight = max(e.weight * 0.95, 0.1)
279
300
 
280
301
  def _nucleate(self, anchor_id: str):
281
- """成核:同锚点下 >= 2 条一致证据形成记忆结晶。
302
+ """双轨成核(多智能体共用机制):
303
+
304
+ - private 轨:同一 agent_id 下 >= 2 条一致证据 -> scope=private 结晶
305
+ - consensus 轨:同一 content 被 >= 2 个 distinct agent_id 证据支持 -> scope=consensus 结晶
282
306
 
283
- 复合键判定:查重限定在当前锚点内((anchor_id, content)),避免跨锚点
284
- 同 content 证据被错误吞掉或并入同一结晶(P0 跨锚点误结晶修复, v0.6.2)。
307
+ 复合键 (anchor_id, content, scope) 防跨锚点/跨轨污染。
308
+ 兼容 legacy:agent_id 为空视为单实例私有(走 private 轨,contributors=["legacy"])。
285
309
  """
286
310
  group = [e for e in self.evidences if e.anchor_id == anchor_id]
287
311
  if len(group) < 2:
288
312
  return
289
- # 本锚点已结晶的 content 集合(复合键判定,杜绝跨锚点污染)
290
- crystallized_contents: set[str] = set()
313
+ # 已结晶键集合(复合键判定,杜绝跨锚点/跨轨污染)
314
+ crystallized_keys: set = set()
291
315
  for m in self.memories:
292
316
  for eid in m.evidence_ids:
293
317
  ev = next((e for e in self.evidences if e.id == eid), None)
294
318
  if ev is not None and ev.anchor_id == anchor_id:
295
- crystallized_contents.add(m.content)
319
+ crystallized_keys.add((m.content, m.scope))
296
320
  break
297
- content_counts = Counter(e.content for e in group)
298
- for content, count in content_counts.items():
299
- if count >= 2 and content not in crystallized_contents:
300
- evidence_ids = [e.id for e in group if e.content == content]
301
- confidence = min(count / len(group), 1.0)
321
+ from collections import defaultdict
322
+ # ── private 轨:按 agent_id 分组,组内同 content >= 2 ──
323
+ by_agent: dict = defaultdict(list)
324
+ for e in group:
325
+ by_agent[e.agent_id if e.agent_id else "legacy"].append(e)
326
+ for owner, evs in by_agent.items():
327
+ content_counts = Counter(e.content for e in evs)
328
+ for content, count in content_counts.items():
329
+ if count >= 2 and (content, "private") not in crystallized_keys:
330
+ evidence_ids = [e.id for e in evs if e.content == content]
331
+ confidence = min(count / len(evs), 1.0)
332
+ self.memories.append(Memory(
333
+ content=content,
334
+ evidence_ids=evidence_ids,
335
+ confidence=confidence,
336
+ scope="private",
337
+ contributors=[owner],
338
+ ))
339
+ # ── consensus 轨:跨 distinct owner 同 content >= 2 ──
340
+ owners_by_content: dict = defaultdict(set)
341
+ for e in group:
342
+ if not e.agent_id:
343
+ continue # legacy 不参与共识轨(无法跨主体)
344
+ owners_by_content[e.content].add(e.agent_id)
345
+ for content, owners in owners_by_content.items():
346
+ if len(owners) < 2:
347
+ continue
348
+ # 已存在的共识结晶:把新一致方并入 contributors(动态扩展,支撑三方+ CCI 计量)
349
+ existing = next((m for m in self.memories if m.scope == "consensus" and m.content == content), None)
350
+ if existing is not None:
351
+ merged = set(existing.contributors) | owners
352
+ if merged != set(existing.contributors):
353
+ existing.contributors = sorted(merged)
354
+ continue
355
+ if (content, "consensus") not in crystallized_keys:
356
+ evidence_ids = [e.id for e in group if e.content == content and e.agent_id in owners]
357
+ confidence = min(len(owners) / 2.0, 1.0)
302
358
  self.memories.append(Memory(
303
359
  content=content,
304
360
  evidence_ids=evidence_ids,
305
361
  confidence=confidence,
362
+ scope="consensus",
363
+ contributors=sorted(owners),
306
364
  ))
307
365
  # 成核后触发自动描述回流(仅当描述为空)
308
366
  self._auto_describe_anchor(anchor_id)
@@ -360,7 +418,10 @@ class WorkspaceState:
360
418
  avg_weight = sum(e.weight for e in group) / len(group)
361
419
  for a in self.anchors:
362
420
  if a.id == anchor_id:
363
- a.stability = avg_weight
421
+ # base = 证据权重均值;stability = base + SEAL 自评层(seal_adjust 不被覆盖)
422
+ base = avg_weight
423
+ a.base_stability = base
424
+ a.stability = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
364
425
  evidence_count = len(group)
365
426
  a.decay_value(evidence_count=evidence_count)
366
427
  a.recalc_strength(evidence_count)
@@ -851,6 +912,51 @@ class SelfRefineEngine:
851
912
  repairs.append(f"REPAIR: 为锚点[{target_anchor.name}]新增evidence: {gold[:40]}")
852
913
  return repairs
853
914
 
915
+ # ── SEAL 落地(arXiv:2605.24426 失败诊断双优化)──
916
+ def diagnose(self, failure: Dict[str, Any]) -> Dict[str, Any]:
917
+ """SEAL 诊断:失败探测归因到锚点,判定失败类型。
918
+
919
+ fail_type:
920
+ retrieval 召回失败(记忆存在但未检索到)→ 策略侧 boost_stability
921
+ reason 推理失败(检索到但判断不中) → 策略侧 downweight_noise
922
+ """
923
+ reason = failure.get("reason", "")
924
+ if "检索" in reason or "无相关" in reason:
925
+ fail_type = "retrieval"
926
+ else:
927
+ fail_type = "reason"
928
+ source = failure.get("source", "")
929
+ target = None
930
+ if source:
931
+ target = next((a for a in self.state.anchors if a.id == source), None)
932
+ if not target:
933
+ ev = next((e for e in self.state.evidences if e.id == source), None)
934
+ if ev:
935
+ target = next((a for a in self.state.anchors if a.id == ev.anchor_id), None)
936
+ if fail_type == "retrieval":
937
+ tune = "boost_stability"
938
+ root = "召回失败:记忆存在但未检索到(权重/描述召回不足)"
939
+ else:
940
+ tune = "downweight_noise"
941
+ root = "推理失败:检索到但关键词判断不中(噪声/描述歧义)"
942
+ return {"fail_type": fail_type, "target_anchor": target.id if target else None,
943
+ "root_cause": root, "tune": tune}
944
+
945
+ def apply_strategy(self, diag: Dict[str, Any]) -> List[Dict[str, Any]]:
946
+ """SEAL 双优化 - 策略侧:修复确认→boost_stability;噪声→downweight。"""
947
+ if not diag.get("target_anchor"):
948
+ return []
949
+ a = next((x for x in self.state.anchors if x.id == diag["target_anchor"]), None)
950
+ if not a:
951
+ return []
952
+ if diag["tune"] == "boost_stability":
953
+ a.seal_adjust = min(0.5, round(a.seal_adjust + 0.1, 3))
954
+ else:
955
+ a.seal_adjust = max(-0.5, round(a.seal_adjust - 0.1, 3))
956
+ base = getattr(a, "base_stability", a.stability)
957
+ projected = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
958
+ return [{"op": diag["tune"], "anchor": a.name, "seal_adjust": a.seal_adjust, "projected_stability": projected}]
959
+
854
960
  def run(self, evidence_ids: List[str] = None) -> Dict[str, Any]:
855
961
  """执行完整后向自进化周期。"""
856
962
  probes = self.generate_probes(evidence_ids)
@@ -862,10 +968,15 @@ class SelfRefineEngine:
862
968
  failures = [r for p, r in zip(probes, results)
863
969
  if not r.get("passed") and p.get("type") != "overview"]
864
970
  repairs = self.repair(failures)
971
+ # SEAL 双优化 - 策略侧:诊断失败 → 调锚点 stability
972
+ diagnoses = [self.diagnose(f) for f in failures]
973
+ strategy_actions = []
974
+ for d in diagnoses:
975
+ strategy_actions.extend(self.apply_strategy(d))
865
976
  self.state.self_refine_probes = probes
866
977
  self.state.self_refine_results = results
867
978
  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]]}
979
+ 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
980
 
870
981
 
871
982
  # ==============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liquid-loop
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
5
5
  Author: fishbook0001
6
6
  Maintainer: fishbook0001
@@ -147,6 +147,26 @@ liquid-loop snapshot
147
147
 
148
148
  ---
149
149
 
150
+ ## MESH 集成(多智能体共识)
151
+
152
+ 液环从 v0.7.0 起内置官方 MESH 集成 `liquid_loop.mesh`,把"多智能体共识协议"落地为可复用代码,作为 agent-mesh 节点的标准接入层。
153
+
154
+ ```python
155
+ from liquid_loop.mesh import validate_evidence, compute_cci, cognitive_health, fetch_state
156
+
157
+ # agent 写入前契约自检(零向量:content 必须精确字符串,禁 embedding)
158
+ ok, errs = validate_evidence({"agent_id": "vera", "content": "用户偏好简洁输出"})
159
+
160
+ # 从 8790 拉取记忆状态,算主体间性共识指数 CCI
161
+ items = fetch_state("http://127.0.0.1:8790")
162
+ health = cognitive_health(items)
163
+ print(health["CCI"], health["consensus_crystals"])
164
+ ```
165
+
166
+ 零向量哲学:一致性判定走**结构化精确相等 + 审计链哈希**,绝不引入任何 embedding / 相似度。规范详见 `mesh/liquid_loop_mesh_v2_spec.md`。
167
+
168
+ ---
169
+
150
170
  ## 架构对比
151
171
 
152
172
  ```
@@ -194,6 +214,7 @@ liquid-loop/
194
214
  │ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
195
215
  │ ├── storage.py # JSON 持久化 + 审计链写入
196
216
  │ ├── entropy.py # 四维熵值计算
217
+ │ ├── mesh/ # MESH v2 多智能体共识协议集成(validate_evidence / compute_cci / ...)
197
218
  │ └── cli.py # Click CLI (11 命令)
198
219
  ├── examples/
199
220
  │ └── quickstart.py
@@ -220,7 +241,7 @@ pytest -v
220
241
  ## 路线图
221
242
 
222
243
  - [ ] 语义一致性结晶(嵌入相似度替代精确匹配)
223
- - [ ] 多 Agent 液环耦合
244
+ - [x] 多 Agent 液环耦合(`liquid_loop.mesh` v2 共识协议,2026-07-15 落地)
224
245
  - [ ] LX 扩展:世界模型预测纳入液环
225
246
  - [ ] LoCoMo / LongMemEval 基准对比
226
247
  - [ ] 边缘端部署优化(<50KB)
@@ -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
@@ -13,6 +14,12 @@ liquid_loop.egg-info/dependency_links.txt
13
14
  liquid_loop.egg-info/entry_points.txt
14
15
  liquid_loop.egg-info/requires.txt
15
16
  liquid_loop.egg-info/top_level.txt
17
+ liquid_loop/mesh/__init__.py
18
+ liquid_loop/mesh/v2.py
19
+ tests/test_consensus_expansion.py
16
20
  tests/test_entropy.py
21
+ tests/test_nucleate_dual_track.py
22
+ tests/test_peek_seal.py
23
+ tests/test_seal_persistence.py
17
24
  tests/test_self_evolve.py
18
25
  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.7.0"
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,45 @@
1
+ """共识结晶 contributors 随新一致方动态扩展(三方+ CCI 计量)。"""
2
+ from liquid_loop import WorkspaceState
3
+ from liquid_loop.workspace import Anchor, Evidence, Memory
4
+
5
+
6
+ def _add(st, name, content, agent_id):
7
+ a = next((x for x in st.anchors if x.name == name), None)
8
+ if a is None:
9
+ a = st.add_anchor(name)
10
+ e = Evidence(content=content, anchor_id=a.id, agent_id=agent_id)
11
+ st.add_evidence(a, content, agent_id=agent_id)
12
+ return a
13
+
14
+
15
+ def test_consensus_expands_with_third_agent():
16
+ st = WorkspaceState()
17
+ # qwenpaw + vera 先达成共识
18
+ _add(st, "液环", "液环是零向量记忆", "qwenpaw")
19
+ _add(st, "液环", "液环是零向量记忆", "qwenpaw") # qwenpaw >=2 -> private
20
+ _add(st, "液环", "液环是零向量记忆", "vera") # +vera -> consensus[qwenpaw,vera]
21
+ cons = next(m for m in st.memories if m.scope == "consensus")
22
+ assert cons.contributors == ["qwenpaw", "vera"], cons.contributors
23
+ # marvis 也写同内容 -> 应并入三方共识
24
+ _add(st, "液环", "液环是零向量记忆", "marvis")
25
+ cons = next(m for m in st.memories if m.scope == "consensus")
26
+ assert cons.contributors == ["marvis", "qwenpaw", "vera"], cons.contributors
27
+
28
+
29
+ def test_consensus_no_duplicate_on_repeat():
30
+ st = WorkspaceState()
31
+ _add(st, "fact", "X", "a")
32
+ _add(st, "fact", "X", "a") # private[a]
33
+ _add(st, "fact", "X", "b") # consensus[a,b]
34
+ _add(st, "fact", "X", "b") # b 重复,不应扩
35
+ cons = next(m for m in st.memories if m.scope == "consensus")
36
+ assert cons.contributors == ["a", "b"], cons.contributors
37
+
38
+
39
+ def test_private_untouched_by_consensus_expansion():
40
+ st = WorkspaceState()
41
+ _add(st, "fact", "Y", "qwenpaw")
42
+ _add(st, "fact", "Y", "qwenpaw") # private[qwenpaw]
43
+ _add(st, "fact", "Y", "vera") # consensus
44
+ priv = [m for m in st.memories if m.scope == "private"]
45
+ assert priv and priv[0].contributors == ["qwenpaw"], priv
@@ -0,0 +1,115 @@
1
+ """双轨成核 + 防污染回归测试(多智能体共用机制)。
2
+
3
+ 锁住行为:
4
+ - private 轨:同一 agent_id 内 >= 2 一致证据 -> scope=private
5
+ - consensus 轨:同一 content 跨 >= 2 distinct agent_id -> scope=consensus
6
+ - 防污染:不同 agent 不互相成私有晶;跨锚点同 content 不合并;复合键 (anchor,content,scope)
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ sys.path.insert(0, str(Path(__file__).parent.parent))
12
+
13
+ from liquid_loop.workspace import WorkspaceState
14
+
15
+
16
+ def _add(st, name, content, agent_id=""):
17
+ a = next((x for x in st.anchors if x.name == name), None)
18
+ if a is None:
19
+ a = st.add_anchor(name)
20
+ return st.add_evidence(a, content, agent_id=agent_id)
21
+
22
+
23
+ def test_private_nucleation_same_agent():
24
+ st = WorkspaceState()
25
+ _add(st, "topic", "液环是零向量记忆", agent_id="qwenpaw")
26
+ _add(st, "topic", "液环是零向量记忆", agent_id="qwenpaw")
27
+ priv = [m for m in st.memories if m.scope == "private"]
28
+ cons = [m for m in st.memories if m.scope == "consensus"]
29
+ assert len(priv) == 1
30
+ assert priv[0].contributors == ["qwenpaw"]
31
+ assert len(cons) == 0
32
+
33
+
34
+ def test_private_isolation_diff_agents():
35
+ st = WorkspaceState()
36
+ _add(st, "topic", "同一结论", agent_id="qwenpaw")
37
+ _add(st, "topic", "同一结论", agent_id="vera")
38
+ # 各 agent 内仅 1 条 -> 不 privately 成核
39
+ assert all(m.scope != "private" for m in st.memories)
40
+ # 但跨 2 distinct owner -> consensus 成核
41
+ cons = [m for m in st.memories if m.scope == "consensus"]
42
+ assert len(cons) == 1
43
+ assert set(cons[0].contributors) == {"qwenpaw", "vera"}
44
+
45
+
46
+ def test_consensus_requires_distinct_owner():
47
+ st = WorkspaceState()
48
+ _add(st, "topic", "共识结论", agent_id="qwenpaw")
49
+ _add(st, "topic", "共识结论", agent_id="qwenpaw") # 同 owner 重复
50
+ # 同 owner 2 条 -> private,但不该有 consensus(只有 1 个 owner)
51
+ cons = [m for m in st.memories if m.scope == "consensus"]
52
+ assert len(cons) == 0
53
+ priv = [m for m in st.memories if m.scope == "private"]
54
+ assert len(priv) == 1
55
+
56
+
57
+ def test_cross_anchor_no_pollution():
58
+ st = WorkspaceState()
59
+ _add(st, "A", "X", agent_id="qwenpaw")
60
+ _add(st, "A", "X", agent_id="qwenpaw")
61
+ _add(st, "B", "X", agent_id="qwenpaw")
62
+ _add(st, "B", "X", agent_id="qwenpaw")
63
+ # 两个锚点各成 1 个 private 晶,不合并
64
+ priv = [m for m in st.memories if m.scope == "private"]
65
+ assert len(priv) == 2
66
+
67
+
68
+ def test_legacy_compat_no_agent_id():
69
+ st = WorkspaceState()
70
+ _add(st, "topic", "legacy 内容", agent_id="")
71
+ _add(st, "topic", "legacy 内容", agent_id="")
72
+ priv = [m for m in st.memories if m.scope == "private"]
73
+ assert len(priv) == 1
74
+ assert priv[0].contributors == ["legacy"]
75
+
76
+
77
+ def test_dual_track_coexist():
78
+ st = WorkspaceState()
79
+ # 私有:qwenpaw 内 2 条独有结论
80
+ _add(st, "T", "qwenpaw 私有结论", agent_id="qwenpaw")
81
+ _add(st, "T", "qwenpaw 私有结论", agent_id="qwenpaw")
82
+ # 共识:qwenpaw + vera 各 1 条共享结论
83
+ _add(st, "T", "共享结论", agent_id="qwenpaw")
84
+ _add(st, "T", "共享结论", agent_id="vera")
85
+ priv = [m for m in st.memories if m.scope == "private"]
86
+ cons = [m for m in st.memories if m.scope == "consensus"]
87
+ assert len(priv) == 1 and priv[0].contributors == ["qwenpaw"]
88
+ assert len(cons) == 1 and set(cons[0].contributors) == {"qwenpaw", "vera"}
89
+
90
+
91
+ def test_no_pollution_between_agents_private():
92
+ st = WorkspaceState()
93
+ _add(st, "T", "结论A", agent_id="qwenpaw")
94
+ _add(st, "T", "结论A", agent_id="qwenpaw")
95
+ _add(st, "T", "结论A", agent_id="vera") # vera 只有 1 条 -> 不成私有
96
+ priv = [m for m in st.memories if m.scope == "private"]
97
+ cons = [m for m in st.memories if m.scope == "consensus"]
98
+ # qwenpaw 私有 1 晶;vera 仅 1 条不足私有;二者合计 2 distinct owner -> consensus 1 晶
99
+ assert len(priv) == 1 and priv[0].contributors == ["qwenpaw"]
100
+ assert len(cons) == 1 and set(cons[0].contributors) == {"qwenpaw", "vera"}
101
+
102
+
103
+ def test_storage_roundtrip_agent_id_scope(tmp_path):
104
+ """持久化往返:agent_id / scope / contributors 必须无损保存加载。"""
105
+ from liquid_loop import storage
106
+ st = WorkspaceState()
107
+ _add(st, "T", "X", agent_id="qwenpaw")
108
+ _add(st, "T", "X", agent_id="qwenpaw")
109
+ storage.save(st, tmp_path)
110
+ st2 = storage.load(tmp_path)
111
+ assert len(st2.memories) == 1
112
+ m = st2.memories[0]
113
+ assert m.scope == "private"
114
+ assert m.contributors == ["qwenpaw"]
115
+ assert st2.evidences[0].agent_id == "qwenpaw"
@@ -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