liquid-loop 0.1.2__tar.gz → 0.3.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.1.2 → liquid_loop-0.3.0}/PKG-INFO +12 -15
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/README.md +11 -14
- liquid_loop-0.3.0/liquid_loop/__init__.py +2 -0
- liquid_loop-0.3.0/liquid_loop/cli.py +322 -0
- liquid_loop-0.3.0/liquid_loop/storage.py +62 -0
- liquid_loop-0.3.0/liquid_loop/workspace.py +248 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/PKG-INFO +12 -15
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/pyproject.toml +1 -1
- liquid_loop-0.1.2/liquid_loop/__init__.py +0 -72
- liquid_loop-0.1.2/liquid_loop/cli.py +0 -182
- liquid_loop-0.1.2/liquid_loop/storage.py +0 -44
- liquid_loop-0.1.2/liquid_loop/workspace.py +0 -130
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/LICENSE +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop/entropy.py +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/SOURCES.txt +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/dependency_links.txt +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/entry_points.txt +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/requires.txt +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/liquid_loop.egg-info/top_level.txt +0 -0
- {liquid_loop-0.1.2 → liquid_loop-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: liquid-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
|
|
5
5
|
Author: fishbook0001
|
|
6
6
|
Maintainer: fishbook0001
|
|
@@ -123,27 +123,24 @@ save(state, Path("."))
|
|
|
123
123
|
# 初始化工作区(创建 .liquid/state.json)
|
|
124
124
|
liquid-loop init
|
|
125
125
|
|
|
126
|
-
#
|
|
126
|
+
# 添加锚点(支持自动四维分类)
|
|
127
127
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
128
128
|
|
|
129
129
|
# 注入证据
|
|
130
130
|
liquid-loop evidence_add "项目目标" "已完成 11 轮实验与 4 个实证包"
|
|
131
131
|
|
|
132
|
-
#
|
|
132
|
+
# 查看状态(含审计链哈希)
|
|
133
133
|
liquid-loop status
|
|
134
|
-
# 输出:
|
|
135
|
-
# ╭──────────────────────────────────────────────╮
|
|
136
|
-
# │ Liquid Loop 认知状态 │
|
|
137
|
-
# ├──────────┬──────┬────────┬────────┬────────┬──┤
|
|
138
|
-
# │ 锚点 │ 证据 │ 结晶 │ 冲突 │ 熵值 │ │
|
|
139
|
-
# ├──────────┼──────┼────────┼────────┼────────┼──┤
|
|
140
|
-
# │ 项目目标 │ 3 │ 1 │ 0 │ 0.2760 │ 🟢│
|
|
141
|
-
# ╰──────────┴──────┴────────┴────────┴────────┴──╯
|
|
142
|
-
# 锚点稳定性: 项目目标=0.88
|
|
143
134
|
|
|
144
135
|
# 列出所有记忆结晶
|
|
145
136
|
liquid-loop memory_list
|
|
146
137
|
|
|
138
|
+
# 审计:验证链式哈希完整性
|
|
139
|
+
liquid-loop audit
|
|
140
|
+
|
|
141
|
+
# 查看审计日志(最近 20 条)
|
|
142
|
+
liquid-loop audit-log --tail 20
|
|
143
|
+
|
|
147
144
|
# 快照(记录当前认知基线)
|
|
148
145
|
liquid-loop snapshot
|
|
149
146
|
```
|
|
@@ -194,10 +191,10 @@ Liquid Loop 是唯一完全自组织 + 零 LLM 管理的系统。
|
|
|
194
191
|
liquid-loop/
|
|
195
192
|
├── liquid_loop/
|
|
196
193
|
│ ├── __init__.py # 公共 API 导出
|
|
197
|
-
│ ├── workspace.py # 核心数据模型
|
|
198
|
-
│ ├── storage.py # JSON 持久化
|
|
194
|
+
│ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
|
|
195
|
+
│ ├── storage.py # JSON 持久化 + 审计链写入
|
|
199
196
|
│ ├── entropy.py # 四维熵值计算
|
|
200
|
-
│ └── cli.py # Click CLI (
|
|
197
|
+
│ └── cli.py # Click CLI (11 命令)
|
|
201
198
|
├── examples/
|
|
202
199
|
│ └── quickstart.py
|
|
203
200
|
├── tests/ # 待补充
|
|
@@ -84,27 +84,24 @@ save(state, Path("."))
|
|
|
84
84
|
# 初始化工作区(创建 .liquid/state.json)
|
|
85
85
|
liquid-loop init
|
|
86
86
|
|
|
87
|
-
#
|
|
87
|
+
# 添加锚点(支持自动四维分类)
|
|
88
88
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
89
89
|
|
|
90
90
|
# 注入证据
|
|
91
91
|
liquid-loop evidence_add "项目目标" "已完成 11 轮实验与 4 个实证包"
|
|
92
92
|
|
|
93
|
-
#
|
|
93
|
+
# 查看状态(含审计链哈希)
|
|
94
94
|
liquid-loop status
|
|
95
|
-
# 输出:
|
|
96
|
-
# ╭──────────────────────────────────────────────╮
|
|
97
|
-
# │ Liquid Loop 认知状态 │
|
|
98
|
-
# ├──────────┬──────┬────────┬────────┬────────┬──┤
|
|
99
|
-
# │ 锚点 │ 证据 │ 结晶 │ 冲突 │ 熵值 │ │
|
|
100
|
-
# ├──────────┼──────┼────────┼────────┼────────┼──┤
|
|
101
|
-
# │ 项目目标 │ 3 │ 1 │ 0 │ 0.2760 │ 🟢│
|
|
102
|
-
# ╰──────────┴──────┴────────┴────────┴────────┴──╯
|
|
103
|
-
# 锚点稳定性: 项目目标=0.88
|
|
104
95
|
|
|
105
96
|
# 列出所有记忆结晶
|
|
106
97
|
liquid-loop memory_list
|
|
107
98
|
|
|
99
|
+
# 审计:验证链式哈希完整性
|
|
100
|
+
liquid-loop audit
|
|
101
|
+
|
|
102
|
+
# 查看审计日志(最近 20 条)
|
|
103
|
+
liquid-loop audit-log --tail 20
|
|
104
|
+
|
|
108
105
|
# 快照(记录当前认知基线)
|
|
109
106
|
liquid-loop snapshot
|
|
110
107
|
```
|
|
@@ -155,10 +152,10 @@ Liquid Loop 是唯一完全自组织 + 零 LLM 管理的系统。
|
|
|
155
152
|
liquid-loop/
|
|
156
153
|
├── liquid_loop/
|
|
157
154
|
│ ├── __init__.py # 公共 API 导出
|
|
158
|
-
│ ├── workspace.py # 核心数据模型
|
|
159
|
-
│ ├── storage.py # JSON 持久化
|
|
155
|
+
│ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
|
|
156
|
+
│ ├── storage.py # JSON 持久化 + 审计链写入
|
|
160
157
|
│ ├── entropy.py # 四维熵值计算
|
|
161
|
-
│ └── cli.py # Click CLI (
|
|
158
|
+
│ └── cli.py # Click CLI (11 命令)
|
|
162
159
|
├── examples/
|
|
163
160
|
│ └── quickstart.py
|
|
164
161
|
├── tests/ # 待补充
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from .workspace import (
|
|
5
|
+
WorkspaceState, Anchor, Evidence, Memory, StateSnapshot,
|
|
6
|
+
AnchorRelation, now, DensityLevel, CognitiveStage, LiquidityLevel,
|
|
7
|
+
)
|
|
8
|
+
from .storage import load, save, get_audit_chain
|
|
9
|
+
from .entropy import calculate
|
|
10
|
+
from .entropy import calculate
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
WORKSPACE = Path.cwd()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load():
|
|
17
|
+
return load(WORKSPACE)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _save(state: WorkspaceState):
|
|
21
|
+
state.updated_at = now()
|
|
22
|
+
save(state, WORKSPACE)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Memory Nucleation: 同一 Anchor 下 >= 2 条一致 Evidence 成核
|
|
26
|
+
def _nucleate(state: WorkspaceState, anchor_id: str):
|
|
27
|
+
group = [e for e in state.evidences if e.anchor_id == anchor_id]
|
|
28
|
+
if len(group) < 2:
|
|
29
|
+
return
|
|
30
|
+
content_counts = Counter(e.content for e in group)
|
|
31
|
+
for content, count in content_counts.items():
|
|
32
|
+
if count >= 2:
|
|
33
|
+
existing = [m for m in state.memories if m.content == content]
|
|
34
|
+
if existing:
|
|
35
|
+
continue
|
|
36
|
+
evidence_ids = [e.id for e in group if e.content == content]
|
|
37
|
+
confidence = min(count / len(group), 1.0)
|
|
38
|
+
memory = Memory(
|
|
39
|
+
content=content,
|
|
40
|
+
evidence_ids=evidence_ids,
|
|
41
|
+
confidence=confidence,
|
|
42
|
+
)
|
|
43
|
+
state.memories.append(memory)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _decay_evidence(state: WorkspaceState, anchor_id: str):
|
|
47
|
+
for e in state.evidences:
|
|
48
|
+
if e.anchor_id == anchor_id:
|
|
49
|
+
e.weight = max(e.weight * 0.95, 0.1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _recalc_stability(state: WorkspaceState, anchor_id: str):
|
|
53
|
+
group = [e for e in state.evidences if e.anchor_id == anchor_id]
|
|
54
|
+
if not group:
|
|
55
|
+
return
|
|
56
|
+
avg_weight = sum(e.weight for e in group) / len(group)
|
|
57
|
+
for a in state.anchors:
|
|
58
|
+
if a.id == anchor_id:
|
|
59
|
+
a.stability = avg_weight
|
|
60
|
+
# 刷新价值衰减 + 锚定强度 + 自动分类
|
|
61
|
+
evidence_count = len(group)
|
|
62
|
+
a.decay_value(evidence_count=evidence_count)
|
|
63
|
+
a.recalc_strength(evidence_count)
|
|
64
|
+
a.auto_classify(evidence_count)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# --- CLI ---
|
|
68
|
+
|
|
69
|
+
@click.group()
|
|
70
|
+
def main():
|
|
71
|
+
"""Liquid Loop — Workspace Cognitive Runtime v0.2"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@main.command()
|
|
76
|
+
def init():
|
|
77
|
+
"""初始化 Liquid Loop 工作区"""
|
|
78
|
+
s = _load()
|
|
79
|
+
if s.anchors or s.evidences:
|
|
80
|
+
click.echo("工作区已初始化,无需重复操作。")
|
|
81
|
+
return
|
|
82
|
+
s.version = "0.2.0"
|
|
83
|
+
_save(s)
|
|
84
|
+
click.echo(f"✓ Liquid Loop v0.2 工作区已初始化: {WORKSPACE}/.liquid/")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@main.command()
|
|
88
|
+
def status():
|
|
89
|
+
"""显示当前认知状态(含四维分类概览)"""
|
|
90
|
+
s = _load()
|
|
91
|
+
ent = calculate(s)
|
|
92
|
+
level = "GREEN" if ent < 0.3 else ("YELLOW" if ent < 0.6 else "RED")
|
|
93
|
+
latest_ev = max((e.timestamp for e in s.evidences), default="无活动")
|
|
94
|
+
click.echo(f"锚点: {len(s.anchors)} | 证据: {len(s.evidences)} | 记忆: {len(s.memories)} | 冲突: {len(s.conflicts)} | 关系: {len(s.relations)}")
|
|
95
|
+
click.echo(f"熵值: {ent:.4f} [{level}] | 最新活动: {latest_ev}")
|
|
96
|
+
click.echo(f"版本: {s.version}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@main.command()
|
|
100
|
+
def anchor_list():
|
|
101
|
+
"""列出所有锚点(含四维分类 + 价值衰减)"""
|
|
102
|
+
s = _load()
|
|
103
|
+
if not s.anchors:
|
|
104
|
+
click.echo("暂无锚点。使用 liquid anchor add <名称> <描述> 添加。")
|
|
105
|
+
return
|
|
106
|
+
for a in s.anchors:
|
|
107
|
+
a.decay_value()
|
|
108
|
+
bar = "█" * int(a.stability * 10) + "░" * (10 - int(a.stability * 10))
|
|
109
|
+
strength_bar = "■" * int(a.anchor_strength * 10) + "□" * (10 - int(a.anchor_strength * 10))
|
|
110
|
+
ev_count = len([e for e in s.evidences if e.anchor_id == a.id])
|
|
111
|
+
click.echo(
|
|
112
|
+
f"[{a.stability:.2f}] {bar} {a.name}: {a.description[:50]}"
|
|
113
|
+
)
|
|
114
|
+
click.echo(
|
|
115
|
+
f" 分类: [{a.value_density}/{a.cognitive_stage}/{a.liquidity}] "
|
|
116
|
+
f"证据:{ev_count} 价值:{a.value_score:.2f} "
|
|
117
|
+
f"锚定:[{strength_bar}] {a.anchor_strength:.2f}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@main.command()
|
|
122
|
+
@click.argument("anchor_name")
|
|
123
|
+
@click.argument("content")
|
|
124
|
+
@click.option("--quality", "-q", default="normal", help="证据质量: strong/normal/weak")
|
|
125
|
+
def evidence_add(anchor_name, content, quality):
|
|
126
|
+
"""为锚点添加证据"""
|
|
127
|
+
s = _load()
|
|
128
|
+
anchor = next((a for a in s.anchors if a.name == anchor_name), None)
|
|
129
|
+
if not anchor:
|
|
130
|
+
click.echo(f"锚点 '{anchor_name}' 不存在。请先创建。")
|
|
131
|
+
return
|
|
132
|
+
evidence = Evidence(
|
|
133
|
+
anchor_id=anchor.id,
|
|
134
|
+
content=content,
|
|
135
|
+
quality=quality if quality in ("strong", "normal", "weak") else "normal",
|
|
136
|
+
)
|
|
137
|
+
s.evidences.append(evidence)
|
|
138
|
+
anchor.evidence_ids.append(evidence.id)
|
|
139
|
+
_decay_evidence(s, anchor.id)
|
|
140
|
+
_recalc_stability(s, anchor.id)
|
|
141
|
+
_nucleate(s, anchor.id)
|
|
142
|
+
_save(s)
|
|
143
|
+
click.echo(f"✓ 证据已添加: [{anchor_name}] {content[:40]}... quality={evidence.quality}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@main.command()
|
|
147
|
+
@click.option("--anchor", "-a", default=None, help="按锚点名称过滤")
|
|
148
|
+
@click.option("--quality", "-q", default=None, help="按质量过滤: strong/normal/weak")
|
|
149
|
+
def evidence_list(anchor, quality):
|
|
150
|
+
"""列出证据"""
|
|
151
|
+
s = _load()
|
|
152
|
+
evs = s.evidences
|
|
153
|
+
if anchor:
|
|
154
|
+
a = next((x for x in s.anchors if x.name == anchor), None)
|
|
155
|
+
if not a:
|
|
156
|
+
click.echo(f"锚点 '{anchor}' 不存在。")
|
|
157
|
+
return
|
|
158
|
+
evs = [e for e in evs if e.anchor_id == a.id]
|
|
159
|
+
click.echo(f"--- {anchor} ---")
|
|
160
|
+
if quality:
|
|
161
|
+
evs = [e for e in evs if e.quality == quality]
|
|
162
|
+
click.echo(f"--- quality={quality} ---")
|
|
163
|
+
if not evs:
|
|
164
|
+
click.echo("暂无证据。")
|
|
165
|
+
return
|
|
166
|
+
for e in sorted(evs, key=lambda x: x.timestamp, reverse=True):
|
|
167
|
+
anchor_name = next((a.name for a in s.anchors if a.id == e.anchor_id), "?")
|
|
168
|
+
click.echo(f"[{e.weight:.2f}|{e.quality}] {e.timestamp[:19]} [{anchor_name}] {e.content}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@main.command()
|
|
172
|
+
def memory_list():
|
|
173
|
+
"""列出已形成的记忆"""
|
|
174
|
+
s = _load()
|
|
175
|
+
if not s.memories:
|
|
176
|
+
click.echo("暂无记忆。当同一锚点下有 2 条以上一致证据时自动成核。")
|
|
177
|
+
return
|
|
178
|
+
for m in sorted(s.memories, key=lambda x: x.confidence, reverse=True):
|
|
179
|
+
click.echo(f"[conf={m.confidence:.2f}] {m.formed_at[:19]} {m.content}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@main.command()
|
|
183
|
+
@click.argument("source")
|
|
184
|
+
@click.argument("target")
|
|
185
|
+
@click.option("--type", "-t", "rel_type", default="relates_to",
|
|
186
|
+
help="关系类型: depends_on/relates_to/supersedes/conflicts_with")
|
|
187
|
+
@click.option("--weight", "-w", default=1.0, help="关系强度 0.0~1.0")
|
|
188
|
+
def relate(source, target, rel_type, weight):
|
|
189
|
+
"""在两个锚点之间建立关系"""
|
|
190
|
+
s = _load()
|
|
191
|
+
src = next((a for a in s.anchors if a.name == source), None)
|
|
192
|
+
tgt = next((a for a in s.anchors if a.name == target), None)
|
|
193
|
+
if not src or not tgt:
|
|
194
|
+
click.echo("源或目标锚点不存在。")
|
|
195
|
+
return
|
|
196
|
+
# 去重
|
|
197
|
+
for r in s.relations:
|
|
198
|
+
if r.source_id == src.id and r.target_id == tgt.id:
|
|
199
|
+
click.echo(f"关系已存在: {source} → {target} [{r.relation_type}]")
|
|
200
|
+
return
|
|
201
|
+
rel = AnchorRelation(
|
|
202
|
+
source_id=src.id,
|
|
203
|
+
target_id=tgt.id,
|
|
204
|
+
relation_type=rel_type,
|
|
205
|
+
weight=min(max(weight, 0.0), 1.0),
|
|
206
|
+
)
|
|
207
|
+
s.relations.append(rel)
|
|
208
|
+
_save(s)
|
|
209
|
+
click.echo(f"✓ 关系已建立: {source} --[{rel_type}]--> {target}")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@main.command()
|
|
213
|
+
def relation_list():
|
|
214
|
+
"""列出所有锚点关系"""
|
|
215
|
+
s = _load()
|
|
216
|
+
if not s.relations:
|
|
217
|
+
click.echo("暂无关系。使用 liquid relate 建立。")
|
|
218
|
+
return
|
|
219
|
+
name_map = {a.id: a.name for a in s.anchors}
|
|
220
|
+
for r in s.relations:
|
|
221
|
+
src = name_map.get(r.source_id, "?")
|
|
222
|
+
tgt = name_map.get(r.target_id, "?")
|
|
223
|
+
click.echo(f"{src} --[{r.relation_type}:{r.weight:.1f}]--> {tgt}")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@main.command()
|
|
227
|
+
def check():
|
|
228
|
+
"""检查工作区熵值(六维)"""
|
|
229
|
+
s = _load()
|
|
230
|
+
ent = calculate(s)
|
|
231
|
+
if ent < 0.3:
|
|
232
|
+
level, icon = "GREEN", "✓"
|
|
233
|
+
elif ent < 0.6:
|
|
234
|
+
level, icon = "YELLOW", "⚠"
|
|
235
|
+
else:
|
|
236
|
+
level, icon = "RED", "✗"
|
|
237
|
+
click.echo(f"{icon} 熵值: {ent:.4f} [{level}]")
|
|
238
|
+
if ent >= 0.6:
|
|
239
|
+
click.echo("建议: 检查锚点漂移、添加新证据或解决冲突。")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@main.command()
|
|
243
|
+
def snapshot():
|
|
244
|
+
"""保存当前状态快照"""
|
|
245
|
+
s = _load()
|
|
246
|
+
ent = calculate(s)
|
|
247
|
+
snap = StateSnapshot(
|
|
248
|
+
entropy=ent,
|
|
249
|
+
anchor_count=len(s.anchors),
|
|
250
|
+
evidence_count=len(s.evidences),
|
|
251
|
+
memory_count=len(s.memories),
|
|
252
|
+
conflict_count=len(s.conflicts),
|
|
253
|
+
)
|
|
254
|
+
s.snapshots.append(snap)
|
|
255
|
+
_save(s)
|
|
256
|
+
click.echo(f"✓ 快照已保存 (熵值: {ent:.4f})")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@main.command()
|
|
260
|
+
def snapshots():
|
|
261
|
+
"""列出历史快照"""
|
|
262
|
+
s = _load()
|
|
263
|
+
if not s.snapshots:
|
|
264
|
+
click.echo("暂无快照。使用 liquid snapshot 保存。")
|
|
265
|
+
return
|
|
266
|
+
for i, snap in enumerate(s.snapshots):
|
|
267
|
+
click.echo(f"[{i}] {snap.timestamp[:19]} 熵={snap.entropy:.4f} "
|
|
268
|
+
f"A:{snap.anchor_count} E:{snap.evidence_count} "
|
|
269
|
+
f"M:{snap.memory_count} C:{snap.conflict_count}")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@main.command()
|
|
273
|
+
def audit():
|
|
274
|
+
"""查看审计链状态"""
|
|
275
|
+
s = _load()
|
|
276
|
+
ac = get_audit_chain(WORKSPACE)
|
|
277
|
+
click.echo(f"审计根哈希: {ac.root}")
|
|
278
|
+
click.echo(f"链长度: {len(ac._chain)}")
|
|
279
|
+
click.echo(f"当前state快照校验: anchors={len(s.anchors)} evidence={len(s.evidences)}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@main.command()
|
|
283
|
+
def audit_log():
|
|
284
|
+
"""查看审计日志"""
|
|
285
|
+
ac = get_audit_chain(WORKSPACE)
|
|
286
|
+
path = WORKSPACE / ".liquid" / "audit.log"
|
|
287
|
+
if not path.exists():
|
|
288
|
+
click.echo("审计日志为空。")
|
|
289
|
+
return
|
|
290
|
+
with open(path, "r") as f:
|
|
291
|
+
content = f.read().strip()
|
|
292
|
+
click.echo(content[-2000:] if len(content) > 2000 else content)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@main.command()
|
|
296
|
+
@click.argument("name")
|
|
297
|
+
@click.argument("description")
|
|
298
|
+
@click.option("--density", "-d", default="medium", help="价值密度: high/medium/low")
|
|
299
|
+
@click.option("--stage", "-s", default="raw", help="认知阶段: raw/wip/crystallized/tooling")
|
|
300
|
+
def anchor_add(name, description, density, stage):
|
|
301
|
+
"""添加锚点(手动指定分类,支持 auto_classify 自动刷新)"""
|
|
302
|
+
s = _load()
|
|
303
|
+
if any(a.name == name for a in s.anchors):
|
|
304
|
+
click.echo(f"锚点 '{name}' 已存在。")
|
|
305
|
+
return
|
|
306
|
+
anchor = Anchor(
|
|
307
|
+
name=name,
|
|
308
|
+
description=description,
|
|
309
|
+
value_density=density if density in ("high", "medium", "low") else "medium",
|
|
310
|
+
cognitive_stage=stage if stage in ("raw", "wip", "crystallized", "tooling") else "raw",
|
|
311
|
+
)
|
|
312
|
+
# 自动分类:基于名称+描述
|
|
313
|
+
anchor.auto_classify(0)
|
|
314
|
+
s.anchors.append(anchor)
|
|
315
|
+
ac = get_audit_chain(WORKSPACE)
|
|
316
|
+
ac.append("anchor_add", f"{anchor.id}:{name}")
|
|
317
|
+
_save(s)
|
|
318
|
+
click.echo(f"✓ 锚点已添加: {name} ({anchor.id}) [{anchor.value_density}/{anchor.cognitive_stage}/{anchor.liquidity}]")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from .workspace import (
|
|
6
|
+
WorkspaceState, Anchor, Evidence, Memory,
|
|
7
|
+
Conflict, StateSnapshot, AnchorRelation, AuditChain, now,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
LIQUID_DIR = ".liquid"
|
|
12
|
+
STATE_FILE = "state.json"
|
|
13
|
+
AUDIT_FILE = "audit.log"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _ensure_dir(workspace_root: Path) -> Path:
|
|
17
|
+
d = workspace_root / LIQUID_DIR
|
|
18
|
+
d.mkdir(exist_ok=True)
|
|
19
|
+
return d
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_audit_chain(workspace_root: Path) -> AuditChain:
|
|
23
|
+
"""获取审计链实例"""
|
|
24
|
+
return AuditChain(str(_ensure_dir(workspace_root) / AUDIT_FILE))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load(workspace_root: Path) -> WorkspaceState:
|
|
28
|
+
path = _ensure_dir(workspace_root) / STATE_FILE
|
|
29
|
+
if not path.exists():
|
|
30
|
+
return WorkspaceState()
|
|
31
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
32
|
+
data = json.load(f)
|
|
33
|
+
return _from_dict(data)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def save(state: WorkspaceState, workspace_root: Path):
|
|
37
|
+
state.updated_at = now()
|
|
38
|
+
path = _ensure_dir(workspace_root) / STATE_FILE
|
|
39
|
+
data = asdict(state)
|
|
40
|
+
# 审计:每次保存记录state快照哈希
|
|
41
|
+
audit = get_audit_chain(workspace_root)
|
|
42
|
+
audit.append("state_save",
|
|
43
|
+
f"anchors={len(state.anchors)}_evidences={len(state.evidences)}"
|
|
44
|
+
f"_memories={len(state.memories)}_relations={len(state.relations)}"
|
|
45
|
+
)
|
|
46
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
47
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _from_dict(data: dict) -> WorkspaceState:
|
|
51
|
+
return WorkspaceState(
|
|
52
|
+
anchors=[Anchor(**a) for a in data.get("anchors", [])],
|
|
53
|
+
evidences=[Evidence(**e) for e in data.get("evidences", [])],
|
|
54
|
+
memories=[Memory(**m) for m in data.get("memories", [])],
|
|
55
|
+
conflicts=[Conflict(**c) for c in data.get("conflicts", [])],
|
|
56
|
+
relations=[AnchorRelation(**r) for r in data.get("relations", [])],
|
|
57
|
+
snapshots=[StateSnapshot(**s) for s in data.get("snapshots", [])],
|
|
58
|
+
version=data.get("version", "0.3.0"),
|
|
59
|
+
updated_at=data.get("updated_at", ""),
|
|
60
|
+
audit_chain_hash=data.get("audit_chain_hash", "genesis"),
|
|
61
|
+
audit_prev_hash=data.get("audit_prev_hash", ""),
|
|
62
|
+
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import uuid
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def now() -> str:
|
|
11
|
+
return datetime.now(timezone.utc).isoformat()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def uid() -> str:
|
|
15
|
+
return uuid.uuid4().hex[:12]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ========== 链式哈希审计(借鉴 KFG MemoryGovernance)==========
|
|
19
|
+
|
|
20
|
+
class AuditChain:
|
|
21
|
+
"""轻量级审计链:每次变更追加 SHA256 链式哈希"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, audit_path: str):
|
|
24
|
+
self._path = audit_path
|
|
25
|
+
self._chain: list[str] = []
|
|
26
|
+
self._load()
|
|
27
|
+
|
|
28
|
+
def _load(self):
|
|
29
|
+
if os.path.exists(self._path):
|
|
30
|
+
with open(self._path, "r") as f:
|
|
31
|
+
for line in f:
|
|
32
|
+
line = line.strip()
|
|
33
|
+
if line:
|
|
34
|
+
parts = line.split("|")
|
|
35
|
+
if len(parts) >= 2:
|
|
36
|
+
self._chain.append(parts[1])
|
|
37
|
+
|
|
38
|
+
def append(self, event_type: str, data: str) -> str:
|
|
39
|
+
prev = self._chain[-1] if self._chain else "genesis"
|
|
40
|
+
chain_hash = hashlib.sha256(f"{event_type}:{data}:{prev}".encode()).hexdigest()[:16]
|
|
41
|
+
self._chain.append(chain_hash)
|
|
42
|
+
ts = now()
|
|
43
|
+
with open(self._path, "a") as f:
|
|
44
|
+
f.write(f"{ts}|{chain_hash}|{event_type}|{data}|{prev}\n")
|
|
45
|
+
return chain_hash
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def root(self) -> str:
|
|
49
|
+
return self._chain[-1] if self._chain else "genesis"
|
|
50
|
+
|
|
51
|
+
def verify(self, entry: str) -> bool:
|
|
52
|
+
"""验证某条完整日志行是否匹配链中记录
|
|
53
|
+
|
|
54
|
+
格式: timestamp|chain_hash|event_type|data|prev_hash
|
|
55
|
+
"""
|
|
56
|
+
if "|" not in entry:
|
|
57
|
+
return False
|
|
58
|
+
parts = entry.strip().split("|")
|
|
59
|
+
if len(parts) < 5:
|
|
60
|
+
return False
|
|
61
|
+
_, stored_hash, event_type, data, prev = parts[:5]
|
|
62
|
+
expected = hashlib.sha256(f"{event_type}:{data}:{prev}".encode()).hexdigest()[:16]
|
|
63
|
+
return expected == stored_hash
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# --- 四维分类(借鉴 KFG 4D Classifier)---
|
|
67
|
+
|
|
68
|
+
# 价值密度等级
|
|
69
|
+
DensityLevel = str # "high" | "medium" | "low"
|
|
70
|
+
|
|
71
|
+
# 认知阶段
|
|
72
|
+
CognitiveStage = str # "raw" | "wip" | "crystallized" | "tooling"
|
|
73
|
+
|
|
74
|
+
# 流动性等级
|
|
75
|
+
LiquidityLevel = str # "hot" | "warm" | "cold" | "frozen"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class Anchor:
|
|
80
|
+
id: str = field(default_factory=uid)
|
|
81
|
+
name: str = ""
|
|
82
|
+
description: str = ""
|
|
83
|
+
created_at: str = field(default_factory=now)
|
|
84
|
+
|
|
85
|
+
# 稳定性(原)
|
|
86
|
+
stability: float = 1.0 # 0.0=完全漂移, 1.0=完全稳定
|
|
87
|
+
evidence_ids: list[str] = field(default_factory=list)
|
|
88
|
+
|
|
89
|
+
# 【新增】四维分类标签(借鉴 KFG)
|
|
90
|
+
value_density: str = "medium" # high | medium | low
|
|
91
|
+
cognitive_stage: str = "raw" # raw | wip | crystallized | tooling
|
|
92
|
+
liquidity: str = "warm" # hot | warm | cold | frozen
|
|
93
|
+
|
|
94
|
+
# 【新增】价值衰减指标(借鉴 KFG Factor.value 计算)
|
|
95
|
+
access_count: int = 0 # 被引用/访问次数
|
|
96
|
+
last_accessed: str = "" # 最后访问时间
|
|
97
|
+
value_score: float = 1.0 # 衰减后价值(0.0~1.0)
|
|
98
|
+
|
|
99
|
+
# 【新增】锚定强度(基于证据支持度 + 访问活跃度)
|
|
100
|
+
anchor_strength: float = 1.0 # 0.0~1.0
|
|
101
|
+
|
|
102
|
+
def decay_value(self, factor: float = 0.95, evidence_count: int = 0) -> float:
|
|
103
|
+
"""价值衰减计算(双因模型,借鉴 KFG MemoryGovernance.check_decay)
|
|
104
|
+
|
|
105
|
+
公式: score = freq_score × 0.4 + time_score × 0.6
|
|
106
|
+
- freq_score: 使用频率(证据数+访问次数),上限1.0
|
|
107
|
+
- time_score: 时间衰减(30天从1.0→0.0)
|
|
108
|
+
"""
|
|
109
|
+
if not self.created_at:
|
|
110
|
+
return 0.0
|
|
111
|
+
try:
|
|
112
|
+
created = datetime.fromisoformat(self.created_at)
|
|
113
|
+
except ValueError:
|
|
114
|
+
return 0.0
|
|
115
|
+
now_dt = datetime.now(timezone.utc)
|
|
116
|
+
days = (now_dt - created).days
|
|
117
|
+
|
|
118
|
+
# 双因模型
|
|
119
|
+
freq_score = min((evidence_count * 0.12 + self.access_count * 0.08), 1.0)
|
|
120
|
+
time_score = max(0.0, 1.0 - days / 30)
|
|
121
|
+
raw = (freq_score * 0.4 + time_score * 0.6) * (factor ** max(days, 0))
|
|
122
|
+
self.value_score = max(0.0, min(1.0, raw))
|
|
123
|
+
return self.value_score
|
|
124
|
+
|
|
125
|
+
def recalc_strength(self, evidence_count: int) -> float:
|
|
126
|
+
"""锚定强度 = 证据支持度 × 活跃度"""
|
|
127
|
+
evidence_factor = min(evidence_count / 5, 1.0) # 5条证据满
|
|
128
|
+
activity = self.value_score
|
|
129
|
+
self.anchor_strength = evidence_factor * 0.6 + activity * 0.4
|
|
130
|
+
self.anchor_strength = max(0.0, min(1.0, self.anchor_strength))
|
|
131
|
+
return self.anchor_strength
|
|
132
|
+
|
|
133
|
+
def auto_classify(self, evidence_count: int) -> dict:
|
|
134
|
+
"""自动推断四维分类标签(借鉴 KFG 4D Classifier 逻辑)
|
|
135
|
+
|
|
136
|
+
根据证据数量、锚定强度、活跃度自动调整:
|
|
137
|
+
- 价值密度: evidence多+强锚定 → high, 否则 low
|
|
138
|
+
- 认知阶段: 强锚定+高价值 → crystallized, 弱→raw
|
|
139
|
+
- 流动性: 最近有活动→hot, 否则→cold/frozen
|
|
140
|
+
"""
|
|
141
|
+
self.decay_value(evidence_count=evidence_count)
|
|
142
|
+
self.recalc_strength(evidence_count)
|
|
143
|
+
|
|
144
|
+
# 价值密度
|
|
145
|
+
if self.anchor_strength > 0.8 and evidence_count >= 3:
|
|
146
|
+
self.value_density = "high"
|
|
147
|
+
elif self.anchor_strength > 0.4 or evidence_count >= 1:
|
|
148
|
+
self.value_density = "medium"
|
|
149
|
+
else:
|
|
150
|
+
self.value_density = "low"
|
|
151
|
+
|
|
152
|
+
# 认知阶段
|
|
153
|
+
if self.value_score > 0.7 and self.anchor_strength > 0.7:
|
|
154
|
+
self.cognitive_stage = "crystallized"
|
|
155
|
+
elif self.value_score > 0.4:
|
|
156
|
+
self.cognitive_stage = "wip"
|
|
157
|
+
elif evidence_count == 0:
|
|
158
|
+
self.cognitive_stage = "raw"
|
|
159
|
+
else:
|
|
160
|
+
self.cognitive_stage = "wip"
|
|
161
|
+
|
|
162
|
+
# 流动性
|
|
163
|
+
if self.last_accessed:
|
|
164
|
+
try:
|
|
165
|
+
last = datetime.fromisoformat(self.last_accessed)
|
|
166
|
+
gap_hours = (datetime.now(timezone.utc) - last).total_seconds() / 3600
|
|
167
|
+
if gap_hours < 24:
|
|
168
|
+
self.liquidity = "hot"
|
|
169
|
+
elif gap_hours < 168:
|
|
170
|
+
self.liquidity = "warm"
|
|
171
|
+
elif gap_hours < 720:
|
|
172
|
+
self.liquidity = "cold"
|
|
173
|
+
else:
|
|
174
|
+
self.liquidity = "frozen"
|
|
175
|
+
except ValueError:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"value_density": self.value_density,
|
|
180
|
+
"cognitive_stage": self.cognitive_stage,
|
|
181
|
+
"liquidity": self.liquidity,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class Evidence:
|
|
187
|
+
id: str = field(default_factory=uid)
|
|
188
|
+
anchor_id: str = ""
|
|
189
|
+
content: str = ""
|
|
190
|
+
timestamp: str = field(default_factory=now)
|
|
191
|
+
weight: float = 1.0 # 证据权重, 默认1.0
|
|
192
|
+
|
|
193
|
+
# 【新增】证据质量标签
|
|
194
|
+
quality: str = "normal" # strong | normal | weak
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class Memory:
|
|
199
|
+
id: str = field(default_factory=uid)
|
|
200
|
+
content: str = ""
|
|
201
|
+
formed_at: str = field(default_factory=now)
|
|
202
|
+
evidence_ids: list[str] = field(default_factory=list) # 成核来源
|
|
203
|
+
confidence: float = 0.0 # 0.0-1.0, 基于证据一致性
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass
|
|
207
|
+
class Conflict:
|
|
208
|
+
anchor_a: str = ""
|
|
209
|
+
anchor_b: str = ""
|
|
210
|
+
description: str = ""
|
|
211
|
+
detected_at: str = field(default_factory=now)
|
|
212
|
+
severity: float = 0.0 # 0.0-1.0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# 【新增】锚点关系(借鉴 KFG Relation + 传递闭包)
|
|
216
|
+
@dataclass
|
|
217
|
+
class AnchorRelation:
|
|
218
|
+
source_id: str = ""
|
|
219
|
+
target_id: str = ""
|
|
220
|
+
relation_type: str = "relates_to" # depends_on | relates_to | supersedes | conflicts_with
|
|
221
|
+
weight: float = 1.0 # 0.0~1.0
|
|
222
|
+
created_at: str = field(default_factory=now)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class StateSnapshot:
|
|
227
|
+
timestamp: str = field(default_factory=now)
|
|
228
|
+
entropy: float = 0.0
|
|
229
|
+
anchor_count: int = 0
|
|
230
|
+
evidence_count: int = 0
|
|
231
|
+
memory_count: int = 0
|
|
232
|
+
conflict_count: int = 0
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class WorkspaceState:
|
|
237
|
+
anchors: list[Anchor] = field(default_factory=list)
|
|
238
|
+
evidences: list[Evidence] = field(default_factory=list)
|
|
239
|
+
memories: list[Memory] = field(default_factory=list)
|
|
240
|
+
conflicts: list[Conflict] = field(default_factory=list)
|
|
241
|
+
snapshots: list[StateSnapshot] = field(default_factory=list)
|
|
242
|
+
# 【新增】锚点关系
|
|
243
|
+
relations: list[AnchorRelation] = field(default_factory=list)
|
|
244
|
+
# 【新增】审计链哈希(每次 save 自动更新)
|
|
245
|
+
audit_chain_hash: str = "genesis"
|
|
246
|
+
audit_prev_hash: str = ""
|
|
247
|
+
version: str = "0.3.0"
|
|
248
|
+
updated_at: str = field(default_factory=now)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: liquid-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
|
|
5
5
|
Author: fishbook0001
|
|
6
6
|
Maintainer: fishbook0001
|
|
@@ -123,27 +123,24 @@ save(state, Path("."))
|
|
|
123
123
|
# 初始化工作区(创建 .liquid/state.json)
|
|
124
124
|
liquid-loop init
|
|
125
125
|
|
|
126
|
-
#
|
|
126
|
+
# 添加锚点(支持自动四维分类)
|
|
127
127
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
128
128
|
|
|
129
129
|
# 注入证据
|
|
130
130
|
liquid-loop evidence_add "项目目标" "已完成 11 轮实验与 4 个实证包"
|
|
131
131
|
|
|
132
|
-
#
|
|
132
|
+
# 查看状态(含审计链哈希)
|
|
133
133
|
liquid-loop status
|
|
134
|
-
# 输出:
|
|
135
|
-
# ╭──────────────────────────────────────────────╮
|
|
136
|
-
# │ Liquid Loop 认知状态 │
|
|
137
|
-
# ├──────────┬──────┬────────┬────────┬────────┬──┤
|
|
138
|
-
# │ 锚点 │ 证据 │ 结晶 │ 冲突 │ 熵值 │ │
|
|
139
|
-
# ├──────────┼──────┼────────┼────────┼────────┼──┤
|
|
140
|
-
# │ 项目目标 │ 3 │ 1 │ 0 │ 0.2760 │ 🟢│
|
|
141
|
-
# ╰──────────┴──────┴────────┴────────┴────────┴──╯
|
|
142
|
-
# 锚点稳定性: 项目目标=0.88
|
|
143
134
|
|
|
144
135
|
# 列出所有记忆结晶
|
|
145
136
|
liquid-loop memory_list
|
|
146
137
|
|
|
138
|
+
# 审计:验证链式哈希完整性
|
|
139
|
+
liquid-loop audit
|
|
140
|
+
|
|
141
|
+
# 查看审计日志(最近 20 条)
|
|
142
|
+
liquid-loop audit-log --tail 20
|
|
143
|
+
|
|
147
144
|
# 快照(记录当前认知基线)
|
|
148
145
|
liquid-loop snapshot
|
|
149
146
|
```
|
|
@@ -194,10 +191,10 @@ Liquid Loop 是唯一完全自组织 + 零 LLM 管理的系统。
|
|
|
194
191
|
liquid-loop/
|
|
195
192
|
├── liquid_loop/
|
|
196
193
|
│ ├── __init__.py # 公共 API 导出
|
|
197
|
-
│ ├── workspace.py # 核心数据模型
|
|
198
|
-
│ ├── storage.py # JSON 持久化
|
|
194
|
+
│ ├── workspace.py # 核心数据模型 + AuditChain + auto_classify + decay
|
|
195
|
+
│ ├── storage.py # JSON 持久化 + 审计链写入
|
|
199
196
|
│ ├── entropy.py # 四维熵值计算
|
|
200
|
-
│ └── cli.py # Click CLI (
|
|
197
|
+
│ └── cli.py # Click CLI (11 命令)
|
|
201
198
|
├── examples/
|
|
202
199
|
│ └── quickstart.py
|
|
203
200
|
├── tests/ # 待补充
|
|
@@ -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.3.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"}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Liquid Loop — Self-Organizing Cognitive Memory for AI Agents
|
|
3
|
-
|
|
4
|
-
A zero-LLM-dependency cognitive runtime implementing the Liquid Loop theory:
|
|
5
|
-
Anchor → Evidence → Memory Nucleation with four-dimensional entropy monitoring.
|
|
6
|
-
|
|
7
|
-
Core Concepts:
|
|
8
|
-
- Anchor: Cognitive focus points with stability tracking
|
|
9
|
-
- Evidence: Observations that accumulate under anchors (exponential decay)
|
|
10
|
-
- Memory: Crystallized knowledge from 2+ consistent evidences
|
|
11
|
-
- Entropy: Four-dimensional cognitive health metric (drift/conflict/fragmentation/gap)
|
|
12
|
-
|
|
13
|
-
Usage:
|
|
14
|
-
from liquid_loop import WorkspaceState, Anchor, Evidence, Memory
|
|
15
|
-
from liquid_loop import load, save, calculate_entropy
|
|
16
|
-
from liquid_loop.cli import main as cli_main
|
|
17
|
-
|
|
18
|
-
# Quick start
|
|
19
|
-
state = WorkspaceState()
|
|
20
|
-
anchor_id = state.add_anchor("核心使命", "系统的核心目标")
|
|
21
|
-
state.add_evidence(anchor_id, "用户偏好简洁输出")
|
|
22
|
-
state.add_evidence(anchor_id, "用户偏好简洁输出") # 2次一致 -> 结晶
|
|
23
|
-
print(f"结晶记忆: {len(state.memories)} 条")
|
|
24
|
-
|
|
25
|
-
CLI:
|
|
26
|
-
python -m liquid_loop init
|
|
27
|
-
python -m liquid_loop anchor_add "项目目标" "完成液环论文"
|
|
28
|
-
python -m liquid_loop evidence_add "项目目标" "已完成实证数据收集"
|
|
29
|
-
python -m liquid_loop status
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
from liquid_loop.workspace import (
|
|
33
|
-
WorkspaceState,
|
|
34
|
-
Anchor,
|
|
35
|
-
Evidence,
|
|
36
|
-
Memory,
|
|
37
|
-
Conflict,
|
|
38
|
-
StateSnapshot,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
from liquid_loop.storage import load, save, LIQUID_DIR, STATE_FILE
|
|
42
|
-
|
|
43
|
-
from liquid_loop.entropy import calculate_entropy
|
|
44
|
-
|
|
45
|
-
__version__ = "0.1.0"
|
|
46
|
-
__author__ = "Fei Ge"
|
|
47
|
-
__license__ = "MIT"
|
|
48
|
-
|
|
49
|
-
__all__ = [
|
|
50
|
-
# Core data models
|
|
51
|
-
"WorkspaceState",
|
|
52
|
-
"Anchor",
|
|
53
|
-
"Evidence",
|
|
54
|
-
"Memory",
|
|
55
|
-
"Conflict",
|
|
56
|
-
"StateSnapshot",
|
|
57
|
-
# Persistence
|
|
58
|
-
"load",
|
|
59
|
-
"save",
|
|
60
|
-
"LIQUID_DIR",
|
|
61
|
-
"STATE_FILE",
|
|
62
|
-
# Entropy
|
|
63
|
-
"calculate_entropy",
|
|
64
|
-
"EntropyComponents",
|
|
65
|
-
# CLI
|
|
66
|
-
"cli_main",
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
# Lazy import CLI to avoid click dependency at import time
|
|
70
|
-
def cli_main():
|
|
71
|
-
from liquid_loop.cli import main
|
|
72
|
-
return main()
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from .workspace import (
|
|
4
|
-
WorkspaceState, Anchor, Evidence, Memory, StateSnapshot, now,
|
|
5
|
-
_nucleate, _decay_evidence, _recalc_stability,
|
|
6
|
-
)
|
|
7
|
-
from .storage import load, save
|
|
8
|
-
from .entropy import calculate as calculate_entropy
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
WORKSPACE = Path.cwd()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _load():
|
|
15
|
-
return load(WORKSPACE)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _save(state: WorkspaceState):
|
|
19
|
-
state.updated_at = now()
|
|
20
|
-
save(state, WORKSPACE)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# --- CLI ---
|
|
24
|
-
|
|
25
|
-
@click.group()
|
|
26
|
-
def main():
|
|
27
|
-
"""Liquid Loop — Workspace Cognitive Runtime"""
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@main.command()
|
|
32
|
-
def init():
|
|
33
|
-
"""初始化 Liquid Loop 工作区"""
|
|
34
|
-
s = _load()
|
|
35
|
-
if s.anchors or s.evidences:
|
|
36
|
-
click.echo("工作区已初始化,无需重复操作。")
|
|
37
|
-
return
|
|
38
|
-
s.version = "0.1.0"
|
|
39
|
-
_save(s)
|
|
40
|
-
click.echo(f"✓ Liquid Loop 工作区已初始化: {WORKSPACE}/.liquid/")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@main.command()
|
|
44
|
-
def status():
|
|
45
|
-
"""显示当前认知状态"""
|
|
46
|
-
s = _load()
|
|
47
|
-
ent = calculate_entropy(s)
|
|
48
|
-
level = "GREEN" if ent < 0.3 else ("YELLOW" if ent < 0.6 else "RED")
|
|
49
|
-
latest_ev = max((e.timestamp for e in s.evidences), default="无活动")
|
|
50
|
-
click.echo(f"锚点: {len(s.anchors)} | 证据: {len(s.evidences)} | 记忆: {len(s.memories)} | 冲突: {len(s.conflicts)}")
|
|
51
|
-
click.echo(f"熵值: {ent:.4f} [{level}] | 最新活动: {latest_ev}")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@main.command()
|
|
55
|
-
@click.argument("name")
|
|
56
|
-
@click.argument("description")
|
|
57
|
-
def anchor_add(name, description):
|
|
58
|
-
"""添加锚点"""
|
|
59
|
-
s = _load()
|
|
60
|
-
# 同名检查
|
|
61
|
-
if any(a.name == name for a in s.anchors):
|
|
62
|
-
click.echo(f"锚点 '{name}' 已存在。")
|
|
63
|
-
return
|
|
64
|
-
anchor = Anchor(name=name, description=description)
|
|
65
|
-
s.anchors.append(anchor)
|
|
66
|
-
_save(s)
|
|
67
|
-
click.echo(f"✓ 锚点已添加: {name} ({anchor.id})")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@main.command()
|
|
71
|
-
def anchor_list():
|
|
72
|
-
"""列出所有锚点"""
|
|
73
|
-
s = _load()
|
|
74
|
-
if not s.anchors:
|
|
75
|
-
click.echo("暂无锚点。使用 liquid anchor add <名称> <描述> 添加。")
|
|
76
|
-
return
|
|
77
|
-
for a in s.anchors:
|
|
78
|
-
bar = "█" * int(a.stability * 10) + "░" * (10 - int(a.stability * 10))
|
|
79
|
-
click.echo(f"[{a.stability:.2f}] {bar} {a.name}: {a.description}")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@main.command()
|
|
83
|
-
@click.argument("anchor_name")
|
|
84
|
-
@click.argument("content")
|
|
85
|
-
def evidence_add(anchor_name, content):
|
|
86
|
-
"""为锚点添加证据"""
|
|
87
|
-
s = _load()
|
|
88
|
-
anchor = next((a for a in s.anchors if a.name == anchor_name), None)
|
|
89
|
-
if not anchor:
|
|
90
|
-
click.echo(f"锚点 '{anchor_name}' 不存在。请先创建。")
|
|
91
|
-
return
|
|
92
|
-
evidence = Evidence(anchor_id=anchor.id, content=content)
|
|
93
|
-
s.evidences.append(evidence)
|
|
94
|
-
anchor.evidence_ids.append(evidence.id)
|
|
95
|
-
# 衰减 + 重算 + 成核
|
|
96
|
-
_decay_evidence(s, anchor.id)
|
|
97
|
-
_recalc_stability(s, anchor.id)
|
|
98
|
-
_nucleate(s, anchor.id)
|
|
99
|
-
_save(s)
|
|
100
|
-
click.echo(f"✓ 证据已添加: [{anchor_name}] {content[:40]}... ({evidence.id})")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@main.command()
|
|
104
|
-
@click.option("--anchor", "-a", default=None, help="按锚点名称过滤")
|
|
105
|
-
def evidence_list(anchor):
|
|
106
|
-
"""列出证据"""
|
|
107
|
-
s = _load()
|
|
108
|
-
evs = s.evidences
|
|
109
|
-
if anchor:
|
|
110
|
-
a = next((x for x in s.anchors if x.name == anchor), None)
|
|
111
|
-
if not a:
|
|
112
|
-
click.echo(f"锚点 '{anchor}' 不存在。")
|
|
113
|
-
return
|
|
114
|
-
evs = [e for e in evs if e.anchor_id == a.id]
|
|
115
|
-
click.echo(f"--- {anchor} ---")
|
|
116
|
-
if not evs:
|
|
117
|
-
click.echo("暂无证据。")
|
|
118
|
-
return
|
|
119
|
-
for e in sorted(evs, key=lambda x: x.timestamp, reverse=True):
|
|
120
|
-
anchor_name = next((a.name for a in s.anchors if a.id == e.anchor_id), "?")
|
|
121
|
-
click.echo(f"[{e.weight:.2f}] {e.timestamp[:19]} [{anchor_name}] {e.content}")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@main.command()
|
|
125
|
-
def memory_list():
|
|
126
|
-
"""列出已形成的记忆"""
|
|
127
|
-
s = _load()
|
|
128
|
-
if not s.memories:
|
|
129
|
-
click.echo("暂无记忆。当同一锚点下有 2 条以上一致证据时自动成核。")
|
|
130
|
-
return
|
|
131
|
-
for m in sorted(s.memories, key=lambda x: x.confidence, reverse=True):
|
|
132
|
-
click.echo(f"[conf={m.confidence:.2f}] {m.formed_at[:19]} {m.content}")
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@main.command()
|
|
136
|
-
def check():
|
|
137
|
-
"""检查工作区熵值"""
|
|
138
|
-
s = _load()
|
|
139
|
-
ent = calculate_entropy(s)
|
|
140
|
-
if ent < 0.3:
|
|
141
|
-
level, icon = "GREEN", "✓"
|
|
142
|
-
elif ent < 0.6:
|
|
143
|
-
level, icon = "YELLOW", "⚠"
|
|
144
|
-
else:
|
|
145
|
-
level, icon = "RED", "✗"
|
|
146
|
-
click.echo(f"{icon} 熵值: {ent:.4f} [{level}]")
|
|
147
|
-
if ent >= 0.6:
|
|
148
|
-
click.echo("建议: 检查锚点漂移、添加新证据或解决冲突。")
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@main.command()
|
|
152
|
-
def snapshot():
|
|
153
|
-
"""保存当前状态快照"""
|
|
154
|
-
s = _load()
|
|
155
|
-
ent = calculate_entropy(s)
|
|
156
|
-
snap = StateSnapshot(
|
|
157
|
-
entropy=ent,
|
|
158
|
-
anchor_count=len(s.anchors),
|
|
159
|
-
evidence_count=len(s.evidences),
|
|
160
|
-
memory_count=len(s.memories),
|
|
161
|
-
conflict_count=len(s.conflicts),
|
|
162
|
-
)
|
|
163
|
-
s.snapshots.append(snap)
|
|
164
|
-
_save(s)
|
|
165
|
-
click.echo(f"✓ 快照已保存 (熵值: {ent:.4f})")
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@main.command()
|
|
169
|
-
def snapshots():
|
|
170
|
-
"""列出历史快照"""
|
|
171
|
-
s = _load()
|
|
172
|
-
if not s.snapshots:
|
|
173
|
-
click.echo("暂无快照。使用 liquid snapshot 保存。")
|
|
174
|
-
return
|
|
175
|
-
for i, snap in enumerate(s.snapshots):
|
|
176
|
-
click.echo(f"[{i}] {snap.timestamp[:19]} 熵={snap.entropy:.4f} "
|
|
177
|
-
f"A:{snap.anchor_count} E:{snap.evidence_count} "
|
|
178
|
-
f"M:{snap.memory_count} C:{snap.conflict_count}")
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if __name__ == "__main__":
|
|
182
|
-
main()
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from dataclasses import asdict
|
|
5
|
-
from .workspace import WorkspaceState, Anchor, Evidence, Memory, Conflict, StateSnapshot
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
LIQUID_DIR = ".liquid"
|
|
9
|
-
STATE_FILE = "state.json"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _ensure_dir(workspace_root) -> Path:
|
|
13
|
-
if isinstance(workspace_root, str):
|
|
14
|
-
workspace_root = Path(workspace_root)
|
|
15
|
-
d = workspace_root / LIQUID_DIR
|
|
16
|
-
d.mkdir(exist_ok=True)
|
|
17
|
-
return d
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def load(workspace_root) -> WorkspaceState:
|
|
21
|
-
path = _ensure_dir(workspace_root) / STATE_FILE
|
|
22
|
-
if not path.exists():
|
|
23
|
-
return WorkspaceState()
|
|
24
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
25
|
-
data = json.load(f)
|
|
26
|
-
return _from_dict(data)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def save(state: WorkspaceState, workspace_root):
|
|
30
|
-
path = _ensure_dir(workspace_root) / STATE_FILE
|
|
31
|
-
with open(path, "w", encoding="utf-8") as f:
|
|
32
|
-
json.dump(asdict(state), f, indent=2, ensure_ascii=False)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _from_dict(data: dict) -> WorkspaceState:
|
|
36
|
-
return WorkspaceState(
|
|
37
|
-
anchors=[Anchor(**a) for a in data.get("anchors", [])],
|
|
38
|
-
evidences=[Evidence(**e) for e in data.get("evidences", [])],
|
|
39
|
-
memories=[Memory(**m) for m in data.get("memories", [])],
|
|
40
|
-
conflicts=[Conflict(**c) for c in data.get("conflicts", [])],
|
|
41
|
-
snapshots=[StateSnapshot(**s) for s in data.get("snapshots", [])],
|
|
42
|
-
version=data.get("version", "0.1.0"),
|
|
43
|
-
updated_at=data.get("updated_at", ""),
|
|
44
|
-
)
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
from typing import Optional
|
|
4
|
-
import uuid
|
|
5
|
-
from collections import Counter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def now() -> str:
|
|
9
|
-
return datetime.now(timezone.utc).isoformat()
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def uid() -> str:
|
|
13
|
-
return uuid.uuid4().hex[:12]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# ── Liquid Loop Core Chain ──────────────────────────────────
|
|
17
|
-
|
|
18
|
-
def _nucleate(state: "WorkspaceState", anchor_id: str):
|
|
19
|
-
"""检查指定 Anchor 下的 Evidence,2+ 条则成核为 Memory"""
|
|
20
|
-
group = [e for e in state.evidences if e.anchor_id == anchor_id]
|
|
21
|
-
if len(group) < 2:
|
|
22
|
-
return
|
|
23
|
-
content_counts = Counter(e.content for e in group)
|
|
24
|
-
for content, count in content_counts.items():
|
|
25
|
-
if count >= 2:
|
|
26
|
-
existing = [m for m in state.memories if m.content == content]
|
|
27
|
-
if existing:
|
|
28
|
-
continue
|
|
29
|
-
evidence_ids = [e.id for e in group if e.content == content]
|
|
30
|
-
confidence = min(count / len(group), 1.0)
|
|
31
|
-
memory = Memory(
|
|
32
|
-
content=content,
|
|
33
|
-
evidence_ids=evidence_ids,
|
|
34
|
-
confidence=confidence,
|
|
35
|
-
)
|
|
36
|
-
state.memories.append(memory)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _decay_evidence(state: "WorkspaceState", anchor_id: str):
|
|
40
|
-
"""Stability decay: 每次添加 Evidence 时,同 Anchor 下的旧 Evidence 权重衰减"""
|
|
41
|
-
for e in state.evidences:
|
|
42
|
-
if e.anchor_id == anchor_id:
|
|
43
|
-
e.weight = max(e.weight * 0.95, 0.1)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _recalc_stability(state: "WorkspaceState", anchor_id: str):
|
|
47
|
-
"""基于证据权重更新 Anchor 稳定性"""
|
|
48
|
-
anchor = next((a for a in state.anchors if a.id == anchor_id), None)
|
|
49
|
-
if not anchor:
|
|
50
|
-
return
|
|
51
|
-
evs = [e for e in state.evidences if e.anchor_id == anchor_id]
|
|
52
|
-
if not evs:
|
|
53
|
-
anchor.stability = 1.0
|
|
54
|
-
return
|
|
55
|
-
# 稳定性 = 平均权重(权重越高越稳定)
|
|
56
|
-
anchor.stability = sum(e.weight for e in evs) / len(evs)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass
|
|
60
|
-
class Anchor:
|
|
61
|
-
id: str = field(default_factory=uid)
|
|
62
|
-
name: str = ""
|
|
63
|
-
description: str = ""
|
|
64
|
-
created_at: str = field(default_factory=now)
|
|
65
|
-
stability: float = 1.0 # 0.0=完全漂移, 1.0=完全稳定
|
|
66
|
-
evidence_ids: list[str] = field(default_factory=list)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@dataclass
|
|
70
|
-
class Evidence:
|
|
71
|
-
id: str = field(default_factory=uid)
|
|
72
|
-
anchor_id: str = ""
|
|
73
|
-
content: str = ""
|
|
74
|
-
timestamp: str = field(default_factory=now)
|
|
75
|
-
weight: float = 1.0 # 证据权重, 默认1.0
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@dataclass
|
|
79
|
-
class Memory:
|
|
80
|
-
id: str = field(default_factory=uid)
|
|
81
|
-
content: str = ""
|
|
82
|
-
formed_at: str = field(default_factory=now)
|
|
83
|
-
evidence_ids: list[str] = field(default_factory=list) # 成核来源
|
|
84
|
-
confidence: float = 0.0 # 0.0-1.0, 基于证据一致性
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@dataclass
|
|
88
|
-
class Conflict:
|
|
89
|
-
anchor_a: str = ""
|
|
90
|
-
anchor_b: str = ""
|
|
91
|
-
description: str = ""
|
|
92
|
-
detected_at: str = field(default_factory=now)
|
|
93
|
-
severity: float = 0.0 # 0.0-1.0
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@dataclass
|
|
97
|
-
class StateSnapshot:
|
|
98
|
-
timestamp: str = field(default_factory=now)
|
|
99
|
-
entropy: float = 0.0
|
|
100
|
-
anchor_count: int = 0
|
|
101
|
-
evidence_count: int = 0
|
|
102
|
-
memory_count: int = 0
|
|
103
|
-
conflict_count: int = 0
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@dataclass
|
|
107
|
-
class WorkspaceState:
|
|
108
|
-
anchors: list[Anchor] = field(default_factory=list)
|
|
109
|
-
evidences: list[Evidence] = field(default_factory=list)
|
|
110
|
-
memories: list[Memory] = field(default_factory=list)
|
|
111
|
-
conflicts: list[Conflict] = field(default_factory=list)
|
|
112
|
-
snapshots: list[StateSnapshot] = field(default_factory=list)
|
|
113
|
-
version: str = "0.1.0"
|
|
114
|
-
updated_at: str = field(default_factory=now)
|
|
115
|
-
|
|
116
|
-
def add_anchor(self, name: str, description: str = "") -> str:
|
|
117
|
-
"""添加锚点,返回 anchor_id"""
|
|
118
|
-
anchor = Anchor(name=name, description=description)
|
|
119
|
-
self.anchors.append(anchor)
|
|
120
|
-
return anchor.id
|
|
121
|
-
|
|
122
|
-
def add_evidence(self, anchor_id: str, content: str) -> str:
|
|
123
|
-
"""添加证据到指定锚点,触发液环自动链:成核→衰减→稳定性重算"""
|
|
124
|
-
evidence = Evidence(anchor_id=anchor_id, content=content)
|
|
125
|
-
self.evidences.append(evidence)
|
|
126
|
-
# Liquid Loop 自动链
|
|
127
|
-
_nucleate(self, anchor_id)
|
|
128
|
-
_decay_evidence(self, anchor_id)
|
|
129
|
-
_recalc_stability(self, anchor_id)
|
|
130
|
-
return evidence.id
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|