内存系统
LibreFang 的内存系统提供持久化存储、语义搜索和知识图谱功能。
目录
概述
LibreFang 内存系统包含:
- SQLite 持久化 - 结构化 KV 存储
- 向量嵌入 - 语义搜索能力
- 知识图谱 - 实体和关系
- 会话管理 - 跨通道记忆
- 会话压缩 - 当单个会话体量过大时自动收缩
- 梦境模式(Auto-Dream) - 可选的后台整固机制,让加入的 agent 定期反思并整理自己的长期记忆,详见 后台整固(梦境模式)
- 使用追踪 - 成本和使用统计
提示: 内存数据默认存储在 ~/.librefang/data/librefang.db,可配置存储路径。
架构
┌─────────────────────────────────────────┐
│ Agent Loop │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Memory Subsystem │
├─────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Session │ │ Vector │ │Knowledge│ │
│ │ Store │ │ Search │ │ Graph │ │
│ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────┤
│ SQLite Database │
└─────────────────────────────────────────┘
配置
基本配置
[memory]
decay_rate = 0.05
sqlite_path = "~/.librefang/data/librefang.db"
高级配置
[memory]
decay_rate = 0.05
sqlite_path = "~/.librefang/data/librefang.db"
vector_dimension = 1536
max_memory_items = 10000
auto_compact = true
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
decay_rate | Float | 0.05 | 记忆置信度衰减率 |
sqlite_path | String | ~/.librefang/data/librefang.db | 数据库路径 |
vector_dimension | Integer | 1536 | 向量维度 |
max_memory_items | Integer | 10000 | 最大记忆条目 |
auto_compact | Boolean | true | 自动压缩 |
会话管理
创建会话
# 创建新会话
librefang session create --name "research-project"
列出会话
# 列出所有会话
librefang session list
会话操作
# 查看会话详情
librefang session info <session-id>
# 删除会话
librefang session delete <session-id>
# 压缩会话
librefang session compact <session-id>
# 导出会话
librefang session export <session-id> --format json
记忆操作
存储记忆
# 存储简单键值(位置参数:agent key value)
librefang memory store <agent> "user:preference:theme" "dark"
# 示例:为 coder agent 存储版本信息
librefang memory store coder "project:version" "1.0"
# 等价形式:使用 set 子命令
librefang memory set coder "note:1" "Important note"
搜索记忆
# 关键词搜索
librefang memory search "project"
# 向量搜索 (语义搜索)
librefang memory search --vector "find information about AI agents"
# 带过滤的搜索
librefang memory search "meeting" --tags "work" --limit 10
记忆操作
# 读取记忆
librefang memory get <key>
# 更新记忆
librefang memory update <key> --value "new value"
# 删除记忆
librefang memory delete <key>
# 列出所有记忆
librefang memory list --prefix "project:"
向量搜索
语义搜索
LibreFang 支持向量嵌入的语义搜索:
# 语义搜索示例
librefang memory search --vector "machine learning techniques for text classification"
相似度阈值
[memory]
similarity_threshold = 0.75
API 端点
# 语义搜索 API(GET 请求,使用查询参数)
curl "http://127.0.0.1:4545/api/memory/search?q=find+information+about+AI&limit=10&threshold=0.8"
知识图谱
实体管理
# 添加实体
librefang kg add-entity --type "person" --name "John Doe" --properties '{"role": "developer"}'
# 列出实体
librefang kg list-entities --type "person"
# 搜索实体
librefang kg search-entities "John"
关系管理
# 添加关系
librefang kg add-relation \
--from "person:john" \
--relation "works_at" \
--to "company:acme"
# 列出关系
librefang kg list-relations --entity "person:john"
# 查询关系
librefang kg query --from "person:john" --relation "works_at"
图谱查询
# 查询路径
librefang kg path --from "person:alice" --to "company:acme"
# 查询子图
librefang kg subgraph --entity "person:bob" --depth 2
会话压缩
自动压缩
当会话消息数量达到阈值时自动压缩。压缩参数由 runtime 内部管理,不在 config.toml 中暴露。默认阈值为 80 条消息,保留最近 20 条原文,其余进行摘要压缩。
手动压缩
# 压缩会话
librefang session compact <session-id>
# 压缩所有会话
librefang session compact-all
# 查看压缩状态
librefang session compaction-status
压缩算法
- 保留最近 N 条消息
- 提取关键信息
- 生成摘要
- 保留工具调用历史
后台整固(梦境模式)
会话压缩只处理 单个会话 不溢出。梦境模式是更长时间尺度上的互补机制:定期唤醒已加入的 agent,把一份四阶段提示词(定位 / 收集 / 整固 / 修剪)交给它,让它自己跨会话地整理长期记忆 —— 通过 memory_store 写入持久见解、修剪过时条目。
默认关闭。 梦境模式会按计划消耗真实 token,因此全局开关和每个 agent 的加入开关默认都是 false,必须显式开启。完整参考见 [auto_dream]。
开启方式
两个开关必须同时为 true:
# ~/.librefang/config.toml
[auto_dream]
enabled = true
min_hours = 24 # 同一 agent 再次梦境的最短间隔
min_sessions = 5 # 上次梦境之后至少触及这么多会话才会触发
# agent manifest (.toml)
auto_dream_enabled = true
# 可选:用 per-agent 覆盖全局阈值
auto_dream_min_hours = 168 # 每周一次,适合低频 agent
auto_dream_min_sessions = 1 # 每个会话之后都触发,适合高频 agent
一次梦境的流程
- 主触发路径是
AgentLoopEnd钩子 —— agent 每完成一次 turn,kernel 立刻依次检查四道闸门:全局启用 → 时间间隔 → 会话活跃数 → 文件锁。任一不通过即跳过。另有稀疏的兜底调度器(默认check_interval_secs = 86400,即每天一次)覆盖从不 turn 的 agent(例如等待外部触发的 channel bot)。 - 闸门通过后,agent 以 fork turn 方式从 canonical session 派生调用(
kernel.run_forked_agent_streaming)—— system prompt、tools、消息前缀和父 turn 字节级一致,命中 Anthropic prompt cache。Fork turn 不会把消息写回 canonical session,所以用户对话历史不会被梦境过程污染。工具白名单(memory_store/memory_recall/memory_list)在 execute 阶段强制,不在 request 构造阶段过滤 —— 这样请求 schema 仍然和父 turn 对齐,cache 才能命中。被 prompt 注入的梦境即使试图调用非 memory 工具,也只会拿到合成的错误结果。 - 流式进度(phase、工具调用次数、涉及的记忆条目、最近一 turn 预览、token / 费用)存入每 agent 的进度注册表,通过状态端点对外暴露。
- 成功后锁的 mtime 前进到 "现在"(作为时间闸门的基准);失败或中止后会回滚 mtime,下次 tick 会重试。
使用入口
- Web Dashboard — 设置页 → 梦境模式卡片,每个 agent 一行:加入开关、运行中/完成/失败 badge、"立即梦境" / "中止" 按钮、运行中时的进度预览。
- TUI —
librefang→ Dashboard tab 顶部会出现 DREAMS 条,每 agent 一行状态字符;未发生过梦境时自动隐藏。 - HTTP API —
GET /api/auto-dream/status、POST /api/auto-dream/agents/{id}/trigger、POST /api/auto-dream/agents/{id}/abort、PUT /api/auto-dream/agents/{id}/enabled。 - 审计日志 — 每次梦境都会发出一条
DreamConsolidation审计事件,附带 phase、token 使用量和美元成本。
手动控制
POST /api/auto-dream/agents/{id}/trigger绕过时间和会话闸门(仍需满足文件锁和加入开关)。POST /api/auto-dream/agents/{id}/abort中止手动触发的梦境,并回滚 mtime 让时间闸门重新打开。- 调度器触发的梦境是顺序串行执行,不支持单独中止 —— 只能等它完成或命中
timeout_secs(默认 600 秒)。
完整字段、默认值、manifest 覆盖和 runtime 工具白名单的强制逻辑见 [auto_dream]。
外部向量后端
近似最近邻检索是目前记忆子系统中唯一接受外部实现的部分,接缝就是 VectorStore trait。
LibreFang 仍然拥有 SQLite 行存储、抽取流水线和全部隔离守卫;你接入的后端只决定哪些记忆 ID 距离查询向量最近。
启用 HTTP 后端
[memory]
vector_backend = "http" # "sqlite"(默认)表示全部在进程内完成
vector_store_url = "http://127.0.0.1:6333/collections/memories"
vector_backend = "http" 而没有 vector_store_url 会让启动失败,而不是静默回退到 SQLite;除 "http" / "sqlite" 以外的取值会被按名字拒绝。
base URL 会去掉尾部斜杠后原样使用,路径直接拼接在它后面。
HTTP 契约
你的服务需要在该 base URL 下提供四个端点。 响应体上限 64 MiB,连接超时 10 秒,单次请求总超时 30 秒。
| 方法 | 路径 | 请求体 | 响应体 |
|---|---|---|---|
| POST | /insert | { id, embedding, payload, metadata } | 忽略;任意 2xx 即视为写入成功 |
| POST | /search | { query_embedding, limit, filter? } | [{ id, payload, score, metadata }] |
| DELETE | /delete | { id } | 忽略;任意 2xx 即视为删除成功 |
| POST | /get_embeddings | { ids } | { "<id>": [f32, …], … } |
filter 是调用方的 MemoryFilter 原样序列化的结果——agent、source、scope、最低置信度、创建时间上下界、metadata 等值对以及 peer。
遵守它可以让后端在排序前先剪枝,这正是它被发送过去的原因。
后端无权决定的部分
/search 返回的一切都被当作不可信数据,因为搜索结果是一个来自 LibreFang 无法控制的进程的 ID 列表。
- ID 从 SQLite 水合,绝不从后端的 payload 水合。 后端返回的
payload和metadata不会进入 prompt,进入 prompt 的是 LibreFang 本地那条 ID 对应的行。 - 水合之后会重新应用调用方的
MemoryFilter。 忽略了所收到 filter 的后端无法扩大调用方的范围:agent、scope、source、置信度下限、创建时间上下界和 metadata 等值都会在水合后的行上重新校验,而peer_id因为不由MemoryFragment承载,会在水合查询中作为 SQL 谓词强制执行。 - 跨会话(
chat_scope)与跨 session(session_scope)隔离在其之后运行,走的是与进程内后端相同的 recall 后置路径,所以接入后端不会重新打开这两道守卫中的任何一道。
失败行为
- 不是 UUID 的 ID 会被丢弃并打
WARN,结果集中的其余部分照常水合。一条畸形记录只损失它自己,而不是整次 recall。 - 水合不到任何行的 ID——已删除、未知、或属于另一个 peer——以同样方式被丢弃。
- 传输层失败(连接被拒、超时、非 2xx、无法解析的响应体)会作为该次 recall 的错误返回。不同调用方的处理不同:prompt 组装路径会记录日志并用手上已有的内容继续,而直接的 API 搜索会把错误返回给调用者。
在进程内实现该 trait
vector_backend = "http" 覆盖了任何你能放到一个小 HTTP 服务背后的后端,这是受支持的路径。
如果你更希望直接链接原生客户端,可以实现 VectorStore 并在启动时挂到 substrate 上:
use librefang_types::memory::{MemoryFilter, VectorSearchResult, VectorStore};
#[async_trait]
impl VectorStore for QdrantVectorStore {
async fn insert(&self, id: &str, embedding: &[f32], payload: &str,
metadata: HashMap<String, serde_json::Value>) -> LibreFangResult<()> { … }
async fn search(&self, query_embedding: &[f32], limit: usize,
filter: Option<MemoryFilter>) -> LibreFangResult<Vec<VectorSearchResult>> { … }
async fn delete(&self, id: &str) -> LibreFangResult<()> { … }
async fn get_embeddings(&self, ids: &[&str]) -> LibreFangResult<HashMap<String, Vec<f32>>> { … }
fn backend_name(&self) -> &str { "qdrant" }
}
search 是在 recall 路径上通过阻塞桥接调用的,因此它不能长时间阻塞执行器——上面那些超时正是为此存在的,原生客户端需要自备一套。
使用追踪
追踪操作
# 查看使用统计
librefang usage
# 查看 Agent 使用
librefang usage --agent <agent-id>
# 查看提供商使用
librefang usage --provider
成本追踪
# 查看成本
librefang cost
# 按时间范围查看成本
librefang cost --from 2025-01-01 --to 2025-01-31
# 导出报告
librefang cost export --format csv
API 端点
KV 内存操作
| 端点 | 方法 | 说明 |
|---|---|---|
/api/memory/search | GET | 搜索记忆(查询参数:q、limit、threshold) |
/api/memory | POST | 存储记忆条目 |
/api/memory/{id} | GET | 获取指定记忆条目 |
/api/memory/{id} | DELETE | 删除记忆条目 |
会话操作
| 端点 | 方法 | 说明 |
|---|---|---|
/api/memory/sessions | GET | 列出会话 |
/api/memory/sessions/{id} | GET | 获取会话详情 |
/api/memory/sessions/{id}/compact | POST | 压缩会话 |
知识图谱
知识图谱是 per-agent 的,不是全局 REST 接口。用 per-agent 端点:
| 端点 | 方法 | 说明 |
|---|---|---|
/api/memory/agents/{id}/relations | GET | 查询该 agent 的知识图谱(实体 + 关系) |
/api/memory/agents/{id}/relations | POST | 给该 agent 的图谱存新关系 |
Agent 也可以从循环里直接用 knowledge_add_entity / knowledge_add_relation / knowledge_query 工具 构建图谱。早期文档列的 /api/memory/kg/* 端点从未发布过 —— 调那些路径会 404。
主动记忆(Proactive Memory)
主动记忆让 agent 自主浮现、整合、召回长期知识,不用显式工具调用。每条记忆同时存三份 —— 语义存储(文本 + embedding)、结构化 KV(memory:{id})、知识图谱(抽取的 entity / relation 三元组) —— 所以 agent 可以按相似度、按 ID、或从已知 entity 沿图谱遍历来召回。
条目 schema
| 字段 | 类型 | 用途 |
|---|---|---|
id | UUID | 唯一记忆 ID |
agent_id | UUID | 所有 agent —— 条目严格按 agent 隔离,不会跨 agent 泄漏 |
content | text | 记忆文本 / 事实 |
source | enum | 来源:auto_memorize、manual_add 等 |
scope | enum | 级别:user_memory、session_memory、agent_memory |
confidence | f64(0.0–1.0) | 相关度分数;随时间衰减 |
metadata | JSON | 自由 KV;含 category(见下) |
created_at | RFC3339 时间戳 | 创建时间 |
accessed_at | RFC3339 时间戳 | 最近访问 —— 驱动衰减 |
access_count | int | 被召回的次数 |
deleted | bool | 软删除标志 |
embedding | vector(独立表) | 余弦相似度搜索用 |
作用域(Scope)
| 作用域 | 生命期 | 用途 |
|---|---|---|
user_memory | 跨 session 持久化 | 用户级事实和偏好(可在该用户的多个 agent 间共享)。 |
session_memory | session_ttl_hours(默认 24)后自动删除 | 当前对话的工作笔记。 |
agent_memory | per-agent 持久化 | Agent 学到的行为和技能,严格隔离。 |
类别(Category)
类别住在 metadata.category。auto_memorize 默认抽取的有:communication_style、preference、expertise、work_style、project_context、personal_detail、frustration。类别列表可配置。
自动整合
每个 agent 每 10 次 auto_memorize 自动触发一次(无需显式 cron / 用户操作)。整合器:
- 用分级相似度梯队找重复或近似条目:
- 子串包含(完全 / 超集 / 子集)
- 向量余弦(存了 embedding 时)
- Jaccard 词重叠(回退)
- 每个重复簇里保留最近创建的那条。
- 软删除其余,记日志方便审计。
整合器只扫最近 100 条,以让 dedup 循环在热路径上保持 O(n²)-安全。要扫完整历史,手动调 POST /api/memory/agents/{id}/consolidate。
衰减
未访问超过 1 天的记忆:
decayed_conf = original_conf × e^(-decay_rate × days_since_access)
final_conf = min(decayed_conf × (1 + log2(access_count)), 1.0)
衰减扫描每小时最多一次(在 auto_retrieve 顶部检查)。默认值:confidence_decay_rate = 0.01(很慢 —— 大约 70 天减半)、session_ttl_hours = 24。一条记忆"过期"的条件是:(a)超过一天没访问,或 (b)scope 是 session_memory 且 TTL 到了。
per-agent vs 跨 agent
除 user_memory 外,所有条目严格按 agent 隔离。Search、consolidate、eviction 都按 agent_id 过滤 —— 一个 agent 没有路径读到另一个 agent 的 agent_memory 或 session_memory。Per-agent 上限默认 1000 条;超过时按"最旧 / 最低 confidence"逐出。
API 端点
主动记忆端点分两类 —— 全局和 per-agent。(KV memory 在 /api/memory/agents/{id}/kv/*,是另一个子系统,不在此列。)
全局 / 跨 agent:
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /api/memory | 列出所有主动记忆(分页,可选 ?category=) |
| POST | /api/memory | 新增记忆条目 |
| GET | /api/memory/search?q=&limit= | 跨所有条目语义搜索 |
| GET | /api/memory/stats | 全局统计 |
| GET | /api/memory/config | 获取记忆配置 |
| PATCH | /api/memory/config | 更新记忆配置 |
| POST | /api/memory/cleanup | 手动跑 session-TTL 清理 |
| POST | /api/memory/decay | 手动跑 confidence 衰减 |
| POST | /api/memory/bulk-delete | 批量删除 |
| PUT | /api/memory/items/{id} | 更新单条记忆 |
| DELETE | /api/memory/items/{id} | 删除单条记忆 |
| GET | /api/memory/items/{id}/history | 记忆编辑历史 |
| GET | /api/memory/user/{user_id} | 该用户跨所有 agent 的 user-level 记忆 |
Per-agent:
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /api/memory/agents/{id} | 列出该 agent 的记忆 |
| DELETE | /api/memory/agents/{id} | 重置(清空)该 agent 的全部记忆 |
| GET | /api/memory/agents/{id}/search?q= | 限制在此 agent 范围的语义搜索 |
| GET | /api/memory/agents/{id}/stats | 该 agent 的统计 |
| DELETE | /api/memory/agents/{id}/level/{level} | 按 scope 清(session、agent、user) |
| GET | /api/memory/agents/{id}/duplicates | 找近似条目但不删 |
| POST | /api/memory/agents/{id}/consolidate | 对完整历史触发整合 |
| GET | /api/memory/agents/{id}/count | 记忆数量 |
| GET | /api/memory/agents/{id}/relations | 查询该 agent 的知识图谱 |
| POST | /api/memory/agents/{id}/relations | 存关系 |
| GET | /api/memory/agents/{id}/export | 导出该 agent 的记忆(JSON) |
| POST | /api/memory/agents/{id}/import | 导入记忆(JSON) |
之前的 /api/memory/proactive/* 命名空间是一个从未发布的草稿端点 —— 用上面这套。如果客户端还在调旧路径,会得到 404。
最佳实践
- 定期压缩 - 防止会话过大
- 使用标签 - 便于组织和搜索
- 设置衰减率 - 控制记忆置信度
- 监控使用 - 跟踪成本和使用量
故障排除
内存搜索慢
# 重建向量索引
librefang memory reindex
# 检查索引状态
librefang memory index-status
数据库膨胀
# 清理旧数据
librefang memory cleanup --older-than 30d
# 压缩数据库
librefang memory vacuum
记忆丢失
# 检查数据库完整性
librefang doctor
# 恢复备份
librefang memory restore --backup <path>