mcpField
S
session-learning

session-learning

github.com/rysweetAgent 工作流76150 次安装21 周前更新
代码仓库

关于 session-learning

为 amplihack 提供跨会话学习的技能,可从对话记录中提取经验,并在后续会话中呈现相关内容。经验按类别存入 YAML,并通过关键词匹配,而非语义搜索。

主要功能

  1. 01会话结束时提取经验
  2. 02会话开始时注入相关经验
  3. 03按类别以 YAML 保存经验
  4. 04通过命令管理经验
  5. 05钩子故障时不中断会话

适用场景

  1. 01调试时回想以往解决方案
  2. 02复用项目工作流经验
  3. 03查看或搜索已保存经验

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rysweet/amplihack --skill session-learning

安装后在 Claude Code 中输入/session-learning确认已加载。

附带文件README.md

详细介绍

会话学习技能

用途

此技能通过以下方式实现跨会话学习:

  1. 在 Stop 钩子处从会话记录中提取经验
  2. 以结构化 YAML 格式存储经验(~/.amplihack/.claude/data/learnings/)
  3. 在 SessionStart 处根据任务相似度注入相关的过往经验
  4. 通过 /amplihack:learnings 功能管理经验

设计理念

极简方案:

  • 每个学习类别对应一个 YAML 文件(而非每个会话对应一个文件)
  • 使用简单的关键词匹配判断相关性(不使用复杂的 ML)
  • 补充现有的 DISCOVERIES.md/PATTERNS.md,而非取而代之
  • 故障安全:绝不阻止会话启动或停止

学习类别

经验分为五个类别存储:

类别文件用途
错误errors.yaml错误模式及其解决方案
工作流workflows.yaml工作流见解和捷径
工具tools.yaml工具使用模式和注意事项
架构architecture.yaml设计决策和权衡
调试debugging.yaml调试策略和根本原因分析

YAML 架构

每个学习文件都遵循以下结构:

# .claude/data/learnings/errors.yaml
category: errors
last_updated: "2025-11-25T12:00:00Z"
learnings:
  - id: "err-001"
    created: "2025-11-25T12:00:00Z"
    keywords:
      - "import"
      - "module not found"
      - "circular dependency"
    summary: "Circular imports cause 'module not found' errors"
    insight: |
      When module A imports from module B and module B imports from module A,
      Python raises ImportError. Solution: Move shared code to a third module
      or use lazy imports.
    example: |
      # Bad: circular import
      # utils.py imports from models.py
      # models.py imports from utils.py

      # Good: extract shared code
      # shared.py has common functions
      # both utils.py and models.py import from shared.py
    confidence: 0.9
    times_used: 3

何时使用此技能

自动使用(通过钩子):

  • 会话停止时:从记录中提取经验
  • 会话开始时:根据提示词关键词注入相关经验

手动使用:

  • 查看或管理经验时
  • 调试并希望回忆过往解决方案时
  • 入职时了解项目特定模式

经验提取流程

步骤 1:分析会话记录

会话停止时,扫描以下内容:

  1. 错误模式:遇到的错误及其解决方式
  2. 工作流见解:效果良好或不佳的步骤
  3. 工具发现:有效使用工具的新方法
  4. 架构决策:设计选择及其依据
  5. 调试策略:根本原因分析模式
步骤 2:提取结构化经验

对于每项重要见解:

  1. 根据类别和时间戳生成唯一 ID
  2. 从上下文中提取关键词(3-5 个相关词)
  3. 编写一句话摘要
  4. 撰写详细见解并附上说明
  5. 如适用,加入代码示例
  6. 设定置信度分数(0.5-1.0)
步骤 3:与现有经验合并
  1. 使用关键词重叠检查是否存在重复经验
  2. 如果存在相似经验(关键词匹配度 >60%),则更新置信度
  3. 否则,将新经验追加到相应类别的文件中

经验注入流程

步骤 1:提取任务关键词

从会话开始提示词中提取:

  • 技术术语(语言、框架、工具)
  • 问题指示词(error、fix、debug、implement)
  • 领域关键词(api、database、auth 等)
步骤 2:查找相关经验

对于每个学习类别:

  1. 从 YAML 加载经验
  2. 计算与任务的关键词重叠度
  3. 按 overlap_score * confidence * recency_weight 排序
  4. 选出最相关的 3 条经验
步骤 3:注入上下文

将相关经验格式化为上下文:

## Past Learnings Relevant to This Task

### [Category]: [Summary]

## [Insight with example if helpful]

使用示例

示例 1:自动提取
Session: Debugging circular import issue in Neo4j module
Duration: 45 minutes
Resolution: Moved shared types to separate file

Extracted Learning:
- Category: errors
- Keywords: [import, circular, neo4j, type]
- Summary: Circular imports in Neo4j types cause ImportError
- Insight: When Neo4jNode imports from connection.py which imports
  Node types, move types to separate types.py module
- Example: types.py with dataclasses, connection.py imports from types.py
示例 2:自动注入
Session Start Prompt: "Fix the import error in the memory module"

Matched Learnings:
1. errors/err-001: "Circular imports cause 'module not found' errors" (85% match)
2. debugging/dbg-003: "Use `python -c` to isolate import issues" (60% match)

Injected Context:
## Past Learnings Relevant to This Task

### Errors: Circular imports cause 'module not found' errors
When module A imports from module B and B imports from A, Python raises
ImportError. Solution: Move shared code to a third module or use lazy imports.
---
示例 3:手动管理
User: Show me what I've learned about testing

Claude (using this skill):
1. Reads .claude/data/learnings/workflows.yaml
2. Filters learnings with keywords containing "test"
3. Displays formatted list with summaries and examples

关键词匹配算法

简单而有效的匹配:

def calculate_relevance(task_keywords: set, learning_keywords: set) -> float:
    """Calculate relevance score between 0 and 1."""
    if not task_keywords or not learning_keywords:
        return 0.0

    # Count overlapping keywords
    overlap = task_keywords & learning_keywords

    # Score: overlap / min(task, learning) to not penalize short queries
    return len(overlap) / min(len(task_keywords), len(learning_keywords))

集成点

与 Stop 钩子集成

Stop 钩子可以调用此技能来提取经验:

  1. 解析记录中的重要事件
  2. 识别错误模式、解决方案和见解
  3. 存储到相应类别的 YAML 文件中
  4. 记录提取摘要
与 Session Start 钩子集成

Session Start 钩子可以注入相关经验:

  1. 解析初始提示词并提取关键词
  2. 在各类别中查找匹配的经验
  3. 格式化为注入的上下文
  4. 纳入会话上下文
使用 /amplihack:learnings 命令

学习管理命令接口:

  • /amplihack:learnings show [category] - 显示学习内容
  • /amplihack:learnings search <query> - 搜索所有类别
  • /amplihack:learnings add - 手动添加一条学习内容
  • /amplihack:learnings stats - 显示学习统计信息

质量指南

何时提取

在以下情况下提取学习内容:

  • 解决耗时超过 10 分钟的问题
  • 发现非显而易见的工具行为
  • 找到适用范围广泛的模式
  • 做出存在权衡的架构决策
何时不提取

在以下情况下跳过提取:

  • 问题只是简单的拼写错误或语法错误
  • 解决方案已存在于 DISCOVERIES.md 或 PATTERNS.md 中
  • 见解过于特定于某个项目,无法复用
  • 置信度较低(<0.5)
学习内容质量检查清单
  • 关键词具体且便于搜索
  • 摘要是一句清晰的话
  • 见解解释了原因,而不只是内容
  • 示例精简且可运行
  • 置信度反映实际确定程度

文件位置

.claude/
  data/
    learnings/
      errors.yaml        # Error patterns and solutions
      workflows.yaml     # Workflow insights
      tools.yaml         # Tool usage patterns
      architecture.yaml  # Design decisions
      debugging.yaml     # Debugging strategies
      _stats.yaml        # Usage statistics (auto-generated)

与现有系统的比较

功能DISCOVERIES.mdPATTERNS.md会话学习
格式MarkdownMarkdownYAML
受众人类人类Agent + 人类
存储单个文件单个文件按类别划分的文件
匹配手动读取手动读取基于关键词的自动匹配
注入手动手动自动
范围重要发现已验证的模式任何有用的见解

互补使用:

  • 使用 DISCOVERIES.md 记录重要且有充分文档说明的发现
  • 使用 PATTERNS.md 记录已验证、可复用且包含代码的模式
  • 使用 Session Learning 记录有助于后续会话的简短见解

错误处理

YAML 解析错误

如果学习文件损坏或无效:

import yaml
from pathlib import Path

def safe_load_learnings(filepath: Path) -> dict:
    """Load learnings with graceful error handling."""
    try:
        content = filepath.read_text()
        data = yaml.safe_load(content)
        if not isinstance(data, dict) or "learnings" not in data:
            print(f"Warning: Invalid structure in {filepath}, using empty learnings")
            return {"category": filepath.stem, "learnings": []}
        return data
    except yaml.YAMLError as e:
        print(f"Warning: YAML error in {filepath}: {e}")
        # Create backup before recovery
        backup = filepath.with_suffix(".yaml.bak")
        filepath.rename(backup)
        print(f"Backed up corrupted file to {backup}")
        return {"category": filepath.stem, "learnings": []}
    except Exception as e:
        print(f"Warning: Could not read {filepath}: {e}")
        return {"category": filepath.stem, "learnings": []}
文件缺失

如果学习目录不存在,则创建该目录:

def ensure_learnings_directory():
    """Create learnings directory and empty files if missing."""
    learnings_dir = Path(".claude/data/learnings")
    learnings_dir.mkdir(parents=True, exist_ok=True)

    categories = ["errors", "workflows", "tools", "architecture", "debugging"]
    for cat in categories:
        filepath = learnings_dir / f"{cat}.yaml"
        if not filepath.exists():
            filepath.write_text(f"category: {cat}\nlearnings: []\n")
故障保护原则

学习系统遵循故障保护设计:

  • 绝不阻塞会话启动:如果注入失败,会话仍正常继续
  • 绝不阻塞会话停止:如果提取失败,会话仍正常结束
  • 记录警告但继续运行:错误会被记录,而不会抛出
  • 修改前创建备份:保留损坏的文件

钩子集成

Stop 钩子:学习提取

将学习提取添加到 Stop 钩子中:

# .claude/tools/amplihack/hooks/stop_hook.py

async def extract_session_learnings(transcript: str, session_id: str):
    """Extract learnings from session transcript at stop."""
    from pathlib import Path
    import yaml
    from datetime import datetime

    # Only extract if session was substantive (not just a quick question)
    if len(transcript) < 1000:
        return

    # Use Claude to extract insights (simplified example)
    extraction_prompt = f"""
    Analyze this session transcript and extract any reusable learnings.

    Categories:
    - errors: Error patterns and solutions
    - workflows: Process improvements
    - tools: Tool usage insights
    - architecture: Design decisions
    - debugging: Debug strategies

    For each learning, provide:
    - category (one of the above)
    - keywords (3-5 searchable terms)
    - summary (one sentence)
    - insight (detailed explanation)
    - example (code if applicable)
    - confidence (0.5-1.0)

    Transcript:
    {transcript[:5000]}  # Truncate for token limits
    """

    # ... call Claude to extract ...
    # ... parse response and add to appropriate YAML files ...

def on_stop(session_data: dict):
    """Stop hook entry point."""
    # ... other stop hook logic ...

    # Extract learnings (non-blocking)
    try:
        import asyncio
        asyncio.create_task(
            extract_session_learnings(
                session_data.get("transcript", ""),
                session_data.get("session_id", "")
            )
        )
    except Exception as e:
        print(f"Learning extraction failed (non-blocking): {e}")
会话启动钩子:学习注入

将学习注入添加到会话启动钩子中:

# .claude/tools/amplihack/hooks/session_start_hook.py

def inject_relevant_learnings(initial_prompt: str) -> str:
    """Find and format relevant learnings for injection."""
    from pathlib import Path
    import yaml

    learnings_dir = Path(".claude/data/learnings")
    if not learnings_dir.exists():
        return ""

    # Extract keywords from prompt
    prompt_lower = initial_prompt.lower()
    task_keywords = set()
    for word in prompt_lower.split():
        if len(word) > 3:  # Skip short words
            task_keywords.add(word.strip(".,!?"))

    # Find matching learnings
    matches = []
    for yaml_file in learnings_dir.glob("*.yaml"):
        if yaml_file.name.startswith("_"):
            continue  # Skip _stats.yaml

        try:
            data = yaml.safe_load(yaml_file.read_text())
            for learning in data.get("learnings", []):
                learning_keywords = set(k.lower() for k in learning.get("keywords", []))
                overlap = task_keywords & learning_keywords
                if overlap:
                    score = len(overlap) * learning.get("confidence", 0.5)
                    matches.append((score, learning))
        except Exception:
            continue

    # Return top 3 matches
    matches.sort(key=lambda x: x[0], reverse=True)
    if not matches:
        return ""

    context = "## Past Learnings Relevant to This Task\n\n"
    for score, learning in matches[:3]:
        context += f"### {learning.get('summary', 'Insight')}\n"
        context += f"{learning.get('insight', '')}\n\n"

    return context

def on_session_start(session_data: dict) -> dict:
    """Session start hook entry point."""
    initial_prompt = session_data.get("prompt", "")

    # Inject relevant learnings
    try:
        learning_context = inject_relevant_learnings(initial_prompt)
        if learning_context:
            session_data["injected_context"] = learning_context
    except Exception as e:
        print(f"Learning injection failed (non-blocking): {e}")

    return session_data

局限性

  1. 关键词匹配并不完美 - 可能遗漏相关学习内容,也可能匹配到不相关的内容
  2. 不具备语义理解能力 - 无法匹配概念相似但措辞不同的见解
  3. 存储在本地 - 学习内容不会在不同机器间同步
  4. 需要手动清理 - 应定期检查过时或错误的学习内容

未来改进

如有需要,可考虑:

  • 使用基于嵌入的相似度匹配,以改善匹配效果
  • 通过 git 在不同机器间同步
  • 随时间推移自动降低置信度
  • 与 Neo4j 集成,建立基于图的学习关系

成功指标

跟踪效果:

  • 注入率:注入了相关学习内容的会话占比
  • 使用率:注入的学习内容帮助解决问题的频率
  • 增长率:每周新增的学习内容数量
  • 质量:用户对学习内容相关性的反馈

评论

支持 Markdown