源码拆解|长期记忆与 Skills,nanobot 如何把上下文做成可收敛的 Prompt

这是 nanobot 源码拆解系列文章的第 3 篇。
我见过一些智能体,对话拉长一点就开始胡言乱语。再加上工具调用,不是跑偏,就是成本爆炸。
这里的问题不在模型,而在上下文(context)管理。上下文本质上是一种资源。它有限、昂贵,还得有人管。
nanobot 号称 ultra-lightweight(超轻量)。轻量不只是少写点代码,而是要承认上下文有限,然后逼系统做收敛。
这篇我想看看 nanobot 如何解决两个关键问题。
-
长期记忆(memory),怎么把对话沉淀成可检索、可持续的状态
-
集成 Skills,怎么把能力说明书纳入 context,又不把 prompt 撑爆
PART 01|两层记忆,MEMORY.md + HISTORY.md
在 nanobot/agent/memory.py 里,MemoryStore 用两份文件表达长期状态。
class MemoryStore:"""Two-layer memory: MEMORY.md (long-term facts) + HISTORY.md (grep-searchable log)."""def __init__(self, workspace: Path):self.memory_dir = ensure_dir(workspace / "memory")self.memory_file = self.memory_dir / "MEMORY.md"self.history_file = self.memory_dir / "HISTORY.md"
这两个文件的角色,我给一个直观翻译。
-
MEMORY.md,事实表(facts table)。偏结构化,尽量写长期有效的用户信息、偏好、项目背景
-
HISTORY.md,流水账(audit log)。偏可检索,强调 grep-searchable
nanobot 把重点放在,先把最关键的两件事补齐,可持续存储,可追溯回放。
PART 02|记忆不会实时写入,走的是窗口触发 + 后台收敛
如果你在 AgentLoop._process_message() 里继续往下看,会看到一个触发条件。
if len(session.messages) > self.memory_window:asyncio.create_task(self._consolidate_memory(session))
它的含义很直白,对话超过 memory_window(默认 50)时,启动一条后台任务去做 consolidation。
为什么要后台?因为写记忆通常要做总结、提炼,甚至要再调一次模型。如果你把它写在主链路里,用户体验会被拖垮。
PART 03|consolidation 的核心,让模型输出严格 JSON
在 nanobot/agent/loop.py 的 _consolidate_memory() 里,我看到 nanobot 用了一个非常务实的办法,让模型扮演记忆整理员,把旧对话压缩成两块内容。
-
history_entry,2-5 句摘要,带时间戳,便于 grep
-
memory_update,更新后的长期记忆全文
它的 prompt 是这样写的(我截取关键段落)。
prompt = f"""You are a memory consolidation agent. Process this conversation and return a JSON object with exactly two keys:1. "history_entry": A paragraph (2-5 sentences) summarizing the key events/decisions/topics. Start with a timestamp like [YYYY-MM-DD HH:MM]. Include enough detail to be useful when found by grep search later.2. "memory_update": The updated long-term memory content. Add any new facts: user location, preferences, personal info, habits, project context, technical decisions, tools/services used. If nothing new, return the existing content unchanged.## Current Long-term Memory{current_memory or "(empty)"}## Conversation to Process{conversation}Respond with ONLY valid JSON, no markdown fences."""
这段设计有两个值得学习的点。
-
把输出约束写死,必须是 JSON,必须只有两个 key。你不在 prompt 里约束输出格式,后面就要在工程里写更多修复逻辑。
-
把当前记忆作为输入,它让更新成为增量过程,不需要每次重新生成。
此外它还引入了 json_repair(同样在 nanobot/agent/loop.py 里),用来应对模型偶尔输出不规范 JSON 的情况。
result = json_repair.loads(text)
你可以要求模型规矩,但也要承认模型会犯错。
PART 04|/new 的设计,先清 session,再归档记忆
/new 这个命令其实是一条开新会话的产品功能。但 nanobot 把它和记忆归档绑在一起了(在 nanobot/agent/loop.py 里能看到这段逻辑)。
它的流程是这样的。
-
先复制旧 messages
-
清空 session
-
异步 consolidation,把旧对话全部归档到 MEMORY/HISTORY
messages_to_archive = session.messages.copy()session.clear()asyncio.create_task(_consolidate_and_cleanup())
这个设计很实用。用户不会因为记忆归档很慢而卡住,同时系统也能在后台把历史沉淀下来。
PART 05|Skills,更像写给模型看的说明书
nanobot 很好地集成了 Skills。一份 SKILL.md,教大模型怎么做事、怎么用工具、有哪些边界。
在 nanobot/agent/skills.py 里,SkillsLoader 的定义就写得很直白。
class SkillsLoader:"""Skills are markdown files (SKILL.md) that teach the agent how to usespecific tools or perform certain tasks."""
它支持两类来源。
-
workspace 的 skills(优先级最高)
-
内置 skills(随项目发布)
这对产品化二次开发很关键。你可以把公司的业务技能放到 workspace,不用去改 nanobot 源码。
PART 06|把 Skills 纳入 context,渐进加载
Skills 最聪明的设计之一,是渐进加载(progressive loading)。
在 nanobot/agent/context.py 的 build_system_prompt() 里,它把技能分成两段。
-
always-loaded skills,直接把全文塞进 prompt(适合少量、关键、必备的技能)
-
available skills summary,只给摘要 + 路径,告诉模型要用就 read_file 去读
这段渐进加载的实现细节分别落在 nanobot/agent/skills.py 里(always skills 和 skills summary 两段逻辑在同一个文件中)。
always_skills = self.skills.get_always_skills()always_content = self.skills.load_skills_for_context(always_skills)skills_summary = self.skills.build_skills_summary()parts.append(f"""# SkillsThe following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.{skills_summary}""")
这段话其实是在把 prompt 成本控制写进系统里。
-
重要的技能常驻(always)
-
其余技能按需加载(on-demand)
PART 07|技能可用性,依赖检查写进元数据
SkillsLoader 还有一个很实用的细节。它会检查 skill 的依赖(bin / env),并在 summary 里标注 available=true/false(同样在 nanobot/agent/skills.py 里)。
def _check_requirements(self, skill_meta: dict) -> bool:requires = skill_meta.get("requires", {})for b in requires.get("bins", []):if not shutil.which(b):return Falsefor env in requires.get("env", []):if not os.environ.get(env):return Falsereturn True
这就是研究代码里少见的产品思维。不要等用户踩坑,再告诉他你缺依赖。先把可用性标出来,让系统自己学会规避不可用技能。
PART 08|如果你要上生产,记忆与 skills 最容易踩的 6 个坑
nanobot 的设计很适合做基线。但生产化时,这两块需要重点加固。
-
隐私与脱敏,写入 MEMORY/HISTORY 前做 PII 检测与 redaction,也要避免把 token、cookie、账号信息写进去
-
可控的记忆格式,MEMORY.md 很灵活,但企业里更需要 schema(JSON/YAML),或至少约束段落模板
-
回写的冲突处理,并发 consolidation 时可能覆盖 MEMORY.md,需要锁或版本号
-
技能的供应链安全,从技能市场安装技能很爽,但生产里必须有签名校验、allowlist、审计
-
可观测性,每次 consolidation 写了什么、删了什么、为什么写,得能追溯
这些点我会在本系列最后一篇文章里给出落地路径。
PART 09|本文小节
做记忆和技能,本质上是在做上下文治理。你治理得好,模型就像一个能长期协作的同事。你治理得不好,模型就像一个每天重启的大脑。
nanobot 的好处是,它让你在几千行代码里看到一条清晰的收敛路线,窗口触发、后台总结、双文件沉淀、渐进加载技能。
下一篇,我想把视角移到另外一块更硬的地方,工具系统与子 agent(subagent)。
以及,怎么把这一整套东西改造成你自己的 AI 产品。
夜雨聆风
