Agent Memory 多维分类——从认知科学到工程实现

yaml 复制代码
title: Agent Memory 多维分类------从认知科学到工程实现
tags: [agent, memory, ai, architecture]
date: 2026-07-13

概述

AI Agent 的长期记忆分类几乎全是从人类认知心理学搬运过来的。当前学界和工业界最主流的分类框架将 Agent Memory 分为 Perceptual、Working、Episodic、Semantic 四类,外加常被忽略但至关重要的 Procedural、Prospective、Collective 三类补充类型。

本文以认知科学为源头,结合 主流框架对比,用 Golang 给出完整参考实现,并补充一个常被忽略的维度:记忆的管理策略(遗忘、生命周期转化、读写操作)。

阅读建议:如果你只想快速了解四类核心记忆,直接跳到 二、四种核心记忆分类。如果你想看代码怎么落地,跳到 [七、Golang 参考实现](#七、Golang 参考实现 "#%E4%B8%83%E3%80%81Golang%20%E5%8F%82%E8%80%83%E5%AE%9E%E7%8E%B0")。


一、认知科学源头

AI Agent 的记忆分类根植于四个经典认知理论。这里只列要点,详细讨论见 认知科学到 AI Agent 记忆分类的完整映射

1.1 Atkinson & Shiffrin 多存储模型(1968)

时间维度感觉记忆(0.5s) → 短期记忆(20-30s) → 长期记忆(永久)

这是 LangGraph 两层架构(Checkpointer / Store)的直接理论来源。

1.2 Tulving 的情景 vs 语义二分法(1972)

长期记忆 = 情景记忆 ("我记得我去过巴黎")+ 语义记忆("我知道巴黎是法国首都")

维度 情景记忆 语义记忆
内容 个人经历的事件 事实、概念、知识
时间 有明确时间标签 时间标签
组织 时空关系 概念关系
遗忘 细节容易丢失 相对稳定

Tulving 后来(1985)补充了 Perceptual Representation Systems------对应 AI 中的 embedding 层。

1.3 Baddeley 的工作记忆模型(1974)

短期记忆不是被动存储,而是主动加工系统中央执行器 → 语音回路 + 视空间画板 + 情景缓冲区

在 AI 中:Context Window = 工作记忆,不只是"存消息",而是模型在此窗口内进行注意、推理、整合。

1.4 Cohen & Squire 的陈述性 vs 程序性(1980)

类型 含义 AI 对应
陈述性/外显记忆 "知道巴黎是法国首都" Episodic + Semantic
程序性/内隐记忆 "知道怎么骑车" System Prompt、Tool Defs、Fine-tuning 权重

二、四种核心记忆分类

上图为四种核心记忆类型的全景架构。图中展示了从用户输入经由感知层编码 → 工作记忆处理 → 情景记忆和语义记忆存储的双通道设计,以及始终生效的程序性记忆。

2.1 Perceptual Memory------感知记忆

认知原型:人类的图像记忆(Iconic Memory,~0.5秒)和回声记忆(Echoic Memory,~3-4秒)。

AI 对应Embedding 层------不是"存储系统",而是输入信号的编码器。

关键特征

  • 模态相关:文本用 text-embedding,图片用 CLIP,音频用 Whisper encoder
  • 是情景记忆和语义记忆的索引基础(所有检索都依赖 embedding 质量)
  • 不直接存储,输出向量供下游使用

框架体现 :LangGraph Store → IndexConfig(embed=my_func),Letta → 内置 embedding 负责 archival memory 索引。

2.2 Working Memory------工作记忆

认知原型:Baddeley 的工作记忆------容量约 7±2 个组块,持续约 20-30 秒。

AI 对应LLM 的 Context Window------Agent "此刻正在思考什么"。

sql 复制代码
┌─── Working Memory (Context Window) ─────────────────────┐
│ System Prompt (行为约束)                                   │
│ [Semantic Block] "用户偏好简洁回答" ← 从语义记忆注入        │
│ [Episodic Result] "上次对话摘要..."  ← 从情景记忆检索        │
│ Human: "帮我修改登录页面样式"         ← 当前输入            │
│ AI: "先看一下代码..."                ← 当前推理            │
│ Tool: read_file → login.go (234行)   ← 工具结果            │
│ [消息超Window时被 SummarizationMiddleware 压缩]            │
│ 容量上限: 模型上下文窗口 (128K / 200K / 1M tokens)         │
└──────────────────────────────────────────────────────────┘

三个关键操作:

操作 认知对应 AI 实现
编码 (Encoding) 注意力选择信息 @before_model 钩子选择性注入记忆
维持 (Maintenance) 复述保持 消息追加到 state["messages"]
操作 (Manipulation) 推理、整合、决策 LLM Attention 在上下文内计算

核心洞察:工作记忆不是"存"出来的,是"管"出来的。上下文管理(裁剪、摘要、选择性注入)的本质就是管理"什么进入工作记忆"。

2.3 Episodic Memory------情景记忆

认知原型 :Tulving------记得(remembering) 而非知道(knowing) 。包含 what、when、where 三要素。

AI 对应过往交互的记录序列------每次对话、每个工具调用、每个决策,以事件为单位存储。

go 复制代码
// 情景记忆的数据结构(参考 Generative Agents 的 Memory Stream)
type EpisodicRecord struct {
    ID          string      `json:"id"`
    Timestamp   time.Time   `json:"timestamp"`
    Content     string      `json:"content"`
    Importance  float64     `json:"importance"`  // LLM 评分 1-10
    Embedding   []float32   `json:"embedding"`
    ThreadID    string      `json:"thread_id"`
}

// 示例数据
examples := []EpisodicRecord{
    {
        Timestamp:  time.Now().Add(-time.Hour),
        Content:   "用户询问了登录页面样式修改,Agent 读取了 login.go",
        Importance: 6,
        ThreadID:  "conv-042",
    },
    {
        Timestamp:  time.Now().Add(-24 * time.Hour),
        Content:   "用户要求用 Fyne 替代终端 UI,不接受 CLI 方案",
        Importance: 9,
        ThreadID:  "conv-041",
    },
}

检索策略:情景记忆的检索混合三个权重------这和语义记忆有本质区别。

复制代码
检索得分 = α × sematic_similarity + β × recency_score + γ × importance_score

Generative Agents 权重: 1.0 × recency + 1.0 × importance + 1.0 × relevance
CrewAI v1.15 权重:   0.5 × semantic + 0.3 × recency + 0.2 × importance

冷启动问题:新用户只有少量 episodic 记录,相似度全部很低时,不注入比注入不相关的更好。应该设置置信度门控。

框架 情景记忆实现
Generative Agents Memory Stream------recency × importance × relevance 检索
Letta Archival Memory------向量DB + archival_memory_search,Out-of-context
LangGraph Store (namespace="conversations")------带时间戳的 JSON 文档
EM-LLM 事件分割 + 两阶段检索(语义相似 → 时间邻近扩展)
CrewAI Unified Memory 的 composite scoring

2.4 Semantic Memory------语义记忆

认知原型 :Tulving------知道(knowing) 。没有时间标签------你知道"巴黎是法国首都"但你不记得"什么时候学会的"。

AI 对应结构化的、精心维护的长期知识------关于用户是谁、Agent 自己是谁、领域知识是什么。

go 复制代码
// 语义记忆 = 精炼的、无时间性的知识
type SemanticStore struct {
    UserProfile    UserProfile     `json:"user_profile"`
    Persona        AgentPersona    `json:"persona"`
    DomainKnowledge map[string]any `json:"domain_knowledge"`
}

type UserProfile struct {
    Name        string            `json:"name"`
    Role        string            `json:"role"`
    TechStack   []string          `json:"tech_stack"`
    Preferences map[string]string `json:"preferences"`
    Constraints map[string]string `json:"constraints"`
}

type AgentPersona struct {
    Name         string   `json:"name"`
    Capabilities []string `json:"capabilities"`
    Limitations  []string `json:"limitations"`
    Rules        []string `json:"behavior_rules"`
}

语义记忆常驻 vs 按需的关键分歧

策略 代表框架 做法
常驻上下文 Letta Memory Blocks 语义记忆固定在上下文中,Agent 用工具自行修改
按需检索 LangGraph Store 语义记忆存在外部,每次对话开始时检索注入

Letta 的做法更接近人类------你不需要"检索"自己的名字,它一直在那里。


三、超越四分类------常被忽略的记忆类型

3.1 Procedural Memory------程序性记忆

认知原型:Cohen & Squire------知道"怎么做"而非"知道是什么"。你可以骑自行车但很难用语言描述。

AI 对应固化在模型行为中,不需要检索:

go 复制代码
// 程序性记忆在 AI Agent 中的四种体现
type ProceduralMemory struct {
    // 1. System Prompt------始终在上下文中的行为规则
    SystemPrompt string

    // 2. Tool Definitions------Agent 的能力边界
    Tools []ToolDef

    // 3. Few-shot Examples------验证过的行为模式
    Examples []InteractionPattern

    // 4. 最终形态:Fine-tuned Weights------模型本能
}

// 示例:Voyager 的 Skill Library 是程序性记忆的代码化实现
type SkillLibrary struct {
    Skills map[string]Skill // 可执行、可组合的代码技能
}

type Skill struct {
    Name        string   `json:"name"`
    Code        string   `json:"code"`         // 可执行代码
    Description string   `json:"description"`
    DependsOn   []string `json:"depends_on"`   // 可组合:技能之间可以调用
}

Voyager 的核心贡献 :技能以可执行代码存储,可以在新环境中直接复用,无需重新生成。这避免了 fine-tuning 的灾难性遗忘。

3.2 Prospective Memory------前瞻记忆

你笔记里没覆盖的类型。它是唯一面向未来的记忆------"将来要做什么"。

认知原型:记住"下午 3 点有会议"------意图和计划。

go 复制代码
// 前瞻记忆------Agent 的意图和待办系统
type ProspectiveMemory struct {
    ActiveGoals     []Goal        `json:"active_goals"`
    ScheduledTasks  []ScheduledTask `json:"scheduled_tasks"`
    InterruptedTasks []InterruptedTask `json:"interrupted_tasks"`
}

type Goal struct {
    ID        string    `json:"id"`
    Content   string    `json:"content"`
    Priority  int       `json:"priority"`
    CreatedAt time.Time `json:"created_at"`
    Status    string    `json:"status"` // active | blocked | completed
    ParentID  string    `json:"parent_id"` // 子目标层级
}

type InterruptedTask struct {
    OriginalGoal  Goal   `json:"original_goal"`
    InterruptedAt time.Time `json:"interrupted_at"`
    Context       string `json:"context"` // 恢复任务需要的上下文
    ResumeHint    string `json:"resume_hint"`
}

为什么重要 :AutoGPT 最大的失败模式就是 goal drift------Agent 在复杂任务中忘记了最初的目标。前瞻记忆就是为解决这个问题而存在的。

3.3 Collective Memory------集体记忆

认知原型:社会共享知识------团队 wiki、组织文化。

AI 对应多 Agent 系统的共享知识池

go 复制代码
// 集体记忆:多 Agent 共享的知识池
type CollectiveMemory struct {
    SharedEpisodic  *VectorStore  // 所有 Agent 可见的事件日志
    SharedSemantic  *GraphStore   // 共享知识图谱
    AgentPrivate    map[string]*MemoryStore // 每个 Agent 的私有记忆
}

// ChatDev 的例子:
// CEO、CTO、Programmer 三个 Agent 共享一个 project memory
// 但各自有自己的 working memory

上图展示了记忆之间的转化关系全景:从 Working → Episodic 的自动沉淀,到 Episodic → Semantic 的 Reflection 提炼,再到 Semantic → Procedural 的长期固化。同时展示了 Collective Memory 与各 Agent 私有记忆的共享与隔离关系。


四、记忆的生命周期与转化

四类记忆不是孤立的------它们之间存在动态的转化关系。这一点在原始认知模型中没有对应,是 AI Agent 独有的工程挑战。

4.1 转化路径

go 复制代码
// 记忆生命周期管理器
type MemoryLifecycleManager struct {
    working     *WorkingMemory
    episodic    *EpisodicStore
    semantic    *SemanticStore
    procedural  *ProceduralConfig
}

// 路径 1: Working → Episodic(自动)
// 每次对话结束时,工作记忆自动沉淀为情景记忆事件
func (m *MemoryLifecycleManager) CommitSession(session *Session) error {
    record := EpisodicRecord{
        Timestamp:  time.Now(),
        Content:    summarizeSession(session),
        Importance: llmScoreImportance(session),  // LLM 评分 1-10
        Embedding:  embed(session.Summary),
        ThreadID:   session.ID,
    }
    return m.episodic.Store(record)
}

// 路径 2: Episodic → Semantic(需要 Reflection)
// 从多条情景记忆中提炼语义知识------定时批量处理,不阻塞用户交互
func (m *MemoryLifecycleManager) Reflect(ctx context.Context, userID string) error {
    recent := m.episodic.Query(userID, sinceLastReflection(userID), limit=20)
    if len(recent) < 3 {
        return nil // 不够多,不值得反思
    }

    insights := m.llmGenerate(ctx, buildReflectionPrompt(recent))
    for _, ins := range insights {
        if ins.Confidence > 0.7 && !m.semantic.AlreadyExists(ins) {
            m.semantic.Upsert(userID, ins.Key, ins.Value)
        }
    }
    return nil
}

// 路径 3: Semantic → Working(注入)
// 每次新对话开始时注入用户画像
func (m *MemoryLifecycleManager) InjectIntoWorking(userID string) string {
    profile := m.semantic.GetProfile(userID)
    prefs   := m.semantic.GetPreferences(userID)
    return fmt.Sprintf("[用户: %s, 偏好: %v]", profile, prefs)
}

// 路径 4: Semantic → Procedural(固化,最慢)
// 经过充分验证的行为模式固化为 System Prompt 或 Fine-tune 样本
func (m *MemoryLifecycleManager) Solidify(rule Rule, evidenceCount int) {
    if evidenceCount >= SOLIDIFICATION_THRESHOLD {
        m.procedural.AddSystemRule(rule.Content)
        logSolidification(rule)
    }
}

4.2 关键设计决策

转化路径 触发时机 成本 可靠性要求
Working → Episodic 每次会话结束 低(一次 embed + insert)
Episodic → Semantic 定时离线(夜间/批量) 中(LLM 调用反思) 高(需去重+冲突检测)
Semantic → Working 每次会话开始 低(一次精确查询) 极高
Semantic → Procedural 经过充分验证后 低(追加配置) 极高(需人工审核)

4.3 EM-LLM 的事件分割贡献

EM-LLM(ICLR 2025)提出了一个关键创新:事件分割(Event Segmentation) 。不是按轮次或 token 数切割情景记忆,而是用 Bayesian Surprise 检测事件边界------当上下文发生显著变化时自动切分为新事件。

vbnet 复制代码
原始 token 序列
    │
    ▼ Bayesian Surprise 检测边界
    ├── Event 1: "用户询问天气"       [tokens 0-145]
    ├── Event 2: "Agent 调用 API"     [tokens 146-312]
    ├── Event 3: "返回结果并解释"     [tokens 313-489]
    └── Event 4: "用户追问细节"       [tokens 490-...]
    │
    ▼ 两阶段检索
  Stage 1: 语义相似度 → 找到 Event 1
  Stage 2: 时间邻近扩展 → 带出 Event 2, 3, 4(保持事件完整性)

这比固定长度的 sliding window 更接近人类处理情景记忆的方式------人类记忆也是按"事件"而非"token 数"组织的。


五、遗忘策略------被忽视的记忆管理维度

几乎所有论文都在研究"怎么存更多",很少有人讨论"怎么忘更好"。但不遗忘反而使系统更差------当 episodic memory 无限增长,检索精度会随噪声密度上升而下降。

上图对比了五种主流遗忘策略在不同维度上的表现。

5.1 策略对比

策略 实现方式 代表系统 优点 缺陷
FIFO 覆盖 固定 buffer,最旧的出队 RET-LLM 简单,内存可控 可能丢弃重要旧信息
手动删除 用户/Agent 显式删除 ChatDB 精确 需主动决策,容易堆积
合并压缩 N 条同类 → 1 条总结 GITM 保留核心,减少存储 丢失细节
重要性衰减 low-importance 沉到底部 Generative Agents 软遗忘,不丢失数据 仍然占用存储
硬遗忘 定期清理低于阈值的老记录 (学术盲区) 释放存储,提升检索质量 需要调阈值

5.2 Golang 实现

go 复制代码
// 遗忘策略接口
type ForgettingStrategy interface {
    ShouldForget(record EpisodicRecord) bool
}

// 策略 1: 软遗忘------重要性衰减
type ImportanceDecay struct {
    DecayRate float64 // 如 0.1 => 每天衰减 10%
}

func (s *ImportanceDecay) Apply(records []EpisodicRecord) []EpisodicRecord {
    for i := range records {
        days := time.Since(records[i].Timestamp).Hours() / 24
        records[i].Importance *= math.Exp(-s.DecayRate * days)
        if records[i].Importance < 0.1 {
            records[i].Importance = 0.1 // floor------不完全消失
        }
    }
    return records
}

// 策略 2: 硬遗忘------存储清理(通常离线执行)
type HardForget struct {
    ImportanceThreshold float64
    AgeDaysThreshold    int
}

func (s *HardForget) Cleanup(store EpisodicStore, userID string) (int, error) {
    candidates, err := store.Query(userID, QueryFilter{
        ImportanceMax: s.ImportanceThreshold,
        OlderThanDays: s.AgeDaysThreshold,
    })
    if err != nil {
        return 0, err
    }
    for _, record := range candidates {
        store.Delete(record.ID)
    }
    return len(candidates), nil
}

// 策略 3: 合并压缩------同类信息聚合
type MergeCompress struct {
    MergeThreshold int // 同类记录达到 N 条时触发合并
}

func (s *MergeCompress) Compact(store EpisodicStore, userID, topic string) error {
    related := store.FindByTopic(userID, topic)
    if len(related) < s.MergeThreshold {
        return nil
    }
    summary := llmSummarize(related) // LLM 总结 N 条 → 1 条
    for _, old := range related {
        store.Delete(old.ID)
    }
    return store.Store(EpisodicRecord{
        Content:    summary,
        Importance: maxImportance(related),
        Timestamp:  time.Now(),
    })
}

// 遗忘调度器:定时执行
type ForgettingScheduler struct {
    strategies []ForgettingStrategy
}

func (s *ForgettingScheduler) RunDaily(ctx context.Context) {
    // 在用户不活跃的时间段(如凌晨 3 点)运行
    for _, strat := range s.strategies {
        strat.Apply(...)
    }
}

六、框架实现对比

维度 Generative Agents Letta (MemGPT) LangGraph CrewAI EM-LLM
Working Context Window Context Window Checkpointer (state) 当前对话 Context Window
Episodic Memory Stream (recency×importance×relevance) Archival Memory (向量DB) Store ("episodic" ns) Unified Memory (composite score) Event-based (Bayesian Surprise 分割)
Semantic Reflections 写回 Stream Memory Blocks (常驻、自编辑) Store ("semantic" ns) Unified Memory (同一 DB) (不单独区分)
Procedural Planning 机制 System Prompt Tool Defs + Prompt Agent Role Config ---
核心创新 Reflection Agent 自己管理记忆(tool calling) namespace 隔离的通用 Store 统一向量库 + 复合评分 认知式事件分割
遗忘策略 重要性衰减 手动 (memory_replace) 开发者自行实现 --- ---
分类哲学 学术完整性优先 Agent 自主管理优先 灵活性与自由度优先 简单统一优先 认知逼真度优先

关键分歧:Letta 让 Agent 自己管理语义记忆(用 tool calling),比其他框架更"认知真实"但也更激进;LangGraph 把分类决策完全交给开发者,是最灵活也最需要设计能力的方案。


七、Golang 参考实现

以下是一个最小可行架构的完整骨架。不是生产级代码,但覆盖了四类核心记忆的读写检索逻辑,可以作为实际项目的起点。

7.1 核心接口定义

scss 复制代码
package memory

import (
    "context"
    "time"
)

// ============================================================
// 核心类型
// ============================================================

// MemoryVector 记忆项的向量表示(感知层输出)
type MemoryVector []float32

// MemoryRecord 通用记忆记录
type MemoryRecord struct {
    ID         string        `json:"id"`
    Content    string        `json:"content"`
    Embedding  MemoryVector  `json:"embedding"`
    Importance float64       `json:"importance"`
    CreatedAt  time.Time     `json:"created_at"`
    UpdatedAt  time.Time     `json:"updated_at"`
    Metadata   map[string]any `json:"metadata"`
}

// RetrievalScore 检索评分
type RetrievalScore struct {
    SemanticSimilarity float64
    Recency            float64
    Importance         float64
    Composite          float64
}

// ============================================================
// 感知层接口 (Perceptual Memory)
// ============================================================

type Embedder interface {
    Embed(ctx context.Context, text string) (MemoryVector, error)
    EmbedBatch(ctx context.Context, texts []string) ([]MemoryVector, error)
    Dimension() int
}

// ============================================================
// 工作记忆接口 (Working Memory)
// ============================================================

type WorkingMemory interface {
    // 管理上下文窗口内的信息
    Append(msg Message)
    GetWindow(maxTokens int) []Message
    Summarize(ctx context.Context, model LLM) (string, error)
    // 注入外部记忆
    Inject(block string) // 语义记忆常驻块
    InjectEpisodic(results []MemoryRecord) // 情景检索结果
}

type Message struct {
    Role      string    `json:"role"`     // system / user / assistant / tool
    Content   string    `json:"content"`
    ToolName  string    `json:"tool,omitempty"`
    Timestamp time.Time `json:"timestamp"`
}

// ============================================================
// 情景记忆接口 (Episodic Memory)
// ============================================================

type EpisodicStore interface {
    // 写入
    Store(record MemoryRecord) error
    StoreBatch(records []MemoryRecord) error

    // 检索(时间+语义混合)
    Search(ctx context.Context, query string, opts SearchOptions) ([]MemoryRecord, error)
    SearchByTime(userID string, since, until time.Time) ([]MemoryRecord, error)

    // 管理
    UpdateImportance(id string, importance float64) error
    Delete(id string) error

    // 遗忘
    ApplyForgetting(strategy ForgettingStrategy, userID string) (int, error)
}

type SearchOptions struct {
    UserID    string
    Limit     int
    // 三要素权重
    Alpha     float64 // semantic weight
    Beta      float64 // recency weight
    Gamma     float64 // importance weight
    // 门控
    MinScore  float64 // 低于此分数的结果不返回(解决冷启动问题)
}

// ============================================================
// 语义记忆接口 (Semantic Memory)
// ============================================================

type SemanticStore interface {
    // 精确读写(语义记忆应该是结构化的)
    Get(namespace, key string) (string, error)
    Put(namespace, key string, value any) error
    Delete(namespace, key string) error
    List(namespace string) (map[string]any, error)

    // 用户画像(最常用的语义记忆子集)
    GetProfile(userID string) (*UserProfile, error)
    UpdatePreference(userID, key, value string) error
}

// ============================================================
// 反思机制 (Reflection)
// ============================================================

type ReflectionEngine interface {
    // 从情景记忆中提炼语义知识
    Reflect(ctx context.Context, userID string, recent []MemoryRecord) ([]Insight, error)
}

type Insight struct {
    Key        string  `json:"key"`
    Value      string  `json:"value"`
    Confidence float64 `json:"confidence"`
    Evidence   []string `json:"evidence"` // 支撑这条 insight 的 episodic record IDs
}

// ============================================================
// 前瞻记忆接口 (Prospective Memory)
// ============================================================

type ProspectiveStore interface {
    // 目标追踪
    SetGoal(goal Goal) error
    GetActiveGoals(userID string) ([]Goal, error)
    CompleteGoal(goalID string) error

    // 中断恢复
    SaveCheckpoint(taskID string, context string) error
    ResumeTask(taskID string) (string, error) // 返回恢复上下文
}

7.2 核心实现:混合检索

go 复制代码
// 情景记忆的检索实现------这是整个系统最核心的逻辑
func (s *episodicStore) Search(
    ctx context.Context, query string, opts SearchOptions,
) ([]MemoryRecord, error) {
    if opts.Limit == 0 {
        opts.Limit = 5
    }
    if opts.Alpha == 0 && opts.Beta == 0 && opts.Gamma == 0 {
        // 默认权重(Generative Agents 风格)
        opts.Alpha, opts.Beta, opts.Gamma = 1.0, 1.0, 1.0
    }

    // Step 1: 语义相似度检索(粗筛)
    queryVec, err := s.embedder.Embed(ctx, query)
    if err != nil {
        return nil, fmt.Errorf("embed query: %w", err)
    }

    candidates, err := s.vectorDB.Search(ctx, queryVec, opts.Limit*3) // 过采样
    if err != nil {
        return nil, fmt.Errorf("vector search: %w", err)
    }

    // Step 2: 复合评分
    now := time.Now()
    type scored struct {
        record MemoryRecord
        score  RetrievalScore
    }
    var scored []scored

    for _, c := range candidates {
        // 语义相似度(从向量搜索中获取)
        semanticScore := c.Score

        // 时间衰减:指数衰减,越近越高
        hoursAgo := now.Sub(c.Record.CreatedAt).Hours()
        recencyScore := math.Exp(-0.01 * hoursAgo) // λ=0.01

        // 重要性(LLM 评分或默认)
        importanceScore := c.Record.Importance
        if importanceScore == 0 {
            importanceScore = 5.0 // 默认中等重要
        }

        composite := opts.Alpha*semanticScore +
            opts.Beta*recencyScore +
            opts.Gamma*importanceScore/10.0

        // 门控:低于阈值的直接丢弃
        if composite < opts.MinScore {
            continue
        }

        scored = append(scored, scored{c.Record, RetrievalScore{
            SemanticSimilarity: semanticScore,
            Recency:            recencyScore,
            Importance:         importanceScore,
            Composite:          composite,
        }})
    }

    // Step 3: 按复合分数排序
    sort.Slice(scored, func(i, j int) bool {
        return scored[i].score.Composite > scored[j].score.Composite
    })

    // Step 4: 截断
    if len(scored) > opts.Limit {
        scored = scored[:opts.Limit]
    }

    records := make([]MemoryRecord, len(scored))
    for i, s := range scored {
        records[i] = s.record
    }

    return records, nil
}

7.3 记忆管理器:聚合所有类型

go 复制代码
// MemoryManager 是所有记忆类型的聚合入口
type MemoryManager struct {
    embedder    Embedder
    working     WorkingMemory
    episodic    EpisodicStore
    semantic    SemanticStore
    prospective ProspectiveStore
    reflector   ReflectionEngine
    llm         LLM
}

func NewMemoryManager(cfg Config) (*MemoryManager, error) {
    embedder, err := NewOpenAIEmbedder(cfg.EmbeddingModel)
    if err != nil {
        return nil, err
    }

    return &MemoryManager{
        embedder:    embedder,
        working:     NewSlidingWindow(cfg.MaxWindowTokens),
        episodic:    NewPGVectorStore(cfg.DBConn, embedder),
        semantic:    NewRedisStore(cfg.RedisAddr), // 语义记忆用 Redis 追求低延迟
        prospective: NewInMemoryProspectiveStore(),
        reflector:   NewLLMReflector(cfg.ReflectModel),
        llm:         NewOpenAI(cfg.Model),
    }, nil
}

// OnSessionStart 每次新会话开始时调用
func (m *MemoryManager) OnSessionStart(ctx context.Context, userID string) error {
    // 1. 注入语义记忆(常驻)
    profile, err := m.semantic.GetProfile(userID)
    if err != nil {
        return err
    }
    m.working.Inject(formatProfileBlock(profile))

    // 2. 检索相关情景记忆(按需)
    recentEpisodes, err := m.episodic.Search(ctx, "recent interactions", SearchOptions{
        UserID:  userID,
        Limit:   3,
        Alpha:   0.5,
        Beta:    0.3,
        Gamma:   0.2,
        MinScore: 0.3, // 门控:太不相关的不要注入
    })
    if err != nil {
        return err
    }
    m.working.InjectEpisodic(recentEpisodes)

    return nil
}

// OnSessionEnd 每次会话结束时调用
func (m *MemoryManager) OnSessionEnd(session *Session) error {
    record := MemoryRecord{
        Content:    buildSessionSummary(session),
        Importance: m.llm.ScoreImportance(session),
        Embedding:  m.embedder.Embed(ctx, session.Summary),
        CreatedAt:  time.Now(),
        Metadata: map[string]any{
            "user_id":    session.UserID,
            "thread_id":  session.ThreadID,
            "msg_count":  len(session.Messages),
        },
    }
    return m.episodic.Store(record)
}

// NightlyReflection 离线反思任务(每天凌晨执行)
func (m *MemoryManager) NightlyReflection(ctx context.Context, userID string) error {
    recent, err := m.episodic.SearchByTime(userID,
        time.Now().Add(-24*time.Hour), time.Now())
    if err != nil || len(recent) < 3 {
        return err
    }

    insights, err := m.reflector.Reflect(ctx, userID, recent)
    if err != nil {
        return err
    }

    for _, ins := range insights {
        if ins.Confidence < 0.7 {
            continue
        }
        if exists, _ := m.semantic.Get("insights", ins.Key); exists != "" {
            continue // 去重:已有相似 insight
        }
        m.semantic.Put("insights", ins.Key, ins.Value)
    }

    // 同时执行遗忘清理
    cleaned, _ := m.episodic.ApplyForgetting(&HardForget{
        ImportanceThreshold: 1.0,
        AgeDaysThreshold:    30,
    }, userID)
    log.Printf("nightly reflection: %d insights, cleaned %d old records", len(insights), cleaned)

    return nil
}

7.4 最小可跑示例

less 复制代码
func main() {
    mgr, _ := memory.NewMemoryManager(memory.Config{
        EmbeddingModel: "text-embedding-3-small",
        Model:          "gpt-4o",
        ReflectModel:   "gpt-4o-mini", // 反思用便宜模型
        DBConn:         os.Getenv("PG_CONN"),
        RedisAddr:      os.Getenv("REDIS_ADDR"),
    })

    ctx := context.Background()
    userID := "user-zhangsan"

    // 1. 会话开始------注入记忆
    mgr.OnSessionStart(ctx, userID)

    // 2. 模拟一轮对话
    session := &memory.Session{
        UserID:   userID,
        ThreadID: "conv-042",
        Messages: []memory.Message{
            {Role: "user", Content: "帮我把登录页的样式改成 Material Design"},
            {Role: "assistant", Content: "好的,我先看一下现在的 login.go..."},
        },
    }

    // 3. 会话结束------自动沉淀
    mgr.OnSessionEnd(session)

    // 4. 下次会话------检索到上次对话
    records, _ := mgr.episodic.Search(ctx, "登录页面样式修改", memory.SearchOptions{
        UserID: userID, Limit: 3,
    })
    for _, r := range records {
        fmt.Printf("[%s] %s (importance: %.1f)\n",
            r.CreatedAt.Format("01-02 15:04"), r.Content, r.Importance)
    }
    // Output:
    // [07-10 15:23] 用户要求用 Material Design 修改登录页面样式 (importance: 7.0)
}

八、总结:双轴设计框架

综合全部分析,Agent 记忆系统的完整设计空间可以归纳为一个双轴框架

分类轴 (What) ------记忆的类型:

类型 核心问题 存储 检索 生命周期
Perceptual 输入如何编码? ---(编码层) --- 实时
Working 此刻在思考什么? Context Window Attention 秒级
Episodic 过去发生了什么? 向量DB + 时间索引 时间+语义混合 日/周级
Semantic 知道什么事实? KV Store (快速读写) 常驻或精确匹配 长期
Procedural 怎么做? System Prompt + Tool Defs 始终生效 版本级
Prospective 将来要做什么? 任务队列 优先级 + 状态 会话/跨会话

管理轴 (How) ------记忆的操作:

scss 复制代码
写入策略              检索策略              遗忘策略           转化策略
    │                    │                    │                 │
自动 vs 主动         注入 vs 按需         软遗忘(衰减)        W→E (自动)
去重 vs 保留         时间 vs 语义         硬遗忘(删除)        E→S (Reflection)
实时 vs 批量         置信度门控           合并(压缩)          S→P (固化)

设计建议速查

你的需求 最小方案 完整方案
"记一个会话就行" Working(裸 Context Window) + Checkpointer
"跨会话记住用户" + Semantic Store + 每次注入 Profile
"智能召回历史对话" + Episodic Store + 时间+语义混合检索 + 置信度门控
"从经验中自我改进" + Reflection 定时任务 + Memory Blocks 自编辑
"全都要" 四类完整实现 + 前瞻记忆 + 记忆生命周期管理 + 遗忘调度

这是一个渐进式的设计路径------不需要一开始就上全部组件。根据任务周期和复杂度,逐步叠加。


参考论文与框架

来源 核心贡献
认知科学到 AI Agent 记忆分类的完整映射 从认知科学到 AI 的完整理论映射
Generative Agents (Park et al. 2023) Memory Stream + Reflection 机制
MemGPT / Letta (Packer et al. 2023) Agent 自主管理记忆,Memory Blocks
Voyager (Wang et al. 2023) Skill Library------可执行代码作为程序性记忆
Reflexion (Shinn et al. 2023) 极简 episodic buffer + verbal reinforcement
EM-LLM (Fountas et al. 2024) Bayesian Surprise 事件分割
LLM Agent Survey (Xi et al. 2023) Memory Operations (Read/Write/Reflect) 框架
相关推荐
ckjoker2 小时前
让AI给AI当法官:手敲LLM-as-a-Judge评估系统,25次调用跑出最弱维度
llm·agent·测试
ArixBit2 小时前
用Go手撸 Agent 框架#8:手写一个最小 Agent,先让 loop 跑起来
go·agent
小毕超3 小时前
生产级智能体框架 Pydantic AI 介绍和使用
agent·pydantic·pydanticai
怕浪猫3 小时前
第14章 性能优化与成本控制
openai·agent·ai编程
MobotStone4 小时前
为什么你的智能体总在第二步就崩溃?
agent
KaneLogger17 小时前
防止 AI Agent 误删文件,最简单的办法就是加一个 Hook
llm·agent·trae
永远的沙雕网友18 小时前
写点粗浅的harness相关内容(1)
agent
为你学会写情书19 小时前
LangChain.js RAG 实战:爬取掘金文章,让 LLM 告别幻觉
agent
带刺的坐椅19 小时前
SolonCode v2026.7.13 发布:多工作区协同、推理水平可控、子代理任务分组、工程体验再进化
llm·agent·codex·solon-ai·claudecode·opencode