记忆系统位于 corelib/memory/,实现了一套多层级、多索引、带遗忘曲线的长期记忆架构。
type Entry struct {
ID string // 唯一标识(纳秒时间戳+随机hex)
Content string // 记忆内容
Category Category // 分类
Tags []string // 标签(含项目路径用于作用域过滤)
CreatedAt time.Time
UpdatedAt time.Time
AccessCount int // 访问计数
Embedding []float32 // 向量嵌入(GemmaEmbedder 生成)
RelatedIDs []string // 关联记忆 ID(图结构)
Strength float64 // 遗忘曲线强度值
Status Status // active / superseded / dormant
Scope Scope // global / project
Pinned bool // 钉选保护,不被压缩/淘汰
CompactForm string // 压缩摘要,用于上下文注入
}采用双轨分类:传统分类 + Claude 风格四类分类。
| 传统分类 | 说明 | 重要性权重 | 作用域 |
|---|---|---|---|
self_identity |
自我身份 | 4.0 | global |
instruction |
用户指令/纠正 | 3.0 | global |
preference |
用户偏好 | 2.0 | global |
project_knowledge |
项目知识 | 2.0 | project |
session_checkpoint |
会话检查点 | 1.5 | project |
conversation_summary |
对话摘要 | 1.0 | project |
Claude 风格映射:user → user_fact,feedback → instruction,project/reference → project_knowledge。
- Semantic Tier:抽象知识(user_fact, preference, instruction, self_identity)
- Episodic Tier:事件记录(conversation_summary, session_checkpoint)
type Store struct {
entries []Entry // 内存中的全部条目
path string // 持久化路径(JSON 文件)
maxItems int // 容量上限:500
bm25 *bm25Index // BM25 全文检索索引
vecIndex *vectorIndex // 向量相似度索引
graph *memoryGraph // 记忆关系图
embedder embedding.Embedder // 嵌入模型
archive *ArchiveStore // 冷存储(淘汰条目归档)
}持久化采用异步写入:signalSave() 通过 channel 通知后台 persistLoop goroutine,延迟 5 秒合并写入,避免频繁 I/O。
基于词频的全文检索,对记忆内容进行分词后构建倒排索引,支持增量更新。
存储 L2 归一化的嵌入向量,余弦相似度退化为点积运算:
func (v *vectorIndex) score(queryEmb []float32) map[string]float64 {
// 对每个条目计算 dot product
sim := dotProduct(queryEmb, emb)
}双向加权图结构,每个节点最多 5 条边:
link(id1, id2, strength)创建双向边- 新边强度低于所有现有边时被拒绝
expand(seedIDs, hops)BFS 扩展,每跳衰减 0.5×
灵感来自 Generative Agents 论文,融合三个信号:
Score = 1.0 × Recency + 1.0 × Importance + 1.0 × Relevance
各信号计算方式:
Recency(时效性):
Recency = exp(-0.005 × hours_since_update)
Importance(重要性):
Importance = CategoryWeight(category) + log(1 + access_count)
Relevance(相关性):融合 BM25 + 向量检索
FusedRelevance = 0.4 × BM25_score + 0.6 × cosine_similarity + project_affinity(+3.0)
- 计算 BM25 分数(store lock 外)
- 计算向量分数(store lock 外)
- 分类处理:
self_identity→ 始终优先召回user_fact→ 次优先- 其他 → Memory Stream 评分排序
- 1-hop 图扩展:对 top 候选执行图扩展,发现关联记忆
- Token 预算控制:最多 20 条,总 token ≤ 2000
两阶段召回:
- Stage 1:BM25+Vector+Graph 宽召回
- Stage 2:LLM 从候选中精选最相关条目
S(t) = S₀ × exp(-λ × hours) // λ = 0.003, 半衰期 ≈ 9.6 天- 休眠阈值:强度 < 0.1 时标记为
dormant - 召回增强:每次被召回时
Strength += 1.0,UpdatedAt重置 - 保护机制:
self_identity类别永不衰减 - 活跃条目保留峰值 Strength,仅在变为休眠时持久化衰减值
每 6 小时执行一次完整维护周期:
Decay → Compress → Promote → Reflect
batchDecayAndMark() 遍历所有条目,计算当前强度,标记休眠条目。
Compressor 执行多步压缩:
- 备份:压缩前创建快照备份
- 精确去重(dedup):删除内容完全相同的条目
- 语义去重(mergeSemanticDuplicates):LLM 判断语义相似条目并合并
- LLM 压缩:对内容 ≥ 200 字符的非保护、非钉选条目,调用 LLM 压缩至 50% 以下
- CompactForm 回填:为缺少摘要的条目生成紧凑表示
- 熔断机制:连续 3 次压缩失败后触发熔断,跳过直到下一个调度周期
压缩 Prompt 策略:
保留名称、数字、路径、命令、技术术语
删除填充词、冗余解释
使用简洁要点或短句
Promoter 实现 MemGPT 风格的 Episodic → Semantic 转换:
- 扫描最近 50 条 Episodic 记忆
- LLM 识别出现 ≥ 3 次的重复主题
- 将重复模式晋升为 Semantic 记忆(preference/instruction/user_fact)
- 每次最多晋升 5 条
Reflector 灵感来自 Generative Agents 的反思机制:
- 前置条件:总条目 ≥ 50,距上次反思 ≥ 24 小时
- 取最近 30 条 Episodic 记忆
- LLM 分析提取高层洞察:用户偏好、习惯、决策模式
- 生成的洞察存储为 Semantic 记忆
会话结束后从对话历史中提取知识点:
- 互斥机制:检测最近 10 条消息中是否已有记忆写入信号("已保存到记忆"、"memory:save" 等),避免重复
- 预压缩:对话超过 20 轮时,先 LLM 压缩再提取
- 冷却时间:默认 1 小时,防止频繁提取
- 提取的知识点按类别分类后存入 Store
冷存储,容量上限 1000 条:
- 淘汰归档:GC 时将低强度条目移入归档
- 智能复活:GC 周期中,根据 top-20 活跃记忆的标签和类别,从归档中找回相关条目
- 持久化:独立 JSON 文件(
archive.json),异步写入
工具系统位于 corelib/tool/,实现了上下文感知的智能工具选择与路由。
type RegisteredTool struct {
Name string // 工具名
Description string // 描述
Category Category // builtin / mcp / skill / non_code
Tags []string // 标签(用于分组激活)
Priority int // 优先级
Status Status // available / degraded / unavailable
InputSchema map[string]interface{} // JSON Schema
Body string // 工具实现体
BodySummary string // 实现体摘要(用于检索增强)
Caps CapRequirement // 平台能力需求(显示器/剪贴板/网络)
Handler Handler // 执行函数
}Registry 线程安全,支持注册/注销/查询/变更回调。注册时自动填充 Body 和 BodySummary。
动态合并三类工具定义:
- Builtin 工具:静态定义(bash, read_file, memory, web_search 等)
- 远程 MCP 工具:从健康的 MCP Server 动态获取
- 本地 MCP 工具:从 stdio MCP Server 获取
延迟加载(Deferred Tools):部分工具不包含在初始 prompt 中,通过 SearchDeferred 按需发现(灵感来自 Claude Code 的 ToolSearchTool 模式)。
始终包含在路由结果中的核心工具:
bash, read_file, write_file, list_directory,
send_and_observe, create_session, list_sessions,
get_session_output, get_session_events, control_session,
call_mcp_tool, list_skills, run_skill,
screenshot, send_file, memory,
web_search, web_fetch, set_nickname,
browser_connect, browser_navigate, browser_click,
discover_tool
当工具总数超过 MaxToolBudget(28)时启动智能路由:
1. 分离核心工具(始终保留)和候选工具
2. BM25 索引候选工具描述
3. 混合检索融合(BM25 + Vector)
4. 三信号评分
5. LLM Rerank(可选)
6. 截断至预算
7. 技能推荐注入
Score = α × Retrieval + β × Experience + γ × Priority
有 UsageTracker 时:α=0.6, β=0.3, γ=0.1
无 UsageTracker 时:α=0.9, γ=0.1
Retrieval Score:BM25 + Vector 融合后 min-max 归一化
Experience Score:基于历史使用记录的经验分
Priority Bonus:clamp(priority × 0.1, 0, 1)
MaxToolBudget = 28:发送给 LLM 的最大工具数MaxDynamicRouted = 18:非核心动态工具的最大数量
融合 BM25(词法)和向量(语义)检索分数:
FusedScore = α × normalize(BM25) + (1-α) × cosine_similarity
默认 α = 0.4(BM25 权重),1-α = 0.6(向量权重)。
- ToolEmbeddingCache:工具描述嵌入的磁盘持久化缓存,按模型指纹隔离
- QueryEmbeddingCache:查询嵌入的 LRU 内存缓存,带 TTL 过期
记录工具调用历史(滚动窗口 2000 条),持久化到 ~/.maclaw/data/tool_usage.json。
对每条匹配记录:
1. Jaccard 相似度 = |query_tokens ∩ record_tokens| / |query_tokens ∪ record_tokens|
2. 时效衰减 = exp(-0.01 × hours)
3. 成功权重 = success ? 1.0 : -0.3
4. weighted_sum += jaccard × recency × success_weight
score = clamp(weighted_sum / count, 0, 1)
最多回溯 200 条匹配记录,避免计算开销过大。
当候选工具超过预算时,取 top-20 候选进行 LLM listwise 重排序:
type Reranker interface {
Rerank(userMessage string, candidates []CandidateSummary, topK int) ([]string, error)
}输入包含工具名、描述、BodySummary;输出 top-5 工具名。重排序结果提升到候选列表前端,其余按融合分数补充。
用户消息中的关键词触发工具组批量激活:
| 关键词 | 激活标签 |
|---|---|
| 数据库 / database | database, sql, query, db |
| git / 版本控制 | git, vcs, version |
| 浏览器 / browser | browser, web, automation, test |
| 记忆 / memory | memory |
| 搜索 / search | web, search, internet, fetch |
| gui / 桌面 | gui, test, automation, desktop |
| 定时 / schedule | schedule, task, cron, timer |
| 配置 / config | config, settings |
支持中英文双语关键词。匹配到的工具组跳过评分直接包含。
为工具生成增强搜索文本,提升检索召回率:
- 持久化到
~/.maclaw/data/tool_enrichment.json - LLM 生成额外查询词(用户可能搜索的关键词)
- 检索时优先使用增强文本替代原始 name+description
推荐最佳编程工具(claude/codex/cursor/opencode/iflow/kilo):
- 每个工具有能力画像:支持语言、框架、任务类型、基础分
- 使用 BM25 对任务描述与能力文本进行匹配
- 结合已安装工具列表过滤
详细路由决策记录到 ~/.maclaw/logs/tool_route.log:
=== Tool Route [2026-04-02 10:30:00] ===
Message: 帮我写一个数据库查询...
Total tools: 45 | Core: 22 | Candidates: 23 | Hybrid: true
Body-aware: true
Top-20 candidates by fused score:
#1 sql_query = 0.8923
#2 db_connect = 0.7654
...
Selected tools (28):
- bash
- read_file
...
Reranker output (5): #1 sql_query #2 db_connect ...
日志文件超过 5MB 自动截断。
每个 Agent 循环(聊天或后台)拥有独立的 LoopContext:
type LoopContext struct {
ID string // 唯一标识
Kind LoopKind // Chat / Background
SlotKind SlotKind // Coding / Scheduled / Auto / SSH / Browser / GUI
Conversation []interface{} // 当前对话消息
History []interface{} // 加载的历史
maxIterations int // 最大迭代次数
iteration int // 当前迭代
status string // running / paused / completed / failed
}基于槽位的并发控制:
| 槽位类型 | 最大并发 |
|---|---|
| Coding | 2 |
| Scheduled | 1 |
| Auto | 1 |
| SSH | 10 |
| Browser | 2 |
| GUI | 1 |
Spawn():有空槽则创建,满则返回 nilSpawnOrQueue():有空槽则创建,满则排队等待Complete():释放槽位,自动调度队列中的等待任务OnChange回调通知 UI 更新
支持 OpenAI 和 Anthropic 双协议:
DoOpenAIRequest():非流式工具调用请求DoStreamOpenAIRequest():流式 SSE 请求DoSimpleLLMRequest():简单聊天请求(无工具调用)- 自动处理 SSE 格式检测、
<think>标签剥离、function_call 块清理 - Provider 适配:MiniMax 等需要 system-role 合并的特殊处理
- Memory Stream 三信号融合:时效性 + 重要性 + 相关性,平衡新旧记忆的召回
- 图扩展召回:1-hop BFS 发现关联记忆,突破关键词匹配的局限
- Ebbinghaus 遗忘曲线:模拟人类记忆衰减,自动淘汰过时信息
- Episodic → Semantic 晋升:重复出现的事件记忆自动提炼为持久知识
- 三信号工具评分:检索 + 经验 + 优先级,兼顾语义匹配和使用习惯
- 分组激活 + 延迟加载:减少初始 prompt 的工具数量,按需激活
- LLM Reranker:对 top 候选进行精排,提升工具选择准确率
- 熔断与归档复活:压缩失败熔断保护,GC 时智能复活归档条目
- 槽位并发控制:不同任务类型独立限流,支持排队调度
- 互斥知识提取:避免主 Agent 和后台提取器产生重复记忆