harness-memory 0.1.1__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.
- harness_memory-0.1.1/.codebuddy/skills/pypi-publish/SKILL.md +307 -0
- harness_memory-0.1.1/.github/workflows/ci.yml +42 -0
- harness_memory-0.1.1/.github/workflows/release.yml +26 -0
- harness_memory-0.1.1/.gitignore +15 -0
- harness_memory-0.1.1/.python-version +1 -0
- harness_memory-0.1.1/AGENTS.md +58 -0
- harness_memory-0.1.1/CHANGELOG.md +21 -0
- harness_memory-0.1.1/LICENSE +21 -0
- harness_memory-0.1.1/Makefile +27 -0
- harness_memory-0.1.1/PKG-INFO +160 -0
- harness_memory-0.1.1/README.md +128 -0
- harness_memory-0.1.1/pyproject.toml +68 -0
- harness_memory-0.1.1/src/harness_memory/__init__.py +27 -0
- harness_memory-0.1.1/src/harness_memory/_version.py +24 -0
- harness_memory-0.1.1/src/harness_memory/backends/__init__.py +40 -0
- harness_memory-0.1.1/src/harness_memory/backends/postgres.py +347 -0
- harness_memory-0.1.1/src/harness_memory/backends/sqlite.py +339 -0
- harness_memory-0.1.1/src/harness_memory/core.py +154 -0
- harness_memory-0.1.1/src/harness_memory/types.py +76 -0
- harness_memory-0.1.1/tests/test_core.py +106 -0
- harness_memory-0.1.1/tests/test_postgres.py +353 -0
- harness_memory-0.1.1/tests/test_sqlite.py +231 -0
- harness_memory-0.1.1/tests/test_types.py +107 -0
- harness_memory-0.1.1/uv.lock +1011 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish
|
|
3
|
+
description: 当需要发布 Python 包到 PyPI 时使用 — 创建 release 分支、升版本号、更新 CHANGELOG、发布到 PyPI、打 tag、并创建合并请求到 main
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Publish
|
|
7
|
+
|
|
8
|
+
自动化 Python 包的完整发布流程。
|
|
9
|
+
|
|
10
|
+
**开始时宣告:** "正在使用 publish 技能发布版本 {VERSION}。"
|
|
11
|
+
|
|
12
|
+
## 配置项
|
|
13
|
+
|
|
14
|
+
以下配置有默认值,可在项目的 `.codebuddy/skills/publish/SKILL.md` 中覆盖。
|
|
15
|
+
|
|
16
|
+
| 配置项 | 默认值 | 说明 |
|
|
17
|
+
|--------|--------|------|
|
|
18
|
+
| `PUBLISH_CMD` | 自动检测(见步骤 5) | 发布到 PyPI 的命令 |
|
|
19
|
+
| `CHANGELOG_FILE` | `CHANGELOG.md` | 相对于仓库根目录的路径,文件不存在则跳过 |
|
|
20
|
+
| `VERSION_FILE` | `pyproject.toml` | 包含版本号的文件 |
|
|
21
|
+
| `VERSION_PATTERN` | `^\s*version\s*=\s*"[^"]+"` | 匹配版本行的正则表达式 |
|
|
22
|
+
| `TAG_PREFIX` | ``(空) | Git tag 前缀,设为 `v` 则生成 `v0.1.14` 风格标签 |
|
|
23
|
+
| `REMOTE` | `origin` | Git 远程仓库名 |
|
|
24
|
+
| `TARGET_BRANCH` | `main` | 合并请求的目标分支 |
|
|
25
|
+
| `RELEASE_BRANCH_PREFIX` | `release/` | release 分支名前缀 |
|
|
26
|
+
|
|
27
|
+
## 调用方式
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
/publish 0.1.14
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
目标版本号是唯一必填参数,其余均从配置或自动检测获取。
|
|
34
|
+
|
|
35
|
+
## 发布流程
|
|
36
|
+
|
|
37
|
+
### 步骤 1 — 读取配置并确认版本
|
|
38
|
+
|
|
39
|
+
1. 获取仓库根目录:`git rev-parse --show-toplevel`
|
|
40
|
+
2. 从 `VERSION_FILE` 读取当前版本:
|
|
41
|
+
```bash
|
|
42
|
+
grep -E '^\s*version\s*=\s*"[^"]+"' pyproject.toml
|
|
43
|
+
```
|
|
44
|
+
3. 检查未提交的更改:
|
|
45
|
+
```bash
|
|
46
|
+
git status --short
|
|
47
|
+
```
|
|
48
|
+
如果存在未提交的更改,它们将被包含在步骤 4 的发布提交中。这是有意为之 — 所有待处理的工作随版本一起发布。
|
|
49
|
+
4. 查找最近的 git tag:
|
|
50
|
+
```bash
|
|
51
|
+
git tag --sort=-creatordate | head -1
|
|
52
|
+
```
|
|
53
|
+
如果没有 tag,视为首次发布(在步骤 3 中使用从仓库初始到 HEAD 的所有提交)。
|
|
54
|
+
5. 展示确认信息:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
当前版本 (pyproject.toml): X.Y.Z
|
|
58
|
+
目标版本: A.B.C
|
|
59
|
+
上次发布 tag: X.Y.Z (YYYY-MM-DD)
|
|
60
|
+
Release 分支: release/A.B.C
|
|
61
|
+
目标分支 (MR): main
|
|
62
|
+
|
|
63
|
+
确认发布 X.Y.Z → A.B.C?[y/N]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
如果用户未输入 `y` 确认,立即中止。
|
|
67
|
+
|
|
68
|
+
### 步骤 2 — 创建 release 分支
|
|
69
|
+
|
|
70
|
+
从当前 HEAD 创建并切换到新的 release 分支:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git checkout -b {RELEASE_BRANCH_PREFIX}{version}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
如果分支已存在,中止:
|
|
77
|
+
```
|
|
78
|
+
✗ 分支 {RELEASE_BRANCH_PREFIX}{version} 已存在。
|
|
79
|
+
请手动删除:git branch -D {RELEASE_BRANCH_PREFIX}{version}
|
|
80
|
+
然后重新运行 /publish。
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 步骤 3 — 分析变更并生成 CHANGELOG 草稿
|
|
84
|
+
|
|
85
|
+
1. 获取上次 tag 以来的提交:
|
|
86
|
+
```bash
|
|
87
|
+
git log {last_tag}..HEAD --oneline
|
|
88
|
+
# 首次发布时:
|
|
89
|
+
git log --oneline
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. 如果没有找到提交:
|
|
93
|
+
```
|
|
94
|
+
⚠ 自上次发布 tag ({last_tag}) 以来没有新提交。
|
|
95
|
+
是否继续?[y/N]
|
|
96
|
+
```
|
|
97
|
+
用户未确认则中止。
|
|
98
|
+
|
|
99
|
+
3. 按提交前缀分类,生成 Keep a Changelog 格式的条目。
|
|
100
|
+
|
|
101
|
+
**CHANGELOG 内容必须用中文书写。** 将每个提交总结为简洁的中文要点 — 不要逐字翻译提交信息。适当合并相关提交。
|
|
102
|
+
|
|
103
|
+
分类规则:
|
|
104
|
+
- 以 `feat:` 或 `feat(` 开头 → **新增**
|
|
105
|
+
- 以 `fix:` 或 `fix(` 开头 → **修复**
|
|
106
|
+
- 以 `refactor:` 或 `perf:` 开头 → **变更**
|
|
107
|
+
- 包含 `!:` 或提交正文含 `BREAKING CHANGE:` → **变更**,加 `**Breaking:**` 前缀
|
|
108
|
+
- 以 `docs:` 开头 → **变更**
|
|
109
|
+
- 以 `chore:`、`test:`、`ci:` 开头 → 忽略(基础设施噪音)
|
|
110
|
+
- 其他所有提交 → **变更**
|
|
111
|
+
- 移除的功能 → **移除**
|
|
112
|
+
- 安全修复 → **安全**
|
|
113
|
+
|
|
114
|
+
输出格式:
|
|
115
|
+
```markdown
|
|
116
|
+
## [A.B.C] - YYYY-MM-DD
|
|
117
|
+
|
|
118
|
+
### 新增
|
|
119
|
+
- 中文描述新增功能
|
|
120
|
+
|
|
121
|
+
### 修复
|
|
122
|
+
- 中文描述修复内容
|
|
123
|
+
|
|
124
|
+
### 变更
|
|
125
|
+
- 中文描述行为变更
|
|
126
|
+
|
|
127
|
+
### 移除
|
|
128
|
+
- 中文描述移除内容
|
|
129
|
+
|
|
130
|
+
### 安全
|
|
131
|
+
- 中文描述安全修复
|
|
132
|
+
```
|
|
133
|
+
省略空的分类。日期使用 ISO 8601 格式(当天日期)。日期行与第一个分类之间、各分类之间保留空行。
|
|
134
|
+
|
|
135
|
+
4. 向用户展示草稿并请求确认:
|
|
136
|
+
```
|
|
137
|
+
CHANGELOG 草稿:
|
|
138
|
+
|
|
139
|
+
{draft}
|
|
140
|
+
|
|
141
|
+
添加到 CHANGELOG.md?[y/N/edit]
|
|
142
|
+
```
|
|
143
|
+
- `y` → 继续
|
|
144
|
+
- `n` → 中止
|
|
145
|
+
- `edit` 或其他反馈 → 询问用户:"需要什么修改?" — 等待回复后重新生成,再次展示确认。循环直到 `y` 或 `n`。
|
|
146
|
+
|
|
147
|
+
5. 如果 `CHANGELOG_FILE` 不存在,跳过此步骤(无需警告)。
|
|
148
|
+
|
|
149
|
+
### 步骤 4 — 更新文件、提交并推送 release 分支
|
|
150
|
+
|
|
151
|
+
按顺序执行:
|
|
152
|
+
|
|
153
|
+
**4a. 更新 CHANGELOG:**
|
|
154
|
+
|
|
155
|
+
在 `CHANGELOG_FILE` 中找到 `## [Unreleased]` 标题,在其后插入新版本条目(保持 `[Unreleased]` 为空):
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
## [Unreleased]
|
|
159
|
+
|
|
160
|
+
## [A.B.C] - YYYY-MM-DD
|
|
161
|
+
### 新增
|
|
162
|
+
- ...
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
如果 `## [Unreleased]` 标题不存在,在 `# Changelog` 标题行之后插入新条目(若无标题则插入到文件顶部)。
|
|
166
|
+
|
|
167
|
+
**4b. 升级 VERSION_FILE 中的版本号:**
|
|
168
|
+
|
|
169
|
+
替换 `VERSION_PATTERN` 匹配到的版本行。对于 `pyproject.toml`:
|
|
170
|
+
```bash
|
|
171
|
+
# 查找当前行:
|
|
172
|
+
grep -n '^\s*version\s*=\s*"[^"]+"' pyproject.toml
|
|
173
|
+
# 用 Edit 工具将该行的 "X.Y.Z" 替换为 "A.B.C"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**4c. 提交:**
|
|
177
|
+
|
|
178
|
+
检查工作树状态:
|
|
179
|
+
```bash
|
|
180
|
+
git status --short
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- 如果有更改(已暂存或未暂存):暂存所有文件并提交:
|
|
184
|
+
```bash
|
|
185
|
+
git add -A
|
|
186
|
+
git commit -m "chore: release {version}"
|
|
187
|
+
```
|
|
188
|
+
- 如果工作树已干净:无需提交,跳过。
|
|
189
|
+
|
|
190
|
+
**4d. 推送 release 分支:**
|
|
191
|
+
```bash
|
|
192
|
+
git push -u {REMOTE} {RELEASE_BRANCH_PREFIX}{version}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
推送失败则中止。
|
|
196
|
+
|
|
197
|
+
### 步骤 5 — 发布到 PyPI
|
|
198
|
+
|
|
199
|
+
**自动检测 PUBLISH_CMD**(仅在配置未覆盖时):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# 检查 Makefile 是否有 publish 目标
|
|
203
|
+
grep -q '^\s*publish\s*:' Makefile 2>/dev/null && echo "make publish" || echo "python -m build && python -m twine upload dist/*"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- 如果 `make publish` 目标存在 → `PUBLISH_CMD=make publish`
|
|
207
|
+
- 否则 → `PUBLISH_CMD=python -m build && python -m twine upload dist/*`
|
|
208
|
+
- 如果 `python -m build` 或 `python -m twine` 未安装,中止:"缺少构建工具。请安装:`pip install build twine`"
|
|
209
|
+
|
|
210
|
+
执行:
|
|
211
|
+
```bash
|
|
212
|
+
{PUBLISH_CMD}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
实时输出。如果退出码非零:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
✗ 发布失败(退出码 N)。已停止 — 不会创建 tag。
|
|
219
|
+
请检查上方输出,解决问题后重新运行 /publish。
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**不继续执行步骤 6。**
|
|
223
|
+
|
|
224
|
+
### 步骤 6 — 打 tag 并推送
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git tag {TAG_PREFIX}{version}
|
|
228
|
+
git push {REMOTE} {TAG_PREFIX}{version}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
如果 `git tag` 因 tag 已存在而失败:
|
|
232
|
+
```
|
|
233
|
+
✗ Tag {TAG_PREFIX}{version} 已存在。
|
|
234
|
+
请手动删除:git tag -d {TAG_PREFIX}{version}
|
|
235
|
+
然后重新运行 /publish 从此步骤重试。
|
|
236
|
+
```
|
|
237
|
+
中止。
|
|
238
|
+
|
|
239
|
+
### 步骤 7 — 创建合并请求
|
|
240
|
+
|
|
241
|
+
使用工丰 MCP 工具从 release 分支创建到 `TARGET_BRANCH` 的合并请求:
|
|
242
|
+
|
|
243
|
+
1. 从 git remote 检测项目路径:
|
|
244
|
+
```bash
|
|
245
|
+
git remote get-url {REMOTE} | sed -E 's|.*[:/](.+/.+)\.git$|\1|'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
2. 使用 `mcp__gongfeng__create_merge_request` 创建 MR:
|
|
249
|
+
- `project_id`:项目完整路径(如 `orcakit/finnie`)
|
|
250
|
+
- `title`:`chore: release {version}`
|
|
251
|
+
- `source_branch`:`{RELEASE_BRANCH_PREFIX}{version}`
|
|
252
|
+
- `target_branch`:`{TARGET_BRANCH}`
|
|
253
|
+
- `description`:步骤 3 生成的 CHANGELOG 条目
|
|
254
|
+
|
|
255
|
+
3. 成功时展示:
|
|
256
|
+
```
|
|
257
|
+
✓ 已发布 {package_name} {version} 到 PyPI
|
|
258
|
+
✓ 已创建并推送 tag {TAG_PREFIX}{version} 到 {REMOTE}
|
|
259
|
+
✓ 已创建合并请求:{RELEASE_BRANCH_PREFIX}{version} → {TARGET_BRANCH}
|
|
260
|
+
链接:{mr_url}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
4. MR 创建失败时展示警告(非致命 — 发布已成功):
|
|
264
|
+
```
|
|
265
|
+
⚠ 发布成功,但合并请求创建失败。
|
|
266
|
+
请手动创建:{RELEASE_BRANCH_PREFIX}{version} → {TARGET_BRANCH}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 步骤 8 — 切回原分支
|
|
270
|
+
|
|
271
|
+
返回创建 release 分支之前所在的分支:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
git checkout {original_branch}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
确保流程结束后用户不会停留在 release 分支上。
|
|
278
|
+
|
|
279
|
+
## 错误处理参考
|
|
280
|
+
|
|
281
|
+
| 场景 | 行为 |
|
|
282
|
+
|------|------|
|
|
283
|
+
| `VERSION_FILE` 未找到 | 中止:"找不到 VERSION_FILE:{path}" |
|
|
284
|
+
| 文件中未匹配到版本号 | 中止:"在 {VERSION_FILE} 中找不到匹配 {VERSION_PATTERN} 的版本行" |
|
|
285
|
+
| 没有 git tag(首次发布) | 使用从仓库初始到 HEAD 的所有提交;提示"首次发布 — 使用完整 git 历史" |
|
|
286
|
+
| 上次 tag 以来无提交 | 警告并询问是否继续 |
|
|
287
|
+
| Release 分支已存在 | 中止并给出删除指令 |
|
|
288
|
+
| 步骤 4 推送失败 | 中止:文件已在本地更新但未推送 |
|
|
289
|
+
| 步骤 5 发布失败 | 中止:不创建 tag、不创建 MR |
|
|
290
|
+
| 步骤 6 tag 已存在 | 中止并给出删除指令 |
|
|
291
|
+
| 步骤 7 MR 创建失败 | 仅警告(发布已成功) |
|
|
292
|
+
|
|
293
|
+
## 红线规则
|
|
294
|
+
|
|
295
|
+
**绝不:**
|
|
296
|
+
- 在发布成功前创建 tag
|
|
297
|
+
- 在发布成功前创建合并请求
|
|
298
|
+
- 跳过步骤 1 的用户确认
|
|
299
|
+
- 跳过步骤 3 的 CHANGELOG 确认
|
|
300
|
+
- 在任何步骤失败后继续执行(步骤 7 MR 失败除外)
|
|
301
|
+
- 流程结束后让用户留在 release 分支
|
|
302
|
+
|
|
303
|
+
**始终:**
|
|
304
|
+
- 任何失败立即中止(步骤 7 除外)
|
|
305
|
+
- 中止前展示完整错误输出
|
|
306
|
+
- 插入新版本条目后保持 `[Unreleased]` 为空
|
|
307
|
+
- 流程结束时切回原分支
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v4
|
|
15
|
+
- run: uv sync --all-extras
|
|
16
|
+
- run: uv run ruff check .
|
|
17
|
+
- run: uv run ruff format --check .
|
|
18
|
+
|
|
19
|
+
typecheck:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v4
|
|
24
|
+
- run: uv sync --all-extras
|
|
25
|
+
- run: uv run mypy --strict src
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: astral-sh/setup-uv@v4
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python-version }}
|
|
37
|
+
- run: uv sync --all-extras
|
|
38
|
+
- run: uv run pytest --cov=harness_memory --cov-report=xml -q
|
|
39
|
+
- uses: codecov/codecov-action@v4
|
|
40
|
+
if: matrix.python-version == '3.12'
|
|
41
|
+
with:
|
|
42
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- uses: astral-sh/setup-uv@v4
|
|
20
|
+
- run: uv build
|
|
21
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
22
|
+
with:
|
|
23
|
+
attestations: true
|
|
24
|
+
- uses: softprops/action-gh-release@v2
|
|
25
|
+
with:
|
|
26
|
+
generate_release_notes: true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Development Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make dev # Install all deps (including optional backends) via uv
|
|
9
|
+
make lint # ruff check + format check
|
|
10
|
+
make format # Auto-fix lint + format
|
|
11
|
+
make typecheck # mypy --strict src
|
|
12
|
+
make test # pytest -q
|
|
13
|
+
make all # lint + typecheck + test (full CI check)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Run a single test file or test:
|
|
17
|
+
```bash
|
|
18
|
+
uv run pytest tests/test_core.py -q
|
|
19
|
+
uv run pytest tests/test_sqlite.py::TestSearchMemories -q
|
|
20
|
+
uv run pytest tests/test_sqlite.py::TestSearchMemories::test_fts_finds_matching_nodes -v
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
This is a pluggable memory system for LLM agents (`orcakit-harness-memory`). Zero runtime dependencies for the core — only stdlib + sqlite3.
|
|
26
|
+
|
|
27
|
+
### Key Layers
|
|
28
|
+
|
|
29
|
+
1. **`Memory` (core.py)** — The public API. Wraps a backend and provides `store()`, `recall()`, `add_conversation()`, `search()`, etc. Contains a factory function (`_resolve_memory_backend`) that lazily imports backend implementations by string name.
|
|
30
|
+
|
|
31
|
+
2. **`MemoryBackend` (backends/__init__.py)** — A `Protocol` (runtime-checkable) defining the storage contract. All backends must implement this interface.
|
|
32
|
+
|
|
33
|
+
3. **Backends** — Concrete implementations:
|
|
34
|
+
- `SqliteMemoryBackend` (backends/sqlite.py) — Default. Uses FTS5 for full-text search. Namespace isolation via table-name prefix.
|
|
35
|
+
- `PostgresMemoryBackend` (backends/postgres.py) — Uses tsvector/GIN for FTS. Namespace isolation via PostgreSQL schemas. Requires `psycopg[binary]>=3.1`.
|
|
36
|
+
- Qdrant backend referenced but not yet implemented in source.
|
|
37
|
+
|
|
38
|
+
4. **Types (types.py)** — Pure dataclasses: `MemoryNode`, `Message`, `ConversationRecord`, `SearchResult`, `ConversationSummary`. No logic, no imports beyond stdlib.
|
|
39
|
+
|
|
40
|
+
### Design Patterns
|
|
41
|
+
|
|
42
|
+
- **Namespace isolation**: SQLite uses table-name prefixes (`{ns}_memory_nodes`, `{ns}_messages`, etc.); PostgreSQL uses schemas. Namespace is sanitized to `[a-z0-9_]`.
|
|
43
|
+
- **Lazy backend imports**: Optional backends (postgres, qdrant) are imported inside the factory function, so missing extras raise clear `ImportError` messages rather than import-time failures.
|
|
44
|
+
- **FTS sync**: SQLite uses triggers to keep FTS5 virtual tables in sync with source tables; PostgreSQL uses generated `TSVECTOR` columns.
|
|
45
|
+
|
|
46
|
+
### Test Conventions
|
|
47
|
+
|
|
48
|
+
- Tests use `tmp_path` fixture to create isolated SQLite databases — no shared state between tests.
|
|
49
|
+
- PostgreSQL tests (`test_postgres.py`) require a running PostgreSQL instance and are typically skipped in local dev.
|
|
50
|
+
- Test classes group related functionality (e.g., `TestStore`, `TestRecall`, `TestConversations`).
|
|
51
|
+
|
|
52
|
+
## Tooling
|
|
53
|
+
|
|
54
|
+
- **Package manager**: `uv` (lockfile: `uv.lock`)
|
|
55
|
+
- **Build**: hatchling + hatch-vcs (version from git tags)
|
|
56
|
+
- **Linting**: ruff (line-length 120, target py311)
|
|
57
|
+
- **Type checking**: mypy strict mode
|
|
58
|
+
- **Python**: 3.11+ (tested on 3.11, 3.12, 3.13)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.1] - 2026-05-24
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
- `Memory` 类 — 统一的记忆存储与召回接口。
|
|
14
|
+
- `MemoryBackend` 协议 — 可插拔的后端抽象。
|
|
15
|
+
- `SqliteMemoryBackend` — 默认后端,基于 FTS5 提供全文搜索。
|
|
16
|
+
- 数据类型:`MemoryNode`、`ConversationRecord`、`Message`、`SearchResult`、`ConversationSummary`。
|
|
17
|
+
- 记忆树:层级化的 root → branch → leaf 结构。
|
|
18
|
+
- 命名空间隔离:多个 agent 可安全共享同一数据库。
|
|
19
|
+
- 后端工厂:将 `"sqlite"` / `"postgres"` / `"qdrant"` 字符串解析为对应实现。
|
|
20
|
+
|
|
21
|
+
[Unreleased]: https://github.com/orcakit/harness-memory/compare/HEAD...HEAD
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orcakit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.PHONY: dev lint format typecheck test all publish clean
|
|
2
|
+
|
|
3
|
+
dev:
|
|
4
|
+
uv sync --all-extras
|
|
5
|
+
|
|
6
|
+
lint:
|
|
7
|
+
uv run ruff check .
|
|
8
|
+
uv run ruff format --check .
|
|
9
|
+
|
|
10
|
+
format:
|
|
11
|
+
uv run ruff check --fix .
|
|
12
|
+
uv run ruff format .
|
|
13
|
+
|
|
14
|
+
typecheck:
|
|
15
|
+
uv run mypy --strict src
|
|
16
|
+
|
|
17
|
+
test:
|
|
18
|
+
uv run pytest -q
|
|
19
|
+
|
|
20
|
+
all: lint typecheck test
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
uv build
|
|
24
|
+
uv publish
|
|
25
|
+
|
|
26
|
+
clean:
|
|
27
|
+
rm -rf dist/ build/ *.egg-info src/harness_memory/_version.py
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: harness-memory
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Pluggable memory system with hierarchical recall, FTS search, and multiple backend support.
|
|
5
|
+
Project-URL: Homepage, https://github.com/orcakit/harness-memory
|
|
6
|
+
Project-URL: Repository, https://github.com/orcakit/harness-memory
|
|
7
|
+
Project-URL: Issues, https://github.com/orcakit/harness-memory/issues
|
|
8
|
+
Author: orcakit
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,fts,llm,memory,recall,sqlite
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
27
|
+
Provides-Extra: postgres
|
|
28
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
29
|
+
Provides-Extra: qdrant
|
|
30
|
+
Requires-Dist: qdrant-client>=1.7; extra == 'qdrant'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# orcakit-harness-memory
|
|
34
|
+
|
|
35
|
+
[](https://github.com/orcakit/harness-memory/actions/workflows/ci.yml)
|
|
36
|
+
[](https://pypi.org/project/orcakit-harness-memory/)
|
|
37
|
+
[](https://pypi.org/project/orcakit-harness-memory/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Pluggable memory system for LLM agents — hierarchical memory tree with full-text search and multiple backend support.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Memory Tree** — Hierarchical summaries (root → branch → leaf) for long-term recall
|
|
45
|
+
- **Full-Text Search** — FTS5/BM25 powered search across conversations and memories
|
|
46
|
+
- **Conversation Records** — Standardized chat history storage and retrieval
|
|
47
|
+
- **Pluggable Backends** — SQLite (default, zero-dep), PostgreSQL, Qdrant
|
|
48
|
+
- **Zero Dependencies** — Core package uses only Python stdlib + sqlite3
|
|
49
|
+
- **Namespace Isolation** — Multiple agents share one database safely
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install orcakit-harness-memory
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Optional backends:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install "orcakit-harness-memory[postgres]" # PostgreSQL backend
|
|
61
|
+
pip install "orcakit-harness-memory[qdrant]" # Qdrant vector backend
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from harness_memory import Memory
|
|
68
|
+
|
|
69
|
+
# Create with default SQLite backend
|
|
70
|
+
memory = Memory(namespace="my-agent")
|
|
71
|
+
|
|
72
|
+
# Store a memory
|
|
73
|
+
memory.store("User prefers Python over Java", topic="preferences")
|
|
74
|
+
|
|
75
|
+
# Recall relevant memories
|
|
76
|
+
results = memory.recall("programming language")
|
|
77
|
+
for node in results:
|
|
78
|
+
print(f"[{node.topic}] {node.content}")
|
|
79
|
+
|
|
80
|
+
# Add a conversation record
|
|
81
|
+
from harness_memory import ConversationRecord, Message
|
|
82
|
+
from datetime import datetime, timezone
|
|
83
|
+
|
|
84
|
+
record = ConversationRecord(
|
|
85
|
+
id="conv-001",
|
|
86
|
+
thread_id="thread-1",
|
|
87
|
+
user="alice",
|
|
88
|
+
started_at=datetime.now(timezone.utc),
|
|
89
|
+
ended_at=datetime.now(timezone.utc),
|
|
90
|
+
messages=[
|
|
91
|
+
Message(role="user", content="How do I use memory?", timestamp=datetime.now(timezone.utc)),
|
|
92
|
+
Message(role="assistant", content="Just call memory.store()!", timestamp=datetime.now(timezone.utc)),
|
|
93
|
+
],
|
|
94
|
+
summary=None,
|
|
95
|
+
metadata={},
|
|
96
|
+
)
|
|
97
|
+
memory.add_conversation(record)
|
|
98
|
+
|
|
99
|
+
# Search conversations
|
|
100
|
+
hits = memory.search("memory")
|
|
101
|
+
for hit in hits:
|
|
102
|
+
print(f"[{hit.role}] {hit.message_content}")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Backend Configuration
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# SQLite (default) — zero dependencies
|
|
109
|
+
memory = Memory(namespace="agent", backend="sqlite")
|
|
110
|
+
memory = Memory(namespace="agent", backend_config={"db_path": "/data/memory.db"})
|
|
111
|
+
|
|
112
|
+
# PostgreSQL — requires [postgres] extra
|
|
113
|
+
memory = Memory(namespace="agent", backend="postgres", backend_config={
|
|
114
|
+
"dsn": "postgresql://user:pass@localhost/memdb"
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
# Qdrant — requires [qdrant] extra
|
|
118
|
+
memory = Memory(namespace="agent", backend="qdrant", backend_config={
|
|
119
|
+
"url": "http://localhost:6333"
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Custom backend — pass any MemoryBackend instance
|
|
123
|
+
memory = Memory(namespace="agent", backend=my_custom_backend)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API Reference
|
|
127
|
+
|
|
128
|
+
### Memory
|
|
129
|
+
|
|
130
|
+
| Method | Description |
|
|
131
|
+
|--------|-------------|
|
|
132
|
+
| `store(content, topic=None)` | Store a memory as a leaf node |
|
|
133
|
+
| `recall(query, limit=5)` | Recall relevant memories via FTS |
|
|
134
|
+
| `add_conversation(record)` | Persist a conversation record |
|
|
135
|
+
| `search(query, limit=10)` | Full-text search across messages |
|
|
136
|
+
| `list_conversations(**filters)` | List conversations with filters |
|
|
137
|
+
| `get_conversation(id)` | Get full conversation by ID |
|
|
138
|
+
| `get_tree()` | Get the complete memory tree |
|
|
139
|
+
|
|
140
|
+
### Data Types
|
|
141
|
+
|
|
142
|
+
- `MemoryNode` — A node in the memory tree (root/branch/leaf)
|
|
143
|
+
- `ConversationRecord` — Complete conversation with messages
|
|
144
|
+
- `Message` — Single message (user/assistant/tool)
|
|
145
|
+
- `SearchResult` — FTS search hit with score
|
|
146
|
+
- `ConversationSummary` — Lightweight conversation listing
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
make dev # Install dev dependencies
|
|
152
|
+
make lint # Ruff check + format check
|
|
153
|
+
make typecheck # mypy strict
|
|
154
|
+
make test # pytest
|
|
155
|
+
make all # lint + typecheck + test
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
[MIT](LICENSE)
|