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.
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/PKG-INFO +23 -2
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/README.md +22 -1
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/__init__.py +3 -3
- liquid_loop-0.7.0/liquid_loop/cognitive_budget.py +105 -0
- liquid_loop-0.7.0/liquid_loop/mesh/__init__.py +50 -0
- liquid_loop-0.7.0/liquid_loop/mesh/v2.py +173 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/storage.py +27 -1
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/workspace.py +127 -16
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/PKG-INFO +23 -2
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/SOURCES.txt +7 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/pyproject.toml +1 -1
- liquid_loop-0.7.0/tests/test_consensus_expansion.py +45 -0
- liquid_loop-0.7.0/tests/test_nucleate_dual_track.py +115 -0
- liquid_loop-0.7.0/tests/test_peek_seal.py +115 -0
- liquid_loop-0.7.0/tests/test_seal_persistence.py +63 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/LICENSE +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/__main__.py +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/cli.py +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop/entropy.py +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/dependency_links.txt +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/entry_points.txt +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/requires.txt +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/liquid_loop.egg-info/top_level.txt +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/setup.cfg +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/tests/test_entropy.py +0 -0
- {liquid_loop-0.6.2 → liquid_loop-0.7.0}/tests/test_self_evolve.py +0 -0
- {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.
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
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.
|
|
2
|
-
__version__ = "0.
|
|
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
|
-
|
|
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
|
|
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
|
-
"""
|
|
302
|
+
"""双轨成核(多智能体共用机制):
|
|
303
|
+
|
|
304
|
+
- private 轨:同一 agent_id 下 >= 2 条一致证据 -> scope=private 结晶
|
|
305
|
+
- consensus 轨:同一 content 被 >= 2 个 distinct agent_id 证据支持 -> scope=consensus 结晶
|
|
282
306
|
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
#
|
|
290
|
-
|
|
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
|
-
|
|
319
|
+
crystallized_keys.add((m.content, m.scope))
|
|
296
320
|
break
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
- [
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|