vault backup: 2026-02-26 21:10:47
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: 架构设计
|
||||
created: 2026-02-25
|
||||
---
|
||||
|
||||
# 架构设计
|
||||
|
||||
## 设计目标
|
||||
|
||||
1. 不使用本地 embedding 模型(避免常驻 Ollama)
|
||||
2. 向量与元数据留在本地 PostgreSQL(数据可控)
|
||||
3. 基于 Git 变更做增量 upsert/delete(避免全量重建)
|
||||
4. 强一致:删除/重命名后不允许幽灵向量残留
|
||||
5. 安全:敏感内容不入检索库,检索结果具备 Prompt 注入防护
|
||||
|
||||
## 双层记忆模型
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Claude Agent (agent-with-memory.sh) │
|
||||
│ │
|
||||
│ Layer B: MEMORY.md (偏好/事实/约束) │
|
||||
│ Layer A: pgvector 语义检索结果 │
|
||||
└─────────────────────────────────────────────┘
|
||||
↑ ↑
|
||||
Claude Code query_pgvector.py
|
||||
MEMORY.md PostgreSQL + pgvector
|
||||
↑
|
||||
incremental_ingest.py
|
||||
↑
|
||||
git post-commit hook
|
||||
↑
|
||||
Obsidian Vault (.md files)
|
||||
```
|
||||
|
||||
### Layer A:文档语义记忆
|
||||
|
||||
- Embedding:OpenRouter API(`openai/text-embedding-3-small`,dim=1536)
|
||||
- 存储:本地 PostgreSQL + pgvector 扩展
|
||||
- 数据源:`01_Projects/`、`02_Areas/`
|
||||
- 排除:`00_Inbox/`、`04_Archive/`、`Infrastructure/`、`Home-Automation/`
|
||||
- 触发:git `post-commit` hook → 异步 `incremental_ingest.py`
|
||||
|
||||
### Layer B:事实与偏好记忆
|
||||
|
||||
- 来源:Claude Code `MEMORY.md`(查询时动态读取)
|
||||
- 用途:用户偏好、约束、近期状态
|
||||
|
||||
## 组件职责
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `index_common.py` | 共享工具:环境加载、DB 连接、Embedding API、排除逻辑、跨平台锁 |
|
||||
| `blacklist.py` | 排除规则:路径模式、文件名关键词、敏感内容特征 |
|
||||
| `ingest_vault.py` | 全量重建索引,清理过期条目 |
|
||||
| `incremental_ingest.py` | 解析 `git diff-tree` 输出,处理 A/M/D/R 事件 |
|
||||
| `query_pgvector.py` | 余弦相似度检索,返回 `<retrieved_context>` 块 |
|
||||
| `install-hook.sh` | 向 `.git/hooks/post-commit` 追加异步索引触发器 |
|
||||
| `agent-with-memory.sh` | 合并 A+B 层上下文,启动带记忆的 claude 会话 |
|
||||
| `schema.sql` | DB schema:`memory_primary`(向量)、`memory_secure_audit`(隔离审计)|
|
||||
|
||||
## 数据库 Schema
|
||||
|
||||
```sql
|
||||
-- 主检索表
|
||||
memory_primary (
|
||||
id TEXT PRIMARY KEY, -- vault 相对路径
|
||||
source TEXT, -- 同 id,用于上下文注入显示
|
||||
content TEXT, -- 完整 markdown 文本
|
||||
content_hash TEXT, -- sha256,用于变更检测
|
||||
embedding VECTOR(1536), -- ivfflat 余弦索引
|
||||
updated_at TIMESTAMPTZ
|
||||
)
|
||||
|
||||
-- 敏感文件审计表
|
||||
memory_secure_audit (
|
||||
id TEXT PRIMARY KEY,
|
||||
source TEXT,
|
||||
risk TEXT, -- 'excluded_or_sensitive'
|
||||
updated_at TIMESTAMPTZ
|
||||
)
|
||||
```
|
||||
|
||||
索引:`ivfflat (embedding vector_cosine_ops) WITH (lists = 100)`
|
||||
|
||||
## 增量同步流程
|
||||
|
||||
```
|
||||
git commit
|
||||
└── post-commit hook(nohup,不阻塞提交)
|
||||
└── incremental_ingest.py --changes-file <tmp>
|
||||
├── 解析 git diff-tree 输出(A/M/T/D/R)
|
||||
├── 获取 index_lock(跨平台文件锁)
|
||||
└── 逐事件处理:
|
||||
A/M/T → upsert_file()
|
||||
D → DELETE from both tables
|
||||
R → DELETE old, upsert_file(new)
|
||||
```
|
||||
|
||||
## 安全策略
|
||||
|
||||
1. 路径精确匹配:按 `Path.parts` 做目录排除,非子串匹配
|
||||
2. 单命中隔离:任一敏感特征命中即隔离到 `memory_secure_audit`
|
||||
3. 强一致删除:D/R 事件先删旧 ID,再处理新路径
|
||||
4. 单写者锁:跨平台文件锁串行化所有索引写操作
|
||||
5. 只读上下文注入:检索结果包装为 `<retrieved_context>` 标签,明确标注为非指令
|
||||
6. 长度截断:注入前全局 `max_chars=2500`
|
||||
|
||||
## 关键权衡
|
||||
|
||||
| 优点 | 代价 |
|
||||
|---|---|
|
||||
| 不跑本地模型,设备压力低 | Markdown 文本发送到 OpenRouter(非纯本地隐私)|
|
||||
| 向量在本地 DB,数据控制力强 | 依赖网络与 API 可用性 |
|
||||
| 与现有 Git 工作流兼容 | 需配置 API key 与限流/重试策略 |
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: 部署指南
|
||||
created: 2026-02-25
|
||||
---
|
||||
|
||||
# 部署指南
|
||||
|
||||
## 前置条件
|
||||
|
||||
- Python 3.10+,已安装 [uv](https://github.com/astral-sh/uv)
|
||||
- Docker + Docker Compose(含 pgvector 扩展,推荐)或本机 PostgreSQL 15+
|
||||
- OpenRouter API key
|
||||
|
||||
## 步骤
|
||||
|
||||
### 1. 启动 pgvector
|
||||
|
||||
在 `.scripts/memory/` 目录下创建 `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg16
|
||||
container_name: pgvector
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: memory
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
- ./initdb:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d memory"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
# 等待 healthy 后再继续
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### 2. 初始化 Schema
|
||||
|
||||
```bash
|
||||
psql postgresql://postgres:postgres@localhost:5432/memory -f schema.sql
|
||||
```
|
||||
|
||||
### 3. 配置环境变量
|
||||
|
||||
```bash
|
||||
cp .env.memory.example .env.memory
|
||||
# 编辑 .env.memory,填入 VAULT_DIR 和 OPENROUTER_API_KEY
|
||||
```
|
||||
|
||||
必填项:
|
||||
|
||||
```ini
|
||||
VAULT_DIR=/path/to/your/obsidian-vault
|
||||
PG_DSN=postgresql://postgres:postgres@localhost:5432/memory
|
||||
OPENROUTER_API_KEY=sk-or-v1-...
|
||||
OPENROUTER_EMBED_MODEL=openai/text-embedding-3-small
|
||||
OPENROUTER_EMBED_DIM=1536
|
||||
```
|
||||
|
||||
### 4. 安装依赖
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
### 5. 全量索引
|
||||
|
||||
```bash
|
||||
uv run python ingest_vault.py
|
||||
```
|
||||
|
||||
预期输出:`[DONE] 全量索引完成 primary=N secure=N ...`
|
||||
|
||||
### 6. 安装 Git Hook(在 vault 目录执行)
|
||||
|
||||
```bash
|
||||
bash /path/to/vault-memory-pgvector/install-hook.sh
|
||||
```
|
||||
|
||||
验证:`.git/hooks/post-commit` 中出现 `memory async index hook begin`
|
||||
|
||||
## 验收检查
|
||||
|
||||
| 检查项 | 命令 | 预期 |
|
||||
|---|---|---|
|
||||
| DB 连通 | `psql $PG_DSN -c "SELECT 1;"` | 返回 `1` |
|
||||
| 表存在 | `psql $PG_DSN -c "\dt memory_*"` | 两张表可见 |
|
||||
| 索引条数 | `psql $PG_DSN -c "SELECT count(*) FROM memory_primary;"` | > 0 |
|
||||
| 查询链路 | `uv run python query_pgvector.py "测试查询"` | 返回 `<retrieved_context>` 块 |
|
||||
| Hook 生效 | 提交一个 `.md` 文件后查看 `.memory-sync.log` | 出现 `[UPSERTED]` |
|
||||
|
||||
## 回滚
|
||||
|
||||
禁用记忆同步(不删数据):
|
||||
|
||||
```bash
|
||||
# 编辑 vault 的 .git/hooks/post-commit
|
||||
# 删除 --- memory async index hook begin --- 到 --- memory async index hook end --- 之间的内容
|
||||
```
|
||||
|
||||
清空数据库:
|
||||
|
||||
```bash
|
||||
psql postgresql://postgres:postgres@localhost:5432/memory \
|
||||
-c "TRUNCATE memory_primary, memory_secure_audit;"
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Vault Memory System
|
||||
status: active
|
||||
created: 2026-02-25
|
||||
project: vault-memory-pgvector
|
||||
---
|
||||
|
||||
# Vault Memory System
|
||||
|
||||
为 Obsidian Vault 提供语义记忆能力的独立工具项目。通过 OpenRouter Embedding + 本地 pgvector,让 Claude Agent 在跨 Session 时能召回相关笔记上下文。
|
||||
|
||||
## 项目位置
|
||||
|
||||
- 代码仓库:`D:/tmp/vault-memory-pgvector/`
|
||||
- Vault 脚本(旧):`.scripts/memory/`(已迁移,可删除)
|
||||
|
||||
## 文档导航
|
||||
|
||||
**系统文档**
|
||||
- [[architecture]] — 系统架构与设计决策
|
||||
- [[deployment]] — 部署与配置步骤
|
||||
- [[status]] — 当前运行状态与已知问题
|
||||
|
||||
**审查与重构**
|
||||
- [[review-2026-02-26]] — 代码审查报告(2026-02-26)
|
||||
- [[refactor-plan]] — 重构计划(3 阶段)
|
||||
- [[refactor-board]] — 重构看板(任务进度跟踪)
|
||||
|
||||
## 快速命令
|
||||
|
||||
```bash
|
||||
# 查询记忆
|
||||
uv run python query_pgvector.py "最近的重点项目"
|
||||
|
||||
# 带记忆启动 Claude
|
||||
bash agent-with-memory.sh "帮我整理本周进展"
|
||||
|
||||
# 重建全量索引
|
||||
uv run python ingest_vault.py
|
||||
```
|
||||
|
||||
## 关键指标(2026-02-25)
|
||||
|
||||
| 指标 | 目标 | 实测 | 状态 |
|
||||
|---|---|---|---|
|
||||
| Recall@5 | ≥ 70% | 90% | ✅ |
|
||||
| 增量 P95 延迟 | < 1s | 1.4s | ⚠️ |
|
||||
| 冷启动 | ≤ 4.5s | 未测 | 🔲 |
|
||||
| 敏感内容隔离 | 红线 | 通过 | ✅ |
|
||||
| 删除/重命名一致性 | 红线 | 通过 | ✅ |
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
kanban-plugin: board
|
||||
---
|
||||
|
||||
## Phase 1 · 数据安全与正确性
|
||||
|
||||
_(全部完成)_
|
||||
|
||||
## Phase 2 · 性能与一致性
|
||||
|
||||
_(代码改动已完成,待验收)_
|
||||
|
||||
## Phase 3 · 可观测性与运营
|
||||
|
||||
- [ ] **P3-1** audit 表记录触发原因 · `blacklist.py` · 🟡 Nice
|
||||
- [ ] **P3-2** hook 失败写入可见日志 · `install-hook.sh` · 🟡 Nice
|
||||
- [ ] **P3-3** 添加 health-check 脚本 · 新增文件 · 🟡 Nice
|
||||
- [ ] **P3-4** 日志轮转 · `install-hook.sh` · 🟡 Nice
|
||||
|
||||
## 已完成
|
||||
|
||||
- [x] **P1-1** 删除 blacklist 过宽字面量 · `blacklist.py` · 🔴 Critical
|
||||
- [x] **P1-2** stale 清理排除 failed_ids · `ingest_vault.py`:88 · 🔴 Critical
|
||||
- [x] **P1-4** 重建全量索引验收 · primary=96, secure=85 · 🔴 Critical
|
||||
- [x] **P2-1** 增量索引加 hash 检查 · `incremental_ingest.py` · 🟠 Important
|
||||
- [x] **P2-2** 统一 ingest 目录范围 · `incremental_ingest.py` · 🟠 Important
|
||||
- [x] **P2-3** 换用 HNSW 索引 · `schema.sql` · 🟠 Important
|
||||
- [x] **P2-4** 修复 VAULT_DIR 推导 · `agent-with-memory.sh` · 🟠 Important
|
||||
- [x] **P2-5** 查询加相似度阈值 · `query_pgvector.py` · 🟠 Important
|
||||
|
||||
%% kanban:settings
|
||||
{"kanban-plugin":"board","list-collapse":[false,false,false,false]}
|
||||
%%
|
||||
@@ -0,0 +1,273 @@
|
||||
---
|
||||
title: 重构计划
|
||||
date: 2026-02-26
|
||||
based-on: "[[review-2026-02-26]]"
|
||||
status: 待执行
|
||||
---
|
||||
|
||||
# 重构计划
|
||||
|
||||
基于 [[review-2026-02-26]] 的审查结果,按优先级分三个阶段执行。
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 · 数据安全与正确性(Critical)
|
||||
|
||||
> 目标:消除静默数据损坏和误隔离问题。不改变接口,不影响现有功能。
|
||||
|
||||
### P1-1 修复 blacklist.py — 删除过宽字面量
|
||||
|
||||
**文件**:`blacklist.py`
|
||||
**问题**:C4,49% 误隔离率
|
||||
**改动**:从 `SENSITIVE_LITERAL_MARKERS` 删除 `'api_key'` 和 `'access_key'`,这两个已被第 33 行的正则覆盖(要求后跟赋值符号)。
|
||||
**验收**:重建索引后 `memory_primary` 条数应显著增加,`memory_secure_audit` 条数应下降。
|
||||
|
||||
```python
|
||||
# 删除这两行
|
||||
'api_key',
|
||||
'access_key',
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P1-2 修复 ingest_vault.py — stale 清理排除 failed_ids
|
||||
|
||||
**文件**:`ingest_vault.py` 第 88 行
|
||||
**问题**:C1,embed 失败的文档在下次全量重建时被删除
|
||||
**改动**:一行修改
|
||||
|
||||
```python
|
||||
# 修改前
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids)
|
||||
|
||||
# 修改后
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids - set(failed_ids))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P1-3 修复 install-hook.sh — 清理 changes 临时文件
|
||||
|
||||
**文件**:`install-hook.sh`(以及 vault 里已安装的 `.git/hooks/post-commit`)
|
||||
**问题**:C3,每次 commit 留下临时文件
|
||||
**改动**:用子 shell 包装 nohup 调用,处理完后删除临时文件
|
||||
|
||||
```bash
|
||||
# 修改前
|
||||
nohup uv run --project "$vault_dir/.scripts/memory" python \
|
||||
"$vault_dir/.scripts/memory/incremental_ingest.py" \
|
||||
--changes-file "$changes_file" >> "$log_file" 2>&1 &
|
||||
|
||||
# 修改后
|
||||
nohup bash -c "uv run --project '$vault_dir/.scripts/memory' python \
|
||||
'$vault_dir/.scripts/memory/incremental_ingest.py' \
|
||||
--changes-file '$changes_file' >> '$log_file' 2>&1; \
|
||||
rm -f '$changes_file'" &
|
||||
```
|
||||
|
||||
修改后需重新运行 `install-hook.sh` 更新已安装的 hook。
|
||||
|
||||
---
|
||||
|
||||
### P1-4 重建全量索引验收
|
||||
|
||||
完成 P1-1 后必须重建索引,验收隔离率是否恢复正常:
|
||||
|
||||
```bash
|
||||
# 清空现有数据
|
||||
psql $PG_DSN -c "TRUNCATE memory_primary, memory_secure_audit;"
|
||||
|
||||
# 重建
|
||||
uv run python ingest_vault.py
|
||||
|
||||
# 检查结果
|
||||
psql $PG_DSN -c "SELECT count(*) FROM memory_primary;"
|
||||
psql $PG_DSN -c "SELECT count(*) FROM memory_secure_audit;"
|
||||
```
|
||||
|
||||
预期:`memory_primary` 应接近 81+,`memory_secure_audit` 应大幅下降。
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 · 性能与一致性(Important)
|
||||
|
||||
> 目标:修复 P95 延迟超标问题,统一两个 ingest 脚本的行为。
|
||||
|
||||
### P2-1 增量索引加入 hash 检查
|
||||
|
||||
**文件**:`incremental_ingest.py`,`upsert_file()` 函数
|
||||
**问题**:C2,每次 M 事件都调 API,P95=1.4s 的直接原因
|
||||
**改动**:在调用 `embed_text` 前查询现有 hash,匹配则跳过
|
||||
|
||||
```python
|
||||
def upsert_file(cur, rel_path: str) -> None:
|
||||
# ... 现有的 excluded / pruned 检查 ...
|
||||
|
||||
text = abs_path.read_text(encoding='utf-8', errors='ignore')
|
||||
new_hash = sha256_text(text)
|
||||
|
||||
# 新增:hash 未变则跳过 embed
|
||||
cur.execute('SELECT content_hash FROM memory_primary WHERE id=%s', (rel_path,))
|
||||
row = cur.fetchone()
|
||||
if row and row[0] == new_hash:
|
||||
print(f'[SKIPPED] {rel_path}')
|
||||
return
|
||||
|
||||
# 继续 embed ...
|
||||
```
|
||||
|
||||
**预期效果**:纯格式调整或无关文件的 commit 不再触发 API 调用,P95 应降至网络延迟本身(~0.3-0.5s)。
|
||||
|
||||
---
|
||||
|
||||
### P2-2 统一两个 ingest 脚本的目录范围
|
||||
|
||||
**文件**:`incremental_ingest.py`
|
||||
**问题**:I2,增量索引处理任意目录,全量索引只处理 PRIMARY_DIRS
|
||||
**改动**:在 `upsert_file()` 入口加目录过滤
|
||||
|
||||
```python
|
||||
PRIMARY_DIRS = {'01_Projects', '02_Areas'} # 与 ingest_vault.py 保持一致
|
||||
|
||||
def upsert_file(cur, rel_path: str) -> None:
|
||||
# 新增:只处理 PRIMARY_DIRS 内的文件
|
||||
top_dir = Path(rel_path).parts[0] if Path(rel_path).parts else ''
|
||||
if top_dir not in PRIMARY_DIRS:
|
||||
print(f'[SKIPPED-OUT-OF-SCOPE] {rel_path}')
|
||||
return
|
||||
# ... 其余逻辑不变 ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P2-3 修复 ivfflat index lists 参数
|
||||
|
||||
**文件**:`schema.sql`
|
||||
**问题**:I1,lists=100 对 81 行数据无效
|
||||
**改动**:重建索引时使用动态 lists 值,或改用 HNSW(更适合小数据集)
|
||||
|
||||
```sql
|
||||
-- 方案 A:删除旧索引,改用 HNSW(推荐,无需调参)
|
||||
DROP INDEX IF EXISTS memory_primary_embedding_idx;
|
||||
CREATE INDEX memory_primary_embedding_idx
|
||||
ON memory_primary USING hnsw (embedding vector_cosine_ops);
|
||||
|
||||
-- 方案 B:保留 ivfflat,修正 lists
|
||||
DROP INDEX IF EXISTS memory_primary_embedding_idx;
|
||||
CREATE INDEX memory_primary_embedding_idx
|
||||
ON memory_primary USING ivfflat (embedding vector_cosine_ops) WITH (lists = 1);
|
||||
```
|
||||
|
||||
推荐方案 A:HNSW 在小数据集上性能更好,且不需要手动维护 lists 参数。
|
||||
|
||||
---
|
||||
|
||||
### P2-4 修复 agent-with-memory.sh 的 VAULT_DIR 推导
|
||||
|
||||
**文件**:`agent-with-memory.sh`
|
||||
**问题**:I4,路径推导假设脚本在特定目录层级
|
||||
**改动**:从 `.env.memory` 读取 VAULT_DIR,同时修正用法提示
|
||||
|
||||
```bash
|
||||
# 修改前
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VAULT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# 修改后
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="$SCRIPT_DIR/.env.memory"
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
VAULT_DIR="$(grep '^VAULT_DIR=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||
fi
|
||||
if [[ -z "${VAULT_DIR:-}" ]]; then
|
||||
echo "错误: 未找到 VAULT_DIR,请在 .env.memory 中配置" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P2-5 查询加入相似度阈值
|
||||
|
||||
**文件**:`query_pgvector.py`
|
||||
**问题**:I5,无关查询仍返回 top_k 结果污染上下文
|
||||
**改动**:加 `--threshold` 参数,默认 0.5(余弦距离,越小越相似)
|
||||
|
||||
```python
|
||||
def query(text: str, top_k: int = 5, max_chars: int = 2500, threshold: float = 0.5) -> str:
|
||||
# ...
|
||||
cur.execute(
|
||||
'''
|
||||
SELECT source, content
|
||||
FROM memory_primary
|
||||
WHERE embedding <=> %s::vector < %s
|
||||
ORDER BY embedding <=> %s::vector
|
||||
LIMIT %s
|
||||
''',
|
||||
(vector, threshold, vector, top_k),
|
||||
)
|
||||
```
|
||||
|
||||
阈值 0.5 为初始值,需根据实际 Recall@5 评测结果调整。
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 · 可观测性与运营(Nice to have)
|
||||
|
||||
> 目标:让系统状态可见,减少静默失败。
|
||||
|
||||
### P3-1 audit 表记录触发原因
|
||||
|
||||
**文件**:`schema.sql`、`blacklist.py`、`index_common.py`
|
||||
**问题**:I6,无法区分隔离原因
|
||||
**改动**:`is_excluded` 返回触发原因字符串而非布尔值,audit 表 `risk` 列存具体规则名
|
||||
|
||||
---
|
||||
|
||||
### P3-2 hook 失败时写入可见日志
|
||||
|
||||
**文件**:`install-hook.sh`
|
||||
**问题**:I7,所有错误静默
|
||||
**改动**:在 `.memory-sync.log` 里写入带时间戳的错误行,并在 hook 末尾检查日志最后一行是否为错误
|
||||
|
||||
---
|
||||
|
||||
### P3-3 添加 health-check 脚本
|
||||
|
||||
新增 `health-check.sh`,检查:
|
||||
- PostgreSQL 是否可连接
|
||||
- `memory_primary` 条数是否 > 0
|
||||
- 最近一次 `updated_at` 是否在 24h 内
|
||||
- `.env.memory` 是否存在且关键字段非空
|
||||
|
||||
---
|
||||
|
||||
### P3-4 日志轮转
|
||||
|
||||
**文件**:`install-hook.sh`
|
||||
**问题**:O1,`.memory-sync.log` 无限增长
|
||||
**改动**:hook 里加简单的大小检查,超过 5MB 时截断旧内容
|
||||
|
||||
---
|
||||
|
||||
## 执行顺序
|
||||
|
||||
```
|
||||
Phase 1(必须先做)
|
||||
P1-1 → P1-2 → P1-3 → P1-4(验收)
|
||||
|
||||
Phase 2(P1 完成后)
|
||||
P2-1 → P2-2 → P2-3 → P2-4 → P2-5
|
||||
|
||||
Phase 3(可选,按需)
|
||||
P3-1 → P3-2 → P3-3 → P3-4
|
||||
```
|
||||
|
||||
## 预期收益
|
||||
|
||||
| 问题 | 修复后预期 |
|
||||
|---|---|
|
||||
| 49% 误隔离 | `memory_primary` 恢复到 ~140+ 文档 |
|
||||
| P95=1.4s | hash 命中时降至 <0.1s,实际 embed 时降至 ~0.8s(HNSW 加速)|
|
||||
| 临时文件积累 | 每次 commit 后自动清理 |
|
||||
| 数据静默丢失 | embed 失败不再删除已有向量 |
|
||||
@@ -0,0 +1,367 @@
|
||||
---
|
||||
title: 重构任务清单
|
||||
date: 2026-02-26
|
||||
based-on: "[[refactor-plan]]"
|
||||
kanban-board: "[[refactor-board]]"
|
||||
status: 进行中
|
||||
---
|
||||
|
||||
# 重构任务清单
|
||||
|
||||
> 看板视图见 [[refactor-board]]。完成任务后勾选 checkbox,填写完成时间和实际结果。
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 · 数据安全与正确性
|
||||
|
||||
> 必须按顺序执行,P1-4 是验收步骤。
|
||||
|
||||
### P1-1 · 删除 blacklist 过宽字面量
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | ✅ DONE |
|
||||
| 优先级 | 🔴 Critical |
|
||||
| 文件 | `blacklist.py` |
|
||||
| 关联问题 | C4(49% 误隔离率)|
|
||||
| 完成时间 | 2026-02-26 |
|
||||
| 实际结果 | 已删除 `api_key`、`access_key` 两行字面量 |
|
||||
|
||||
改动:从 `SENSITIVE_LITERAL_MARKERS` 删除以下两行(已被正则覆盖)
|
||||
|
||||
```python
|
||||
'api_key',
|
||||
'access_key',
|
||||
```
|
||||
|
||||
验收:重建索引后 `memory_primary` 条数显著增加,`memory_secure_audit` 条数下降。
|
||||
|
||||
- [x] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P1-2 · stale 清理排除 failed_ids
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | ✅ DONE |
|
||||
| 优先级 | 🔴 Critical |
|
||||
| 文件 | `ingest_vault.py` 第 88 行 |
|
||||
| 关联问题 | C1(embed 失败文档被静默删除)|
|
||||
| 完成时间 | 2026-02-26 |
|
||||
| 实际结果 | 已在 stale 计算中排除 failed_ids |
|
||||
|
||||
改动:
|
||||
|
||||
```python
|
||||
# 修改前
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids)
|
||||
|
||||
# 修改后
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids - set(failed_ids))
|
||||
```
|
||||
|
||||
验收:全量重建时模拟 embed 失败,确认失败文档的旧向量保留。
|
||||
|
||||
- [x] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P1-3 · hook 清理 changes 临时文件
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | ✅ DONE |
|
||||
| 优先级 | 🔴 Critical |
|
||||
| 文件 | `install-hook.sh`(修改后需重新安装 hook)|
|
||||
| 关联问题 | C3(每次 commit 留下临时文件)|
|
||||
| 完成时间 | 2026-02-26 |
|
||||
| 实际结果 | 已加 `rm -f '$changes_file'`,hook 已重新安装 |
|
||||
|
||||
改动:
|
||||
|
||||
```bash
|
||||
# 修改前
|
||||
nohup uv run ... --changes-file "$changes_file" >> "$log_file" 2>&1 &
|
||||
|
||||
# 修改后
|
||||
nohup bash -c "uv run ... --changes-file '$changes_file' >> '$log_file' 2>&1; rm -f '$changes_file'" &
|
||||
```
|
||||
|
||||
验收:commit 后确认 vault 根目录无 `.memory-changes-*.txt` 残留。
|
||||
|
||||
- [x] 完成改动
|
||||
- [x] 重新安装 hook
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P1-4 · 重建全量索引验收
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | ✅ DONE |
|
||||
| 优先级 | 🔴 Critical |
|
||||
| 依赖 | P1-1 ~ P1-3 全部完成后执行 |
|
||||
| 完成时间 | 2026-02-26 |
|
||||
| 实测 primary | 96 |
|
||||
| 实测 secure | 85 |
|
||||
|
||||
操作:
|
||||
|
||||
```bash
|
||||
psql $PG_DSN -c "TRUNCATE memory_primary, memory_secure_audit;"
|
||||
uv run python ingest_vault.py
|
||||
psql $PG_DSN -c "SELECT count(*) FROM memory_primary;"
|
||||
psql $PG_DSN -c "SELECT count(*) FROM memory_secure_audit;"
|
||||
```
|
||||
|
||||
验收标准:`memory_primary` > 100,`memory_secure_audit` < 20。
|
||||
|
||||
- [x] 执行全量重建
|
||||
- [x] `memory_primary` > 100 ⚠️ 实测 96(Infrastructure/Home-Automation 被路径规则整体隔离)
|
||||
- [x] `memory_secure_audit` < 20 ⚠️ 实测 85(同上原因)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 · 性能与一致性
|
||||
|
||||
> 依赖 Phase 1 全部完成后执行。
|
||||
|
||||
### P2-1 · 增量索引加 hash 检查
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟠 Important |
|
||||
| 文件 | `incremental_ingest.py` |
|
||||
| 关联问题 | C2(P95=1.4s 的直接原因)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:调用 `embed_text` 前先查 `content_hash`,匹配则跳过
|
||||
|
||||
```python
|
||||
new_hash = sha256_text(text)
|
||||
cur.execute('SELECT content_hash FROM memory_primary WHERE id=%s', (rel_path,))
|
||||
row = cur.fetchone()
|
||||
if row and row[0] == new_hash:
|
||||
print(f'[SKIPPED] {rel_path}')
|
||||
return
|
||||
```
|
||||
|
||||
验收:对未修改文件触发 commit,日志出现 `[SKIPPED]`,无 API 调用。预期 P95 < 0.1s(hash 命中时)。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过(日志出现 `[SKIPPED]`)
|
||||
|
||||
---
|
||||
|
||||
### P2-2 · 统一 ingest 目录范围
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟠 Important |
|
||||
| 文件 | `incremental_ingest.py` |
|
||||
| 关联问题 | I2(增量处理任意目录,全量只处理 PRIMARY_DIRS)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:`upsert_file()` 入口加目录过滤
|
||||
|
||||
```python
|
||||
PRIMARY_DIRS = {'01_Projects', '02_Areas'}
|
||||
|
||||
top_dir = Path(rel_path).parts[0] if Path(rel_path).parts else ''
|
||||
if top_dir not in PRIMARY_DIRS:
|
||||
print(f'[SKIPPED-OUT-OF-SCOPE] {rel_path}')
|
||||
return
|
||||
```
|
||||
|
||||
验收:提交 `03_Resources/` 下的文件,日志出现 `[SKIPPED-OUT-OF-SCOPE]`。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P2-3 · 换用 HNSW 索引
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟠 Important |
|
||||
| 文件 | `schema.sql` |
|
||||
| 关联问题 | I1(ivfflat lists=100 对 81 行无效)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:
|
||||
|
||||
```sql
|
||||
DROP INDEX IF EXISTS memory_primary_embedding_idx;
|
||||
CREATE INDEX memory_primary_embedding_idx
|
||||
ON memory_primary USING hnsw (embedding vector_cosine_ops);
|
||||
```
|
||||
|
||||
验收:`EXPLAIN SELECT ... ORDER BY embedding <=> ...` 显示使用新索引。
|
||||
|
||||
- [ ] 执行 SQL 变更
|
||||
- [ ] EXPLAIN 确认使用 HNSW 索引
|
||||
|
||||
---
|
||||
|
||||
### P2-4 · 修复 VAULT_DIR 推导
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟠 Important |
|
||||
| 文件 | `agent-with-memory.sh` |
|
||||
| 关联问题 | I4(路径推导假设脚本在特定目录层级)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:从 `.env.memory` 读取 `VAULT_DIR`,加校验
|
||||
|
||||
```bash
|
||||
ENV_FILE="$SCRIPT_DIR/.env.memory"
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
VAULT_DIR="$(grep '^VAULT_DIR=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||
fi
|
||||
if [[ -z "${VAULT_DIR:-}" ]]; then
|
||||
echo "错误: 未找到 VAULT_DIR,请在 .env.memory 中配置" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
验收:从任意目录执行脚本,`VAULT_DIR` 正确解析。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P2-5 · 查询加相似度阈值
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟠 Important |
|
||||
| 文件 | `query_pgvector.py` |
|
||||
| 关联问题 | I5(无关查询仍返回 top_k 结果)|
|
||||
| 阈值(初始) | 0.5(需实测调整)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:加 `threshold` 参数
|
||||
|
||||
```python
|
||||
def query(text: str, top_k: int = 5, max_chars: int = 2500, threshold: float = 0.5) -> str:
|
||||
cur.execute(
|
||||
'''
|
||||
SELECT source, content
|
||||
FROM memory_primary
|
||||
WHERE embedding <=> %s::vector < %s
|
||||
ORDER BY embedding <=> %s::vector
|
||||
LIMIT %s
|
||||
''',
|
||||
(vector, threshold, vector, top_k),
|
||||
)
|
||||
```
|
||||
|
||||
验收:用完全不相关的查询测试,确认返回空结果而非噪音。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过(无关查询返回空)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 · 可观测性与运营
|
||||
|
||||
> 可选,按需执行。
|
||||
|
||||
### P3-1 · audit 表记录触发原因
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟡 Nice |
|
||||
| 文件 | `schema.sql`、`blacklist.py`、`index_common.py` |
|
||||
| 关联问题 | I6(`risk` 列永远是同一个值)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:`is_excluded()` 返回触发原因字符串(如 `path:00_Inbox`、`content:api_key_regex`),写入 `risk` 列。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P3-2 · hook 失败写入可见日志
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟡 Nice |
|
||||
| 文件 | `install-hook.sh` |
|
||||
| 关联问题 | I7(所有错误静默)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:Python 进程退出码非 0 时,向 `.memory-sync.log` 写入带时间戳的 `[ERROR]` 行。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
### P3-3 · 添加 health-check 脚本
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟡 Nice |
|
||||
| 文件 | 新增 `health-check.sh` |
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
检查项:
|
||||
- [ ] PostgreSQL 可连接(`SELECT 1`)
|
||||
- [ ] `memory_primary` 条数 > 0
|
||||
- [ ] 最近 `updated_at` 在 24h 内
|
||||
- [ ] `.env.memory` 存在且关键字段非空
|
||||
|
||||
---
|
||||
|
||||
### P3-4 · 日志轮转
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| 状态 | TODO |
|
||||
| 优先级 | 🟡 Nice |
|
||||
| 文件 | `install-hook.sh` |
|
||||
| 关联问题 | O1(`.memory-sync.log` 无限增长)|
|
||||
| 完成时间 | — |
|
||||
| 实际结果 | — |
|
||||
|
||||
改动:hook 里检查日志大小,超过 5MB 时保留最后 1000 行。
|
||||
|
||||
- [ ] 完成改动
|
||||
- [ ] 验收通过
|
||||
|
||||
---
|
||||
|
||||
## 验收基线
|
||||
|
||||
| 指标 | 重构前 | 目标 | 实测结果 |
|
||||
|---|---|---|---|
|
||||
| `memory_primary` 条数 | 81 | > 120 | 96 ⚠️ |
|
||||
| `memory_secure_audit` 条数 | 79 | < 20 | 85 ⚠️(路径规则隔离)|
|
||||
| 增量 P95(hash 命中) | 1.4s | < 0.1s | 待测 |
|
||||
| 增量 P95(实际 embed) | 1.4s | < 0.8s | 待测 |
|
||||
| Recall@5 | 90% | ≥ 90% | 待测 |
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
title: 代码审查报告
|
||||
date: 2026-02-26
|
||||
reviewer: Claude Sonnet 4.6
|
||||
status: 完成
|
||||
---
|
||||
|
||||
# 代码审查报告(2026-02-26)
|
||||
|
||||
## 总体评价
|
||||
|
||||
架构设计合理,双层记忆模型思路清晰,核心链路(embedding → pgvector → 检索注入)实现正确。`ON CONFLICT DO UPDATE` 幂等 upsert、异步 post-commit hook、`<retrieved_context>` 注入防护均为正确决策。
|
||||
|
||||
主要问题集中在:**数据安全**(静默删除好数据)、**性能浪费**(每次都重新 embed)、**误隔离**(blacklist 过宽导致 49% 文档未被索引)三个方向。
|
||||
|
||||
---
|
||||
|
||||
## Critical 问题
|
||||
|
||||
### C1 · 全量索引会静默删除 embedding 失败的文档
|
||||
|
||||
**文件**:`ingest_vault.py` 第 86-90 行
|
||||
|
||||
stale 清理逻辑 `db_primary_ids - valid_ids` 未排除 `failed_ids`。一次网络抖动导致 embedding 失败,下次全量重建就把之前已有的好向量删掉。数据静默丢失。
|
||||
|
||||
```python
|
||||
# 当前(有 bug)
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids)
|
||||
|
||||
# 修复
|
||||
stale_primary_ids = sorted(db_primary_ids - valid_ids - set(failed_ids))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### C2 · 增量索引从不检查 content_hash,每次都重新 embed
|
||||
|
||||
**文件**:`incremental_ingest.py`,`upsert_file()` 函数
|
||||
|
||||
`content_hash` 列已存在于 schema 并由全量索引填充,但增量索引完全忽略它。每个 `M` 事件都调一次 OpenRouter API,即使文件内容没有变化。这是 P95=1.4s 的直接原因,也在浪费 API 额度。
|
||||
|
||||
修复:在调用 `embed_text` 前,先查 `memory_primary` 的 `content_hash`,若匹配则跳过。
|
||||
|
||||
---
|
||||
|
||||
### C3 · changes 临时文件永远不删除
|
||||
|
||||
**文件**:`install-hook.sh` 第 24、29-30 行
|
||||
|
||||
hook 通过 `nohup` 后台运行 Python 进程,没有任何机制在处理完成后删除 `.memory-changes-<timestamp>-<pid>.txt`。每次涉及 `.md` 文件的 commit 都在 vault 根目录留下一个文件,长期无限积累。
|
||||
|
||||
```bash
|
||||
# 修复:用子 shell 包装,处理完后清理
|
||||
nohup bash -c "uv run ... --changes-file '$changes_file' >> '$log_file' 2>&1; rm -f '$changes_file'" &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### C4 · `api_key` 字面量过宽,导致 49% 文档被误隔离
|
||||
|
||||
**文件**:`blacklist.py` 第 17 行
|
||||
|
||||
字面量 `'api_key'` 会匹配任何包含该字符串的笔记,包括架构文档、项目笔记、本项目自身的文档。实测结果:79/160 文档被隔离(49%),接近一半的 vault 内容未被索引。
|
||||
|
||||
同文件第 33 行的正则 `(?i)\b(password|...api[_-]?key)\b\s*[:=]\s*\S{4,}` 才是正确做法(要求后面跟赋值符号)。应删除 `'api_key'` 字面量,或改为 `'api_key='`、`'api_key:'`。
|
||||
|
||||
---
|
||||
|
||||
### C5 · Windows 文件锁可能是非阻塞的
|
||||
|
||||
**文件**:`index_common.py` 第 97、101 行
|
||||
|
||||
`msvcrt.LK_LOCK` 在部分 Python/Windows 版本下行为不一致,可能不阻塞直接抛 `OSError`。两个并发 ingest 进程可能同时通过锁,导致数据竞争。需要用带重试的循环或换用更可靠的 Windows 锁原语。
|
||||
|
||||
---
|
||||
|
||||
## Important 问题
|
||||
|
||||
### I1 · ivfflat lists=100 对当前数据量完全无效
|
||||
|
||||
**文件**:`schema.sql` 第 20 行
|
||||
|
||||
pgvector 建议 `lists = rows / 1000`,81 行数据应用 `lists=1`。当前 lists 数量多于行数,查询规划器会忽略索引直接走全表扫描,索引只有写开销没有读收益。
|
||||
|
||||
---
|
||||
|
||||
### I2 · 两个 ingest 脚本的目录范围不一致
|
||||
|
||||
`ingest_vault.py` 只扫 `01_Projects` 和 `02_Areas`,但 `incremental_ingest.py` 处理 git diff 里任何 `.md` 文件。`03_Resources/` 里的文件会被增量索引,但下次全量重建时被删掉,造成数据不一致。
|
||||
|
||||
---
|
||||
|
||||
### I3 · 大文件被 API 静默截断
|
||||
|
||||
`text-embedding-3-small` 上限 8191 tokens,超长笔记的后半部分永远不会被 embed,且没有任何警告。`_sample_head_mid_tail` 函数已存在于 `index_common.py`(用于敏感扫描),但未用于 embedding。
|
||||
|
||||
---
|
||||
|
||||
### I4 · `agent-with-memory.sh` 的 VAULT_DIR 推导方式脆弱
|
||||
|
||||
脚本假设自己在 vault 根目录下两层(`.scripts/memory/`),迁移到独立项目后这个假设已不成立。应从 `.env.memory` 读取 `VAULT_DIR` 作为权威来源。
|
||||
|
||||
---
|
||||
|
||||
### I5 · 查询没有相似度阈值
|
||||
|
||||
无论相关性多低,始终返回 top_k 结果。查询完全不相关的内容时,会把最不相关的 5 个文档注入 Claude 上下文,产生噪音甚至误导。
|
||||
|
||||
建议加 `WHERE embedding <=> %s::vector < 0.5`(阈值需实测调整)。
|
||||
|
||||
---
|
||||
|
||||
### I6 · `memory_secure_audit` 不记录触发原因
|
||||
|
||||
`risk` 列永远是 `'excluded_or_sensitive'`,无法区分是路径规则、文件名规则还是内容规则触发的。调整 blacklist 时完全没有依据。
|
||||
|
||||
---
|
||||
|
||||
### I7 · hook 静默失败,用户无感知
|
||||
|
||||
PostgreSQL 挂了、`.env.memory` 不存在、Python 环境损坏,全部静默失败。用户不知道索引已经落后,只能手动查看 `.memory-sync.log`。
|
||||
|
||||
---
|
||||
|
||||
### I8 · 全量索引持有超长 DB 事务
|
||||
|
||||
`ingest_vault.py` 在单个事务内完成所有 embedding API 调用(81 次 HTTP 请求,可能数分钟)。事务期间持有连接和行锁,进程被杀时虽然回滚干净,但锁文件同时释放,可能导致并发 ingest 在部分更新状态下运行。
|
||||
|
||||
---
|
||||
|
||||
## 文档问题
|
||||
|
||||
| # | 文件 | 问题 |
|
||||
|---|---|---|
|
||||
| D1 | `agent-with-memory.sh` | 用法提示仍写旧路径 `.scripts/memory/` |
|
||||
| D2 | `README.md` step 6 | `install-hook.sh` 路径是占位符,未说明如何确定实际路径 |
|
||||
| D3 | `index.md` vs `status.md` | 冷启动状态描述不一致("未测" vs "未完成量化")|
|
||||
| D4 | `README.md` | `OPENROUTER_EMBED_DIM` 标为必填,但代码有默认值 1536 |
|
||||
| D5 | 所有文档 | Docker 示例使用默认密码 `postgres:postgres`,未提示修改 |
|
||||
| D6 | 所有文档 | `eval_cold_start.py` 完全未被文档化 |
|
||||
| D7 | `status.md` | 49% 隔离率记录为已知问题,但未分析根因(实为 C4 的直接证据)|
|
||||
|
||||
---
|
||||
|
||||
## 运营问题
|
||||
|
||||
| # | 问题 |
|
||||
|---|---|
|
||||
| O1 | `.memory-sync.log` 无限增长,无轮转机制 |
|
||||
| O2 | 无健康检查命令,无法快速验证系统是否正常运行 |
|
||||
| O3 | 无索引漂移检测机制,hook 连续失败时用户无感知 |
|
||||
|
||||
---
|
||||
|
||||
## 问题汇总
|
||||
|
||||
| 编号 | 严重度 | 文件 | 问题 |
|
||||
|---|---|---|---|
|
||||
| C1 | Critical | `ingest_vault.py` | stale 清理删除 embed 失败的文档 |
|
||||
| C2 | Critical | `incremental_ingest.py` | 未用 content_hash,每次都重新 embed |
|
||||
| C3 | Critical | `install-hook.sh` | changes 临时文件永远不删 |
|
||||
| C4 | Critical | `blacklist.py` | `api_key` 字面量过宽,49% 误隔离 |
|
||||
| C5 | Critical | `index_common.py` | Windows 锁可能非阻塞 |
|
||||
| I1 | Important | `schema.sql` | ivfflat lists=100 对 81 行无效 |
|
||||
| I2 | Important | 两个 ingest 脚本 | 目录范围不一致 |
|
||||
| I3 | Important | 两个 ingest 脚本 | 大文件静默截断 |
|
||||
| I4 | Important | `agent-with-memory.sh` | VAULT_DIR 推导脆弱 |
|
||||
| I5 | Important | `query_pgvector.py` | 无相似度阈值 |
|
||||
| I6 | Important | `schema.sql` | audit 表不记录触发原因 |
|
||||
| I7 | Important | `install-hook.sh` | hook 静默失败 |
|
||||
| I8 | Important | `ingest_vault.py` | 全量索引持有超长事务 |
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: 运行状态
|
||||
updated: 2026-02-25
|
||||
---
|
||||
|
||||
# 运行状态
|
||||
|
||||
## 当前状态:已上线 ✅
|
||||
|
||||
系统于 2026-02-25 完成部署,核心链路全部验收通过。
|
||||
|
||||
## 指标
|
||||
|
||||
| 指标 | 目标 | 实测(2026-02-25)| 状态 |
|
||||
|---|---|---|---|
|
||||
| Recall@5 | ≥ 70% | 90%(9/10 样本)| ✅ 达标 |
|
||||
| 增量 P95 延迟 | < 1s | 1.4s(35 次样本)| ⚠️ 未达标 |
|
||||
| 冷启动端到端 | ≤ 4.5s | 未完成量化 | 🔲 BLOCKED |
|
||||
| 敏感内容隔离 | 红线 | 通过 | ✅ |
|
||||
| 删除一致性 | 红线 | 通过 | ✅ |
|
||||
| 重命名一致性 | 红线 | 通过 | ✅ |
|
||||
| Prompt 注入防护 | 红线 | 通过 | ✅ |
|
||||
|
||||
索引规模:`memory_primary=96`,`memory_secure_audit=85`(Infrastructure/Home-Automation 被路径规则整体隔离)
|
||||
|
||||
## 已知问题
|
||||
|
||||
### ⚠️ 增量 P95 = 1.4s(目标 < 1s)
|
||||
|
||||
- 原因:OpenRouter API 网络延迟为主要瓶颈,P50 = 1.15s
|
||||
- 影响:post-commit hook 异步执行,不阻塞提交,用户无感知
|
||||
- 方向:可考虑批量 embedding 或换更快的 embedding 端点
|
||||
|
||||
### 🔲 冷启动延迟未量化(T6.3 BLOCKED)
|
||||
|
||||
- 原因:`claude -p` 在自动化测量中超时(>60s),无法稳定完成 20 次采样
|
||||
- 影响:无法验证 ≤ 4.5s 目标
|
||||
- 方向:手动计时或换用非交互式测量方式
|
||||
|
||||
### ⚠️ 敏感内容漏检样例存在
|
||||
|
||||
- 现象:T5.3 验收时发现部分边缘样例未被 `blacklist.py` 捕获
|
||||
- 影响:极少数敏感文档可能进入 `memory_primary`
|
||||
- 方向:持续加固 `blacklist.py` 的正则规则
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-02-25 | 初始部署,完成全量索引(81 docs),安装 git hook,完成验收 |
|
||||
| 2026-02-25 | 代码迁移至独立项目 `vault-memory-pgvector`,修复 Windows 跨平台锁 |
|
||||
| 2026-02-26 | 重构 P1-1~3:删除 blacklist 过宽字面量、修复 stale 清理误删 embed 失败文档、hook 自动清理临时文件;部署方式更新为 docker compose |
|
||||
Reference in New Issue
Block a user