agent-archive
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-archivesearch all my sessions for anything about database migrations"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-archive
跨 Agent 的项目级会话档案馆。
给定一个项目路径,把所有 Agent 在这个项目里发生过的会话,按时间合并成一条线。
许可证:MIT(见 LICENSE)。
research/reference-src/下是第三方项目的源码副本(MIT), 版权与许可声明见 NOTICE.md。
为什么只做这一件事
跨 Agent 的记忆、蒸馏、交接摘要都建立在"你首先得知道发生过什么"之上。 而各家 Agent 的会话存储格式互不兼容、路径各不相同,这件事没有标准答案, 只能逐家适配。
本工具只做三件事:扫描 → 归一 → 时间线。不做蒸馏,不做向量检索, 不往 Agent 里注入任何东西。它先把事实记录下来,其余都是可选视图。
在此之上还有一层环境层(env):识别本机每个 Agent 配了哪些 MCP 服务、
装了哪些技能、有哪些记忆。会话层回答"聊过什么",环境层回答"会什么"——
两层都只提供事实与出处,不替任何 Agent 做决定。
四种看法,同一份数据:终端里的 projects/timeline/cat,
浏览器里的 serve,导出成 Markdown 的 export,
以及给其他 Agent 用的 /api/* 与 MCP server。
定位:一个 AI 能读得懂的档案馆,而不是一个会读东西的 AI。 事实层(扫描、归一、时间线)是确定性的、可复现的、零成本的; 总结、归纳、蒸馏交给用户自己的模型——通过 MCP,不通过我们的 API Key。
Related MCP server: kairan
用法
# 本机有哪些项目(自动过滤掉家目录、桌面、临时目录)
node src/cli.js projects
# 某个项目的完整时间线
node src/cli.js timeline E:\softpi-shop
# 不分天,平铺
node src/cli.js timeline . --flat
# 这个项目在各 Agent 里的分布
node src/cli.js agents E:\softpi-shop
# 扫描统计与缓存位置
node src/cli.js stats
# 采集体检:覆盖率、缺口、正文可读性
node src/cli.js doctor
# 模型调用成本(按 Agent / 模型 / 会话)
node src/cli.js calls
node src/cli.js calls --by session
# 扫描归档里的明文凭证(只报告,掩码输出)
node src/cli.js secrets
# 配置模型(用户自己的 API Key)
node src/cli.js llm --key sk-xxx --model deepseek-chat --base-url https://api.deepseek.com
# 生成项目摘要(默认只估算成本,加 --run 才真的调模型)
node src/cli.js digest E:\softpi-shop
node src/cli.js digest E:\softpi-shop --run --out digest.md
# 审计/刷新已过期的摘要(默认只审计,加 --run 才重算)
node src/cli.js refresh
node src/cli.js refresh --run
# 建全文索引(第一次约一分钟,之后只处理变过的会话)
node src/cli.js index
node src/cli.js index --force # 全部重建
# 全文搜索
node src/cli.js search 迁移
node src/cli.js search 缓存 --agent zcode --limit 5
# 导出 Markdown
node src/cli.js export E:\softpi-shop # 项目时间线
node src/cli.js export E:\softpi-shop --bundle # 该项目全部会话正文
node src/cli.js export 01a11a93 --session # 单场会话
node src/cli.js export . --out archive.md # 写文件而不是 stdout
# 读一场会话的正文
node src/cli.js cat --project . # 该项目最近一场
node src/cli.js cat 1d551b54 # 按 id 前缀
node src/cli.js cat 019a30ad --tools # 展开工具调用与输出
# 起本地网页查看器(浏览器打开 http://127.0.0.1:7788)
node src/cli.js serve
node src/cli.js serve --port 8000通用选项:--json 输出机器可读结果,--no-cache 强制全量重扫。
搜索:为什么需要索引,以及中文怎么切
全量读一遍 863 场会话要 33 秒、55 M 字符。每次搜索实时扫不可行,
所以正文先在本地落一份 SQLite FTS5 索引(~/.agent-archive/index/)。
中文分词是这里唯一有技术含量的地方。 FTS5 自带的 unicode61
不切中文——整段中文是一个 token,搜「缓存」永远搜不到;trigram
要求查询至少 3 个字符,「迁移」「缓存」这类两字词同样落空。
所以自己切:连续汉字段切成长度 2 的滑窗(数据库迁移 →
数据 据库 库迁 迁移),英文和标点原样交给 unicode61。
第一次的写法是「原文 + 全量二元组」,实测索引文本膨胀 4 倍 (120 场就 34 M 字符),建索引慢到跑不完。改成只存 token 序列后 降到 1.95 倍,863 场 56 秒建完,索引 179 MB。
摘要不用 FTS5 的 snippet():它从索引文本里取,会把二元组一起抓出来,
摘录变成「约 约束 束:」这样的碎片。改为回原始正文里定位关键词取上下文。
只索引人说的话和工具调用摘要,不索引完整工具输出——那些多是日志和 源码,放进去既占空间又把真正的对话信号淹没。
合并:一场对话可能散在多个文件里
Codex 的续接会话会为同一 session id 产出多个 rollout 文件
(rollout-<时间>-<父uuid>.jsonl.zst 和
rollout-<时间>-<父uuid>_<子uuid>.jsonl),时间上首尾相接。
registry.js 按 agent|id 合并它们:时间取包络,正文读取时按
顺序拼接。
不合并的后果很直观——合并前本机 2225 个「会话」里只有 904 个唯一 id, 同一场对话在时间线里重复出现几十次。合并后是 863 场。
另外 Codex 的子代理会话(source: {subagent: {...}})整条跳过:
它们是父会话内部的一步,session_id 指向父会话,id 才是自己的。
用 session_id 当标识会让几十个子代理会话挤成同一条。
导出:三种粒度
命令 | 内容 | 用途 |
| 会话清单 + 按天时间线 | 看这个项目经历了什么 |
| 单场对话完整正文 | 贴进 issue、给别的模型读 |
| 该项目所有会话正文合成一份 | 完整交接材料 |
三种都默认把正文包成 > 引用块——这样原文里偶然出现的 # 结论
不会把导出文档的大纲撕开。工具调用压成一行摘要,加 --tools
展开入参与输出。
网页上对应三个按钮,走同一个 /export 接口,所以浏览器里看到的
和导出的永远是同一份内容(两者都调用 src/export.js)。
噪声剥离是导出质量的前提
导出时发现 Codex 会话有 78% 的正文是注入内容:<app-context>(桌面端
能力说明)、<recommended_plugins>(插件清单)之类。它们以
role: "developer" 的消息出现——这个角色名容易被忽略,因为直觉上
只想着 user/assistant。
修法是:developer 消息整条归为系统内容,并且和 user 消息一样过
stripBlocks;剥空了就丢弃。同一场会话导出从 118 KB 降到 19.5 KB。
stripBlocks 也从"逐个列标签名"改成按形状剥(成对标签、自闭合标签、
无闭合时剥到末尾)。实测 904 场里有 273 场标题是这类标签,
硬编码名单永远追不上 Codex 加功能的速度。
接给别的 Agent:MCP server
这是这个产品真正的用法——不用自己接模型。档案馆包成一个 MCP server,用户已经在用的 Agent(Claude Code、Codex、ZCode、Cursor、 OpenCode、Kiro……)自己就能来读。
node src/cli.js mcp # 打印可粘贴的配置
node src/cli.js mcp --json # 只要配置 JSON粘进对应 Agent 的 MCP 配置即可(各家的位置不同,命令会列出来)。
零依赖实现。 MCP 的 stdio 传输就是「stdin/stdout 上一行一条 JSON-RPC」,手写足够,不需要引入任何 npm 包。
九个工具
工具 | 作用 |
| 有哪些项目、多少会话、哪些 Agent |
| 某项目的会话清单(只有元数据) |
| 全文搜索,返回命中处摘录 |
| 读已生成的项目摘要;没有就提示怎么生成,不现编 |
| 读某场会话已有的摘要,用来判断值不值得深挖 |
| 读正文,分段,默认 8000 字 |
| 采集质量、索引规模、已有摘要数量 |
| 导出 Markdown |
| 环境层:各 Agent 的 MCP / 技能 / 使用率 / 记忆( |
| 调用层:每场会话/每个 Agent 的 token、缓存命中、费用 |
read_digest 和 read_session_summary 是视图层的入口。它们只读已有的
摘要,不会替你调模型——生成要花钱,必须由用户在终端显式执行。
推荐给 Agent 的调用顺序也写在 initialize 的 instructions 里:
list_projects → read_digest(有现成交接材料吗)→
没有就 search_sessions / project_timeline 定位 → read_session 读正文翻 984 场会话的成本,因此可以降一个数量级。
为什么要默认截断
一场会话常有十几万字(约 5 万 token)。如果 read_session 直接返回
全文,Agent 手滑点一次就能烧掉大量上下文额度,而且它往往只需要其中
一段。
所以每个返回正文的工具都有 maxChars 上限(默认 8000,硬上限 40000),
超出时明确报告 totalChars / hasMore / nextOffset,让调用方自己
决定要不要继续读。列表类工具只给元数据,从不夹带正文。
所有工具都标记 readOnlyHint: true——它们不会修改任何 Agent 的原始数据。
调用层:calls——为一场对话付了多少钱
node src/cli.js calls # 按 Agent 汇总
node src/cli.js calls --by model # 按模型
node src/cli.js calls --by session # 按会话,并报出与会话层的匹配率会话层记录"说了什么",调用层记录"为它花了多少"。同一场对话换个模型, 成本能差十倍,而正文完全一样——只看会话看不出这件事。
数据来自本地模型网关的调用日志(本机实测 ~/.config/magpie/usage.jsonl
15 天 2.5 万条)。网关记得比会话文件细:
字段 | 含义 |
| 本次请求的未缓存输入、输出 token |
| 提示词缓存命中与写入 |
| 首 token 延迟、整次耗时 |
| 供应商自己报的费用(缺价时不估) |
| 结果、重试次数 |
| 接会话层的钩子 |
本机实测:输入里 96.8% 是缓存读。没有这个口径,任何成本视图都是错的。
边界要一起报。 网关只看得见经过它的调用:本机 dsh 和 hermes 的
session 字段全空(不走网关),qoder 只有 76 场被网关看到而归档有 310 场。
所以 calls 同时给两个数——86 场会话可对上 · 17704/25868 次调用(68%)。
只报前者会让人以为覆盖率是 100%。这和技能使用率只统计显式调用是同一种诚实。
没装网关就优雅降级:返回 ready: false 和一句怎么启用的说明,不报错。
调用层是可选视图,不是依赖。
凭证扫描:secrets
node src/cli.js secrets # 扫全部归档
node src/cli.js secrets high # 只看高危以上
node src/cli.js secrets --agent zcode会话归档里常有真实凭证——AK 粘进对话、工具输出回显、部署脚本硬编码。 这些埋在几万条消息里,人自己看不见,但档案馆有全文,扫一遍就能找出来。
内置特征:阿里云 LTAI*、AWS AKIA*、GitHub gh[pousr]_* /
github_pat_*、sk-*(OpenAI 兼容)、sk-ant-*、私钥文件头、
ALIBABA_CLOUD_ACCESS_KEY_SECRET 赋值、通用的 api_key= / secret=。
输出一律掩码,而且掩码比命中更激进:命中只认已知前缀, 掩码会把上下文里任何 24 位以上的连续字母数字都打掉—— 否则这个工具自己就成了泄漏渠道(第一版就踩过这个坑)。
它只报告,不处置。 真正的修法是去对应平台停用/轮换凭证; 删聊天记录没用,备份和索引里还在。
视图层:摘要由模型做,事实不由模型做
这是整个产品的分界线:
事实层(确定性) 视图层(模型生成)
扫描 → 归一 → 时间线 ──────→ 摘要 / 交接 / 洞察
可复现、可审计、零成本 可重算、有版本、有成本模型只在「这些事实意味着什么」上工作,绝不参与「发生了什么」。 原因很实际:如果摘要烘进事实层,换模型就没法重算,档案馆会被锁死在 生成它的那一代模型上。原始正文存着,随时能用任何模型重新分析。
配置
node src/cli.js llm --key sk-xxx --model deepseek-chat \
--base-url https://api.deepseek.com
node src/cli.js llm # 看当前配置(密钥掩码)密钥写在 ~/.agent-archive/llm.json(权限 600),不进项目目录——
这个目录会进 git,密钥一旦进了历史就洗不掉。
两种请求形状覆盖绝大多数场景:openai(/v1/chat/completions,
DeepSeek / Kimi / Qwen / GLM / Ollama / LM Studio / 各种中转站都是这个)
和 anthropic(/v1/messages)。
环境变量优先于配置文件,方便临时切换:AGENT_ARCHIVE_LLM_API_KEY /
_MODEL / _BASE_URL / _PROVIDER。
生成摘要
node src/cli.js digest E:\softpi-shop # 只估算,不花钱
node src/cli.js digest E:\softpi-shop --run # 真的跑
node src/cli.js digest E:\softpi-shop --limit 20 --run --out digest.md默认只估算。 模型调用要花钱,用户得先看到量级再决定:
预计成本
会话 5 场(项目共 732 场)
已缓存 0 场(这部分不花钱)
输入 约 98.5k token四元组缓存
每份衍生数据的键是:
(kind, subject, source_hash, prompt_version, model)改任何一个都是未命中:会话又聊了几轮(hash 变)、提示词改好了 (version 变)、换了个模型(model 变)。这是视图层能被安全重算的 全部前提——没有它,三个月后你无法回答"哪些摘要是旧的""换模型后 该重算哪些"。
旧结果不会被删,所以能拿新旧模型做对比。
分层
一场会话十几万字、一个项目几百场会话,一次性塞进模型既超上下文 也烧钱。所以逐层收敛:
会话正文 ─┬─ 超长就切块 → 每块摘要 → 合并
└─ 不超长就直接摘要
↓ 会话摘要(缓存)
↓ 项目摘要(缓存)关于云端
不做。理由不是隐私,而是它加不了核心能力:知道每家 Agent 把会话 藏在哪里、怎么解开,100% 是本地的事,云端帮不上。云端能加的是多机 同步和团队共享,那是另一个客户群。
真要做的话顺序是:先同步衍生摘要,不同步原始会话——原始对话里有
API key、客户信息、内部 URL,上传即不可撤销。(这不是假设:
secrets 命令在本机 960 场会话里扫出了 14 条真实凭证。)
增量刷新:refresh
node src/cli.js refresh # 审计:哪些过期了、为什么、要花多少
node src/cli.js refresh --run # 真的重算
node src/cli.js refresh --project E:\softpi-shop四元组缓存本来就让"变了才重算"成立——换模型、改提示词、会话追加内容,
键自然 miss。所以 refresh 不负责重算逻辑,它解决的是另一个问题:
花钱之前看得见哪些过期了。
之前只能对整个项目盲跑 digest --run,跑完才知道花了多少。项目一多就
变成"不知道要不要跑、跑了也不知道值不值"。
过期原因分五类,处置方式完全不同:
原因 | 含义 | 该不该重算 |
| 四元组全对得上 | 不用 |
| 会话有新内容 | 应该 |
| 换了模型 | 想用新模型口径才重算 |
| 提示词改过 | 确认改进过才重算 |
| 本项目里有会话摘要过期 | 跟着重算 |
| 源会话已不在 | 可以清掉 |
一个刻意的边界:只刷新已经存在的摘要,不给没有摘要的项目新建。
否则一条 refresh 命令可能悄悄花掉一大笔,而用户以为只是"更新一下"。
审计本身一次模型都不调用——它只读本地缓存、重算哈希、比对四元组。
网页上的新鲜度提示
项目页和会话页的摘要标题旁会出现「可能已过期」的徽标,命中三种情况之一时:
生成它的模型不是当前配置的模型
提示词版本变过
生成之后这个项目新增了会话
但网页只做不花钱的检查。 有一项它查不了:会话内容变了但数量没变 (同一场又聊了几轮)。这一项要重读正文才能判断,全量 33 秒,不能放在 每次请求里。
所以提示里会明说「这一项查不了,需要准确结果请运行 refresh」。宁可承认
能力边界,也不给一个看起来确定、实际会漏的结论——那比没有提示更糟。
本地网页查看器
serve 起一个只读的本地服务,同一个进程同时提供页面和 JSON:
地址 | 内容 |
| 所有项目 |
| 某项目的时间线(默认最近 300 场, |
| 某场会话的正文 |
| 全文搜索,关键词高亮 |
| 能力面:各 Agent 的 MCP / 技能 / 记忆( |
| 项目清单 |
| 时间线 |
| 各 Agent 分布 |
| 搜索结果 |
| 项目摘要(如果有) |
| 会话正文, |
| 能力面数据, |
/env 用六个 tab 而不是一个长页:技能清单有 290 行、记忆有 21 处,
全堆一页会让人找不到重点。默认落在总览——那才是第一眼该看的。
tab 参数只认白名单里的值,别的一律回落到总览,不把用户输入拼进页面。
页面上的环境变量只有键名:值在 env/mcp.js 的 normalize() 里
就被丢掉了,这一层根本拿不到。
项目页顶部会展示已生成的摘要(折叠块),会话页顶部展示会话摘要。 摘要是视图层,和下面的时间线(事实层)在视觉上分开摆—— 点开摘要旁边永远有一句「模型生成的视图,核对细节请点开下面的会话」。
摘要用极简的 Markdown 渲染(标题/列表/加粗/行内代码,约 40 行), 先整体转义再套标签,所以摘要里即使混进 HTML 也不会执行。 为了这点需求引入一个 markdown 解析器不值得。
三个硬约束:
只监听 127.0.0.1,并校验 Host 头——否则恶意网页可以用 DNS rebinding 把域名指过来,跨域读走本机会话存档。
只处理 GET/HEAD,没有写操作。
会话正文一律 HTML 转义。这些内容是别的 Agent 写的,不能当可信 HTML。
页面里的折叠块(工具调用、推理)默认只传摘要,展开时才去
/api/session?...&message=N 取那一条。一场会话的推理加工具输出常有
几十万字,全量内联会把页面撑到几百 KB;按需加载后同一页面只有 118 KB。
前端不引任何外部脚本或样式,全靠一个内联 script 和几十行 CSS, 深色模式跟随系统设置。
cat:怎么把一场对话读得下去
一场两小时的对话可能有几千行工具输出。全部倒出来,人是不会读的。所以:
内容 | 默认 | 展开 |
用户与助手说的话 | 显示 | — |
工具调用 | 压成一行摘要 |
|
推理摘要 | 隐藏 |
|
注入内容(环境、指令、提醒) | 折成一行计数 |
|
工具调用摘要认得各家入参形状:Claude Code 的 {command, description}、
Codex 的 {command:["powershell.exe","-NoProfile",...]}、ZCode 的
{command, description}。取的是真正有信息量的那一段,不是整个 JSON。
--json 给其他 Agent 或脚本消费,结构是 {session, messages},
消息里是 {role, parts, ts, meta},parts 保持原顺序——
说了什么和干了什么是交织的,打乱会读错。
当前支持
Agent | 存储位置 | 备注 |
Claude Code |
| 斜杠命令需过滤 |
Codex |
| zstd;续接会话需合并 |
ZCode |
|
|
OpenCode |
| 与 ZCode 同源结构 |
Qoder |
| 新格式带时间戳和 cwd;旧格式只能靠 mtime |
DeepSeek Harness |
| 多帧 zstd;子代理跳过 |
Hermes |
| 多数无 cwd; |
Cursor |
| VSCode 键值库 |
Antigravity |
| 部分会话是加密 .pb |
Kiro |
| 正文在执行包里 |
Copilot Chat |
| 大量空会话 |
格式差异就是适配成本。 每家的坑都不一样,都在各自扫描器里写明了:
OpenCode 与 ZCode 同源(session/message/part 三表),但没有
semantics.origin,只能靠 part 类型过滤。108 场里 57 场是子代理、 3 场归档,真实顶层会话 48 场。Cursor 消息顺序不在 KV 表里,在
composerData.fullConversationHeadersOnly——按 key 排序会得到乱序对话。 项目路径要从workspaceProjectDir(c:\...\.cursor\projects\e-softpi-shop) 反解成e:/softpi-shop。Antigravity 有三套并存的数据。索引库(FTS5)覆盖 21 场,但只有 3 场有明文
transcript_full.jsonl,另 18 场是.pb(熵 8.0, gzip/deflate/brotli 全解不开)。元数据能搜到,正文读不到—— doctor 会如实报 1/3 而不是假装能读。Kiro 的会话文件里 assistant 正文全是占位符
"On it.",真回复在 执行包里(actions[].output.message),靠executionId关联。Copilot 的 16 个会话文件里 14 个
requests是空数组,文件存在 不代表有对话。Qoder 有两套存储。
cache/projects/.../conversation-history/是旧格式, 没有时间戳也没有 cwd,只能靠文件 mtime 和目录名近似;projects/<工作区>/<uuid>.jsonl是现在的主存储,每行带timestamp/cwd/sessionId,工具调用和结果都在文件里。 本机实测新格式 291 场、旧格式只有 19 场——只读旧格式会漏掉九成以上。 新格式里attachment(技能清单)、active-leaf(对话树指针)、file-history-snapshot(文件备份)是运行时事件,不是对话内容,读正文时按类型过滤。DeepSeek Harness 的会话文件默认是
.zstd,而且是多帧追加—— 每批事件单独一帧,不是一个大帧。Node 的zstdDecompressSync只解第一帧, 直接读会得到一行 header 就结束;扫描器按帧魔数切开逐帧解。 Windows 桌面版把DSH_HOME指到~/DeepSeekHarness,不是~/.dsh。 子代理也是独立 session 文件(header 带parentSession),和 Codex 子代理一样跳过。 正文里user/message/assistant/message/tool/call/tool/result是对话,*-chunks是流式增量、request/*是请求头,读进来只会重复。Hermes 的主存储是
state.db(SQLite),不是sessions/*.jsonl。 这是本项目最大的一次数据修正:早期只扫 jsonl,本机读到 34 场, 而库里实际有 219 场(211 场有消息,去掉子代理和空会话后 165 场)。 Hermes 官方文档也写明旧版*.jsonl不再写入或读取。 有 cwd 的只有 46/219——多数是接飞书渠道的聊天,不绑定目录, 但那 46 场必须归到项目,硬编码project: null会让它们永远进不了时间线。
sameProject() 会把"只记项目名"和"记完整路径"的条目并成同一个项目,
否则 softpi-shop 和 e:/softpi-shop 会在列表里并排两行。
环境层:env——这台机器上各 Agent 会什么
会话层回答"聊过什么",环境层回答"具备什么能力",调用层回答"花了多少"。
node src/cli.js env # 每个 Agent 一行
node src/cli.js env mcp # MCP 服务清单,跨 Agent 归并
node src/cli.js env skills # 技能清单,按内容去重
node src/cli.js env usage # 技能使用率:哪些真用过
node src/cli.js env memory # 记忆与指令文件
node src/cli.js env audit # 体检对应的 MCP 工具是 inventory(type 取 overview / mcp / skills / usage / memory / audit)。
这是给新 Agent 用的入口:它自己调一次 inventory,就知道这台机器上别的
Agent 配了什么、装了什么、记得什么,再自己决定要不要跟进——本工具不替它搬。
技能使用率:本机 290 个技能里用过 25 个
这是环境层唯一"两边数据都要"的功能,也是别的工具做不了的:要同时知道 装了哪些技能(技能目录)和用过哪些(会话里的工具调用)。
本机实测:290 个技能,用过 25 个,使用率 8.6%。调用最多的是
control-browser(32 次),其余大多一两次。265 个从没被调用过。
但"从没用过"是个上限,不是定论。 实测各家的做法不一样:
Agent | 怎么用技能 | 能否统计 |
ZCode / Claude Code | 调 | ✅ |
Codex | 没有 Skill 工具,直接 Read 那个 SKILL.md | ❌ |
Hermes | 走自己的机制,形态未统一 | ❌ |
所以只统计显式调用 Skill 工具的记录。Codex 那 269 场提到 "Skill" 的 会话里,绝大多数只是在正文里聊到这个词(讨论技能、贴 GitHub URL)。
这里有一个刻意的取舍:不把"读了 SKILL.md 就算用过"算进来。 那会显著抬高使用率,但也会让"从没用过"这个结论失去意义——而后者 才是这个功能的价值。宁可报一个偏保守的下限,并在输出里明说覆盖范围。
同名的技能可能是不同实现
env usage 里会看到三个 xlsx,那不是重复行。本机确实有三个内容哈希
不同的 xlsx 技能(ZCode 两个版本 + Hermes 一份)。它们各自匹配到同一批
使用记录,所以调用次数一样——输出里会标 [同名 3 份实现] 区分。
为什么单独一层,而不是并进会话表
会话是按项目组织的,换 Agent 就断了。MCP / 技能 / 记忆是可迁移的能力与偏好, 恰恰是新 Agent 最想要的东西,而且现在没有任何标准接口。两层共用同一个做法: 没有协议可依附,只能一家一个适配器,集中成注册表。
MCP:按启动指纹认,不按名字认
这是和参照项目最大的分歧。cc-switch 的 McpServer.id 就是原生配置里的键名,
所以同一个服务在不同 Agent 里名字不同时,它会被算成两个。
本机的真实数据(env mcp 实测):
● supabase 4 条注册 / 4 家
别名 adbsupabase / supabase
启动 local / npx一个 supabase 服务,4 条注册,2 个名字,3 种启动形态。如果按名字认,
"我到底配了几份、哪份是活的"就永远答不了。所以身份从 command + args 推:
剥掉
cmd /c、sh -c这类包装层(Windows 上极常见)找
npx拉起的包名 →pkg:@scope/name#npx找
node_modules/<包名>/→pkg:@scope/name#local其余取
dist/之后的脚本路径,或可执行文件名
服务身份 = 去掉安装方式后缀的包名,所以 npx 拉的和本地装的能归到一起;
而 #npx / #local 这个后缀单独保留,正好回答"两边装的到底是不是一个东西"。
supabase 就因此报出「安装形态不一致」——一边钉版本、一边每次拉最新。
各家格式差异(都在 src/env/mcp.js 里写明):
Agent | 位置 | 形状 |
Claude Code |
| 顶层 |
Codex |
|
|
ZCode |
| 多一层: |
Qoder |
|
|
Cursor |
|
|
OpenCode |
|
|
Hermes |
|
|
Gemini |
|
|
TOML 和 YAML 都是手写的最小解析器,只取 mcp_servers 那一段——
为一个字段引依赖不值得,而 Codex 的多行数组用单行正则会被吃掉半行
(第一版就是这么失败的)。
凭证:只给键名,值永不出这个模块
本机实测 3 份配置里有明文阿里云 AccessKey(Claude 全局、ZCode、Qoder)。
MCP 工具如果原样回传 env,等于把这些凭证直接送进某个模型的上下文。
所以规则是硬的:normalize() 只保留 Object.keys(env),值在那一行就被丢掉,
下游任何一层都拿不到。env audit 会提醒"这个服务有明文凭证",
但也只说键名,并明确写「值未读取、未传输」。
要看值请自己打开配置文件。这个工具自己不能成为泄漏渠道——
和 secrets 命令「掩码比命中更激进」是同一条教训。
技能:按内容哈希认,而且必须跟随符号链接
本机 375 处安装、290 个独立技能。同一个技能在五个目录里装着,按名字认会撑爆列表。
身份用 SKILL.md 的 SHA-256:名字会改(design-taste-frontend 和
-v1 是两代),同名不同内容也真的存在(同一插件的新旧版本就是)。
踩过的坑:第一版用 Dirent.isDirectory() 遍历,Qoder 的 3 个和 Kiro 的
28 个技能全部漏掉,而且静默漏——显示"0 个技能"而不是报错。原因是它们
全是指向 ~/.agents/skills/<name> 的符号链接,isDirectory() 对链接返回 false。
而这恰恰是最典型的跨 Agent 场景:用链接共享一份技能。修法是 lstat + stat,
真目录才参与环路保护(链接不记录已访问路径,否则会误判成环)。
现在 Kiro 显示 28 个、Qoder 32 个,lark-* 系列正确报成三家共享。
每条产出还带 symlinked 计数:全是链接说明只维护一份(好),
有真有假说明有人手工复制过(会漂)。
插件缓存里的历史版本不能算"多个技能"
ZCode 的插件缓存按 <插件>/<版本>/skills/<技能> 组织,升级后旧版本不删。
本机 122 个技能里面有 60 个是历史版本残留——docx 8 份(0.1.0 一路到
0.2.1)、pdf 8 份、computer-use 6 份。
那不是"装了 8 个不同技能",是同一个技能留了 8 个版本。所以这个根按
(插件, 技能名) 只保留版本号最高的一份。ZCode 从 122 落到 62,
全机从 300 落到 290——这才是真实数字。
版本判断只处理数字段(0.2.1 > 0.1.5);读不到版本号的目录原样保留,
宁可多报一个也不猜谁新谁旧。
性能:从 3.2 秒到 0.55 秒
第一版全量扫一次 3.2 秒,而 MCP 是常驻进程、每次 inventory 都重扫一遍。
三段优化,每一段都是先量再改:
改法 | 省下 | 为什么快 |
不陪每个真目录做 | 1.2 s | 环路只可能由符号链接造成,真目录没有回边。Windows 上每次 realpath 要 1-2ms,ZCode 缓存有 700 多个目录 |
一次读文件,正文和哈希共用同一个 buffer | ~0.15 s | 原先是 |
以 | ~1.0 s | 与会话层 |
汇总层还有 30 秒 TTL 的缓存,因为一个会话里 inventory 可能被连调几次
(先看 overview,再按类型细看)。缓存返回 structuredClone,不是同一引用——
MCP 工具会拿列表去 filter/map,一旦改到缓存对象,后面所有调用都读到污染数据,
症状是"技能数莫名多一个",极难查。
记忆:只报出处,绝不合并
这是最容易做错的一层。用户的 Hermes USER.md 和 Codex MEMORY.md
完全可能写着两条相反的偏好。合并成"你的记忆"是在说谎,而且新 Agent
读到一句被平滑过的结论,比读到两条带出处的矛盾更危险。
所以产出只有三样:在哪、什么格式、有多大。正文不在这层返回。
指令和记忆分开计数,因为语义不同:
instruction — 每轮注入的行为约定(
AGENTS.md/SOUL.md/GEMINI.md)memory — 跨会话积累的事实(
MEMORY.md/ 分片目录 / SQLite 库)
本机 21 处、322 条,覆盖 3 种格式:
Agent | 形态 |
Codex |
|
Hermes |
|
Qoder |
|
ZCode | 项目级: |
ZCode 的项目级记忆是这一层最有价值的部分——它按项目分开,正好能对上
会话层的项目维度。但目录名会混进临时工作区(1792567885219-...、default-...、
draft-...),这些如果当成项目名,会让"这个项目有哪些记忆"里全是噪音。
所以先判内部前缀、再剥 16 位哈希、最后过一道形状检查;认不出项目的
归到 unattributed,不塞进项目列表冒充项目。
体检 env audit
和会话层的 doctor 同一个思路:报数字,不报感觉。检查的都是
"看起来正常、实际会让人做错判断"的东西:
检查 | 为什么值得报 |
安装形态不一致 | npx 每次拉最新、本地安装钉版本,两边行为可能不同 |
同名不同别名 | 不是错误,但会让人以为配了两份 |
明文凭证 | 会被每个读到该配置的 Agent 拿到 |
记忆格式分散 | 换 Agent 后旧记忆不会自动跟过去 |
有数据但无配置能力 | 可能没配过,也可能这家配置放在没适配的位置 |
技能只在共享目录 | 共享目录本身不生效,要有 Agent 目录里的链接或副本才读得到 |
采集体检:doctor
stats 报的是产量(扫了多少文件、识别出多少会话),doctor 报的是质量:
node src/cli.js doctor它检查三件事,任何一项坏掉都会让下游静默失真:
支柱 | 坏了会怎样 |
时间 | 时间线排序错乱,用户看不出问题但结论会错 |
项目 | 会话进不了任何项目,等于对用户隐身 |
标题 | 列表看起来全是垃圾 |
外加:正文抽样可读性、重复 id、以及本机有哪些 Agent 目录存在但还没接入。
指标设计上刻意区分了「没有」和「丢了」:
无项目:该 Agent 多数不记录工作目录(Hermes 165 场里只有 37 场归到了项目),不算缺陷
临时路径:路径存在但属于 Agent 的临时落脚点 (
.zcode/workspace/<uuid>、Documents/Codex/<日期>),被isProjectPath主动过滤,是设计如此占位标题:桌面端自动触发的会话没有真人输入,标题退化为来源名
(Codex Desktop),这是合理降级
所以"待办"里只列真的该修的东西,不会长期挂着已经查清的正常项。
跨平台
各 Agent 的存储位置在不同系统上遵循不同约定。src/platform.js 负责应用数据类路径,
但大多数 Agent 的家目录点目录三平台一致,真正需要单独处理的是少数几个。
类型 | Windows | macOS | Linux |
VS Code 系应用数据 |
|
|
|
XDG 数据目录 |
| 通常是 |
|
家目录点目录 |
| 同左 | 同左 |
受影响的是 Cursor / Kiro / Copilot Chat / OpenCode 四家。
macOS 上最容易搞错的三件事
这三条都是查了官方文档和源码才确认的,和直觉不一样:
一、OpenCode 在 macOS 上不用 Application Support。
它自己的 global-roots.ts 里写死 XDG_DATA_HOME || ~/.local/share,
没有 process.platform === 'darwin' 分支。所以 macOS 上也是
~/.local/share/opencode/opencode.db。看到 ~/.local/share 别以为是 Linux 专用。
(有意思的是:OpenCode 的管理员级配置确实在
/Library/Application Support/opencode/——有 ~ 和没 ~ 是两个完全不同的位置。)
二、~/Library/Application Support/Claude 是桌面应用,不是 Claude Code。
Claude Code 的 CLI 三平台都用 ~/.claude/,官方文档从没给过 Application Support 路径。
两者是不同产品、不同会话格式。
三、Hermes 在 Windows 上官方位置是 %LOCALAPPDATA%/hermes,mac/Linux 是 ~/.hermes。
这是少数真的按平台分叉的一家(源码里 sys.platform == "win32" 判断)。
环境变量是比平台推断更可靠的一层
这是跨平台最值得投入的部分:每个 OS 的默认路径都可能不同,但环境变量是
各家自己定义的、跨平台语义一致的。用户把 CLAUDE_CONFIG_DIR 指到外置盘,
在哪个系统上都该找到。
已支持的覆盖变量(逗号分隔多值,支持 ~ 展开):
Agent | 变量 |
Claude Code |
|
Codex |
|
OpenCode |
|
Qoder |
|
Hermes |
|
Cursor |
|
Kiro |
|
Antigravity |
|
Gemini |
|
Gemini CLI 有一个 macOS 专属的改道:在 sandbox-exec 下写 ~/.gemini
会被 Seatbelt 拦,运行时目录(含会话)改到 ~/.cache/.gemini。
源码注释原文如此。所以两个位置都要探测。
实现上不用 process.platform 硬分支选路径,而是给出候选列表、
按顺序挑第一个真实存在的。这样换机、跨平台拷贝备份、或者某个应用
在某平台上没按约定写盘时,都能命中而不是静默失败。
isProjectPath 的临时目录名单同时涵盖 Windows(Desktop/Documents/
AppData)和 macOS(Library/Applications/Movies…)的系统目录;
assertUnder 的大小写比较跟随文件系统——Windows/macOS 忽略大小写,
Linux 严格区分(在 Linux 上忽略大小写是个真实的绕过口子)。
多根场景用 assertUnderAny:路径落在任意一个候选根下就合法
(CLAUDE_CONFIG_DIR 指到外置盘、~/.config/claude 的历史遗留都要能读)。
macOS 上还要注意的几处细节
/var是/private/var的符号链接,/tmp、/var/folders/...同理。 路径相等比较必须realpath两侧,否则同一条路径会判不相等。APFS 默认大小写不敏感,但可以挂载 case-sensitive 卷。 所以去重前先
realpath,不要依赖"大小写不敏感"当去重手段。 (claude-code-log就把/private/var/folders/硬编码进临时路径过滤表—— 这是路径比较必须 realpath 的典型证据。)沙盒化的 app 会把
$HOME重定向到~/Library/Containers/<bundle-id>/Data/。Desktop 类应用值得两个位置都探。在后台服务(launchd)里跑时
HOME可能不是用户家目录。 Hermes 源码专门用pwd.getpwuid(os.getuid()).pw_dir兜底。
验证状态:Windows 上 10 家 Agent 全部实测通过(1131 场会话, 解析失败 0、重复 id 0)。macOS 与 Linux 仍未在真机跑过—— 路径约定已按官方文档和源码核对过,但这个仓库里没有第二台机器可用。
在 Mac / Linux 上首次使用,跑这一个就够:
node research/probes/mac-check.mjs它是单文件、零依赖的,不需要 clone 整个仓库——把文件复制过去
node mac-check.mjs就能跑。会输出一份报告到/tmp/agent-archive-mac-check.txt,直接贴回来即可。它做路径层面的自检:11 个 Agent 的候选路径哪些真实存在、 每个位置有多少会话文件、哪些环境变量被读进来。全程只读。
想跑更完整的验证(真的解析会话、验证正文可读性):
node research/probes/audit-paths.mjs # 秒级:路径假设对不对 node research/probes/verify-mac.mjs # 分钟级:实际读到多少会话 node research/probes/run-all.mjs # 全部七个回归套件
Cursor 的路径反解是个特例
Cursor 的项目目录名是「绝对路径把所有分隔符换成 -」——
/Users/alice/my-repo → Users-alice-my-repo。这个编码有损:
Users-a-my-app 可能是 /Users/a/my-app,也可能是 /Users/a/my/app。
所以取项目路径有明确的优先级:
workspaceStorage/<hash>/workspace.json里的真实路径(权威来源)目录名反解,且只在能确定形状时(Windows 盘符 /
Users-/home-前缀)其余情况返回
null
第 3 条是关键:宁可留空,也不给一个看起来合理、实际错误的路径。 错的路径会静默污染项目归集,比缺数据难发现得多。
为何只有 10 家
本机装了 13 个 Agent,其中三个确认读不了,doctor 会把它们单独列成
「已调研、确认读不了」而不是待办:
Agent | 结论 | 证据 |
Trae | 会话库加密 |
|
Windsurf | 会话文件加密 |
|
Cline | 本机无数据 | 所有库 0 行, |
不去破解加密。 那既不可靠,也会产出错误内容。doctor 把「还没做」
和「做不了」分开报,待办里只留前者——否则清单永远挂着几个不可能完成项。
Antigravity 是中间态:21 场里 3 场有明文 transcript_full.jsonl,
另 18 场是加密 .pb。所以它的元数据能搜、正文只能读 3 场,
readMessages 会直接抛错说明原因,doctor 的正文抽样如实报 1/3。
设计要点
统一模型(src/model.js):所有 scanner 产出
{agent, id, project, title, start, end, source, resume}。
下游只认这个形状,不关心来源格式。
项目路径归一化:Windows 上同一个项目会出现 e:\softpi-shop、
E:\softpi-shop、E:/softpi-shop 三种写法,来自不同 Agent。
比较用小写规范键,展示保留原样。
路径解释在查询层:CLI 只把用户敲的文本原样传下去,
src/query.js 的 absProject() 负责归一成绝对路径。
项目身份属于查询层,不属于展示层。
缓存(src/registry.js):以文件路径为键,(mtime, size) 都没变就复用
上次的解析结果。这与 CC Switch 的 FileParseCache 是同一策略——绝大多数
会话在两次扫描之间没有变化,而重新解析一个 1.8 MB 的 JSONL 要几十毫秒。
实测 2042 个文件第二次扫描命中 2041 个。
噪音过滤:各 Agent 会把斜杠命令、环境上下文、AGENTS.md 指令块、 IDE 状态当成用户消息写进会话。这些会污染标题,必须在抽取"第一条真实 用户输入"时剥掉。没有用户输入的会话(自动化、子代理触发)退到助手 第一句话,再退到触发来源。
只读:所有 scanner 只读,绝不修改 Agent 的原始会话文件。ZCode 的 SQLite 以 readOnly 打开,用完立即关闭句柄(Windows 上不关会锁文件)。
越界校验:正文读取的路径来自缓存里记的 source 字段。如果只信它,
一份被改过的缓存就能让本工具读任意文件。所以每次读之前都要求路径落在
该 Agent 自己的根目录之下(messages.assertUnder),与 CC Switch 的
resolve_under_roots 同一个思路。
新增一个 Agent
写一个 src/scanners/<name>.js,导出:
export const name = 'xxx';
export function discover() { /* 返回文件路径数组 */ }
export function parse(file) { /* 一个文件一个会话 */ }
// 或者
export function parseAll(file) { /* 一个文件多个会话,返回数组 */ }
// 想看正文再加一个(cat 用)
export function readMessages(session) { /* 返回统一消息数组 */ }然后在 src/registry.js 的 scanners 数组里加一行。
这不是设计上的偷懒,而是这类工具的唯一组织方式:没有 Agent 提供 "读取我的会话"的标准接口,只能一家一个适配器,把它集中成注册表 才可维护。
下一步(还没做)
在 Mac / Linux 上跑一次
research/probes/verify-mac.mjs,按输出修正平台差异批量刷新多项目:现在
refresh一次处理全部,但缺一个"只刷最近活跃项目"的过滤
只有等这个档案馆在你手上真正用起来之后,才值得往上加蒸馏和交接摘要。
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Read-only MCP server exposing a user ORANO library to their own AI agent.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
Related MCP Servers
- AlicenseAqualityBmaintenanceA read-only MCP server that exposes local coding-agent session logs as three tools for introspection of recent work, debugging tool failures, and tracking token usage and estimated cost without parsing log files.3MIT
- AlicenseNot gradedqualityBmaintenanceA local MCP server that lets AI agents publish markdown/HTML to a browser viewer with session management, revision diffs, and live updates via a single tool call.2MIT
- FlicenseBqualityCmaintenanceRead-only MCP server for local Warp/Oz agent data, exposing memories, notebooks, conversations, queries, project rules, and cross-agent handoffs.13-
- FlicenseBqualityCmaintenanceProvides a minimal MCP server with safe tools for AI assistants to search project files, read files, search local documentation, and run pre-approved checks within a single project directory.4-