compound-memory 0.1.0__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.
Potentially problematic release.
This version of compound-memory might be problematic. Click here for more details.
- {compound_memory-0.1.0/src/compound_memory.egg-info → compound_memory-0.3.0}/PKG-INFO +2 -2
- {compound_memory-0.1.0 → compound_memory-0.3.0}/README.md +1 -1
- {compound_memory-0.1.0 → compound_memory-0.3.0}/pyproject.toml +1 -1
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/__init__.py +1 -1
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/cli.py +75 -9
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/embedding.py +23 -10
- compound_memory-0.3.0/src/compound_memory/extraction.py +521 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/model.py +4 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/scoring.py +13 -0
- compound_memory-0.3.0/src/compound_memory/server.py +118 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/storage.py +180 -26
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/vector_index.py +5 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0/src/compound_memory.egg-info}/PKG-INFO +2 -2
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory.egg-info/SOURCES.txt +3 -0
- compound_memory-0.3.0/tests/test_embedding.py +154 -0
- compound_memory-0.3.0/tests/test_extraction.py +548 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_lifecycle.py +80 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_mcp_tools.py +79 -2
- compound_memory-0.3.0/tests/test_ns_isolation.py +329 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_scoring.py +25 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_vector_index.py +1 -0
- compound_memory-0.1.0/src/compound_memory/server.py +0 -88
- compound_memory-0.1.0/tests/test_embedding.py +0 -58
- {compound_memory-0.1.0 → compound_memory-0.3.0}/LICENSE +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/setup.cfg +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/index.py +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory/review_queue.py +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory.egg-info/dependency_links.txt +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory.egg-info/entry_points.txt +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory.egg-info/requires.txt +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/src/compound_memory.egg-info/top_level.txt +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_distill.py +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_index.py +0 -0
- {compound_memory-0.1.0 → compound_memory-0.3.0}/tests/test_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compound-memory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Local multi-agent shared memory with compounding (MCP server + CLI)
|
|
5
5
|
Author: chinwe
|
|
6
6
|
License-Expression: MIT
|
|
@@ -147,4 +147,4 @@ PyPI 版本不可重传,tag 必须与 `pyproject.toml` 的 `version` 一致(
|
|
|
147
147
|
|
|
148
148
|
1. **一次性配置**(PyPI → 项目 → Publishing):owner `chinwe`、repo `compound-memory`、workflow `release.yml`、environment `pypi`。首次发布时项目尚不存在,在 pypi.org 用"pending publisher"预注册即可。
|
|
149
149
|
2. **发布**:`git tag v0.1.0 && git push origin v0.1.0` → `release.yml` 自动 build + `uv publish`。
|
|
150
|
-
3. 发布后 `uvx compound-memory-server`
|
|
150
|
+
3. 发布后 `uvx --from compound-memory compound-memory-server` 即为通用安装形态(`uvx` 的参数是包名,script 名不同须用 `--from`;MCP 配置里的 `command` 换成 uvx 后不再依赖仓库克隆路径)。
|
|
@@ -118,4 +118,4 @@ PyPI 版本不可重传,tag 必须与 `pyproject.toml` 的 `version` 一致(
|
|
|
118
118
|
|
|
119
119
|
1. **一次性配置**(PyPI → 项目 → Publishing):owner `chinwe`、repo `compound-memory`、workflow `release.yml`、environment `pypi`。首次发布时项目尚不存在,在 pypi.org 用"pending publisher"预注册即可。
|
|
120
120
|
2. **发布**:`git tag v0.1.0 && git push origin v0.1.0` → `release.yml` 自动 build + `uv publish`。
|
|
121
|
-
3. 发布后 `uvx compound-memory-server`
|
|
121
|
+
3. 发布后 `uvx --from compound-memory compound-memory-server` 即为通用安装形态(`uvx` 的参数是包名,script 名不同须用 `--from`;MCP 配置里的 `command` 换成 uvx 后不再依赖仓库克隆路径)。
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "compound-memory"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Local multi-agent shared memory with compounding (MCP server + CLI)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# PEP 639 SPDX 表达式;与 License:: classifier 互斥,故后者已移除
|
|
@@ -5,12 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import datetime as dt
|
|
7
7
|
import json
|
|
8
|
+
import os
|
|
8
9
|
import sys
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
from . import __version__
|
|
13
14
|
from .embedding import auto_encoder
|
|
15
|
+
from .extraction import extract, extract_dir
|
|
14
16
|
from .storage import DISTILL_DUP_SIM_THRESHOLD, MEMORY_TYPES, MemoryStore, PROMOTION_USES_THRESHOLD, default_root
|
|
15
17
|
|
|
16
18
|
|
|
@@ -19,8 +21,13 @@ def _emit(payload: Any) -> None:
|
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def _open_store(args: argparse.Namespace) -> MemoryStore:
|
|
22
|
-
# CLI 与 server 同权:vec extra +
|
|
23
|
-
|
|
24
|
+
# CLI 与 server 同权:vec extra + 模型就绪即启用向量路,否则自动降级纯词面;
|
|
25
|
+
# 环境注入的进程身份(若有)同样生效——未设置时保持自报身份模式
|
|
26
|
+
return MemoryStore(
|
|
27
|
+
Path(args.root),
|
|
28
|
+
embedder=auto_encoder(),
|
|
29
|
+
agent_id=os.environ.get("COMPOUND_MEMORY_AGENT_ID") or None,
|
|
30
|
+
)
|
|
24
31
|
|
|
25
32
|
|
|
26
33
|
def cmd_init(args: argparse.Namespace) -> None:
|
|
@@ -29,19 +36,33 @@ def cmd_init(args: argparse.Namespace) -> None:
|
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
def cmd_write(args: argparse.Namespace) -> None:
|
|
32
|
-
_emit(
|
|
39
|
+
_emit(
|
|
40
|
+
_open_store(args).write(
|
|
41
|
+
content=args.content,
|
|
42
|
+
type=args.type,
|
|
43
|
+
source=args.source,
|
|
44
|
+
ns=args.ns,
|
|
45
|
+
key=args.key,
|
|
46
|
+
valid_from=args.valid_from,
|
|
47
|
+
valid_until=args.valid_until,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
33
50
|
|
|
34
51
|
|
|
35
52
|
def cmd_search(args: argparse.Namespace) -> None:
|
|
36
53
|
_emit(
|
|
37
54
|
_open_store(args).search(
|
|
38
|
-
query=args.query,
|
|
55
|
+
query=args.query,
|
|
56
|
+
ns=args.ns,
|
|
57
|
+
top_k=args.top_k,
|
|
58
|
+
include_neighbors=args.include_neighbors,
|
|
59
|
+
reader=args.reader,
|
|
39
60
|
)
|
|
40
61
|
)
|
|
41
62
|
|
|
42
63
|
|
|
43
64
|
def cmd_get(args: argparse.Namespace) -> None:
|
|
44
|
-
_emit(_open_store(args).get(args.id))
|
|
65
|
+
_emit(_open_store(args).get(args.id, reader=args.reader))
|
|
45
66
|
|
|
46
67
|
|
|
47
68
|
def cmd_link(args: argparse.Namespace) -> None:
|
|
@@ -62,7 +83,7 @@ def cmd_decay(args: argparse.Namespace) -> None:
|
|
|
62
83
|
|
|
63
84
|
|
|
64
85
|
def cmd_revive(args: argparse.Namespace) -> None:
|
|
65
|
-
_emit(_open_store(args).revive(args.id))
|
|
86
|
+
_emit(_open_store(args).revive(args.id, reader=args.reader))
|
|
66
87
|
|
|
67
88
|
|
|
68
89
|
def cmd_stats(args: argparse.Namespace) -> None:
|
|
@@ -88,6 +109,7 @@ def cmd_distill_plan(args: argparse.Namespace) -> None:
|
|
|
88
109
|
min_uses=args.min_uses,
|
|
89
110
|
min_confidence=args.min_confidence,
|
|
90
111
|
ns=args.ns,
|
|
112
|
+
reader=args.reader,
|
|
91
113
|
)
|
|
92
114
|
)
|
|
93
115
|
|
|
@@ -106,6 +128,14 @@ def cmd_distill_apply(args: argparse.Namespace) -> None:
|
|
|
106
128
|
)
|
|
107
129
|
|
|
108
130
|
|
|
131
|
+
def cmd_extract(args: argparse.Namespace) -> None:
|
|
132
|
+
target = Path(args.transcript)
|
|
133
|
+
if target.is_dir():
|
|
134
|
+
_emit(extract_dir(target, _open_store(args)))
|
|
135
|
+
else:
|
|
136
|
+
_emit(extract(target, _open_store(args)))
|
|
137
|
+
|
|
138
|
+
|
|
109
139
|
def cmd_git_log(args: argparse.Namespace) -> None:
|
|
110
140
|
_emit(_open_store(args).git_log(limit=args.limit))
|
|
111
141
|
|
|
@@ -121,22 +151,35 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
121
151
|
p = sub.add_parser("write")
|
|
122
152
|
p.add_argument("content"); p.add_argument("type", choices=MEMORY_TYPES)
|
|
123
153
|
p.add_argument("source"); p.add_argument("--ns", default="_shared"); p.add_argument("--key", default=None)
|
|
154
|
+
p.add_argument("--valid-from", default=None, help="ISO date: fact valid from (annotation)")
|
|
155
|
+
p.add_argument("--valid-until", default=None,
|
|
156
|
+
help="ISO date: fact expires after this day (excluded from search, still readable via get)")
|
|
124
157
|
p.set_defaults(func=cmd_write)
|
|
125
158
|
|
|
126
159
|
p = sub.add_parser("search")
|
|
127
|
-
p.add_argument("query")
|
|
160
|
+
p.add_argument("query")
|
|
161
|
+
p.add_argument("--ns", default=None,
|
|
162
|
+
help="scope: '_shared', 'agent-<name>', or omit for _shared + your own private ns")
|
|
163
|
+
p.add_argument("--top-k", type=int, default=5)
|
|
164
|
+
p.add_argument("--reader", default=None, help="caller identity, required for private agent-* namespaces")
|
|
128
165
|
p.add_argument("--no-neighbors", dest="include_neighbors", action="store_false",
|
|
129
166
|
help="omit embedded one-hop neighbors from hits")
|
|
130
167
|
p.set_defaults(func=cmd_search)
|
|
131
168
|
|
|
132
|
-
p = sub.add_parser("get")
|
|
169
|
+
p = sub.add_parser("get")
|
|
170
|
+
p.add_argument("id")
|
|
171
|
+
p.add_argument("--reader", default=None, help="caller identity, required for private agent-* namespaces")
|
|
172
|
+
p.set_defaults(func=cmd_get)
|
|
133
173
|
p = sub.add_parser("link"); p.add_argument("a"); p.add_argument("b"); p.set_defaults(func=cmd_link)
|
|
134
174
|
p = sub.add_parser("feedback"); p.add_argument("id"); p.add_argument("agent"); p.set_defaults(func=cmd_feedback)
|
|
135
175
|
|
|
136
176
|
p = sub.add_parser("decay"); p.add_argument("--now", default=None, help="ISO date override (testing)")
|
|
137
177
|
p.set_defaults(func=cmd_decay)
|
|
138
178
|
|
|
139
|
-
p = sub.add_parser("revive")
|
|
179
|
+
p = sub.add_parser("revive")
|
|
180
|
+
p.add_argument("id")
|
|
181
|
+
p.add_argument("--reader", default=None, help="caller identity, required for private agent-* namespaces")
|
|
182
|
+
p.set_defaults(func=cmd_revive)
|
|
140
183
|
p = sub.add_parser(
|
|
141
184
|
"distill-plan",
|
|
142
185
|
help="scan distillation candidates and print a signal-annotated list",
|
|
@@ -148,6 +191,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
148
191
|
p.add_argument("--min-uses", type=int, default=1, help="activity gate: uses >= this")
|
|
149
192
|
p.add_argument("--min-confidence", type=float, default=0.5, help="activity gate: confidence >= this")
|
|
150
193
|
p.add_argument("--ns", default="_shared")
|
|
194
|
+
p.add_argument("--reader", default=None, help="caller identity, required for private agent-* namespaces")
|
|
151
195
|
p.set_defaults(func=cmd_distill_plan)
|
|
152
196
|
p = sub.add_parser("distill-apply")
|
|
153
197
|
p.add_argument("content"); p.add_argument("type", choices=MEMORY_TYPES); p.add_argument("source")
|
|
@@ -170,6 +214,28 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
170
214
|
p.add_argument("--all", action="store_true", help="clear the whole queue")
|
|
171
215
|
p.set_defaults(func=cmd_review_resolve)
|
|
172
216
|
p = sub.add_parser("git-log"); p.add_argument("--limit", type=int, default=5); p.set_defaults(func=cmd_git_log)
|
|
217
|
+
p = sub.add_parser(
|
|
218
|
+
"extract",
|
|
219
|
+
help="scan a session transcript for memory candidates (deterministic pass, no LLM)",
|
|
220
|
+
description="Deterministic candidate extraction from a session transcript. Transcript shapes are "
|
|
221
|
+
"auto-detected by content (not filename): WorkBuddy session log jsonl, the ZCode session database "
|
|
222
|
+
"(~/.zcode/cli/db/db.sqlite, full history), Claude Code session log jsonl, and DeepSeek Harness "
|
|
223
|
+
"zstd-compressed session files. Pass a directory to batch-scan every supported session file under it "
|
|
224
|
+
"(<project>/<session>.jsonl layouts and <project>/<session>/session.jsonl.zstd; subagents/ skipped — "
|
|
225
|
+
"their role:user is the team-lead agent's task brief, not the human's own statement). "
|
|
226
|
+
"ZCode rollout/model-io snapshots and WorkBuddy traces/ are deliberately unsupported — they keep only "
|
|
227
|
+
"the most recent / first turns, so accepting them would look like a scan while silently dropping most "
|
|
228
|
+
"of the history. "
|
|
229
|
+
"Pattern matching only (statement -> fact, pitfall -> insight); the manifest lands in "
|
|
230
|
+
"<root>/extract/last-candidates.json. Writing stays with the agent: confirm each candidate "
|
|
231
|
+
"via memory_write (same-key conflicts still enter the review queue).",
|
|
232
|
+
)
|
|
233
|
+
p.add_argument(
|
|
234
|
+
"transcript",
|
|
235
|
+
help="path to a session log jsonl (WorkBuddy / Claude Code), the ZCode session database (.sqlite), "
|
|
236
|
+
"a DeepSeek Harness session file (.jsonl.zstd), or a directory of session logs",
|
|
237
|
+
)
|
|
238
|
+
p.set_defaults(func=cmd_extract)
|
|
173
239
|
return parser
|
|
174
240
|
|
|
175
241
|
|
|
@@ -22,6 +22,11 @@ from typing import Callable
|
|
|
22
22
|
MODEL_REPO_ID = os.environ.get("COMPOUND_MEMORY_EMBEDDING_MODEL", "Xenova/bge-small-zh-v1.5")
|
|
23
23
|
EMBED_DIM = int(os.environ.get("COMPOUND_MEMORY_EMBEDDING_DIM", "512"))
|
|
24
24
|
|
|
25
|
+
# 单次 onnx run 的批量上限:rebuild 把全库一次喂进来会变成单次巨批 run
|
|
26
|
+
# (LongMemEval 960 条长会话实测 >40 分钟无进度),切块让成本线性可控。
|
|
27
|
+
# 对调用方 encode 仍是一次全量调用——这是批量面优化,与增量索引无关。
|
|
28
|
+
ENCODE_CHUNK = 32
|
|
29
|
+
|
|
25
30
|
|
|
26
31
|
def _cache_glob(repo_id: str) -> str:
|
|
27
32
|
"""HF 缓存目录 glob:repo id 的 "/" 替换为 "--"(如 a/b → models--a--b)。"""
|
|
@@ -76,7 +81,11 @@ class BgeEncoder:
|
|
|
76
81
|
self._tokenizer: "Tokenizer | None" = None
|
|
77
82
|
|
|
78
83
|
def encode(self, texts: list[str]) -> list[list[float]]:
|
|
79
|
-
"""批量编码;L2 归一化后的 [CLS] 表示(余弦可直接用作相似度)。
|
|
84
|
+
"""批量编码;L2 归一化后的 [CLS] 表示(余弦可直接用作相似度)。
|
|
85
|
+
|
|
86
|
+
内部按 ENCODE_CHUNK 切块逐次 run:外部仍是一次全量调用、返回顺序
|
|
87
|
+
与输入一一对应,只是把单次巨批拆成有界小批(issue #16)。
|
|
88
|
+
"""
|
|
80
89
|
assert VEC_AVAILABLE # 构造已保证;reassure 类型检查
|
|
81
90
|
if self._session is None or self._tokenizer is None:
|
|
82
91
|
self._tokenizer = Tokenizer.from_file(str(self._tokenizer_path))
|
|
@@ -84,13 +93,17 @@ class BgeEncoder:
|
|
|
84
93
|
self._tokenizer.enable_padding()
|
|
85
94
|
self._session = ort.InferenceSession(str(self._onnx_path), providers=["CPUExecutionProvider"])
|
|
86
95
|
encs = self._tokenizer.encode_batch(texts)
|
|
87
|
-
feed = {
|
|
88
|
-
"input_ids": np.array([e.ids for e in encs], dtype=np.int64),
|
|
89
|
-
"attention_mask": np.array([e.attention_mask for e in encs], dtype=np.int64),
|
|
90
|
-
"token_type_ids": np.array([e.type_ids for e in encs], dtype=np.int64),
|
|
91
|
-
}
|
|
92
96
|
names = {i.name for i in self._session.get_inputs()}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
out_rows: list[list[float]] = []
|
|
98
|
+
for start in range(0, len(encs), ENCODE_CHUNK):
|
|
99
|
+
batch = encs[start : start + ENCODE_CHUNK]
|
|
100
|
+
feed = {
|
|
101
|
+
"input_ids": np.array([e.ids for e in batch], dtype=np.int64),
|
|
102
|
+
"attention_mask": np.array([e.attention_mask for e in batch], dtype=np.int64),
|
|
103
|
+
"token_type_ids": np.array([e.type_ids for e in batch], dtype=np.int64),
|
|
104
|
+
}
|
|
105
|
+
out = self._session.run(None, {k: v for k, v in feed.items() if k in names})[0]
|
|
106
|
+
cls = out[:, 0, :]
|
|
107
|
+
normed = cls / np.linalg.norm(cls, axis=1, keepdims=True)
|
|
108
|
+
out_rows.extend(normed.tolist())
|
|
109
|
+
return out_rows
|