Agent 最大的问题不是失败,而是重复失败

做 AI Agent 工程时,我越来越明显地感觉到一个问题:一次工具调用失败并不可怕,可怕的是下一次还用同样的方式失败。

比如文件路径没确认就开始改代码,MCP 工具 schema 没读就直接调用,命令失败后又原样重试,计划已经走不通却没有及时 replan。这些错误并不是单点 bug,而是“经验没有沉淀下来”的结果。

所以我在 SkillLite 里补了一层项目经验库:不是把所有聊天记录存起来,而是把有复盘价值的内容整理成项目内 Markdown Wiki。它的目标很直接:让 Agent 的失败经验可以被人审查、被项目保存、被下一次查询到。

项目地址:SkillLite

这篇文章的核心问题

这篇文章讨论的是一个工程问题:

如何让 AI Agent 在项目里形成可维护的经验闭环?

SkillLite 的做法可以概括成六句话:

  • 项目知识放在 .skilllite/wiki/
  • 原始资料先进入 raw/
  • 编译后的知识进入 wiki/
  • 查询前自动判断内容是否过期。
  • 遇到 replan 或连续工具失败时,提示用户是否记录经验。
  • 经验写入结构化 lesson,而不是完整聊天记录。

先看最终使用方式

用户不需要理解内部实现,也可以直接使用这组命令:

# 初始化项目 Wiki
skilllite wiki init --workspace .

# 把项目资料写入 raw,并默认生成可查询文章
skilllite wiki ingest docs/agent-debug-note.md --workspace .

# 查询项目经验
skilllite wiki query "tool failure retry strategy" --workspace .

# 查看 raw 和 compiled wiki 是否一致
skilllite wiki status --workspace .

# 校验 Wiki 结构和链接
skilllite wiki lint --workspace .

如果一次 chat 或桌面 Assistant 对话里出现了 replan,或者工具连续失败达到阈值,对话结束后会提示用户是否记录经验。用户确认后才会执行:

skilllite wiki record-lesson \
  --workspace . \
  --title "Conversation lesson: consecutive tool failures" \
  --trigger consecutive_tool_failures \
  --summary "连续工具失败后应先检查路径、schema 或命令输出" \
  --body "## What Happened..."

这里最关键的不是命令数量,而是边界:自动提示,但不自动污染项目知识库。

流程图:从失败信号到项目经验

顺利

出现 replan

连续工具失败

重复工具失败

Agent 执行任务

执行是否顺利

正常结束

生成 WikiUpdateSuggestion

整理标题、触发原因、错误摘要、建议 lesson

用户是否确认写入

不修改 Wiki

skilllite wiki record-lesson

写入 .skilllite/wiki/raw

自动 compile

生成 .skilllite/wiki/wiki 文章

后续 query 可检索

这个流程有一个设计原则:Agent 负责发现“值得记录的信号”,用户负责确认“是否进入项目知识库”。

为什么不直接存完整聊天记录?

完整聊天记录通常不是好知识。它里面有大量临时上下文、试错过程、无关输出,甚至可能包含不该进入项目仓库的信息。

对下一次 Agent 执行真正有帮助的,通常只有四件事:

需要沉淀的信息 作用
发生了什么 让后来者理解场景
根因是什么 避免再次误判
应该怎么优化 给 Agent 或开发者新的执行策略
下次检查什么 把经验变成 checklist

所以 SkillLite 的 lesson 不保存聊天流水,而是保存经验模板:

## What Happened

这次发生了什么。

## Root Cause

确认后的根因是什么。

## Optimization

下次应该怎么调整执行方式。

## Next Time

- 先检查路径、命令输出、schema 或依赖。
- 不要重复同一个失败调用。
- 修复验证后更新这条 lesson。

这会让 Wiki 更像“项目经验手册”,而不是“聊天记录垃圾桶”。

代码看点一:什么时候提示记录经验?

SkillLite 并不会每轮对话都建议写 Wiki。触发条件被限制在几类高价值信号上:

pub const WIKI_CONSECUTIVE_TOOL_FAILURE_THRESHOLD: usize = 3;

pub enum WikiUpdateTrigger {
    Replan,
    ConsecutiveToolFailures,
    RepeatedToolFailures,
}

判断逻辑也比较克制:

let trigger = if feedback.replans > 0 {
    WikiUpdateTrigger::Replan
} else if feedback.max_consecutive_tool_failures >= WIKI_CONSECUTIVE_TOOL_FAILURE_THRESHOLD {
    WikiUpdateTrigger::ConsecutiveToolFailures
} else if feedback.max_repeated_tool_failures >= WIKI_CONSECUTIVE_TOOL_FAILURE_THRESHOLD {
    WikiUpdateTrigger::RepeatedToolFailures
} else {
    return None;
};

普通成功对话不需要额外打扰用户。真正值得记录的是那些暴露出“执行策略问题”的对话。

代码看点二:建议 payload 里有什么?

当系统判断这轮对话值得复盘,会构造一个结构化建议:

pub struct WikiUpdateSuggestion {
    pub trigger: WikiUpdateTrigger,
    pub replan_count: usize,
    pub failed_tool_count: usize,
    pub failed_tools: Vec<String>,
    pub error_summaries: Vec<String>,
    pub proposed_title: String,
    pub proposed_lesson: String,
}

这里没有直接写文件,而是生成一份“建议”。这份建议可以被 CLI chat 使用,也可以通过 RPC done 事件交给桌面 Assistant。

换句话说,Agent 层只输出结构化信号,真正的写入动作留给用户确认后的命令层。

代码看点三:record-lesson 如何避免写成散文?

用户传入的 body 可能很短,也可能格式不完整。为了保证 Wiki 里的 lesson 可用,命令层会补齐模板:

fn lesson_body_with_template(summary: &str, trigger: &str, body: &str) -> String {
    let trimmed = body.trim();
    if trimmed.contains("## What Happened")
        && trimmed.contains("## Root Cause")
        && trimmed.contains("## Optimization")
        && trimmed.contains("## Next Time")
    {
        return trimmed.to_string();
    }

    let what_happened = if trimmed.is_empty() {
        summary.trim()
    } else {
        trimmed
    };

    format!(
        "## What Happened\n\n{}\n\nTrigger: `{}`.\n\n\
## Root Cause\n\nDescribe the confirmed reason this run needed replanning or hit repeated tool failures.\n\n\
## Optimization\n\nDocument the improved approach that avoids repeating the same failed path.\n\n\
## Next Time\n\n- Check the relevant file path, command output, schema, or dependency first.\n- Change the approach before retrying the same tool call.\n- Keep this lesson updated after the successful fix is confirmed.\n",
        what_happened,
        trigger.trim()
    )
}

这个函数的作用不是美化 Markdown,而是保证每条经验都能回答同一组问题。统一结构以后,后续查询、总结、人工 review 都更稳定。

Wiki 怎么保持新鲜?

只把资料写进 Markdown 还不够。项目资料会变,编译后的文章也会过期。

SkillLite 没有用文件时间戳判断 freshness,而是给 raw content 计算 fingerprint:

fn content_fingerprint(content: &str) -> String {
    let mut hash = 0xcbf29ce484222325u64;
    for byte in content.as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(0x100000001b3);
    }
    format!("fnv1a64:{hash:016x}")
}

编译文章时会把 fingerprint 写到 frontmatter:

sources: [raw/debug-note.md]
source_fingerprints: [raw/debug-note.md=fnv1a64:...]

后续 wiki statuswiki query 会重新计算 raw 的 fingerprint。如果对不上,就说明 compiled article 已经过期。

查询前自动刷新:不用 watcher 也能动态维护

很多知识库一提“动态更新”,就会想到后台 watcher。但 watcher 会带来额外复杂度:进程管理、跨平台差异、误触发、资源占用、写入时机不可控。

SkillLite 采用了更简单的策略:查询前检查。

if !no_compile {
    let freshness = wiki_freshness(&wiki)?;
    if freshness.needs_refresh() {
        let compiled = compile_raw_sources(&wiki)?;
        rebuild_indexes(&wiki)?;
        append_log(
            &wiki,
            "auto-compile",
            &format!("Refreshed {} raw source(s) before query", compiled),
        )?;
    }
}

这样做的好处是:

  • 不需要常驻进程。
  • 不会在用户无感知时不断写文件。
  • 每次查询都能尽量拿到最新结果。
  • 需要保留旧状态时,可以用 --no-compile 跳过刷新。

为什么主存储选 Markdown,而不是 SQLite?

SQLite 很适合做索引,但这次我不想让它成为 Repo Wiki 的事实来源。

项目经验库首先要服务团队协作,而 Markdown 在这方面更自然:

维度 Markdown SQLite
人能不能直接读 可以 不方便
Code Review 是否友好 友好 不直观
Git 冲突是否可处理 可手动处理 通常更麻烦
LLM 是否容易读取 容易 需要查询层
适合作为什么 事实来源 派生索引或缓存

所以当前设计是:Markdown 是主存储,未来如果需要向量召回或复杂检索,可以在 Markdown 之上再生成索引。

CSDN 发布建议

这篇文章可以直接用下面这个标题:

别再让 AI Agent 重复踩坑:SkillLite 用 Repo Wiki 沉淀项目经验

如果想更偏搜索,可以用:

AI Agent 项目经验库实践:SkillLite 的 LLM Wiki 自动维护方案

如果想更偏工程复盘,可以用:

从工具失败到经验沉淀:SkillLite 的 Repo Wiki 设计复盘

摘要建议控制在 120 字左右:

本文介绍 SkillLite 如何用 Markdown-only Repo Wiki 沉淀 AI Agent 的项目经验:通过 raw/wiki 分层、source fingerprint、查询前自动刷新,以及 replan/连续工具失败后的用户确认式 lesson 记录,让 Agent 的失败经验变成可审查、可复用的项目知识。

FAQ

这套机制是自动写 Wiki 吗?

不是静默自动写。ingestquery 会触发自动编译或刷新;对话失败经验只会生成建议,用户确认后才写入。

为什么连续工具失败阈值是 3?

一次失败可能是偶发,两次失败可能是参数问题,连续三次通常说明策略或前置检查有问题,值得提示复盘。

record-lesson 会保存完整对话吗?

不会。它保存结构化经验,包括发生了什么、根因、优化方式和下次检查项。

.skilllite/wiki/ 可以提交到 Git 吗?

它是项目级 Markdown 知识库,具备可提交和可审查的条件。是否提交取决于团队策略以及内容是否包含敏感信息。

这和 memory 有什么区别?

memory 更偏用户级、跨项目、私有长期记忆;Repo Wiki 更偏项目级、可读、可审查、可协作的知识。

总结

SkillLite 这次做的不是“再加一个文档目录”,而是给 AI Agent 增加一个项目经验闭环:

  1. 项目资料进入 .skilllite/wiki/raw/
  2. 编译后生成可查询的 Wiki 文章。
  3. fingerprint 判断知识是否过期。
  4. query 前自动刷新 stale 内容。
  5. replan 和连续工具失败触发经验记录建议。
  6. 用户确认后写入结构化 lesson。

对 Agent 系统来说,真正有价值的不是把所有上下文都保存下来,而是把失败中提炼出的经验留在项目里。这样下一次执行时,Agent 不只是“重新尝试”,而是能带着项目已经验证过的经验继续工作。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐