mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-02 23:33:58 +00:00
feat(memory): N2a 第二块砖 + N2d 数据面 —— 轻量图记忆装配(temp 可写/主库只读/并集)与回收合入
按用户确认的形态:**独立存储实例**(不给共享记忆层加 space 列)。
## LightMemory:子的图记忆装配(设计 §5.6)
temp 实例(独立存储,读写) ← 子的一切图记忆写入落这里,与子同生共死
主库受限句柄(只读) ← 子只能读(query_only 结构性拒绝写入)
子的查询 = 两实例各查一次 + **应用层合并**(并集)
- 写入**只落 temp**(`Commit` 不接受 main 方向)
- 并集合并规则:实体按**名字**去重(同名保留 mention_count 较大者)、
关系按 (源名, 关系, 目标名) 去重;结果排序确定(便于断言与展示稳定)
- 单侧查询失败不影响另一侧(只有两侧都失败才报错)
- `AllowWrite=false`(用户给的备选简化):**没有 temp 实例**,子对图记忆完全只读,
写入被拒;读主库照常
## ExportTriples + 回收合入(设计 §9,N2d 数据面)
- `GraphDB.ExportTriples(limit)`:导出**活跃**三元组,把实体名一并带出
⇒ 合入侧直接复用 `Commit`(按实体名 upsert + 关系唯一约束)
- 回收主流程:子写 temp → 父导出 → **父选哪几条** → 写进 main。
未选中的**不进**主库;重复收割**幂等**(不产生重复实体)
## 验收(7 项新测试)
`light_memory_test.go`(5):
- 写只落 temp、查询是并集、主库无子的痕迹
- **两个子的 temp 互不可见**(只有 main 共享)
- 写禁用时完全只读(无 temp 实例、写入被拒、读照常)
- 并集去重(同名实体只出现一次)
- 合并确定性(去重 + 排序 + mention_count 取大)
`reclaim_test.go`(2):
- 导出只含活跃关系且带实体名、limit 生效
- 回收主流程(选中的进主库、未选中的不进、重复收割幂等)
全仓 go test ./... 37 包 ok / 0 FAIL。
This commit is contained in:
@ -626,6 +626,46 @@ func (g *GraphDB) Recall(keywords []string, seedEntities []string, depth int, se
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ExportTriples 导出库中的**活跃**三元组(供父 agent 在回收阶段收割子的 temp)。
|
||||
//
|
||||
// 设计 docs/zh/resident-subagent-design.md §9(回收 = 父读 temp → 选记录 → 合入 main)。
|
||||
// 只导出 `status='active'` 的关系,并把实体名一并带出(Relation 已含 SourceName/TargetName),
|
||||
// 于是合入侧可以直接复用 Commit —— 它按实体名 upsert、按
|
||||
// (source, relation, target) 幂等,因此"重复收割"不会造成重复条目。
|
||||
//
|
||||
// limit <= 0 表示不限量。
|
||||
func (g *GraphDB) ExportTriples(limit int) ([]Triple, error) {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
q := `SELECT s.name, r.relation_type, t.name, r.confidence
|
||||
FROM relations r
|
||||
JOIN entities s ON s.id = r.source_id
|
||||
JOIN entities t ON t.id = r.target_id
|
||||
WHERE r.status = 'active'
|
||||
ORDER BY r.id`
|
||||
args := []interface{}{}
|
||||
if limit > 0 {
|
||||
q += " LIMIT ?"
|
||||
args = append(args, limit)
|
||||
}
|
||||
rows, err := g.db.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []Triple
|
||||
for rows.Next() {
|
||||
var tr Triple
|
||||
if err := rows.Scan(&tr.Subject, &tr.Relation, &tr.Object, &tr.Confidence); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, tr)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (g *GraphDB) Purge(criteria map[string]string, mode string) (int, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user